mqtt-plus 1.4.11 → 1.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -65,10 +65,10 @@ each extending the previous. The final exported class `MQTTp` sits at
65
65
  the bottom of this chain:
66
66
 
67
67
  ```
68
- OptionsTrait — configuration (id, codec, timeout, chunkSize, chunkCredit, topicMake/topicMatch)
68
+ OptionsTrait — configuration (id, codec, timeout, share, chunkSize, chunkCredit, topicMake/topicMatch)
69
69
  ↓ CodecTrait — CBOR/JSON codec handling
70
- ↓ EncodeTrait — message encoding/validation (valibot schemas)
71
- ↓ MsgTrait — message class definitions and parsing
70
+ ↓ EncodeTrait — string/buffer conversion utilities (str2buf, buf2str)
71
+ ↓ MsgTrait — message class definitions, valibot schemas, and parsing
72
72
  ↓ TraceTrait — EventEmitter + structured logging
73
73
  ↓ BaseTrait — MQTT client hookup, subscription management, message routing
74
74
  ↓ SubscriptionTrait — ref-counted MQTT topic subscription management
@@ -92,15 +92,15 @@ Each trait lives in its own file: `src/mqtt-plus-<trait>.ts`.
92
92
  | `src/mqtt-plus-api.ts` | Branded endpoint type definitions (Event, Service, Source, Sink) and APISchema generic |
93
93
  | `src/mqtt-plus-info.ts` | Info/context object types passed to pattern callbacks (sender metadata, etc.) |
94
94
  | `src/mqtt-plus-error.ts` | Spool (resource cleanup) and run (error handling) utilities |
95
- | `src/mqtt-plus-util.ts` | Stream/buffer conversion, RefCountedSubscription, and CreditGate flow control |
95
+ | `src/mqtt-plus-util.ts` | PLazy, CreditGate flow control, and stream/buffer collection utilities |
96
96
  | `src/mqtt-plus-version.ts` | Version utility for converting version strings to numeric format |
97
- | `src/mqtt-plus-options.ts` | OptionsTrait — configuration (id, codec, timeout, chunkSize, chunkCredit, topicMake/topicMatch) |
97
+ | `src/mqtt-plus-options.ts` | OptionsTrait — configuration (id, codec, timeout, share, chunkSize, chunkCredit, topicMake/topicMatch) |
98
98
  | `src/mqtt-plus-codec.ts` | CodecTrait — CBOR and JSON codec encoding/decoding |
99
- | `src/mqtt-plus-encode.ts` | EncodeTrait — message validation and encoding via valibot schemas |
100
- | `src/mqtt-plus-msg.ts` | MsgTrait — message class definitions and parsing logic |
99
+ | `src/mqtt-plus-encode.ts` | EncodeTrait — string/buffer conversion utilities (str2buf, buf2str) |
100
+ | `src/mqtt-plus-msg.ts` | MsgTrait — message class definitions, valibot schemas, and parsing logic |
101
101
  | `src/mqtt-plus-trace.ts` | TraceTrait — EventEmitter and structured logging |
102
102
  | `src/mqtt-plus-base.ts` | BaseTrait — MQTT client connection, subscription management, message routing |
103
- | `src/mqtt-plus-subscription.ts` | SubscriptionTrait — ref-counted MQTT topic subscription management |
103
+ | `src/mqtt-plus-subscription.ts` | SubscriptionTrait — RefCountedSubscription class and ref-counted MQTT topic subscription management |
104
104
  | `src/mqtt-plus-timer.ts` | TimerTrait — named timer management (refresh/clear) |
105
105
  | `src/mqtt-plus-meta.ts` | MetaTrait — instance and per-request metadata management |
106
106
  | `src/mqtt-plus-auth.ts` | AuthTrait — JWT authentication (jose) and role-based access control |
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
  ChangeLog
3
3
  =========
4
4
 
5
+ 1.4.12 (2026-03-05)
6
+ -------------------
7
+
8
+ - IMPROVEMENT: make all subscriptions ref-counted and spooled
9
+ - UPDATE: update documentation
10
+ - UPDATE: upgrade NPM dependencies
11
+ - CLEANUP: cleanup code
12
+
5
13
  1.4.11 (2026-03-05)
6
14
  -------------------
7
15
 
package/README.md CHANGED
@@ -176,7 +176,7 @@ Notice
176
176
 
177
177
  > [!Note]
178
178
  > **MQTT+** and its peer dependency **MQTT.js** provide a powerful
179
- > functionality, but are not small in size. **MQTT+** is 3.500 LoC
179
+ > functionality, but are not small in size. **MQTT+** is 3.900 LoC
180
180
  > and 75 KB in size (ESM and CJS format). When bundled with all its
181
181
  > dependencies, it is 220 KB in size (UMD format). Its peer dependency
182
182
  > **MQTT.js** is 370 KB (ESM and CJS format) and 860 KB (UMD format) in
@@ -11,7 +11,6 @@ export declare class BaseTrait<T extends APISchema = APISchema> extends TraceTra
11
11
  constructor(mqtt: MqttClient | null, options?: Partial<APIOptions>);
12
12
  destroy(): Promise<void>;
13
13
  protected makeRegistration(spool: Spool, kind: string, name: string, key: string): Registration;
14
- protected subscribeTopicAndSpool(spool: Spool, topic: string, options?: Partial<IClientSubscribeOptions>): Promise<void>;
15
14
  protected subscribeTopic(topic: string, options?: Partial<IClientSubscribeOptions>): Promise<void>;
16
15
  protected unsubscribeTopic(topic: string): Promise<void>;
17
16
  protected publishToTopic(topic: string, message: string | Uint8Array, options?: IClientPublishOptions): Promise<void>;
@@ -22,7 +22,7 @@
22
22
  ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  */
24
24
  import { TraceTrait } from "./mqtt-plus-trace";
25
- import { run, ensureError } from "./mqtt-plus-error";
25
+ import { ensureError } from "./mqtt-plus-error";
26
26
  import { PLazy } from "./mqtt-plus-util";
27
27
  /* MQTTp Base class with shared infrastructure */
28
28
  export class BaseTrait extends TraceTrait {
@@ -94,11 +94,6 @@ export class BaseTrait extends TraceTrait {
94
94
  }
95
95
  };
96
96
  }
97
- /* subscribe to an MQTT topic and spool the unsubscription */
98
- async subscribeTopicAndSpool(spool, topic, options = {}) {
99
- await run(`subscribe to MQTT topic "${topic}"`, spool, () => this.subscribeTopic(topic, { qos: 2, ...options }));
100
- spool.roll(() => this.unsubscribeTopic(topic).catch(() => { }));
101
- }
102
97
  /* subscribe to an MQTT topic (Promise-based) */
103
98
  async subscribeTopic(topic, options = {}) {
104
99
  this.log("info", `subscribing to MQTT topic "${topic}"`);
@@ -137,8 +137,7 @@ export class ServiceTrait extends EventTrait {
137
137
  requestId = nanoid();
138
138
  /* subscribe to MQTT response topic */
139
139
  const responseTopic = this.options.topicMake(name, "service-call-response", this.options.id);
140
- await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
141
- spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
140
+ await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
142
141
  /* create promise for MQTT response handling */
143
142
  const timerId = `service-call:${requestId}`;
144
143
  const promise = new Promise((resolve, reject) => {
@@ -36,13 +36,13 @@ export class SinkTrait extends SourceTrait {
36
36
  this.pushStreams = new Map();
37
37
  this.pushSpools = new Map();
38
38
  }
39
- /* destroy sink trait */
39
+ /* destroy trait */
40
40
  async destroy() {
41
41
  for (const stream of this.pushStreams.values())
42
42
  stream.destroy(new Error("sink destroyed"));
43
+ this.pushStreams.clear();
43
44
  for (const spool of this.pushSpools.values())
44
45
  await spool.unroll();
45
- this.pushStreams.clear();
46
46
  this.pushSpools.clear();
47
47
  await super.destroy();
48
48
  }
@@ -76,7 +76,7 @@ export class SinkTrait extends SourceTrait {
76
76
  const topicReqB = this.options.topicMake(topicS, "sink-push-request");
77
77
  const topicReqD = this.options.topicMake(name, "sink-push-request", this.options.id);
78
78
  const topicChunkD = this.options.topicMake(name, "sink-push-chunk", this.options.id);
79
- /* remember the registration */
79
+ /* react on sink push request */
80
80
  this.onRequest.set(`sink-push-request:${name}`, async (request, topicName) => {
81
81
  /* determine information */
82
82
  const requestId = request.id;
@@ -258,8 +258,7 @@ export class SinkTrait extends SourceTrait {
258
258
  requestId = nanoid();
259
259
  /* subscribe to response topic (for ack/nak) */
260
260
  const responseTopic = this.options.topicMake(name, "sink-push-response", this.options.id);
261
- await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
262
- spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
261
+ await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
263
262
  /* define abort controller and signal */
264
263
  const abortController = new AbortController();
265
264
  const abortSignal = abortController.signal;
@@ -326,8 +325,7 @@ export class SinkTrait extends SourceTrait {
326
325
  /* subscribe to credit topic if flow control is active */
327
326
  if (creditGate) {
328
327
  const creditTopic = this.options.topicMake(name, "sink-push-credit", this.options.id);
329
- await run(`subscribe to MQTT topic "${creditTopic}"`, spool, () => this.subscriptions.subscribe(creditTopic, { qos: options.qos ?? 2 }));
330
- spool.roll(() => this.subscriptions.unsubscribe(creditTopic));
328
+ await this.subscribeTopicAndSpool(spool, creditTopic, { qos: options.qos ?? 2 });
331
329
  const gate = creditGate;
332
330
  spool.roll(() => { gate.abort(); });
333
331
  /* update credit callback to include gate replenish */
@@ -230,10 +230,8 @@ export class SourceTrait extends ServiceTrait {
230
230
  /* subscribe to response topic (for ack/nak) and chunk topic (for data) */
231
231
  const responseTopic = this.options.topicMake(name, "source-fetch-response", this.options.id);
232
232
  const chunkTopic = this.options.topicMake(name, "source-fetch-chunk", this.options.id);
233
- await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
234
- spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
235
- await run(`subscribe to MQTT topic "${chunkTopic}"`, spool, () => this.subscriptions.subscribe(chunkTopic, { qos: options.qos ?? 2 }));
236
- spool.roll(() => this.subscriptions.unsubscribe(chunkTopic));
233
+ await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
234
+ await this.subscribeTopicAndSpool(spool, chunkTopic, { qos: options.qos ?? 2 });
237
235
  /* credit-based flow control state */
238
236
  const chunkCredit = this.options.chunkCredit;
239
237
  let chunksReceived = 0;
@@ -1,6 +1,7 @@
1
1
  import type { IClientSubscribeOptions } from "mqtt";
2
2
  import type { APISchema } from "./mqtt-plus-api";
3
3
  import { BaseTrait } from "./mqtt-plus-base";
4
+ import { Spool } from "./mqtt-plus-error";
4
5
  declare class RefCountedSubscription {
5
6
  private subscribeFn;
6
7
  private unsubscribeFn;
@@ -18,6 +19,7 @@ declare class RefCountedSubscription {
18
19
  }
19
20
  export declare class SubscriptionTrait<T extends APISchema = APISchema> extends BaseTrait<T> {
20
21
  protected subscriptions: RefCountedSubscription;
22
+ protected subscribeTopicAndSpool(spool: Spool, topic: string, options?: Partial<IClientSubscribeOptions>): Promise<void>;
21
23
  destroy(): Promise<void>;
22
24
  }
23
25
  export {};
@@ -22,6 +22,7 @@
22
22
  ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  */
24
24
  import { BaseTrait } from "./mqtt-plus-base";
25
+ import { run } from "./mqtt-plus-error";
25
26
  /* reference-counted subscription helper */
26
27
  class RefCountedSubscription {
27
28
  /* initial construction with configuration */
@@ -162,6 +163,11 @@ export class SubscriptionTrait extends BaseTrait {
162
163
  super(...arguments);
163
164
  this.subscriptions = new RefCountedSubscription((topic, options) => this.subscribeTopic(topic, options), (topic) => this.unsubscribeTopic(topic));
164
165
  }
166
+ /* subscribe to an MQTT topic (reference-counted) and spool the unsubscription */
167
+ async subscribeTopicAndSpool(spool, topic, options = {}) {
168
+ await run(`subscribe to MQTT topic "${topic}"`, spool, () => this.subscriptions.subscribe(topic, { qos: 2, ...options }));
169
+ spool.roll(() => this.subscriptions.unsubscribe(topic));
170
+ }
165
171
  /* destroy subscription trait */
166
172
  async destroy() {
167
173
  await this.subscriptions.flush();
@@ -972,12 +972,6 @@ class BaseTrait extends TraceTrait {
972
972
  }
973
973
  };
974
974
  }
975
- /* subscribe to an MQTT topic and spool the unsubscription */
976
- async subscribeTopicAndSpool(spool, topic, options = {}) {
977
- await run(`subscribe to MQTT topic "${topic}"`, spool, () => this.subscribeTopic(topic, { qos: 2, ...options }));
978
- spool.roll(() => this.unsubscribeTopic(topic).catch(() => {
979
- }));
980
- }
981
975
  /* subscribe to an MQTT topic (Promise-based) */
982
976
  async subscribeTopic(topic, options = {}) {
983
977
  this.log("info", `subscribing to MQTT topic "${topic}"`);
@@ -1191,6 +1185,11 @@ class SubscriptionTrait extends BaseTrait {
1191
1185
  super(...arguments);
1192
1186
  this.subscriptions = new RefCountedSubscription((topic, options) => this.subscribeTopic(topic, options), (topic) => this.unsubscribeTopic(topic));
1193
1187
  }
1188
+ /* subscribe to an MQTT topic (reference-counted) and spool the unsubscription */
1189
+ async subscribeTopicAndSpool(spool, topic, options = {}) {
1190
+ await run(`subscribe to MQTT topic "${topic}"`, spool, () => this.subscriptions.subscribe(topic, { qos: 2, ...options }));
1191
+ spool.roll(() => this.subscriptions.unsubscribe(topic));
1192
+ }
1194
1193
  /* destroy subscription trait */
1195
1194
  async destroy() {
1196
1195
  await this.subscriptions.flush();
@@ -1522,8 +1521,7 @@ class ServiceTrait extends EventTrait {
1522
1521
  while (this.onResponse.has(`service-call-response:${requestId}`))
1523
1522
  requestId = nanoid.nanoid();
1524
1523
  const responseTopic = this.options.topicMake(name, "service-call-response", this.options.id);
1525
- await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
1526
- spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
1524
+ await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
1527
1525
  const timerId = `service-call:${requestId}`;
1528
1526
  const promise = new Promise((resolve, reject) => {
1529
1527
  this.timerRefresh(timerId, async () => {
@@ -1719,10 +1717,8 @@ class SourceTrait extends ServiceTrait {
1719
1717
  requestId = nanoid.nanoid();
1720
1718
  const responseTopic = this.options.topicMake(name, "source-fetch-response", this.options.id);
1721
1719
  const chunkTopic = this.options.topicMake(name, "source-fetch-chunk", this.options.id);
1722
- await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
1723
- spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
1724
- await run(`subscribe to MQTT topic "${chunkTopic}"`, spool, () => this.subscriptions.subscribe(chunkTopic, { qos: options.qos ?? 2 }));
1725
- spool.roll(() => this.subscriptions.unsubscribe(chunkTopic));
1720
+ await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
1721
+ await this.subscribeTopicAndSpool(spool, chunkTopic, { qos: options.qos ?? 2 });
1726
1722
  const chunkCredit = this.options.chunkCredit;
1727
1723
  let chunksReceived = 0;
1728
1724
  let creditGranted = chunkCredit;
@@ -1835,13 +1831,13 @@ class SinkTrait extends SourceTrait {
1835
1831
  this.pushStreams = /* @__PURE__ */ new Map();
1836
1832
  this.pushSpools = /* @__PURE__ */ new Map();
1837
1833
  }
1838
- /* destroy sink trait */
1834
+ /* destroy trait */
1839
1835
  async destroy() {
1840
1836
  for (const stream of this.pushStreams.values())
1841
1837
  stream.destroy(new Error("sink destroyed"));
1838
+ this.pushStreams.clear();
1842
1839
  for (const spool of this.pushSpools.values())
1843
1840
  await spool.unroll();
1844
- this.pushStreams.clear();
1845
1841
  this.pushSpools.clear();
1846
1842
  await super.destroy();
1847
1843
  }
@@ -2033,8 +2029,7 @@ class SinkTrait extends SourceTrait {
2033
2029
  while (this.onResponse.has(`sink-push-response:${requestId}`) || this.onResponse.has(`sink-push-credit:${requestId}`))
2034
2030
  requestId = nanoid.nanoid();
2035
2031
  const responseTopic = this.options.topicMake(name, "sink-push-response", this.options.id);
2036
- await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
2037
- spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
2032
+ await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
2038
2033
  const abortController = new AbortController();
2039
2034
  const abortSignal = abortController.signal;
2040
2035
  if (data instanceof node_stream.Readable) {
@@ -2098,8 +2093,7 @@ class SinkTrait extends SourceTrait {
2098
2093
  creditGate = new CreditGate(initialCredit);
2099
2094
  if (creditGate) {
2100
2095
  const creditTopic = this.options.topicMake(name, "sink-push-credit", this.options.id);
2101
- await run(`subscribe to MQTT topic "${creditTopic}"`, spool, () => this.subscriptions.subscribe(creditTopic, { qos: options.qos ?? 2 }));
2102
- spool.roll(() => this.subscriptions.unsubscribe(creditTopic));
2096
+ await this.subscribeTopicAndSpool(spool, creditTopic, { qos: options.qos ?? 2 });
2103
2097
  const gate = creditGate;
2104
2098
  spool.roll(() => {
2105
2099
  gate.abort();
@@ -951,12 +951,6 @@ class BaseTrait extends TraceTrait {
951
951
  }
952
952
  };
953
953
  }
954
- /* subscribe to an MQTT topic and spool the unsubscription */
955
- async subscribeTopicAndSpool(spool, topic, options = {}) {
956
- await run(`subscribe to MQTT topic "${topic}"`, spool, () => this.subscribeTopic(topic, { qos: 2, ...options }));
957
- spool.roll(() => this.unsubscribeTopic(topic).catch(() => {
958
- }));
959
- }
960
954
  /* subscribe to an MQTT topic (Promise-based) */
961
955
  async subscribeTopic(topic, options = {}) {
962
956
  this.log("info", `subscribing to MQTT topic "${topic}"`);
@@ -1170,6 +1164,11 @@ class SubscriptionTrait extends BaseTrait {
1170
1164
  super(...arguments);
1171
1165
  this.subscriptions = new RefCountedSubscription((topic, options) => this.subscribeTopic(topic, options), (topic) => this.unsubscribeTopic(topic));
1172
1166
  }
1167
+ /* subscribe to an MQTT topic (reference-counted) and spool the unsubscription */
1168
+ async subscribeTopicAndSpool(spool, topic, options = {}) {
1169
+ await run(`subscribe to MQTT topic "${topic}"`, spool, () => this.subscriptions.subscribe(topic, { qos: 2, ...options }));
1170
+ spool.roll(() => this.subscriptions.unsubscribe(topic));
1171
+ }
1173
1172
  /* destroy subscription trait */
1174
1173
  async destroy() {
1175
1174
  await this.subscriptions.flush();
@@ -1501,8 +1500,7 @@ class ServiceTrait extends EventTrait {
1501
1500
  while (this.onResponse.has(`service-call-response:${requestId}`))
1502
1501
  requestId = nanoid();
1503
1502
  const responseTopic = this.options.topicMake(name, "service-call-response", this.options.id);
1504
- await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
1505
- spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
1503
+ await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
1506
1504
  const timerId = `service-call:${requestId}`;
1507
1505
  const promise = new Promise((resolve, reject) => {
1508
1506
  this.timerRefresh(timerId, async () => {
@@ -1698,10 +1696,8 @@ class SourceTrait extends ServiceTrait {
1698
1696
  requestId = nanoid();
1699
1697
  const responseTopic = this.options.topicMake(name, "source-fetch-response", this.options.id);
1700
1698
  const chunkTopic = this.options.topicMake(name, "source-fetch-chunk", this.options.id);
1701
- await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
1702
- spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
1703
- await run(`subscribe to MQTT topic "${chunkTopic}"`, spool, () => this.subscriptions.subscribe(chunkTopic, { qos: options.qos ?? 2 }));
1704
- spool.roll(() => this.subscriptions.unsubscribe(chunkTopic));
1699
+ await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
1700
+ await this.subscribeTopicAndSpool(spool, chunkTopic, { qos: options.qos ?? 2 });
1705
1701
  const chunkCredit = this.options.chunkCredit;
1706
1702
  let chunksReceived = 0;
1707
1703
  let creditGranted = chunkCredit;
@@ -1814,13 +1810,13 @@ class SinkTrait extends SourceTrait {
1814
1810
  this.pushStreams = /* @__PURE__ */ new Map();
1815
1811
  this.pushSpools = /* @__PURE__ */ new Map();
1816
1812
  }
1817
- /* destroy sink trait */
1813
+ /* destroy trait */
1818
1814
  async destroy() {
1819
1815
  for (const stream of this.pushStreams.values())
1820
1816
  stream.destroy(new Error("sink destroyed"));
1817
+ this.pushStreams.clear();
1821
1818
  for (const spool of this.pushSpools.values())
1822
1819
  await spool.unroll();
1823
- this.pushStreams.clear();
1824
1820
  this.pushSpools.clear();
1825
1821
  await super.destroy();
1826
1822
  }
@@ -2012,8 +2008,7 @@ class SinkTrait extends SourceTrait {
2012
2008
  while (this.onResponse.has(`sink-push-response:${requestId}`) || this.onResponse.has(`sink-push-credit:${requestId}`))
2013
2009
  requestId = nanoid();
2014
2010
  const responseTopic = this.options.topicMake(name, "sink-push-response", this.options.id);
2015
- await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
2016
- spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
2011
+ await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
2017
2012
  const abortController = new AbortController();
2018
2013
  const abortSignal = abortController.signal;
2019
2014
  if (data instanceof Readable) {
@@ -2077,8 +2072,7 @@ class SinkTrait extends SourceTrait {
2077
2072
  creditGate = new CreditGate(initialCredit);
2078
2073
  if (creditGate) {
2079
2074
  const creditTopic = this.options.topicMake(name, "sink-push-credit", this.options.id);
2080
- await run(`subscribe to MQTT topic "${creditTopic}"`, spool, () => this.subscriptions.subscribe(creditTopic, { qos: options.qos ?? 2 }));
2081
- spool.roll(() => this.subscriptions.unsubscribe(creditTopic));
2075
+ await this.subscribeTopicAndSpool(spool, creditTopic, { qos: options.qos ?? 2 });
2082
2076
  const gate = creditGate;
2083
2077
  spool.roll(() => {
2084
2078
  gate.abort();