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.
- package/README.md +177 -45
- package/dst-stage1/mqtt-plus-api.d.ts +26 -0
- package/dst-stage1/mqtt-plus-api.js +24 -0
- package/dst-stage1/mqtt-plus-base.d.ts +19 -0
- package/dst-stage1/mqtt-plus-base.js +114 -0
- package/dst-stage1/mqtt-plus-codec.d.ts +6 -0
- package/dst-stage1/mqtt-plus-codec.js +11 -0
- package/dst-stage1/mqtt-plus-event.d.ts +18 -0
- package/dst-stage1/mqtt-plus-event.js +103 -0
- package/dst-stage1/mqtt-plus-info.d.ts +15 -0
- package/dst-stage1/mqtt-plus-info.js +24 -0
- package/dst-stage1/mqtt-plus-msg.d.ts +31 -10
- package/dst-stage1/mqtt-plus-msg.js +92 -29
- package/dst-stage1/mqtt-plus-options.d.ts +20 -0
- package/dst-stage1/mqtt-plus-options.js +46 -0
- package/dst-stage1/mqtt-plus-receiver.d.ts +12 -0
- package/dst-stage1/mqtt-plus-receiver.js +42 -0
- package/dst-stage1/mqtt-plus-resource.d.ts +19 -0
- package/dst-stage1/mqtt-plus-resource.js +194 -0
- package/dst-stage1/mqtt-plus-service.d.ts +22 -0
- package/dst-stage1/mqtt-plus-service.js +220 -0
- package/dst-stage1/mqtt-plus-stream.d.ts +20 -0
- package/dst-stage1/mqtt-plus-stream.js +152 -0
- package/dst-stage1/mqtt-plus.d.ts +5 -72
- package/dst-stage1/mqtt-plus.js +4 -384
- package/dst-stage2/mqtt-plus.cjs.js +494 -171
- package/dst-stage2/mqtt-plus.esm.js +492 -170
- package/dst-stage2/mqtt-plus.umd.js +14 -14
- package/etc/eslint.mts +3 -2
- package/etc/stx.conf +7 -1
- package/etc/tsc.tsbuildinfo +1 -1
- package/package.json +17 -5
- package/src/mqtt-plus-api.ts +66 -0
- package/src/mqtt-plus-base.ts +155 -0
- package/src/mqtt-plus-codec.ts +20 -0
- package/src/mqtt-plus-event.ts +164 -0
- package/src/mqtt-plus-info.ts +43 -0
- package/src/mqtt-plus-msg.ts +132 -38
- package/src/mqtt-plus-options.ts +70 -0
- package/src/mqtt-plus-receiver.ts +52 -0
- package/src/mqtt-plus-resource.ts +277 -0
- package/src/mqtt-plus-service.ts +292 -0
- package/src/mqtt-plus-stream.ts +222 -0
- package/src/mqtt-plus.ts +6 -575
- package/tst/mqtt-plus.spec.ts +238 -0
- 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)
|
|
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/
|
|
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:
|
|
129
|
-
codec:
|
|
130
|
-
timeout:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
- `
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
(default: `` (name,
|
|
156
|
-
- `
|
|
157
|
-
|
|
158
|
-
|
|
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: (
|
|
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
|
-
`
|
|
182
|
-
`${event}/event-notice/${
|
|
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: (
|
|
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
|
-
`
|
|
202
|
-
`${service}/service-
|
|
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 `
|
|
223
|
-
(default: `${event}/event-notice` or `${event}/event-notice/${
|
|
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 `
|
|
244
|
-
(default: `${service}/service-
|
|
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-
|
|
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-
|
|
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
|
|
386
|
-
import MQTT
|
|
387
|
-
import MQTTp
|
|
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-
|
|
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-
|
|
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
|
+
}
|