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.
- package/AGENTS.md +55 -44
- package/CHANGELOG.md +14 -0
- package/README.md +4 -3
- package/doc/mqtt-plus-api.md +693 -680
- package/doc/mqtt-plus-architecture.d2 +139 -0
- package/doc/mqtt-plus-architecture.md +10 -0
- package/doc/mqtt-plus-architecture.svg +95 -0
- package/doc/mqtt-plus-broker-setup.md +9 -3
- package/doc/mqtt-plus-comm.md +73 -0
- package/doc/mqtt-plus-internals.md +3 -3
- package/dst-stage1/mqtt-plus-base.d.ts +3 -2
- package/dst-stage1/mqtt-plus-base.js +53 -22
- package/dst-stage1/mqtt-plus-event.d.ts +0 -2
- package/dst-stage1/mqtt-plus-event.js +6 -26
- package/dst-stage1/mqtt-plus-meta.d.ts +2 -2
- package/dst-stage1/mqtt-plus-meta.js +2 -2
- package/dst-stage1/mqtt-plus-msg.d.ts +2 -0
- package/dst-stage1/mqtt-plus-msg.js +17 -0
- package/dst-stage1/mqtt-plus-service.d.ts +0 -5
- package/dst-stage1/mqtt-plus-service.js +12 -48
- package/dst-stage1/mqtt-plus-sink.d.ts +0 -10
- package/dst-stage1/mqtt-plus-sink.js +25 -92
- package/dst-stage1/mqtt-plus-source.d.ts +0 -10
- package/dst-stage1/mqtt-plus-source.js +23 -88
- package/dst-stage1/mqtt-plus-subscription.d.ts +20 -0
- package/dst-stage1/mqtt-plus-subscription.js +126 -0
- package/dst-stage1/mqtt-plus-timer.d.ts +8 -0
- package/dst-stage1/mqtt-plus-timer.js +57 -0
- package/dst-stage1/mqtt-plus-topic.d.ts +20 -0
- package/dst-stage1/mqtt-plus-topic.js +112 -0
- package/dst-stage1/mqtt-plus-trace.js +2 -0
- package/dst-stage1/mqtt-plus-util.d.ts +0 -13
- package/dst-stage1/mqtt-plus-util.js +1 -77
- package/dst-stage1/mqtt-plus-version.d.ts +0 -1
- package/dst-stage1/mqtt-plus-version.js +0 -6
- package/dst-stage1/tsc.tsbuildinfo +1 -1
- package/dst-stage2/mqtt-plus.cjs.js +242 -292
- package/dst-stage2/mqtt-plus.esm.js +240 -290
- package/dst-stage2/mqtt-plus.umd.js +12 -12
- package/etc/knip.jsonc +1 -1
- package/etc/stx.conf +6 -4
- package/package.json +1 -1
- package/src/mqtt-plus-base.ts +56 -26
- package/src/mqtt-plus-event.ts +8 -24
- package/src/mqtt-plus-meta.ts +3 -3
- package/src/mqtt-plus-msg.ts +28 -0
- package/src/mqtt-plus-service.ts +12 -50
- package/src/mqtt-plus-sink.ts +32 -105
- package/src/mqtt-plus-source.ts +29 -99
- package/src/mqtt-plus-subscription.ts +141 -0
- package/src/mqtt-plus-timer.ts +61 -0
- package/src/mqtt-plus-trace.ts +4 -0
- package/src/mqtt-plus-util.ts +1 -81
- package/src/mqtt-plus-version.ts +0 -7
- package/tst/mqtt-plus-0-fixture.ts +2 -2
- package/tst/mqtt-plus-0-mosquitto.ts +5 -0
- package/tst/mqtt-plus-1-api.spec.ts +1 -1
- package/tst/mqtt-plus-2-event.spec.ts +0 -6
- package/tst/mqtt-plus-3-service.spec.ts +3 -7
- package/tst/mqtt-plus-4-sink.spec.ts +14 -9
- package/tst/mqtt-plus-5-source.spec.ts +11 -5
- package/tst/mqtt-plus-6-misc.spec.ts +23 -23
- package/tst/tsc.json +1 -1
- package/doc/mqtt-plus-communication.md +0 -68
- /package/doc/{mqtt-plus-1-event-emission.d2 → mqtt-plus-comm-event-emission.d2} +0 -0
- /package/doc/{mqtt-plus-1-event-emission.svg → mqtt-plus-comm-event-emission.svg} +0 -0
- /package/doc/{mqtt-plus-2-service-call.d2 → mqtt-plus-comm-service-call.d2} +0 -0
- /package/doc/{mqtt-plus-2-service-call.svg → mqtt-plus-comm-service-call.svg} +0 -0
- /package/doc/{mqtt-plus-3-sink-push.d2 → mqtt-plus-comm-sink-push.d2} +0 -0
- /package/doc/{mqtt-plus-3-sink-push.svg → mqtt-plus-comm-sink-push.svg} +0 -0
- /package/doc/{mqtt-plus-4-source-fetch.d2 → mqtt-plus-comm-source-fetch.d2} +0 -0
- /package/doc/{mqtt-plus-4-source-fetch.svg → mqtt-plus-comm-source-fetch.svg} +0 -0
- /package/{doc/theme.d2 → etc/d2.theme.d2} +0 -0
|
@@ -3,13 +3,13 @@ const node_stream = require("node:stream");
|
|
|
3
3
|
const nanoid = require("nanoid");
|
|
4
4
|
const node_buffer = require("node:buffer");
|
|
5
5
|
const PLazy = require("p-lazy");
|
|
6
|
-
const v = require("valibot");
|
|
7
|
-
const CBOR = require("cbor2");
|
|
8
6
|
const sign = require("jose/jwt/sign");
|
|
9
7
|
const verify = require("jose/jwt/verify");
|
|
10
8
|
const pbkdf2 = require("@stablelib/pbkdf2");
|
|
11
9
|
const sha256 = require("@stablelib/sha256");
|
|
12
10
|
const node_events = require("node:events");
|
|
11
|
+
const v = require("valibot");
|
|
12
|
+
const CBOR = require("cbor2");
|
|
13
13
|
function _interopNamespaceDefault(e) {
|
|
14
14
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
15
15
|
if (e) {
|
|
@@ -26,78 +26,10 @@ function _interopNamespaceDefault(e) {
|
|
|
26
26
|
n.default = e;
|
|
27
27
|
return Object.freeze(n);
|
|
28
28
|
}
|
|
29
|
-
const v__namespace = /* @__PURE__ */ _interopNamespaceDefault(v);
|
|
30
|
-
const CBOR__namespace = /* @__PURE__ */ _interopNamespaceDefault(CBOR);
|
|
31
29
|
const pbkdf2__namespace = /* @__PURE__ */ _interopNamespaceDefault(pbkdf2);
|
|
32
30
|
const sha256__namespace = /* @__PURE__ */ _interopNamespaceDefault(sha256);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.subscribeFn = subscribeFn;
|
|
36
|
-
this.unsubscribeFn = unsubscribeFn;
|
|
37
|
-
this.lingerMs = lingerMs;
|
|
38
|
-
this.counts = /* @__PURE__ */ new Map();
|
|
39
|
-
this.pending = /* @__PURE__ */ new Map();
|
|
40
|
-
this.lingers = /* @__PURE__ */ new Map();
|
|
41
|
-
}
|
|
42
|
-
async subscribe(topic, options = { qos: 2 }) {
|
|
43
|
-
const count = this.counts.get(topic) ?? 0;
|
|
44
|
-
this.counts.set(topic, count + 1);
|
|
45
|
-
const linger = this.lingers.get(topic);
|
|
46
|
-
if (linger) {
|
|
47
|
-
clearTimeout(linger);
|
|
48
|
-
this.lingers.delete(topic);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (count === 0) {
|
|
52
|
-
const promise = this.subscribeFn(topic, options).finally(() => {
|
|
53
|
-
this.pending.delete(topic);
|
|
54
|
-
}).catch((err) => {
|
|
55
|
-
const count2 = this.counts.get(topic);
|
|
56
|
-
if (count2) {
|
|
57
|
-
if (count2 <= 1)
|
|
58
|
-
this.counts.delete(topic);
|
|
59
|
-
else
|
|
60
|
-
this.counts.set(topic, count2 - 1);
|
|
61
|
-
}
|
|
62
|
-
throw err;
|
|
63
|
-
});
|
|
64
|
-
this.pending.set(topic, promise);
|
|
65
|
-
return promise;
|
|
66
|
-
} else {
|
|
67
|
-
const pending = this.pending.get(topic);
|
|
68
|
-
if (pending)
|
|
69
|
-
return pending;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
async unsubscribe(topic) {
|
|
73
|
-
const count = this.counts.get(topic);
|
|
74
|
-
if (count) {
|
|
75
|
-
if (count <= 1) {
|
|
76
|
-
this.counts.delete(topic);
|
|
77
|
-
if (this.lingerMs > 0) {
|
|
78
|
-
const timer = setTimeout(() => {
|
|
79
|
-
this.lingers.delete(topic);
|
|
80
|
-
this.unsubscribeFn(topic).catch(() => {
|
|
81
|
-
});
|
|
82
|
-
}, this.lingerMs);
|
|
83
|
-
this.lingers.set(topic, timer);
|
|
84
|
-
} else
|
|
85
|
-
await this.unsubscribeFn(topic).catch(() => {
|
|
86
|
-
});
|
|
87
|
-
} else
|
|
88
|
-
this.counts.set(topic, count - 1);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
async flush() {
|
|
92
|
-
const topics = [...this.lingers.keys()];
|
|
93
|
-
for (const topic of topics) {
|
|
94
|
-
clearTimeout(this.lingers.get(topic));
|
|
95
|
-
this.lingers.delete(topic);
|
|
96
|
-
await this.unsubscribeFn(topic).catch(() => {
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
31
|
+
const v__namespace = /* @__PURE__ */ _interopNamespaceDefault(v);
|
|
32
|
+
const CBOR__namespace = /* @__PURE__ */ _interopNamespaceDefault(CBOR);
|
|
101
33
|
class CreditGate {
|
|
102
34
|
constructor(initialCredit) {
|
|
103
35
|
this.waiters = [];
|
|
@@ -887,6 +819,14 @@ class Msg {
|
|
|
887
819
|
} else
|
|
888
820
|
throw new Error("invalid object: not of any known type");
|
|
889
821
|
}
|
|
822
|
+
/* guard for request messages */
|
|
823
|
+
isRequest(msg) {
|
|
824
|
+
return msg instanceof EventEmission || msg instanceof ServiceCallRequest || msg instanceof SourceFetchRequest || msg instanceof SinkPushRequest;
|
|
825
|
+
}
|
|
826
|
+
/* guard for response messages */
|
|
827
|
+
isResponse(msg) {
|
|
828
|
+
return msg instanceof ServiceCallResponse || msg instanceof SinkPushResponse || msg instanceof SinkPushChunk || msg instanceof SinkPushCredit || msg instanceof SourceFetchResponse || msg instanceof SourceFetchChunk || msg instanceof SourceFetchCredit;
|
|
829
|
+
}
|
|
890
830
|
}
|
|
891
831
|
class MsgTrait extends EncodeTrait {
|
|
892
832
|
constructor() {
|
|
@@ -901,6 +841,7 @@ class LogEvent {
|
|
|
901
841
|
this.msg = msg;
|
|
902
842
|
this.data = data;
|
|
903
843
|
}
|
|
844
|
+
/* resolve all pending promises in the log event */
|
|
904
845
|
async resolve() {
|
|
905
846
|
if (this.msg instanceof Promise)
|
|
906
847
|
this.msg = await this.msg.catch(() => "<resolve-failed>");
|
|
@@ -910,6 +851,7 @@ class LogEvent {
|
|
|
910
851
|
this.data[field] = await this.data[field].catch(() => "<resolve-failed>");
|
|
911
852
|
}
|
|
912
853
|
}
|
|
854
|
+
/* render log event as string */
|
|
913
855
|
toString() {
|
|
914
856
|
const timestamp = new Date(this.timestamp);
|
|
915
857
|
const year = timestamp.getFullYear();
|
|
@@ -967,6 +909,8 @@ class BaseTrait extends TraceTrait {
|
|
|
967
909
|
/* construct API class */
|
|
968
910
|
constructor(mqtt, options = {}) {
|
|
969
911
|
super(options);
|
|
912
|
+
this.onRequest = /* @__PURE__ */ new Map();
|
|
913
|
+
this.onResponse = /* @__PURE__ */ new Map();
|
|
970
914
|
if (mqtt === null) {
|
|
971
915
|
this.log("info", "establishing proxy MQTT client");
|
|
972
916
|
mqtt = new Proxy({}, {
|
|
@@ -998,7 +942,7 @@ class BaseTrait extends TraceTrait {
|
|
|
998
942
|
this.mqtt.on("message", this._messageHandler);
|
|
999
943
|
}
|
|
1000
944
|
/* destroy API class */
|
|
1001
|
-
destroy() {
|
|
945
|
+
async destroy() {
|
|
1002
946
|
this.log("info", "un-hooking from MQTT client");
|
|
1003
947
|
this.mqtt.off("message", this._messageHandler);
|
|
1004
948
|
}
|
|
@@ -1057,31 +1001,174 @@ class BaseTrait extends TraceTrait {
|
|
|
1057
1001
|
});
|
|
1058
1002
|
}
|
|
1059
1003
|
/* handle incoming MQTT message */
|
|
1060
|
-
_onMessage(topic,
|
|
1061
|
-
|
|
1062
|
-
|
|
1004
|
+
_onMessage(topic, data, packet) {
|
|
1005
|
+
const topicMatch = this.options.topicMatch(topic);
|
|
1006
|
+
if (topicMatch === null)
|
|
1007
|
+
return;
|
|
1008
|
+
if (typeof data === "string")
|
|
1009
|
+
this.log("info", `received from MQTT topic "${topic}" (type: string, length: ${data.length} chars)`);
|
|
1063
1010
|
else
|
|
1064
|
-
this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${
|
|
1065
|
-
let
|
|
1011
|
+
this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${data.byteLength} bytes)`);
|
|
1012
|
+
let payload;
|
|
1066
1013
|
try {
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
const err = _err instanceof Error ? new Error(`failed to parse message: ${_err.message}`, { cause: _err }) : new Error("failed to parse message");
|
|
1071
|
-
this.error(err);
|
|
1014
|
+
payload = this.codec.decode(data);
|
|
1015
|
+
} catch (err) {
|
|
1016
|
+
this.error(ensureError(err, "failed to parse message into object"));
|
|
1072
1017
|
return;
|
|
1073
1018
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
this.
|
|
1077
|
-
})
|
|
1019
|
+
let message;
|
|
1020
|
+
try {
|
|
1021
|
+
message = this.msg.parse(payload);
|
|
1022
|
+
} catch (err) {
|
|
1023
|
+
this.error(ensureError(err, "failed to parse object into typed message object"));
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
this.log("debug", `received from MQTT topic "${topic}"`, { message });
|
|
1027
|
+
if (this.msg.isRequest(message)) {
|
|
1028
|
+
const handler = this.onRequest.get(`${topicMatch.operation}:${message.name}`);
|
|
1029
|
+
if (handler !== void 0) {
|
|
1030
|
+
try {
|
|
1031
|
+
handler(message, topicMatch.name);
|
|
1032
|
+
} catch (err) {
|
|
1033
|
+
this.error(ensureError(err, `dispatching request message from MQTT topic "${topic}" failed`));
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
} else if (this.msg.isResponse(message)) {
|
|
1037
|
+
const handler = this.onResponse.get(`${topicMatch.operation}:${message.id}`);
|
|
1038
|
+
if (handler !== void 0) {
|
|
1039
|
+
try {
|
|
1040
|
+
handler(message, topicMatch.name);
|
|
1041
|
+
} catch (err) {
|
|
1042
|
+
this.error(ensureError(err, `dispatching response message from MQTT topic "${topic}" failed`));
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1078
1046
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1047
|
+
}
|
|
1048
|
+
class RefCountedSubscription {
|
|
1049
|
+
constructor(subscribeFn, unsubscribeFn, lingerMs = 30 * 1e3) {
|
|
1050
|
+
this.subscribeFn = subscribeFn;
|
|
1051
|
+
this.unsubscribeFn = unsubscribeFn;
|
|
1052
|
+
this.lingerMs = lingerMs;
|
|
1053
|
+
this.counts = /* @__PURE__ */ new Map();
|
|
1054
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
1055
|
+
this.lingers = /* @__PURE__ */ new Map();
|
|
1056
|
+
}
|
|
1057
|
+
/* subscribe to a topic (reference-counted) */
|
|
1058
|
+
async subscribe(topic, options = { qos: 2 }) {
|
|
1059
|
+
const count = this.counts.get(topic) ?? 0;
|
|
1060
|
+
this.counts.set(topic, count + 1);
|
|
1061
|
+
const linger = this.lingers.get(topic);
|
|
1062
|
+
if (linger) {
|
|
1063
|
+
clearTimeout(linger);
|
|
1064
|
+
this.lingers.delete(topic);
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
if (count === 0) {
|
|
1068
|
+
const promise = this.subscribeFn(topic, options).finally(() => {
|
|
1069
|
+
this.pending.delete(topic);
|
|
1070
|
+
}).catch((err) => {
|
|
1071
|
+
const count2 = this.counts.get(topic);
|
|
1072
|
+
if (count2) {
|
|
1073
|
+
if (count2 <= 1)
|
|
1074
|
+
this.counts.delete(topic);
|
|
1075
|
+
else
|
|
1076
|
+
this.counts.set(topic, count2 - 1);
|
|
1077
|
+
}
|
|
1078
|
+
throw err;
|
|
1079
|
+
});
|
|
1080
|
+
this.pending.set(topic, promise);
|
|
1081
|
+
return promise;
|
|
1082
|
+
} else {
|
|
1083
|
+
const pending = this.pending.get(topic);
|
|
1084
|
+
if (pending)
|
|
1085
|
+
return pending.catch((err) => {
|
|
1086
|
+
const count2 = this.counts.get(topic);
|
|
1087
|
+
if (count2) {
|
|
1088
|
+
if (count2 <= 1)
|
|
1089
|
+
this.counts.delete(topic);
|
|
1090
|
+
else
|
|
1091
|
+
this.counts.set(topic, count2 - 1);
|
|
1092
|
+
}
|
|
1093
|
+
throw err;
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
/* unsubscribe from a topic (reference-counted) */
|
|
1098
|
+
async unsubscribe(topic) {
|
|
1099
|
+
const count = this.counts.get(topic);
|
|
1100
|
+
if (count) {
|
|
1101
|
+
if (count <= 1) {
|
|
1102
|
+
this.counts.delete(topic);
|
|
1103
|
+
if (this.lingerMs > 0) {
|
|
1104
|
+
const timer = setTimeout(() => {
|
|
1105
|
+
this.lingers.delete(topic);
|
|
1106
|
+
this.unsubscribeFn(topic).catch(() => {
|
|
1107
|
+
});
|
|
1108
|
+
}, this.lingerMs);
|
|
1109
|
+
this.lingers.set(topic, timer);
|
|
1110
|
+
} else
|
|
1111
|
+
await this.unsubscribeFn(topic).catch(() => {
|
|
1112
|
+
});
|
|
1113
|
+
} else
|
|
1114
|
+
this.counts.set(topic, count - 1);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
/* flush all pending linger timers and unsubscribe */
|
|
1118
|
+
async flush() {
|
|
1119
|
+
const topics = [...this.lingers.keys()];
|
|
1120
|
+
for (const topic of topics) {
|
|
1121
|
+
clearTimeout(this.lingers.get(topic));
|
|
1122
|
+
this.lingers.delete(topic);
|
|
1123
|
+
}
|
|
1124
|
+
for (const topic of topics)
|
|
1125
|
+
await this.unsubscribeFn(topic).catch(() => {
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
class SubscriptionTrait extends BaseTrait {
|
|
1130
|
+
constructor() {
|
|
1131
|
+
super(...arguments);
|
|
1132
|
+
this.subscriptions = new RefCountedSubscription((topic, options) => this._subscribeTopic(topic, options), (topic) => this._unsubscribeTopic(topic));
|
|
1133
|
+
}
|
|
1134
|
+
/* destroy topic trait */
|
|
1135
|
+
async destroy() {
|
|
1136
|
+
await this.subscriptions.flush();
|
|
1137
|
+
await super.destroy();
|
|
1082
1138
|
}
|
|
1083
1139
|
}
|
|
1084
|
-
class
|
|
1140
|
+
class TimerTrait extends SubscriptionTrait {
|
|
1141
|
+
constructor() {
|
|
1142
|
+
super(...arguments);
|
|
1143
|
+
this.timers = /* @__PURE__ */ new Map();
|
|
1144
|
+
}
|
|
1145
|
+
/* destroy timer trait */
|
|
1146
|
+
async destroy() {
|
|
1147
|
+
for (const timer of this.timers.values())
|
|
1148
|
+
clearTimeout(timer);
|
|
1149
|
+
this.timers.clear();
|
|
1150
|
+
await super.destroy();
|
|
1151
|
+
}
|
|
1152
|
+
/* refresh (or start) a named timer */
|
|
1153
|
+
timerRefresh(id, onTimeout) {
|
|
1154
|
+
const timer = this.timers.get(id);
|
|
1155
|
+
if (timer !== void 0)
|
|
1156
|
+
clearTimeout(timer);
|
|
1157
|
+
this.timers.set(id, setTimeout(() => {
|
|
1158
|
+
this.timers.delete(id);
|
|
1159
|
+
onTimeout();
|
|
1160
|
+
}, this.options.timeout));
|
|
1161
|
+
}
|
|
1162
|
+
/* clear a named timer */
|
|
1163
|
+
timerClear(id) {
|
|
1164
|
+
const timer = this.timers.get(id);
|
|
1165
|
+
if (timer !== void 0) {
|
|
1166
|
+
clearTimeout(timer);
|
|
1167
|
+
this.timers.delete(id);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
class MetaTrait extends TimerTrait {
|
|
1085
1172
|
constructor() {
|
|
1086
1173
|
super(...arguments);
|
|
1087
1174
|
this._meta = /* @__PURE__ */ new Map();
|
|
@@ -1188,10 +1275,6 @@ class AuthTrait extends MetaTrait {
|
|
|
1188
1275
|
}
|
|
1189
1276
|
}
|
|
1190
1277
|
class EventTrait extends AuthTrait {
|
|
1191
|
-
constructor() {
|
|
1192
|
-
super(...arguments);
|
|
1193
|
-
this.events = /* @__PURE__ */ new Map();
|
|
1194
|
-
}
|
|
1195
1278
|
async event(nameOrConfig, ...args) {
|
|
1196
1279
|
let name;
|
|
1197
1280
|
let callback;
|
|
@@ -1209,12 +1292,12 @@ class EventTrait extends AuthTrait {
|
|
|
1209
1292
|
callback = args[0];
|
|
1210
1293
|
}
|
|
1211
1294
|
const spool = new Spool();
|
|
1212
|
-
if (this.
|
|
1295
|
+
if (this.onRequest.has(`event-emission:${name}`))
|
|
1213
1296
|
throw new Error(`event: event "${name}" already registered`);
|
|
1214
1297
|
const topicS = share ? `$share/${share}/${name}` : name;
|
|
1215
1298
|
const topicB = this.options.topicMake(topicS, "event-emission");
|
|
1216
1299
|
const topicD = this.options.topicMake(name, "event-emission", this.options.id);
|
|
1217
|
-
this.
|
|
1300
|
+
this.onRequest.set(`event-emission:${name}`, (request, topicName) => {
|
|
1218
1301
|
const senderId = request.sender;
|
|
1219
1302
|
const params = request.params ?? [];
|
|
1220
1303
|
const info = { sender: senderId ?? "" };
|
|
@@ -1236,7 +1319,7 @@ class EventTrait extends AuthTrait {
|
|
|
1236
1319
|
});
|
|
1237
1320
|
});
|
|
1238
1321
|
spool.roll(() => {
|
|
1239
|
-
this.
|
|
1322
|
+
this.onRequest.delete(`event-emission:${name}`);
|
|
1240
1323
|
});
|
|
1241
1324
|
await run(`subscribe to MQTT topic "${topicB}"`, spool, () => this._subscribeTopic(topicB, { qos: 2, ...options }));
|
|
1242
1325
|
spool.roll(() => this._unsubscribeTopic(topicB).catch(() => {
|
|
@@ -1246,7 +1329,7 @@ class EventTrait extends AuthTrait {
|
|
|
1246
1329
|
}));
|
|
1247
1330
|
return {
|
|
1248
1331
|
destroy: async () => {
|
|
1249
|
-
if (!this.
|
|
1332
|
+
if (!this.onRequest.has(`event-emission:${name}`))
|
|
1250
1333
|
throw new Error(`destroy: event "${name}" not registered`);
|
|
1251
1334
|
await spool.unroll(false)?.catch((err) => {
|
|
1252
1335
|
this.error(err, `destroy: failed to cleanup: ${err.message}`);
|
|
@@ -1259,14 +1342,14 @@ class EventTrait extends AuthTrait {
|
|
|
1259
1342
|
let params;
|
|
1260
1343
|
let receiver;
|
|
1261
1344
|
let options = {};
|
|
1262
|
-
let meta
|
|
1345
|
+
let meta;
|
|
1263
1346
|
let dry;
|
|
1264
1347
|
if (typeof eventOrConfig === "object" && eventOrConfig !== null) {
|
|
1265
1348
|
event = eventOrConfig.event;
|
|
1266
1349
|
params = eventOrConfig.params;
|
|
1267
1350
|
receiver = eventOrConfig.receiver;
|
|
1268
1351
|
options = eventOrConfig.options ?? {};
|
|
1269
|
-
meta = eventOrConfig.meta
|
|
1352
|
+
meta = eventOrConfig.meta;
|
|
1270
1353
|
dry = eventOrConfig.dry;
|
|
1271
1354
|
} else {
|
|
1272
1355
|
event = eventOrConfig;
|
|
@@ -1285,29 +1368,8 @@ class EventTrait extends AuthTrait {
|
|
|
1285
1368
|
this.error(err, `emitting event "${event}" failed`);
|
|
1286
1369
|
});
|
|
1287
1370
|
}
|
|
1288
|
-
/* dispatch message (Event pattern handling) */
|
|
1289
|
-
async _dispatchMessage(topic, message) {
|
|
1290
|
-
await super._dispatchMessage(topic, message);
|
|
1291
|
-
const topicMatch = this.options.topicMatch(topic);
|
|
1292
|
-
if (topicMatch !== null && topicMatch.operation === "event-emission" && message instanceof EventEmission) {
|
|
1293
|
-
const handler = this.events.get(message.name);
|
|
1294
|
-
if (handler !== void 0)
|
|
1295
|
-
handler(message, topicMatch.name);
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
1371
|
}
|
|
1299
1372
|
class ServiceTrait extends EventTrait {
|
|
1300
|
-
constructor() {
|
|
1301
|
-
super(...arguments);
|
|
1302
|
-
this.services = /* @__PURE__ */ new Map();
|
|
1303
|
-
this.callCallbacks = /* @__PURE__ */ new Map();
|
|
1304
|
-
this.callSubscriptions = new RefCountedSubscription((topic, options) => this._subscribeTopic(topic, options), (topic) => this._unsubscribeTopic(topic));
|
|
1305
|
-
}
|
|
1306
|
-
/* destroy service trait */
|
|
1307
|
-
destroy() {
|
|
1308
|
-
super.destroy();
|
|
1309
|
-
this.callSubscriptions.flush();
|
|
1310
|
-
}
|
|
1311
1373
|
async service(nameOrConfig, ...args) {
|
|
1312
1374
|
let name;
|
|
1313
1375
|
let callback;
|
|
@@ -1325,12 +1387,12 @@ class ServiceTrait extends EventTrait {
|
|
|
1325
1387
|
callback = args[0];
|
|
1326
1388
|
}
|
|
1327
1389
|
const spool = new Spool();
|
|
1328
|
-
if (this.
|
|
1329
|
-
throw new Error(`
|
|
1390
|
+
if (this.onRequest.has(`service-call-request:${name}`))
|
|
1391
|
+
throw new Error(`service: service "${name}" already registered`);
|
|
1330
1392
|
const topicS = `$share/${share}/${name}`;
|
|
1331
1393
|
const topicB = this.options.topicMake(topicS, "service-call-request");
|
|
1332
1394
|
const topicD = this.options.topicMake(name, "service-call-request", this.options.id);
|
|
1333
|
-
this.
|
|
1395
|
+
this.onRequest.set(`service-call-request:${name}`, (request, topicName) => {
|
|
1334
1396
|
const requestId = request.id;
|
|
1335
1397
|
const senderId = request.sender;
|
|
1336
1398
|
if (senderId === void 0 || senderId === "")
|
|
@@ -1364,7 +1426,7 @@ class ServiceTrait extends EventTrait {
|
|
|
1364
1426
|
});
|
|
1365
1427
|
});
|
|
1366
1428
|
spool.roll(() => {
|
|
1367
|
-
this.
|
|
1429
|
+
this.onRequest.delete(`service-call-request:${name}`);
|
|
1368
1430
|
});
|
|
1369
1431
|
await run(`subscribe to MQTT topic "${topicB}"`, spool, () => this._subscribeTopic(topicB, { qos: 2, ...options }));
|
|
1370
1432
|
spool.roll(() => this._unsubscribeTopic(topicB).catch(() => {
|
|
@@ -1374,8 +1436,8 @@ class ServiceTrait extends EventTrait {
|
|
|
1374
1436
|
}));
|
|
1375
1437
|
return {
|
|
1376
1438
|
destroy: async () => {
|
|
1377
|
-
if (!this.
|
|
1378
|
-
throw new Error(`destroy: service "${name}"
|
|
1439
|
+
if (!this.onRequest.has(`service-call-request:${name}`))
|
|
1440
|
+
throw new Error(`destroy: service "${name}" not registered`);
|
|
1379
1441
|
await spool.unroll(false)?.catch((err) => {
|
|
1380
1442
|
this.error(err, `destroy: failed to cleanup: ${err.message}`);
|
|
1381
1443
|
});
|
|
@@ -1387,13 +1449,13 @@ class ServiceTrait extends EventTrait {
|
|
|
1387
1449
|
let params;
|
|
1388
1450
|
let receiver;
|
|
1389
1451
|
let options = {};
|
|
1390
|
-
let meta
|
|
1452
|
+
let meta;
|
|
1391
1453
|
if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
|
|
1392
1454
|
name = nameOrConfig.name;
|
|
1393
1455
|
params = nameOrConfig.params;
|
|
1394
1456
|
receiver = nameOrConfig.receiver;
|
|
1395
1457
|
options = nameOrConfig.options ?? {};
|
|
1396
|
-
meta = nameOrConfig.meta
|
|
1458
|
+
meta = nameOrConfig.meta;
|
|
1397
1459
|
} else {
|
|
1398
1460
|
name = nameOrConfig;
|
|
1399
1461
|
params = args;
|
|
@@ -1401,8 +1463,8 @@ class ServiceTrait extends EventTrait {
|
|
|
1401
1463
|
const spool = new Spool();
|
|
1402
1464
|
const requestId = nanoid.nanoid();
|
|
1403
1465
|
const responseTopic = this.options.topicMake(name, "service-call-response", this.options.id);
|
|
1404
|
-
await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.
|
|
1405
|
-
spool.roll(() => this.
|
|
1466
|
+
await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
|
|
1467
|
+
spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
|
|
1406
1468
|
const promise = new Promise((resolve, reject) => {
|
|
1407
1469
|
let timer = setTimeout(async () => {
|
|
1408
1470
|
timer = null;
|
|
@@ -1415,7 +1477,7 @@ class ServiceTrait extends EventTrait {
|
|
|
1415
1477
|
timer = null;
|
|
1416
1478
|
}
|
|
1417
1479
|
});
|
|
1418
|
-
this.
|
|
1480
|
+
this.onResponse.set(`service-call-response:${requestId}`, async (response) => {
|
|
1419
1481
|
await spool.unroll();
|
|
1420
1482
|
if (response.error !== void 0)
|
|
1421
1483
|
reject(new Error(response.error));
|
|
@@ -1423,7 +1485,7 @@ class ServiceTrait extends EventTrait {
|
|
|
1423
1485
|
resolve(response.result);
|
|
1424
1486
|
});
|
|
1425
1487
|
spool.roll(() => {
|
|
1426
|
-
this.
|
|
1488
|
+
this.onResponse.delete(`service-call-response:${requestId}`);
|
|
1427
1489
|
});
|
|
1428
1490
|
});
|
|
1429
1491
|
const auth = this.authenticate();
|
|
@@ -1434,56 +1496,11 @@ class ServiceTrait extends EventTrait {
|
|
|
1434
1496
|
await run(`publish service request as MQTT message to topic "${topic}"`, spool, () => this._publishToTopic(topic, message, { qos: 2, ...options }));
|
|
1435
1497
|
return promise;
|
|
1436
1498
|
}
|
|
1437
|
-
/* dispatch message (Service pattern handling) */
|
|
1438
|
-
async _dispatchMessage(topic, message) {
|
|
1439
|
-
await super._dispatchMessage(topic, message);
|
|
1440
|
-
const topicMatch = this.options.topicMatch(topic);
|
|
1441
|
-
if (topicMatch !== null && topicMatch.operation === "service-call-request" && message instanceof ServiceCallRequest) {
|
|
1442
|
-
const handler = this.services.get(message.name);
|
|
1443
|
-
if (handler !== void 0)
|
|
1444
|
-
handler(message, topicMatch.name);
|
|
1445
|
-
} else if (topicMatch !== null && topicMatch.operation === "service-call-response" && topicMatch.peerId === this.options.id && message instanceof ServiceCallResponse) {
|
|
1446
|
-
const handler = this.callCallbacks.get(message.id);
|
|
1447
|
-
if (handler !== void 0)
|
|
1448
|
-
handler(message);
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
1499
|
}
|
|
1452
1500
|
class SourceTrait extends ServiceTrait {
|
|
1453
1501
|
constructor() {
|
|
1454
1502
|
super(...arguments);
|
|
1455
|
-
this.sources = /* @__PURE__ */ new Map();
|
|
1456
|
-
this.fetchResponseCallbacks = /* @__PURE__ */ new Map();
|
|
1457
|
-
this.fetchChunkCallbacks = /* @__PURE__ */ new Map();
|
|
1458
|
-
this.sourceCreditCallbacks = /* @__PURE__ */ new Map();
|
|
1459
1503
|
this.sourceCreditGates = /* @__PURE__ */ new Map();
|
|
1460
|
-
this.sourceTimers = /* @__PURE__ */ new Map();
|
|
1461
|
-
this.fetchSubscriptions = new RefCountedSubscription((topic, options) => this._subscribeTopic(topic, options), (topic) => this._unsubscribeTopic(topic));
|
|
1462
|
-
}
|
|
1463
|
-
/* refresh source timer for a specific request */
|
|
1464
|
-
_refreshSourceTimer(requestId) {
|
|
1465
|
-
const timer = this.sourceTimers.get(requestId);
|
|
1466
|
-
if (timer !== void 0)
|
|
1467
|
-
clearTimeout(timer);
|
|
1468
|
-
this.sourceTimers.set(requestId, setTimeout(() => {
|
|
1469
|
-
this.sourceTimers.delete(requestId);
|
|
1470
|
-
const gate = this.sourceCreditGates.get(requestId);
|
|
1471
|
-
if (gate !== void 0)
|
|
1472
|
-
gate.abort();
|
|
1473
|
-
}, this.options.timeout));
|
|
1474
|
-
}
|
|
1475
|
-
/* clear source timer for a specific request */
|
|
1476
|
-
_clearSourceTimer(requestId) {
|
|
1477
|
-
const timer = this.sourceTimers.get(requestId);
|
|
1478
|
-
if (timer !== void 0) {
|
|
1479
|
-
clearTimeout(timer);
|
|
1480
|
-
this.sourceTimers.delete(requestId);
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
/* destroy source trait */
|
|
1484
|
-
destroy() {
|
|
1485
|
-
super.destroy();
|
|
1486
|
-
this.fetchSubscriptions.flush();
|
|
1487
1504
|
}
|
|
1488
1505
|
async source(nameOrConfig, ...args) {
|
|
1489
1506
|
let name;
|
|
@@ -1502,13 +1519,13 @@ class SourceTrait extends ServiceTrait {
|
|
|
1502
1519
|
callback = args[0];
|
|
1503
1520
|
}
|
|
1504
1521
|
const spool = new Spool();
|
|
1505
|
-
if (this.
|
|
1522
|
+
if (this.onRequest.has(`source-fetch-request:${name}`))
|
|
1506
1523
|
throw new Error(`source: source "${name}" already established`);
|
|
1507
1524
|
const topicS = `$share/${share}/${name}`;
|
|
1508
1525
|
const topicReqB = this.options.topicMake(topicS, "source-fetch-request");
|
|
1509
1526
|
const topicReqD = this.options.topicMake(name, "source-fetch-request", this.options.id);
|
|
1510
1527
|
const topicCreditD = this.options.topicMake(name, "source-fetch-credit", this.options.id);
|
|
1511
|
-
this.
|
|
1528
|
+
this.onRequest.set(`source-fetch-request:${name}`, (request, topicName) => {
|
|
1512
1529
|
const requestId = request.id;
|
|
1513
1530
|
const params = request.params ?? [];
|
|
1514
1531
|
const sender = request.sender;
|
|
@@ -1529,8 +1546,12 @@ class SourceTrait extends ServiceTrait {
|
|
|
1529
1546
|
const message = this.codec.encode(response);
|
|
1530
1547
|
await this._publishToTopic(responseTopic, message, { qos: 2 });
|
|
1531
1548
|
};
|
|
1532
|
-
const refreshSourceTimeout = () => this.
|
|
1533
|
-
|
|
1549
|
+
const refreshSourceTimeout = () => this.timerRefresh(requestId, () => {
|
|
1550
|
+
const gate = this.sourceCreditGates.get(requestId);
|
|
1551
|
+
if (gate !== void 0)
|
|
1552
|
+
gate.abort();
|
|
1553
|
+
});
|
|
1554
|
+
const clearSourceTimeout = () => this.timerClear(requestId);
|
|
1534
1555
|
refreshSourceTimeout();
|
|
1535
1556
|
const sendChunk = async (chunk, error, final) => {
|
|
1536
1557
|
refreshSourceTimeout();
|
|
@@ -1542,9 +1563,9 @@ class SourceTrait extends ServiceTrait {
|
|
|
1542
1563
|
const creditGate = initialCredit !== void 0 && initialCredit > 0 ? new CreditGate(initialCredit) : void 0;
|
|
1543
1564
|
if (creditGate) {
|
|
1544
1565
|
this.sourceCreditGates.set(requestId, creditGate);
|
|
1545
|
-
this.
|
|
1566
|
+
this.onResponse.set(`source-fetch-credit:${requestId}`, (creditParsed) => {
|
|
1546
1567
|
creditGate.replenish(creditParsed.credit);
|
|
1547
|
-
|
|
1568
|
+
refreshSourceTimeout();
|
|
1548
1569
|
});
|
|
1549
1570
|
}
|
|
1550
1571
|
let ackSent = false;
|
|
@@ -1582,11 +1603,11 @@ class SourceTrait extends ServiceTrait {
|
|
|
1582
1603
|
creditGate.abort();
|
|
1583
1604
|
this.sourceCreditGates.delete(requestId);
|
|
1584
1605
|
}
|
|
1585
|
-
this.
|
|
1606
|
+
this.onResponse.delete(`source-fetch-credit:${requestId}`);
|
|
1586
1607
|
});
|
|
1587
1608
|
});
|
|
1588
1609
|
spool.roll(() => {
|
|
1589
|
-
this.
|
|
1610
|
+
this.onRequest.delete(`source-fetch-request:${name}`);
|
|
1590
1611
|
});
|
|
1591
1612
|
await run(`subscribe to MQTT topic "${topicReqB}"`, spool, () => this._subscribeTopic(topicReqB, { qos: 2, ...options }));
|
|
1592
1613
|
spool.roll(() => this._unsubscribeTopic(topicReqB).catch(() => {
|
|
@@ -1599,7 +1620,7 @@ class SourceTrait extends ServiceTrait {
|
|
|
1599
1620
|
}));
|
|
1600
1621
|
return {
|
|
1601
1622
|
destroy: async () => {
|
|
1602
|
-
if (!this.
|
|
1623
|
+
if (!this.onRequest.has(`source-fetch-request:${name}`))
|
|
1603
1624
|
throw new Error(`destroy: source "${name}" not established`);
|
|
1604
1625
|
await spool.unroll(false)?.catch((err) => {
|
|
1605
1626
|
this.error(err, `destroy: failed to cleanup: ${err.message}`);
|
|
@@ -1627,10 +1648,10 @@ class SourceTrait extends ServiceTrait {
|
|
|
1627
1648
|
const requestId = nanoid.nanoid();
|
|
1628
1649
|
const responseTopic = this.options.topicMake(name, "source-fetch-response", this.options.id);
|
|
1629
1650
|
const chunkTopic = this.options.topicMake(name, "source-fetch-chunk", this.options.id);
|
|
1630
|
-
await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.
|
|
1631
|
-
spool.roll(() => this.
|
|
1632
|
-
await run(`subscribe to MQTT topic "${chunkTopic}"`, spool, () => this.
|
|
1633
|
-
spool.roll(() => this.
|
|
1651
|
+
await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
|
|
1652
|
+
spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
|
|
1653
|
+
await run(`subscribe to MQTT topic "${chunkTopic}"`, spool, () => this.subscriptions.subscribe(chunkTopic, { qos: options.qos ?? 2 }));
|
|
1654
|
+
spool.roll(() => this.subscriptions.unsubscribe(chunkTopic));
|
|
1634
1655
|
const chunkCredit = this.options.chunkCredit;
|
|
1635
1656
|
let chunksReceived = 0;
|
|
1636
1657
|
let creditGranted = chunkCredit;
|
|
@@ -1638,7 +1659,7 @@ class SourceTrait extends ServiceTrait {
|
|
|
1638
1659
|
const stream = new node_stream.Readable({
|
|
1639
1660
|
highWaterMark: chunkCredit > 0 ? chunkCredit * this.options.chunkSize : 16 * 1024,
|
|
1640
1661
|
read: (_size) => {
|
|
1641
|
-
if (chunkCredit <= 0 || !this.
|
|
1662
|
+
if (chunkCredit <= 0 || !this.onResponse.has(`source-fetch-chunk:${requestId}`))
|
|
1642
1663
|
return;
|
|
1643
1664
|
const targetId = serverId ?? receiver;
|
|
1644
1665
|
if (!targetId)
|
|
@@ -1681,7 +1702,7 @@ class SourceTrait extends ServiceTrait {
|
|
|
1681
1702
|
refreshTimeout();
|
|
1682
1703
|
stream.once("close", () => spool.unroll());
|
|
1683
1704
|
stream.once("error", () => spool.unroll());
|
|
1684
|
-
this.
|
|
1705
|
+
this.onResponse.set(`source-fetch-response:${requestId}`, (response) => {
|
|
1685
1706
|
if (response.sender)
|
|
1686
1707
|
serverId = response.sender;
|
|
1687
1708
|
metaResolve?.(response.meta);
|
|
@@ -1691,7 +1712,7 @@ class SourceTrait extends ServiceTrait {
|
|
|
1691
1712
|
} else
|
|
1692
1713
|
refreshTimeout();
|
|
1693
1714
|
});
|
|
1694
|
-
this.
|
|
1715
|
+
this.onResponse.set(`source-fetch-chunk:${requestId}`, (response) => {
|
|
1695
1716
|
if (response.sender)
|
|
1696
1717
|
serverId = response.sender;
|
|
1697
1718
|
if (response.error) {
|
|
@@ -1710,8 +1731,8 @@ class SourceTrait extends ServiceTrait {
|
|
|
1710
1731
|
}
|
|
1711
1732
|
});
|
|
1712
1733
|
spool.roll(() => {
|
|
1713
|
-
this.
|
|
1714
|
-
this.
|
|
1734
|
+
this.onResponse.delete(`source-fetch-response:${requestId}`);
|
|
1735
|
+
this.onResponse.delete(`source-fetch-chunk:${requestId}`);
|
|
1715
1736
|
});
|
|
1716
1737
|
const auth = this.authenticate();
|
|
1717
1738
|
const metaStore = this.metaStore(meta);
|
|
@@ -1727,67 +1748,12 @@ class SourceTrait extends ServiceTrait {
|
|
|
1727
1748
|
makeMutuallyExclusiveFields(result, "stream", "buffer");
|
|
1728
1749
|
return result;
|
|
1729
1750
|
}
|
|
1730
|
-
/* dispatch message (Source Fetch pattern handling) */
|
|
1731
|
-
async _dispatchMessage(topic, message) {
|
|
1732
|
-
await super._dispatchMessage(topic, message);
|
|
1733
|
-
const topicMatch = this.options.topicMatch(topic);
|
|
1734
|
-
if (topicMatch !== null && topicMatch.operation === "source-fetch-request" && message instanceof SourceFetchRequest) {
|
|
1735
|
-
const handler = this.sources.get(message.name);
|
|
1736
|
-
if (handler !== void 0)
|
|
1737
|
-
handler(message, topicMatch.name);
|
|
1738
|
-
} else if (topicMatch !== null && topicMatch.operation === "source-fetch-response" && message instanceof SourceFetchResponse) {
|
|
1739
|
-
const handler = this.fetchResponseCallbacks.get(message.id);
|
|
1740
|
-
if (handler !== void 0)
|
|
1741
|
-
handler(message);
|
|
1742
|
-
} else if (topicMatch !== null && topicMatch.operation === "source-fetch-chunk" && message instanceof SourceFetchChunk) {
|
|
1743
|
-
const handler = this.fetchChunkCallbacks.get(message.id);
|
|
1744
|
-
if (handler !== void 0)
|
|
1745
|
-
handler(message);
|
|
1746
|
-
} else if (topicMatch !== null && topicMatch.operation === "source-fetch-credit" && message instanceof SourceFetchCredit) {
|
|
1747
|
-
const handler = this.sourceCreditCallbacks.get(message.id);
|
|
1748
|
-
if (handler !== void 0)
|
|
1749
|
-
handler(message);
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
1751
|
}
|
|
1753
1752
|
class SinkTrait extends SourceTrait {
|
|
1754
1753
|
constructor() {
|
|
1755
1754
|
super(...arguments);
|
|
1756
|
-
this.sinks = /* @__PURE__ */ new Map();
|
|
1757
1755
|
this.pushStreams = /* @__PURE__ */ new Map();
|
|
1758
1756
|
this.pushSpools = /* @__PURE__ */ new Map();
|
|
1759
|
-
this.pushTimers = /* @__PURE__ */ new Map();
|
|
1760
|
-
this.pushChunkCallbacks = /* @__PURE__ */ new Map();
|
|
1761
|
-
this.pushResponseCallbacks = /* @__PURE__ */ new Map();
|
|
1762
|
-
this.pushCreditCallbacks = /* @__PURE__ */ new Map();
|
|
1763
|
-
this.pushSubscriptions = new RefCountedSubscription((topic, options) => this._subscribeTopic(topic, options), (topic) => this._unsubscribeTopic(topic));
|
|
1764
|
-
}
|
|
1765
|
-
/* destroy sink trait */
|
|
1766
|
-
destroy() {
|
|
1767
|
-
super.destroy();
|
|
1768
|
-
this.pushSubscriptions.flush();
|
|
1769
|
-
}
|
|
1770
|
-
/* refresh push timer for a specific request */
|
|
1771
|
-
_refreshPushTimer(requestId) {
|
|
1772
|
-
const timer = this.pushTimers.get(requestId);
|
|
1773
|
-
if (timer !== void 0)
|
|
1774
|
-
clearTimeout(timer);
|
|
1775
|
-
this.pushTimers.set(requestId, setTimeout(() => {
|
|
1776
|
-
this.pushTimers.delete(requestId);
|
|
1777
|
-
const stream = this.pushStreams.get(requestId);
|
|
1778
|
-
if (stream !== void 0)
|
|
1779
|
-
stream.destroy(new Error("push stream timeout"));
|
|
1780
|
-
const spool = this.pushSpools.get(requestId);
|
|
1781
|
-
spool?.unroll();
|
|
1782
|
-
}, this.options.timeout));
|
|
1783
|
-
}
|
|
1784
|
-
/* clear push timer for a specific request */
|
|
1785
|
-
_clearPushTimer(requestId) {
|
|
1786
|
-
const timer = this.pushTimers.get(requestId);
|
|
1787
|
-
if (timer !== void 0) {
|
|
1788
|
-
clearTimeout(timer);
|
|
1789
|
-
this.pushTimers.delete(requestId);
|
|
1790
|
-
}
|
|
1791
1757
|
}
|
|
1792
1758
|
async sink(nameOrConfig, ...args) {
|
|
1793
1759
|
let name;
|
|
@@ -1806,13 +1772,13 @@ class SinkTrait extends SourceTrait {
|
|
|
1806
1772
|
callback = args[0];
|
|
1807
1773
|
}
|
|
1808
1774
|
const spool = new Spool();
|
|
1809
|
-
if (this.
|
|
1775
|
+
if (this.onRequest.has(`sink-push-request:${name}`))
|
|
1810
1776
|
throw new Error(`sink: sink "${name}" already established`);
|
|
1811
1777
|
const topicS = `$share/${share}/${name}`;
|
|
1812
1778
|
const topicReqB = this.options.topicMake(topicS, "sink-push-request");
|
|
1813
1779
|
const topicReqD = this.options.topicMake(name, "sink-push-request", this.options.id);
|
|
1814
1780
|
const topicChunkD = this.options.topicMake(name, "sink-push-chunk", this.options.id);
|
|
1815
|
-
this.
|
|
1781
|
+
this.onRequest.set(`sink-push-request:${name}`, (request, topicName) => {
|
|
1816
1782
|
const requestId = request.id;
|
|
1817
1783
|
const params = request.params ?? [];
|
|
1818
1784
|
const sender = request.sender;
|
|
@@ -1850,8 +1816,14 @@ class SinkTrait extends SourceTrait {
|
|
|
1850
1816
|
chunksReceived: 0,
|
|
1851
1817
|
creditGranted: chunkCredit
|
|
1852
1818
|
} : void 0;
|
|
1853
|
-
const refreshPushTimeout = () => this.
|
|
1854
|
-
|
|
1819
|
+
const refreshPushTimeout = () => this.timerRefresh(requestId, () => {
|
|
1820
|
+
const stream = this.pushStreams.get(requestId);
|
|
1821
|
+
if (stream !== void 0)
|
|
1822
|
+
stream.destroy(new Error("push stream timeout"));
|
|
1823
|
+
const spool2 = this.pushSpools.get(requestId);
|
|
1824
|
+
spool2?.unroll();
|
|
1825
|
+
});
|
|
1826
|
+
const clearPushTimeout = () => this.timerClear(requestId);
|
|
1855
1827
|
const readable = new node_stream.Readable({
|
|
1856
1828
|
highWaterMark: chunkCredit > 0 ? chunkCredit * this.options.chunkSize : 16 * 1024,
|
|
1857
1829
|
read: (_size) => {
|
|
@@ -1876,7 +1848,7 @@ class SinkTrait extends SourceTrait {
|
|
|
1876
1848
|
});
|
|
1877
1849
|
readable.once("close", () => reqSpool.unroll());
|
|
1878
1850
|
readable.once("error", () => reqSpool.unroll());
|
|
1879
|
-
this.
|
|
1851
|
+
this.onResponse.set(`sink-push-chunk:${requestId}`, (chunkParsed, chunkTopicName) => {
|
|
1880
1852
|
if (chunkTopicName !== chunkParsed.name)
|
|
1881
1853
|
throw new Error(`sink name mismatch between topic "${chunkTopicName}" and payload "${chunkParsed.name}"`);
|
|
1882
1854
|
if (chunkParsed.error !== void 0) {
|
|
@@ -1896,7 +1868,7 @@ class SinkTrait extends SourceTrait {
|
|
|
1896
1868
|
}
|
|
1897
1869
|
});
|
|
1898
1870
|
reqSpool.roll(() => {
|
|
1899
|
-
this.
|
|
1871
|
+
this.onResponse.delete(`sink-push-chunk:${requestId}`);
|
|
1900
1872
|
});
|
|
1901
1873
|
refreshPushTimeout();
|
|
1902
1874
|
reqSpool.roll(() => {
|
|
@@ -1919,7 +1891,7 @@ class SinkTrait extends SourceTrait {
|
|
|
1919
1891
|
});
|
|
1920
1892
|
});
|
|
1921
1893
|
spool.roll(() => {
|
|
1922
|
-
this.
|
|
1894
|
+
this.onRequest.delete(`sink-push-request:${name}`);
|
|
1923
1895
|
});
|
|
1924
1896
|
await run(`subscribe to MQTT topic "${topicReqB}"`, spool, () => this._subscribeTopic(topicReqB, { qos: 2, ...options }));
|
|
1925
1897
|
spool.roll(() => this._unsubscribeTopic(topicReqB).catch(() => {
|
|
@@ -1932,7 +1904,7 @@ class SinkTrait extends SourceTrait {
|
|
|
1932
1904
|
}));
|
|
1933
1905
|
return {
|
|
1934
1906
|
destroy: async () => {
|
|
1935
|
-
if (!this.
|
|
1907
|
+
if (!this.onRequest.has(`sink-push-request:${name}`))
|
|
1936
1908
|
throw new Error(`destroy: sink "${name}" not established`);
|
|
1937
1909
|
await spool.unroll(false)?.catch((err) => {
|
|
1938
1910
|
this.error(err, `destroy: failed to cleanup: ${err.message}`);
|
|
@@ -1962,8 +1934,8 @@ class SinkTrait extends SourceTrait {
|
|
|
1962
1934
|
const spool = new Spool();
|
|
1963
1935
|
const requestId = nanoid.nanoid();
|
|
1964
1936
|
const responseTopic = this.options.topicMake(name, "sink-push-response", this.options.id);
|
|
1965
|
-
await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.
|
|
1966
|
-
spool.roll(() => this.
|
|
1937
|
+
await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () => this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }));
|
|
1938
|
+
spool.roll(() => this.subscriptions.unsubscribe(responseTopic));
|
|
1967
1939
|
const abortController = new AbortController();
|
|
1968
1940
|
const abortSignal = abortController.signal;
|
|
1969
1941
|
let timer = null;
|
|
@@ -1993,7 +1965,7 @@ class SinkTrait extends SourceTrait {
|
|
|
1993
1965
|
spool.roll(() => {
|
|
1994
1966
|
abortSignal.removeEventListener("abort", onAbort);
|
|
1995
1967
|
});
|
|
1996
|
-
this.
|
|
1968
|
+
this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
|
|
1997
1969
|
if (response.error)
|
|
1998
1970
|
reject(new Error(response.error));
|
|
1999
1971
|
else {
|
|
@@ -2004,13 +1976,13 @@ class SinkTrait extends SourceTrait {
|
|
|
2004
1976
|
}
|
|
2005
1977
|
});
|
|
2006
1978
|
spool.roll(() => {
|
|
2007
|
-
this.
|
|
1979
|
+
this.onResponse.delete(`sink-push-response:${requestId}`);
|
|
2008
1980
|
});
|
|
2009
|
-
this.
|
|
1981
|
+
this.onResponse.set(`sink-push-credit:${requestId}`, (_response) => {
|
|
2010
1982
|
refreshTimeout();
|
|
2011
1983
|
});
|
|
2012
1984
|
spool.roll(() => {
|
|
2013
|
-
this.
|
|
1985
|
+
this.onResponse.delete(`sink-push-credit:${requestId}`);
|
|
2014
1986
|
});
|
|
2015
1987
|
const auth = this.authenticate();
|
|
2016
1988
|
const metaStore = this.metaStore(meta);
|
|
@@ -2021,7 +1993,7 @@ class SinkTrait extends SourceTrait {
|
|
|
2021
1993
|
reject(err);
|
|
2022
1994
|
});
|
|
2023
1995
|
});
|
|
2024
|
-
this.
|
|
1996
|
+
this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
|
|
2025
1997
|
if (response.error)
|
|
2026
1998
|
abortController.abort(new Error(response.error));
|
|
2027
1999
|
});
|
|
@@ -2029,13 +2001,13 @@ class SinkTrait extends SourceTrait {
|
|
|
2029
2001
|
creditGate = new CreditGate(initialCredit);
|
|
2030
2002
|
if (creditGate) {
|
|
2031
2003
|
const creditTopic = this.options.topicMake(name, "sink-push-credit", this.options.id);
|
|
2032
|
-
await run(`subscribe to MQTT topic "${creditTopic}"`, spool, () => this.
|
|
2033
|
-
spool.roll(() => this.
|
|
2004
|
+
await run(`subscribe to MQTT topic "${creditTopic}"`, spool, () => this.subscriptions.subscribe(creditTopic, { qos: 2 }));
|
|
2005
|
+
spool.roll(() => this.subscriptions.unsubscribe(creditTopic));
|
|
2034
2006
|
const gate = creditGate;
|
|
2035
2007
|
spool.roll(() => {
|
|
2036
2008
|
gate.abort();
|
|
2037
2009
|
});
|
|
2038
|
-
this.
|
|
2010
|
+
this.onResponse.set(`sink-push-credit:${requestId}`, (response) => {
|
|
2039
2011
|
gate.replenish(response.credit);
|
|
2040
2012
|
refreshTimeout();
|
|
2041
2013
|
});
|
|
@@ -2063,28 +2035,6 @@ class SinkTrait extends SourceTrait {
|
|
|
2063
2035
|
await spool.unroll();
|
|
2064
2036
|
}
|
|
2065
2037
|
}
|
|
2066
|
-
/* dispatch incoming MQTT message */
|
|
2067
|
-
async _dispatchMessage(topic, message) {
|
|
2068
|
-
await super._dispatchMessage(topic, message);
|
|
2069
|
-
const topicMatch = this.options.topicMatch(topic);
|
|
2070
|
-
if (topicMatch !== null && topicMatch.operation === "sink-push-request" && message instanceof SinkPushRequest) {
|
|
2071
|
-
const handler = this.sinks.get(message.name);
|
|
2072
|
-
if (handler !== void 0)
|
|
2073
|
-
handler(message, topicMatch.name);
|
|
2074
|
-
} else if (topicMatch !== null && topicMatch.operation === "sink-push-response" && message instanceof SinkPushResponse) {
|
|
2075
|
-
const handler = this.pushResponseCallbacks.get(message.id);
|
|
2076
|
-
if (handler !== void 0)
|
|
2077
|
-
handler(message);
|
|
2078
|
-
} else if (topicMatch !== null && topicMatch.operation === "sink-push-chunk" && message instanceof SinkPushChunk) {
|
|
2079
|
-
const handler = this.pushChunkCallbacks.get(message.id);
|
|
2080
|
-
if (handler !== void 0)
|
|
2081
|
-
handler(message, topicMatch.name);
|
|
2082
|
-
} else if (topicMatch !== null && topicMatch.operation === "sink-push-credit" && message instanceof SinkPushCredit) {
|
|
2083
|
-
const handler = this.pushCreditCallbacks.get(message.id);
|
|
2084
|
-
if (handler !== void 0)
|
|
2085
|
-
handler(message);
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
2038
|
}
|
|
2089
2039
|
class MQTTp extends SinkTrait {
|
|
2090
2040
|
}
|