mqtt-plus 1.4.22 → 1.4.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ase/service.log +10402 -0
- package/.ase/task/TASK-P18.md +47 -0
- package/.ase/task/TASK-P2.md +76 -0
- package/.ase/task/TASK-P21.md +75 -0
- package/.claude/settings.local.json +7 -0
- package/CHANGELOG.md +21 -1
- package/README.md +4 -4
- package/dst-stage1/mqtt-plus-api.d.ts +6 -6
- package/dst-stage1/mqtt-plus-base.js +34 -4
- package/dst-stage1/mqtt-plus-codec.js +15 -6
- package/dst-stage1/mqtt-plus-error.js +47 -14
- package/dst-stage1/mqtt-plus-event.js +1 -1
- package/dst-stage1/mqtt-plus-options.js +1 -3
- package/dst-stage1/mqtt-plus-service.js +1 -1
- package/dst-stage1/mqtt-plus-sink.js +10 -2
- package/dst-stage1/mqtt-plus-source.d.ts +1 -0
- package/dst-stage1/mqtt-plus-source.js +11 -3
- package/dst-stage1/mqtt-plus-subscription.js +7 -2
- package/dst-stage1/mqtt-plus-timer.js +4 -2
- package/dst-stage2/mqtt-plus.cjs.cjs +94 -36
- package/dst-stage2/mqtt-plus.esm.js +94 -36
- package/dst-stage2/mqtt-plus.umd.js +11 -11
- package/package.json +1 -1
- package/src/mqtt-plus-api.ts +5 -5
- package/src/mqtt-plus-base.ts +35 -4
- package/src/mqtt-plus-codec.ts +15 -8
- package/src/mqtt-plus-error.ts +52 -14
- package/src/mqtt-plus-event.ts +1 -1
- package/src/mqtt-plus-options.ts +1 -4
- package/src/mqtt-plus-service.ts +1 -1
- package/src/mqtt-plus-sink.ts +12 -2
- package/src/mqtt-plus-source.ts +13 -3
- package/src/mqtt-plus-subscription.ts +7 -2
- package/src/mqtt-plus-timer.ts +4 -2
- package/tst/mqtt-plus-4-sink.spec.ts +43 -0
- package/tst/mqtt-plus-5-source.spec.ts +35 -0
- package/tst/mqtt-plus-6-misc.spec.ts +82 -5
- package/tst/mqtt-plus-8-run.spec.ts +107 -0
- package/.ase/service.yaml +0 -1
|
@@ -76,7 +76,8 @@ export class TimerTrait extends SubscriptionTrait {
|
|
|
76
76
|
ac.abort();
|
|
77
77
|
resolve();
|
|
78
78
|
}, durationMs);
|
|
79
|
-
timer.unref
|
|
79
|
+
if (typeof timer.unref === "function")
|
|
80
|
+
timer.unref();
|
|
80
81
|
if (signal !== undefined) {
|
|
81
82
|
if (ac !== undefined)
|
|
82
83
|
signal.addEventListener("abort", () => {
|
|
@@ -101,7 +102,8 @@ export class TimerTrait extends SubscriptionTrait {
|
|
|
101
102
|
ac.abort();
|
|
102
103
|
reject(new Error(info));
|
|
103
104
|
}, durationMs);
|
|
104
|
-
timer.unref
|
|
105
|
+
if (typeof timer.unref === "function")
|
|
106
|
+
timer.unref();
|
|
105
107
|
if (signal !== undefined) {
|
|
106
108
|
if (ac !== undefined)
|
|
107
109
|
signal.addEventListener("abort", () => {
|
|
@@ -309,6 +309,31 @@ function runUnroll(isAsync, spool) {
|
|
|
309
309
|
if (isAsync && !(result instanceof Promise)) result = Promise.resolve(result);
|
|
310
310
|
return result;
|
|
311
311
|
}
|
|
312
|
+
function runSettle(isAsync, pending, spool, onfinally, description) {
|
|
313
|
+
if (!isAsync) {
|
|
314
|
+
let failure;
|
|
315
|
+
try {
|
|
316
|
+
runFinally(false, onfinally, description);
|
|
317
|
+
} catch (arg) {
|
|
318
|
+
failure = ensureError(arg, description);
|
|
319
|
+
}
|
|
320
|
+
if (pending || failure !== void 0) {
|
|
321
|
+
runUnroll(false, spool);
|
|
322
|
+
if (!pending && failure !== void 0) throw failure;
|
|
323
|
+
}
|
|
324
|
+
} else return (async () => {
|
|
325
|
+
let failure;
|
|
326
|
+
try {
|
|
327
|
+
await runFinally(true, onfinally, description);
|
|
328
|
+
} catch (arg) {
|
|
329
|
+
failure = ensureError(arg, description);
|
|
330
|
+
}
|
|
331
|
+
if (pending || failure !== void 0) {
|
|
332
|
+
await runUnroll(true, spool);
|
|
333
|
+
if (!pending && failure !== void 0) throw failure;
|
|
334
|
+
}
|
|
335
|
+
})();
|
|
336
|
+
}
|
|
312
337
|
function run(...args) {
|
|
313
338
|
let description;
|
|
314
339
|
let spool;
|
|
@@ -360,40 +385,41 @@ function run(...args) {
|
|
|
360
385
|
result = oncatch(error);
|
|
361
386
|
} catch (arg) {
|
|
362
387
|
error = ensureError(arg, description);
|
|
363
|
-
|
|
364
|
-
runUnroll(false, spool);
|
|
388
|
+
runSettle(false, true, spool, onfinally, description);
|
|
365
389
|
throw error;
|
|
366
390
|
}
|
|
367
|
-
|
|
391
|
+
if (spool && oncleanup) spool.roll(result, oncleanup);
|
|
392
|
+
runSettle(false, false, spool, onfinally, description);
|
|
368
393
|
return result;
|
|
369
394
|
}
|
|
370
|
-
|
|
371
|
-
runUnroll(false, spool);
|
|
395
|
+
runSettle(false, true, spool, onfinally, description);
|
|
372
396
|
throw error;
|
|
373
397
|
}
|
|
374
398
|
if (result instanceof Promise) return result.then(async (result) => {
|
|
375
|
-
await runFinally(true, onfinally, description);
|
|
376
399
|
if (spool && oncleanup) spool.roll(result, oncleanup);
|
|
400
|
+
await runSettle(true, false, spool, onfinally, description);
|
|
377
401
|
return result;
|
|
378
402
|
}, async (arg) => {
|
|
379
403
|
let error = ensureError(arg, description);
|
|
380
|
-
if (oncatch)
|
|
381
|
-
|
|
382
|
-
|
|
404
|
+
if (oncatch) {
|
|
405
|
+
let result;
|
|
406
|
+
try {
|
|
407
|
+
result = await oncatch(error);
|
|
408
|
+
} catch (arg) {
|
|
409
|
+
error = ensureError(arg, description);
|
|
410
|
+
await runSettle(true, true, spool, onfinally, description);
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
if (spool && oncleanup) spool.roll(result, oncleanup);
|
|
414
|
+
await runSettle(true, false, spool, onfinally, description);
|
|
383
415
|
return result;
|
|
384
|
-
} catch (arg) {
|
|
385
|
-
error = ensureError(arg, description);
|
|
386
|
-
await runFinally(true, onfinally, description);
|
|
387
|
-
await runUnroll(true, spool);
|
|
388
|
-
throw error;
|
|
389
416
|
}
|
|
390
|
-
await
|
|
391
|
-
await runUnroll(true, spool);
|
|
417
|
+
await runSettle(true, true, spool, onfinally, description);
|
|
392
418
|
throw error;
|
|
393
419
|
});
|
|
394
420
|
else {
|
|
395
|
-
runFinally(false, onfinally, description);
|
|
396
421
|
if (spool && oncleanup) spool.roll(result, oncleanup);
|
|
422
|
+
runSettle(false, false, spool, onfinally, description);
|
|
397
423
|
return result;
|
|
398
424
|
}
|
|
399
425
|
}
|
|
@@ -402,7 +428,7 @@ function run(...args) {
|
|
|
402
428
|
var OptionsTrait = class {
|
|
403
429
|
constructor(options = {}) {
|
|
404
430
|
this.options = {
|
|
405
|
-
id:
|
|
431
|
+
id: "",
|
|
406
432
|
codec: "cbor",
|
|
407
433
|
timeout: 10 * 1e3,
|
|
408
434
|
share: "",
|
|
@@ -425,7 +451,7 @@ var OptionsTrait = class {
|
|
|
425
451
|
};
|
|
426
452
|
//#endregion
|
|
427
453
|
//#region dst-stage1/mqtt-plus-codec.js
|
|
428
|
-
var JSONX = class {
|
|
454
|
+
var JSONX = class JSONX {
|
|
429
455
|
static uint8ArrayToBase64(arr) {
|
|
430
456
|
return node_buffer.Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString("base64");
|
|
431
457
|
}
|
|
@@ -433,10 +459,13 @@ var JSONX = class {
|
|
|
433
459
|
return new Uint8Array(node_buffer.Buffer.from(base64, "base64"));
|
|
434
460
|
}
|
|
435
461
|
static stringify(obj) {
|
|
436
|
-
return JSON.stringify(obj, (
|
|
462
|
+
return JSON.stringify(obj, function(key, value) {
|
|
463
|
+
const orig = this[key];
|
|
464
|
+
return orig instanceof node_buffer.Buffer ? { __Buffer: JSONX.uint8ArrayToBase64(orig) } : orig instanceof Uint8Array ? { __Uint8Array: JSONX.uint8ArrayToBase64(orig) } : value;
|
|
465
|
+
});
|
|
437
466
|
}
|
|
438
467
|
static parse(json) {
|
|
439
|
-
return JSON.parse(json, (_, value) => typeof value?.__Uint8Array === "string" ? this.base64ToUint8Array(value.__Uint8Array) : value);
|
|
468
|
+
return JSON.parse(json, (_, value) => typeof value?.__Buffer === "string" ? node_buffer.Buffer.from(value.__Buffer, "base64") : typeof value?.__Uint8Array === "string" ? this.base64ToUint8Array(value.__Uint8Array) : value);
|
|
440
469
|
}
|
|
441
470
|
};
|
|
442
471
|
var Codec = class {
|
|
@@ -931,13 +960,20 @@ var BaseTrait = class extends TraceTrait {
|
|
|
931
960
|
} });
|
|
932
961
|
}
|
|
933
962
|
this.mqtt = mqtt;
|
|
963
|
+
if (this.options.id === "") {
|
|
964
|
+
const clientId = mqtt.isFakeProxy ? void 0 : mqtt.options?.clientId;
|
|
965
|
+
this.options.id = typeof clientId === "string" && clientId !== "" ? clientId : (0, nanoid.nanoid)();
|
|
966
|
+
}
|
|
934
967
|
this.log("info", "hooking into MQTT client");
|
|
935
968
|
this.messageHandler = (topic, message, packet) => {
|
|
936
969
|
if (this.destroyed) return;
|
|
937
970
|
let input;
|
|
938
971
|
if (this.options.codec === "json") input = message.toString();
|
|
939
972
|
else if (this.options.codec === "cbor") input = Buffer.isBuffer(message) ? new Uint8Array(message.buffer, message.byteOffset, message.byteLength) : message;
|
|
940
|
-
else
|
|
973
|
+
else {
|
|
974
|
+
this.error(/* @__PURE__ */ new Error("invalid codec configured"));
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
941
977
|
this._onMessage(topic, input, packet);
|
|
942
978
|
};
|
|
943
979
|
this.mqtt.on("message", this.messageHandler);
|
|
@@ -967,10 +1003,14 @@ var BaseTrait = class extends TraceTrait {
|
|
|
967
1003
|
this.mqtt.subscribe(topic, {
|
|
968
1004
|
qos: 2,
|
|
969
1005
|
...options
|
|
970
|
-
}, (err,
|
|
1006
|
+
}, (err, granted) => {
|
|
971
1007
|
if (err) {
|
|
972
1008
|
this.error(err, `subscribing to MQTT topic "${topic}" failed`);
|
|
973
1009
|
reject(err);
|
|
1010
|
+
} else if (Array.isArray(granted) && granted.some((grant) => typeof grant?.qos === "number" && grant.qos >= 128)) {
|
|
1011
|
+
const error = /* @__PURE__ */ new Error(`broker rejected subscription to MQTT topic "${topic}" (grant codes: ${granted.map((grant) => grant?.qos).join(", ")})`);
|
|
1012
|
+
this.error(error, `subscribing to MQTT topic "${topic}" failed`);
|
|
1013
|
+
reject(error);
|
|
974
1014
|
} else resolve();
|
|
975
1015
|
});
|
|
976
1016
|
});
|
|
@@ -1012,7 +1052,13 @@ var BaseTrait = class extends TraceTrait {
|
|
|
1012
1052
|
}
|
|
1013
1053
|
_onMessage(topic, data, _packet) {
|
|
1014
1054
|
if (this.destroyed) return;
|
|
1015
|
-
|
|
1055
|
+
let topicMatch;
|
|
1056
|
+
try {
|
|
1057
|
+
topicMatch = this.options.topicMatch(topic);
|
|
1058
|
+
} catch (err) {
|
|
1059
|
+
this.error(ensureError(err, "failed to match MQTT topic"));
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1016
1062
|
if (topicMatch === null) return;
|
|
1017
1063
|
if (typeof data === "string") this.log("info", `received from MQTT topic "${topic}" (type: string, length: ${data.length} chars)`);
|
|
1018
1064
|
else this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${data.byteLength} bytes)`);
|
|
@@ -1114,7 +1160,7 @@ var RefCountedSubscription = class {
|
|
|
1114
1160
|
}
|
|
1115
1161
|
}
|
|
1116
1162
|
async unsubscribe(topic) {
|
|
1117
|
-
if (this.decrementCount(topic) === 0) if (this.lingerMs > 0) {
|
|
1163
|
+
if (this.decrementCount(topic) === 0) if (this.lingerMs > 0 && !topic.startsWith("$share/")) {
|
|
1118
1164
|
const timer = setTimeout(() => {
|
|
1119
1165
|
this.lingers.delete(topic);
|
|
1120
1166
|
const promise = this.unsubscribeFn(topic).catch(() => {}).finally(() => {
|
|
@@ -1208,7 +1254,7 @@ var TimerTrait = class extends SubscriptionTrait {
|
|
|
1208
1254
|
if (ac !== void 0) ac.abort();
|
|
1209
1255
|
resolve();
|
|
1210
1256
|
}, durationMs);
|
|
1211
|
-
timer.unref();
|
|
1257
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
1212
1258
|
if (signal !== void 0) {
|
|
1213
1259
|
if (ac !== void 0) signal.addEventListener("abort", () => {
|
|
1214
1260
|
if (timer !== null) clearTimeout(timer);
|
|
@@ -1232,7 +1278,7 @@ var TimerTrait = class extends SubscriptionTrait {
|
|
|
1232
1278
|
if (ac !== void 0) ac.abort();
|
|
1233
1279
|
reject(new Error(info));
|
|
1234
1280
|
}, durationMs);
|
|
1235
|
-
timer.unref();
|
|
1281
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
1236
1282
|
if (signal !== void 0) {
|
|
1237
1283
|
if (ac !== void 0) signal.addEventListener("abort", () => {
|
|
1238
1284
|
if (timer !== null) clearTimeout(timer);
|
|
@@ -1413,7 +1459,7 @@ var EventTrait = class extends AuthTrait {
|
|
|
1413
1459
|
if (request.receiver) info.receiver = request.receiver;
|
|
1414
1460
|
if (request.meta) info.meta = request.meta;
|
|
1415
1461
|
try {
|
|
1416
|
-
if (auth) info.authenticated = await this.authenticated(senderId, request.auth, auth, `event "${name}"`);
|
|
1462
|
+
if (auth !== void 0) info.authenticated = await this.authenticated(senderId, request.auth, auth, `event "${name}"`);
|
|
1417
1463
|
await callback(...params, info);
|
|
1418
1464
|
} catch (result) {
|
|
1419
1465
|
const error = ensureError(result);
|
|
@@ -1554,7 +1600,7 @@ var ServiceTrait = class extends EventTrait {
|
|
|
1554
1600
|
armServiceTimeout();
|
|
1555
1601
|
let removeAbortListener;
|
|
1556
1602
|
try {
|
|
1557
|
-
if (auth) info.authenticated = await this.authenticated(senderId, request.auth, auth, `service "${name}"`);
|
|
1603
|
+
if (auth !== void 0) info.authenticated = await this.authenticated(senderId, request.auth, auth, `service "${name}"`);
|
|
1558
1604
|
const abortPromise = new Promise((_resolve, reject) => {
|
|
1559
1605
|
const onAbort = () => {
|
|
1560
1606
|
reject(ensureError(abortSignal.reason));
|
|
@@ -1692,8 +1738,11 @@ var SourceTrait = class extends ServiceTrait {
|
|
|
1692
1738
|
this.sourceControllers = /* @__PURE__ */ new Map();
|
|
1693
1739
|
this.sourceSpools = /* @__PURE__ */ new Map();
|
|
1694
1740
|
this.sourceRequests = /* @__PURE__ */ new Map();
|
|
1741
|
+
this.fetchAborts = /* @__PURE__ */ new Map();
|
|
1695
1742
|
}
|
|
1696
1743
|
async destroy() {
|
|
1744
|
+
for (const abort of this.fetchAborts.values()) abort(/* @__PURE__ */ new Error("instance destroyed"));
|
|
1745
|
+
this.fetchAborts.clear();
|
|
1697
1746
|
for (const controller of this.sourceControllers.values()) controller.abort(/* @__PURE__ */ new Error("source destroyed"));
|
|
1698
1747
|
for (const gate of this.sourceCreditGates.values()) gate.abort();
|
|
1699
1748
|
for (const spool of [...this.sourceSpools.values()]) await spool.unroll();
|
|
@@ -1733,10 +1782,6 @@ var SourceTrait = class extends ServiceTrait {
|
|
|
1733
1782
|
const sender = request.sender;
|
|
1734
1783
|
const receiver = request.receiver;
|
|
1735
1784
|
const reqSpool = new Spool();
|
|
1736
|
-
this.sourceSpools.set(requestId, reqSpool);
|
|
1737
|
-
reqSpool.roll(() => {
|
|
1738
|
-
this.sourceSpools.delete(requestId);
|
|
1739
|
-
});
|
|
1740
1785
|
if (sender === void 0 || sender === "") {
|
|
1741
1786
|
this.error(/* @__PURE__ */ new Error("invalid request: missing sender"));
|
|
1742
1787
|
await reqSpool.unroll();
|
|
@@ -1763,6 +1808,10 @@ var SourceTrait = class extends ServiceTrait {
|
|
|
1763
1808
|
reqSpool.roll(() => {
|
|
1764
1809
|
this.sourceControllers.delete(requestId);
|
|
1765
1810
|
});
|
|
1811
|
+
this.sourceSpools.set(requestId, reqSpool);
|
|
1812
|
+
reqSpool.roll(() => {
|
|
1813
|
+
this.sourceSpools.delete(requestId);
|
|
1814
|
+
});
|
|
1766
1815
|
const info = {
|
|
1767
1816
|
sender,
|
|
1768
1817
|
signal: abortSignal
|
|
@@ -1848,7 +1897,7 @@ var SourceTrait = class extends ServiceTrait {
|
|
|
1848
1897
|
reqSpool.roll(() => {
|
|
1849
1898
|
this.onResponse.delete(`source-fetch-credit:${requestId}`);
|
|
1850
1899
|
});
|
|
1851
|
-
if (auth) info.authenticated = await this.authenticated(sender, request.auth, auth, `source "${name}"`);
|
|
1900
|
+
if (auth !== void 0) info.authenticated = await this.authenticated(sender, request.auth, auth, `source "${name}"`);
|
|
1852
1901
|
const callbackPromise = Promise.resolve(callback(...params, info));
|
|
1853
1902
|
callbackPromise.catch(() => {});
|
|
1854
1903
|
await Promise.race([callbackPromise, abortPromise]);
|
|
@@ -2007,6 +2056,10 @@ var SourceTrait = class extends ServiceTrait {
|
|
|
2007
2056
|
metaReject(error);
|
|
2008
2057
|
stream.destroy(error);
|
|
2009
2058
|
};
|
|
2059
|
+
this.fetchAborts.set(requestId, endWithError);
|
|
2060
|
+
spool.roll(() => {
|
|
2061
|
+
this.fetchAborts.delete(requestId);
|
|
2062
|
+
});
|
|
2010
2063
|
let cancelled = false;
|
|
2011
2064
|
const cancelAndUnroll = (reason) => {
|
|
2012
2065
|
if (!cancelled && !streamEnded) {
|
|
@@ -2223,7 +2276,7 @@ var SinkTrait = class extends SourceTrait {
|
|
|
2223
2276
|
try {
|
|
2224
2277
|
if (topicName !== request.name) throw new Error(`sink name mismatch (topic: "${topicName}", payload: "${request.name}")`);
|
|
2225
2278
|
let authenticated = void 0;
|
|
2226
|
-
if (auth) authenticated = await this.authenticated(sender, request.auth, auth, `sink "${name}"`);
|
|
2279
|
+
if (auth !== void 0) authenticated = await this.authenticated(sender, request.auth, auth, `sink "${name}"`);
|
|
2227
2280
|
const creditState = chunkCredit > 0 ? {
|
|
2228
2281
|
chunksReceived: 0,
|
|
2229
2282
|
creditGranted: chunkCredit
|
|
@@ -2233,7 +2286,11 @@ var SinkTrait = class extends SourceTrait {
|
|
|
2233
2286
|
const pushTimerId = `sink-push-recv:${requestId}`;
|
|
2234
2287
|
const refreshPushTimeout = () => this.timerRefresh(pushTimerId, () => {
|
|
2235
2288
|
if (streamEnded || this.destroying) return;
|
|
2236
|
-
|
|
2289
|
+
const error = /* @__PURE__ */ new Error("push stream timeout");
|
|
2290
|
+
abortController.abort(error);
|
|
2291
|
+
streamEnded = true;
|
|
2292
|
+
const stream = this.pushStreams.get(requestId);
|
|
2293
|
+
if (stream !== void 0 && !stream.destroyed) stream.destroy(error);
|
|
2237
2294
|
if (sender && !errorResponseSent) {
|
|
2238
2295
|
errorResponseSent = true;
|
|
2239
2296
|
const cancelMsg = this.msg.makeSinkPushCredit(requestId, name, 0, this.options.id, sender);
|
|
@@ -2513,6 +2570,7 @@ var SinkTrait = class extends SourceTrait {
|
|
|
2513
2570
|
pushInitialResolve = resolve;
|
|
2514
2571
|
pushInitialReject = reject;
|
|
2515
2572
|
});
|
|
2573
|
+
pushInitial.catch(() => {});
|
|
2516
2574
|
this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
|
|
2517
2575
|
if (response.name !== name) {
|
|
2518
2576
|
const error = /* @__PURE__ */ new Error(`sink response name mismatch (expected "${name}", got "${response.name}")`);
|
|
@@ -282,6 +282,31 @@ function runUnroll(isAsync, spool) {
|
|
|
282
282
|
if (isAsync && !(result instanceof Promise)) result = Promise.resolve(result);
|
|
283
283
|
return result;
|
|
284
284
|
}
|
|
285
|
+
function runSettle(isAsync, pending, spool, onfinally, description) {
|
|
286
|
+
if (!isAsync) {
|
|
287
|
+
let failure;
|
|
288
|
+
try {
|
|
289
|
+
runFinally(false, onfinally, description);
|
|
290
|
+
} catch (arg) {
|
|
291
|
+
failure = ensureError(arg, description);
|
|
292
|
+
}
|
|
293
|
+
if (pending || failure !== void 0) {
|
|
294
|
+
runUnroll(false, spool);
|
|
295
|
+
if (!pending && failure !== void 0) throw failure;
|
|
296
|
+
}
|
|
297
|
+
} else return (async () => {
|
|
298
|
+
let failure;
|
|
299
|
+
try {
|
|
300
|
+
await runFinally(true, onfinally, description);
|
|
301
|
+
} catch (arg) {
|
|
302
|
+
failure = ensureError(arg, description);
|
|
303
|
+
}
|
|
304
|
+
if (pending || failure !== void 0) {
|
|
305
|
+
await runUnroll(true, spool);
|
|
306
|
+
if (!pending && failure !== void 0) throw failure;
|
|
307
|
+
}
|
|
308
|
+
})();
|
|
309
|
+
}
|
|
285
310
|
function run(...args) {
|
|
286
311
|
let description;
|
|
287
312
|
let spool;
|
|
@@ -333,40 +358,41 @@ function run(...args) {
|
|
|
333
358
|
result = oncatch(error);
|
|
334
359
|
} catch (arg) {
|
|
335
360
|
error = ensureError(arg, description);
|
|
336
|
-
|
|
337
|
-
runUnroll(false, spool);
|
|
361
|
+
runSettle(false, true, spool, onfinally, description);
|
|
338
362
|
throw error;
|
|
339
363
|
}
|
|
340
|
-
|
|
364
|
+
if (spool && oncleanup) spool.roll(result, oncleanup);
|
|
365
|
+
runSettle(false, false, spool, onfinally, description);
|
|
341
366
|
return result;
|
|
342
367
|
}
|
|
343
|
-
|
|
344
|
-
runUnroll(false, spool);
|
|
368
|
+
runSettle(false, true, spool, onfinally, description);
|
|
345
369
|
throw error;
|
|
346
370
|
}
|
|
347
371
|
if (result instanceof Promise) return result.then(async (result) => {
|
|
348
|
-
await runFinally(true, onfinally, description);
|
|
349
372
|
if (spool && oncleanup) spool.roll(result, oncleanup);
|
|
373
|
+
await runSettle(true, false, spool, onfinally, description);
|
|
350
374
|
return result;
|
|
351
375
|
}, async (arg) => {
|
|
352
376
|
let error = ensureError(arg, description);
|
|
353
|
-
if (oncatch)
|
|
354
|
-
|
|
355
|
-
|
|
377
|
+
if (oncatch) {
|
|
378
|
+
let result;
|
|
379
|
+
try {
|
|
380
|
+
result = await oncatch(error);
|
|
381
|
+
} catch (arg) {
|
|
382
|
+
error = ensureError(arg, description);
|
|
383
|
+
await runSettle(true, true, spool, onfinally, description);
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
if (spool && oncleanup) spool.roll(result, oncleanup);
|
|
387
|
+
await runSettle(true, false, spool, onfinally, description);
|
|
356
388
|
return result;
|
|
357
|
-
} catch (arg) {
|
|
358
|
-
error = ensureError(arg, description);
|
|
359
|
-
await runFinally(true, onfinally, description);
|
|
360
|
-
await runUnroll(true, spool);
|
|
361
|
-
throw error;
|
|
362
389
|
}
|
|
363
|
-
await
|
|
364
|
-
await runUnroll(true, spool);
|
|
390
|
+
await runSettle(true, true, spool, onfinally, description);
|
|
365
391
|
throw error;
|
|
366
392
|
});
|
|
367
393
|
else {
|
|
368
|
-
runFinally(false, onfinally, description);
|
|
369
394
|
if (spool && oncleanup) spool.roll(result, oncleanup);
|
|
395
|
+
runSettle(false, false, spool, onfinally, description);
|
|
370
396
|
return result;
|
|
371
397
|
}
|
|
372
398
|
}
|
|
@@ -375,7 +401,7 @@ function run(...args) {
|
|
|
375
401
|
var OptionsTrait = class {
|
|
376
402
|
constructor(options = {}) {
|
|
377
403
|
this.options = {
|
|
378
|
-
id:
|
|
404
|
+
id: "",
|
|
379
405
|
codec: "cbor",
|
|
380
406
|
timeout: 10 * 1e3,
|
|
381
407
|
share: "",
|
|
@@ -398,7 +424,7 @@ var OptionsTrait = class {
|
|
|
398
424
|
};
|
|
399
425
|
//#endregion
|
|
400
426
|
//#region dst-stage1/mqtt-plus-codec.js
|
|
401
|
-
var JSONX = class {
|
|
427
|
+
var JSONX = class JSONX {
|
|
402
428
|
static uint8ArrayToBase64(arr) {
|
|
403
429
|
return Buffer$1.from(arr.buffer, arr.byteOffset, arr.byteLength).toString("base64");
|
|
404
430
|
}
|
|
@@ -406,10 +432,13 @@ var JSONX = class {
|
|
|
406
432
|
return new Uint8Array(Buffer$1.from(base64, "base64"));
|
|
407
433
|
}
|
|
408
434
|
static stringify(obj) {
|
|
409
|
-
return JSON.stringify(obj, (
|
|
435
|
+
return JSON.stringify(obj, function(key, value) {
|
|
436
|
+
const orig = this[key];
|
|
437
|
+
return orig instanceof Buffer$1 ? { __Buffer: JSONX.uint8ArrayToBase64(orig) } : orig instanceof Uint8Array ? { __Uint8Array: JSONX.uint8ArrayToBase64(orig) } : value;
|
|
438
|
+
});
|
|
410
439
|
}
|
|
411
440
|
static parse(json) {
|
|
412
|
-
return JSON.parse(json, (_, value) => typeof value?.__Uint8Array === "string" ? this.base64ToUint8Array(value.__Uint8Array) : value);
|
|
441
|
+
return JSON.parse(json, (_, value) => typeof value?.__Buffer === "string" ? Buffer$1.from(value.__Buffer, "base64") : typeof value?.__Uint8Array === "string" ? this.base64ToUint8Array(value.__Uint8Array) : value);
|
|
413
442
|
}
|
|
414
443
|
};
|
|
415
444
|
var Codec = class {
|
|
@@ -904,13 +933,20 @@ var BaseTrait = class extends TraceTrait {
|
|
|
904
933
|
} });
|
|
905
934
|
}
|
|
906
935
|
this.mqtt = mqtt;
|
|
936
|
+
if (this.options.id === "") {
|
|
937
|
+
const clientId = mqtt.isFakeProxy ? void 0 : mqtt.options?.clientId;
|
|
938
|
+
this.options.id = typeof clientId === "string" && clientId !== "" ? clientId : nanoid();
|
|
939
|
+
}
|
|
907
940
|
this.log("info", "hooking into MQTT client");
|
|
908
941
|
this.messageHandler = (topic, message, packet) => {
|
|
909
942
|
if (this.destroyed) return;
|
|
910
943
|
let input;
|
|
911
944
|
if (this.options.codec === "json") input = message.toString();
|
|
912
945
|
else if (this.options.codec === "cbor") input = Buffer.isBuffer(message) ? new Uint8Array(message.buffer, message.byteOffset, message.byteLength) : message;
|
|
913
|
-
else
|
|
946
|
+
else {
|
|
947
|
+
this.error(/* @__PURE__ */ new Error("invalid codec configured"));
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
914
950
|
this._onMessage(topic, input, packet);
|
|
915
951
|
};
|
|
916
952
|
this.mqtt.on("message", this.messageHandler);
|
|
@@ -940,10 +976,14 @@ var BaseTrait = class extends TraceTrait {
|
|
|
940
976
|
this.mqtt.subscribe(topic, {
|
|
941
977
|
qos: 2,
|
|
942
978
|
...options
|
|
943
|
-
}, (err,
|
|
979
|
+
}, (err, granted) => {
|
|
944
980
|
if (err) {
|
|
945
981
|
this.error(err, `subscribing to MQTT topic "${topic}" failed`);
|
|
946
982
|
reject(err);
|
|
983
|
+
} else if (Array.isArray(granted) && granted.some((grant) => typeof grant?.qos === "number" && grant.qos >= 128)) {
|
|
984
|
+
const error = /* @__PURE__ */ new Error(`broker rejected subscription to MQTT topic "${topic}" (grant codes: ${granted.map((grant) => grant?.qos).join(", ")})`);
|
|
985
|
+
this.error(error, `subscribing to MQTT topic "${topic}" failed`);
|
|
986
|
+
reject(error);
|
|
947
987
|
} else resolve();
|
|
948
988
|
});
|
|
949
989
|
});
|
|
@@ -985,7 +1025,13 @@ var BaseTrait = class extends TraceTrait {
|
|
|
985
1025
|
}
|
|
986
1026
|
_onMessage(topic, data, _packet) {
|
|
987
1027
|
if (this.destroyed) return;
|
|
988
|
-
|
|
1028
|
+
let topicMatch;
|
|
1029
|
+
try {
|
|
1030
|
+
topicMatch = this.options.topicMatch(topic);
|
|
1031
|
+
} catch (err) {
|
|
1032
|
+
this.error(ensureError(err, "failed to match MQTT topic"));
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
989
1035
|
if (topicMatch === null) return;
|
|
990
1036
|
if (typeof data === "string") this.log("info", `received from MQTT topic "${topic}" (type: string, length: ${data.length} chars)`);
|
|
991
1037
|
else this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${data.byteLength} bytes)`);
|
|
@@ -1087,7 +1133,7 @@ var RefCountedSubscription = class {
|
|
|
1087
1133
|
}
|
|
1088
1134
|
}
|
|
1089
1135
|
async unsubscribe(topic) {
|
|
1090
|
-
if (this.decrementCount(topic) === 0) if (this.lingerMs > 0) {
|
|
1136
|
+
if (this.decrementCount(topic) === 0) if (this.lingerMs > 0 && !topic.startsWith("$share/")) {
|
|
1091
1137
|
const timer = setTimeout(() => {
|
|
1092
1138
|
this.lingers.delete(topic);
|
|
1093
1139
|
const promise = this.unsubscribeFn(topic).catch(() => {}).finally(() => {
|
|
@@ -1181,7 +1227,7 @@ var TimerTrait = class extends SubscriptionTrait {
|
|
|
1181
1227
|
if (ac !== void 0) ac.abort();
|
|
1182
1228
|
resolve();
|
|
1183
1229
|
}, durationMs);
|
|
1184
|
-
timer.unref();
|
|
1230
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
1185
1231
|
if (signal !== void 0) {
|
|
1186
1232
|
if (ac !== void 0) signal.addEventListener("abort", () => {
|
|
1187
1233
|
if (timer !== null) clearTimeout(timer);
|
|
@@ -1205,7 +1251,7 @@ var TimerTrait = class extends SubscriptionTrait {
|
|
|
1205
1251
|
if (ac !== void 0) ac.abort();
|
|
1206
1252
|
reject(new Error(info));
|
|
1207
1253
|
}, durationMs);
|
|
1208
|
-
timer.unref();
|
|
1254
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
1209
1255
|
if (signal !== void 0) {
|
|
1210
1256
|
if (ac !== void 0) signal.addEventListener("abort", () => {
|
|
1211
1257
|
if (timer !== null) clearTimeout(timer);
|
|
@@ -1386,7 +1432,7 @@ var EventTrait = class extends AuthTrait {
|
|
|
1386
1432
|
if (request.receiver) info.receiver = request.receiver;
|
|
1387
1433
|
if (request.meta) info.meta = request.meta;
|
|
1388
1434
|
try {
|
|
1389
|
-
if (auth) info.authenticated = await this.authenticated(senderId, request.auth, auth, `event "${name}"`);
|
|
1435
|
+
if (auth !== void 0) info.authenticated = await this.authenticated(senderId, request.auth, auth, `event "${name}"`);
|
|
1390
1436
|
await callback(...params, info);
|
|
1391
1437
|
} catch (result) {
|
|
1392
1438
|
const error = ensureError(result);
|
|
@@ -1527,7 +1573,7 @@ var ServiceTrait = class extends EventTrait {
|
|
|
1527
1573
|
armServiceTimeout();
|
|
1528
1574
|
let removeAbortListener;
|
|
1529
1575
|
try {
|
|
1530
|
-
if (auth) info.authenticated = await this.authenticated(senderId, request.auth, auth, `service "${name}"`);
|
|
1576
|
+
if (auth !== void 0) info.authenticated = await this.authenticated(senderId, request.auth, auth, `service "${name}"`);
|
|
1531
1577
|
const abortPromise = new Promise((_resolve, reject) => {
|
|
1532
1578
|
const onAbort = () => {
|
|
1533
1579
|
reject(ensureError(abortSignal.reason));
|
|
@@ -1665,8 +1711,11 @@ var SourceTrait = class extends ServiceTrait {
|
|
|
1665
1711
|
this.sourceControllers = /* @__PURE__ */ new Map();
|
|
1666
1712
|
this.sourceSpools = /* @__PURE__ */ new Map();
|
|
1667
1713
|
this.sourceRequests = /* @__PURE__ */ new Map();
|
|
1714
|
+
this.fetchAborts = /* @__PURE__ */ new Map();
|
|
1668
1715
|
}
|
|
1669
1716
|
async destroy() {
|
|
1717
|
+
for (const abort of this.fetchAborts.values()) abort(/* @__PURE__ */ new Error("instance destroyed"));
|
|
1718
|
+
this.fetchAborts.clear();
|
|
1670
1719
|
for (const controller of this.sourceControllers.values()) controller.abort(/* @__PURE__ */ new Error("source destroyed"));
|
|
1671
1720
|
for (const gate of this.sourceCreditGates.values()) gate.abort();
|
|
1672
1721
|
for (const spool of [...this.sourceSpools.values()]) await spool.unroll();
|
|
@@ -1706,10 +1755,6 @@ var SourceTrait = class extends ServiceTrait {
|
|
|
1706
1755
|
const sender = request.sender;
|
|
1707
1756
|
const receiver = request.receiver;
|
|
1708
1757
|
const reqSpool = new Spool();
|
|
1709
|
-
this.sourceSpools.set(requestId, reqSpool);
|
|
1710
|
-
reqSpool.roll(() => {
|
|
1711
|
-
this.sourceSpools.delete(requestId);
|
|
1712
|
-
});
|
|
1713
1758
|
if (sender === void 0 || sender === "") {
|
|
1714
1759
|
this.error(/* @__PURE__ */ new Error("invalid request: missing sender"));
|
|
1715
1760
|
await reqSpool.unroll();
|
|
@@ -1736,6 +1781,10 @@ var SourceTrait = class extends ServiceTrait {
|
|
|
1736
1781
|
reqSpool.roll(() => {
|
|
1737
1782
|
this.sourceControllers.delete(requestId);
|
|
1738
1783
|
});
|
|
1784
|
+
this.sourceSpools.set(requestId, reqSpool);
|
|
1785
|
+
reqSpool.roll(() => {
|
|
1786
|
+
this.sourceSpools.delete(requestId);
|
|
1787
|
+
});
|
|
1739
1788
|
const info = {
|
|
1740
1789
|
sender,
|
|
1741
1790
|
signal: abortSignal
|
|
@@ -1821,7 +1870,7 @@ var SourceTrait = class extends ServiceTrait {
|
|
|
1821
1870
|
reqSpool.roll(() => {
|
|
1822
1871
|
this.onResponse.delete(`source-fetch-credit:${requestId}`);
|
|
1823
1872
|
});
|
|
1824
|
-
if (auth) info.authenticated = await this.authenticated(sender, request.auth, auth, `source "${name}"`);
|
|
1873
|
+
if (auth !== void 0) info.authenticated = await this.authenticated(sender, request.auth, auth, `source "${name}"`);
|
|
1825
1874
|
const callbackPromise = Promise.resolve(callback(...params, info));
|
|
1826
1875
|
callbackPromise.catch(() => {});
|
|
1827
1876
|
await Promise.race([callbackPromise, abortPromise]);
|
|
@@ -1980,6 +2029,10 @@ var SourceTrait = class extends ServiceTrait {
|
|
|
1980
2029
|
metaReject(error);
|
|
1981
2030
|
stream.destroy(error);
|
|
1982
2031
|
};
|
|
2032
|
+
this.fetchAborts.set(requestId, endWithError);
|
|
2033
|
+
spool.roll(() => {
|
|
2034
|
+
this.fetchAborts.delete(requestId);
|
|
2035
|
+
});
|
|
1983
2036
|
let cancelled = false;
|
|
1984
2037
|
const cancelAndUnroll = (reason) => {
|
|
1985
2038
|
if (!cancelled && !streamEnded) {
|
|
@@ -2196,7 +2249,7 @@ var SinkTrait = class extends SourceTrait {
|
|
|
2196
2249
|
try {
|
|
2197
2250
|
if (topicName !== request.name) throw new Error(`sink name mismatch (topic: "${topicName}", payload: "${request.name}")`);
|
|
2198
2251
|
let authenticated = void 0;
|
|
2199
|
-
if (auth) authenticated = await this.authenticated(sender, request.auth, auth, `sink "${name}"`);
|
|
2252
|
+
if (auth !== void 0) authenticated = await this.authenticated(sender, request.auth, auth, `sink "${name}"`);
|
|
2200
2253
|
const creditState = chunkCredit > 0 ? {
|
|
2201
2254
|
chunksReceived: 0,
|
|
2202
2255
|
creditGranted: chunkCredit
|
|
@@ -2206,7 +2259,11 @@ var SinkTrait = class extends SourceTrait {
|
|
|
2206
2259
|
const pushTimerId = `sink-push-recv:${requestId}`;
|
|
2207
2260
|
const refreshPushTimeout = () => this.timerRefresh(pushTimerId, () => {
|
|
2208
2261
|
if (streamEnded || this.destroying) return;
|
|
2209
|
-
|
|
2262
|
+
const error = /* @__PURE__ */ new Error("push stream timeout");
|
|
2263
|
+
abortController.abort(error);
|
|
2264
|
+
streamEnded = true;
|
|
2265
|
+
const stream = this.pushStreams.get(requestId);
|
|
2266
|
+
if (stream !== void 0 && !stream.destroyed) stream.destroy(error);
|
|
2210
2267
|
if (sender && !errorResponseSent) {
|
|
2211
2268
|
errorResponseSent = true;
|
|
2212
2269
|
const cancelMsg = this.msg.makeSinkPushCredit(requestId, name, 0, this.options.id, sender);
|
|
@@ -2486,6 +2543,7 @@ var SinkTrait = class extends SourceTrait {
|
|
|
2486
2543
|
pushInitialResolve = resolve;
|
|
2487
2544
|
pushInitialReject = reject;
|
|
2488
2545
|
});
|
|
2546
|
+
pushInitial.catch(() => {});
|
|
2489
2547
|
this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
|
|
2490
2548
|
if (response.name !== name) {
|
|
2491
2549
|
const error = /* @__PURE__ */ new Error(`sink response name mismatch (expected "${name}", got "${response.name}")`);
|