mqtt-plus 1.4.2 → 1.4.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/AGENTS.md CHANGED
@@ -107,26 +107,36 @@ Each trait lives in its own file: `src/mqtt-plus-<trait>.ts`.
107
107
 
108
108
  ### Documentation
109
109
 
110
- The `doc/` directory contains D2 diagram sources and generated SVG files
111
- illustrating the four communication patterns:
110
+ The `doc/` directory contains Markdown documentation, D2 diagram sources,
111
+ and generated SVG files:
112
112
 
113
- - `doc/mqtt-plus-comm-event-emission.{d2,svg}`
114
- - `doc/mqtt-plus-comm-service-call.{d2,svg}`
115
- - `doc/mqtt-plus-comm-sink-push.{d2,svg}`
116
- - `doc/mqtt-plus-comm-source-fetch.{d2,svg}`
117
- - `doc/theme.d2` — shared D2 theme
113
+ - `doc/mqtt-plus-api.md` — public API reference
114
+ - `doc/mqtt-plus-architecture.{d2,svg,md}` — architecture overview (diagram + docs)
115
+ - `doc/mqtt-plus-broker-setup.md` — MQTT broker setup guide
116
+ - `doc/mqtt-plus-comm.md` — communication patterns overview
117
+ - `doc/mqtt-plus-comm-event-emission.{d2,svg}` — Event Emission pattern diagram
118
+ - `doc/mqtt-plus-comm-service-call.{d2,svg}` — Service Call pattern diagram
119
+ - `doc/mqtt-plus-comm-sink-push.{d2,svg}` — Sink Push pattern diagram
120
+ - `doc/mqtt-plus-comm-source-fetch.{d2,svg}` — Source Fetch pattern diagram
121
+ - `doc/mqtt-plus-internals.md` — internal implementation details
118
122
 
119
- Regenerate with `npm start build-doc` (requires the `etc/d2.mts` helper script).
123
+ Regenerate diagrams with `npm start build-doc` (requires the `etc/d2.mts` helper script).
120
124
 
121
125
  ### Tests
122
126
 
123
127
  Test files live in `tst/`:
124
128
 
125
- | File | Role |
126
- |-------------------------------|------|
127
- | `tst/mqtt-plus.spec.ts` | Main Mocha test suite covering all four communication patterns |
128
- | `tst/mqtt-plus-mosquitto.ts` | Helper for starting/stopping the Mosquitto MQTT broker |
129
- | `tst/tsc.json` | TypeScript configuration for the test directory |
129
+ | File | Role |
130
+ |-----------------------------------|------|
131
+ | `tst/mqtt-plus-0-fixture.ts` | Shared test fixture setup (broker, MQTTp instances, etc.) |
132
+ | `tst/mqtt-plus-0-mosquitto.ts` | Helper for starting/stopping the Mosquitto MQTT broker |
133
+ | `tst/mqtt-plus-1-api.spec.ts` | API type and endpoint definition tests |
134
+ | `tst/mqtt-plus-2-event.spec.ts` | Event Emission pattern tests |
135
+ | `tst/mqtt-plus-3-service.spec.ts` | Service Call / RPC pattern tests |
136
+ | `tst/mqtt-plus-4-sink.spec.ts` | Sink Push pattern tests |
137
+ | `tst/mqtt-plus-5-source.spec.ts` | Source Fetch pattern tests |
138
+ | `tst/mqtt-plus-6-misc.spec.ts` | Miscellaneous / edge-case tests |
139
+ | `tst/tsc.json` | TypeScript configuration for the test directory |
130
140
 
131
141
  ### Type System
132
142
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
  ChangeLog
3
3
  =========
4
4
 
5
+ 1.4.4 (2026-02-21)
6
+ ------------------
7
+
8
+ - CLEANUP: cleanup documentation
9
+
10
+ 1.4.3 (2026-02-21)
11
+ ------------------
12
+
13
+ - IMPROVEMENT: allow JWT expirations
14
+ - DOCUMENTATION: document more internals
15
+
5
16
  1.4.2 (2026-02-21)
6
17
  ------------------
7
18
 
package/README.md CHANGED
@@ -117,15 +117,29 @@ mqtt.on("connect", async () => {
117
117
  Documentation
118
118
  -------------
119
119
 
120
+ Main documentation:
121
+
120
122
  - [**Communication Patterns**](doc/mqtt-plus-comm.md)
121
123
  - [**Application Programming Interface (API)**](doc/mqtt-plus-api.md)
122
- - [**Architecture Overview**](doc/mqtt-plus-architecture.md)
123
- - [Extra: Internals](doc/mqtt-plus-internals.md)
124
+
125
+ Additional auxilliary documentation:
126
+
127
+ - [Extra: Architecture Overview](doc/mqtt-plus-architecture.md)
128
+ - [Extra: Internal Protocol](doc/mqtt-plus-internals.md)
124
129
  - [Extra: Broker Setup](doc/mqtt-plus-broker-setup.md)
125
130
 
126
131
  Notice
127
132
  ------
128
133
 
134
+ > [!Note]
135
+ > **MQTT+** and its peer dependency **MQTT** provide a powerful
136
+ > functionality, but are not small in size. **MQTT+** is 3.500 LoC
137
+ > and 75 KB in size (ESM and CJS format). When bundled with all its
138
+ > dependencies, it is 220 KB in size (UMD format). Its peer dependency
139
+ > **MQTT.js** is 370 KB (ESM and CJS format) and 860 KB (UMD format) in
140
+ > size. For a Node.js application, this usually doesn't matter. For a
141
+ > HTML5 SPA it matters more, but usually is still acceptable.
142
+
129
143
  > [!Note]
130
144
  > **MQTT+** is still somewhat similar to and originally derived from the weaker
131
145
  > [MQTT-JSON-RPC](https://github.com/rse/mqtt-json-rpc) library of the same
@@ -139,7 +153,7 @@ Notice
139
153
  License
140
154
  -------
141
155
 
142
- Copyright (c) 2018-2026 Dr. Ralf S. Engelschall (http://engelschall.com/)
156
+ Copyright &copy; 2018-2026 Dr. Ralf S. Engelschall (http://engelschall.com/)
143
157
 
144
158
  Permission is hereby granted, free of charge, to any person obtaining
145
159
  a copy of this software and associated documentation files (the
@@ -159,3 +173,4 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
159
173
  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
160
174
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
161
175
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
176
+
@@ -1,59 +1,380 @@
1
1
 
2
2
  MQTT+ Internals
3
- ---------------
3
+ ===============
4
4
 
5
- In the following, we assume that an **MQTT+** instance is created with:
5
+ Overview
6
+ --------
6
7
 
7
- ```ts
8
- import MQTT from "mqtt"
9
- import MQTTp from "mqtt-plus"
8
+ **MQTT+** implements a message-oriented protocol on top of standard MQTT.
9
+ Each MQTT+ instance is identified by a unique **peer ID** (a NanoID).
10
+ All messages are encoded as structured objects and transported as MQTT
11
+ payloads on well-defined MQTT topics. The protocol supports four
12
+ communication patterns: **Event Emission**, **Service Call**, **Sink
13
+ Push**, and **Source Fetch**.
10
14
 
11
- export type API = {
12
- "example/sample": Event<(a1: string, a2: number) => void>
13
- ...
14
- }
15
- const mqtt = MQTT.connect("...", { ... })
16
- const mqttp = new MQTTp<API>(mqtt, { codec: "json" })
17
- ```
15
+ Message Encoding
16
+ ----------------
17
+
18
+ Messages are encoded using one of two codecs, selected at instance creation:
19
+
20
+ - **CBOR** (default): Binary encoding via the `cbor2` library.
21
+ `Uint8Array` and `Buffer` values are encoded natively.
22
+ MQTT payloads are `Uint8Array`.
23
+
24
+ - **JSON**: Text encoding via a custom `JSONX` serializer.
25
+ `Uint8Array` values are encoded as `{ "__Uint8Array": "<base64>" }`.
26
+ MQTT payloads are UTF-8 strings.
27
+
28
+ Message Base Structure
29
+ ----------------------
30
+
31
+ Every MQTT+ message shares a common base structure:
32
+
33
+ | Field | Type | Description |
34
+ |------------|-------------------|------------------------------------------------|
35
+ | `version` | `string` | Protocol version identifier, format `MQTT+/X.X` (e.g. `MQTT+/1.4`). Must match between peers. |
36
+ | `type` | `string` | One of the 11 message types (see below). |
37
+ | `id` | `string` | NanoID correlating requests with their responses. |
38
+ | `sender` | `string?` | NanoID of the sending peer. |
39
+ | `receiver` | `string?` | NanoID of the intended receiving peer. |
40
+
41
+ The `version` field is checked on every incoming message. A mismatch
42
+ causes the message to be rejected.
43
+
44
+ Message Types
45
+ -------------
46
+
47
+ The protocol defines 11 message types, grouped by communication pattern:
48
+
49
+ ### Event Emission (1 message type)
50
+
51
+ | Type | Direction | Purpose |
52
+ |--------------------|-----------|-------------------------------|
53
+ | `event-emission` | one-way | Fire-and-forget event notification |
54
+
55
+ ### Service Call (2 message types)
56
+
57
+ | Type | Direction | Purpose |
58
+ |--------------------------|-------------|--------------------------|
59
+ | `service-call-request` | caller -> callee | RPC request with parameters |
60
+ | `service-call-response` | callee -> caller | RPC result or error |
61
+
62
+ ### Sink Push (4 message types)
63
+
64
+ | Type | Direction | Purpose |
65
+ |----------------------|------------------|-------------------------------|
66
+ | `sink-push-request` | pusher -> sink | Initiate data push |
67
+ | `sink-push-response` | sink -> pusher | Acknowledge (ack) or reject (nak) |
68
+ | `sink-push-chunk` | pusher -> sink | Transfer a data chunk |
69
+ | `sink-push-credit` | sink -> pusher | Replenish flow control credit |
70
+
71
+ ### Source Fetch (4 message types)
72
+
73
+ | Type | Direction | Purpose |
74
+ |-------------------------|--------------------|-------------------------------|
75
+ | `source-fetch-request` | fetcher -> source | Initiate data fetch |
76
+ | `source-fetch-response` | source -> fetcher | Acknowledge (ack) or reject (nak) |
77
+ | `source-fetch-chunk` | source -> fetcher | Transfer a data chunk |
78
+ | `source-fetch-credit` | fetcher -> source | Replenish flow control credit |
79
+
80
+ Message Fields by Type
81
+ ----------------------
82
+
83
+ ### `event-emission`
84
+
85
+ | Field | Type | Required | Description |
86
+ |----------|------------------------|----------|-------------------------------|
87
+ | `name` | `string` | yes | Endpoint name |
88
+ | `params` | `any[]` | no | Event parameters (max 64) |
89
+ | `auth` | `string[]` | no | JWT tokens (max 8, each max 8192 chars) |
90
+ | `meta` | `Record<string, any>` | no | Arbitrary metadata (non-array object) |
91
+
92
+ ### `service-call-request`
93
+
94
+ | Field | Type | Required | Description |
95
+ |----------|------------------------|----------|-------------------------------|
96
+ | `name` | `string` | yes | Service endpoint name |
97
+ | `params` | `any[]` | no | Call parameters (max 64) |
98
+ | `auth` | `string[]` | no | JWT tokens (max 8) |
99
+ | `meta` | `Record<string, any>` | no | Arbitrary metadata |
18
100
 
19
- Internally, remote services are assigned to MQTT topics. When calling a
20
- remote service named `example/hello` with parameters `"world"` and `42` via...
101
+ ### `service-call-response`
21
102
 
22
- ```ts
23
- mqttp.call("example/hello", "world", 42).then((result) => {
24
- ...
25
- })
103
+ | Field | Type | Required | Description |
104
+ |----------|-----------|----------|--------------------------------------|
105
+ | `result` | `any` | no | Return value on success |
106
+ | `error` | `string` | no | Error message on failure |
107
+
108
+ Exactly one of `result` or `error` is present.
109
+
110
+ ### `sink-push-request`
111
+
112
+ | Field | Type | Required | Description |
113
+ |----------|------------------------|----------|-------------------------------|
114
+ | `name` | `string` | yes | Sink endpoint name |
115
+ | `params` | `any[]` | no | Push parameters (max 64) |
116
+ | `auth` | `string[]` | no | JWT tokens (max 8) |
117
+ | `meta` | `Record<string, any>` | no | Arbitrary metadata |
118
+
119
+ ### `sink-push-response`
120
+
121
+ | Field | Type | Required | Description |
122
+ |----------|------------------------|----------|-------------------------------|
123
+ | `name` | `string` | yes | Sink endpoint name |
124
+ | `error` | `string` | no | Error message (nak) or absent (ack) |
125
+ | `auth` | `string[]` | no | JWT tokens (max 8) |
126
+ | `meta` | `Record<string, any>` | no | Arbitrary metadata |
127
+ | `credit` | `integer` | no | Initial flow control credit (min 1) |
128
+
129
+ ### `sink-push-chunk`
130
+
131
+ | Field | Type | Required | Description |
132
+ |---------|--------------|----------|------------------------------------|
133
+ | `name` | `string` | yes | Sink endpoint name |
134
+ | `chunk` | `Uint8Array` | no | Data chunk payload |
135
+ | `error` | `string` | no | Error message (aborts the stream) |
136
+ | `final` | `boolean` | no | `true` on the last chunk |
137
+
138
+ ### `sink-push-credit`
139
+
140
+ | Field | Type | Required | Description |
141
+ |----------|-----------|----------|-------------------------------------|
142
+ | `name` | `string` | yes | Sink endpoint name |
143
+ | `credit` | `integer` | yes | Number of additional credits (min 1)|
144
+
145
+ ### `source-fetch-request`
146
+
147
+ | Field | Type | Required | Description |
148
+ |----------|------------------------|----------|-------------------------------|
149
+ | `name` | `string` | yes | Source endpoint name |
150
+ | `params` | `any[]` | no | Fetch parameters (max 64) |
151
+ | `auth` | `string[]` | no | JWT tokens (max 8) |
152
+ | `meta` | `Record<string, any>` | no | Arbitrary metadata |
153
+ | `credit` | `integer` | no | Initial flow control credit (min 1) |
154
+
155
+ ### `source-fetch-response`
156
+
157
+ | Field | Type | Required | Description |
158
+ |----------|------------------------|----------|-------------------------------|
159
+ | `name` | `string` | yes | Source endpoint name |
160
+ | `error` | `string` | no | Error message (nak) or absent (ack) |
161
+ | `auth` | `string[]` | no | JWT tokens (max 8) |
162
+ | `meta` | `Record<string, any>` | no | Arbitrary metadata |
163
+
164
+ ### `source-fetch-chunk`
165
+
166
+ | Field | Type | Required | Description |
167
+ |---------|--------------|----------|------------------------------------|
168
+ | `name` | `string` | yes | Source endpoint name |
169
+ | `chunk` | `Uint8Array` | no | Data chunk payload |
170
+ | `error` | `string` | no | Error message (aborts the stream) |
171
+ | `final` | `boolean` | no | `true` on the last chunk |
172
+
173
+ ### `source-fetch-credit`
174
+
175
+ | Field | Type | Required | Description |
176
+ |----------|-----------|----------|-------------------------------------|
177
+ | `name` | `string` | yes | Source endpoint name |
178
+ | `credit` | `integer` | yes | Number of additional credits (min 1)|
179
+
180
+ MQTT Topic Structure
181
+ --------------------
182
+
183
+ MQTT+ maps messages to MQTT topics using the pattern:
184
+
185
+ ```
186
+ {name}/{operation}/{peerId}
26
187
  ```
27
188
 
28
- ...the following message is sent to the permanent MQTT topic
29
- `example/hello/service-call-request/any` (the shown NanoIDs are just
30
- pseudo ones):
189
+ - **`name`**: The endpoint name (e.g. `example/hello`).
190
+ - **`operation`**: The message type (e.g. `service-call-request`).
191
+ - **`peerId`**: Either the target peer's NanoID (for directed messages) or
192
+ `any` (for broadcast messages).
193
+
194
+ ### Broadcast Topics (Requests)
195
+
196
+ Request messages are published to broadcast topics when no specific
197
+ receiver is targeted:
198
+
199
+ | Pattern | Operation | Purpose |
200
+ |----------------------------|-------------------------|------------------------|
201
+ | `{name}/event-emission/any` | `event-emission` | Broadcast event |
202
+ | `{name}/event-emission/{peerId}` | `event-emission` | Directed event |
203
+ | `$share/{share}/{name}/service-call-request/any` | `service-call-request` | Shared service request |
204
+ | `$share/{share}/{name}/source-fetch-request/any` | `source-fetch-request` | Shared fetch request |
205
+ | `$share/{share}/{name}/sink-push-request/any` | `sink-push-request` | Shared push request |
206
+
207
+ Service, source, and sink requests use **MQTT shared subscriptions**
208
+ (`$share/{group}/...`) to distribute load across multiple handlers
209
+ (default group: `"default"`). Event emissions do *not* use shared
210
+ subscriptions by default (all registered handlers receive the event).
211
+
212
+ ### Direct Topics (Responses and Chunks)
213
+
214
+ Response messages, chunks, and credits are sent to peer-specific topics:
215
+
216
+ | Pattern | Operation |
217
+ |--------------------------------------------------|---------------------------|
218
+ | `{name}/service-call-response/{clientId}` | `service-call-response` |
219
+ | `{name}/sink-push-response/{clientId}` | `sink-push-response` |
220
+ | `{name}/sink-push-chunk/{sinkId}` | `sink-push-chunk` |
221
+ | `{name}/sink-push-credit/{pusherId}` | `sink-push-credit` |
222
+ | `{name}/source-fetch-response/{clientId}` | `source-fetch-response` |
223
+ | `{name}/source-fetch-chunk/{clientId}` | `source-fetch-chunk` |
224
+ | `{name}/source-fetch-credit/{sourceId}` | `source-fetch-credit` |
225
+
226
+ The `{clientId}` is the `sender` field from the corresponding request
227
+ message, ensuring responses are routed back to the originating peer only.
228
+
229
+ ### Topic Customization
230
+
231
+ The topic structure is fully customizable through the `topicMake` and
232
+ `topicMatch` options at instance creation time.
233
+
234
+ MQTT QoS Levels
235
+ ---------------
236
+
237
+ | Communication Pattern | QoS | Rationale |
238
+ |-----------------------|-----|-----------------------------------|
239
+ | Event Emission | 0 | Best-effort, fire-and-forget |
240
+ | Service Call | 2 | Exactly-once for reliable RPC |
241
+ | Sink Push | 2 | Exactly-once for reliable data transfer |
242
+ | Source Fetch | 2 | Exactly-once for reliable data transfer |
243
+
244
+ Credit-Based Flow Control
245
+ -------------------------
246
+
247
+ Sink Push and Source Fetch patterns use a **credit-based flow control**
248
+ mechanism to prevent the data producer from overwhelming the consumer.
249
+
250
+ ### How It Works
251
+
252
+ 1. The **consumer** grants an initial number of credits (default: 4)
253
+ to the **producer** (via the response message or request message).
254
+ 2. Each chunk sent by the producer **consumes one credit**.
255
+ 3. When credits are exhausted, the producer **blocks** (waits).
256
+ 4. As the consumer processes chunks, it sends **credit messages** to
257
+ replenish the producer's credit, unblocking it.
258
+ 5. The chunk size is configurable (default: 16 KB).
259
+
260
+ ### Configuration
261
+
262
+ | Option | Default | Description |
263
+ |---------------|-----------|------------------------------------------|
264
+ | `chunkSize` | `16384` | Maximum bytes per chunk (16 KB) |
265
+ | `chunkCredit` | `4` | Number of chunks allowed in-flight |
266
+
267
+ Setting `chunkCredit` to `0` disables flow control entirely.
268
+
269
+ ### Direction of Credit
270
+
271
+ | Pattern | Credit Sender | Credit Receiver | Credit Message Type |
272
+ |--------------|---------------|-----------------|-------------------------|
273
+ | Sink Push | Sink | Pusher | `sink-push-credit` |
274
+ | Source Fetch | Fetcher | Source | `source-fetch-credit` |
275
+
276
+ Authentication
277
+ --------------
278
+
279
+ MQTT+ provides optional JWT-based authentication and role-based
280
+ authorization on any endpoint.
281
+
282
+ ### Setup
283
+
284
+ 1. The **server** sets a shared secret via `credential(secret)`.
285
+ The secret is derived into a 256-bit key using PBKDF2-SHA256
286
+ (600,000 iterations).
287
+
288
+ 2. The server **issues JWT tokens** via `issue({ roles, id?, exp? })`,
289
+ signed with HS256.
290
+
291
+ 3. The **client** stores tokens via `authenticate(token)`.
292
+
293
+ ### Token Transmission
294
+
295
+ When a client sends a request (`event-emission`, `service-call-request`,
296
+ `sink-push-request`, or `source-fetch-request`), all stored JWT tokens
297
+ are included in the `auth` field (max 8 tokens, each max 8192 characters).
298
+
299
+ ### Validation
300
+
301
+ On the server side, the handler validates the tokens against
302
+ the configured credential and required roles:
303
+
304
+ - The token must be a valid HS256-signed JWT.
305
+ - If the token payload contains an `id` field, it must match the `sender`
306
+ peer ID of the request.
307
+ - If the token payload contains an `exp` field, the token must not be
308
+ expired.
309
+ - The token's `roles` array must contain at least one of the
310
+ required roles.
311
+
312
+ ### Authentication Modes
313
+
314
+ | Mode | Behavior |
315
+ |------------|----------------------------------------------------|
316
+ | `require` | Request is rejected if no valid token is found. |
317
+ | `optional` | Request passes even if no valid token is found. |
318
+
319
+ Message Dispatching
320
+ -------------------
321
+
322
+ ### Request Dispatching
323
+
324
+ Incoming request messages are dispatched based on the combination of
325
+ their `type` and `name` fields. The dispatch key is
326
+ `{operation}:{name}` (e.g. `service-call-request:example/hello`).
327
+
328
+ ### Response Dispatching
329
+
330
+ Incoming response messages are dispatched based on the combination
331
+ of their `type` and `id` fields. The dispatch key is
332
+ `{operation}:{requestId}` (e.g. `service-call-response:vwLzfQDu2uEeOdOfIlT42`).
333
+ This ensures responses are correlated to their originating requests.
334
+
335
+ Timeouts
336
+ --------
337
+
338
+ All bi-directional patterns (Service Call, Sink Push, Source Fetch) are
339
+ guarded by a configurable timeout (default: 10 seconds). If no response
340
+ or progress is received within the timeout, the operation is aborted
341
+ with a timeout error. For streaming patterns, each chunk or credit
342
+ message resets the timeout.
343
+
344
+ Error Handling
345
+ --------------
346
+
347
+ Errors are communicated in two ways, depending on timing:
348
+
349
+ 1. **Before data transfer starts**: The response message carries an
350
+ `error` field (nak response).
351
+
352
+ 2. **During data transfer**: A chunk message carries an `error` field
353
+ and `final: true`, terminating the stream.
354
+
355
+ Example Message Exchange
356
+ ------------------------
357
+
358
+ A service call to `example/hello` with parameters `"world"` and `42`:
359
+
360
+ **Request** (published to `example/hello/service-call-request/any`):
31
361
 
32
362
  ```json
33
363
  {
34
- "type": "service-call-request",
35
- "id": "vwLzfQDu2uEeOdOfIlT42",
36
- "name": "example/hello",
37
- "params": [ "world", 42 ],
38
- "sender": "2IBMSk0NPnrz1AeTERoea"
364
+ "version": "MQTT+/1.4",
365
+ "type": "service-call-request",
366
+ "id": "vwLzfQDu2uEeOdOfIlT42",
367
+ "name": "example/hello",
368
+ "params": [ "world", 42 ],
369
+ "sender": "2IBMSk0NPnrz1AeTERoea"
39
370
  }
40
371
  ```
41
372
 
42
- Beforehand, this `example/hello` service should have been established with...
43
-
44
- ```ts
45
- mqttp.service("example/hello", (a1, a2) => {
46
- return `${a1}:${a2}`
47
- })
48
- ```
49
-
50
- ...and then its result, in the above `mqttp.call()` example `"world:42"`, is then
51
- sent back as the following success response
52
- message to the temporary (client-specific) MQTT topic
53
- `example/hello/service-call-response/2IBMSk0NPnrz1AeTERoea`:
373
+ **Response** (published to `example/hello/service-call-response/2IBMSk0NPnrz1AeTERoea`):
54
374
 
55
375
  ```json
56
376
  {
377
+ "version": "MQTT+/1.4",
57
378
  "type": "service-call-response",
58
379
  "id": "vwLzfQDu2uEeOdOfIlT42",
59
380
  "result": "world:42",
@@ -62,7 +383,6 @@ message to the temporary (client-specific) MQTT topic
62
383
  }
63
384
  ```
64
385
 
65
- The `sender` field is the NanoID of the MQTT+ sender instance and
66
- `id` is the NanoID of the particular service request. The `sender` is
67
- used for sending back the response message to the requestor only. The
68
- `id` is used for correlating the response to the request only.
386
+ The `id` field correlates the response to the request. The `sender`
387
+ field in the request is used as the peer-specific suffix in the response
388
+ topic, ensuring only the caller receives the response.
@@ -9,6 +9,7 @@ export type AuthOption = AuthRole | {
9
9
  type TokenPayload = {
10
10
  roles: AuthRole[];
11
11
  id?: string;
12
+ exp?: number;
12
13
  };
13
14
  export declare class AuthTrait<T extends APISchema = APISchema> extends MetaTrait<T> {
14
15
  private _credential;
@@ -118,7 +118,7 @@ export class SubscriptionTrait extends BaseTrait {
118
118
  super(...arguments);
119
119
  this.subscriptions = new RefCountedSubscription((topic, options) => this._subscribeTopic(topic, options), (topic) => this._unsubscribeTopic(topic));
120
120
  }
121
- /* destroy topic trait */
121
+ /* destroy subscription trait */
122
122
  async destroy() {
123
123
  await this.subscriptions.flush();
124
124
  await super.destroy();
@@ -1131,7 +1131,7 @@ class SubscriptionTrait extends BaseTrait {
1131
1131
  super(...arguments);
1132
1132
  this.subscriptions = new RefCountedSubscription((topic, options) => this._subscribeTopic(topic, options), (topic) => this._unsubscribeTopic(topic));
1133
1133
  }
1134
- /* destroy topic trait */
1134
+ /* destroy subscription trait */
1135
1135
  async destroy() {
1136
1136
  await this.subscriptions.flush();
1137
1137
  await super.destroy();
@@ -1110,7 +1110,7 @@ class SubscriptionTrait extends BaseTrait {
1110
1110
  super(...arguments);
1111
1111
  this.subscriptions = new RefCountedSubscription((topic, options) => this._subscribeTopic(topic, options), (topic) => this._unsubscribeTopic(topic));
1112
1112
  }
1113
- /* destroy topic trait */
1113
+ /* destroy subscription trait */
1114
1114
  async destroy() {
1115
1115
  await this.subscriptions.flush();
1116
1116
  await super.destroy();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mqtt-plus",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "MQTT Communication Patterns",
5
5
  "keywords": [ "mqtt",
6
6
  "event", "emit",
@@ -36,7 +36,7 @@ import { MetaTrait } from "./mqtt-plus-meta"
36
36
  export type AuthMode = "require" | "optional"
37
37
  export type AuthRole = string
38
38
  export type AuthOption = AuthRole | { mode: AuthMode, roles: AuthRole[] }
39
- type TokenPayload = { roles: AuthRole[], id?: string }
39
+ type TokenPayload = { roles: AuthRole[], id?: string, exp?: number }
40
40
 
41
41
  /* authentication trait */
42
42
  export class AuthTrait<T extends APISchema = APISchema> extends MetaTrait<T> {
@@ -133,7 +133,7 @@ export class SubscriptionTrait<T extends APISchema = APISchema> extends BaseTrai
133
133
  (topic) => this._unsubscribeTopic(topic)
134
134
  )
135
135
 
136
- /* destroy topic trait */
136
+ /* destroy subscription trait */
137
137
  override async destroy () {
138
138
  await this.subscriptions.flush()
139
139
  await super.destroy()