mqtt-plus 0.9.4 → 0.9.6

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 CHANGED
@@ -22,10 +22,10 @@ $ npm install mqtt mqtt-plus
22
22
  About
23
23
  -----
24
24
 
25
- This is **MQTT+**, an companion addon API for the excellent
25
+ This is **MQTT+**, a companion addon API for the excellent
26
26
  [MQTT](http://mqtt.org/) client TypeScript/JavaScript API
27
- [MQTT.js](https://www.npmjs.com/package/mqtt), provoding additional
28
- communication patterns with optional type safety:
27
+ [MQTT.js](https://www.npmjs.com/package/mqtt), providing additional
28
+ communication patterns with type safety:
29
29
 
30
30
  - **Event Emission**:
31
31
 
@@ -38,7 +38,9 @@ communication patterns with optional type safety:
38
38
 
39
39
  In contrast to the regular MQTT message publish/subscribe, this
40
40
  pattern allows to direct the event to particular subscribers and
41
- provides optional information about the sender to subscribers.
41
+ provides optional information about the sender and receiver to subscribers.
42
+
43
+ ![Event Emission](doc/mqtt-plus-1-event-emission.svg)
42
44
 
43
45
  - **Stream Transfer**:
44
46
 
@@ -54,6 +56,8 @@ communication patterns with optional type safety:
54
56
  pattern allows to transfer arbitrary amounts of arbitrary data by
55
57
  chunking the data via a stream.
56
58
 
59
+ ![Stream Transfer](doc/mqtt-plus-2-stream-transfer.svg)
60
+
57
61
  - **Service Call**:
58
62
 
59
63
  Service Call is a *bi-directional* communication pattern.
@@ -69,6 +73,8 @@ communication patterns with optional type safety:
69
73
  Procedure Call](https://en.wikipedia.org/wiki/Remote_procedure_call)
70
74
  (RPC) style communication.
71
75
 
76
+ ![Service Call](doc/mqtt-plus-3-service-call.svg)
77
+
72
78
  - **Resource Transfer**:
73
79
 
74
80
  Resource Transfer is a *bi-directional* communication pattern.
@@ -78,14 +84,7 @@ communication patterns with optional type safety:
78
84
  of a directed resource transfer) or one arbitrary provisioner is called and
79
85
  sends or receives the resource and its arguments.
80
86
 
81
- > [!Note]
82
- > **MQTT+** is similar to and derived from
83
- > [MQTT-JSON-RPC](https://github.com/rse/mqtt-json-rpc) of the same
84
- > author, but instead of just JSON, MQTT+ encodes packets as JSON
85
- > or CBOR (default), uses an own packet format (allowing sender and
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.
87
+ ![Resource Transfer](doc/mqtt-plus-4-resource-transfer.svg)
89
88
 
90
89
  Usage
91
90
  -----
@@ -100,9 +99,10 @@ pattern of each endpoint:
100
99
  import type * as MQTTpt from "mqtt-plus"
101
100
 
102
101
  export type API = {
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 */
102
+ "example/sample": MQTTpt.Event<(a1: string, a2: number) => void>
103
+ "example/upload": MQTTpt.Stream<(name: string) => void>
104
+ "example/hello": MQTTpt.Service<(a1: string, a2: number) => string>
105
+ "example/resource": MQTTpt.Resource<(filename: string) => void>
106
106
  }
107
107
  ```
108
108
 
@@ -122,11 +122,11 @@ const mqtt = MQTT.connect("wss://127.0.0.1:8883", { ... })
122
122
  const mqttp = new MQTTp<API>(mqtt)
123
123
 
124
124
  mqtt.on("connect", async () => {
125
- mqttp.subscribe("example/sample", (a1, a2) => {
126
- console.log("example/sample: ", a1, a2)
125
+ await mqttp.subscribe("example/sample", (a1, a2, info) => {
126
+ console.log("example/sample:", a1, a2, "from:", info.sender)
127
127
  })
128
- mqttp.register("example/hello", (a1, a2) => {
129
- console.log("example/hello: ", a1, a2)
128
+ await mqttp.register("example/hello", (a1, a2, info) => {
129
+ console.log("example/hello:", a1, a2, "from:", info.sender)
130
130
  return `${a1}:${a2}`
131
131
  })
132
132
  })
@@ -143,9 +143,9 @@ const mqtt = MQTT.connect("wss://127.0.0.1:8883", { ... })
143
143
  const mqttp = new MQTTp<API>(mqtt)
144
144
 
145
145
  mqtt.on("connect", () => {
146
- mqttp.emit("example/sample", "foo", true)
146
+ mqttp.emit("example/sample", "world", 42)
147
147
  mqttp.call("example/hello", "world", 42).then((response) => {
148
- console.log("example/hello response: ", response)
148
+ console.log("example/hello response:", response)
149
149
  mqtt.end()
150
150
  })
151
151
  })
@@ -184,7 +184,7 @@ The **MQTT+** API provides the following methods:
184
184
  - `timeout`: Communication timeout in milliseconds (default: `10000`).
185
185
  - `chunkSize`: Chunk size in bytes for stream transfers (default: `16384`).
186
186
  - `topicMake`: Custom topic generation function.
187
- The `operation` parameter is one of: `event-notice`, `stream-chunk`, `service-call`, `resource-transfer`.
187
+ The `operation` parameter is one of: `event-emission`, `stream-transfer`, `service-call-request`, `service-call-response`, `resource-transfer-request`, `resource-transfer-response`.
188
188
  (default: `` (name, operation, peerId) => `${name}/${operation}` + (peerId ? `/${peerId}` : "/any") ``)
189
189
  - `topicMatch`: Custom topic matching function.
190
190
  Returns `{ name, operation, peerId? }` or `null` if no match. The `peerId` is `undefined` for broadcast topics (ending with `/any`).
@@ -209,8 +209,8 @@ The **MQTT+** API provides the following methods:
209
209
  There is no return value of `callback`.
210
210
 
211
211
  Internally, on the MQTT broker, the topics generated by
212
- `topicMake(event, "event-notice")` (default: `${event}/event-notice/any` and
213
- `${event}/event-notice/${peerId}`) are subscribed. Returns a
212
+ `topicMake(event, "event-emission")` (default: `${event}/event-emission/any` and
213
+ `${event}/event-emission/${peerId}`) are subscribed. Returns a
214
214
  `Subscription` object with an `unsubscribe()` method.
215
215
 
216
216
  - **Stream Attachment**:<br/>
@@ -233,8 +233,8 @@ The **MQTT+** API provides the following methods:
233
233
  There is no return value of `callback`.
234
234
 
235
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
236
+ `topicMake(stream, "stream-transfer")` (default: `${stream}/stream-transfer/any` and
237
+ `${stream}/stream-transfer/${peerId}`) are subscribed. Returns an
238
238
  `Attachment` object with an `unattach()` method.
239
239
 
240
240
  - **Service Registration**:<br/>
@@ -256,8 +256,8 @@ The **MQTT+** API provides the following methods:
256
256
  The return value of `callback` will resolve the `Promise` returned by the remote `call()`.
257
257
 
258
258
  Internally, on the MQTT broker, the topics by
259
- `topicMake(service, "service-call")` (default: `${service}/service-call/any` and
260
- `${service}/service-call/${peerId}`) are subscribed. Returns a
259
+ `topicMake(service, "service-call-request")` (default: `${service}/service-call-request/any` and
260
+ `${service}/service-call-request/${peerId}`) are subscribed. Returns a
261
261
  `Registration` object with an `unregister()` method.
262
262
 
263
263
  - **Resource Provisioning**:<br/>
@@ -268,19 +268,19 @@ The **MQTT+** API provides the following methods:
268
268
  options?: MQTT::IClientSubscribeOptions
269
269
  callback: (
270
270
  ...params: any[],
271
- info: { sender: string, receiver?: string }
272
- ) => any
271
+ info: { sender: string, receiver?: string, resource: Buffer | null }
272
+ ) => void
273
273
  ): Promise<Provisioning>
274
274
 
275
275
  Provision a resource.
276
276
  The `resource` has to be a valid MQTT topic name.
277
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.
278
+ The `callback` is called with the `params` passed to a remote `fetch()`.
279
+ The `callback` should set `info.resource` to a `Buffer` containing the resource data.
280
280
 
281
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
282
+ `topicMake(resource, "resource-transfer-request")` (default: `${resource}/resource-transfer-request/any` and
283
+ `${resource}/resource-transfer-request/${peerId}`) are subscribed. Returns a
284
284
  `Provisioning` object with an `unprovision()` method.
285
285
 
286
286
  - **Event Emission**:<br/>
@@ -300,8 +300,8 @@ The **MQTT+** API provides the following methods:
300
300
  The remote `subscribe()` `callback` is called with `params` and its
301
301
  return value is silently ignored.
302
302
 
303
- Internally, publishes to the MQTT topic by `topicMake(event, "event-notice", peerId)`
304
- (default: `${event}/event-notice/any` or `${event}/event-notice/${peerId}`).
303
+ Internally, publishes to the MQTT topic by `topicMake(event, "event-emission", peerId)`
304
+ (default: `${event}/event-emission/any` or `${event}/event-emission/${peerId}`).
305
305
 
306
306
  - **Stream Transfer**:<br/>
307
307
 
@@ -327,8 +327,8 @@ The **MQTT+** API provides the following methods:
327
327
  The remote `attach()` `callback` is called with `params` and an `info` object
328
328
  containing a `stream.Readable` for consuming the transferred data.
329
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}`).
330
+ Internally, publishes to the MQTT topic by `topicMake(stream, "stream-transfer", peerId)`
331
+ (default: `${stream}/stream-transfer/any` or `${stream}/stream-transfer/${peerId}`).
332
332
 
333
333
  - **Service Call**:<br/>
334
334
 
@@ -348,8 +348,8 @@ The **MQTT+** API provides the following methods:
348
348
  return value resolves the returned `Promise`. If the remote `callback`
349
349
  throws an exception, this rejects the returned `Promise`.
350
350
 
351
- Internally, on the MQTT broker, the topic by `topicMake(service, "service-call", peerId)`
352
- (default: `${service}/service-call/${peerId}`) is temporarily subscribed
351
+ Internally, on the MQTT broker, the topic by `topicMake(service, "service-call-response", peerId)`
352
+ (default: `${service}/service-call-response/${peerId}`) is temporarily subscribed
353
353
  for receiving the response.
354
354
 
355
355
  - **Resource Transfer**:<br/>
@@ -371,8 +371,8 @@ The **MQTT+** API provides the following methods:
371
371
  throws an exception, this rejects the returned `Promise`.
372
372
 
373
373
  Internally, on the MQTT broker, the topic by
374
- `topicMake(resource, "resource-transfer", peerId)` (default:
375
- `${resource}/resource-transfer/${peerId}`) is temporarily subscribed
374
+ `topicMake(resource, "resource-transfer-response", peerId)` (default:
375
+ `${resource}/resource-transfer-response/${peerId}`) is temporarily subscribed
376
376
  for receiving the response.
377
377
 
378
378
  - **Receiver Wrapping**:<br/>
@@ -407,15 +407,16 @@ mqttp.call("example/hello", "world", 42).then((result) => {
407
407
  ```
408
408
 
409
409
  ...the following message is sent to the permanent MQTT topic
410
- `example/hello/service-call/any` (the shown NanoIDs are just pseudo
411
- ones):
410
+ `example/hello/service-call-request/any` (the shown NanoIDs are just
411
+ pseudo ones):
412
412
 
413
413
  ```json
414
414
  {
415
+ "type": "service-call-request",
415
416
  "id": "vwLzfQDu2uEeOdOfIlT42",
416
- "sender": "2IBMSk0NPnrz1AeTERoea",
417
- "method": "example/hello",
418
- "params": [ "world", 42 ]
417
+ "service": "example/hello",
418
+ "params": [ "world", 42 ],
419
+ "sender": "2IBMSk0NPnrz1AeTERoea"
419
420
  }
420
421
  ```
421
422
 
@@ -430,13 +431,15 @@ mqttp.register("example/hello", (a1, a2) => {
430
431
  ...and then its result, in the above `mqttp.call()` example `"world:42"`, is then
431
432
  sent back as the following success response
432
433
  message to the temporary (client-specific) MQTT topic
433
- `example/hello/service-call/2IBMSk0NPnrz1AeTERoea`:
434
+ `example/hello/service-call-response/2IBMSk0NPnrz1AeTERoea`:
434
435
 
435
436
  ```json
436
437
  {
437
- "id": "vwLzfQDu2uEeOdOfIlT42",
438
- "sender": "2IBMSk0NPnrz1AeTERoea",
439
- "result": "world:42"
438
+ "type": "service-call-response",
439
+ "id": "vwLzfQDu2uEeOdOfIlT42",
440
+ "result": "world:42",
441
+ "sender": "2IBMSk0NPnrz1AeTERoea",
442
+ "receiver": "2IBMSk0NPnrz1AeTERoea"
440
443
  }
441
444
  ```
442
445
 
@@ -542,18 +545,18 @@ mqtt.on("close", () => { console.log("CLOSE") })
542
545
  mqtt.on("reconnect", () => { console.log("RECONNECT") })
543
546
  mqtt.on("message", (topic, message) => { console.log("RECEIVED", topic, message.toString()) })
544
547
 
545
- mqtt.on("connect", () => {
548
+ mqtt.on("connect", async () => {
546
549
  console.log("CONNECT")
547
- mqttp.register("example/hello", (a1, a2) => {
548
- console.log("example/hello: request: ", a1, a2)
550
+ await mqttp.register("example/hello", (a1, a2, info) => {
551
+ console.log("example/hello: request:", a1, a2, "from:", info.sender)
549
552
  return `${a1}:${a2}`
550
553
  })
551
- mqttp.call("example/hello", "world", 42).then((result) => {
552
- console.log("example/hello success: ", result)
554
+ mqttp.call("example/hello", "world", 42).then(async (result) => {
555
+ console.log("example/hello success:", result)
553
556
  mqtt.end()
554
557
  await mosquitto.stop()
555
558
  }).catch((err) => {
556
- console.log("example/hello error: ", err)
559
+ console.log("example/hello error:", err)
557
560
  })
558
561
  })
559
562
  ```
@@ -563,17 +566,29 @@ The output will be:
563
566
  ```
564
567
  $ node sample.ts
565
568
  CONNECT
566
- RECEIVED example/hello/service-call/any {"id":"vwLzfQDu2uEeOdOfIlT42","sender":"2IBMSk0NPnrz1AeTERoea","service":"example/hello","params":["world",42]}
567
- example/hello: request: world 42 undefined
568
- RECEIVED example/hello/service-call/2IBMSk0NPnrz1AeTERoea {"id":"vwLzfQDu2uEeOdOfIlT42","sender":"2IBMSk0NPnrz1AeTERoea","receiver":"2IBMSk0NPnrz1AeTERoea","result":"world:42"}
569
- example/hello success: world:42
569
+ RECEIVED example/hello/service-call-request/any {"id":"vwLzfQDu2uEeOdOfIlT42","sender":"2IBMSk0NPnrz1AeTERoea","service":"example/hello","params":["world",42]}
570
+ example/hello: request: world 42 from: 2IBMSk0NPnrz1AeTERoea
571
+ RECEIVED example/hello/service-call-response/2IBMSk0NPnrz1AeTERoea {"id":"vwLzfQDu2uEeOdOfIlT42","sender":"2IBMSk0NPnrz1AeTERoea","receiver":"2IBMSk0NPnrz1AeTERoea","result":"world:42"}
572
+ example/hello success: world:42
570
573
  CLOSE
571
574
  ```
572
575
 
576
+ Notice
577
+ ------
578
+
579
+ > [!Note]
580
+ > **MQTT+** is somewhat similar to and originally derived from
581
+ > [MQTT-JSON-RPC](https://github.com/rse/mqtt-json-rpc) of the same
582
+ > author, but instead of just JSON, MQTT+ encodes packets as JSON
583
+ > or CBOR (default), uses an own packet format (allowing sender and
584
+ > receiver information), uses shorter NanoIDs instead of longer UUIDs
585
+ > for identification of sender, receiver and requests, and additionally
586
+ > provides stream transfer and resource transfer support.
587
+
573
588
  License
574
589
  -------
575
590
 
576
- Copyright (c) 2018-2025 Dr. Ralf S. Engelschall (http://engelschall.com/)
591
+ Copyright (c) 2018-2026 Dr. Ralf S. Engelschall (http://engelschall.com/)
577
592
 
578
593
  Permission is hereby granted, free of charge, to any person obtaining
579
594
  a copy of this software and associated documentation files (the
@@ -0,0 +1,18 @@
1
+
2
+ shape: sequence_diagram
3
+
4
+ client: "Client"
5
+ broker: "Broker"
6
+ server: "Server"
7
+
8
+ broker.class: brown
9
+
10
+ subscribe("foo/bar"): {
11
+ server -> broker: "op: subscribe\ntopic: foo/bar/event-emission/any"
12
+ }
13
+
14
+ emit("foo/bar"): {
15
+ client -> broker: "op: publish\ntopic: foo/bar/event-emission/any\ndata: Event-Emission"
16
+ broker -> server: "op: publish\ntopic: foo/bar/event-emission/any\ndata: Event-Emission"
17
+ }
18
+
@@ -0,0 +1,104 @@
1
+ <?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-d2-version="v0.7.0-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 670 524"><svg class="d2-1243767131 d2-svg" width="670" height="524" viewBox="11 51 670 524"><rect x="11.000000" y="51.000000" width="670.000000" height="524.000000" rx="0.000000" fill="#ffffff" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
2
+ .d2-1243767131 .text {
3
+ font-family: "d2-1243767131-font-regular";
4
+ }
5
+ @font-face {
6
+ font-family: d2-1243767131-font-regular;
7
+ src: url("data:application/font-woff;base64,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");
8
+ }
9
+ .d2-1243767131 .text-italic {
10
+ font-family: "d2-1243767131-font-italic";
11
+ }
12
+ @font-face {
13
+ font-family: d2-1243767131-font-italic;
14
+ src: url("data:application/font-woff;base64,d09GRgABAAAAAA4IAAoAAAAAFeQAARhRAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAAA9AAAAGAAAABgW1SVeGNtYXAAAAFUAAAAoAAAANQD2QQrZ2x5ZgAAAfQAAAdyAAAKWLLv+VRoZWFkAAAJaAAAADYAAAA2G7Ur2mhoZWEAAAmgAAAAJAAAACQLeAjIaG10eAAACcQAAACQAAAAkDmFBIVsb2NhAAAKVAAAAEoAAABKNxo0hm1heHAAAAqgAAAAIAAAACAAPAD2bmFtZQAACsAAAAMmAAAIMgntVzNwb3N0AAAN6AAAACAAAAAg/8YAMgADAeEBkAAFAAACigJY//EASwKKAlgARAFeADIBIwAAAgsFAwMEAwkCBCAAAHcAAAADAAAAAAAAAABBREJPAAEAIP//Au7/BgAAA9gBESAAAZMAAAAAAeYClAAAACAAA3icfM05LgcBAEfhb8zYxxj7PkRFIXolcQIn0AjRiELiNmK5gAtQOgYnoFGofmKi/r/2Kx4KpQK1yhs6rVJjy7Zde/YdOHLsxJkLV27cJvS+8++HvZ86d+n6z/ORn3znM195z2te8pynPOYh97nrb4MqrNnQWbdp1ZBSZdiIUWPGTZhUm9KY1poxa868BYuWLFvhFwAA//8BAAD//+DjJ+x4nHxWXWzb1tl+zyEt+kf+kSiRlmyZkiiRskxJNmmJtmVJlv/kHymJnTo1kshx+rVBviQrvHbZsqVBsxQIimHLMiAYkKJbihYFuvRiRXqzH6ADimHwOuRiQDZkCNZh6+oMyYo0hlCsRU0Oh3Jl2Re7oQ8snvd9n+c8z3MIDRACwM/ia0BBE7SDE9wAGhugKE3XRZ7SZFlkGF1mWSZ0Ca1feoWeOPxx5LXPFYEufPdn8/9efRtf2zqDLpZffNE48vIzzzz58KERRX9+CABAgQiA/fgqOMBL1hqrqZzbZbMxDGf9FSlNTSUHJXFnIb7082Nn+iZCSJsuvLBvZGXl8NTckdPPrTxbmv0GvjpXUCaVRtqeH5otK+ibBT2mbj2YKqoZ0g/BsFnBMfwqCAANQUlKDmaxpnI8I0lisA27XRynqSmdt9lQcP5kqv/wheLQQmeKTUkjx8ZDwbl0ZMIvhsr2iXP7Ste+VdCjvX458/S50XQ56e9ShZjVg2DqwFehxeKJCTAaIzIBRnwJnWo1Poo+bnukIakNX83/afyzcUBgNyvIwK9CFIAPSrJuzZAclGSZDJhK1Qa02dwujuerzNyfWIsM+5b00YVYuBhNJ4+m06uC5pmOh5O+gVAxMZg+YR8Z6etTJ4dCKhf3zurqojoYiff0Cv1dUoKLdRf0kSODgEE2K+g/aBNcZHI+WCNF0zVK1EWbTVZTul5j6N2xojK3oskZB81mj+caaXHZKe0PKW61OzSRFAbsR5amv31UiwQyhncmnBiLJ/4iBaOzZTVnnQEGwaygT/E6uImyCGKREVmNYTQLqtvVhmU1i8lZBy0dPJAzDsqVu1KSORx6Ima1T4Ymkj39vcEFMe7S7JFABq+/t+rrO3yItB6Lzpa1bCYavi8FAUHYrKBbaBO6d6HbYXT7xO/uf1opHU8qo1yMlXz9h1LDI/4UF/SW7CfKk88vJYKeft49uTYxPu11qK5wFYtsVrBch2WHu/9N3oiT6pBKV7fZ2xfey57sP/be1tBe+rCF5TdoE7wQru9HFMEEbDX1UlqKKIgg/Oeh/4/NH+3X8z32BuO3Tf6JqG+Y7/EtXDcx5ewVkyv2U8en1haV+AG1W2vLHQh7HJpbQOGWztbuAWEJEPQBoB/gO8ATPYs5XK9Ihoib6lvKteQ72vdlvFFnV3OXI9Db6HjK/n9L6K3hhoW5g60tOtOs9h3MGsvEHwIAuovXwWP5fefcGUpkydikLCVcKfV30L2LSjbZmC2O0vRM90x8Cq8/zIiJ/JAQMj5AiquzdT4aN94yTVITvsC3sAR+ALBBYKbqd9LrE7wOdqsXRfqxoswwwpXSKv58+f2z+8prXrxu+BD6g/HxJ8+dBwSKWYEv8Do4CcPJQZ3VKKKTbWq/lredL11AyEHZGNTM2XMODz699SOmiXIinKbpWl/8AG0ST5OeVYj8NlDbLqT1oI/nGFo6KI0MNCSWw5kUTWdLGZouuGeUKcLBNDfTN4U2ZkMDekTR8kOOHlc9DzurGva7aBM662fYSzPp2LsY38Wy1WEvyTW9o3toE9rBV6+/qmktzW2b6s7+FWVuRd1/TJlficYWtJRKHvaTR6aeX4pXn2Pja5PjhYm1yfFpUtv8zNTQp2iz6iWmbuI2LFopwbC7cqH5ezkbFV6KW5ZSpVEWO4U363PhNn53TIhtG0o4eQOh7WCQ/hUO7OjjBbQJHXUc8Yz0FTcttK8Y87i7OryhopBBG2Ul0zTZmEsbtwGZX5oVdAFtgrw3t/fGNkntami/MVD29PNjUjTTOxQfVmaV+Fx3nNUC0kDKnx3sX7QPRiQhEhe9suDN9vblw6GeiMsbE3okZ3BUiU2GycyjZgUt4zO1PEvpxJWa5cS6PPvl2CCNhgstxVC+67z9wjDVHWzztjg6EvZcrN3bipzDDZcvZ40HTmdPT3ODzrST2kNmBT1CG8SbX9XeUT+7HWlv15Q54ysoU0VyCUSesI/rDoFFKeMO6yGSQcuGd07UqjynAdDf0Qa0AhAXchyvpUhBdKlQDNE2mnaE2B+WjC20YdwX58XQbAh5DK+113zfTKCP0AZ4ARiLZzKLvqtKG7Y1+9s8Tmc473EeLEoNjRTtCDu/XzT+4UnP/JFhhpsyqojuG48CJVEsBpFj63GipACAaQJvVmAd/RXLEIQx9HWwQdD6/0V0E/0Y3wCJ3IvAgAhvbt/vH6Jm5AEKQNc1RrTfa/0wna7izJsH0JP4HrQD8FUZ6LzN+q7gv9MZ0E/OxU6daXK1vTP2xuLZ3/+67Lls/O2n8ROrEsF5xzwAD7b3yiknyW8iKqIbFDt1usnZrpIS73gvo8BPEieOSezY64tnP/gV2fsLcxW9jn9HZmKQhmbQrSGj9Bp14stXajqH22iD/E5yUDheegptWAQjKOB5uIVvke8V1jrzakidY3tE3uUT8TzPeQKdnMcPyOLqZThD3uXr3p3mPHI31xm2d3Nexcd5FFL3IroJj/ENaABgCVHMRb59no2im9ePHr3+XwAAAP//AQAA//9yvyB0AAAAAQAAAAEYUT3lfz9fDzz1AAED6AAAAADYXaDMAAAAAN1mLzf+vf7dCB0DyQACAAMAAgAAAAAAAAABAAAD2P7vAAAIQP69/bwIHQPoAML/0QAAAAAAAAAAAAAAJAJ0ACQAyAAAAkcAIwImADkB9wAjAfoADAIZACcCGAAfAbMAJQIXACcB4QAlARoAKwILAB8A7QAfAdwAHwD4ACwDHwAfAg0AHwIDACcCF//2AVYAHwGS//wBRQA8AhAAOAHAADsBwP/CAPIAFwGXAIABKwAjASMAQQEl/9QBVP+4AO0AHwAAAEcA8gAXAPIAgAAAAC4ALgBmAJgAsADwASgBYAGOAcYCAAIoAlICXgJ4ApoC3AMGAzQDbgOMA8gD9gQiBEAEcAR8BIgElgS0BNIE4gTwBQYFHAUsAAAAAQAAACQAjAAMAGYABwABAAAAAAAAAAAAAAAAAAQAA3icnJTbThtXFIY/B9tterqoUERu0L5MpWRMoxAl4cqUoIyKcOpxepCqSoM9PojxzMgzmJIn6HXfom+Rqz5Gn6LqdbV/L4MdRUEgBPx79jr8a61/bWCT/9igVr8L/N2cG66x3fzZ8B2+aB4Z3mC/+ZnhOg8b/xhuMGi8NdzkQaNr+BPe1f80/ClP6r8ZvstW/dDw5zyubxr+csPxr+GveMK7Ba7BM/4wXGOLwvAdNvnV8Ab3sJi1OvfYMdzga7YNN9kGekyoSJmQMcIxZMKIM2YklEQkzJgwJGGAI6RNSqWvGbGQY/TBrzERFTNiRRxT4UiJSIkpGVvEt/LKea2MQ51mdtemYkzMiTxOiclw5IzIyUg4VZyKioIXtGhR0hffgoqSgJIJKQE5M0a06HDIET3GTChxHCqSZxaRM6TinFj5nVn4zvRJyCiN1RkZA/F04pfIO+QIR4dCtquRj9YiPMTxo7w9t1y23xLo160wW8+7ZBMzVz9TdSXVzbkmONatz9vmB+GKF7hb9WedyfU9Guh/pcgnnGn+A00qE5MM57ZoE0lBkbuPY1/nkEgd+YmQHq/o8Iaezm26dGlzTI+Ql/Lt0MXxHR2OOZBHKLy4O5RijvkFx/eEsvGxE+vPYmIJv1OYuktxnKmOKYV67pkHqjVRhTefsN+hfE0dpXz62iNv6TS/THsWMzJVFGI4VS+X2iitfwNTxFS1+Nle3fttmNvuLbf4glw77NW64OQnt2B03VSD9zRzrp+AmAE5J7LokzOlRcWFeL8m5owUx4G690pbUtG+9PF5LqSShKkYhGSKM6PQ39h0Exn3/prunb0lA/l7pqeXVd0mi1Ovrmb0Rt1b3kXW5WRlAi2bar6ipr64Zqb9RDu1yj+Sb6nXLecRoeIudvtDr8AOz9llj7Gy9HUzv7zzr4S32FMHTklkNZSmfQ2PCdgl4Cm77PKcp+/1csnGGR+3xmc1f5sD9umwd201C9sO+7xci/bxzH+J7Y7qcTy6PD279TQf3EC132jfrt7NribnpzG3aFfbcUzM1HNxW6s1ufsE/wMAAP//AQAA//9yoVFAAAAAAwAA//UAAP/OADIAAAAAAAAAAAAAAAAAAAAAAAAAAA==");
15
+ }]]></style><style type="text/css"><![CDATA[.shape {
16
+ shape-rendering: geometricPrecision;
17
+ stroke-linejoin: round;
18
+ }
19
+ .connection {
20
+ stroke-linecap: round;
21
+ stroke-linejoin: round;
22
+ }
23
+ .blend {
24
+ mix-blend-mode: multiply;
25
+ opacity: 0.5;
26
+ }
27
+
28
+ .d2-1243767131 .fill-N1{fill:#303030;}
29
+ .d2-1243767131 .fill-N2{fill:#606060;}
30
+ .d2-1243767131 .fill-N3{fill:#909090;}
31
+ .d2-1243767131 .fill-N4{fill:#c0c0c0;}
32
+ .d2-1243767131 .fill-N5{fill:#e0e0e0;}
33
+ .d2-1243767131 .fill-N6{fill:#f0f0f0;}
34
+ .d2-1243767131 .fill-N7{fill:#ffffff;}
35
+ .d2-1243767131 .fill-B1{fill:#336699;}
36
+ .d2-1243767131 .fill-B2{fill:#6699cc;}
37
+ .d2-1243767131 .fill-B3{fill:#99ccff;}
38
+ .d2-1243767131 .fill-B4{fill:#c0d0ff;}
39
+ .d2-1243767131 .fill-B5{fill:#e0f0ff;}
40
+ .d2-1243767131 .fill-B6{fill:#f0f8ff;}
41
+ .d2-1243767131 .fill-AA2{fill:#cfb098;}
42
+ .d2-1243767131 .fill-AA4{fill:#efd0b8;}
43
+ .d2-1243767131 .fill-AA5{fill:#ffe0c8;}
44
+ .d2-1243767131 .fill-AB4{fill:#efd0b8;}
45
+ .d2-1243767131 .fill-AB5{fill:#ffe0c8;}
46
+ .d2-1243767131 .stroke-N1{stroke:#303030;}
47
+ .d2-1243767131 .stroke-N2{stroke:#606060;}
48
+ .d2-1243767131 .stroke-N3{stroke:#909090;}
49
+ .d2-1243767131 .stroke-N4{stroke:#c0c0c0;}
50
+ .d2-1243767131 .stroke-N5{stroke:#e0e0e0;}
51
+ .d2-1243767131 .stroke-N6{stroke:#f0f0f0;}
52
+ .d2-1243767131 .stroke-N7{stroke:#ffffff;}
53
+ .d2-1243767131 .stroke-B1{stroke:#336699;}
54
+ .d2-1243767131 .stroke-B2{stroke:#6699cc;}
55
+ .d2-1243767131 .stroke-B3{stroke:#99ccff;}
56
+ .d2-1243767131 .stroke-B4{stroke:#c0d0ff;}
57
+ .d2-1243767131 .stroke-B5{stroke:#e0f0ff;}
58
+ .d2-1243767131 .stroke-B6{stroke:#f0f8ff;}
59
+ .d2-1243767131 .stroke-AA2{stroke:#cfb098;}
60
+ .d2-1243767131 .stroke-AA4{stroke:#efd0b8;}
61
+ .d2-1243767131 .stroke-AA5{stroke:#ffe0c8;}
62
+ .d2-1243767131 .stroke-AB4{stroke:#efd0b8;}
63
+ .d2-1243767131 .stroke-AB5{stroke:#ffe0c8;}
64
+ .d2-1243767131 .background-color-N1{background-color:#303030;}
65
+ .d2-1243767131 .background-color-N2{background-color:#606060;}
66
+ .d2-1243767131 .background-color-N3{background-color:#909090;}
67
+ .d2-1243767131 .background-color-N4{background-color:#c0c0c0;}
68
+ .d2-1243767131 .background-color-N5{background-color:#e0e0e0;}
69
+ .d2-1243767131 .background-color-N6{background-color:#f0f0f0;}
70
+ .d2-1243767131 .background-color-N7{background-color:#ffffff;}
71
+ .d2-1243767131 .background-color-B1{background-color:#336699;}
72
+ .d2-1243767131 .background-color-B2{background-color:#6699cc;}
73
+ .d2-1243767131 .background-color-B3{background-color:#99ccff;}
74
+ .d2-1243767131 .background-color-B4{background-color:#c0d0ff;}
75
+ .d2-1243767131 .background-color-B5{background-color:#e0f0ff;}
76
+ .d2-1243767131 .background-color-B6{background-color:#f0f8ff;}
77
+ .d2-1243767131 .background-color-AA2{background-color:#cfb098;}
78
+ .d2-1243767131 .background-color-AA4{background-color:#efd0b8;}
79
+ .d2-1243767131 .background-color-AA5{background-color:#ffe0c8;}
80
+ .d2-1243767131 .background-color-AB4{background-color:#efd0b8;}
81
+ .d2-1243767131 .background-color-AB5{background-color:#ffe0c8;}
82
+ .d2-1243767131 .color-N1{color:#303030;}
83
+ .d2-1243767131 .color-N2{color:#606060;}
84
+ .d2-1243767131 .color-N3{color:#909090;}
85
+ .d2-1243767131 .color-N4{color:#c0c0c0;}
86
+ .d2-1243767131 .color-N5{color:#e0e0e0;}
87
+ .d2-1243767131 .color-N6{color:#f0f0f0;}
88
+ .d2-1243767131 .color-N7{color:#ffffff;}
89
+ .d2-1243767131 .color-B1{color:#336699;}
90
+ .d2-1243767131 .color-B2{color:#6699cc;}
91
+ .d2-1243767131 .color-B3{color:#99ccff;}
92
+ .d2-1243767131 .color-B4{color:#c0d0ff;}
93
+ .d2-1243767131 .color-B5{color:#e0f0ff;}
94
+ .d2-1243767131 .color-B6{color:#f0f8ff;}
95
+ .d2-1243767131 .color-AA2{color:#cfb098;}
96
+ .d2-1243767131 .color-AA4{color:#efd0b8;}
97
+ .d2-1243767131 .color-AA5{color:#ffe0c8;}
98
+ .d2-1243767131 .color-AB4{color:#efd0b8;}
99
+ .d2-1243767131 .color-AB5{color:#ffe0c8;}.appendix text.text{fill:#303030}.md{--color-fg-default:#303030;--color-fg-muted:#606060;--color-fg-subtle:#909090;--color-canvas-default:#ffffff;--color-canvas-subtle:#f0f0f0;--color-border-default:#336699;--color-border-muted:#6699cc;--color-neutral-muted:#f0f0f0;--color-accent-fg:#6699cc;--color-accent-emphasis:#6699cc;--color-attention-subtle:#606060;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-dark-d2-1243767131);mix-blend-mode:overlay}.sketch-overlay-B2{fill:url(#streaks-normal-d2-1243767131);mix-blend-mode:color-burn}.sketch-overlay-B3{fill:url(#streaks-normal-d2-1243767131);mix-blend-mode:color-burn}.sketch-overlay-B4{fill:url(#streaks-normal-d2-1243767131);mix-blend-mode:color-burn}.sketch-overlay-B5{fill:url(#streaks-bright-d2-1243767131);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-1243767131);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-normal-d2-1243767131);mix-blend-mode:color-burn}.sketch-overlay-AA4{fill:url(#streaks-normal-d2-1243767131);mix-blend-mode:color-burn}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-1243767131);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-normal-d2-1243767131);mix-blend-mode:color-burn}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-1243767131);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-1243767131);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-1243767131);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-1243767131);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-1243767131);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-normal-d2-1243767131);mix-blend-mode:color-burn}.sketch-overlay-N6{fill:url(#streaks-bright-d2-1243767131);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-1243767131);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g class="Y2xpZW50"><g class="shape" ><rect x="12.000000" y="52.000000" width="100.000000" height="66.000000" stroke="#336699" fill="#e0f0ff" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="62.000000" y="90.500000" fill="#303030" class="text fill-N1" style="text-anchor:middle;font-size:16px">Client</text></g><g class="YnJva2Vy brown"><g class="shape" ><rect x="296.000000" y="52.000000" width="100.000000" height="66.000000" stroke="#cfb098" fill="#ffe0c9" style="stroke-width:2;" /></g><text x="346.000000" y="90.500000" fill="#303030" class="text fill-N1" style="text-anchor:middle;font-size:16px">Broker</text></g><g class="c2VydmVy"><g class="shape" ><rect x="580.000000" y="52.000000" width="100.000000" height="66.000000" stroke="#336699" fill="#e0f0ff" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="630.000000" y="90.500000" fill="#303030" class="text fill-N1" style="text-anchor:middle;font-size:16px">Server</text></g><g class="KGNsaWVudCAtLSApWzBd"><path d="M 62.000000 120.000000 L 62.000000 573.000000" stroke="#6699cc" fill="none" class="connection stroke-B2" style="stroke-width:2;stroke-dasharray:12.000000,11.838767;" mask="url(#d2-1243767131)" /></g><g class="KGJyb2tlciAtLSApWzBd"><path d="M 346.000000 120.000000 L 346.000000 573.000000" stroke="#cfb098" fill="none" class="connection" style="stroke-width:2;stroke-dasharray:12.000000,11.838767;" mask="url(#d2-1243767131)" /></g><g class="KHNlcnZlciAtLSApWzBd"><path d="M 630.000000 120.000000 L 630.000000 573.000000" stroke="#6699cc" fill="none" class="connection stroke-B2" style="stroke-width:2;stroke-dasharray:12.000000,11.838767;" mask="url(#d2-1243767131)" /></g><g class="JiMzOTtzdWJzY3JpYmUoJiMzNDtmb28vYmFyJiMzNDspJiMzOTs="><g class="shape blend" ><rect x="306.000000" y="175.000000" width="364.000000" height="92.000000" stroke="#336699" fill="#e0e0e0" class=" stroke-B1 fill-N5" style="stroke-width:0;" /></g><rect x="311.000000" y="180.000000" width="138.000000" height="21.000000" fill="#e0e0e0" class=" fill-N5" /><text x="380.000000" y="196.000000" fill="#303030" class="text fill-N1" style="text-anchor:middle;font-size:16px">subscribe(&#34;foo/bar&#34;)</text></g><g class="JiMzOTtlbWl0KCYjMzQ7Zm9vL2JhciYjMzQ7KSYjMzk7"><g class="shape blend" ><rect x="22.000000" y="312.000000" width="648.000000" height="230.000000" stroke="#336699" fill="#e0e0e0" class=" stroke-B1 fill-N5" style="stroke-width:0;" /></g><rect x="27.000000" y="317.000000" width="104.000000" height="21.000000" fill="#e0e0e0" class=" fill-N5" /><text x="79.000000" y="333.000000" fill="#303030" class="text fill-N1" style="text-anchor:middle;font-size:16px">emit(&#34;foo/bar&#34;)</text></g><g class="KHNlcnZlciAtJmd0OyBicm9rZXIpWzBd"><marker id="mk-d2-1243767131-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" fill="#336699" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 628.000000 237.000000 L 350.000000 237.000000" stroke="#336699" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-1243767131-3488378134)" mask="url(#d2-1243767131)" /><text x="488.000000" y="235.000000" fill="#606060" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px"><tspan x="488.000000" dy="0.000000">op: subscribe</tspan><tspan x="488.000000" dy="18.500000">topic: foo/bar/event-emission/any</tspan></text></g><g class="KGNsaWVudCAtJmd0OyBicm9rZXIpWzBd"><path d="M 64.000000 382.000000 L 342.000000 382.000000" stroke="#336699" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-1243767131-3488378134)" mask="url(#d2-1243767131)" /><text x="204.000000" y="372.000000" fill="#606060" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px"><tspan x="204.000000" dy="0.000000">op: publish</tspan><tspan x="204.000000" dy="17.666667">topic: foo/bar/event-emission/any</tspan><tspan x="204.000000" dy="17.666667">data: Event-Emission</tspan></text></g><g class="KGJyb2tlciAtJmd0OyBzZXJ2ZXIpWzBd"><path d="M 348.000000 504.000000 L 626.000000 504.000000" stroke="#336699" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-1243767131-3488378134)" mask="url(#d2-1243767131)" /><text x="488.000000" y="494.000000" fill="#606060" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px"><tspan x="488.000000" dy="0.000000">op: publish</tspan><tspan x="488.000000" dy="17.666667">topic: foo/bar/event-emission/any</tspan><tspan x="488.000000" dy="17.666667">data: Event-Emission</tspan></text></g><mask id="d2-1243767131" maskUnits="userSpaceOnUse" x="11" y="51" width="670" height="524">
100
+ <rect x="11" y="51" width="670" height="524" fill="white"></rect>
101
+ <rect x="374.000000" y="219.000000" width="228" height="37" fill="black"></rect>
102
+ <rect x="90.000000" y="356.000000" width="228" height="53" fill="black"></rect>
103
+ <rect x="374.000000" y="478.000000" width="228" height="53" fill="black"></rect>
104
+ </mask></svg></svg>
@@ -0,0 +1,22 @@
1
+
2
+ shape: sequence_diagram
3
+
4
+ client: "Client"
5
+ broker: "Broker"
6
+ server: "Server"
7
+
8
+ broker.class: brown
9
+
10
+ attach("foo/bar") {
11
+ server -> broker: "op: subscribe\ntopic: foo/bar/stream-transfer"
12
+ }
13
+
14
+ fetch("foo/bar") {
15
+ client -> broker: "op: publish\ntopic: foo/bar/stream-transfer/any\ndata: Stream-Transfer #1"
16
+ broker -> server: "op: publish\ntopic: foo/bar/stream-transfer/any\ndata: Stream-Transfer #1"
17
+ client -> broker: "op: publish\ntopic: foo/bar/stream-transfer/any\ndata: Stream-Transfer #N"
18
+ broker -> server: "op: publish\ntopic: foo/bar/stream-transfer/any\ndata: Stream-Transfer #N"
19
+ client -> broker: "op: publish\ntopic: foo/bar/stream-transfer/any\ndata: Stream-Transfer EoS"
20
+ broker -> server: "op: publish\ntopic: foo/bar/stream-transfer/any\ndata: Stream-Transfer EoS"
21
+ }
22
+