mqtt-plus 1.4.22 → 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 (39) hide show
  1. package/.ase/service.log +10402 -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 +21 -1
  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 +34 -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-options.js +1 -3
  14. package/dst-stage1/mqtt-plus-service.js +1 -1
  15. package/dst-stage1/mqtt-plus-sink.js +10 -2
  16. package/dst-stage1/mqtt-plus-source.d.ts +1 -0
  17. package/dst-stage1/mqtt-plus-source.js +11 -3
  18. package/dst-stage1/mqtt-plus-subscription.js +7 -2
  19. package/dst-stage1/mqtt-plus-timer.js +4 -2
  20. package/dst-stage2/mqtt-plus.cjs.cjs +94 -36
  21. package/dst-stage2/mqtt-plus.esm.js +94 -36
  22. package/dst-stage2/mqtt-plus.umd.js +11 -11
  23. package/package.json +1 -1
  24. package/src/mqtt-plus-api.ts +5 -5
  25. package/src/mqtt-plus-base.ts +35 -4
  26. package/src/mqtt-plus-codec.ts +15 -8
  27. package/src/mqtt-plus-error.ts +52 -14
  28. package/src/mqtt-plus-event.ts +1 -1
  29. package/src/mqtt-plus-options.ts +1 -4
  30. package/src/mqtt-plus-service.ts +1 -1
  31. package/src/mqtt-plus-sink.ts +12 -2
  32. package/src/mqtt-plus-source.ts +13 -3
  33. package/src/mqtt-plus-subscription.ts +7 -2
  34. package/src/mqtt-plus-timer.ts +4 -2
  35. package/tst/mqtt-plus-4-sink.spec.ts +43 -0
  36. package/tst/mqtt-plus-5-source.spec.ts +35 -0
  37. package/tst/mqtt-plus-6-misc.spec.ts +82 -5
  38. package/tst/mqtt-plus-8-run.spec.ts +107 -0
  39. package/.ase/service.yaml +0 -1
@@ -76,7 +76,8 @@ export class TimerTrait extends SubscriptionTrait {
76
76
  ac.abort();
77
77
  resolve();
78
78
  }, durationMs);
79
- timer.unref();
79
+ if (typeof timer.unref === "function")
80
+ timer.unref();
80
81
  if (signal !== undefined) {
81
82
  if (ac !== undefined)
82
83
  signal.addEventListener("abort", () => {
@@ -101,7 +102,8 @@ export class TimerTrait extends SubscriptionTrait {
101
102
  ac.abort();
102
103
  reject(new Error(info));
103
104
  }, durationMs);
104
- timer.unref();
105
+ if (typeof timer.unref === "function")
106
+ timer.unref();
105
107
  if (signal !== undefined) {
106
108
  if (ac !== undefined)
107
109
  signal.addEventListener("abort", () => {
@@ -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
  }
@@ -402,7 +428,7 @@ function run(...args) {
402
428
  var OptionsTrait = class {
403
429
  constructor(options = {}) {
404
430
  this.options = {
405
- id: (0, nanoid.nanoid)(),
431
+ id: "",
406
432
  codec: "cbor",
407
433
  timeout: 10 * 1e3,
408
434
  share: "",
@@ -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 {
@@ -931,13 +960,20 @@ var BaseTrait = class extends TraceTrait {
931
960
  } });
932
961
  }
933
962
  this.mqtt = mqtt;
963
+ if (this.options.id === "") {
964
+ const clientId = mqtt.isFakeProxy ? void 0 : mqtt.options?.clientId;
965
+ this.options.id = typeof clientId === "string" && clientId !== "" ? clientId : (0, nanoid.nanoid)();
966
+ }
934
967
  this.log("info", "hooking into MQTT client");
935
968
  this.messageHandler = (topic, message, packet) => {
936
969
  if (this.destroyed) return;
937
970
  let input;
938
971
  if (this.options.codec === "json") input = message.toString();
939
972
  else if (this.options.codec === "cbor") input = Buffer.isBuffer(message) ? new Uint8Array(message.buffer, message.byteOffset, message.byteLength) : message;
940
- else throw new Error("invalid codec configured");
973
+ else {
974
+ this.error(/* @__PURE__ */ new Error("invalid codec configured"));
975
+ return;
976
+ }
941
977
  this._onMessage(topic, input, packet);
942
978
  };
943
979
  this.mqtt.on("message", this.messageHandler);
@@ -967,10 +1003,14 @@ var BaseTrait = class extends TraceTrait {
967
1003
  this.mqtt.subscribe(topic, {
968
1004
  qos: 2,
969
1005
  ...options
970
- }, (err, _granted) => {
1006
+ }, (err, granted) => {
971
1007
  if (err) {
972
1008
  this.error(err, `subscribing to MQTT topic "${topic}" failed`);
973
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);
974
1014
  } else resolve();
975
1015
  });
976
1016
  });
@@ -1012,7 +1052,13 @@ var BaseTrait = class extends TraceTrait {
1012
1052
  }
1013
1053
  _onMessage(topic, data, _packet) {
1014
1054
  if (this.destroyed) return;
1015
- 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
+ }
1016
1062
  if (topicMatch === null) return;
1017
1063
  if (typeof data === "string") this.log("info", `received from MQTT topic "${topic}" (type: string, length: ${data.length} chars)`);
1018
1064
  else this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${data.byteLength} bytes)`);
@@ -1114,7 +1160,7 @@ var RefCountedSubscription = class {
1114
1160
  }
1115
1161
  }
1116
1162
  async unsubscribe(topic) {
1117
- if (this.decrementCount(topic) === 0) if (this.lingerMs > 0) {
1163
+ if (this.decrementCount(topic) === 0) if (this.lingerMs > 0 && !topic.startsWith("$share/")) {
1118
1164
  const timer = setTimeout(() => {
1119
1165
  this.lingers.delete(topic);
1120
1166
  const promise = this.unsubscribeFn(topic).catch(() => {}).finally(() => {
@@ -1208,7 +1254,7 @@ var TimerTrait = class extends SubscriptionTrait {
1208
1254
  if (ac !== void 0) ac.abort();
1209
1255
  resolve();
1210
1256
  }, durationMs);
1211
- timer.unref();
1257
+ if (typeof timer.unref === "function") timer.unref();
1212
1258
  if (signal !== void 0) {
1213
1259
  if (ac !== void 0) signal.addEventListener("abort", () => {
1214
1260
  if (timer !== null) clearTimeout(timer);
@@ -1232,7 +1278,7 @@ var TimerTrait = class extends SubscriptionTrait {
1232
1278
  if (ac !== void 0) ac.abort();
1233
1279
  reject(new Error(info));
1234
1280
  }, durationMs);
1235
- timer.unref();
1281
+ if (typeof timer.unref === "function") timer.unref();
1236
1282
  if (signal !== void 0) {
1237
1283
  if (ac !== void 0) signal.addEventListener("abort", () => {
1238
1284
  if (timer !== null) clearTimeout(timer);
@@ -1413,7 +1459,7 @@ var EventTrait = class extends AuthTrait {
1413
1459
  if (request.receiver) info.receiver = request.receiver;
1414
1460
  if (request.meta) info.meta = request.meta;
1415
1461
  try {
1416
- 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}"`);
1417
1463
  await callback(...params, info);
1418
1464
  } catch (result) {
1419
1465
  const error = ensureError(result);
@@ -1554,7 +1600,7 @@ var ServiceTrait = class extends EventTrait {
1554
1600
  armServiceTimeout();
1555
1601
  let removeAbortListener;
1556
1602
  try {
1557
- 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}"`);
1558
1604
  const abortPromise = new Promise((_resolve, reject) => {
1559
1605
  const onAbort = () => {
1560
1606
  reject(ensureError(abortSignal.reason));
@@ -1692,8 +1738,11 @@ var SourceTrait = class extends ServiceTrait {
1692
1738
  this.sourceControllers = /* @__PURE__ */ new Map();
1693
1739
  this.sourceSpools = /* @__PURE__ */ new Map();
1694
1740
  this.sourceRequests = /* @__PURE__ */ new Map();
1741
+ this.fetchAborts = /* @__PURE__ */ new Map();
1695
1742
  }
1696
1743
  async destroy() {
1744
+ for (const abort of this.fetchAborts.values()) abort(/* @__PURE__ */ new Error("instance destroyed"));
1745
+ this.fetchAborts.clear();
1697
1746
  for (const controller of this.sourceControllers.values()) controller.abort(/* @__PURE__ */ new Error("source destroyed"));
1698
1747
  for (const gate of this.sourceCreditGates.values()) gate.abort();
1699
1748
  for (const spool of [...this.sourceSpools.values()]) await spool.unroll();
@@ -1733,10 +1782,6 @@ var SourceTrait = class extends ServiceTrait {
1733
1782
  const sender = request.sender;
1734
1783
  const receiver = request.receiver;
1735
1784
  const reqSpool = new Spool();
1736
- this.sourceSpools.set(requestId, reqSpool);
1737
- reqSpool.roll(() => {
1738
- this.sourceSpools.delete(requestId);
1739
- });
1740
1785
  if (sender === void 0 || sender === "") {
1741
1786
  this.error(/* @__PURE__ */ new Error("invalid request: missing sender"));
1742
1787
  await reqSpool.unroll();
@@ -1763,6 +1808,10 @@ var SourceTrait = class extends ServiceTrait {
1763
1808
  reqSpool.roll(() => {
1764
1809
  this.sourceControllers.delete(requestId);
1765
1810
  });
1811
+ this.sourceSpools.set(requestId, reqSpool);
1812
+ reqSpool.roll(() => {
1813
+ this.sourceSpools.delete(requestId);
1814
+ });
1766
1815
  const info = {
1767
1816
  sender,
1768
1817
  signal: abortSignal
@@ -1848,7 +1897,7 @@ var SourceTrait = class extends ServiceTrait {
1848
1897
  reqSpool.roll(() => {
1849
1898
  this.onResponse.delete(`source-fetch-credit:${requestId}`);
1850
1899
  });
1851
- 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}"`);
1852
1901
  const callbackPromise = Promise.resolve(callback(...params, info));
1853
1902
  callbackPromise.catch(() => {});
1854
1903
  await Promise.race([callbackPromise, abortPromise]);
@@ -2007,6 +2056,10 @@ var SourceTrait = class extends ServiceTrait {
2007
2056
  metaReject(error);
2008
2057
  stream.destroy(error);
2009
2058
  };
2059
+ this.fetchAborts.set(requestId, endWithError);
2060
+ spool.roll(() => {
2061
+ this.fetchAborts.delete(requestId);
2062
+ });
2010
2063
  let cancelled = false;
2011
2064
  const cancelAndUnroll = (reason) => {
2012
2065
  if (!cancelled && !streamEnded) {
@@ -2223,7 +2276,7 @@ var SinkTrait = class extends SourceTrait {
2223
2276
  try {
2224
2277
  if (topicName !== request.name) throw new Error(`sink name mismatch (topic: "${topicName}", payload: "${request.name}")`);
2225
2278
  let authenticated = void 0;
2226
- 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}"`);
2227
2280
  const creditState = chunkCredit > 0 ? {
2228
2281
  chunksReceived: 0,
2229
2282
  creditGranted: chunkCredit
@@ -2233,7 +2286,11 @@ var SinkTrait = class extends SourceTrait {
2233
2286
  const pushTimerId = `sink-push-recv:${requestId}`;
2234
2287
  const refreshPushTimeout = () => this.timerRefresh(pushTimerId, () => {
2235
2288
  if (streamEnded || this.destroying) return;
2236
- 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);
2237
2294
  if (sender && !errorResponseSent) {
2238
2295
  errorResponseSent = true;
2239
2296
  const cancelMsg = this.msg.makeSinkPushCredit(requestId, name, 0, this.options.id, sender);
@@ -2513,6 +2570,7 @@ var SinkTrait = class extends SourceTrait {
2513
2570
  pushInitialResolve = resolve;
2514
2571
  pushInitialReject = reject;
2515
2572
  });
2573
+ pushInitial.catch(() => {});
2516
2574
  this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
2517
2575
  if (response.name !== name) {
2518
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
  }
@@ -375,7 +401,7 @@ function run(...args) {
375
401
  var OptionsTrait = class {
376
402
  constructor(options = {}) {
377
403
  this.options = {
378
- id: nanoid(),
404
+ id: "",
379
405
  codec: "cbor",
380
406
  timeout: 10 * 1e3,
381
407
  share: "",
@@ -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 {
@@ -904,13 +933,20 @@ var BaseTrait = class extends TraceTrait {
904
933
  } });
905
934
  }
906
935
  this.mqtt = mqtt;
936
+ if (this.options.id === "") {
937
+ const clientId = mqtt.isFakeProxy ? void 0 : mqtt.options?.clientId;
938
+ this.options.id = typeof clientId === "string" && clientId !== "" ? clientId : nanoid();
939
+ }
907
940
  this.log("info", "hooking into MQTT client");
908
941
  this.messageHandler = (topic, message, packet) => {
909
942
  if (this.destroyed) return;
910
943
  let input;
911
944
  if (this.options.codec === "json") input = message.toString();
912
945
  else if (this.options.codec === "cbor") input = Buffer.isBuffer(message) ? new Uint8Array(message.buffer, message.byteOffset, message.byteLength) : message;
913
- else throw new Error("invalid codec configured");
946
+ else {
947
+ this.error(/* @__PURE__ */ new Error("invalid codec configured"));
948
+ return;
949
+ }
914
950
  this._onMessage(topic, input, packet);
915
951
  };
916
952
  this.mqtt.on("message", this.messageHandler);
@@ -940,10 +976,14 @@ var BaseTrait = class extends TraceTrait {
940
976
  this.mqtt.subscribe(topic, {
941
977
  qos: 2,
942
978
  ...options
943
- }, (err, _granted) => {
979
+ }, (err, granted) => {
944
980
  if (err) {
945
981
  this.error(err, `subscribing to MQTT topic "${topic}" failed`);
946
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);
947
987
  } else resolve();
948
988
  });
949
989
  });
@@ -985,7 +1025,13 @@ var BaseTrait = class extends TraceTrait {
985
1025
  }
986
1026
  _onMessage(topic, data, _packet) {
987
1027
  if (this.destroyed) return;
988
- 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
+ }
989
1035
  if (topicMatch === null) return;
990
1036
  if (typeof data === "string") this.log("info", `received from MQTT topic "${topic}" (type: string, length: ${data.length} chars)`);
991
1037
  else this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${data.byteLength} bytes)`);
@@ -1087,7 +1133,7 @@ var RefCountedSubscription = class {
1087
1133
  }
1088
1134
  }
1089
1135
  async unsubscribe(topic) {
1090
- if (this.decrementCount(topic) === 0) if (this.lingerMs > 0) {
1136
+ if (this.decrementCount(topic) === 0) if (this.lingerMs > 0 && !topic.startsWith("$share/")) {
1091
1137
  const timer = setTimeout(() => {
1092
1138
  this.lingers.delete(topic);
1093
1139
  const promise = this.unsubscribeFn(topic).catch(() => {}).finally(() => {
@@ -1181,7 +1227,7 @@ var TimerTrait = class extends SubscriptionTrait {
1181
1227
  if (ac !== void 0) ac.abort();
1182
1228
  resolve();
1183
1229
  }, durationMs);
1184
- timer.unref();
1230
+ if (typeof timer.unref === "function") timer.unref();
1185
1231
  if (signal !== void 0) {
1186
1232
  if (ac !== void 0) signal.addEventListener("abort", () => {
1187
1233
  if (timer !== null) clearTimeout(timer);
@@ -1205,7 +1251,7 @@ var TimerTrait = class extends SubscriptionTrait {
1205
1251
  if (ac !== void 0) ac.abort();
1206
1252
  reject(new Error(info));
1207
1253
  }, durationMs);
1208
- timer.unref();
1254
+ if (typeof timer.unref === "function") timer.unref();
1209
1255
  if (signal !== void 0) {
1210
1256
  if (ac !== void 0) signal.addEventListener("abort", () => {
1211
1257
  if (timer !== null) clearTimeout(timer);
@@ -1386,7 +1432,7 @@ var EventTrait = class extends AuthTrait {
1386
1432
  if (request.receiver) info.receiver = request.receiver;
1387
1433
  if (request.meta) info.meta = request.meta;
1388
1434
  try {
1389
- 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}"`);
1390
1436
  await callback(...params, info);
1391
1437
  } catch (result) {
1392
1438
  const error = ensureError(result);
@@ -1527,7 +1573,7 @@ var ServiceTrait = class extends EventTrait {
1527
1573
  armServiceTimeout();
1528
1574
  let removeAbortListener;
1529
1575
  try {
1530
- 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}"`);
1531
1577
  const abortPromise = new Promise((_resolve, reject) => {
1532
1578
  const onAbort = () => {
1533
1579
  reject(ensureError(abortSignal.reason));
@@ -1665,8 +1711,11 @@ var SourceTrait = class extends ServiceTrait {
1665
1711
  this.sourceControllers = /* @__PURE__ */ new Map();
1666
1712
  this.sourceSpools = /* @__PURE__ */ new Map();
1667
1713
  this.sourceRequests = /* @__PURE__ */ new Map();
1714
+ this.fetchAborts = /* @__PURE__ */ new Map();
1668
1715
  }
1669
1716
  async destroy() {
1717
+ for (const abort of this.fetchAborts.values()) abort(/* @__PURE__ */ new Error("instance destroyed"));
1718
+ this.fetchAborts.clear();
1670
1719
  for (const controller of this.sourceControllers.values()) controller.abort(/* @__PURE__ */ new Error("source destroyed"));
1671
1720
  for (const gate of this.sourceCreditGates.values()) gate.abort();
1672
1721
  for (const spool of [...this.sourceSpools.values()]) await spool.unroll();
@@ -1706,10 +1755,6 @@ var SourceTrait = class extends ServiceTrait {
1706
1755
  const sender = request.sender;
1707
1756
  const receiver = request.receiver;
1708
1757
  const reqSpool = new Spool();
1709
- this.sourceSpools.set(requestId, reqSpool);
1710
- reqSpool.roll(() => {
1711
- this.sourceSpools.delete(requestId);
1712
- });
1713
1758
  if (sender === void 0 || sender === "") {
1714
1759
  this.error(/* @__PURE__ */ new Error("invalid request: missing sender"));
1715
1760
  await reqSpool.unroll();
@@ -1736,6 +1781,10 @@ var SourceTrait = class extends ServiceTrait {
1736
1781
  reqSpool.roll(() => {
1737
1782
  this.sourceControllers.delete(requestId);
1738
1783
  });
1784
+ this.sourceSpools.set(requestId, reqSpool);
1785
+ reqSpool.roll(() => {
1786
+ this.sourceSpools.delete(requestId);
1787
+ });
1739
1788
  const info = {
1740
1789
  sender,
1741
1790
  signal: abortSignal
@@ -1821,7 +1870,7 @@ var SourceTrait = class extends ServiceTrait {
1821
1870
  reqSpool.roll(() => {
1822
1871
  this.onResponse.delete(`source-fetch-credit:${requestId}`);
1823
1872
  });
1824
- 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}"`);
1825
1874
  const callbackPromise = Promise.resolve(callback(...params, info));
1826
1875
  callbackPromise.catch(() => {});
1827
1876
  await Promise.race([callbackPromise, abortPromise]);
@@ -1980,6 +2029,10 @@ var SourceTrait = class extends ServiceTrait {
1980
2029
  metaReject(error);
1981
2030
  stream.destroy(error);
1982
2031
  };
2032
+ this.fetchAborts.set(requestId, endWithError);
2033
+ spool.roll(() => {
2034
+ this.fetchAborts.delete(requestId);
2035
+ });
1983
2036
  let cancelled = false;
1984
2037
  const cancelAndUnroll = (reason) => {
1985
2038
  if (!cancelled && !streamEnded) {
@@ -2196,7 +2249,7 @@ var SinkTrait = class extends SourceTrait {
2196
2249
  try {
2197
2250
  if (topicName !== request.name) throw new Error(`sink name mismatch (topic: "${topicName}", payload: "${request.name}")`);
2198
2251
  let authenticated = void 0;
2199
- 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}"`);
2200
2253
  const creditState = chunkCredit > 0 ? {
2201
2254
  chunksReceived: 0,
2202
2255
  creditGranted: chunkCredit
@@ -2206,7 +2259,11 @@ var SinkTrait = class extends SourceTrait {
2206
2259
  const pushTimerId = `sink-push-recv:${requestId}`;
2207
2260
  const refreshPushTimeout = () => this.timerRefresh(pushTimerId, () => {
2208
2261
  if (streamEnded || this.destroying) return;
2209
- 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);
2210
2267
  if (sender && !errorResponseSent) {
2211
2268
  errorResponseSent = true;
2212
2269
  const cancelMsg = this.msg.makeSinkPushCredit(requestId, name, 0, this.options.id, sender);
@@ -2486,6 +2543,7 @@ var SinkTrait = class extends SourceTrait {
2486
2543
  pushInitialResolve = resolve;
2487
2544
  pushInitialReject = reject;
2488
2545
  });
2546
+ pushInitial.catch(() => {});
2489
2547
  this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
2490
2548
  if (response.name !== name) {
2491
2549
  const error = /* @__PURE__ */ new Error(`sink response name mismatch (expected "${name}", got "${response.name}")`);