mqtt-plus 0.9.4 → 0.9.5

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,16 @@ 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
 
87
+ ![Resource Transfer](doc/mqtt-plus-4-resource-transfer.svg)
88
+
81
89
  > [!Note]
82
90
  > **MQTT+** is similar to and derived from
83
91
  > [MQTT-JSON-RPC](https://github.com/rse/mqtt-json-rpc) of the same
84
92
  > author, but instead of just JSON, MQTT+ encodes packets as JSON
85
93
  > or CBOR (default), uses an own packet format (allowing sender and
86
94
  > 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.
95
+ > for identification of sender, receiver and requests, and additionally
96
+ > provides stream transfer and resource transfer support.
89
97
 
90
98
  Usage
91
99
  -----
@@ -100,9 +108,10 @@ pattern of each endpoint:
100
108
  import type * as MQTTpt from "mqtt-plus"
101
109
 
102
110
  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 */
111
+ "example/sample": MQTTpt.Event<(a1: string, a2: number) => void>
112
+ "example/upload": MQTTpt.Stream<(name: string) => void>
113
+ "example/hello": MQTTpt.Service<(a1: string, a2: number) => string>
114
+ "example/resource": MQTTpt.Resource<(filename: string) => void>
106
115
  }
107
116
  ```
108
117
 
@@ -122,11 +131,11 @@ const mqtt = MQTT.connect("wss://127.0.0.1:8883", { ... })
122
131
  const mqttp = new MQTTp<API>(mqtt)
123
132
 
124
133
  mqtt.on("connect", async () => {
125
- mqttp.subscribe("example/sample", (a1, a2) => {
126
- console.log("example/sample: ", a1, a2)
134
+ await mqttp.subscribe("example/sample", (a1, a2, info) => {
135
+ console.log("example/sample:", a1, a2, "from:", info.sender)
127
136
  })
128
- mqttp.register("example/hello", (a1, a2) => {
129
- console.log("example/hello: ", a1, a2)
137
+ await mqttp.register("example/hello", (a1, a2, info) => {
138
+ console.log("example/hello:", a1, a2, "from:", info.sender)
130
139
  return `${a1}:${a2}`
131
140
  })
132
141
  })
@@ -143,9 +152,9 @@ const mqtt = MQTT.connect("wss://127.0.0.1:8883", { ... })
143
152
  const mqttp = new MQTTp<API>(mqtt)
144
153
 
145
154
  mqtt.on("connect", () => {
146
- mqttp.emit("example/sample", "foo", true)
155
+ mqttp.emit("example/sample", "world", 42)
147
156
  mqttp.call("example/hello", "world", 42).then((response) => {
148
- console.log("example/hello response: ", response)
157
+ console.log("example/hello response:", response)
149
158
  mqtt.end()
150
159
  })
151
160
  })
@@ -184,7 +193,7 @@ The **MQTT+** API provides the following methods:
184
193
  - `timeout`: Communication timeout in milliseconds (default: `10000`).
185
194
  - `chunkSize`: Chunk size in bytes for stream transfers (default: `16384`).
186
195
  - `topicMake`: Custom topic generation function.
187
- The `operation` parameter is one of: `event-notice`, `stream-chunk`, `service-call`, `resource-transfer`.
196
+ The `operation` parameter is one of: `event-emission`, `stream-transfer`, `service-call-request`, `service-call-response`, `resource-transfer-request`, `resource-transfer-response`.
188
197
  (default: `` (name, operation, peerId) => `${name}/${operation}` + (peerId ? `/${peerId}` : "/any") ``)
189
198
  - `topicMatch`: Custom topic matching function.
190
199
  Returns `{ name, operation, peerId? }` or `null` if no match. The `peerId` is `undefined` for broadcast topics (ending with `/any`).
@@ -209,8 +218,8 @@ The **MQTT+** API provides the following methods:
209
218
  There is no return value of `callback`.
210
219
 
211
220
  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
221
+ `topicMake(event, "event-emission")` (default: `${event}/event-emission/any` and
222
+ `${event}/event-emission/${peerId}`) are subscribed. Returns a
214
223
  `Subscription` object with an `unsubscribe()` method.
215
224
 
216
225
  - **Stream Attachment**:<br/>
@@ -233,8 +242,8 @@ The **MQTT+** API provides the following methods:
233
242
  There is no return value of `callback`.
234
243
 
235
244
  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
245
+ `topicMake(stream, "stream-transfer")` (default: `${stream}/stream-transfer/any` and
246
+ `${stream}/stream-transfer/${peerId}`) are subscribed. Returns an
238
247
  `Attachment` object with an `unattach()` method.
239
248
 
240
249
  - **Service Registration**:<br/>
@@ -256,8 +265,8 @@ The **MQTT+** API provides the following methods:
256
265
  The return value of `callback` will resolve the `Promise` returned by the remote `call()`.
257
266
 
258
267
  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
268
+ `topicMake(service, "service-call-request")` (default: `${service}/service-call-request/any` and
269
+ `${service}/service-call-request/${peerId}`) are subscribed. Returns a
261
270
  `Registration` object with an `unregister()` method.
262
271
 
263
272
  - **Resource Provisioning**:<br/>
@@ -268,19 +277,19 @@ The **MQTT+** API provides the following methods:
268
277
  options?: MQTT::IClientSubscribeOptions
269
278
  callback: (
270
279
  ...params: any[],
271
- info: { sender: string, receiver?: string }
272
- ) => any
280
+ info: { sender: string, receiver?: string, resource: Buffer | null }
281
+ ) => void
273
282
  ): Promise<Provisioning>
274
283
 
275
284
  Provision a resource.
276
285
  The `resource` has to be a valid MQTT topic name.
277
286
  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.
287
+ The `callback` is called with the `params` passed to a remote `fetch()`.
288
+ The `callback` should set `info.resource` to a `Buffer` containing the resource data.
280
289
 
281
290
  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
291
+ `topicMake(resource, "resource-transfer-request")` (default: `${resource}/resource-transfer-request/any` and
292
+ `${resource}/resource-transfer-request/${peerId}`) are subscribed. Returns a
284
293
  `Provisioning` object with an `unprovision()` method.
285
294
 
286
295
  - **Event Emission**:<br/>
@@ -300,8 +309,8 @@ The **MQTT+** API provides the following methods:
300
309
  The remote `subscribe()` `callback` is called with `params` and its
301
310
  return value is silently ignored.
302
311
 
303
- Internally, publishes to the MQTT topic by `topicMake(event, "event-notice", peerId)`
304
- (default: `${event}/event-notice/any` or `${event}/event-notice/${peerId}`).
312
+ Internally, publishes to the MQTT topic by `topicMake(event, "event-emission", peerId)`
313
+ (default: `${event}/event-emission/any` or `${event}/event-emission/${peerId}`).
305
314
 
306
315
  - **Stream Transfer**:<br/>
307
316
 
@@ -327,8 +336,8 @@ The **MQTT+** API provides the following methods:
327
336
  The remote `attach()` `callback` is called with `params` and an `info` object
328
337
  containing a `stream.Readable` for consuming the transferred data.
329
338
 
330
- Internally, publishes to the MQTT topic by `topicMake(stream, "stream-chunk", peerId)`
331
- (default: `${stream}/stream-chunk/any` or `${stream}/stream-chunk/${peerId}`).
339
+ Internally, publishes to the MQTT topic by `topicMake(stream, "stream-transfer", peerId)`
340
+ (default: `${stream}/stream-transfer/any` or `${stream}/stream-transfer/${peerId}`).
332
341
 
333
342
  - **Service Call**:<br/>
334
343
 
@@ -348,8 +357,8 @@ The **MQTT+** API provides the following methods:
348
357
  return value resolves the returned `Promise`. If the remote `callback`
349
358
  throws an exception, this rejects the returned `Promise`.
350
359
 
351
- Internally, on the MQTT broker, the topic by `topicMake(service, "service-call", peerId)`
352
- (default: `${service}/service-call/${peerId}`) is temporarily subscribed
360
+ Internally, on the MQTT broker, the topic by `topicMake(service, "service-call-response", peerId)`
361
+ (default: `${service}/service-call-response/${peerId}`) is temporarily subscribed
353
362
  for receiving the response.
354
363
 
355
364
  - **Resource Transfer**:<br/>
@@ -371,8 +380,8 @@ The **MQTT+** API provides the following methods:
371
380
  throws an exception, this rejects the returned `Promise`.
372
381
 
373
382
  Internally, on the MQTT broker, the topic by
374
- `topicMake(resource, "resource-transfer", peerId)` (default:
375
- `${resource}/resource-transfer/${peerId}`) is temporarily subscribed
383
+ `topicMake(resource, "resource-transfer-response", peerId)` (default:
384
+ `${resource}/resource-transfer-response/${peerId}`) is temporarily subscribed
376
385
  for receiving the response.
377
386
 
378
387
  - **Receiver Wrapping**:<br/>
@@ -407,15 +416,16 @@ mqttp.call("example/hello", "world", 42).then((result) => {
407
416
  ```
408
417
 
409
418
  ...the following message is sent to the permanent MQTT topic
410
- `example/hello/service-call/any` (the shown NanoIDs are just pseudo
411
- ones):
419
+ `example/hello/service-call-request/any` (the shown NanoIDs are just
420
+ pseudo ones):
412
421
 
413
422
  ```json
414
423
  {
424
+ "type": "service-call-request",
415
425
  "id": "vwLzfQDu2uEeOdOfIlT42",
416
- "sender": "2IBMSk0NPnrz1AeTERoea",
417
- "method": "example/hello",
418
- "params": [ "world", 42 ]
426
+ "service": "example/hello",
427
+ "params": [ "world", 42 ],
428
+ "sender": "2IBMSk0NPnrz1AeTERoea"
419
429
  }
420
430
  ```
421
431
 
@@ -430,13 +440,15 @@ mqttp.register("example/hello", (a1, a2) => {
430
440
  ...and then its result, in the above `mqttp.call()` example `"world:42"`, is then
431
441
  sent back as the following success response
432
442
  message to the temporary (client-specific) MQTT topic
433
- `example/hello/service-call/2IBMSk0NPnrz1AeTERoea`:
443
+ `example/hello/service-call-response/2IBMSk0NPnrz1AeTERoea`:
434
444
 
435
445
  ```json
436
446
  {
437
- "id": "vwLzfQDu2uEeOdOfIlT42",
438
- "sender": "2IBMSk0NPnrz1AeTERoea",
439
- "result": "world:42"
447
+ "type": "service-call-response",
448
+ "id": "vwLzfQDu2uEeOdOfIlT42",
449
+ "result": "world:42",
450
+ "sender": "2IBMSk0NPnrz1AeTERoea",
451
+ "receiver": "2IBMSk0NPnrz1AeTERoea"
440
452
  }
441
453
  ```
442
454
 
@@ -542,18 +554,18 @@ mqtt.on("close", () => { console.log("CLOSE") })
542
554
  mqtt.on("reconnect", () => { console.log("RECONNECT") })
543
555
  mqtt.on("message", (topic, message) => { console.log("RECEIVED", topic, message.toString()) })
544
556
 
545
- mqtt.on("connect", () => {
557
+ mqtt.on("connect", async () => {
546
558
  console.log("CONNECT")
547
- mqttp.register("example/hello", (a1, a2) => {
548
- console.log("example/hello: request: ", a1, a2)
559
+ await mqttp.register("example/hello", (a1, a2, info) => {
560
+ console.log("example/hello: request:", a1, a2, "from:", info.sender)
549
561
  return `${a1}:${a2}`
550
562
  })
551
- mqttp.call("example/hello", "world", 42).then((result) => {
552
- console.log("example/hello success: ", result)
563
+ mqttp.call("example/hello", "world", 42).then(async (result) => {
564
+ console.log("example/hello success:", result)
553
565
  mqtt.end()
554
566
  await mosquitto.stop()
555
567
  }).catch((err) => {
556
- console.log("example/hello error: ", err)
568
+ console.log("example/hello error:", err)
557
569
  })
558
570
  })
559
571
  ```
@@ -563,17 +575,17 @@ The output will be:
563
575
  ```
564
576
  $ node sample.ts
565
577
  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
578
+ RECEIVED example/hello/service-call-request/any {"id":"vwLzfQDu2uEeOdOfIlT42","sender":"2IBMSk0NPnrz1AeTERoea","service":"example/hello","params":["world",42]}
579
+ example/hello: request: world 42 from: 2IBMSk0NPnrz1AeTERoea
580
+ RECEIVED example/hello/service-call-response/2IBMSk0NPnrz1AeTERoea {"id":"vwLzfQDu2uEeOdOfIlT42","sender":"2IBMSk0NPnrz1AeTERoea","receiver":"2IBMSk0NPnrz1AeTERoea","result":"world:42"}
581
+ example/hello success: world:42
570
582
  CLOSE
571
583
  ```
572
584
 
573
585
  License
574
586
  -------
575
587
 
576
- Copyright (c) 2018-2025 Dr. Ralf S. Engelschall (http://engelschall.com/)
588
+ Copyright (c) 2018-2026 Dr. Ralf S. Engelschall (http://engelschall.com/)
577
589
 
578
590
  Permission is hereby granted, free of charge, to any person obtaining
579
591
  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,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");
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
+