mqtt-plus 1.4.25 → 1.4.27

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 (68) hide show
  1. package/.ase/config.yaml +1 -0
  2. package/.ase/service.log +34 -0
  3. package/.ase/service.yaml +1 -1
  4. package/.ase/task/TASK-P18.md +1 -1
  5. package/.ase/task/TASK-P21.md +1 -1
  6. package/.claude/settings.local.json +4 -1
  7. package/AGENTS.md +1 -1
  8. package/CHANGELOG.md +29 -5
  9. package/README.md +2 -2
  10. package/doc/mqtt-plus-broker-setup.md +1 -1
  11. package/doc/mqtt-plus-comm.md +3 -3
  12. package/doc/mqtt-plus-internals.md +6 -0
  13. package/dst-stage1/mqtt-plus-base.d.ts +2 -2
  14. package/dst-stage1/mqtt-plus-base.js +13 -10
  15. package/dst-stage1/mqtt-plus-error.js +54 -28
  16. package/dst-stage1/mqtt-plus-event.js +3 -1
  17. package/dst-stage1/mqtt-plus-options.js +7 -0
  18. package/dst-stage1/mqtt-plus-service.js +9 -6
  19. package/dst-stage1/mqtt-plus-sink.js +27 -54
  20. package/dst-stage1/mqtt-plus-source.js +13 -9
  21. package/dst-stage1/mqtt-plus-util.d.ts +1 -0
  22. package/dst-stage1/mqtt-plus-util.js +25 -4
  23. package/dst-stage1/mqtt-plus.d.ts +5 -1
  24. package/dst-stage1/mqtt-plus.js +10 -1
  25. package/dst-stage2/mqtt-plus.cjs.cjs +169 -123
  26. package/dst-stage2/mqtt-plus.esm.js +168 -122
  27. package/dst-stage2/mqtt-plus.umd.js +11 -11
  28. package/etc/stx.conf +3 -2
  29. package/etc/vite.mts +1 -1
  30. package/package.d/@typescript-eslint+typescript-estree+8.66.0.patch +12 -0
  31. package/package.d/{vite+8.1.4.patch → vite+8.2.1.patch} +2 -2
  32. package/package.json +16 -15
  33. package/src/mqtt-plus-base.ts +15 -12
  34. package/src/mqtt-plus-error.ts +65 -33
  35. package/src/mqtt-plus-event.ts +2 -1
  36. package/src/mqtt-plus-options.ts +8 -0
  37. package/src/mqtt-plus-service.ts +9 -6
  38. package/src/mqtt-plus-sink.ts +28 -54
  39. package/src/mqtt-plus-source.ts +13 -9
  40. package/src/mqtt-plus-util.ts +27 -4
  41. package/src/mqtt-plus.ts +14 -3
  42. package/tst/mqtt-plus-0-fixture.ts +1 -0
  43. package/tst/mqtt-plus-2-event.spec.ts +25 -0
  44. package/tst/mqtt-plus-4-sink.spec.ts +23 -0
  45. package/tst/mqtt-plus-5-source.spec.ts +26 -0
  46. package/tst/mqtt-plus-6-misc.spec.ts +58 -0
  47. package/tst/mqtt-plus-8-run.spec.ts +60 -0
  48. package/dst-stage1/mqtt-plus-api.js.map +0 -1
  49. package/dst-stage1/mqtt-plus-auth.js.map +0 -1
  50. package/dst-stage1/mqtt-plus-base.js.map +0 -1
  51. package/dst-stage1/mqtt-plus-codec.js.map +0 -1
  52. package/dst-stage1/mqtt-plus-encode.js.map +0 -1
  53. package/dst-stage1/mqtt-plus-error.js.map +0 -1
  54. package/dst-stage1/mqtt-plus-event.js.map +0 -1
  55. package/dst-stage1/mqtt-plus-info.js.map +0 -1
  56. package/dst-stage1/mqtt-plus-meta.js.map +0 -1
  57. package/dst-stage1/mqtt-plus-msg.js.map +0 -1
  58. package/dst-stage1/mqtt-plus-options.js.map +0 -1
  59. package/dst-stage1/mqtt-plus-service.js.map +0 -1
  60. package/dst-stage1/mqtt-plus-sink.js.map +0 -1
  61. package/dst-stage1/mqtt-plus-source.js.map +0 -1
  62. package/dst-stage1/mqtt-plus-subscription.js.map +0 -1
  63. package/dst-stage1/mqtt-plus-timer.js.map +0 -1
  64. package/dst-stage1/mqtt-plus-trace.js.map +0 -1
  65. package/dst-stage1/mqtt-plus-util.js.map +0 -1
  66. package/dst-stage1/mqtt-plus-version.js.map +0 -1
  67. package/dst-stage1/mqtt-plus.js.map +0 -1
  68. package/package.d/@typescript-eslint+typescript-estree+8.63.0.patch +0 -12
@@ -119,6 +119,7 @@ var ReadableTee = class extends Readable {
119
119
  }
120
120
  };
121
121
  async function sendBufferAsChunks(buffer, chunkSize, sendChunk, creditGate, abortSignal) {
122
+ if (!(Number.isFinite(chunkSize) && chunkSize > 0)) throw new Error("invalid chunk size");
122
123
  if (buffer.byteLength === 0) await sendChunk(void 0, void 0, true);
123
124
  else for (let i = 0; i < buffer.byteLength; i += chunkSize) {
124
125
  if (abortSignal?.aborted) throw abortSignal.reason ?? /* @__PURE__ */ new Error("aborted");
@@ -130,6 +131,7 @@ async function sendBufferAsChunks(buffer, chunkSize, sendChunk, creditGate, abor
130
131
  }
131
132
  }
132
133
  async function sendStreamAsChunks(readable, chunkSize, sendChunk, creditGate, abortSignal) {
134
+ if (!(Number.isFinite(chunkSize) && chunkSize > 0)) throw new Error("invalid chunk size");
133
135
  let pending;
134
136
  for await (const raw of readable) {
135
137
  if (abortSignal?.aborted) throw abortSignal.reason ?? /* @__PURE__ */ new Error("aborted");
@@ -152,11 +154,17 @@ async function sendStreamAsChunks(readable, chunkSize, sendChunk, creditGate, ab
152
154
  await sendChunk(pending, void 0, true);
153
155
  } else await sendChunk(void 0, void 0, true);
154
156
  }
157
+ function alignHandlerParams(params, handler) {
158
+ if (typeof handler !== "function") return params;
159
+ const arity = handler.length - 1;
160
+ if (params.length >= arity) return params;
161
+ return [...params, ...Array(arity - params.length).fill(void 0)];
162
+ }
155
163
  function makeMutuallyExclusiveFields(obj, f1Name, f2Name, onConsumed) {
156
164
  if (!(typeof obj === "object" && obj !== null)) throw new Error("invalid object");
157
165
  let consumed;
158
- const f1Value = obj[f1Name];
159
- const f2Value = obj[f2Name];
166
+ let f1Value = obj[f1Name];
167
+ let f2Value = obj[f2Name];
160
168
  Object.defineProperty(obj, f1Name, {
161
169
  get: () => {
162
170
  if (consumed === "f2") throw new Error(`field "${f1Name}" is mutually exclusive with field "${f2Name}" and "${f2Name}" was already consumed`);
@@ -164,7 +172,10 @@ function makeMutuallyExclusiveFields(obj, f1Name, f2Name, onConsumed) {
164
172
  consumed = "f1";
165
173
  return f1Value;
166
174
  },
167
- enumerable: true,
175
+ set: (value) => {
176
+ f1Value = value;
177
+ },
178
+ enumerable: false,
168
179
  configurable: true
169
180
  });
170
181
  Object.defineProperty(obj, f2Name, {
@@ -174,7 +185,10 @@ function makeMutuallyExclusiveFields(obj, f1Name, f2Name, onConsumed) {
174
185
  consumed = "f2";
175
186
  return f2Value;
176
187
  },
177
- enumerable: true,
188
+ set: (value) => {
189
+ f2Value = value;
190
+ },
191
+ enumerable: false,
178
192
  configurable: true
179
193
  });
180
194
  }
@@ -258,8 +272,10 @@ function ensureError(error, prefix, debug = false) {
258
272
  } else return new Error(msg);
259
273
  }
260
274
  function runFinally(isAsync, onfinally, description) {
261
- if (!onfinally) if (isAsync) return Promise.resolve();
262
- else return;
275
+ if (!onfinally) {
276
+ if (isAsync) return Promise.resolve();
277
+ else return;
278
+ }
263
279
  let result;
264
280
  try {
265
281
  result = onfinally();
@@ -275,8 +291,10 @@ function runFinally(isAsync, onfinally, description) {
275
291
  return result;
276
292
  }
277
293
  function runUnroll(isAsync, spool) {
278
- if (!spool) if (isAsync) return Promise.resolve();
279
- else return;
294
+ if (!spool) {
295
+ if (isAsync) return Promise.resolve();
296
+ else return;
297
+ }
280
298
  let result = spool.unroll();
281
299
  if (!isAsync && result instanceof Promise) throw new Error("spool unroll returned Promise in non-async context");
282
300
  if (isAsync && !(result instanceof Promise)) result = Promise.resolve(result);
@@ -290,9 +308,12 @@ function runSettle(isAsync, pending, spool, onfinally, description) {
290
308
  } catch (arg) {
291
309
  failure = ensureError(arg, description);
292
310
  }
293
- if (pending || failure !== void 0) {
311
+ if (pending !== void 0 || failure !== void 0) {
294
312
  runUnroll(false, spool);
295
- if (!pending && failure !== void 0) throw failure;
313
+ if (failure !== void 0) {
314
+ if (pending !== void 0) pending.suppressed = failure;
315
+ else throw failure;
316
+ }
296
317
  }
297
318
  } else return (async () => {
298
319
  let failure;
@@ -301,12 +322,34 @@ function runSettle(isAsync, pending, spool, onfinally, description) {
301
322
  } catch (arg) {
302
323
  failure = ensureError(arg, description);
303
324
  }
304
- if (pending || failure !== void 0) {
325
+ if (pending !== void 0 || failure !== void 0) {
305
326
  await runUnroll(true, spool);
306
- if (!pending && failure !== void 0) throw failure;
327
+ if (failure !== void 0) {
328
+ if (pending !== void 0) pending.suppressed = failure;
329
+ else throw failure;
330
+ }
307
331
  }
308
332
  })();
309
333
  }
334
+ function runFail(error, spool, onfinally, description) {
335
+ const attach = (arg) => {
336
+ error.suppressed = ensureError(arg, description);
337
+ };
338
+ let result = void 0;
339
+ try {
340
+ result = onfinally?.();
341
+ } catch (arg) {
342
+ attach(arg);
343
+ }
344
+ if (result instanceof Promise) return result.catch(attach).then(() => spool?.unroll()).then(() => {
345
+ throw error;
346
+ });
347
+ const unrolled = spool?.unroll();
348
+ if (unrolled instanceof Promise) return unrolled.then(() => {
349
+ throw error;
350
+ });
351
+ throw error;
352
+ }
310
353
  function run(...args) {
311
354
  let description;
312
355
  let spool;
@@ -352,25 +395,18 @@ function run(...args) {
352
395
  try {
353
396
  result = action();
354
397
  } catch (arg) {
355
- let error = ensureError(arg, description);
356
- if (oncatch) {
357
- try {
358
- result = oncatch(error);
359
- } catch (arg) {
360
- error = ensureError(arg, description);
361
- runSettle(false, true, spool, onfinally, description);
362
- throw error;
363
- }
364
- if (spool && oncleanup) spool.roll(result, oncleanup);
365
- runSettle(false, false, spool, onfinally, description);
366
- return result;
398
+ const error = ensureError(arg, description);
399
+ if (!oncatch) return runFail(error, spool, onfinally, description);
400
+ try {
401
+ result = oncatch(error);
402
+ } catch (arg) {
403
+ return runFail(ensureError(arg, description), spool, onfinally, description);
367
404
  }
368
- runSettle(false, true, spool, onfinally, description);
369
- throw error;
405
+ oncatch = void 0;
370
406
  }
371
407
  if (result instanceof Promise) return result.then(async (result) => {
372
408
  if (spool && oncleanup) spool.roll(result, oncleanup);
373
- await runSettle(true, false, spool, onfinally, description);
409
+ await runSettle(true, void 0, spool, onfinally, description);
374
410
  return result;
375
411
  }, async (arg) => {
376
412
  let error = ensureError(arg, description);
@@ -380,19 +416,19 @@ function run(...args) {
380
416
  result = await oncatch(error);
381
417
  } catch (arg) {
382
418
  error = ensureError(arg, description);
383
- await runSettle(true, true, spool, onfinally, description);
419
+ await runSettle(true, error, spool, onfinally, description);
384
420
  throw error;
385
421
  }
386
422
  if (spool && oncleanup) spool.roll(result, oncleanup);
387
- await runSettle(true, false, spool, onfinally, description);
423
+ await runSettle(true, void 0, spool, onfinally, description);
388
424
  return result;
389
425
  }
390
- await runSettle(true, true, spool, onfinally, description);
426
+ await runSettle(true, error, spool, onfinally, description);
391
427
  throw error;
392
428
  });
393
429
  else {
394
430
  if (spool && oncleanup) spool.roll(result, oncleanup);
395
- runSettle(false, false, spool, onfinally, description);
431
+ runSettle(false, void 0, spool, onfinally, description);
396
432
  return result;
397
433
  }
398
434
  }
@@ -403,9 +439,9 @@ var OptionsTrait = class {
403
439
  this.options = {
404
440
  id: "",
405
441
  codec: "cbor",
406
- timeout: 10 * 1e3,
442
+ timeout: 1e4,
407
443
  share: "",
408
- chunkSize: 16 * 1024,
444
+ chunkSize: 16384,
409
445
  chunkCredit: 4,
410
446
  topicMake: (name, operation, peerId) => {
411
447
  return `${name}/${operation}/${peerId ?? "any"}`;
@@ -420,6 +456,9 @@ var OptionsTrait = class {
420
456
  },
421
457
  ...options
422
458
  };
459
+ if (!(Number.isFinite(this.options.timeout) && this.options.timeout > 0)) throw new Error("options: \"timeout\" must be a positive number");
460
+ if (!(Number.isInteger(this.options.chunkSize) && this.options.chunkSize > 0)) throw new Error("options: \"chunkSize\" must be a positive integer");
461
+ if (!(Number.isInteger(this.options.chunkCredit) && this.options.chunkCredit >= 0)) throw new Error("options: \"chunkCredit\" must be a non-negative integer");
423
462
  }
424
463
  };
425
464
  //#endregion
@@ -932,13 +971,13 @@ var BaseTrait = class extends TraceTrait {
932
971
  };
933
972
  } });
934
973
  }
935
- this.mqtt = mqtt;
974
+ this._mqtt = mqtt;
936
975
  if (this.options.id === "") {
937
976
  const clientId = mqtt.isFakeProxy ? void 0 : mqtt.options?.clientId;
938
977
  this.options.id = typeof clientId === "string" && clientId !== "" ? clientId : nanoid();
939
978
  }
940
979
  this.log("info", "hooking into MQTT client");
941
- this.messageHandler = (topic, message, packet) => {
980
+ this._messageHandler = (topic, message, packet) => {
942
981
  if (this.destroyed) return;
943
982
  let input;
944
983
  if (this.options.codec === "json") input = message.toString();
@@ -949,12 +988,12 @@ var BaseTrait = class extends TraceTrait {
949
988
  }
950
989
  this._onMessage(topic, input, packet);
951
990
  };
952
- this.mqtt.on("message", this.messageHandler);
991
+ this._mqtt.on("message", this._messageHandler);
953
992
  }
954
993
  async destroy() {
955
994
  this.destroyed = true;
956
995
  this.log("info", "un-hooking from MQTT client");
957
- this.mqtt.off("message", this.messageHandler);
996
+ this._mqtt.off("message", this._messageHandler);
958
997
  this.onRequest.clear();
959
998
  this.onResponse.clear();
960
999
  }
@@ -973,7 +1012,7 @@ var BaseTrait = class extends TraceTrait {
973
1012
  async subscribeTopic(topic, options = {}) {
974
1013
  this.log("info", `subscribing to MQTT topic "${topic}"`);
975
1014
  return new Promise((resolve, reject) => {
976
- this.mqtt.subscribe(topic, {
1015
+ this._mqtt.subscribe(topic, {
977
1016
  qos: 2,
978
1017
  ...options
979
1018
  }, (err, granted) => {
@@ -991,7 +1030,7 @@ var BaseTrait = class extends TraceTrait {
991
1030
  async unsubscribeTopic(topic) {
992
1031
  this.log("info", `unsubscribing from MQTT topic "${topic}"`);
993
1032
  return new Promise((resolve, reject) => {
994
- this.mqtt.unsubscribe(topic, (err, _packet) => {
1033
+ this._mqtt.unsubscribe(topic, (err, _packet) => {
995
1034
  if (err) {
996
1035
  this.error(err, `unsubscribing from MQTT topic "${topic}" failed`);
997
1036
  reject(err);
@@ -1015,7 +1054,7 @@ var BaseTrait = class extends TraceTrait {
1015
1054
  this.log("debug", `publishing to MQTT topic "${topic}"`, { message: messageOnDemand });
1016
1055
  return new Promise((resolve, reject) => {
1017
1056
  const messageData = typeof message === "string" ? message : Buffer.from(message.buffer, message.byteOffset, message.byteLength);
1018
- this.mqtt.publish(topic, messageData, options, (err) => {
1057
+ this._mqtt.publish(topic, messageData, options, (err) => {
1019
1058
  if (err) {
1020
1059
  this.error(err, `publishing to MQTT topic "${topic}" failed`);
1021
1060
  reject(err);
@@ -1050,7 +1089,10 @@ var BaseTrait = class extends TraceTrait {
1050
1089
  return;
1051
1090
  }
1052
1091
  this.log("debug", `received from MQTT topic "${topic}"`, { message });
1053
- if (topicMatch.peerId !== void 0 && message.receiver !== void 0 && message.receiver !== topicMatch.peerId) this.log("warning", `receiver mismatch on direct topic "${topic}" (expected "${topicMatch.peerId}", got "${message.receiver}")`);
1092
+ if (topicMatch.peerId !== void 0 && message.receiver !== void 0 && message.receiver !== topicMatch.peerId) {
1093
+ this.log("warning", `receiver mismatch -- dropped message from direct topic "${topic}" (expected "${topicMatch.peerId}", got "${message.receiver}")`);
1094
+ return;
1095
+ }
1054
1096
  if (this.msg.isRequest(message)) {
1055
1097
  const handler = this.onRequest.get(`${message.type}:${message.name}`);
1056
1098
  if (handler !== void 0) Promise.resolve().then(() => {
@@ -1073,7 +1115,7 @@ var BaseTrait = class extends TraceTrait {
1073
1115
  //#endregion
1074
1116
  //#region dst-stage1/mqtt-plus-subscription.js
1075
1117
  var RefCountedSubscription = class {
1076
- constructor(subscribeFn, unsubscribeFn, lingerMs = 30 * 1e3) {
1118
+ constructor(subscribeFn, unsubscribeFn, lingerMs = 3e4) {
1077
1119
  this.subscribeFn = subscribeFn;
1078
1120
  this.unsubscribeFn = unsubscribeFn;
1079
1121
  this.lingerMs = lingerMs;
@@ -1089,12 +1131,14 @@ var RefCountedSubscription = class {
1089
1131
  }
1090
1132
  decrementCount(topic) {
1091
1133
  const count = this.counts.get(topic);
1092
- if (count !== void 0) if (count <= 1) {
1093
- this.counts.delete(topic);
1094
- return 0;
1095
- } else {
1096
- this.counts.set(topic, count - 1);
1097
- return count - 1;
1134
+ if (count !== void 0) {
1135
+ if (count <= 1) {
1136
+ this.counts.delete(topic);
1137
+ return 0;
1138
+ } else {
1139
+ this.counts.set(topic, count - 1);
1140
+ return count - 1;
1141
+ }
1098
1142
  }
1099
1143
  }
1100
1144
  clearCount(topic) {
@@ -1133,21 +1177,23 @@ var RefCountedSubscription = class {
1133
1177
  }
1134
1178
  }
1135
1179
  async unsubscribe(topic) {
1136
- if (this.decrementCount(topic) === 0) if (this.lingerMs > 0 && !topic.startsWith("$share/")) {
1137
- const timer = setTimeout(() => {
1138
- this.lingers.delete(topic);
1180
+ if (this.decrementCount(topic) === 0) {
1181
+ if (this.lingerMs > 0 && !topic.startsWith("$share/")) {
1182
+ const timer = setTimeout(() => {
1183
+ this.lingers.delete(topic);
1184
+ const promise = this.unsubscribeFn(topic).catch(() => {}).finally(() => {
1185
+ this.unsubbing.delete(topic);
1186
+ });
1187
+ this.unsubbing.set(topic, promise);
1188
+ }, this.lingerMs);
1189
+ this.lingers.set(topic, timer);
1190
+ } else {
1139
1191
  const promise = this.unsubscribeFn(topic).catch(() => {}).finally(() => {
1140
1192
  this.unsubbing.delete(topic);
1141
1193
  });
1142
1194
  this.unsubbing.set(topic, promise);
1143
- }, this.lingerMs);
1144
- this.lingers.set(topic, timer);
1145
- } else {
1146
- const promise = this.unsubscribeFn(topic).catch(() => {}).finally(() => {
1147
- this.unsubbing.delete(topic);
1148
- });
1149
- this.unsubbing.set(topic, promise);
1150
- await promise;
1195
+ await promise;
1196
+ }
1151
1197
  }
1152
1198
  }
1153
1199
  async flush() {
@@ -1416,7 +1462,7 @@ var EventTrait = class extends AuthTrait {
1416
1462
  this.log("warning", `event name mismatch -- dropped request for "${name}" (topic: "${topicName}", payload: "${request.name}")`, { requestId });
1417
1463
  return;
1418
1464
  }
1419
- const params = request.params ?? [];
1465
+ const params = alignHandlerParams(request.params ?? [], callback);
1420
1466
  if (this.eventControllers.has(requestId)) {
1421
1467
  this.log("warning", `duplicate event request id -- dropped request for event "${name}"`, { requestId });
1422
1468
  return;
@@ -1540,7 +1586,7 @@ var ServiceTrait = class extends EventTrait {
1540
1586
  if (request.receiver && request.receiver !== this.options.id) return;
1541
1587
  const requestId = request.id;
1542
1588
  const senderId = request.sender;
1543
- const params = request.params ?? [];
1589
+ const params = alignHandlerParams(request.params ?? [], callback);
1544
1590
  if (senderId === void 0 || senderId === "") {
1545
1591
  this.error(/* @__PURE__ */ new Error("invalid request: missing sender"));
1546
1592
  return;
@@ -1644,7 +1690,9 @@ var ServiceTrait = class extends EventTrait {
1644
1690
  const settle = () => {
1645
1691
  if (settled) return false;
1646
1692
  settled = true;
1647
- spool.unroll();
1693
+ spool.unroll(false)?.catch((err) => {
1694
+ this.error(ensureError(err, `cleanup after call of service "${name}" failed`));
1695
+ });
1648
1696
  return true;
1649
1697
  };
1650
1698
  const promise = new Promise((resolve, reject) => {
@@ -1751,7 +1799,7 @@ var SourceTrait = class extends ServiceTrait {
1751
1799
  this.onRequest.set(`source-fetch-request:${name}`, async (request, topicName) => {
1752
1800
  if (request.receiver && request.receiver !== this.options.id) return;
1753
1801
  const requestId = request.id;
1754
- const params = request.params ?? [];
1802
+ const params = alignHandlerParams(request.params ?? [], callback);
1755
1803
  const sender = request.sender;
1756
1804
  const receiver = request.receiver;
1757
1805
  const reqSpool = new Spool();
@@ -1804,11 +1852,16 @@ var SourceTrait = class extends ServiceTrait {
1804
1852
  if (current.size === 0) this.sourceRequests.delete(name);
1805
1853
  }
1806
1854
  });
1807
- abortSignal.addEventListener("abort", () => {
1808
- if (info.stream instanceof Readable && !info.stream.destroyed) info.stream.destroy(ensureError(abortSignal.reason));
1809
- }, { once: true });
1855
+ const noopError = () => {};
1856
+ const destroyStream = () => {
1857
+ if (info.stream instanceof Readable && !info.stream.destroyed) {
1858
+ info.stream.on("error", noopError);
1859
+ info.stream.destroy(ensureError(abortSignal.reason));
1860
+ }
1861
+ };
1862
+ abortSignal.addEventListener("abort", destroyStream, { once: true });
1810
1863
  reqSpool.roll(() => {
1811
- if (abortSignal.aborted && info.stream instanceof Readable && !info.stream.destroyed) info.stream.destroy(ensureError(abortSignal.reason));
1864
+ if (abortSignal.aborted) destroyStream();
1812
1865
  });
1813
1866
  let abortReject;
1814
1867
  const abortPromise = new Promise((_resolve, reject) => {
@@ -1955,7 +2008,7 @@ var SourceTrait = class extends ServiceTrait {
1955
2008
  const sourceNameMismatchError = (kind, actualName) => /* @__PURE__ */ new Error(`${kind} name mismatch (expected "${name}", got "${actualName}")`);
1956
2009
  await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
1957
2010
  const chunkCredit = this.options.chunkCredit;
1958
- const maxBufferedBytes = chunkCredit > 0 ? chunkCredit * this.options.chunkSize : 16 * 1024;
2011
+ const maxBufferedBytes = chunkCredit > 0 ? chunkCredit * this.options.chunkSize : 16384;
1959
2012
  let chunksReceived = 0;
1960
2013
  let creditGranted = chunkCredit;
1961
2014
  let responderId = receiver;
@@ -2193,7 +2246,7 @@ var SinkTrait = class extends SourceTrait {
2193
2246
  this.onRequest.set(`sink-push-request:${name}`, async (request, topicName) => {
2194
2247
  if (request.receiver && request.receiver !== this.options.id) return;
2195
2248
  const requestId = request.id;
2196
- const params = request.params ?? [];
2249
+ const params = alignHandlerParams(request.params ?? [], callback);
2197
2250
  const sender = request.sender;
2198
2251
  const receiver = request.receiver;
2199
2252
  const reqSpool = new Spool();
@@ -2254,7 +2307,7 @@ var SinkTrait = class extends SourceTrait {
2254
2307
  chunksReceived: 0,
2255
2308
  creditGranted: chunkCredit
2256
2309
  } : void 0;
2257
- const maxBufferedBytes = chunkCredit > 0 ? chunkCredit * this.options.chunkSize : 16 * 1024;
2310
+ const maxBufferedBytes = chunkCredit > 0 ? chunkCredit * this.options.chunkSize : 16384;
2258
2311
  let streamEnded = false;
2259
2312
  const pushTimerId = `sink-push-recv:${requestId}`;
2260
2313
  const refreshPushTimeout = () => this.timerRefresh(pushTimerId, () => {
@@ -2484,8 +2537,12 @@ var SinkTrait = class extends SourceTrait {
2484
2537
  });
2485
2538
  if (data instanceof Readable) {
2486
2539
  const stream = data;
2540
+ const noopError = () => {};
2487
2541
  abortSignal.addEventListener("abort", () => {
2488
- if (!stream.destroyed) stream.destroy(ensureError(abortSignal.reason));
2542
+ if (!stream.destroyed) {
2543
+ stream.on("error", noopError);
2544
+ stream.destroy(ensureError(abortSignal.reason));
2545
+ }
2489
2546
  }, { once: true });
2490
2547
  }
2491
2548
  const pushTimerId = `sink-push-send:${requestId}`;
@@ -2519,18 +2576,21 @@ var SinkTrait = class extends SourceTrait {
2519
2576
  pushFinalizeReject = reject;
2520
2577
  });
2521
2578
  pushFinalize.catch(() => {});
2579
+ const failPush = (error) => {
2580
+ abortController.abort(error);
2581
+ if (!pushAcked) {
2582
+ pushInitialSettled = true;
2583
+ pushInitialReject(error);
2584
+ } else if (!pushFinalized) {
2585
+ pushFinalized = true;
2586
+ pushFinalizeReject(error);
2587
+ }
2588
+ };
2522
2589
  const lockResponder = (kind, sender) => {
2523
2590
  if (sender === void 0 || sender === "") {
2524
2591
  const error = /* @__PURE__ */ new Error(`received ${kind} without sender`);
2525
2592
  remoteErrorObject = error;
2526
- abortController.abort(error);
2527
- if (!pushAcked) {
2528
- pushInitialSettled = true;
2529
- pushInitialReject(error);
2530
- } else if (!pushFinalized) {
2531
- pushFinalized = true;
2532
- pushFinalizeReject(error);
2533
- }
2593
+ failPush(error);
2534
2594
  return false;
2535
2595
  }
2536
2596
  if (responderId === void 0) responderId = sender;
@@ -2548,37 +2608,25 @@ var SinkTrait = class extends SourceTrait {
2548
2608
  if (response.name !== name) {
2549
2609
  const error = /* @__PURE__ */ new Error(`sink response name mismatch (expected "${name}", got "${response.name}")`);
2550
2610
  remoteErrorObject = error;
2551
- abortController.abort(error);
2552
- if (!pushAcked) {
2553
- pushInitialSettled = true;
2554
- pushInitialReject(error);
2555
- } else if (!pushFinalized) {
2556
- pushFinalized = true;
2557
- pushFinalizeReject(error);
2558
- }
2611
+ failPush(error);
2559
2612
  return;
2560
2613
  }
2561
2614
  if (!lockResponder("sink response", response.sender)) return;
2562
2615
  if (response.error) {
2563
2616
  const error = new Error(response.error);
2564
2617
  remoteErrorObject = error;
2565
- abortController.abort(error);
2566
- if (!pushAcked) {
2567
- pushInitialSettled = true;
2568
- pushInitialReject(error);
2569
- } else if (!pushFinalized) {
2570
- pushFinalized = true;
2571
- pushFinalizeReject(error);
2572
- }
2618
+ failPush(error);
2573
2619
  } else if (!pushAcked) {
2574
2620
  initialCredit = response.credit;
2575
2621
  pushAcked = true;
2576
2622
  pushInitialSettled = true;
2577
2623
  pushInitialResolve();
2578
- } else if (!pushFinalized) if (pushDataComplete) {
2579
- pushFinalized = true;
2580
- pushFinalizeResolve();
2581
- } else pushTerminalReceived = true;
2624
+ } else if (!pushFinalized) {
2625
+ if (pushDataComplete) {
2626
+ pushFinalized = true;
2627
+ pushFinalizeResolve();
2628
+ } else pushTerminalReceived = true;
2629
+ }
2582
2630
  });
2583
2631
  spool.roll(() => {
2584
2632
  this.onResponse.delete(`sink-push-response:${requestId}`);
@@ -2587,28 +2635,14 @@ var SinkTrait = class extends SourceTrait {
2587
2635
  if (response.name !== name) {
2588
2636
  const error = /* @__PURE__ */ new Error(`sink credit name mismatch (expected "${name}", got "${response.name}")`);
2589
2637
  remoteErrorObject = error;
2590
- abortController.abort(error);
2591
- if (!pushAcked) {
2592
- pushInitialSettled = true;
2593
- pushInitialReject(error);
2594
- } else if (!pushFinalized) {
2595
- pushFinalized = true;
2596
- pushFinalizeReject(error);
2597
- }
2638
+ failPush(error);
2598
2639
  return;
2599
2640
  }
2600
2641
  if (!lockResponder("sink credit", response.sender)) return;
2601
2642
  if (response.credit === 0) {
2602
2643
  cancelledByReceiver = true;
2603
2644
  const error = /* @__PURE__ */ new Error(`push to sink "${name}" cancelled by receiver`);
2604
- abortController.abort(error);
2605
- if (!pushAcked) {
2606
- pushInitialSettled = true;
2607
- pushInitialReject(error);
2608
- } else if (!pushFinalized) {
2609
- pushFinalized = true;
2610
- pushFinalizeReject(error);
2611
- }
2645
+ failPush(error);
2612
2646
  return;
2613
2647
  }
2614
2648
  if (creditGate !== void 0) {
@@ -2617,11 +2651,7 @@ var SinkTrait = class extends SourceTrait {
2617
2651
  } else if (pushAcked && initialCredit === void 0) {
2618
2652
  const error = /* @__PURE__ */ new Error(`push to sink "${name}" received unsolicited credit (credit-flow disabled)`);
2619
2653
  remoteErrorObject = error;
2620
- abortController.abort(error);
2621
- if (!pushFinalized) {
2622
- pushFinalized = true;
2623
- pushFinalizeReject(error);
2624
- }
2654
+ failPush(error);
2625
2655
  } else pendingCredit += response.credit;
2626
2656
  });
2627
2657
  spool.roll(() => {
@@ -2708,7 +2738,7 @@ var SinkTrait = class extends SourceTrait {
2708
2738
  }
2709
2739
  } catch {}
2710
2740
  if (pushAcked && !remoteErrorObject) await new Promise((resolve) => {
2711
- setImmediate(resolve);
2741
+ setTimeout(resolve, 0);
2712
2742
  });
2713
2743
  if (remoteErrorObject) throw remoteErrorObject;
2714
2744
  throw err;
@@ -2719,6 +2749,22 @@ var SinkTrait = class extends SourceTrait {
2719
2749
  };
2720
2750
  //#endregion
2721
2751
  //#region dst-stage1/mqtt-plus.js
2722
- var MQTTp = class extends SinkTrait {};
2752
+ var MQTTp = class extends SinkTrait {
2753
+ static {
2754
+ this.VERSION = "1.4";
2755
+ }
2756
+ static {
2757
+ this.MIN_VERSION = "1.4";
2758
+ }
2759
+ static {
2760
+ this.version = version;
2761
+ }
2762
+ static {
2763
+ this.minVersion = minVersion;
2764
+ }
2765
+ static {
2766
+ this.versionToNum = versionToNum;
2767
+ }
2768
+ };
2723
2769
  //#endregion
2724
2770
  export { MQTTp as default };