mqtt-plus 0.9.2 → 0.9.4

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.
Files changed (46) hide show
  1. package/README.md +177 -45
  2. package/dst-stage1/mqtt-plus-api.d.ts +26 -0
  3. package/dst-stage1/mqtt-plus-api.js +24 -0
  4. package/dst-stage1/mqtt-plus-base.d.ts +19 -0
  5. package/dst-stage1/mqtt-plus-base.js +114 -0
  6. package/dst-stage1/mqtt-plus-codec.d.ts +6 -0
  7. package/dst-stage1/mqtt-plus-codec.js +11 -0
  8. package/dst-stage1/mqtt-plus-event.d.ts +18 -0
  9. package/dst-stage1/mqtt-plus-event.js +103 -0
  10. package/dst-stage1/mqtt-plus-info.d.ts +15 -0
  11. package/dst-stage1/mqtt-plus-info.js +24 -0
  12. package/dst-stage1/mqtt-plus-msg.d.ts +31 -10
  13. package/dst-stage1/mqtt-plus-msg.js +92 -29
  14. package/dst-stage1/mqtt-plus-options.d.ts +20 -0
  15. package/dst-stage1/mqtt-plus-options.js +46 -0
  16. package/dst-stage1/mqtt-plus-receiver.d.ts +12 -0
  17. package/dst-stage1/mqtt-plus-receiver.js +42 -0
  18. package/dst-stage1/mqtt-plus-resource.d.ts +19 -0
  19. package/dst-stage1/mqtt-plus-resource.js +194 -0
  20. package/dst-stage1/mqtt-plus-service.d.ts +22 -0
  21. package/dst-stage1/mqtt-plus-service.js +220 -0
  22. package/dst-stage1/mqtt-plus-stream.d.ts +20 -0
  23. package/dst-stage1/mqtt-plus-stream.js +152 -0
  24. package/dst-stage1/mqtt-plus.d.ts +5 -72
  25. package/dst-stage1/mqtt-plus.js +4 -384
  26. package/dst-stage2/mqtt-plus.cjs.js +494 -171
  27. package/dst-stage2/mqtt-plus.esm.js +492 -170
  28. package/dst-stage2/mqtt-plus.umd.js +14 -14
  29. package/etc/eslint.mts +3 -2
  30. package/etc/stx.conf +7 -1
  31. package/etc/tsc.tsbuildinfo +1 -1
  32. package/package.json +17 -5
  33. package/src/mqtt-plus-api.ts +66 -0
  34. package/src/mqtt-plus-base.ts +155 -0
  35. package/src/mqtt-plus-codec.ts +20 -0
  36. package/src/mqtt-plus-event.ts +164 -0
  37. package/src/mqtt-plus-info.ts +43 -0
  38. package/src/mqtt-plus-msg.ts +132 -38
  39. package/src/mqtt-plus-options.ts +70 -0
  40. package/src/mqtt-plus-receiver.ts +52 -0
  41. package/src/mqtt-plus-resource.ts +277 -0
  42. package/src/mqtt-plus-service.ts +292 -0
  43. package/src/mqtt-plus-stream.ts +222 -0
  44. package/src/mqtt-plus.ts +6 -575
  45. package/tst/mqtt-plus.spec.ts +238 -0
  46. package/tst/tsc.json +30 -0
package/README.md CHANGED
@@ -22,7 +22,7 @@ $ npm install mqtt mqtt-plus
22
22
  About
23
23
  -----
24
24
 
25
- This is **MQTT+**, an addon API for the excellent
25
+ This is **MQTT+**, an companion addon API for the excellent
26
26
  [MQTT](http://mqtt.org/) client TypeScript/JavaScript API
27
27
  [MQTT.js](https://www.npmjs.com/package/mqtt), provoding additional
28
28
  communication patterns with optional type safety:
@@ -40,6 +40,20 @@ communication patterns with optional type safety:
40
40
  pattern allows to direct the event to particular subscribers and
41
41
  provides optional information about the sender to subscribers.
42
42
 
43
+ - **Stream Transfer**:
44
+
45
+ Stream Transfer is a *uni-directional* communication pattern.
46
+ A stream is the combination of a stream name, a `Readable` stream object and optionally zero or more arguments.
47
+ You *attach* to a stream.
48
+ When a stream is *transferred*, either a single particular attacher (in case of
49
+ a directed stream transfer) or all attachers are called and receive the
50
+ arguments as extra information. The `Readable` stream is available via
51
+ `info.stream` in the attacher callback.
52
+
53
+ In contrast to the regular MQTT message publish/subscribe, this
54
+ pattern allows to transfer arbitrary amounts of arbitrary data by
55
+ chunking the data via a stream.
56
+
43
57
  - **Service Call**:
44
58
 
45
59
  Service Call is a *bi-directional* communication pattern.
@@ -55,26 +69,48 @@ communication patterns with optional type safety:
55
69
  Procedure Call](https://en.wikipedia.org/wiki/Remote_procedure_call)
56
70
  (RPC) style communication.
57
71
 
72
+ - **Resource Transfer**:
73
+
74
+ Resource Transfer is a *bi-directional* communication pattern.
75
+ A Resource is the combination of a resource name and optionally zero or more arguments.
76
+ You *provision* for a resource transfer.
77
+ When a resource is *fetched* or *pushed*, a single particular provisioner (in case
78
+ of a directed resource transfer) or one arbitrary provisioner is called and
79
+ sends or receives the resource and its arguments.
80
+
58
81
  > [!Note]
59
82
  > **MQTT+** is similar to and derived from
60
83
  > [MQTT-JSON-RPC](https://github.com/rse/mqtt-json-rpc) of the same
61
84
  > author, but instead of just JSON, MQTT+ encodes packets as JSON
62
85
  > or CBOR (default), uses an own packet format (allowing sender and
63
- > receiver information) and uses shorter NanoIDs instead of longer UUIDs
64
- > for identification of sender, receiver and requests.
86
+ > receiver information), uses shorter NanoIDs instead of longer UUIDs
87
+ > for identification of sender, receiver and requests, and has
88
+ > no support for stream transfers.
65
89
 
66
90
  Usage
67
91
  -----
68
92
 
69
93
  ### API:
70
94
 
95
+ The API type defines the available endpoints. Use the marker types
96
+ `Event<T>`, `Stream<T>`, and `Service<T>` to declare the communication
97
+ pattern of each endpoint:
98
+
71
99
  ```ts
100
+ import type * as MQTTpt from "mqtt-plus"
101
+
72
102
  export type API = {
73
- "example/sample": (a1: string, a2: boolean) => void
74
- "example/hello": (a1: string, a2: number) => string
103
+ "example/sample": MQTTpt.Event<(a1: string, a2: boolean) => void> /* event */
104
+ "example/upload": MQTTpt.Stream<(a1: string, a2: number) => void> /* stream */
105
+ "example/hello": MQTTpt.Service<(a1: string, a2: number) => string> /* service */
75
106
  }
76
107
  ```
77
108
 
109
+ The marker types ensure that `subscribe()` and `emit()` only accept
110
+ `Event<T>` endpoints, `attach()` and `transfer()` only accept
111
+ `Stream<T>` endpoints, and `register()` and `call()` only accept
112
+ `Service<T>` endpoints.
113
+
78
114
  ### Server:
79
115
 
80
116
  ```ts
@@ -125,15 +161,12 @@ The **MQTT+** API provides the following methods:
125
161
  constructor<API>(
126
162
  mqtt: MqttClient,
127
163
  options?: {
128
- id: string
129
- codec: "cbor" | "json"
130
- timeout: number
131
- topicEventNoticeMake: (topic: string) => TopicMatching | null
132
- topicServiceRequestMake: (topic: string) => TopicMatching | null
133
- topicServiceResponseMake: (topic: string) => TopicMatching | null
134
- topicEventNoticeMatch: { name: string, clientId?: string }
135
- topicServiceRequestMatch: { name: string, clientId?: string }
136
- topicServiceResponseMatch: { name: string, clientId?: string }
164
+ id: string
165
+ codec: "cbor" | "json"
166
+ timeout: number
167
+ chunkSize: number
168
+ topicMake: (name: string, operation: string, peerId?: string) => string
169
+ topicMatch: (topic: string) => { name: string, operation: string, peerId?: string } | null
137
170
  }
138
171
  )
139
172
 
@@ -149,18 +182,13 @@ The **MQTT+** API provides the following methods:
149
182
  - `id`: Custom MQTT peer identifier (default: auto-generated NanoID).
150
183
  - `codec`: Encoding format (default: `cbor`).
151
184
  - `timeout`: Communication timeout in milliseconds (default: `10000`).
152
- - `topicEventNoticeMake`: Custom topic generation for event notices.
153
- (default: `` (name, clientId) => clientId ? `${name}/event-notice/${clientId}` : `${name}/event-notice` ``)
154
- - `topicServiceRequestMake`: Custom topic generation for service requests.
155
- (default: `` (name, clientId) => clientId ? `${name}/service-request/${clientId}` : `${name}/service-request` ``)
156
- - `topicServiceResponseMake`): Custom topic generation for service responses.
157
- (default: `` (name, clientId) => clientId ? `${name}/service-response/${clientId}` : `${name}/service-response` ``)
158
- - `topicEventNoticeMatch`: Custom topic matching for event notices.
159
- (default: `` (topic) => { const m = topic.match(/^(.+?)\/event-notice(?:\/(.+))?$/); return m ? { name: m[1], clientId: m[2] } : null } ``)
160
- - `topicServiceRequestMatch`: Custom topic matching for service requests.
161
- (default: `` (topic) => { const m = topic.match(/^(.+?)\/service-request(?:\/(.+))?$/); return m ? { name: m[1], clientId: m[2] } : null } ``)
162
- - `topicServiceResponseMatch`: Custom topic matching for service responses.
163
- (default: `` (topic) => { const m = topic.match(/^(.+?)\/service-response\/(.+)$/); return m ? { name: m[1], clientId: m[2] } : null } ``)
185
+ - `chunkSize`: Chunk size in bytes for stream transfers (default: `16384`).
186
+ - `topicMake`: Custom topic generation function.
187
+ The `operation` parameter is one of: `event-notice`, `stream-chunk`, `service-call`, `resource-transfer`.
188
+ (default: `` (name, operation, peerId) => `${name}/${operation}` + (peerId ? `/${peerId}` : "/any") ``)
189
+ - `topicMatch`: Custom topic matching function.
190
+ Returns `{ name, operation, peerId? }` or `null` if no match. The `peerId` is `undefined` for broadcast topics (ending with `/any`).
191
+ (default: `` (topic) => { const m = topic.match(/^(.+)\/([^/]+)\/([^/]+)$/); return m ? { name: m[1], operation: m[2], peerId: m[3] === "any" ? undefined : m[3] } : null } ``)
164
192
 
165
193
  - **Event Subscription**:<br/>
166
194
 
@@ -168,7 +196,10 @@ The **MQTT+** API provides the following methods:
168
196
  subscribe(
169
197
  event: string,
170
198
  options?: MQTT::IClientSubscribeOptions
171
- callback: (...params: any[], info: { sender: string, receiver?: string }) => void
199
+ callback: (
200
+ ...params: any[],
201
+ info: { sender: string, receiver?: string }
202
+ ) => void
172
203
  ): Promise<Subscription>
173
204
 
174
205
  Subscribe to an event.
@@ -178,17 +209,44 @@ The **MQTT+** API provides the following methods:
178
209
  There is no return value of `callback`.
179
210
 
180
211
  Internally, on the MQTT broker, the topics generated by
181
- `topicEventNoticeMake()` (default: `${event}/event-notice` and
182
- `${event}/event-notice/${clientId}`) are subscribed. Returns a
212
+ `topicMake(event, "event-notice")` (default: `${event}/event-notice/any` and
213
+ `${event}/event-notice/${peerId}`) are subscribed. Returns a
183
214
  `Subscription` object with an `unsubscribe()` method.
184
215
 
216
+ - **Stream Attachment**:<br/>
217
+
218
+ /* (simplified TypeScript API method signature) */
219
+ attach(
220
+ stream: string,
221
+ options?: MQTT::IClientSubscribeOptions
222
+ callback: (
223
+ ...params: any[],
224
+ info: { sender: string, receiver?: string, stream: stream.Readable }
225
+ ) => void
226
+ ): Promise<Attachment>
227
+
228
+ Attach to (observe) a stream.
229
+ The `stream` has to be a valid MQTT topic name.
230
+ The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
231
+ The `callback` is called with the `params` passed to a remote `transfer()`.
232
+ The `info.stream` provides a Node.js `Readable` stream for consuming the transferred data.
233
+ There is no return value of `callback`.
234
+
235
+ Internally, on the MQTT broker, the topics generated by
236
+ `topicMake(stream, "stream-chunk")` (default: `${stream}/stream-chunk/any` and
237
+ `${stream}/stream-chunk/${peerId}`) are subscribed. Returns an
238
+ `Attachment` object with an `unattach()` method.
239
+
185
240
  - **Service Registration**:<br/>
186
241
 
187
242
  /* (simplified TypeScript API method signature) */
188
243
  register(
189
244
  service: string,
190
245
  options?: MQTT::IClientSubscribeOptions
191
- callback: (...params: any[], info: { sender: string, receiver?: string }) => any
246
+ callback: (
247
+ ...params: any[],
248
+ info: { sender: string, receiver?: string }
249
+ ) => any
192
250
  ): Promise<Registration>
193
251
 
194
252
  Register a service.
@@ -198,10 +256,33 @@ The **MQTT+** API provides the following methods:
198
256
  The return value of `callback` will resolve the `Promise` returned by the remote `call()`.
199
257
 
200
258
  Internally, on the MQTT broker, the topics by
201
- `topicServiceRequestMake()` (default: `${service}/service-request` and
202
- `${service}/service-request/${clientId}`) are subscribed. Returns a
259
+ `topicMake(service, "service-call")` (default: `${service}/service-call/any` and
260
+ `${service}/service-call/${peerId}`) are subscribed. Returns a
203
261
  `Registration` object with an `unregister()` method.
204
262
 
263
+ - **Resource Provisioning**:<br/>
264
+
265
+ /* (simplified TypeScript API method signature) */
266
+ provision(
267
+ resource: string,
268
+ options?: MQTT::IClientSubscribeOptions
269
+ callback: (
270
+ ...params: any[],
271
+ info: { sender: string, receiver?: string }
272
+ ) => any
273
+ ): Promise<Provisioning>
274
+
275
+ Provision a resource.
276
+ The `resource` has to be a valid MQTT topic name.
277
+ The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
278
+ The `callback` is called with the `params` passed to a remote `call()`.
279
+ The return value of `callback` will resolve the `Promise` returned by the remote `fetch()` call.
280
+
281
+ Internally, on the MQTT broker, the topics by
282
+ `topicMake(resource, "resource-transfer")` (default: `${resource}/resource-transfer/any` and
283
+ `${resource}/resource-transfer/${peerId}`) are subscribed. Returns a
284
+ `Provisioning` object with an `unprovision()` method.
285
+
205
286
  - **Event Emission**:<br/>
206
287
 
207
288
  /* (simplified TypeScript API method signature) */
@@ -219,8 +300,35 @@ The **MQTT+** API provides the following methods:
219
300
  The remote `subscribe()` `callback` is called with `params` and its
220
301
  return value is silently ignored.
221
302
 
222
- Internally, publishes to the MQTT topic by `topicEventNoticeMake(event, clientId)`
223
- (default: `${event}/event-notice` or `${event}/event-notice/${clientId}`).
303
+ Internally, publishes to the MQTT topic by `topicMake(event, "event-notice", peerId)`
304
+ (default: `${event}/event-notice/any` or `${event}/event-notice/${peerId}`).
305
+
306
+ - **Stream Transfer**:<br/>
307
+
308
+ /* (simplified TypeScript API method signature) */
309
+ transfer(
310
+ stream: string,
311
+ readable: stream.Readable,
312
+ receiver?: Receiver,
313
+ options?: MQTT::IClientPublishOptions,
314
+ ...params: any[]
315
+ ): Promise<void>
316
+
317
+ Transfer a stream to all attachers or a specific attacher.
318
+ The `readable` is a Node.js `Readable` stream providing the data to transfer.
319
+ The optional `receiver` directs the transfer to a specific attacher only.
320
+ The optional `options` allows setting MQTT.js `publish()` options like `qos` or `retain`.
321
+
322
+ The data is read from `readable` in chunks (default: 16KB,
323
+ configurable via `chunkSize` option) and sent over MQTT until the
324
+ stream is closed. The returned `Promise` resolves when the entire
325
+ stream has been transferred.
326
+
327
+ The remote `attach()` `callback` is called with `params` and an `info` object
328
+ containing a `stream.Readable` for consuming the transferred data.
329
+
330
+ Internally, publishes to the MQTT topic by `topicMake(stream, "stream-chunk", peerId)`
331
+ (default: `${stream}/stream-chunk/any` or `${stream}/stream-chunk/${peerId}`).
224
332
 
225
333
  - **Service Call**:<br/>
226
334
 
@@ -240,8 +348,31 @@ The **MQTT+** API provides the following methods:
240
348
  return value resolves the returned `Promise`. If the remote `callback`
241
349
  throws an exception, this rejects the returned `Promise`.
242
350
 
243
- Internally, on the MQTT broker, the topic by `topicServiceResponseMake(service, clientId)`
244
- (default: `${service}/service-response/${clientId}`) is temporarily subscribed
351
+ Internally, on the MQTT broker, the topic by `topicMake(service, "service-call", peerId)`
352
+ (default: `${service}/service-call/${peerId}`) is temporarily subscribed
353
+ for receiving the response.
354
+
355
+ - **Resource Transfer**:<br/>
356
+
357
+ /* (simplified TypeScript API method signature) */
358
+ fetch(
359
+ blob: string,
360
+ receiver?: Receiver,
361
+ options?: MQTT::IClientSubscribeOptions,
362
+ ...params: any[]
363
+ ): Promise<Buffer>
364
+
365
+ Fetches a resource from any resource provisioner or from a specific provisioner.
366
+ The optional `receiver` directs the call to a specific provisioner only.
367
+ The optional `options` allows setting MQTT.js `publish()` options like `qos` or `retain`.
368
+
369
+ The remote `provision()` `callback` is called with `params` and its
370
+ return value resolves the returned `Promise`. If the remote `callback`
371
+ throws an exception, this rejects the returned `Promise`.
372
+
373
+ Internally, on the MQTT broker, the topic by
374
+ `topicMake(resource, "resource-transfer", peerId)` (default:
375
+ `${resource}/resource-transfer/${peerId}`) is temporarily subscribed
245
376
  for receiving the response.
246
377
 
247
378
  - **Receiver Wrapping**:<br/>
@@ -276,7 +407,7 @@ mqttp.call("example/hello", "world", 42).then((result) => {
276
407
  ```
277
408
 
278
409
  ...the following message is sent to the permanent MQTT topic
279
- `example/hello/service-request` (the shown NanoIDs are just pseudo
410
+ `example/hello/service-call/any` (the shown NanoIDs are just pseudo
280
411
  ones):
281
412
 
282
413
  ```json
@@ -299,7 +430,7 @@ mqttp.register("example/hello", (a1, a2) => {
299
430
  ...and then its result, in the above `mqttp.call()` example `"world:42"`, is then
300
431
  sent back as the following success response
301
432
  message to the temporary (client-specific) MQTT topic
302
- `example/hello/service-response/2IBMSk0NPnrz1AeTERoea`:
433
+ `example/hello/service-call/2IBMSk0NPnrz1AeTERoea`:
303
434
 
304
435
  ```json
305
436
  {
@@ -382,9 +513,10 @@ it in action and tracing its communication (the typing of the `MQTTp`
382
513
  class with `API` is optional, but strongly suggested):
383
514
 
384
515
  ```ts
385
- import Mosquitto from "mosquitto"
386
- import MQTT from "mqtt"
387
- import MQTTp from "mqtt-plus"
516
+ import Mosquitto from "mosquitto"
517
+ import MQTT from "mqtt"
518
+ import MQTTp from "mqtt-plus"
519
+ import type * as MQTTpt from "mqtt-plus"
388
520
 
389
521
  const mosquitto = new Mosquitto()
390
522
  await mosquitto.start()
@@ -396,8 +528,8 @@ const mqtt = MQTT.connect("mqtt://127.0.0.1:1883", {
396
528
  })
397
529
 
398
530
  type API = {
399
- "example/sample": (a1: string, a2: number) => void
400
- "example/hello": (a1: string, a2: number) => string
531
+ "example/sample": MQTTpt.Event<(a1: string, a2: number) => void>
532
+ "example/hello": MQTTpt.Service<(a1: string, a2: number) => string>
401
533
  }
402
534
 
403
535
  const mqttp = new MQTTp<API>(mqtt, { codec: "json" })
@@ -431,9 +563,9 @@ The output will be:
431
563
  ```
432
564
  $ node sample.ts
433
565
  CONNECT
434
- RECEIVED example/hello/service-request {"id":"vwLzfQDu2uEeOdOfIlT42","sender":"2IBMSk0NPnrz1AeTERoea","service":"example/hello","params":["world",42]}
566
+ RECEIVED example/hello/service-call/any {"id":"vwLzfQDu2uEeOdOfIlT42","sender":"2IBMSk0NPnrz1AeTERoea","service":"example/hello","params":["world",42]}
435
567
  example/hello: request: world 42 undefined
436
- RECEIVED example/hello/service-response/2IBMSk0NPnrz1AeTERoea {"id":"vwLzfQDu2uEeOdOfIlT42","sender":"2IBMSk0NPnrz1AeTERoea","receiver":"2IBMSk0NPnrz1AeTERoea","result":"world:42"}
568
+ RECEIVED example/hello/service-call/2IBMSk0NPnrz1AeTERoea {"id":"vwLzfQDu2uEeOdOfIlT42","sender":"2IBMSk0NPnrz1AeTERoea","receiver":"2IBMSk0NPnrz1AeTERoea","result":"world:42"}
437
569
  example/hello success: world:42
438
570
  CLOSE
439
571
  ```
@@ -0,0 +1,26 @@
1
+ type Brand<T> = T & {
2
+ readonly __brand: unique symbol;
3
+ };
4
+ export type APIEndpoint = APIEndpointEvent | APIEndpointStream | APIEndpointService | APIEndpointResource;
5
+ export type APIEndpointEvent = (...args: any[]) => void;
6
+ export type APIEndpointStream = (...args: any[]) => any;
7
+ export type APIEndpointService = (...args: any[]) => any;
8
+ export type APIEndpointResource = (...args: any[]) => Promise<Buffer>;
9
+ export type Event<T extends APIEndpointEvent> = Brand<T>;
10
+ export type Stream<T extends APIEndpointStream> = Brand<T>;
11
+ export type Service<T extends APIEndpointService> = Brand<T>;
12
+ export type Resource<T extends APIEndpointResource> = Brand<T>;
13
+ export type APISchema = Record<string, APIEndpoint>;
14
+ export type EventKeys<T> = string extends keyof T ? string : {
15
+ [K in keyof T]: T[K] extends Event<infer _F> ? K : never;
16
+ }[keyof T];
17
+ export type StreamKeys<T> = string extends keyof T ? string : {
18
+ [K in keyof T]: T[K] extends Stream<infer _F> ? K : never;
19
+ }[keyof T];
20
+ export type ServiceKeys<T> = string extends keyof T ? string : {
21
+ [K in keyof T]: T[K] extends Service<infer _F> ? K : never;
22
+ }[keyof T];
23
+ export type ResourceKeys<T> = string extends keyof T ? string : {
24
+ [K in keyof T]: T[K] extends Resource<infer _F> ? K : never;
25
+ }[keyof T];
26
+ export {};
@@ -0,0 +1,24 @@
1
+ /*
2
+ ** MQTT+ -- MQTT Communication Patterns
3
+ ** Copyright (c) 2018-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ **
5
+ ** Permission is hereby granted, free of charge, to any person obtaining
6
+ ** a copy of this software and associated documentation files (the
7
+ ** "Software"), to deal in the Software without restriction, including
8
+ ** without limitation the rights to use, copy, modify, merge, publish,
9
+ ** distribute, sublicense, and/or sell copies of the Software, and to
10
+ ** permit persons to whom the Software is furnished to do so, subject to
11
+ ** the following conditions:
12
+ **
13
+ ** The above copyright notice and this permission notice shall be included
14
+ ** in all copies or substantial portions of the Software.
15
+ **
16
+ ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ */
24
+ export {};
@@ -0,0 +1,19 @@
1
+ import { MqttClient, IClientPublishOptions, IClientSubscribeOptions } from "mqtt";
2
+ import { APISchema } from "./mqtt-plus-api";
3
+ import { APIOptions } from "./mqtt-plus-options";
4
+ import { ReceiverTrait } from "./mqtt-plus-receiver";
5
+ export declare class BaseTrait<T extends APISchema = APISchema> extends ReceiverTrait<T> {
6
+ protected mqtt: MqttClient;
7
+ constructor(mqtt: MqttClient, options?: Partial<APIOptions>);
8
+ destroy(): void;
9
+ protected _subscribeTopic(topic: string, options?: Partial<IClientSubscribeOptions>): Promise<void>;
10
+ protected _unsubscribeTopic(topic: string): Promise<void>;
11
+ private _isIClientPublishOptions;
12
+ protected _parseCallArgs<U extends any[]>(args: any[]): {
13
+ receiver?: string;
14
+ options: IClientPublishOptions;
15
+ params: U;
16
+ };
17
+ private _onMessage;
18
+ protected _dispatchMessage(_topic: string, _parsed: any): void;
19
+ }
@@ -0,0 +1,114 @@
1
+ /*
2
+ ** MQTT+ -- MQTT Communication Patterns
3
+ ** Copyright (c) 2018-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ **
5
+ ** Permission is hereby granted, free of charge, to any person obtaining
6
+ ** a copy of this software and associated documentation files (the
7
+ ** "Software"), to deal in the Software without restriction, including
8
+ ** without limitation the rights to use, copy, modify, merge, publish,
9
+ ** distribute, sublicense, and/or sell copies of the Software, and to
10
+ ** permit persons to whom the Software is furnished to do so, subject to
11
+ ** the following conditions:
12
+ **
13
+ ** The above copyright notice and this permission notice shall be included
14
+ ** in all copies or substantial portions of the Software.
15
+ **
16
+ ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ */
24
+ import { ReceiverTrait } from "./mqtt-plus-receiver";
25
+ /* MQTTp Base class with shared infrastructure */
26
+ export class BaseTrait extends ReceiverTrait {
27
+ /* construct API class */
28
+ constructor(mqtt, options = {}) {
29
+ super(options);
30
+ /* store MQTT client */
31
+ this.mqtt = mqtt;
32
+ /* hook into the MQTT message processing */
33
+ this.mqtt.on("message", (topic, message, packet) => {
34
+ this._onMessage(topic, message, packet);
35
+ });
36
+ }
37
+ /* destroy API class */
38
+ destroy() {
39
+ this.mqtt.removeAllListeners();
40
+ }
41
+ /* subscribe to an MQTT topic (Promise-based) */
42
+ async _subscribeTopic(topic, options = {}) {
43
+ return new Promise((resolve, reject) => {
44
+ this.mqtt.subscribe(topic, { qos: 2, ...options }, (err, _granted) => {
45
+ if (err)
46
+ reject(err);
47
+ else
48
+ resolve();
49
+ });
50
+ });
51
+ }
52
+ /* unsubscribe from an MQTT topic (Promise-based) */
53
+ async _unsubscribeTopic(topic) {
54
+ return new Promise((resolve, reject) => {
55
+ this.mqtt.unsubscribe(topic, (err, _packet) => {
56
+ if (err)
57
+ reject(err);
58
+ else
59
+ resolve();
60
+ });
61
+ });
62
+ }
63
+ /* check whether argument has structure of interface IClientPublishOptions */
64
+ _isIClientPublishOptions(arg) {
65
+ if (typeof arg !== "object")
66
+ return false;
67
+ const keys = ["qos", "retain", "dup", "properties", "cbStorePut"];
68
+ return Object.keys(arg).every((key) => keys.includes(key));
69
+ }
70
+ /* parse optional peerId and options from variadic arguments */
71
+ _parseCallArgs(args) {
72
+ let receiver;
73
+ let options = {};
74
+ let params = args;
75
+ if (args.length >= 2 && this._isReceiver(args[0]) && this._isIClientPublishOptions(args[1])) {
76
+ receiver = this._getReceiver(args[0]);
77
+ options = args[1];
78
+ params = args.slice(2);
79
+ }
80
+ else if (args.length >= 1 && this._isReceiver(args[0])) {
81
+ receiver = this._getReceiver(args[0]);
82
+ params = args.slice(1);
83
+ }
84
+ else if (args.length >= 1 && this._isIClientPublishOptions(args[0])) {
85
+ options = args[0];
86
+ params = args.slice(1);
87
+ }
88
+ return { receiver, options, params };
89
+ }
90
+ /* handle incoming MQTT message */
91
+ _onMessage(topic, message, packet) {
92
+ /* try to parse payload as payload */
93
+ let parsed;
94
+ try {
95
+ let input = message;
96
+ if (this.options.codec === "json")
97
+ input = message.toString();
98
+ const payload = this.codec.decode(input);
99
+ parsed = this.msg.parse(payload);
100
+ }
101
+ catch (_err) {
102
+ const err = _err instanceof Error
103
+ ? new Error(`failed to parse message: ${_err.message}`)
104
+ : new Error("failed to parse message");
105
+ this.mqtt.emit("error", err);
106
+ return;
107
+ }
108
+ /* dispatch to trait handlers */
109
+ this._dispatchMessage(topic, parsed);
110
+ }
111
+ /* dispatch parsed message to appropriate handler
112
+ (base implementation, to be overridden in super-traits) */
113
+ _dispatchMessage(_topic, _parsed) { }
114
+ }
@@ -1,6 +1,12 @@
1
+ import { APISchema } from "./mqtt-plus-api";
2
+ import { APIOptions, OptionsTrait } from "./mqtt-plus-options";
1
3
  export default class Codec {
2
4
  private type;
3
5
  constructor(type: "cbor" | "json");
4
6
  encode(data: unknown): Buffer | string;
5
7
  decode(data: Buffer | string): unknown;
6
8
  }
9
+ export declare class CodecTrait<T extends APISchema = APISchema> extends OptionsTrait<T> {
10
+ protected codec: Codec;
11
+ constructor(options?: Partial<APIOptions>);
12
+ }
@@ -21,7 +21,9 @@
21
21
  ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
22
  ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  */
24
+ /* external requirements */
24
25
  import CBOR from "cbor";
26
+ import { OptionsTrait } from "./mqtt-plus-options";
25
27
  /* the encoder/decoder abstraction */
26
28
  export default class Codec {
27
29
  constructor(type) {
@@ -72,3 +74,12 @@ export default class Codec {
72
74
  return result;
73
75
  }
74
76
  }
77
+ /* Codec trait */
78
+ export class CodecTrait extends OptionsTrait {
79
+ /* construct API class */
80
+ constructor(options = {}) {
81
+ super(options);
82
+ /* establish a codec */
83
+ this.codec = new Codec(this.options.codec);
84
+ }
85
+ }
@@ -0,0 +1,18 @@
1
+ import { IClientPublishOptions, IClientSubscribeOptions } from "mqtt";
2
+ import { APISchema, EventKeys } from "./mqtt-plus-api";
3
+ import type { WithInfo, InfoEvent } from "./mqtt-plus-info";
4
+ import type { Receiver } from "./mqtt-plus-receiver";
5
+ import { BaseTrait } from "./mqtt-plus-base";
6
+ export interface Subscription {
7
+ unsubscribe(): Promise<void>;
8
+ }
9
+ export declare class EventTrait<T extends APISchema = APISchema> extends BaseTrait<T> {
10
+ private subscriptions;
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>;
13
+ emit<K extends EventKeys<T> & string>(event: K, ...params: Parameters<T[K]>): void;
14
+ emit<K extends EventKeys<T> & string>(event: K, receiver: Receiver, ...params: Parameters<T[K]>): void;
15
+ emit<K extends EventKeys<T> & string>(event: K, options: IClientPublishOptions, ...params: Parameters<T[K]>): void;
16
+ emit<K extends EventKeys<T> & string>(event: K, receiver: Receiver, options: IClientPublishOptions, ...params: Parameters<T[K]>): void;
17
+ protected _dispatchMessage(topic: string, parsed: any): void;
18
+ }