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
|
@@ -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
|
-
|
|
13
|
-
|
|
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,
|
|
1040
|
-
|
|
1041
|
-
|
|
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: ${
|
|
1044
|
-
let
|
|
990
|
+
this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${data.byteLength} bytes)`);
|
|
991
|
+
let payload;
|
|
1045
992
|
try {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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
|
-
|
|
1054
|
-
|
|
1055
|
-
this.
|
|
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
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1308
|
-
throw new Error(`
|
|
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.
|
|
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.
|
|
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.
|
|
1357
|
-
throw new Error(`destroy: service "${name}"
|
|
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.
|
|
1384
|
-
spool.roll(() => this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1512
|
-
|
|
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.
|
|
1545
|
+
this.onResponse.set(`source-fetch-credit:${requestId}`, (creditParsed) => {
|
|
1525
1546
|
creditGate.replenish(creditParsed.credit);
|
|
1526
|
-
|
|
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.
|
|
1585
|
+
this.onResponse.delete(`source-fetch-credit:${requestId}`);
|
|
1565
1586
|
});
|
|
1566
1587
|
});
|
|
1567
1588
|
spool.roll(() => {
|
|
1568
|
-
this.
|
|
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.
|
|
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.
|
|
1610
|
-
spool.roll(() => this.
|
|
1611
|
-
await run(`subscribe to MQTT topic "${chunkTopic}"`, spool, () => this.
|
|
1612
|
-
spool.roll(() => this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1693
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
1833
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1945
|
-
spool.roll(() => this.
|
|
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.
|
|
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.
|
|
1958
|
+
this.onResponse.delete(`sink-push-response:${requestId}`);
|
|
1987
1959
|
});
|
|
1988
|
-
this.
|
|
1960
|
+
this.onResponse.set(`sink-push-credit:${requestId}`, (_response) => {
|
|
1989
1961
|
refreshTimeout();
|
|
1990
1962
|
});
|
|
1991
1963
|
spool.roll(() => {
|
|
1992
|
-
this.
|
|
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.
|
|
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.
|
|
2012
|
-
spool.roll(() => this.
|
|
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.
|
|
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
|
}
|