mqtt-plus 1.4.23 → 1.4.24

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 (37) hide show
  1. package/.ase/service.log +10381 -0
  2. package/.ase/task/TASK-P18.md +47 -0
  3. package/.ase/task/TASK-P2.md +76 -0
  4. package/.ase/task/TASK-P21.md +75 -0
  5. package/.claude/settings.local.json +7 -0
  6. package/CHANGELOG.md +18 -0
  7. package/README.md +4 -4
  8. package/dst-stage1/mqtt-plus-api.d.ts +6 -6
  9. package/dst-stage1/mqtt-plus-base.js +21 -4
  10. package/dst-stage1/mqtt-plus-codec.js +15 -6
  11. package/dst-stage1/mqtt-plus-error.js +47 -14
  12. package/dst-stage1/mqtt-plus-event.js +1 -1
  13. package/dst-stage1/mqtt-plus-service.js +1 -1
  14. package/dst-stage1/mqtt-plus-sink.js +10 -2
  15. package/dst-stage1/mqtt-plus-source.d.ts +1 -0
  16. package/dst-stage1/mqtt-plus-source.js +11 -3
  17. package/dst-stage1/mqtt-plus-subscription.js +7 -2
  18. package/dst-stage1/mqtt-plus-timer.js +4 -2
  19. package/dst-stage2/mqtt-plus.cjs.cjs +89 -35
  20. package/dst-stage2/mqtt-plus.esm.js +89 -35
  21. package/dst-stage2/mqtt-plus.umd.js +11 -11
  22. package/package.json +1 -1
  23. package/src/mqtt-plus-api.ts +5 -5
  24. package/src/mqtt-plus-base.ts +21 -4
  25. package/src/mqtt-plus-codec.ts +15 -8
  26. package/src/mqtt-plus-error.ts +52 -14
  27. package/src/mqtt-plus-event.ts +1 -1
  28. package/src/mqtt-plus-service.ts +1 -1
  29. package/src/mqtt-plus-sink.ts +12 -2
  30. package/src/mqtt-plus-source.ts +13 -3
  31. package/src/mqtt-plus-subscription.ts +7 -2
  32. package/src/mqtt-plus-timer.ts +4 -2
  33. package/tst/mqtt-plus-4-sink.spec.ts +43 -0
  34. package/tst/mqtt-plus-5-source.spec.ts +35 -0
  35. package/tst/mqtt-plus-6-misc.spec.ts +82 -5
  36. package/tst/mqtt-plus-8-run.spec.ts +107 -0
  37. package/.ase/service.yaml +0 -1
@@ -309,6 +309,31 @@ function runUnroll(isAsync, spool) {
309
309
  if (isAsync && !(result instanceof Promise)) result = Promise.resolve(result);
310
310
  return result;
311
311
  }
312
+ function runSettle(isAsync, pending, spool, onfinally, description) {
313
+ if (!isAsync) {
314
+ let failure;
315
+ try {
316
+ runFinally(false, onfinally, description);
317
+ } catch (arg) {
318
+ failure = ensureError(arg, description);
319
+ }
320
+ if (pending || failure !== void 0) {
321
+ runUnroll(false, spool);
322
+ if (!pending && failure !== void 0) throw failure;
323
+ }
324
+ } else return (async () => {
325
+ let failure;
326
+ try {
327
+ await runFinally(true, onfinally, description);
328
+ } catch (arg) {
329
+ failure = ensureError(arg, description);
330
+ }
331
+ if (pending || failure !== void 0) {
332
+ await runUnroll(true, spool);
333
+ if (!pending && failure !== void 0) throw failure;
334
+ }
335
+ })();
336
+ }
312
337
  function run(...args) {
313
338
  let description;
314
339
  let spool;
@@ -360,40 +385,41 @@ function run(...args) {
360
385
  result = oncatch(error);
361
386
  } catch (arg) {
362
387
  error = ensureError(arg, description);
363
- runFinally(false, onfinally, description);
364
- runUnroll(false, spool);
388
+ runSettle(false, true, spool, onfinally, description);
365
389
  throw error;
366
390
  }
367
- runFinally(false, onfinally, description);
391
+ if (spool && oncleanup) spool.roll(result, oncleanup);
392
+ runSettle(false, false, spool, onfinally, description);
368
393
  return result;
369
394
  }
370
- runFinally(false, onfinally, description);
371
- runUnroll(false, spool);
395
+ runSettle(false, true, spool, onfinally, description);
372
396
  throw error;
373
397
  }
374
398
  if (result instanceof Promise) return result.then(async (result) => {
375
- await runFinally(true, onfinally, description);
376
399
  if (spool && oncleanup) spool.roll(result, oncleanup);
400
+ await runSettle(true, false, spool, onfinally, description);
377
401
  return result;
378
402
  }, async (arg) => {
379
403
  let error = ensureError(arg, description);
380
- if (oncatch) try {
381
- const result = await oncatch(error);
382
- await runFinally(true, onfinally, description);
404
+ if (oncatch) {
405
+ let result;
406
+ try {
407
+ result = await oncatch(error);
408
+ } catch (arg) {
409
+ error = ensureError(arg, description);
410
+ await runSettle(true, true, spool, onfinally, description);
411
+ throw error;
412
+ }
413
+ if (spool && oncleanup) spool.roll(result, oncleanup);
414
+ await runSettle(true, false, spool, onfinally, description);
383
415
  return result;
384
- } catch (arg) {
385
- error = ensureError(arg, description);
386
- await runFinally(true, onfinally, description);
387
- await runUnroll(true, spool);
388
- throw error;
389
416
  }
390
- await runFinally(true, onfinally, description);
391
- await runUnroll(true, spool);
417
+ await runSettle(true, true, spool, onfinally, description);
392
418
  throw error;
393
419
  });
394
420
  else {
395
- runFinally(false, onfinally, description);
396
421
  if (spool && oncleanup) spool.roll(result, oncleanup);
422
+ runSettle(false, false, spool, onfinally, description);
397
423
  return result;
398
424
  }
399
425
  }
@@ -425,7 +451,7 @@ var OptionsTrait = class {
425
451
  };
426
452
  //#endregion
427
453
  //#region dst-stage1/mqtt-plus-codec.js
428
- var JSONX = class {
454
+ var JSONX = class JSONX {
429
455
  static uint8ArrayToBase64(arr) {
430
456
  return node_buffer.Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString("base64");
431
457
  }
@@ -433,10 +459,13 @@ var JSONX = class {
433
459
  return new Uint8Array(node_buffer.Buffer.from(base64, "base64"));
434
460
  }
435
461
  static stringify(obj) {
436
- return JSON.stringify(obj, (_, value) => value instanceof Uint8Array ? { __Uint8Array: this.uint8ArrayToBase64(value) } : value);
462
+ return JSON.stringify(obj, function(key, value) {
463
+ const orig = this[key];
464
+ return orig instanceof node_buffer.Buffer ? { __Buffer: JSONX.uint8ArrayToBase64(orig) } : orig instanceof Uint8Array ? { __Uint8Array: JSONX.uint8ArrayToBase64(orig) } : value;
465
+ });
437
466
  }
438
467
  static parse(json) {
439
- return JSON.parse(json, (_, value) => typeof value?.__Uint8Array === "string" ? this.base64ToUint8Array(value.__Uint8Array) : value);
468
+ return JSON.parse(json, (_, value) => typeof value?.__Buffer === "string" ? node_buffer.Buffer.from(value.__Buffer, "base64") : typeof value?.__Uint8Array === "string" ? this.base64ToUint8Array(value.__Uint8Array) : value);
440
469
  }
441
470
  };
442
471
  var Codec = class {
@@ -941,7 +970,10 @@ var BaseTrait = class extends TraceTrait {
941
970
  let input;
942
971
  if (this.options.codec === "json") input = message.toString();
943
972
  else if (this.options.codec === "cbor") input = Buffer.isBuffer(message) ? new Uint8Array(message.buffer, message.byteOffset, message.byteLength) : message;
944
- else throw new Error("invalid codec configured");
973
+ else {
974
+ this.error(/* @__PURE__ */ new Error("invalid codec configured"));
975
+ return;
976
+ }
945
977
  this._onMessage(topic, input, packet);
946
978
  };
947
979
  this.mqtt.on("message", this.messageHandler);
@@ -971,10 +1003,14 @@ var BaseTrait = class extends TraceTrait {
971
1003
  this.mqtt.subscribe(topic, {
972
1004
  qos: 2,
973
1005
  ...options
974
- }, (err, _granted) => {
1006
+ }, (err, granted) => {
975
1007
  if (err) {
976
1008
  this.error(err, `subscribing to MQTT topic "${topic}" failed`);
977
1009
  reject(err);
1010
+ } else if (Array.isArray(granted) && granted.some((grant) => typeof grant?.qos === "number" && grant.qos >= 128)) {
1011
+ const error = /* @__PURE__ */ new Error(`broker rejected subscription to MQTT topic "${topic}" (grant codes: ${granted.map((grant) => grant?.qos).join(", ")})`);
1012
+ this.error(error, `subscribing to MQTT topic "${topic}" failed`);
1013
+ reject(error);
978
1014
  } else resolve();
979
1015
  });
980
1016
  });
@@ -1016,7 +1052,13 @@ var BaseTrait = class extends TraceTrait {
1016
1052
  }
1017
1053
  _onMessage(topic, data, _packet) {
1018
1054
  if (this.destroyed) return;
1019
- const topicMatch = this.options.topicMatch(topic);
1055
+ let topicMatch;
1056
+ try {
1057
+ topicMatch = this.options.topicMatch(topic);
1058
+ } catch (err) {
1059
+ this.error(ensureError(err, "failed to match MQTT topic"));
1060
+ return;
1061
+ }
1020
1062
  if (topicMatch === null) return;
1021
1063
  if (typeof data === "string") this.log("info", `received from MQTT topic "${topic}" (type: string, length: ${data.length} chars)`);
1022
1064
  else this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${data.byteLength} bytes)`);
@@ -1118,7 +1160,7 @@ var RefCountedSubscription = class {
1118
1160
  }
1119
1161
  }
1120
1162
  async unsubscribe(topic) {
1121
- if (this.decrementCount(topic) === 0) if (this.lingerMs > 0) {
1163
+ if (this.decrementCount(topic) === 0) if (this.lingerMs > 0 && !topic.startsWith("$share/")) {
1122
1164
  const timer = setTimeout(() => {
1123
1165
  this.lingers.delete(topic);
1124
1166
  const promise = this.unsubscribeFn(topic).catch(() => {}).finally(() => {
@@ -1212,7 +1254,7 @@ var TimerTrait = class extends SubscriptionTrait {
1212
1254
  if (ac !== void 0) ac.abort();
1213
1255
  resolve();
1214
1256
  }, durationMs);
1215
- timer.unref();
1257
+ if (typeof timer.unref === "function") timer.unref();
1216
1258
  if (signal !== void 0) {
1217
1259
  if (ac !== void 0) signal.addEventListener("abort", () => {
1218
1260
  if (timer !== null) clearTimeout(timer);
@@ -1236,7 +1278,7 @@ var TimerTrait = class extends SubscriptionTrait {
1236
1278
  if (ac !== void 0) ac.abort();
1237
1279
  reject(new Error(info));
1238
1280
  }, durationMs);
1239
- timer.unref();
1281
+ if (typeof timer.unref === "function") timer.unref();
1240
1282
  if (signal !== void 0) {
1241
1283
  if (ac !== void 0) signal.addEventListener("abort", () => {
1242
1284
  if (timer !== null) clearTimeout(timer);
@@ -1417,7 +1459,7 @@ var EventTrait = class extends AuthTrait {
1417
1459
  if (request.receiver) info.receiver = request.receiver;
1418
1460
  if (request.meta) info.meta = request.meta;
1419
1461
  try {
1420
- if (auth) info.authenticated = await this.authenticated(senderId, request.auth, auth, `event "${name}"`);
1462
+ if (auth !== void 0) info.authenticated = await this.authenticated(senderId, request.auth, auth, `event "${name}"`);
1421
1463
  await callback(...params, info);
1422
1464
  } catch (result) {
1423
1465
  const error = ensureError(result);
@@ -1558,7 +1600,7 @@ var ServiceTrait = class extends EventTrait {
1558
1600
  armServiceTimeout();
1559
1601
  let removeAbortListener;
1560
1602
  try {
1561
- if (auth) info.authenticated = await this.authenticated(senderId, request.auth, auth, `service "${name}"`);
1603
+ if (auth !== void 0) info.authenticated = await this.authenticated(senderId, request.auth, auth, `service "${name}"`);
1562
1604
  const abortPromise = new Promise((_resolve, reject) => {
1563
1605
  const onAbort = () => {
1564
1606
  reject(ensureError(abortSignal.reason));
@@ -1696,8 +1738,11 @@ var SourceTrait = class extends ServiceTrait {
1696
1738
  this.sourceControllers = /* @__PURE__ */ new Map();
1697
1739
  this.sourceSpools = /* @__PURE__ */ new Map();
1698
1740
  this.sourceRequests = /* @__PURE__ */ new Map();
1741
+ this.fetchAborts = /* @__PURE__ */ new Map();
1699
1742
  }
1700
1743
  async destroy() {
1744
+ for (const abort of this.fetchAborts.values()) abort(/* @__PURE__ */ new Error("instance destroyed"));
1745
+ this.fetchAborts.clear();
1701
1746
  for (const controller of this.sourceControllers.values()) controller.abort(/* @__PURE__ */ new Error("source destroyed"));
1702
1747
  for (const gate of this.sourceCreditGates.values()) gate.abort();
1703
1748
  for (const spool of [...this.sourceSpools.values()]) await spool.unroll();
@@ -1737,10 +1782,6 @@ var SourceTrait = class extends ServiceTrait {
1737
1782
  const sender = request.sender;
1738
1783
  const receiver = request.receiver;
1739
1784
  const reqSpool = new Spool();
1740
- this.sourceSpools.set(requestId, reqSpool);
1741
- reqSpool.roll(() => {
1742
- this.sourceSpools.delete(requestId);
1743
- });
1744
1785
  if (sender === void 0 || sender === "") {
1745
1786
  this.error(/* @__PURE__ */ new Error("invalid request: missing sender"));
1746
1787
  await reqSpool.unroll();
@@ -1767,6 +1808,10 @@ var SourceTrait = class extends ServiceTrait {
1767
1808
  reqSpool.roll(() => {
1768
1809
  this.sourceControllers.delete(requestId);
1769
1810
  });
1811
+ this.sourceSpools.set(requestId, reqSpool);
1812
+ reqSpool.roll(() => {
1813
+ this.sourceSpools.delete(requestId);
1814
+ });
1770
1815
  const info = {
1771
1816
  sender,
1772
1817
  signal: abortSignal
@@ -1852,7 +1897,7 @@ var SourceTrait = class extends ServiceTrait {
1852
1897
  reqSpool.roll(() => {
1853
1898
  this.onResponse.delete(`source-fetch-credit:${requestId}`);
1854
1899
  });
1855
- if (auth) info.authenticated = await this.authenticated(sender, request.auth, auth, `source "${name}"`);
1900
+ if (auth !== void 0) info.authenticated = await this.authenticated(sender, request.auth, auth, `source "${name}"`);
1856
1901
  const callbackPromise = Promise.resolve(callback(...params, info));
1857
1902
  callbackPromise.catch(() => {});
1858
1903
  await Promise.race([callbackPromise, abortPromise]);
@@ -2011,6 +2056,10 @@ var SourceTrait = class extends ServiceTrait {
2011
2056
  metaReject(error);
2012
2057
  stream.destroy(error);
2013
2058
  };
2059
+ this.fetchAborts.set(requestId, endWithError);
2060
+ spool.roll(() => {
2061
+ this.fetchAborts.delete(requestId);
2062
+ });
2014
2063
  let cancelled = false;
2015
2064
  const cancelAndUnroll = (reason) => {
2016
2065
  if (!cancelled && !streamEnded) {
@@ -2227,7 +2276,7 @@ var SinkTrait = class extends SourceTrait {
2227
2276
  try {
2228
2277
  if (topicName !== request.name) throw new Error(`sink name mismatch (topic: "${topicName}", payload: "${request.name}")`);
2229
2278
  let authenticated = void 0;
2230
- if (auth) authenticated = await this.authenticated(sender, request.auth, auth, `sink "${name}"`);
2279
+ if (auth !== void 0) authenticated = await this.authenticated(sender, request.auth, auth, `sink "${name}"`);
2231
2280
  const creditState = chunkCredit > 0 ? {
2232
2281
  chunksReceived: 0,
2233
2282
  creditGranted: chunkCredit
@@ -2237,7 +2286,11 @@ var SinkTrait = class extends SourceTrait {
2237
2286
  const pushTimerId = `sink-push-recv:${requestId}`;
2238
2287
  const refreshPushTimeout = () => this.timerRefresh(pushTimerId, () => {
2239
2288
  if (streamEnded || this.destroying) return;
2240
- abortController.abort(/* @__PURE__ */ new Error("push stream timeout"));
2289
+ const error = /* @__PURE__ */ new Error("push stream timeout");
2290
+ abortController.abort(error);
2291
+ streamEnded = true;
2292
+ const stream = this.pushStreams.get(requestId);
2293
+ if (stream !== void 0 && !stream.destroyed) stream.destroy(error);
2241
2294
  if (sender && !errorResponseSent) {
2242
2295
  errorResponseSent = true;
2243
2296
  const cancelMsg = this.msg.makeSinkPushCredit(requestId, name, 0, this.options.id, sender);
@@ -2517,6 +2570,7 @@ var SinkTrait = class extends SourceTrait {
2517
2570
  pushInitialResolve = resolve;
2518
2571
  pushInitialReject = reject;
2519
2572
  });
2573
+ pushInitial.catch(() => {});
2520
2574
  this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
2521
2575
  if (response.name !== name) {
2522
2576
  const error = /* @__PURE__ */ new Error(`sink response name mismatch (expected "${name}", got "${response.name}")`);
@@ -282,6 +282,31 @@ function runUnroll(isAsync, spool) {
282
282
  if (isAsync && !(result instanceof Promise)) result = Promise.resolve(result);
283
283
  return result;
284
284
  }
285
+ function runSettle(isAsync, pending, spool, onfinally, description) {
286
+ if (!isAsync) {
287
+ let failure;
288
+ try {
289
+ runFinally(false, onfinally, description);
290
+ } catch (arg) {
291
+ failure = ensureError(arg, description);
292
+ }
293
+ if (pending || failure !== void 0) {
294
+ runUnroll(false, spool);
295
+ if (!pending && failure !== void 0) throw failure;
296
+ }
297
+ } else return (async () => {
298
+ let failure;
299
+ try {
300
+ await runFinally(true, onfinally, description);
301
+ } catch (arg) {
302
+ failure = ensureError(arg, description);
303
+ }
304
+ if (pending || failure !== void 0) {
305
+ await runUnroll(true, spool);
306
+ if (!pending && failure !== void 0) throw failure;
307
+ }
308
+ })();
309
+ }
285
310
  function run(...args) {
286
311
  let description;
287
312
  let spool;
@@ -333,40 +358,41 @@ function run(...args) {
333
358
  result = oncatch(error);
334
359
  } catch (arg) {
335
360
  error = ensureError(arg, description);
336
- runFinally(false, onfinally, description);
337
- runUnroll(false, spool);
361
+ runSettle(false, true, spool, onfinally, description);
338
362
  throw error;
339
363
  }
340
- runFinally(false, onfinally, description);
364
+ if (spool && oncleanup) spool.roll(result, oncleanup);
365
+ runSettle(false, false, spool, onfinally, description);
341
366
  return result;
342
367
  }
343
- runFinally(false, onfinally, description);
344
- runUnroll(false, spool);
368
+ runSettle(false, true, spool, onfinally, description);
345
369
  throw error;
346
370
  }
347
371
  if (result instanceof Promise) return result.then(async (result) => {
348
- await runFinally(true, onfinally, description);
349
372
  if (spool && oncleanup) spool.roll(result, oncleanup);
373
+ await runSettle(true, false, spool, onfinally, description);
350
374
  return result;
351
375
  }, async (arg) => {
352
376
  let error = ensureError(arg, description);
353
- if (oncatch) try {
354
- const result = await oncatch(error);
355
- await runFinally(true, onfinally, description);
377
+ if (oncatch) {
378
+ let result;
379
+ try {
380
+ result = await oncatch(error);
381
+ } catch (arg) {
382
+ error = ensureError(arg, description);
383
+ await runSettle(true, true, spool, onfinally, description);
384
+ throw error;
385
+ }
386
+ if (spool && oncleanup) spool.roll(result, oncleanup);
387
+ await runSettle(true, false, spool, onfinally, description);
356
388
  return result;
357
- } catch (arg) {
358
- error = ensureError(arg, description);
359
- await runFinally(true, onfinally, description);
360
- await runUnroll(true, spool);
361
- throw error;
362
389
  }
363
- await runFinally(true, onfinally, description);
364
- await runUnroll(true, spool);
390
+ await runSettle(true, true, spool, onfinally, description);
365
391
  throw error;
366
392
  });
367
393
  else {
368
- runFinally(false, onfinally, description);
369
394
  if (spool && oncleanup) spool.roll(result, oncleanup);
395
+ runSettle(false, false, spool, onfinally, description);
370
396
  return result;
371
397
  }
372
398
  }
@@ -398,7 +424,7 @@ var OptionsTrait = class {
398
424
  };
399
425
  //#endregion
400
426
  //#region dst-stage1/mqtt-plus-codec.js
401
- var JSONX = class {
427
+ var JSONX = class JSONX {
402
428
  static uint8ArrayToBase64(arr) {
403
429
  return Buffer$1.from(arr.buffer, arr.byteOffset, arr.byteLength).toString("base64");
404
430
  }
@@ -406,10 +432,13 @@ var JSONX = class {
406
432
  return new Uint8Array(Buffer$1.from(base64, "base64"));
407
433
  }
408
434
  static stringify(obj) {
409
- return JSON.stringify(obj, (_, value) => value instanceof Uint8Array ? { __Uint8Array: this.uint8ArrayToBase64(value) } : value);
435
+ return JSON.stringify(obj, function(key, value) {
436
+ const orig = this[key];
437
+ return orig instanceof Buffer$1 ? { __Buffer: JSONX.uint8ArrayToBase64(orig) } : orig instanceof Uint8Array ? { __Uint8Array: JSONX.uint8ArrayToBase64(orig) } : value;
438
+ });
410
439
  }
411
440
  static parse(json) {
412
- return JSON.parse(json, (_, value) => typeof value?.__Uint8Array === "string" ? this.base64ToUint8Array(value.__Uint8Array) : value);
441
+ return JSON.parse(json, (_, value) => typeof value?.__Buffer === "string" ? Buffer$1.from(value.__Buffer, "base64") : typeof value?.__Uint8Array === "string" ? this.base64ToUint8Array(value.__Uint8Array) : value);
413
442
  }
414
443
  };
415
444
  var Codec = class {
@@ -914,7 +943,10 @@ var BaseTrait = class extends TraceTrait {
914
943
  let input;
915
944
  if (this.options.codec === "json") input = message.toString();
916
945
  else if (this.options.codec === "cbor") input = Buffer.isBuffer(message) ? new Uint8Array(message.buffer, message.byteOffset, message.byteLength) : message;
917
- else throw new Error("invalid codec configured");
946
+ else {
947
+ this.error(/* @__PURE__ */ new Error("invalid codec configured"));
948
+ return;
949
+ }
918
950
  this._onMessage(topic, input, packet);
919
951
  };
920
952
  this.mqtt.on("message", this.messageHandler);
@@ -944,10 +976,14 @@ var BaseTrait = class extends TraceTrait {
944
976
  this.mqtt.subscribe(topic, {
945
977
  qos: 2,
946
978
  ...options
947
- }, (err, _granted) => {
979
+ }, (err, granted) => {
948
980
  if (err) {
949
981
  this.error(err, `subscribing to MQTT topic "${topic}" failed`);
950
982
  reject(err);
983
+ } else if (Array.isArray(granted) && granted.some((grant) => typeof grant?.qos === "number" && grant.qos >= 128)) {
984
+ const error = /* @__PURE__ */ new Error(`broker rejected subscription to MQTT topic "${topic}" (grant codes: ${granted.map((grant) => grant?.qos).join(", ")})`);
985
+ this.error(error, `subscribing to MQTT topic "${topic}" failed`);
986
+ reject(error);
951
987
  } else resolve();
952
988
  });
953
989
  });
@@ -989,7 +1025,13 @@ var BaseTrait = class extends TraceTrait {
989
1025
  }
990
1026
  _onMessage(topic, data, _packet) {
991
1027
  if (this.destroyed) return;
992
- const topicMatch = this.options.topicMatch(topic);
1028
+ let topicMatch;
1029
+ try {
1030
+ topicMatch = this.options.topicMatch(topic);
1031
+ } catch (err) {
1032
+ this.error(ensureError(err, "failed to match MQTT topic"));
1033
+ return;
1034
+ }
993
1035
  if (topicMatch === null) return;
994
1036
  if (typeof data === "string") this.log("info", `received from MQTT topic "${topic}" (type: string, length: ${data.length} chars)`);
995
1037
  else this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${data.byteLength} bytes)`);
@@ -1091,7 +1133,7 @@ var RefCountedSubscription = class {
1091
1133
  }
1092
1134
  }
1093
1135
  async unsubscribe(topic) {
1094
- if (this.decrementCount(topic) === 0) if (this.lingerMs > 0) {
1136
+ if (this.decrementCount(topic) === 0) if (this.lingerMs > 0 && !topic.startsWith("$share/")) {
1095
1137
  const timer = setTimeout(() => {
1096
1138
  this.lingers.delete(topic);
1097
1139
  const promise = this.unsubscribeFn(topic).catch(() => {}).finally(() => {
@@ -1185,7 +1227,7 @@ var TimerTrait = class extends SubscriptionTrait {
1185
1227
  if (ac !== void 0) ac.abort();
1186
1228
  resolve();
1187
1229
  }, durationMs);
1188
- timer.unref();
1230
+ if (typeof timer.unref === "function") timer.unref();
1189
1231
  if (signal !== void 0) {
1190
1232
  if (ac !== void 0) signal.addEventListener("abort", () => {
1191
1233
  if (timer !== null) clearTimeout(timer);
@@ -1209,7 +1251,7 @@ var TimerTrait = class extends SubscriptionTrait {
1209
1251
  if (ac !== void 0) ac.abort();
1210
1252
  reject(new Error(info));
1211
1253
  }, durationMs);
1212
- timer.unref();
1254
+ if (typeof timer.unref === "function") timer.unref();
1213
1255
  if (signal !== void 0) {
1214
1256
  if (ac !== void 0) signal.addEventListener("abort", () => {
1215
1257
  if (timer !== null) clearTimeout(timer);
@@ -1390,7 +1432,7 @@ var EventTrait = class extends AuthTrait {
1390
1432
  if (request.receiver) info.receiver = request.receiver;
1391
1433
  if (request.meta) info.meta = request.meta;
1392
1434
  try {
1393
- if (auth) info.authenticated = await this.authenticated(senderId, request.auth, auth, `event "${name}"`);
1435
+ if (auth !== void 0) info.authenticated = await this.authenticated(senderId, request.auth, auth, `event "${name}"`);
1394
1436
  await callback(...params, info);
1395
1437
  } catch (result) {
1396
1438
  const error = ensureError(result);
@@ -1531,7 +1573,7 @@ var ServiceTrait = class extends EventTrait {
1531
1573
  armServiceTimeout();
1532
1574
  let removeAbortListener;
1533
1575
  try {
1534
- if (auth) info.authenticated = await this.authenticated(senderId, request.auth, auth, `service "${name}"`);
1576
+ if (auth !== void 0) info.authenticated = await this.authenticated(senderId, request.auth, auth, `service "${name}"`);
1535
1577
  const abortPromise = new Promise((_resolve, reject) => {
1536
1578
  const onAbort = () => {
1537
1579
  reject(ensureError(abortSignal.reason));
@@ -1669,8 +1711,11 @@ var SourceTrait = class extends ServiceTrait {
1669
1711
  this.sourceControllers = /* @__PURE__ */ new Map();
1670
1712
  this.sourceSpools = /* @__PURE__ */ new Map();
1671
1713
  this.sourceRequests = /* @__PURE__ */ new Map();
1714
+ this.fetchAborts = /* @__PURE__ */ new Map();
1672
1715
  }
1673
1716
  async destroy() {
1717
+ for (const abort of this.fetchAborts.values()) abort(/* @__PURE__ */ new Error("instance destroyed"));
1718
+ this.fetchAborts.clear();
1674
1719
  for (const controller of this.sourceControllers.values()) controller.abort(/* @__PURE__ */ new Error("source destroyed"));
1675
1720
  for (const gate of this.sourceCreditGates.values()) gate.abort();
1676
1721
  for (const spool of [...this.sourceSpools.values()]) await spool.unroll();
@@ -1710,10 +1755,6 @@ var SourceTrait = class extends ServiceTrait {
1710
1755
  const sender = request.sender;
1711
1756
  const receiver = request.receiver;
1712
1757
  const reqSpool = new Spool();
1713
- this.sourceSpools.set(requestId, reqSpool);
1714
- reqSpool.roll(() => {
1715
- this.sourceSpools.delete(requestId);
1716
- });
1717
1758
  if (sender === void 0 || sender === "") {
1718
1759
  this.error(/* @__PURE__ */ new Error("invalid request: missing sender"));
1719
1760
  await reqSpool.unroll();
@@ -1740,6 +1781,10 @@ var SourceTrait = class extends ServiceTrait {
1740
1781
  reqSpool.roll(() => {
1741
1782
  this.sourceControllers.delete(requestId);
1742
1783
  });
1784
+ this.sourceSpools.set(requestId, reqSpool);
1785
+ reqSpool.roll(() => {
1786
+ this.sourceSpools.delete(requestId);
1787
+ });
1743
1788
  const info = {
1744
1789
  sender,
1745
1790
  signal: abortSignal
@@ -1825,7 +1870,7 @@ var SourceTrait = class extends ServiceTrait {
1825
1870
  reqSpool.roll(() => {
1826
1871
  this.onResponse.delete(`source-fetch-credit:${requestId}`);
1827
1872
  });
1828
- if (auth) info.authenticated = await this.authenticated(sender, request.auth, auth, `source "${name}"`);
1873
+ if (auth !== void 0) info.authenticated = await this.authenticated(sender, request.auth, auth, `source "${name}"`);
1829
1874
  const callbackPromise = Promise.resolve(callback(...params, info));
1830
1875
  callbackPromise.catch(() => {});
1831
1876
  await Promise.race([callbackPromise, abortPromise]);
@@ -1984,6 +2029,10 @@ var SourceTrait = class extends ServiceTrait {
1984
2029
  metaReject(error);
1985
2030
  stream.destroy(error);
1986
2031
  };
2032
+ this.fetchAborts.set(requestId, endWithError);
2033
+ spool.roll(() => {
2034
+ this.fetchAborts.delete(requestId);
2035
+ });
1987
2036
  let cancelled = false;
1988
2037
  const cancelAndUnroll = (reason) => {
1989
2038
  if (!cancelled && !streamEnded) {
@@ -2200,7 +2249,7 @@ var SinkTrait = class extends SourceTrait {
2200
2249
  try {
2201
2250
  if (topicName !== request.name) throw new Error(`sink name mismatch (topic: "${topicName}", payload: "${request.name}")`);
2202
2251
  let authenticated = void 0;
2203
- if (auth) authenticated = await this.authenticated(sender, request.auth, auth, `sink "${name}"`);
2252
+ if (auth !== void 0) authenticated = await this.authenticated(sender, request.auth, auth, `sink "${name}"`);
2204
2253
  const creditState = chunkCredit > 0 ? {
2205
2254
  chunksReceived: 0,
2206
2255
  creditGranted: chunkCredit
@@ -2210,7 +2259,11 @@ var SinkTrait = class extends SourceTrait {
2210
2259
  const pushTimerId = `sink-push-recv:${requestId}`;
2211
2260
  const refreshPushTimeout = () => this.timerRefresh(pushTimerId, () => {
2212
2261
  if (streamEnded || this.destroying) return;
2213
- abortController.abort(/* @__PURE__ */ new Error("push stream timeout"));
2262
+ const error = /* @__PURE__ */ new Error("push stream timeout");
2263
+ abortController.abort(error);
2264
+ streamEnded = true;
2265
+ const stream = this.pushStreams.get(requestId);
2266
+ if (stream !== void 0 && !stream.destroyed) stream.destroy(error);
2214
2267
  if (sender && !errorResponseSent) {
2215
2268
  errorResponseSent = true;
2216
2269
  const cancelMsg = this.msg.makeSinkPushCredit(requestId, name, 0, this.options.id, sender);
@@ -2490,6 +2543,7 @@ var SinkTrait = class extends SourceTrait {
2490
2543
  pushInitialResolve = resolve;
2491
2544
  pushInitialReject = reject;
2492
2545
  });
2546
+ pushInitial.catch(() => {});
2493
2547
  this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
2494
2548
  if (response.name !== name) {
2495
2549
  const error = /* @__PURE__ */ new Error(`sink response name mismatch (expected "${name}", got "${response.name}")`);