mqtt-plus 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/AGENTS.md +55 -44
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +4 -3
  4. package/doc/mqtt-plus-api.md +693 -680
  5. package/doc/mqtt-plus-architecture.d2 +139 -0
  6. package/doc/mqtt-plus-architecture.md +10 -0
  7. package/doc/mqtt-plus-architecture.svg +95 -0
  8. package/doc/mqtt-plus-broker-setup.md +9 -3
  9. package/doc/mqtt-plus-comm.md +73 -0
  10. package/doc/mqtt-plus-internals.md +3 -3
  11. package/dst-stage1/mqtt-plus-base.d.ts +3 -2
  12. package/dst-stage1/mqtt-plus-base.js +53 -22
  13. package/dst-stage1/mqtt-plus-event.d.ts +0 -2
  14. package/dst-stage1/mqtt-plus-event.js +6 -26
  15. package/dst-stage1/mqtt-plus-meta.d.ts +2 -2
  16. package/dst-stage1/mqtt-plus-meta.js +2 -2
  17. package/dst-stage1/mqtt-plus-msg.d.ts +2 -0
  18. package/dst-stage1/mqtt-plus-msg.js +17 -0
  19. package/dst-stage1/mqtt-plus-service.d.ts +0 -5
  20. package/dst-stage1/mqtt-plus-service.js +12 -48
  21. package/dst-stage1/mqtt-plus-sink.d.ts +0 -10
  22. package/dst-stage1/mqtt-plus-sink.js +25 -92
  23. package/dst-stage1/mqtt-plus-source.d.ts +0 -10
  24. package/dst-stage1/mqtt-plus-source.js +23 -88
  25. package/dst-stage1/mqtt-plus-subscription.d.ts +20 -0
  26. package/dst-stage1/mqtt-plus-subscription.js +126 -0
  27. package/dst-stage1/mqtt-plus-timer.d.ts +8 -0
  28. package/dst-stage1/mqtt-plus-timer.js +57 -0
  29. package/dst-stage1/mqtt-plus-topic.d.ts +20 -0
  30. package/dst-stage1/mqtt-plus-topic.js +112 -0
  31. package/dst-stage1/mqtt-plus-trace.js +2 -0
  32. package/dst-stage1/mqtt-plus-util.d.ts +0 -13
  33. package/dst-stage1/mqtt-plus-util.js +1 -77
  34. package/dst-stage1/mqtt-plus-version.d.ts +0 -1
  35. package/dst-stage1/mqtt-plus-version.js +0 -6
  36. package/dst-stage1/tsc.tsbuildinfo +1 -1
  37. package/dst-stage2/mqtt-plus.cjs.js +242 -292
  38. package/dst-stage2/mqtt-plus.esm.js +240 -290
  39. package/dst-stage2/mqtt-plus.umd.js +12 -12
  40. package/etc/knip.jsonc +1 -1
  41. package/etc/stx.conf +6 -4
  42. package/package.json +1 -1
  43. package/src/mqtt-plus-base.ts +56 -26
  44. package/src/mqtt-plus-event.ts +8 -24
  45. package/src/mqtt-plus-meta.ts +3 -3
  46. package/src/mqtt-plus-msg.ts +28 -0
  47. package/src/mqtt-plus-service.ts +12 -50
  48. package/src/mqtt-plus-sink.ts +32 -105
  49. package/src/mqtt-plus-source.ts +29 -99
  50. package/src/mqtt-plus-subscription.ts +141 -0
  51. package/src/mqtt-plus-timer.ts +61 -0
  52. package/src/mqtt-plus-trace.ts +4 -0
  53. package/src/mqtt-plus-util.ts +1 -81
  54. package/src/mqtt-plus-version.ts +0 -7
  55. package/tst/mqtt-plus-0-fixture.ts +2 -2
  56. package/tst/mqtt-plus-0-mosquitto.ts +5 -0
  57. package/tst/mqtt-plus-1-api.spec.ts +1 -1
  58. package/tst/mqtt-plus-2-event.spec.ts +0 -6
  59. package/tst/mqtt-plus-3-service.spec.ts +3 -7
  60. package/tst/mqtt-plus-4-sink.spec.ts +14 -9
  61. package/tst/mqtt-plus-5-source.spec.ts +11 -5
  62. package/tst/mqtt-plus-6-misc.spec.ts +23 -23
  63. package/tst/tsc.json +1 -1
  64. package/doc/mqtt-plus-communication.md +0 -68
  65. /package/doc/{mqtt-plus-1-event-emission.d2 → mqtt-plus-comm-event-emission.d2} +0 -0
  66. /package/doc/{mqtt-plus-1-event-emission.svg → mqtt-plus-comm-event-emission.svg} +0 -0
  67. /package/doc/{mqtt-plus-2-service-call.d2 → mqtt-plus-comm-service-call.d2} +0 -0
  68. /package/doc/{mqtt-plus-2-service-call.svg → mqtt-plus-comm-service-call.svg} +0 -0
  69. /package/doc/{mqtt-plus-3-sink-push.d2 → mqtt-plus-comm-sink-push.d2} +0 -0
  70. /package/doc/{mqtt-plus-3-sink-push.svg → mqtt-plus-comm-sink-push.svg} +0 -0
  71. /package/doc/{mqtt-plus-4-source-fetch.d2 → mqtt-plus-comm-source-fetch.d2} +0 -0
  72. /package/doc/{mqtt-plus-4-source-fetch.svg → mqtt-plus-comm-source-fetch.svg} +0 -0
  73. /package/{doc/theme.d2 → etc/d2.theme.d2} +0 -0
@@ -2,81 +2,13 @@ import { Readable } from "node:stream";
2
2
  import { nanoid } from "nanoid";
3
3
  import { Buffer as Buffer$1 } from "node:buffer";
4
4
  import PLazy from "p-lazy";
5
- import * as v from "valibot";
6
- import * as CBOR from "cbor2";
7
5
  import { SignJWT } from "jose/jwt/sign";
8
6
  import { jwtVerify } from "jose/jwt/verify";
9
7
  import * as pbkdf2 from "@stablelib/pbkdf2";
10
8
  import * as sha256 from "@stablelib/sha256";
11
9
  import { EventEmitter } from "node:events";
12
- class RefCountedSubscription {
13
- constructor(subscribeFn, unsubscribeFn, lingerMs = 30 * 1e3) {
14
- this.subscribeFn = subscribeFn;
15
- this.unsubscribeFn = unsubscribeFn;
16
- this.lingerMs = lingerMs;
17
- this.counts = /* @__PURE__ */ new Map();
18
- this.pending = /* @__PURE__ */ new Map();
19
- this.lingers = /* @__PURE__ */ new Map();
20
- }
21
- async subscribe(topic, options = { qos: 2 }) {
22
- const count = this.counts.get(topic) ?? 0;
23
- this.counts.set(topic, count + 1);
24
- const linger = this.lingers.get(topic);
25
- if (linger) {
26
- clearTimeout(linger);
27
- this.lingers.delete(topic);
28
- return;
29
- }
30
- if (count === 0) {
31
- const promise = this.subscribeFn(topic, options).finally(() => {
32
- this.pending.delete(topic);
33
- }).catch((err) => {
34
- const count2 = this.counts.get(topic);
35
- if (count2) {
36
- if (count2 <= 1)
37
- this.counts.delete(topic);
38
- else
39
- this.counts.set(topic, count2 - 1);
40
- }
41
- throw err;
42
- });
43
- this.pending.set(topic, promise);
44
- return promise;
45
- } else {
46
- const pending = this.pending.get(topic);
47
- if (pending)
48
- return pending;
49
- }
50
- }
51
- async unsubscribe(topic) {
52
- const count = this.counts.get(topic);
53
- if (count) {
54
- if (count <= 1) {
55
- this.counts.delete(topic);
56
- if (this.lingerMs > 0) {
57
- const timer = setTimeout(() => {
58
- this.lingers.delete(topic);
59
- this.unsubscribeFn(topic).catch(() => {
60
- });
61
- }, this.lingerMs);
62
- this.lingers.set(topic, timer);
63
- } else
64
- await this.unsubscribeFn(topic).catch(() => {
65
- });
66
- } else
67
- this.counts.set(topic, count - 1);
68
- }
69
- }
70
- async flush() {
71
- const topics = [...this.lingers.keys()];
72
- for (const topic of topics) {
73
- clearTimeout(this.lingers.get(topic));
74
- this.lingers.delete(topic);
75
- await this.unsubscribeFn(topic).catch(() => {
76
- });
77
- }
78
- }
79
- }
10
+ import * as v from "valibot";
11
+ import * as CBOR from "cbor2";
80
12
  class CreditGate {
81
13
  constructor(initialCredit) {
82
14
  this.waiters = [];
@@ -866,6 +798,14 @@ class Msg {
866
798
  } else
867
799
  throw new Error("invalid object: not of any known type");
868
800
  }
801
+ /* guard for request messages */
802
+ isRequest(msg) {
803
+ return msg instanceof EventEmission || msg instanceof ServiceCallRequest || msg instanceof SourceFetchRequest || msg instanceof SinkPushRequest;
804
+ }
805
+ /* guard for response messages */
806
+ isResponse(msg) {
807
+ return msg instanceof ServiceCallResponse || msg instanceof SinkPushResponse || msg instanceof SinkPushChunk || msg instanceof SinkPushCredit || msg instanceof SourceFetchResponse || msg instanceof SourceFetchChunk || msg instanceof SourceFetchCredit;
808
+ }
869
809
  }
870
810
  class MsgTrait extends EncodeTrait {
871
811
  constructor() {
@@ -880,6 +820,7 @@ class LogEvent {
880
820
  this.msg = msg;
881
821
  this.data = data;
882
822
  }
823
+ /* resolve all pending promises in the log event */
883
824
  async resolve() {
884
825
  if (this.msg instanceof Promise)
885
826
  this.msg = await this.msg.catch(() => "<resolve-failed>");
@@ -889,6 +830,7 @@ class LogEvent {
889
830
  this.data[field] = await this.data[field].catch(() => "<resolve-failed>");
890
831
  }
891
832
  }
833
+ /* render log event as string */
892
834
  toString() {
893
835
  const timestamp = new Date(this.timestamp);
894
836
  const year = timestamp.getFullYear();
@@ -946,6 +888,8 @@ class BaseTrait extends TraceTrait {
946
888
  /* construct API class */
947
889
  constructor(mqtt, options = {}) {
948
890
  super(options);
891
+ this.onRequest = /* @__PURE__ */ new Map();
892
+ this.onResponse = /* @__PURE__ */ new Map();
949
893
  if (mqtt === null) {
950
894
  this.log("info", "establishing proxy MQTT client");
951
895
  mqtt = new Proxy({}, {
@@ -977,7 +921,7 @@ class BaseTrait extends TraceTrait {
977
921
  this.mqtt.on("message", this._messageHandler);
978
922
  }
979
923
  /* destroy API class */
980
- destroy() {
924
+ async destroy() {
981
925
  this.log("info", "un-hooking from MQTT client");
982
926
  this.mqtt.off("message", this._messageHandler);
983
927
  }
@@ -1036,31 +980,174 @@ class BaseTrait extends TraceTrait {
1036
980
  });
1037
981
  }
1038
982
  /* handle incoming MQTT message */
1039
- _onMessage(topic, message, packet) {
1040
- if (typeof message === "string")
1041
- this.log("info", `received from MQTT topic "${topic}" (type: string, length: ${message.length} chars)`);
983
+ _onMessage(topic, data, packet) {
984
+ const topicMatch = this.options.topicMatch(topic);
985
+ if (topicMatch === null)
986
+ return;
987
+ if (typeof data === "string")
988
+ this.log("info", `received from MQTT topic "${topic}" (type: string, length: ${data.length} chars)`);
1042
989
  else
1043
- this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${message.byteLength} bytes)`);
1044
- let parsed;
990
+ this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${data.byteLength} bytes)`);
991
+ let payload;
1045
992
  try {
1046
- const payload = this.codec.decode(message);
1047
- parsed = this.msg.parse(payload);
1048
- } catch (_err) {
1049
- const err = _err instanceof Error ? new Error(`failed to parse message: ${_err.message}`, { cause: _err }) : new Error("failed to parse message");
1050
- this.error(err);
993
+ payload = this.codec.decode(data);
994
+ } catch (err) {
995
+ this.error(ensureError(err, "failed to parse message into object"));
1051
996
  return;
1052
997
  }
1053
- this.log("debug", `received from MQTT topic "${topic}"`, { message: parsed });
1054
- this._dispatchMessage(topic, parsed).catch((err) => {
1055
- this.error(err, `dispatching message from MQTT topic "${topic}" failed`);
1056
- });
998
+ let message;
999
+ try {
1000
+ message = this.msg.parse(payload);
1001
+ } catch (err) {
1002
+ this.error(ensureError(err, "failed to parse object into typed message object"));
1003
+ return;
1004
+ }
1005
+ this.log("debug", `received from MQTT topic "${topic}"`, { message });
1006
+ if (this.msg.isRequest(message)) {
1007
+ const handler = this.onRequest.get(`${topicMatch.operation}:${message.name}`);
1008
+ if (handler !== void 0) {
1009
+ try {
1010
+ handler(message, topicMatch.name);
1011
+ } catch (err) {
1012
+ this.error(ensureError(err, `dispatching request message from MQTT topic "${topic}" failed`));
1013
+ }
1014
+ }
1015
+ } else if (this.msg.isResponse(message)) {
1016
+ const handler = this.onResponse.get(`${topicMatch.operation}:${message.id}`);
1017
+ if (handler !== void 0) {
1018
+ try {
1019
+ handler(message, topicMatch.name);
1020
+ } catch (err) {
1021
+ this.error(ensureError(err, `dispatching response message from MQTT topic "${topic}" failed`));
1022
+ }
1023
+ }
1024
+ }
1057
1025
  }
1058
- /* dispatch parsed message to appropriate handler
1059
- (base implementation, to be overridden in sub-traits) */
1060
- async _dispatchMessage(_topic, _parsed) {
1026
+ }
1027
+ class RefCountedSubscription {
1028
+ constructor(subscribeFn, unsubscribeFn, lingerMs = 30 * 1e3) {
1029
+ this.subscribeFn = subscribeFn;
1030
+ this.unsubscribeFn = unsubscribeFn;
1031
+ this.lingerMs = lingerMs;
1032
+ this.counts = /* @__PURE__ */ new Map();
1033
+ this.pending = /* @__PURE__ */ new Map();
1034
+ this.lingers = /* @__PURE__ */ new Map();
1035
+ }
1036
+ /* subscribe to a topic (reference-counted) */
1037
+ async subscribe(topic, options = { qos: 2 }) {
1038
+ const count = this.counts.get(topic) ?? 0;
1039
+ this.counts.set(topic, count + 1);
1040
+ const linger = this.lingers.get(topic);
1041
+ if (linger) {
1042
+ clearTimeout(linger);
1043
+ this.lingers.delete(topic);
1044
+ return;
1045
+ }
1046
+ if (count === 0) {
1047
+ const promise = this.subscribeFn(topic, options).finally(() => {
1048
+ this.pending.delete(topic);
1049
+ }).catch((err) => {
1050
+ const count2 = this.counts.get(topic);
1051
+ if (count2) {
1052
+ if (count2 <= 1)
1053
+ this.counts.delete(topic);
1054
+ else
1055
+ this.counts.set(topic, count2 - 1);
1056
+ }
1057
+ throw err;
1058
+ });
1059
+ this.pending.set(topic, promise);
1060
+ return promise;
1061
+ } else {
1062
+ const pending = this.pending.get(topic);
1063
+ if (pending)
1064
+ return pending.catch((err) => {
1065
+ const count2 = this.counts.get(topic);
1066
+ if (count2) {
1067
+ if (count2 <= 1)
1068
+ this.counts.delete(topic);
1069
+ else
1070
+ this.counts.set(topic, count2 - 1);
1071
+ }
1072
+ throw err;
1073
+ });
1074
+ }
1075
+ }
1076
+ /* unsubscribe from a topic (reference-counted) */
1077
+ async unsubscribe(topic) {
1078
+ const count = this.counts.get(topic);
1079
+ if (count) {
1080
+ if (count <= 1) {
1081
+ this.counts.delete(topic);
1082
+ if (this.lingerMs > 0) {
1083
+ const timer = setTimeout(() => {
1084
+ this.lingers.delete(topic);
1085
+ this.unsubscribeFn(topic).catch(() => {
1086
+ });
1087
+ }, this.lingerMs);
1088
+ this.lingers.set(topic, timer);
1089
+ } else
1090
+ await this.unsubscribeFn(topic).catch(() => {
1091
+ });
1092
+ } else
1093
+ this.counts.set(topic, count - 1);
1094
+ }
1095
+ }
1096
+ /* flush all pending linger timers and unsubscribe */
1097
+ async flush() {
1098
+ const topics = [...this.lingers.keys()];
1099
+ for (const topic of topics) {
1100
+ clearTimeout(this.lingers.get(topic));
1101
+ this.lingers.delete(topic);
1102
+ }
1103
+ for (const topic of topics)
1104
+ await this.unsubscribeFn(topic).catch(() => {
1105
+ });
1106
+ }
1107
+ }
1108
+ class SubscriptionTrait extends BaseTrait {
1109
+ constructor() {
1110
+ super(...arguments);
1111
+ this.subscriptions = new RefCountedSubscription((topic, options) => this._subscribeTopic(topic, options), (topic) => this._unsubscribeTopic(topic));
1112
+ }
1113
+ /* destroy topic trait */
1114
+ async destroy() {
1115
+ await this.subscriptions.flush();
1116
+ await super.destroy();
1117
+ }
1118
+ }
1119
+ class TimerTrait extends SubscriptionTrait {
1120
+ constructor() {
1121
+ super(...arguments);
1122
+ this.timers = /* @__PURE__ */ new Map();
1123
+ }
1124
+ /* destroy timer trait */
1125
+ async destroy() {
1126
+ for (const timer of this.timers.values())
1127
+ clearTimeout(timer);
1128
+ this.timers.clear();
1129
+ await super.destroy();
1130
+ }
1131
+ /* refresh (or start) a named timer */
1132
+ timerRefresh(id, onTimeout) {
1133
+ const timer = this.timers.get(id);
1134
+ if (timer !== void 0)
1135
+ clearTimeout(timer);
1136
+ this.timers.set(id, setTimeout(() => {
1137
+ this.timers.delete(id);
1138
+ onTimeout();
1139
+ }, this.options.timeout));
1140
+ }
1141
+ /* clear a named timer */
1142
+ timerClear(id) {
1143
+ const timer = this.timers.get(id);
1144
+ if (timer !== void 0) {
1145
+ clearTimeout(timer);
1146
+ this.timers.delete(id);
1147
+ }
1061
1148
  }
1062
1149
  }
1063
- class MetaTrait extends BaseTrait {
1150
+ class MetaTrait extends TimerTrait {
1064
1151
  constructor() {
1065
1152
  super(...arguments);
1066
1153
  this._meta = /* @__PURE__ */ new Map();
@@ -1167,10 +1254,6 @@ class AuthTrait extends MetaTrait {
1167
1254
  }
1168
1255
  }
1169
1256
  class EventTrait extends AuthTrait {
1170
- constructor() {
1171
- super(...arguments);
1172
- this.events = /* @__PURE__ */ new Map();
1173
- }
1174
1257
  async event(nameOrConfig, ...args) {
1175
1258
  let name;
1176
1259
  let callback;
@@ -1188,12 +1271,12 @@ class EventTrait extends AuthTrait {
1188
1271
  callback = args[0];
1189
1272
  }
1190
1273
  const spool = new Spool();
1191
- if (this.events.has(name))
1274
+ if (this.onRequest.has(`event-emission:${name}`))
1192
1275
  throw new Error(`event: event "${name}" already registered`);
1193
1276
  const topicS = share ? `$share/${share}/${name}` : name;
1194
1277
  const topicB = this.options.topicMake(topicS, "event-emission");
1195
1278
  const topicD = this.options.topicMake(name, "event-emission", this.options.id);
1196
- this.events.set(name, (request, topicName) => {
1279
+ this.onRequest.set(`event-emission:${name}`, (request, topicName) => {
1197
1280
  const senderId = request.sender;
1198
1281
  const params = request.params ?? [];
1199
1282
  const info = { sender: senderId ?? "" };
@@ -1215,7 +1298,7 @@ class EventTrait extends AuthTrait {
1215
1298
  });
1216
1299
  });
1217
1300
  spool.roll(() => {
1218
- this.events.delete(name);
1301
+ this.onRequest.delete(`event-emission:${name}`);
1219
1302
  });
1220
1303
  await run(`subscribe to MQTT topic "${topicB}"`, spool, () => this._subscribeTopic(topicB, { qos: 2, ...options }));
1221
1304
  spool.roll(() => this._unsubscribeTopic(topicB).catch(() => {
@@ -1225,7 +1308,7 @@ class EventTrait extends AuthTrait {
1225
1308
  }));
1226
1309
  return {
1227
1310
  destroy: async () => {
1228
- if (!this.events.has(name))
1311
+ if (!this.onRequest.has(`event-emission:${name}`))
1229
1312
  throw new Error(`destroy: event "${name}" not registered`);
1230
1313
  await spool.unroll(false)?.catch((err) => {
1231
1314
  this.error(err, `destroy: failed to cleanup: ${err.message}`);
@@ -1238,14 +1321,14 @@ class EventTrait extends AuthTrait {
1238
1321
  let params;
1239
1322
  let receiver;
1240
1323
  let options = {};
1241
- let meta = {};
1324
+ let meta;
1242
1325
  let dry;
1243
1326
  if (typeof eventOrConfig === "object" && eventOrConfig !== null) {
1244
1327
  event = eventOrConfig.event;
1245
1328
  params = eventOrConfig.params;
1246
1329
  receiver = eventOrConfig.receiver;
1247
1330
  options = eventOrConfig.options ?? {};
1248
- meta = eventOrConfig.meta ?? {};
1331
+ meta = eventOrConfig.meta;
1249
1332
  dry = eventOrConfig.dry;
1250
1333
  } else {
1251
1334
  event = eventOrConfig;
@@ -1264,29 +1347,8 @@ class EventTrait extends AuthTrait {
1264
1347
  this.error(err, `emitting event "${event}" failed`);
1265
1348
  });
1266
1349
  }
1267
- /* dispatch message (Event pattern handling) */
1268
- async _dispatchMessage(topic, message) {
1269
- await super._dispatchMessage(topic, message);
1270
- const topicMatch = this.options.topicMatch(topic);
1271
- if (topicMatch !== null && topicMatch.operation === "event-emission" && message instanceof EventEmission) {
1272
- const handler = this.events.get(message.name);
1273
- if (handler !== void 0)
1274
- handler(message, topicMatch.name);
1275
- }
1276
- }
1277
1350
  }
1278
1351
  class ServiceTrait extends EventTrait {
1279
- constructor() {
1280
- super(...arguments);
1281
- this.services = /* @__PURE__ */ new Map();
1282
- this.callCallbacks = /* @__PURE__ */ new Map();
1283
- this.callSubscriptions = new RefCountedSubscription((topic, options) => this._subscribeTopic(topic, options), (topic) => this._unsubscribeTopic(topic));
1284
- }
1285
- /* destroy service trait */
1286
- destroy() {
1287
- super.destroy();
1288
- this.callSubscriptions.flush();
1289
- }
1290
1352
  async service(nameOrConfig, ...args) {
1291
1353
  let name;
1292
1354
  let callback;
@@ -1304,12 +1366,12 @@ class ServiceTrait extends EventTrait {
1304
1366
  callback = args[0];
1305
1367
  }
1306
1368
  const spool = new Spool();
1307
- if (this.services.has(name))
1308
- throw new Error(`register: service "${name}" already registered`);
1369
+ if (this.onRequest.has(`service-call-request:${name}`))
1370
+ throw new Error(`service: service "${name}" already registered`);
1309
1371
  const topicS = `$share/${share}/${name}`;
1310
1372
  const topicB = this.options.topicMake(topicS, "service-call-request");
1311
1373
  const topicD = this.options.topicMake(name, "service-call-request", this.options.id);
1312
- this.services.set(name, (request, topicName) => {
1374
+ this.onRequest.set(`service-call-request:${name}`, (request, topicName) => {
1313
1375
  const requestId = request.id;
1314
1376
  const senderId = request.sender;
1315
1377
  if (senderId === void 0 || senderId === "")
@@ -1343,7 +1405,7 @@ class ServiceTrait extends EventTrait {
1343
1405
  });
1344
1406
  });
1345
1407
  spool.roll(() => {
1346
- this.services.delete(name);
1408
+ this.onRequest.delete(`service-call-request:${name}`);
1347
1409
  });
1348
1410
  await run(`subscribe to MQTT topic "${topicB}"`, spool, () => this._subscribeTopic(topicB, { qos: 2, ...options }));
1349
1411
  spool.roll(() => this._unsubscribeTopic(topicB).catch(() => {
@@ -1353,8 +1415,8 @@ class ServiceTrait extends EventTrait {
1353
1415
  }));
1354
1416
  return {
1355
1417
  destroy: async () => {
1356
- if (!this.services.has(name))
1357
- throw new Error(`destroy: service "${name}" no longer registered`);
1418
+ if (!this.onRequest.has(`service-call-request:${name}`))
1419
+ throw new Error(`destroy: service "${name}" not registered`);
1358
1420
  await spool.unroll(false)?.catch((err) => {
1359
1421
  this.error(err, `destroy: failed to cleanup: ${err.message}`);
1360
1422
  });
@@ -1366,13 +1428,13 @@ class ServiceTrait extends EventTrait {
1366
1428
  let params;
1367
1429
  let receiver;
1368
1430
  let options = {};
1369
- let meta = {};
1431
+ let meta;
1370
1432
  if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1371
1433
  name = nameOrConfig.name;
1372
1434
  params = nameOrConfig.params;
1373
1435
  receiver = nameOrConfig.receiver;
1374
1436
  options = nameOrConfig.options ?? {};
1375
- meta = nameOrConfig.meta ?? {};
1437
+ meta = nameOrConfig.meta;
1376
1438
  } else {
1377
1439
  name = nameOrConfig;
1378
1440
  params = args;
@@ -1380,8 +1442,8 @@ class ServiceTrait extends EventTrait {
1380
1442
  const spool = new Spool();
1381
1443
  const requestId = nanoid();
1382
1444
  const responseTopic = this.options.topicMake(name, "service-call-response", this.options.id);
1383
- await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.callSubscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
1384
- spool.roll(() => this.callSubscriptions.unsubscribe(responseTopic));
1445
+ await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
1446
+ spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
1385
1447
  const promise = new Promise((resolve, reject) => {
1386
1448
  let timer = setTimeout(async () => {
1387
1449
  timer = null;
@@ -1394,7 +1456,7 @@ class ServiceTrait extends EventTrait {
1394
1456
  timer = null;
1395
1457
  }
1396
1458
  });
1397
- this.callCallbacks.set(requestId, async (response) => {
1459
+ this.onResponse.set(`service-call-response:${requestId}`, async (response) => {
1398
1460
  await spool.unroll();
1399
1461
  if (response.error !== void 0)
1400
1462
  reject(new Error(response.error));
@@ -1402,7 +1464,7 @@ class ServiceTrait extends EventTrait {
1402
1464
  resolve(response.result);
1403
1465
  });
1404
1466
  spool.roll(() => {
1405
- this.callCallbacks.delete(requestId);
1467
+ this.onResponse.delete(`service-call-response:${requestId}`);
1406
1468
  });
1407
1469
  });
1408
1470
  const auth = this.authenticate();
@@ -1413,56 +1475,11 @@ class ServiceTrait extends EventTrait {
1413
1475
  await run(`publish service request as MQTT message to topic "${topic}"`, spool, () => this._publishToTopic(topic, message, { qos: 2, ...options }));
1414
1476
  return promise;
1415
1477
  }
1416
- /* dispatch message (Service pattern handling) */
1417
- async _dispatchMessage(topic, message) {
1418
- await super._dispatchMessage(topic, message);
1419
- const topicMatch = this.options.topicMatch(topic);
1420
- if (topicMatch !== null && topicMatch.operation === "service-call-request" && message instanceof ServiceCallRequest) {
1421
- const handler = this.services.get(message.name);
1422
- if (handler !== void 0)
1423
- handler(message, topicMatch.name);
1424
- } else if (topicMatch !== null && topicMatch.operation === "service-call-response" && topicMatch.peerId === this.options.id && message instanceof ServiceCallResponse) {
1425
- const handler = this.callCallbacks.get(message.id);
1426
- if (handler !== void 0)
1427
- handler(message);
1428
- }
1429
- }
1430
1478
  }
1431
1479
  class SourceTrait extends ServiceTrait {
1432
1480
  constructor() {
1433
1481
  super(...arguments);
1434
- this.sources = /* @__PURE__ */ new Map();
1435
- this.fetchResponseCallbacks = /* @__PURE__ */ new Map();
1436
- this.fetchChunkCallbacks = /* @__PURE__ */ new Map();
1437
- this.sourceCreditCallbacks = /* @__PURE__ */ new Map();
1438
1482
  this.sourceCreditGates = /* @__PURE__ */ new Map();
1439
- this.sourceTimers = /* @__PURE__ */ new Map();
1440
- this.fetchSubscriptions = new RefCountedSubscription((topic, options) => this._subscribeTopic(topic, options), (topic) => this._unsubscribeTopic(topic));
1441
- }
1442
- /* refresh source timer for a specific request */
1443
- _refreshSourceTimer(requestId) {
1444
- const timer = this.sourceTimers.get(requestId);
1445
- if (timer !== void 0)
1446
- clearTimeout(timer);
1447
- this.sourceTimers.set(requestId, setTimeout(() => {
1448
- this.sourceTimers.delete(requestId);
1449
- const gate = this.sourceCreditGates.get(requestId);
1450
- if (gate !== void 0)
1451
- gate.abort();
1452
- }, this.options.timeout));
1453
- }
1454
- /* clear source timer for a specific request */
1455
- _clearSourceTimer(requestId) {
1456
- const timer = this.sourceTimers.get(requestId);
1457
- if (timer !== void 0) {
1458
- clearTimeout(timer);
1459
- this.sourceTimers.delete(requestId);
1460
- }
1461
- }
1462
- /* destroy source trait */
1463
- destroy() {
1464
- super.destroy();
1465
- this.fetchSubscriptions.flush();
1466
1483
  }
1467
1484
  async source(nameOrConfig, ...args) {
1468
1485
  let name;
@@ -1481,13 +1498,13 @@ class SourceTrait extends ServiceTrait {
1481
1498
  callback = args[0];
1482
1499
  }
1483
1500
  const spool = new Spool();
1484
- if (this.sources.has(name))
1501
+ if (this.onRequest.has(`source-fetch-request:${name}`))
1485
1502
  throw new Error(`source: source "${name}" already established`);
1486
1503
  const topicS = `$share/${share}/${name}`;
1487
1504
  const topicReqB = this.options.topicMake(topicS, "source-fetch-request");
1488
1505
  const topicReqD = this.options.topicMake(name, "source-fetch-request", this.options.id);
1489
1506
  const topicCreditD = this.options.topicMake(name, "source-fetch-credit", this.options.id);
1490
- this.sources.set(name, (request, topicName) => {
1507
+ this.onRequest.set(`source-fetch-request:${name}`, (request, topicName) => {
1491
1508
  const requestId = request.id;
1492
1509
  const params = request.params ?? [];
1493
1510
  const sender = request.sender;
@@ -1508,8 +1525,12 @@ class SourceTrait extends ServiceTrait {
1508
1525
  const message = this.codec.encode(response);
1509
1526
  await this._publishToTopic(responseTopic, message, { qos: 2 });
1510
1527
  };
1511
- const refreshSourceTimeout = () => this._refreshSourceTimer(requestId);
1512
- const clearSourceTimeout = () => this._clearSourceTimer(requestId);
1528
+ const refreshSourceTimeout = () => this.timerRefresh(requestId, () => {
1529
+ const gate = this.sourceCreditGates.get(requestId);
1530
+ if (gate !== void 0)
1531
+ gate.abort();
1532
+ });
1533
+ const clearSourceTimeout = () => this.timerClear(requestId);
1513
1534
  refreshSourceTimeout();
1514
1535
  const sendChunk = async (chunk, error, final) => {
1515
1536
  refreshSourceTimeout();
@@ -1521,9 +1542,9 @@ class SourceTrait extends ServiceTrait {
1521
1542
  const creditGate = initialCredit !== void 0 && initialCredit > 0 ? new CreditGate(initialCredit) : void 0;
1522
1543
  if (creditGate) {
1523
1544
  this.sourceCreditGates.set(requestId, creditGate);
1524
- this.sourceCreditCallbacks.set(requestId, (creditParsed) => {
1545
+ this.onResponse.set(`source-fetch-credit:${requestId}`, (creditParsed) => {
1525
1546
  creditGate.replenish(creditParsed.credit);
1526
- this._refreshSourceTimer(requestId);
1547
+ refreshSourceTimeout();
1527
1548
  });
1528
1549
  }
1529
1550
  let ackSent = false;
@@ -1561,11 +1582,11 @@ class SourceTrait extends ServiceTrait {
1561
1582
  creditGate.abort();
1562
1583
  this.sourceCreditGates.delete(requestId);
1563
1584
  }
1564
- this.sourceCreditCallbacks.delete(requestId);
1585
+ this.onResponse.delete(`source-fetch-credit:${requestId}`);
1565
1586
  });
1566
1587
  });
1567
1588
  spool.roll(() => {
1568
- this.sources.delete(name);
1589
+ this.onRequest.delete(`source-fetch-request:${name}`);
1569
1590
  });
1570
1591
  await run(`subscribe to MQTT topic "${topicReqB}"`, spool, () => this._subscribeTopic(topicReqB, { qos: 2, ...options }));
1571
1592
  spool.roll(() => this._unsubscribeTopic(topicReqB).catch(() => {
@@ -1578,7 +1599,7 @@ class SourceTrait extends ServiceTrait {
1578
1599
  }));
1579
1600
  return {
1580
1601
  destroy: async () => {
1581
- if (!this.sources.has(name))
1602
+ if (!this.onRequest.has(`source-fetch-request:${name}`))
1582
1603
  throw new Error(`destroy: source "${name}" not established`);
1583
1604
  await spool.unroll(false)?.catch((err) => {
1584
1605
  this.error(err, `destroy: failed to cleanup: ${err.message}`);
@@ -1606,10 +1627,10 @@ class SourceTrait extends ServiceTrait {
1606
1627
  const requestId = nanoid();
1607
1628
  const responseTopic = this.options.topicMake(name, "source-fetch-response", this.options.id);
1608
1629
  const chunkTopic = this.options.topicMake(name, "source-fetch-chunk", this.options.id);
1609
- await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.fetchSubscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
1610
- spool.roll(() => this.fetchSubscriptions.unsubscribe(responseTopic));
1611
- await run(`subscribe to MQTT topic "${chunkTopic}"`, spool, () => this.fetchSubscriptions.subscribe(chunkTopic, { qos: options.qos ?? 2 }));
1612
- spool.roll(() => this.fetchSubscriptions.unsubscribe(chunkTopic));
1630
+ await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
1631
+ spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
1632
+ await run(`subscribe to MQTT topic "${chunkTopic}"`, spool, () => this.subscriptions.subscribe(chunkTopic, { qos: options.qos ?? 2 }));
1633
+ spool.roll(() => this.subscriptions.unsubscribe(chunkTopic));
1613
1634
  const chunkCredit = this.options.chunkCredit;
1614
1635
  let chunksReceived = 0;
1615
1636
  let creditGranted = chunkCredit;
@@ -1617,7 +1638,7 @@ class SourceTrait extends ServiceTrait {
1617
1638
  const stream = new Readable({
1618
1639
  highWaterMark: chunkCredit > 0 ? chunkCredit * this.options.chunkSize : 16 * 1024,
1619
1640
  read: (_size) => {
1620
- if (chunkCredit <= 0 || !this.fetchChunkCallbacks.has(requestId))
1641
+ if (chunkCredit <= 0 || !this.onResponse.has(`source-fetch-chunk:${requestId}`))
1621
1642
  return;
1622
1643
  const targetId = serverId ?? receiver;
1623
1644
  if (!targetId)
@@ -1660,7 +1681,7 @@ class SourceTrait extends ServiceTrait {
1660
1681
  refreshTimeout();
1661
1682
  stream.once("close", () => spool.unroll());
1662
1683
  stream.once("error", () => spool.unroll());
1663
- this.fetchResponseCallbacks.set(requestId, (response) => {
1684
+ this.onResponse.set(`source-fetch-response:${requestId}`, (response) => {
1664
1685
  if (response.sender)
1665
1686
  serverId = response.sender;
1666
1687
  metaResolve?.(response.meta);
@@ -1670,7 +1691,7 @@ class SourceTrait extends ServiceTrait {
1670
1691
  } else
1671
1692
  refreshTimeout();
1672
1693
  });
1673
- this.fetchChunkCallbacks.set(requestId, (response) => {
1694
+ this.onResponse.set(`source-fetch-chunk:${requestId}`, (response) => {
1674
1695
  if (response.sender)
1675
1696
  serverId = response.sender;
1676
1697
  if (response.error) {
@@ -1689,8 +1710,8 @@ class SourceTrait extends ServiceTrait {
1689
1710
  }
1690
1711
  });
1691
1712
  spool.roll(() => {
1692
- this.fetchResponseCallbacks.delete(requestId);
1693
- this.fetchChunkCallbacks.delete(requestId);
1713
+ this.onResponse.delete(`source-fetch-response:${requestId}`);
1714
+ this.onResponse.delete(`source-fetch-chunk:${requestId}`);
1694
1715
  });
1695
1716
  const auth = this.authenticate();
1696
1717
  const metaStore = this.metaStore(meta);
@@ -1706,67 +1727,12 @@ class SourceTrait extends ServiceTrait {
1706
1727
  makeMutuallyExclusiveFields(result, "stream", "buffer");
1707
1728
  return result;
1708
1729
  }
1709
- /* dispatch message (Source Fetch pattern handling) */
1710
- async _dispatchMessage(topic, message) {
1711
- await super._dispatchMessage(topic, message);
1712
- const topicMatch = this.options.topicMatch(topic);
1713
- if (topicMatch !== null && topicMatch.operation === "source-fetch-request" && message instanceof SourceFetchRequest) {
1714
- const handler = this.sources.get(message.name);
1715
- if (handler !== void 0)
1716
- handler(message, topicMatch.name);
1717
- } else if (topicMatch !== null && topicMatch.operation === "source-fetch-response" && message instanceof SourceFetchResponse) {
1718
- const handler = this.fetchResponseCallbacks.get(message.id);
1719
- if (handler !== void 0)
1720
- handler(message);
1721
- } else if (topicMatch !== null && topicMatch.operation === "source-fetch-chunk" && message instanceof SourceFetchChunk) {
1722
- const handler = this.fetchChunkCallbacks.get(message.id);
1723
- if (handler !== void 0)
1724
- handler(message);
1725
- } else if (topicMatch !== null && topicMatch.operation === "source-fetch-credit" && message instanceof SourceFetchCredit) {
1726
- const handler = this.sourceCreditCallbacks.get(message.id);
1727
- if (handler !== void 0)
1728
- handler(message);
1729
- }
1730
- }
1731
1730
  }
1732
1731
  class SinkTrait extends SourceTrait {
1733
1732
  constructor() {
1734
1733
  super(...arguments);
1735
- this.sinks = /* @__PURE__ */ new Map();
1736
1734
  this.pushStreams = /* @__PURE__ */ new Map();
1737
1735
  this.pushSpools = /* @__PURE__ */ new Map();
1738
- this.pushTimers = /* @__PURE__ */ new Map();
1739
- this.pushChunkCallbacks = /* @__PURE__ */ new Map();
1740
- this.pushResponseCallbacks = /* @__PURE__ */ new Map();
1741
- this.pushCreditCallbacks = /* @__PURE__ */ new Map();
1742
- this.pushSubscriptions = new RefCountedSubscription((topic, options) => this._subscribeTopic(topic, options), (topic) => this._unsubscribeTopic(topic));
1743
- }
1744
- /* destroy sink trait */
1745
- destroy() {
1746
- super.destroy();
1747
- this.pushSubscriptions.flush();
1748
- }
1749
- /* refresh push timer for a specific request */
1750
- _refreshPushTimer(requestId) {
1751
- const timer = this.pushTimers.get(requestId);
1752
- if (timer !== void 0)
1753
- clearTimeout(timer);
1754
- this.pushTimers.set(requestId, setTimeout(() => {
1755
- this.pushTimers.delete(requestId);
1756
- const stream = this.pushStreams.get(requestId);
1757
- if (stream !== void 0)
1758
- stream.destroy(new Error("push stream timeout"));
1759
- const spool = this.pushSpools.get(requestId);
1760
- spool?.unroll();
1761
- }, this.options.timeout));
1762
- }
1763
- /* clear push timer for a specific request */
1764
- _clearPushTimer(requestId) {
1765
- const timer = this.pushTimers.get(requestId);
1766
- if (timer !== void 0) {
1767
- clearTimeout(timer);
1768
- this.pushTimers.delete(requestId);
1769
- }
1770
1736
  }
1771
1737
  async sink(nameOrConfig, ...args) {
1772
1738
  let name;
@@ -1785,13 +1751,13 @@ class SinkTrait extends SourceTrait {
1785
1751
  callback = args[0];
1786
1752
  }
1787
1753
  const spool = new Spool();
1788
- if (this.sinks.has(name))
1754
+ if (this.onRequest.has(`sink-push-request:${name}`))
1789
1755
  throw new Error(`sink: sink "${name}" already established`);
1790
1756
  const topicS = `$share/${share}/${name}`;
1791
1757
  const topicReqB = this.options.topicMake(topicS, "sink-push-request");
1792
1758
  const topicReqD = this.options.topicMake(name, "sink-push-request", this.options.id);
1793
1759
  const topicChunkD = this.options.topicMake(name, "sink-push-chunk", this.options.id);
1794
- this.sinks.set(name, (request, topicName) => {
1760
+ this.onRequest.set(`sink-push-request:${name}`, (request, topicName) => {
1795
1761
  const requestId = request.id;
1796
1762
  const params = request.params ?? [];
1797
1763
  const sender = request.sender;
@@ -1829,8 +1795,14 @@ class SinkTrait extends SourceTrait {
1829
1795
  chunksReceived: 0,
1830
1796
  creditGranted: chunkCredit
1831
1797
  } : void 0;
1832
- const refreshPushTimeout = () => this._refreshPushTimer(requestId);
1833
- const clearPushTimeout = () => this._clearPushTimer(requestId);
1798
+ const refreshPushTimeout = () => this.timerRefresh(requestId, () => {
1799
+ const stream = this.pushStreams.get(requestId);
1800
+ if (stream !== void 0)
1801
+ stream.destroy(new Error("push stream timeout"));
1802
+ const spool2 = this.pushSpools.get(requestId);
1803
+ spool2?.unroll();
1804
+ });
1805
+ const clearPushTimeout = () => this.timerClear(requestId);
1834
1806
  const readable = new Readable({
1835
1807
  highWaterMark: chunkCredit > 0 ? chunkCredit * this.options.chunkSize : 16 * 1024,
1836
1808
  read: (_size) => {
@@ -1855,7 +1827,7 @@ class SinkTrait extends SourceTrait {
1855
1827
  });
1856
1828
  readable.once("close", () => reqSpool.unroll());
1857
1829
  readable.once("error", () => reqSpool.unroll());
1858
- this.pushChunkCallbacks.set(requestId, (chunkParsed, chunkTopicName) => {
1830
+ this.onResponse.set(`sink-push-chunk:${requestId}`, (chunkParsed, chunkTopicName) => {
1859
1831
  if (chunkTopicName !== chunkParsed.name)
1860
1832
  throw new Error(`sink name mismatch between topic "${chunkTopicName}" and payload "${chunkParsed.name}"`);
1861
1833
  if (chunkParsed.error !== void 0) {
@@ -1875,7 +1847,7 @@ class SinkTrait extends SourceTrait {
1875
1847
  }
1876
1848
  });
1877
1849
  reqSpool.roll(() => {
1878
- this.pushChunkCallbacks.delete(requestId);
1850
+ this.onResponse.delete(`sink-push-chunk:${requestId}`);
1879
1851
  });
1880
1852
  refreshPushTimeout();
1881
1853
  reqSpool.roll(() => {
@@ -1898,7 +1870,7 @@ class SinkTrait extends SourceTrait {
1898
1870
  });
1899
1871
  });
1900
1872
  spool.roll(() => {
1901
- this.sinks.delete(name);
1873
+ this.onRequest.delete(`sink-push-request:${name}`);
1902
1874
  });
1903
1875
  await run(`subscribe to MQTT topic "${topicReqB}"`, spool, () => this._subscribeTopic(topicReqB, { qos: 2, ...options }));
1904
1876
  spool.roll(() => this._unsubscribeTopic(topicReqB).catch(() => {
@@ -1911,7 +1883,7 @@ class SinkTrait extends SourceTrait {
1911
1883
  }));
1912
1884
  return {
1913
1885
  destroy: async () => {
1914
- if (!this.sinks.has(name))
1886
+ if (!this.onRequest.has(`sink-push-request:${name}`))
1915
1887
  throw new Error(`destroy: sink "${name}" not established`);
1916
1888
  await spool.unroll(false)?.catch((err) => {
1917
1889
  this.error(err, `destroy: failed to cleanup: ${err.message}`);
@@ -1941,8 +1913,8 @@ class SinkTrait extends SourceTrait {
1941
1913
  const spool = new Spool();
1942
1914
  const requestId = nanoid();
1943
1915
  const responseTopic = this.options.topicMake(name, "sink-push-response", this.options.id);
1944
- await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.pushSubscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
1945
- spool.roll(() => this.pushSubscriptions.unsubscribe(responseTopic));
1916
+ await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
1917
+ spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
1946
1918
  const abortController = new AbortController();
1947
1919
  const abortSignal = abortController.signal;
1948
1920
  let timer = null;
@@ -1972,7 +1944,7 @@ class SinkTrait extends SourceTrait {
1972
1944
  spool.roll(() => {
1973
1945
  abortSignal.removeEventListener("abort", onAbort);
1974
1946
  });
1975
- this.pushResponseCallbacks.set(requestId, (response) => {
1947
+ this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
1976
1948
  if (response.error)
1977
1949
  reject(new Error(response.error));
1978
1950
  else {
@@ -1983,13 +1955,13 @@ class SinkTrait extends SourceTrait {
1983
1955
  }
1984
1956
  });
1985
1957
  spool.roll(() => {
1986
- this.pushResponseCallbacks.delete(requestId);
1958
+ this.onResponse.delete(`sink-push-response:${requestId}`);
1987
1959
  });
1988
- this.pushCreditCallbacks.set(requestId, (_response) => {
1960
+ this.onResponse.set(`sink-push-credit:${requestId}`, (_response) => {
1989
1961
  refreshTimeout();
1990
1962
  });
1991
1963
  spool.roll(() => {
1992
- this.pushCreditCallbacks.delete(requestId);
1964
+ this.onResponse.delete(`sink-push-credit:${requestId}`);
1993
1965
  });
1994
1966
  const auth = this.authenticate();
1995
1967
  const metaStore = this.metaStore(meta);
@@ -2000,7 +1972,7 @@ class SinkTrait extends SourceTrait {
2000
1972
  reject(err);
2001
1973
  });
2002
1974
  });
2003
- this.pushResponseCallbacks.set(requestId, (response) => {
1975
+ this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
2004
1976
  if (response.error)
2005
1977
  abortController.abort(new Error(response.error));
2006
1978
  });
@@ -2008,13 +1980,13 @@ class SinkTrait extends SourceTrait {
2008
1980
  creditGate = new CreditGate(initialCredit);
2009
1981
  if (creditGate) {
2010
1982
  const creditTopic = this.options.topicMake(name, "sink-push-credit", this.options.id);
2011
- await run(`subscribe to MQTT topic "${creditTopic}"`, spool, () => this.pushSubscriptions.subscribe(creditTopic, { qos: 2 }));
2012
- spool.roll(() => this.pushSubscriptions.unsubscribe(creditTopic));
1983
+ await run(`subscribe to MQTT topic "${creditTopic}"`, spool, () => this.subscriptions.subscribe(creditTopic, { qos: 2 }));
1984
+ spool.roll(() => this.subscriptions.unsubscribe(creditTopic));
2013
1985
  const gate = creditGate;
2014
1986
  spool.roll(() => {
2015
1987
  gate.abort();
2016
1988
  });
2017
- this.pushCreditCallbacks.set(requestId, (response) => {
1989
+ this.onResponse.set(`sink-push-credit:${requestId}`, (response) => {
2018
1990
  gate.replenish(response.credit);
2019
1991
  refreshTimeout();
2020
1992
  });
@@ -2042,28 +2014,6 @@ class SinkTrait extends SourceTrait {
2042
2014
  await spool.unroll();
2043
2015
  }
2044
2016
  }
2045
- /* dispatch incoming MQTT message */
2046
- async _dispatchMessage(topic, message) {
2047
- await super._dispatchMessage(topic, message);
2048
- const topicMatch = this.options.topicMatch(topic);
2049
- if (topicMatch !== null && topicMatch.operation === "sink-push-request" && message instanceof SinkPushRequest) {
2050
- const handler = this.sinks.get(message.name);
2051
- if (handler !== void 0)
2052
- handler(message, topicMatch.name);
2053
- } else if (topicMatch !== null && topicMatch.operation === "sink-push-response" && message instanceof SinkPushResponse) {
2054
- const handler = this.pushResponseCallbacks.get(message.id);
2055
- if (handler !== void 0)
2056
- handler(message);
2057
- } else if (topicMatch !== null && topicMatch.operation === "sink-push-chunk" && message instanceof SinkPushChunk) {
2058
- const handler = this.pushChunkCallbacks.get(message.id);
2059
- if (handler !== void 0)
2060
- handler(message, topicMatch.name);
2061
- } else if (topicMatch !== null && topicMatch.operation === "sink-push-credit" && message instanceof SinkPushCredit) {
2062
- const handler = this.pushCreditCallbacks.get(message.id);
2063
- if (handler !== void 0)
2064
- handler(message);
2065
- }
2066
- }
2067
2017
  }
2068
2018
  class MQTTp extends SinkTrait {
2069
2019
  }