mqtt-plus 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/AGENTS.md +55 -44
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +4 -3
  4. package/doc/mqtt-plus-api.md +693 -680
  5. package/doc/mqtt-plus-architecture.d2 +139 -0
  6. package/doc/mqtt-plus-architecture.md +10 -0
  7. package/doc/mqtt-plus-architecture.svg +95 -0
  8. package/doc/mqtt-plus-broker-setup.md +9 -3
  9. package/doc/mqtt-plus-comm.md +73 -0
  10. package/doc/mqtt-plus-internals.md +3 -3
  11. package/dst-stage1/mqtt-plus-base.d.ts +3 -2
  12. package/dst-stage1/mqtt-plus-base.js +53 -22
  13. package/dst-stage1/mqtt-plus-event.d.ts +0 -2
  14. package/dst-stage1/mqtt-plus-event.js +6 -26
  15. package/dst-stage1/mqtt-plus-meta.d.ts +2 -2
  16. package/dst-stage1/mqtt-plus-meta.js +2 -2
  17. package/dst-stage1/mqtt-plus-msg.d.ts +2 -0
  18. package/dst-stage1/mqtt-plus-msg.js +17 -0
  19. package/dst-stage1/mqtt-plus-service.d.ts +0 -5
  20. package/dst-stage1/mqtt-plus-service.js +12 -48
  21. package/dst-stage1/mqtt-plus-sink.d.ts +0 -10
  22. package/dst-stage1/mqtt-plus-sink.js +25 -92
  23. package/dst-stage1/mqtt-plus-source.d.ts +0 -10
  24. package/dst-stage1/mqtt-plus-source.js +23 -88
  25. package/dst-stage1/mqtt-plus-subscription.d.ts +20 -0
  26. package/dst-stage1/mqtt-plus-subscription.js +126 -0
  27. package/dst-stage1/mqtt-plus-timer.d.ts +8 -0
  28. package/dst-stage1/mqtt-plus-timer.js +57 -0
  29. package/dst-stage1/mqtt-plus-topic.d.ts +20 -0
  30. package/dst-stage1/mqtt-plus-topic.js +112 -0
  31. package/dst-stage1/mqtt-plus-trace.js +2 -0
  32. package/dst-stage1/mqtt-plus-util.d.ts +0 -13
  33. package/dst-stage1/mqtt-plus-util.js +1 -77
  34. package/dst-stage1/mqtt-plus-version.d.ts +0 -1
  35. package/dst-stage1/mqtt-plus-version.js +0 -6
  36. package/dst-stage1/tsc.tsbuildinfo +1 -1
  37. package/dst-stage2/mqtt-plus.cjs.js +242 -292
  38. package/dst-stage2/mqtt-plus.esm.js +240 -290
  39. package/dst-stage2/mqtt-plus.umd.js +12 -12
  40. package/etc/knip.jsonc +1 -1
  41. package/etc/stx.conf +6 -4
  42. package/package.json +1 -1
  43. package/src/mqtt-plus-base.ts +56 -26
  44. package/src/mqtt-plus-event.ts +8 -24
  45. package/src/mqtt-plus-meta.ts +3 -3
  46. package/src/mqtt-plus-msg.ts +28 -0
  47. package/src/mqtt-plus-service.ts +12 -50
  48. package/src/mqtt-plus-sink.ts +32 -105
  49. package/src/mqtt-plus-source.ts +29 -99
  50. package/src/mqtt-plus-subscription.ts +141 -0
  51. package/src/mqtt-plus-timer.ts +61 -0
  52. package/src/mqtt-plus-trace.ts +4 -0
  53. package/src/mqtt-plus-util.ts +1 -81
  54. package/src/mqtt-plus-version.ts +0 -7
  55. package/tst/mqtt-plus-0-fixture.ts +2 -2
  56. package/tst/mqtt-plus-0-mosquitto.ts +5 -0
  57. package/tst/mqtt-plus-1-api.spec.ts +1 -1
  58. package/tst/mqtt-plus-2-event.spec.ts +0 -6
  59. package/tst/mqtt-plus-3-service.spec.ts +3 -7
  60. package/tst/mqtt-plus-4-sink.spec.ts +14 -9
  61. package/tst/mqtt-plus-5-source.spec.ts +11 -5
  62. package/tst/mqtt-plus-6-misc.spec.ts +23 -23
  63. package/tst/tsc.json +1 -1
  64. package/doc/mqtt-plus-communication.md +0 -68
  65. /package/doc/{mqtt-plus-1-event-emission.d2 → mqtt-plus-comm-event-emission.d2} +0 -0
  66. /package/doc/{mqtt-plus-1-event-emission.svg → mqtt-plus-comm-event-emission.svg} +0 -0
  67. /package/doc/{mqtt-plus-2-service-call.d2 → mqtt-plus-comm-service-call.d2} +0 -0
  68. /package/doc/{mqtt-plus-2-service-call.svg → mqtt-plus-comm-service-call.svg} +0 -0
  69. /package/doc/{mqtt-plus-3-sink-push.d2 → mqtt-plus-comm-sink-push.d2} +0 -0
  70. /package/doc/{mqtt-plus-3-sink-push.svg → mqtt-plus-comm-sink-push.svg} +0 -0
  71. /package/doc/{mqtt-plus-4-source-fetch.d2 → mqtt-plus-comm-source-fetch.d2} +0 -0
  72. /package/doc/{mqtt-plus-4-source-fetch.svg → mqtt-plus-comm-source-fetch.svg} +0 -0
  73. /package/{doc/theme.d2 → etc/d2.theme.d2} +0 -0
@@ -0,0 +1,61 @@
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
+
25
+ /* internal requirements */
26
+ import type { APISchema } from "./mqtt-plus-api"
27
+ import { SubscriptionTrait } from "./mqtt-plus-subscription"
28
+
29
+ /* Timer trait with reusable timer management */
30
+ export class TimerTrait<T extends APISchema = APISchema> extends SubscriptionTrait<T> {
31
+ /* internal state */
32
+ private timers = new Map<string, ReturnType<typeof setTimeout>>()
33
+
34
+ /* destroy timer trait */
35
+ override async destroy () {
36
+ for (const timer of this.timers.values())
37
+ clearTimeout(timer)
38
+ this.timers.clear()
39
+ await super.destroy()
40
+ }
41
+
42
+ /* refresh (or start) a named timer */
43
+ protected timerRefresh (id: string, onTimeout: () => void) {
44
+ const timer = this.timers.get(id)
45
+ if (timer !== undefined)
46
+ clearTimeout(timer)
47
+ this.timers.set(id, setTimeout(() => {
48
+ this.timers.delete(id)
49
+ onTimeout()
50
+ }, this.options.timeout))
51
+ }
52
+
53
+ /* clear a named timer */
54
+ protected timerClear (id: string) {
55
+ const timer = this.timers.get(id)
56
+ if (timer !== undefined) {
57
+ clearTimeout(timer)
58
+ this.timers.delete(id)
59
+ }
60
+ }
61
+ }
@@ -38,6 +38,8 @@ class LogEvent {
38
38
  public msg: string | Promise<string>,
39
39
  public data?: Record<string, Promise<any> | any>
40
40
  ) {}
41
+
42
+ /* resolve all pending promises in the log event */
41
43
  async resolve () {
42
44
  if (this.msg instanceof Promise)
43
45
  this.msg = await this.msg.catch(() => "<resolve-failed>")
@@ -46,6 +48,8 @@ class LogEvent {
46
48
  if (this.data[field] instanceof Promise)
47
49
  this.data[field] = await this.data[field].catch(() => "<resolve-failed>")
48
50
  }
51
+
52
+ /* render log event as string */
49
53
  toString () {
50
54
  /* render time */
51
55
  const timestamp = new Date(this.timestamp)
@@ -28,86 +28,6 @@ import { Readable } from "node:stream"
28
28
 
29
29
  /* external requirements */
30
30
  import PLazy from "p-lazy"
31
- import type { IClientSubscribeOptions } from "mqtt"
32
-
33
- /* reference-counted subscription helper */
34
- export class RefCountedSubscription {
35
- private counts = new Map<string, number>()
36
- private pending = new Map<string, Promise<void>>()
37
- private lingers = new Map<string, ReturnType<typeof setTimeout>>()
38
- constructor (
39
- private subscribeFn: (topic: string, options: IClientSubscribeOptions) => Promise<void>,
40
- private unsubscribeFn: (topic: string) => Promise<void>,
41
- private lingerMs: number = 30 * 1000
42
- ) {}
43
- async subscribe (topic: string, options: IClientSubscribeOptions = { qos: 2 }): Promise<void> {
44
- /* increment count first to reserve our interest */
45
- const count = this.counts.get(topic) ?? 0
46
- this.counts.set(topic, count + 1)
47
-
48
- /* optionally just cancel a pending linger unsubscription
49
- (subscription is still kept active on the broker) */
50
- const linger = this.lingers.get(topic)
51
- if (linger) {
52
- clearTimeout(linger)
53
- this.lingers.delete(topic)
54
- return
55
- }
56
-
57
- /* if we are the first, we must perform the actual subscription */
58
- if (count === 0) {
59
- const promise = this.subscribeFn(topic, options).finally(() => {
60
- this.pending.delete(topic)
61
- }).catch((err: Error) => {
62
- const count = this.counts.get(topic)
63
- if (count) {
64
- if (count <= 1)
65
- this.counts.delete(topic)
66
- else
67
- this.counts.set(topic, count - 1)
68
- }
69
- throw err
70
- })
71
- this.pending.set(topic, promise)
72
- return promise
73
- }
74
- else {
75
- /* perhaps still need to wait for a pending subscription */
76
- const pending = this.pending.get(topic)
77
- if (pending)
78
- return pending
79
- }
80
- }
81
- async unsubscribe (topic: string): Promise<void> {
82
- const count = this.counts.get(topic)
83
- if (count) {
84
- if (count <= 1) {
85
- this.counts.delete(topic)
86
- if (this.lingerMs > 0) {
87
- /* defer the actual broker unsubscription */
88
- const timer = setTimeout(() => {
89
- this.lingers.delete(topic)
90
- this.unsubscribeFn(topic).catch(() => {})
91
- }, this.lingerMs)
92
- this.lingers.set(topic, timer)
93
- }
94
- else
95
- await this.unsubscribeFn(topic).catch(() => {})
96
- }
97
- else
98
- this.counts.set(topic, count - 1)
99
- }
100
- }
101
- async flush (): Promise<void> {
102
- /* flush all pending linger timers and unsubscribe immediately */
103
- const topics = [ ...this.lingers.keys() ]
104
- for (const topic of topics) {
105
- clearTimeout(this.lingers.get(topic))
106
- this.lingers.delete(topic)
107
- await this.unsubscribeFn(topic).catch(() => {})
108
- }
109
- }
110
- }
111
31
 
112
32
  /* credit-based flow control gate for chunk producers */
113
33
  export class CreditGate {
@@ -166,7 +86,7 @@ export class CreditGate {
166
86
  }
167
87
  }
168
88
 
169
- /* concatenate elements of an Uint8Array array */
89
+ /* concatenate elements of a Uint8Array array */
170
90
  function uint8ArrayConcat (arrays: Uint8Array[]) {
171
91
  const totalLength = arrays.reduce((acc, value) => acc + value.byteLength, 0)
172
92
  const result = new Uint8Array(totalLength)
@@ -33,13 +33,6 @@ export const versionToNum = (str: string) => {
33
33
  return parseInt(m[1], 10) + minor / 100
34
34
  }
35
35
 
36
- /* convert version number to string */
37
- export const versionToStr = (num: number) => {
38
- const intPart = Math.floor(num)
39
- const decPart = Math.round((num - intPart) * 100)
40
- return `${intPart.toFixed(0)}.${decPart.toFixed(0).padStart(2, "0")}`
41
- }
42
-
43
36
  /* package version (string format, injected) */
44
37
  declare const __VERSION__: string
45
38
  export const VERSION = __VERSION__
@@ -68,7 +68,7 @@ export const mochaHooks = {
68
68
  /* connect with MQTT as client */
69
69
  ctx.mqttC = MQTT.connect("mqtt://127.0.0.1:1883",
70
70
  { clientId: "client" })
71
- ctx.apiC = new MQTTp<API>(ctx.mqttC, { id: "client", timeout: 1000 })
71
+ ctx.apiC = new MQTTp<API>(ctx.mqttC, { id: "client", timeout: 500 })
72
72
  await new Promise<void>((resolve, reject) => {
73
73
  ctx.mqttC.once("connect", () => { resolve() })
74
74
  ctx.mqttC.once("error", (err: Error) => { reject(err) })
@@ -81,7 +81,7 @@ export const mochaHooks = {
81
81
  /* connect with MQTT as server */
82
82
  ctx.mqttS = MQTT.connect("mqtt://127.0.0.1:1883",
83
83
  { clientId: "server", username: "example", password: "example" })
84
- ctx.apiS = new MQTTp<API>(ctx.mqttS, { id: "server", timeout: 1000 })
84
+ ctx.apiS = new MQTTp<API>(ctx.mqttS, { id: "server", timeout: 500 })
85
85
  await new Promise<void>((resolve, reject) => {
86
86
  ctx.mqttS.once("connect", () => { resolve() })
87
87
  ctx.mqttS.once("error", (err: Error) => { reject(err) })
@@ -56,6 +56,7 @@ const ACL = textframe(`
56
56
  topic write example/server/+/source-fetch-request/+
57
57
  pattern read example/server/+/source-fetch-response/%c
58
58
  pattern read example/server/+/source-fetch-chunk/%c
59
+ topic write example/server/+/source-fetch-credit/+
59
60
 
60
61
  topic read example/client/+/source-fetch-request/any
61
62
  pattern read example/client/+/source-fetch-request/%c
@@ -72,6 +73,7 @@ const ACL = textframe(`
72
73
  pattern read example/client/+/sink-push-request/%c
73
74
  pattern write example/client/+/sink-push-response/%c
74
75
  pattern read example/client/+/sink-push-chunk/%c
76
+ pattern read example/client/+/sink-push-credit/%c
75
77
 
76
78
  # ==== server/authenticated ACL ====
77
79
 
@@ -105,6 +107,8 @@ const ACL = textframe(`
105
107
  pattern read $share/server/example/server/+/source-fetch-request/%c
106
108
  topic write example/server/+/source-fetch-response/+
107
109
  topic write example/server/+/source-fetch-chunk/+
110
+ pattern read example/server/+/source-fetch-credit/%c
111
+ pattern read $share/server/example/server/+/source-fetch-credit/%c
108
112
 
109
113
  topic write example/client/+/source-fetch-request/+
110
114
  pattern read example/client/+/source-fetch-response/%c
@@ -119,6 +123,7 @@ const ACL = textframe(`
119
123
  topic write example/server/+/sink-push-response/+
120
124
  pattern read example/server/+/sink-push-chunk/%c
121
125
  pattern read $share/default/example/server/+/sink-push-chunk/%c
126
+ topic write example/client/+/sink-push-credit/+
122
127
 
123
128
  topic write example/client/+/sink-push-request/+
124
129
  pattern read example/client/+/sink-push-response/%c
@@ -100,7 +100,7 @@ describe("MQTT+ API", function () {
100
100
 
101
101
  /* trigger some log activity by emitting an event */
102
102
  apiTmp.emit("example/server/sample", "test", 1)
103
- await new Promise((resolve) => { setTimeout(resolve, 100) })
103
+ await new Promise((resolve) => { setTimeout(resolve, 10) })
104
104
 
105
105
  /* verify log entries were captured */
106
106
  expect(logEntries.length).to.be.greaterThan(0)
@@ -41,7 +41,6 @@ describe("MQTT+ Event Emission", function () {
41
41
  /* test case: Event Emission */
42
42
  it("MQTT+ Event Emission", async function () {
43
43
  /* setup */
44
- this.timeout(1000)
45
44
  const spy = sinon.spy()
46
45
 
47
46
  /* register to event */
@@ -64,7 +63,6 @@ describe("MQTT+ Event Emission", function () {
64
63
  /* test case: Event Emission (Object API) */
65
64
  it("MQTT+ Event Emission (Object API)", async function () {
66
65
  /* setup */
67
- this.timeout(1000)
68
66
  const spy = sinon.spy()
69
67
 
70
68
  /* register event */
@@ -93,7 +91,6 @@ describe("MQTT+ Event Emission", function () {
93
91
  /* test case: Event Emission with Meta Information */
94
92
  it("MQTT+ Event Emission (Meta Information)", async function () {
95
93
  /* setup */
96
- this.timeout(1000)
97
94
  const spy = sinon.spy()
98
95
 
99
96
  /* register event */
@@ -122,9 +119,6 @@ describe("MQTT+ Event Emission", function () {
122
119
 
123
120
  /* test case: Event Emission (Duplicate Registration) */
124
121
  it("MQTT+ Event Emission (Duplicate Registration)", async function () {
125
- /* setup */
126
- this.timeout(1000)
127
-
128
122
  /* register event */
129
123
  const reg = await ctx.apiS.event("example/server/sample", () => {})
130
124
 
@@ -41,7 +41,6 @@ describe("MQTT+ Service Call", function () {
41
41
  /* test case: Service Call */
42
42
  it("MQTT+ Service Call", async function () {
43
43
  /* setup */
44
- this.timeout(1000)
45
44
  const spy = sinon.spy()
46
45
 
47
46
  /* provide service */
@@ -82,7 +81,6 @@ describe("MQTT+ Service Call", function () {
82
81
  /* test case: Service Call (Object API) */
83
82
  it("MQTT+ Service Call (Object API)", async function () {
84
83
  /* setup */
85
- this.timeout(1000)
86
84
  const spy = sinon.spy()
87
85
 
88
86
  /* provide service */
@@ -111,7 +109,6 @@ describe("MQTT+ Service Call", function () {
111
109
  /* test case: Service Call (Meta Information) */
112
110
  it("MQTT+ Service Call (Meta Information)", async function () {
113
111
  /* setup */
114
- this.timeout(1000)
115
112
  const spy = sinon.spy()
116
113
 
117
114
  /* provide service that checks metadata */
@@ -142,7 +139,8 @@ describe("MQTT+ Service Call", function () {
142
139
 
143
140
  /* test case: Service Call (Timeout) */
144
141
  it("MQTT+ Service Call (Timeout)", async function () {
145
- /* setup (higher timeout for this test) */
142
+ /* setup (higher timeout for this particular test) */
143
+ this.slow(2000)
146
144
  this.timeout(2000)
147
145
  const spy = sinon.spy()
148
146
 
@@ -160,7 +158,6 @@ describe("MQTT+ Service Call", function () {
160
158
  /* test case: Service Call (Direct Receiver) */
161
159
  it("MQTT+ Service Call (Direct Receiver)", async function () {
162
160
  /* setup */
163
- this.timeout(1000)
164
161
  const spy = sinon.spy()
165
162
 
166
163
  /* provide service on server */
@@ -188,13 +185,12 @@ describe("MQTT+ Service Call", function () {
188
185
  /* test case: Service Call (Async Handler) */
189
186
  it("MQTT+ Service Call (Async Handler)", async function () {
190
187
  /* setup */
191
- this.timeout(1000)
192
188
  const spy = sinon.spy()
193
189
 
194
190
  /* provide async service */
195
191
  const registration = await ctx.apiS.service("example/server/login", async (password: string) => {
196
192
  spy("service")
197
- await new Promise((resolve) => { setTimeout(resolve, 50) })
193
+ await new Promise((resolve) => { setTimeout(resolve, 0) })
198
194
  if (password !== "secret")
199
195
  throw new Error("invalid password")
200
196
  return "token-abc"
@@ -45,7 +45,9 @@ const { expect } = chai
45
45
  describe("MQTT+ Sink Push", function () {
46
46
  /* test case: Sink Push (Buffer) */
47
47
  it("MQTT+ Sink Push (Buffer)", async function () {
48
- this.timeout(2000)
48
+ /* setup */
49
+ this.slow(1000)
50
+ this.timeout(1000)
49
51
  const spy = sinon.spy()
50
52
 
51
53
  /* generate random data */
@@ -61,7 +63,7 @@ describe("MQTT+ Sink Push", function () {
61
63
  info.buffer!.then((buf: Uint8Array) => {
62
64
  spy("buffer")
63
65
  expect(Buffer.from(buf)).to.deep.equal(data)
64
- })
66
+ }).catch(() => {})
65
67
  })
66
68
 
67
69
  /* push a buffer (instead of a stream) */
@@ -70,7 +72,7 @@ describe("MQTT+ Sink Push", function () {
70
72
  }).catch((err: Error) => {
71
73
  spy("push-error")
72
74
  })
73
- await new Promise((resolve) => { setTimeout(resolve, 1000) })
75
+ await new Promise((resolve) => { setTimeout(resolve, 100) })
74
76
  expect(spy.getCalls().map((call) => call.firstArg))
75
77
  .to.be.same.deep.members([ "sink", "push-success", "buffer" ])
76
78
 
@@ -81,7 +83,8 @@ describe("MQTT+ Sink Push", function () {
81
83
  /* test case: Sink Push (Stream) */
82
84
  it("MQTT+ Sink Push (Stream)", async function () {
83
85
  /* setup */
84
- this.timeout(2000)
86
+ this.slow(1000)
87
+ this.timeout(1000)
85
88
  const spy = sinon.spy()
86
89
 
87
90
  /* generate random data */
@@ -93,7 +96,7 @@ describe("MQTT+ Sink Push", function () {
93
96
  if (name !== "foo")
94
97
  throw new Error("invalid sink push")
95
98
  expect(name).to.be.equal("foo")
96
- expect(info).to.be.of.an("object")
99
+ expect(info).to.be.an("object")
97
100
  expect(info.stream).to.be.instanceOf(stream.Readable)
98
101
  const chunks: Buffer[] = []
99
102
  info.stream!.on("data", (chunk: Buffer) => {
@@ -117,7 +120,7 @@ describe("MQTT+ Sink Push", function () {
117
120
  }).catch((err: Error) => {
118
121
  spy("transfer-error")
119
122
  })
120
- await new Promise((resolve) => { setTimeout(resolve, 1000) })
123
+ await new Promise((resolve) => { setTimeout(resolve, 100) })
121
124
  expect(spy.getCalls().map((call) => call.firstArg))
122
125
  .to.be.same.deep.members([ "sink", "transfer-success", "end" ])
123
126
 
@@ -127,7 +130,9 @@ describe("MQTT+ Sink Push", function () {
127
130
 
128
131
  /* test case: Sink Push (Meta Information) */
129
132
  it("MQTT+ Sink Push (Meta Information)", async function () {
130
- this.timeout(2000)
133
+ /* setup */
134
+ this.slow(1000)
135
+ this.timeout(1000)
131
136
  const spy = sinon.spy()
132
137
 
133
138
  /* set instance-level meta on client */
@@ -148,7 +153,7 @@ describe("MQTT+ Sink Push", function () {
148
153
  info.buffer!.then((buf: Uint8Array) => {
149
154
  spy("buffer")
150
155
  expect(Buffer.from(buf)).to.deep.equal(data)
151
- })
156
+ }).catch(() => {})
152
157
  })
153
158
 
154
159
  /* push with metadata */
@@ -162,7 +167,7 @@ describe("MQTT+ Sink Push", function () {
162
167
  }).catch((err: Error) => {
163
168
  spy("push-error")
164
169
  })
165
- await new Promise((resolve) => { setTimeout(resolve, 1000) })
170
+ await new Promise((resolve) => { setTimeout(resolve, 100) })
166
171
  expect(spy.getCalls().map((call) => call.firstArg))
167
172
  .to.be.same.deep.members([ "sink", "push-success", "buffer" ])
168
173
 
@@ -41,7 +41,9 @@ const { expect } = chai
41
41
  describe("MQTT+ Source Fetch", function () {
42
42
  /* test case: Source Fetch (Buffer) */
43
43
  it("MQTT+ Source Fetch (Buffer)", async function () {
44
- this.timeout(3000)
44
+ /* setup */
45
+ this.slow(2000)
46
+ this.timeout(2000)
45
47
 
46
48
  /* establish source */
47
49
  const sourcing = await ctx.apiS.source("example/server/download", async (filename, info) => {
@@ -65,7 +67,7 @@ describe("MQTT+ Source Fetch", function () {
65
67
  expect(error2).to.be.equal("invalid source")
66
68
 
67
69
  /* fetch non-existing source (invalid source name) */
68
- const result3 = await ctx.apiC.fetch("example/server/download-invalid", "foo").catch((err) => err.message)
70
+ const result3 = await ctx.apiC.fetch("example/server/download-invalid", "foo")
69
71
  const error3 = await result3.buffer.catch((err: Error) => {
70
72
  return err.message
71
73
  })
@@ -76,7 +78,9 @@ describe("MQTT+ Source Fetch", function () {
76
78
 
77
79
  /* test case: Source Fetch (Stream) */
78
80
  it("MQTT+ Source Fetch (Stream)", async function () {
79
- this.timeout(3000)
81
+ /* setup */
82
+ this.slow(1000)
83
+ this.timeout(1000)
80
84
 
81
85
  /* establish source providing data via stream */
82
86
  const sourcing = await ctx.apiS.source("example/server/download", async (filename, info) => {
@@ -104,13 +108,15 @@ describe("MQTT+ Source Fetch", function () {
104
108
 
105
109
  /* test case: Source Fetch (Meta Information) */
106
110
  it("MQTT+ Source Fetch (Meta Information)", async function () {
111
+ /* setup */
112
+ this.slow(1000)
107
113
  this.timeout(1000)
108
114
 
109
115
  /* set instance-level meta on server */
110
116
  ctx.apiS.meta("server-version", "1.0")
111
117
 
112
118
  /* establish source */
113
- const sourcing = await ctx.apiS.source("example/server/download", async (filename, info) => {
119
+ const sourcing = await ctx.apiS.source("example/server/download", async (_filename, info) => {
114
120
  info.buffer = Promise.resolve(Buffer.from("data"))
115
121
  })
116
122
 
@@ -121,7 +127,7 @@ describe("MQTT+ Source Fetch", function () {
121
127
  expect(meta!["server-version"]).to.be.equal("1.0")
122
128
 
123
129
  /* cleanup */
124
- ctx.apiS.meta("server-version", undefined)
130
+ ctx.apiS.meta("server-version", null)
125
131
  await sourcing.destroy()
126
132
  })
127
133
  })
@@ -46,9 +46,11 @@ const { expect } = chai
46
46
 
47
47
  /* test suite */
48
48
  describe("MQTT+ Miscellaneous", function () {
49
- /* test case: Dry-Run & Last-Will */
49
+ /* test case: Dry-Run & Last-Will */
50
50
  it("MQTT+ Dry-Run & MQTT Last-Will", async function () {
51
- this.timeout(3000)
51
+ /* setup */
52
+ this.slow(2000)
53
+ this.timeout(2000)
52
54
 
53
55
  /* generate connection close event */
54
56
  const mqttpDry = new MQTTp<API>(null, { id: "my-client" })
@@ -63,11 +65,11 @@ describe("MQTT+ Miscellaneous", function () {
63
65
  mqttServer.once("connect", () => { resolve() })
64
66
  mqttServer.once("error", (err: Error) => { reject(err) })
65
67
  })
66
- const apiServer = new MQTTp<API>(mqttServer, { timeout: 1000 })
68
+ const apiServer = new MQTTp<API>(mqttServer, { timeout: 100 })
67
69
 
68
70
  /* observe connection events */
69
71
  const spy = sinon.spy()
70
- apiServer.event("example/server/connection", (state) => {
72
+ const eventReg = await apiServer.event("example/server/connection", (state) => {
71
73
  expect(state).to.match(/^(?:open|close)$/)
72
74
  spy(state)
73
75
  })
@@ -84,7 +86,7 @@ describe("MQTT+ Miscellaneous", function () {
84
86
  mqttClient.once("connect", () => { resolve() })
85
87
  mqttClient.once("error", (err: Error) => { reject(err) })
86
88
  })
87
- const apiClient = new MQTTp<API>(mqttClient, { timeout: 1000 })
89
+ const apiClient = new MQTTp<API>(mqttClient, { timeout: 100 })
88
90
 
89
91
  /* send connection open event */
90
92
  await apiClient.emit("example/server/connection", "open")
@@ -93,11 +95,12 @@ describe("MQTT+ Miscellaneous", function () {
93
95
  /* perform unexpected destruction of client */
94
96
  apiClient.destroy()
95
97
  mqttClient.end(true)
96
- await new Promise((resolve) => { setTimeout(resolve, 1000) })
98
+ await new Promise((resolve) => { setTimeout(resolve, 100) })
97
99
 
98
100
  /* perform regular destruction of client */
101
+ await eventReg.destroy()
99
102
  apiServer.destroy()
100
- mqttServer.end()
103
+ await mqttServer.endAsync()
101
104
 
102
105
  /* ensure connection open and close events were seen */
103
106
  expect(spy.getCalls().map((call) => call.firstArg))
@@ -107,7 +110,8 @@ describe("MQTT+ Miscellaneous", function () {
107
110
  /* test case: Authentication */
108
111
  it("MQTT+ Authentication", async function () {
109
112
  /* setup */
110
- this.timeout(3000)
113
+ this.slow(1000)
114
+ this.timeout(1000)
111
115
  const spy = sinon.spy()
112
116
 
113
117
  /* credentials */
@@ -141,13 +145,12 @@ describe("MQTT+ Miscellaneous", function () {
141
145
  })
142
146
 
143
147
  /* call service (without token) */
144
- await ctx.apiC.call("example/server/hello", "world", 42).then(async (result) => {
148
+ await ctx.apiC.call("example/server/hello", "world", 42).then(async (_result) => {
145
149
  spy("call1-success")
146
- }).catch((err: Error) => {
150
+ }).catch((_err: Error) => {
147
151
  spy("call1-error")
148
152
  })
149
- expect(spy.getCalls()
150
- .map((call) => call.firstArg))
153
+ expect(spy.getCalls().map((call) => call.firstArg))
151
154
  .to.be.deep.equal([ "call1-error" ])
152
155
  spy.resetHistory()
153
156
 
@@ -155,35 +158,32 @@ describe("MQTT+ Miscellaneous", function () {
155
158
  await ctx.apiC.call("example/server/login", userCred).then(async (token) => {
156
159
  spy("login-success")
157
160
  expect(token).to.be.equal(userToken)
158
- }).catch((err: Error) => {
161
+ }).catch((_err: Error) => {
159
162
  spy("login-error")
160
163
  })
161
- expect(spy.getCalls()
162
- .map((call) => call.firstArg))
164
+ expect(spy.getCalls().map((call) => call.firstArg))
163
165
  .to.be.deep.equal([ "login", "login-success" ])
164
166
  spy.resetHistory()
165
167
 
166
168
  /* call service (with wrong token) */
167
169
  await ctx.apiC.authenticate("wrong")
168
- await ctx.apiC.call("example/server/hello", "world", 42).then(async (result) => {
170
+ await ctx.apiC.call("example/server/hello", "world", 42).then(async (_result) => {
169
171
  spy("call2-success")
170
- }).catch((err: Error) => {
172
+ }).catch((_err: Error) => {
171
173
  spy("call2-error")
172
174
  })
173
- expect(spy.getCalls()
174
- .map((call) => call.firstArg))
175
+ expect(spy.getCalls().map((call) => call.firstArg))
175
176
  .to.be.deep.equal([ "call2-error" ])
176
177
  spy.resetHistory()
177
178
 
178
179
  /* call service (with correct token) */
179
180
  await ctx.apiC.authenticate(userToken)
180
- await ctx.apiC.call("example/server/hello", "world", 42).then(async (result) => {
181
+ await ctx.apiC.call("example/server/hello", "world", 42).then(async (_result) => {
181
182
  spy("call3-success")
182
- }).catch((err: Error) => {
183
+ }).catch((_err: Error) => {
183
184
  spy("call3-error")
184
185
  })
185
- expect(spy.getCalls()
186
- .map((call) => call.firstArg))
186
+ expect(spy.getCalls().map((call) => call.firstArg))
187
187
  .to.be.deep.equal([ "hello", "call3-success" ])
188
188
 
189
189
  /* destroy service */
package/tst/tsc.json CHANGED
@@ -22,7 +22,7 @@
22
22
  }
23
23
  },
24
24
  "include": [
25
- "../tst/**/*.spec.ts"
25
+ "*.ts"
26
26
  ],
27
27
  "exclude": [
28
28
  "../node_modules"