@zlink-systems/stream-connector 0.17.0 → 0.18.1
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/dist/browser/Contracts/Calls/ZlinkStreamCalls.d.ts +8 -2
- package/dist/browser/Contracts/IZlinkStreamConnector.d.ts +24 -3
- package/dist/browser/Contracts/ZlinkStreamConnectorOptions.d.ts +5 -17
- package/dist/browser/Runtime/Calls/ZlinkStreamCallBuilders.d.ts +1 -1
- package/dist/browser/Runtime/Calls/ZlinkStreamObservationBuilders.d.ts +3 -3
- package/dist/browser/Runtime/ZlinkStreamConnector.d.ts +39 -6
- package/dist/browser/Runtime/ZlinkStreamConnectorLifecycle.d.ts +41 -4
- package/dist/browser/Runtime/ZlinkStreamConnectorOptions.d.ts +15 -1
- package/dist/browser/Runtime/ZlinkStreamFrameSender.d.ts +1 -3
- package/dist/browser/Runtime/ZlinkStreamReceiveDispatcher.d.ts +15 -4
- package/dist/browser/Runtime/ZlinkStreamReceivedMessages.d.ts +48 -1
- package/dist/browser/index.d.ts +9 -4
- package/dist/browser/index.global.js +382 -223
- package/dist/browser/index.mjs +370 -197
- package/dist/package.json +1 -0
- package/package.json +3 -2
- package/dist/browser/Runtime/ZlinkStreamRuntimeMetrics.d.ts +0 -17
|
@@ -23,7 +23,6 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
23
23
|
// packages/stream-connector/src/index.ts
|
|
24
24
|
var index_exports = {};
|
|
25
25
|
__export(index_exports, {
|
|
26
|
-
DefaultZlinkStreamConnector: () => DefaultZlinkStreamConnector,
|
|
27
26
|
ZlinkStreamCodec: () => ZlinkStreamCodec,
|
|
28
27
|
ZlinkStreamCompression: () => ZlinkStreamCompression,
|
|
29
28
|
ZlinkStreamConnectionState: () => ZlinkStreamConnectionState,
|
|
@@ -707,6 +706,53 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
707
706
|
return new TextDecoder().decode(value);
|
|
708
707
|
}
|
|
709
708
|
|
|
709
|
+
// packages/stream-connector/src/Runtime/ZlinkStreamAssertions.ts
|
|
710
|
+
var zlinkStreamAssert = {
|
|
711
|
+
ensure(condition, message) {
|
|
712
|
+
if (typeof message !== "string" || message.trim().length === 0) {
|
|
713
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "zlinkStreamAssert.ensure requires a non-empty diagnostic message.");
|
|
714
|
+
}
|
|
715
|
+
if (!condition) {
|
|
716
|
+
throw connectorError("validationFailed" /* ValidationFailed */, message);
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
async expectFailure(action, errorKind) {
|
|
720
|
+
let failure;
|
|
721
|
+
try {
|
|
722
|
+
await action();
|
|
723
|
+
} catch (error) {
|
|
724
|
+
failure = error;
|
|
725
|
+
}
|
|
726
|
+
if (failure === void 0) {
|
|
727
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "Expected action to fail.");
|
|
728
|
+
}
|
|
729
|
+
const streamError = unwrapStreamError(failure);
|
|
730
|
+
if (errorKind !== void 0 && streamError.code !== errorKind) {
|
|
731
|
+
throw connectorError(
|
|
732
|
+
"validationFailed" /* ValidationFailed */,
|
|
733
|
+
`Expected failure kind '${errorKind}', got '${streamError.code}'.`,
|
|
734
|
+
failure
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
return streamError;
|
|
738
|
+
},
|
|
739
|
+
async expectTimeout(action) {
|
|
740
|
+
let failure;
|
|
741
|
+
try {
|
|
742
|
+
await action();
|
|
743
|
+
} catch (error) {
|
|
744
|
+
failure = error;
|
|
745
|
+
}
|
|
746
|
+
if (failure === void 0) {
|
|
747
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "Expected action to time out.");
|
|
748
|
+
}
|
|
749
|
+
const code = unwrapStreamError(failure).code;
|
|
750
|
+
if (code !== "requestTimeout" /* RequestTimeout */ && code !== "connectTimeout" /* ConnectTimeout */) {
|
|
751
|
+
throw failure;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
710
756
|
// packages/stream-connector/src/Runtime/Protocol/ZlinkStreamPacketNameValidator.ts
|
|
711
757
|
function validateName(name, allowReserved = false) {
|
|
712
758
|
if (name.length === 0) {
|
|
@@ -867,15 +913,18 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
867
913
|
this.timeoutMs = timeoutMs;
|
|
868
914
|
return this;
|
|
869
915
|
}
|
|
870
|
-
submit(signal) {
|
|
916
|
+
async submit(signal) {
|
|
871
917
|
var _a;
|
|
872
918
|
this.markExecuted();
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
919
|
+
const timeoutMs = (_a = this.timeoutMs) != null ? _a : this.connector.options.waitTimeoutMs;
|
|
920
|
+
const message = await this.connector.waitForMessage(this.name, timeoutMs, this.predicate, signal);
|
|
921
|
+
if (message === void 0) {
|
|
922
|
+
throw connectorError(
|
|
923
|
+
"validationFailed" /* ValidationFailed */,
|
|
924
|
+
`No '${this.name}' message arrived within ${timeoutMs}ms.`
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
return message;
|
|
879
928
|
}
|
|
880
929
|
ensureConfigurable() {
|
|
881
930
|
if (this.executed) {
|
|
@@ -907,13 +956,9 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
907
956
|
if (this.windowMs === void 0) {
|
|
908
957
|
throw connectorError("validationFailed" /* ValidationFailed */, "expectNone requires within(windowMs).");
|
|
909
958
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
if (unwrapStreamError(error).code === "requestTimeout" /* RequestTimeout */) {
|
|
914
|
-
return;
|
|
915
|
-
}
|
|
916
|
-
throw error;
|
|
959
|
+
const message = await this.connector.waitForMessage(this.name, this.windowMs, () => true, signal);
|
|
960
|
+
if (message === void 0) {
|
|
961
|
+
return;
|
|
917
962
|
}
|
|
918
963
|
throw connectorError(
|
|
919
964
|
"validationFailed" /* ValidationFailed */,
|
|
@@ -949,6 +994,9 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
949
994
|
this.timeoutMs = timeoutMs;
|
|
950
995
|
return this;
|
|
951
996
|
}
|
|
997
|
+
// Spec stream-connector 32 §10.1: the predicate reads the whole message, and
|
|
998
|
+
// the call answers with the messages themselves, so a caller can assert on the
|
|
999
|
+
// packet name and the metadata and not only on the payload.
|
|
952
1000
|
async run(signal) {
|
|
953
1001
|
var _a;
|
|
954
1002
|
this.markExecuted();
|
|
@@ -957,13 +1005,13 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
957
1005
|
}
|
|
958
1006
|
const timeoutMs = (_a = this.timeoutMs) != null ? _a : this.connector.options.waitTimeoutMs;
|
|
959
1007
|
const deadline = Date.now() + timeoutMs;
|
|
960
|
-
const
|
|
1008
|
+
const messages = [];
|
|
961
1009
|
for (const predicate of this.predicates) {
|
|
962
1010
|
const message = await this.connector.waitForMessage(
|
|
963
1011
|
this.name,
|
|
964
1012
|
Math.max(0, deadline - Date.now()),
|
|
965
1013
|
(candidate) => {
|
|
966
|
-
if (!predicate(candidate
|
|
1014
|
+
if (!predicate(candidate)) {
|
|
967
1015
|
throw connectorError(
|
|
968
1016
|
"validationFailed" /* ValidationFailed */,
|
|
969
1017
|
`Message '${this.name}' arrived out of the expected sequence.`
|
|
@@ -973,9 +1021,15 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
973
1021
|
},
|
|
974
1022
|
signal
|
|
975
1023
|
);
|
|
976
|
-
|
|
1024
|
+
if (message === void 0) {
|
|
1025
|
+
throw connectorError(
|
|
1026
|
+
"validationFailed" /* ValidationFailed */,
|
|
1027
|
+
`The '${this.name}' sequence did not complete within ${timeoutMs}ms.`
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
messages.push(message);
|
|
977
1031
|
}
|
|
978
|
-
return
|
|
1032
|
+
return messages;
|
|
979
1033
|
}
|
|
980
1034
|
ensureConfigurable() {
|
|
981
1035
|
if (this.executed) {
|
|
@@ -1076,7 +1130,7 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1076
1130
|
}
|
|
1077
1131
|
function validatePayload(payloadLength, maxPayloadSize) {
|
|
1078
1132
|
if (payloadLength > maxPayloadSize) {
|
|
1079
|
-
throw connectorError("
|
|
1133
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "Payload exceeds MaxSendPayloadSize.");
|
|
1080
1134
|
}
|
|
1081
1135
|
}
|
|
1082
1136
|
|
|
@@ -1326,7 +1380,7 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1326
1380
|
"detailed" /* Detailed */
|
|
1327
1381
|
]);
|
|
1328
1382
|
function normalizeOptions(options, defaultTransportFactory) {
|
|
1329
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D
|
|
1383
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D;
|
|
1330
1384
|
const endpoint = options.endpoint;
|
|
1331
1385
|
if (endpoint.trim().length === 0) {
|
|
1332
1386
|
throw connectorError("configurationError" /* ConfigurationError */, "Endpoint must not be empty.");
|
|
@@ -1359,22 +1413,30 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1359
1413
|
initialDelayMs: (_r = (_q = options.reconnect) == null ? void 0 : _q.initialDelayMs) != null ? _r : 250,
|
|
1360
1414
|
maxDelayMs: (_t = (_s = options.reconnect) == null ? void 0 : _s.maxDelayMs) != null ? _t : 5e3,
|
|
1361
1415
|
backoffFactor: (_v = (_u = options.reconnect) == null ? void 0 : _u.backoffFactor) != null ? _v : 2,
|
|
1362
|
-
maxAttempts: (
|
|
1416
|
+
maxAttempts: ((_w = options.reconnect) == null ? void 0 : _w.maxAttempts) === void 0 ? 3 : options.reconnect.maxAttempts
|
|
1363
1417
|
},
|
|
1364
|
-
maxSendPayloadSize: (
|
|
1365
|
-
maxReceivePayloadSize: (
|
|
1366
|
-
dispatchMode: (
|
|
1367
|
-
compression: (
|
|
1418
|
+
maxSendPayloadSize: (_x = options.maxSendPayloadSize) != null ? _x : 64 * 1024,
|
|
1419
|
+
maxReceivePayloadSize: (_y = options.maxReceivePayloadSize) != null ? _y : 64 * 1024,
|
|
1420
|
+
dispatchMode: (_z = options.dispatchMode) != null ? _z : "manual" /* Manual */,
|
|
1421
|
+
compression: (_A = options.compression) != null ? _A : "lz4" /* Lz4 */,
|
|
1368
1422
|
compressionCodec: resolveCompressionCodec2(options),
|
|
1369
|
-
nameResolver: (
|
|
1370
|
-
transportFactory: (
|
|
1423
|
+
nameResolver: (_B = options.nameResolver) != null ? _B : defaultPacketNameResolver,
|
|
1424
|
+
transportFactory: (_C = options.transportFactory) != null ? _C : defaultTransportFactory,
|
|
1371
1425
|
codec: options.codec,
|
|
1372
|
-
meterProvider: options.meterProvider,
|
|
1373
1426
|
// Spec 26 §4: the default diagnostics level is Errors, which preserves
|
|
1374
1427
|
// the connector's established wire behavior.
|
|
1375
|
-
diagnosticsLevel: (
|
|
1428
|
+
diagnosticsLevel: (_D = options.diagnosticsLevel) != null ? _D : "errors" /* Errors */
|
|
1376
1429
|
};
|
|
1377
1430
|
}
|
|
1431
|
+
var defaultPacketNameResolver = {
|
|
1432
|
+
resolve(payloadType) {
|
|
1433
|
+
const declared = payloadType.packetName;
|
|
1434
|
+
if (typeof declared === "string" && declared.length > 0) {
|
|
1435
|
+
return declared;
|
|
1436
|
+
}
|
|
1437
|
+
return payloadType.name;
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1378
1440
|
function resolveCompressionCodec2(options) {
|
|
1379
1441
|
var _a;
|
|
1380
1442
|
const compression = (_a = options.compression) != null ? _a : "lz4" /* Lz4 */;
|
|
@@ -1411,7 +1473,7 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1411
1473
|
}
|
|
1412
1474
|
}
|
|
1413
1475
|
function validateReconnect(options) {
|
|
1414
|
-
var _a, _b, _c, _d
|
|
1476
|
+
var _a, _b, _c, _d;
|
|
1415
1477
|
const enabled = (_a = options == null ? void 0 : options.enabled) != null ? _a : true;
|
|
1416
1478
|
if (!enabled) {
|
|
1417
1479
|
return;
|
|
@@ -1421,7 +1483,8 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1421
1483
|
if (((_d = options == null ? void 0 : options.backoffFactor) != null ? _d : 2) < 1) {
|
|
1422
1484
|
throw connectorError("validationFailed" /* ValidationFailed */, "Reconnect BackoffFactor must be at least 1.0.");
|
|
1423
1485
|
}
|
|
1424
|
-
|
|
1486
|
+
const maxAttempts = (options == null ? void 0 : options.maxAttempts) === void 0 ? 3 : options.maxAttempts;
|
|
1487
|
+
if (maxAttempts !== null && !(Number.isFinite(maxAttempts) && maxAttempts > 0)) {
|
|
1425
1488
|
throw connectorError("validationFailed" /* ValidationFailed */, "Reconnect MaxAttempts must be null or positive.");
|
|
1426
1489
|
}
|
|
1427
1490
|
}
|
|
@@ -1546,6 +1609,16 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1546
1609
|
__publicField(this, "queueHead", 0);
|
|
1547
1610
|
__publicField(this, "queuedCount", 0);
|
|
1548
1611
|
__publicField(this, "drainTask");
|
|
1612
|
+
// True for as long as `drain` is on the stack, handler awaits included. It
|
|
1613
|
+
// marks the execution context a registered handler runs in, so a `dispatch`
|
|
1614
|
+
// made from inside a handler is recognised as re-entry rather than a fresh
|
|
1615
|
+
// pump. It is not a lock: a single event loop admits no second thread, and
|
|
1616
|
+
// nothing ever waits for this flag to fall.
|
|
1617
|
+
__publicField(this, "draining", false);
|
|
1618
|
+
// Spec stream-connector 32 §10: arrivals per packet name on the current
|
|
1619
|
+
// connection. It is raised where a packet arrives, never where one is taken,
|
|
1620
|
+
// so consuming does not lower it and the dispatch mode does not change it.
|
|
1621
|
+
__publicField(this, "receivedCounts", /* @__PURE__ */ new Map());
|
|
1549
1622
|
}
|
|
1550
1623
|
on(name, handler) {
|
|
1551
1624
|
validateName(name);
|
|
@@ -1572,32 +1645,89 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1572
1645
|
* `Manual` needs no dispatch pump to complete a wait. The queue is scanned in
|
|
1573
1646
|
* a microtask so a message that arrived before the wait started is still
|
|
1574
1647
|
* observed, and so the caller has its subscription in hand by then.
|
|
1648
|
+
*
|
|
1649
|
+
* @param onConnectionEnded Called, instead of {@link observer}, when
|
|
1650
|
+
* {@link connectionEnded} abandons this registration because the
|
|
1651
|
+
* connection it was watching ended before a message matched (spec
|
|
1652
|
+
* stream-connector 32 §10.1.1: "연결이 끝나 대기를 이어갈 수 없으면
|
|
1653
|
+
* `Disconnected`다", released when that connection ends).
|
|
1575
1654
|
*/
|
|
1576
|
-
observe(name, observer) {
|
|
1655
|
+
observe(name, observer, onConnectionEnded) {
|
|
1577
1656
|
validateName(name);
|
|
1578
1657
|
let set = this.observers.get(name);
|
|
1579
1658
|
if (set === void 0) {
|
|
1580
1659
|
set = /* @__PURE__ */ new Set();
|
|
1581
1660
|
this.observers.set(name, set);
|
|
1582
1661
|
}
|
|
1583
|
-
|
|
1662
|
+
const registration = { consume: observer, onConnectionEnded };
|
|
1663
|
+
set.add(registration);
|
|
1584
1664
|
queueMicrotask(() => {
|
|
1585
1665
|
var _a;
|
|
1586
|
-
if (((_a = this.observers.get(name)) == null ? void 0 : _a.has(
|
|
1587
|
-
this.offerQueued(name,
|
|
1666
|
+
if (((_a = this.observers.get(name)) == null ? void 0 : _a.has(registration)) === true) {
|
|
1667
|
+
this.offerQueued(name, registration);
|
|
1588
1668
|
}
|
|
1589
1669
|
});
|
|
1590
1670
|
return subscription(() => {
|
|
1591
|
-
set.delete(
|
|
1671
|
+
set.delete(registration);
|
|
1592
1672
|
if (set.size === 0 && this.observers.get(name) === set) {
|
|
1593
1673
|
this.observers.delete(name);
|
|
1594
1674
|
}
|
|
1595
1675
|
});
|
|
1596
1676
|
}
|
|
1597
|
-
|
|
1677
|
+
/** Spec stream-connector 32 §10: arrivals under `name` on this connection. */
|
|
1678
|
+
receivedCount(name) {
|
|
1598
1679
|
var _a;
|
|
1599
|
-
|
|
1600
|
-
|
|
1680
|
+
return (_a = this.receivedCounts.get(name)) != null ? _a : 0;
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Rebaselines the queue for a connection that was just established. Spec
|
|
1684
|
+
* stream-connector 32 §10 (line ~649): the reference point is the moment a
|
|
1685
|
+
* connection is established, so counts restart at 0 and whatever the
|
|
1686
|
+
* previous connection left unconsumed goes with it — keeping the queue
|
|
1687
|
+
* while only the counts reset would let counts and queue describe two
|
|
1688
|
+
* different connections, and let `waitFor` hand back a packet from before
|
|
1689
|
+
* the drop as if the new connection had delivered it.
|
|
1690
|
+
*
|
|
1691
|
+
* `ZlinkStreamMessage`/`ZlinkStreamEncodedPayload` are plain data (name,
|
|
1692
|
+
* metadata, a `Uint8Array` payload) with no dispose/close of their own —
|
|
1693
|
+
* unlike Java's queued frames, which `closeMessage` releases — so dropping
|
|
1694
|
+
* the queue's references is the whole of the release here.
|
|
1695
|
+
*
|
|
1696
|
+
* Wait surfaces are not touched here. The ones of the previous connection
|
|
1697
|
+
* were released by {@link connectionEnded} when that connection ended, and
|
|
1698
|
+
* one registered since then is waiting for this connection.
|
|
1699
|
+
*/
|
|
1700
|
+
resetForNewConnection() {
|
|
1701
|
+
this.receivedCounts.clear();
|
|
1702
|
+
this.queue.length = 0;
|
|
1703
|
+
this.queueHead = 0;
|
|
1704
|
+
this.queuedCount = 0;
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Releases every registered wait surface because the connection it was
|
|
1708
|
+
* watching has ended — a transport loss, a server close, or `close()`.
|
|
1709
|
+
* Spec stream-connector 32 §10.1.1: "푸는 시점은 연결이 끝난 때이지 다음
|
|
1710
|
+
* 연결이 성립한 때가 아니다". The release belongs to the ending, so a wait
|
|
1711
|
+
* does not hang until its own timeout when no next connection comes
|
|
1712
|
+
* (reconnect off, attempts spent) and does not silently rebind to the next
|
|
1713
|
+
* one when it does. The queue and the counts stay: they are rebaselined by
|
|
1714
|
+
* the next {@link resetForNewConnection}, not by the ending (§10).
|
|
1715
|
+
*/
|
|
1716
|
+
connectionEnded() {
|
|
1717
|
+
if (this.observers.size === 0) {
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
const abandoned = [...this.observers.values()].flatMap((set) => [...set]);
|
|
1721
|
+
this.observers.clear();
|
|
1722
|
+
for (const registration of abandoned) {
|
|
1723
|
+
registration.onConnectionEnded();
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
enqueue(message, signal) {
|
|
1727
|
+
var _a, _b;
|
|
1728
|
+
this.receivedCounts.set(message.name, ((_a = this.receivedCounts.get(message.name)) != null ? _a : 0) + 1);
|
|
1729
|
+
for (const registration of [...(_b = this.observers.get(message.name)) != null ? _b : []]) {
|
|
1730
|
+
if (registration.consume(message)) {
|
|
1601
1731
|
return;
|
|
1602
1732
|
}
|
|
1603
1733
|
}
|
|
@@ -1610,18 +1740,28 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1610
1740
|
/**
|
|
1611
1741
|
* Runs the registered handlers the receive path left queued. `Manual` calls
|
|
1612
1742
|
* this from `dispatch`; `Immediate` has already drained on arrival.
|
|
1743
|
+
*
|
|
1744
|
+
* A handler that calls `dispatch` arrives back here from inside the drain it
|
|
1745
|
+
* was started by. `scheduleDrain` would find `drainTask` already set and
|
|
1746
|
+
* return, and the await below would then be the drain waiting on itself —
|
|
1747
|
+
* a deadlock with neither timeout nor error. The drain loop already takes
|
|
1748
|
+
* every message a handler exists for, so there is nothing a second drain
|
|
1749
|
+
* would deliver and returning is the whole of the correct behaviour.
|
|
1613
1750
|
*/
|
|
1614
1751
|
async pump() {
|
|
1752
|
+
if (this.draining) {
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1615
1755
|
this.scheduleDrain();
|
|
1616
1756
|
await this.drainTask;
|
|
1617
1757
|
}
|
|
1618
|
-
offerQueued(name,
|
|
1758
|
+
offerQueued(name, registration) {
|
|
1619
1759
|
for (let index = this.queueHead; index < this.queue.length; index += 1) {
|
|
1620
1760
|
const queued = this.queue[index];
|
|
1621
1761
|
if (queued === void 0 || queued.message.name !== name) {
|
|
1622
1762
|
continue;
|
|
1623
1763
|
}
|
|
1624
|
-
if (!
|
|
1764
|
+
if (!registration.consume(queued.message)) {
|
|
1625
1765
|
continue;
|
|
1626
1766
|
}
|
|
1627
1767
|
this.removeAt(index);
|
|
@@ -1640,23 +1780,28 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1640
1780
|
});
|
|
1641
1781
|
}
|
|
1642
1782
|
async drain() {
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1783
|
+
this.draining = true;
|
|
1784
|
+
try {
|
|
1785
|
+
for (let index = this.findDeliverableIndex(); index >= 0; index = this.findDeliverableIndex()) {
|
|
1786
|
+
const queued = this.queue[index];
|
|
1787
|
+
if (queued === void 0) continue;
|
|
1788
|
+
this.removeAt(index);
|
|
1789
|
+
const { message, signal } = queued;
|
|
1790
|
+
const handlers = [...this.handlers.get(message.name)];
|
|
1791
|
+
for (const handler of handlers) {
|
|
1792
|
+
try {
|
|
1793
|
+
await handler(message, signal);
|
|
1794
|
+
} catch (cause) {
|
|
1795
|
+
await this.events.publishError({
|
|
1796
|
+
code: "userCallbackFailed" /* UserCallbackFailed */,
|
|
1797
|
+
message: "Typed message handler failed.",
|
|
1798
|
+
cause
|
|
1799
|
+
}, signal);
|
|
1800
|
+
}
|
|
1658
1801
|
}
|
|
1659
1802
|
}
|
|
1803
|
+
} finally {
|
|
1804
|
+
this.draining = false;
|
|
1660
1805
|
}
|
|
1661
1806
|
}
|
|
1662
1807
|
removeAt(index) {
|
|
@@ -1702,10 +1847,9 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1702
1847
|
|
|
1703
1848
|
// packages/stream-connector/src/Runtime/ZlinkStreamFrameSender.ts
|
|
1704
1849
|
var ZlinkStreamFrameSender = class {
|
|
1705
|
-
constructor(protocol, flowContext
|
|
1850
|
+
constructor(protocol, flowContext) {
|
|
1706
1851
|
this.protocol = protocol;
|
|
1707
1852
|
this.flowContext = flowContext;
|
|
1708
|
-
this.metrics = metrics;
|
|
1709
1853
|
__publicField(this, "pendingWrites", /* @__PURE__ */ new Set());
|
|
1710
1854
|
}
|
|
1711
1855
|
async send(connection, kind, name, payload, metadata, compress, requestSeq, signal, correlationId, explicitFlow) {
|
|
@@ -1731,7 +1875,6 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1731
1875
|
this.pendingWrites.add(write);
|
|
1732
1876
|
try {
|
|
1733
1877
|
await write;
|
|
1734
|
-
this.metrics.outbound(frame.byteLength);
|
|
1735
1878
|
} finally {
|
|
1736
1879
|
this.pendingWrites.delete(write);
|
|
1737
1880
|
}
|
|
@@ -1760,17 +1903,29 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1760
1903
|
|
|
1761
1904
|
// packages/stream-connector/src/Runtime/ZlinkStreamReceiveDispatcher.ts
|
|
1762
1905
|
var ZlinkStreamReceiveDispatcher = class {
|
|
1763
|
-
constructor(protocol, pendingRequests, receivedMessages, frameSender, events, flowContext,
|
|
1906
|
+
constructor(protocol, pendingRequests, receivedMessages, frameSender, events, flowContext, serverClosing) {
|
|
1764
1907
|
this.protocol = protocol;
|
|
1765
1908
|
this.pendingRequests = pendingRequests;
|
|
1766
1909
|
this.receivedMessages = receivedMessages;
|
|
1767
1910
|
this.frameSender = frameSender;
|
|
1768
1911
|
this.events = events;
|
|
1769
1912
|
this.flowContext = flowContext;
|
|
1770
|
-
this.metrics = metrics;
|
|
1771
1913
|
this.serverClosing = serverClosing;
|
|
1772
1914
|
}
|
|
1773
|
-
|
|
1915
|
+
/**
|
|
1916
|
+
* @param isCurrent Tells whether the connection this batch was read from is
|
|
1917
|
+
* still the connector's connection. It is asked again at the head of every
|
|
1918
|
+
* frame, not only after the read: `dispatch` awaits application code —
|
|
1919
|
+
* registered handlers in `Immediate`, the error handler on a failed frame —
|
|
1920
|
+
* and a disconnect and reconnect can complete inside that await. Without
|
|
1921
|
+
* the recheck the rest of a dead connection's batch lands on the new
|
|
1922
|
+
* connection's state, and the §10 arrival counts, which start at 0 when a
|
|
1923
|
+
* connection is established, take the stale frames on top.
|
|
1924
|
+
* @param connectionForSend Resolves the connection a reply belongs on at the
|
|
1925
|
+
* moment it is written. The captured `connection` is the one the batch was
|
|
1926
|
+
* read from, which a reconnect may already have replaced.
|
|
1927
|
+
*/
|
|
1928
|
+
async readAndDispatch(connection, signal, isCurrent, connectionForSend) {
|
|
1774
1929
|
if ((connection == null ? void 0 : connection.read) === void 0) {
|
|
1775
1930
|
return { available: false, inbound: false };
|
|
1776
1931
|
}
|
|
@@ -1781,7 +1936,6 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1781
1936
|
if (frameBytes === void 0) {
|
|
1782
1937
|
return { available: false, inbound: false };
|
|
1783
1938
|
}
|
|
1784
|
-
this.metrics.inbound(frameBytes.byteLength);
|
|
1785
1939
|
const flowEnabled = this.protocol.flowEnabled();
|
|
1786
1940
|
let frames;
|
|
1787
1941
|
try {
|
|
@@ -1794,8 +1948,11 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1794
1948
|
return { available: true, inbound: false };
|
|
1795
1949
|
}
|
|
1796
1950
|
for (const frame of frames) {
|
|
1951
|
+
if (isCurrent !== void 0 && !isCurrent()) {
|
|
1952
|
+
break;
|
|
1953
|
+
}
|
|
1797
1954
|
try {
|
|
1798
|
-
await this.dispatch(connection, frame.header, frame.payload, signal, flowEnabled);
|
|
1955
|
+
await this.dispatch(connection, frame.header, frame.payload, signal, flowEnabled, connectionForSend);
|
|
1799
1956
|
} catch (cause) {
|
|
1800
1957
|
if (frame.header.kind === 5 /* Control */ && frame.header.name === ZLINK_STREAM_HEARTBEAT_PING) {
|
|
1801
1958
|
throw cause;
|
|
@@ -1808,7 +1965,7 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1808
1965
|
}
|
|
1809
1966
|
return { available: true, inbound: true };
|
|
1810
1967
|
}
|
|
1811
|
-
async dispatch(connection, header, payload, signal, flowEnabled) {
|
|
1968
|
+
async dispatch(connection, header, payload, signal, flowEnabled, connectionForSend) {
|
|
1812
1969
|
if (header.kind === 3 /* Response */ && header.requestSeq !== void 0) {
|
|
1813
1970
|
try {
|
|
1814
1971
|
if (!this.pendingRequests.resolve(header.requestSeq, {
|
|
@@ -1861,7 +2018,7 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1861
2018
|
return;
|
|
1862
2019
|
}
|
|
1863
2020
|
if (header.kind === 5 /* Control */) {
|
|
1864
|
-
await this.dispatchControl(connection, header, payload, signal);
|
|
2021
|
+
await this.dispatchControl(connection, header, payload, signal, connectionForSend);
|
|
1865
2022
|
return;
|
|
1866
2023
|
}
|
|
1867
2024
|
if (header.kind === 1 /* Send */) {
|
|
@@ -1875,8 +2032,8 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1875
2032
|
}, signal);
|
|
1876
2033
|
}
|
|
1877
2034
|
}
|
|
1878
|
-
async dispatchControl(connection, header, payload, signal) {
|
|
1879
|
-
var _a;
|
|
2035
|
+
async dispatchControl(connection, header, payload, signal, connectionForSend) {
|
|
2036
|
+
var _a, _b;
|
|
1880
2037
|
if (header.name === ZLINK_SESSION_CLOSING) {
|
|
1881
2038
|
const closing = decodeSessionClosing(payload);
|
|
1882
2039
|
await ((_a = this.serverClosing) == null ? void 0 : _a.call(this, closing.closeReason));
|
|
@@ -1887,7 +2044,11 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1887
2044
|
}
|
|
1888
2045
|
if (header.name === ZLINK_STREAM_HEARTBEAT_PING) {
|
|
1889
2046
|
try {
|
|
1890
|
-
await this.frameSender.sendControl(
|
|
2047
|
+
await this.frameSender.sendControl(
|
|
2048
|
+
(_b = connectionForSend == null ? void 0 : connectionForSend()) != null ? _b : connection,
|
|
2049
|
+
ZLINK_STREAM_HEARTBEAT_PONG,
|
|
2050
|
+
signal
|
|
2051
|
+
);
|
|
1891
2052
|
} catch (cause) {
|
|
1892
2053
|
throw connectorError(
|
|
1893
2054
|
"sendFailed" /* SendFailed */,
|
|
@@ -1920,15 +2081,17 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1920
2081
|
}
|
|
1921
2082
|
|
|
1922
2083
|
// packages/stream-connector/src/Runtime/ZlinkStreamConnectorLifecycle.ts
|
|
2084
|
+
function randomizedDelay(baseDelayMs) {
|
|
2085
|
+
return Math.round(baseDelayMs * (0.5 + Math.random() * 0.5));
|
|
2086
|
+
}
|
|
1923
2087
|
var ZlinkStreamConnectorLifecycle = class {
|
|
1924
|
-
constructor(options, pendingRequests, frameSender, receiveDispatcher, receivedMessages, events
|
|
2088
|
+
constructor(options, pendingRequests, frameSender, receiveDispatcher, receivedMessages, events) {
|
|
1925
2089
|
this.options = options;
|
|
1926
2090
|
this.pendingRequests = pendingRequests;
|
|
1927
2091
|
this.frameSender = frameSender;
|
|
1928
2092
|
this.receiveDispatcher = receiveDispatcher;
|
|
1929
2093
|
this.receivedMessages = receivedMessages;
|
|
1930
2094
|
this.events = events;
|
|
1931
|
-
this.metrics = metrics;
|
|
1932
2095
|
__publicField(this, "receiveLoopAbort");
|
|
1933
2096
|
__publicField(this, "receiveLoopSleeping", false);
|
|
1934
2097
|
__publicField(this, "receiveLoopWake");
|
|
@@ -1937,6 +2100,7 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1937
2100
|
__publicField(this, "connectionGeneration", 0);
|
|
1938
2101
|
__publicField(this, "currentState", "created" /* Created */);
|
|
1939
2102
|
__publicField(this, "heartbeatTimer");
|
|
2103
|
+
__publicField(this, "heartbeatTickRunning", false);
|
|
1940
2104
|
__publicField(this, "lastInboundAt", 0);
|
|
1941
2105
|
__publicField(this, "closeTask");
|
|
1942
2106
|
__publicField(this, "connectTask");
|
|
@@ -1974,6 +2138,7 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1974
2138
|
return await this.connectTask;
|
|
1975
2139
|
}
|
|
1976
2140
|
async connectOnce(signal) {
|
|
2141
|
+
var _a;
|
|
1977
2142
|
await this.setState("connecting" /* Connecting */, void 0, signal);
|
|
1978
2143
|
try {
|
|
1979
2144
|
const connection = await this.connectWithReconnect(signal);
|
|
@@ -1989,6 +2154,7 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
1989
2154
|
this.currentConnection = connection;
|
|
1990
2155
|
this.connectionGeneration += 1;
|
|
1991
2156
|
this.disconnectedPublished = false;
|
|
2157
|
+
this.receivedMessages.resetForNewConnection();
|
|
1992
2158
|
this.lastInboundAt = Date.now();
|
|
1993
2159
|
await this.setState("connected" /* Connected */, void 0, signal);
|
|
1994
2160
|
this.startHeartbeat();
|
|
@@ -2000,7 +2166,9 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2000
2166
|
throw new ZlinkStreamException(error2);
|
|
2001
2167
|
}
|
|
2002
2168
|
const error = toStreamError(cause, "connectTimeout" /* ConnectTimeout */, "Connect failed.");
|
|
2169
|
+
(_a = this.closeReasonValue) != null ? _a : this.closeReasonValue = "TransportError";
|
|
2003
2170
|
await this.setState("disconnected" /* Disconnected */, error, signal);
|
|
2171
|
+
await this.publishDisconnectedOnce(signal);
|
|
2004
2172
|
throw new ZlinkStreamException(error);
|
|
2005
2173
|
}
|
|
2006
2174
|
}
|
|
@@ -2047,8 +2215,9 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2047
2215
|
errors.push(error);
|
|
2048
2216
|
}
|
|
2049
2217
|
this.pendingRequests.failAll({ code: "disconnected" /* Disconnected */, message: "Connector closed." });
|
|
2218
|
+
this.receivedMessages.connectionEnded();
|
|
2050
2219
|
await this.setState("closed" /* Closed */, void 0, signal);
|
|
2051
|
-
|
|
2220
|
+
this.publishDisconnectedWithoutWaiting(signal);
|
|
2052
2221
|
if (errors.length === 1) throw errors[0];
|
|
2053
2222
|
if (errors.length > 1) throw new AggregateError(errors, "Stream connector close failed.");
|
|
2054
2223
|
}
|
|
@@ -2079,7 +2248,8 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2079
2248
|
const result = await this.receiveDispatcher.readAndDispatch(
|
|
2080
2249
|
connection,
|
|
2081
2250
|
signal,
|
|
2082
|
-
() => this.isCurrentConnection(connection, generation)
|
|
2251
|
+
() => this.isCurrentConnection(connection, generation),
|
|
2252
|
+
() => this.connectionForSend()
|
|
2083
2253
|
);
|
|
2084
2254
|
if (result.inbound && this.isCurrentConnection(connection, generation)) {
|
|
2085
2255
|
this.lastInboundAt = Date.now();
|
|
@@ -2091,25 +2261,18 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2091
2261
|
let delayMs = this.options.reconnect.initialDelayMs;
|
|
2092
2262
|
let lastError;
|
|
2093
2263
|
const maxAttempts = this.options.reconnect.enabled ? this.options.reconnect.maxAttempts : 1;
|
|
2094
|
-
|
|
2264
|
+
const unlimited = maxAttempts === null;
|
|
2265
|
+
while (unlimited || attempt < maxAttempts) {
|
|
2095
2266
|
attempt += 1;
|
|
2096
|
-
if (attempt > 1) {
|
|
2097
|
-
this.metrics.reconnect();
|
|
2098
|
-
}
|
|
2099
|
-
const handshakeStartedAt = performance.now();
|
|
2100
2267
|
try {
|
|
2101
|
-
|
|
2102
|
-
this.metrics.handshakeCompleted(handshakeStartedAt);
|
|
2103
|
-
return connection;
|
|
2268
|
+
return await this.options.transportFactory.connect(this.options, signal);
|
|
2104
2269
|
} catch (cause) {
|
|
2105
|
-
this.metrics.handshakeCompleted(handshakeStartedAt);
|
|
2106
|
-
this.metrics.handshakeFailed(cause);
|
|
2107
2270
|
lastError = toStreamError(cause, "connectTimeout" /* ConnectTimeout */, "Connect failed.");
|
|
2108
|
-
if (!this.options.reconnect.enabled || attempt >= maxAttempts) {
|
|
2271
|
+
if (!this.options.reconnect.enabled || !unlimited && attempt >= maxAttempts) {
|
|
2109
2272
|
break;
|
|
2110
2273
|
}
|
|
2111
2274
|
await this.setState("reconnecting" /* Reconnecting */, lastError, signal);
|
|
2112
|
-
await delay(delayMs, signal);
|
|
2275
|
+
await delay(randomizedDelay(delayMs), signal);
|
|
2113
2276
|
delayMs = Math.min(
|
|
2114
2277
|
this.options.reconnect.maxDelayMs,
|
|
2115
2278
|
Math.ceil(delayMs * this.options.reconnect.backoffFactor)
|
|
@@ -2125,15 +2288,25 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2125
2288
|
if (!this.options.heartbeat.enabled) {
|
|
2126
2289
|
return;
|
|
2127
2290
|
}
|
|
2128
|
-
|
|
2129
|
-
|
|
2291
|
+
const timer = setInterval(() => {
|
|
2292
|
+
if (this.heartbeatTickRunning) {
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
this.heartbeatTickRunning = true;
|
|
2296
|
+
void this.runHeartbeatTick().finally(() => {
|
|
2297
|
+
if (this.heartbeatTimer === timer) {
|
|
2298
|
+
this.heartbeatTickRunning = false;
|
|
2299
|
+
}
|
|
2300
|
+
});
|
|
2130
2301
|
}, this.options.heartbeat.intervalMs);
|
|
2302
|
+
this.heartbeatTimer = timer;
|
|
2131
2303
|
}
|
|
2132
2304
|
stopHeartbeat() {
|
|
2133
2305
|
if (this.heartbeatTimer !== void 0) {
|
|
2134
2306
|
clearInterval(this.heartbeatTimer);
|
|
2135
2307
|
this.heartbeatTimer = void 0;
|
|
2136
2308
|
}
|
|
2309
|
+
this.heartbeatTickRunning = false;
|
|
2137
2310
|
}
|
|
2138
2311
|
// Spec stream-connector 32 §7: the receive loop runs in both dispatch modes.
|
|
2139
2312
|
// `Manual` only changes what the loop does with a frame — it queues the
|
|
@@ -2252,41 +2425,96 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2252
2425
|
if (this.disconnectTask !== void 0) {
|
|
2253
2426
|
return await this.disconnectTask;
|
|
2254
2427
|
}
|
|
2255
|
-
this.disconnectTask = this.
|
|
2428
|
+
this.disconnectTask = this.tearDownConnection(error).finally(() => {
|
|
2256
2429
|
this.disconnectTask = void 0;
|
|
2257
2430
|
});
|
|
2258
|
-
|
|
2431
|
+
await this.disconnectTask;
|
|
2432
|
+
await this.announceDisconnect(error);
|
|
2259
2433
|
}
|
|
2260
2434
|
isCurrentConnection(connection, generation) {
|
|
2261
2435
|
return !this.closeRequested && this.currentConnection === connection && this.connectionGeneration === generation;
|
|
2262
2436
|
}
|
|
2263
|
-
|
|
2437
|
+
/** Transport teardown only — no application callback runs from here. */
|
|
2438
|
+
async tearDownConnection(error) {
|
|
2264
2439
|
this.stopHeartbeat();
|
|
2265
2440
|
this.stopReceiveLoop();
|
|
2266
2441
|
const connection = this.currentConnection;
|
|
2267
2442
|
this.currentConnection = void 0;
|
|
2268
2443
|
this.pendingRequests.failAll(error);
|
|
2444
|
+
this.receivedMessages.connectionEnded();
|
|
2269
2445
|
try {
|
|
2270
2446
|
await (connection == null ? void 0 : connection.close());
|
|
2271
2447
|
} catch {
|
|
2272
2448
|
}
|
|
2449
|
+
}
|
|
2450
|
+
/**
|
|
2451
|
+
* Runs once the teardown promise has settled, so a handler reached from here
|
|
2452
|
+
* may call `connect` without waiting for a task its own caller still holds.
|
|
2453
|
+
* The reconnect is queued before the notification is awaited for the same
|
|
2454
|
+
* reason: spec stream-connector 32 §6 has reconnect on by default, and a
|
|
2455
|
+
* handler that is slow — or whose promise never settles at all — must not
|
|
2456
|
+
* cost the connector the attempt. The state is already `Disconnected` and the
|
|
2457
|
+
* state handlers are already invoked by the time the queued microtask runs,
|
|
2458
|
+
* because `setState` records the state and hands the change to the handlers
|
|
2459
|
+
* before it awaits any of them.
|
|
2460
|
+
*/
|
|
2461
|
+
async announceDisconnect(error) {
|
|
2273
2462
|
if (this.closeRequested) return;
|
|
2274
|
-
|
|
2275
|
-
|
|
2463
|
+
const announce = this.claimDisconnectedPublish();
|
|
2464
|
+
const notified = (async () => {
|
|
2465
|
+
await this.setState("disconnected" /* Disconnected */, error);
|
|
2466
|
+
if (announce) {
|
|
2467
|
+
await this.events.publishDisconnected();
|
|
2468
|
+
}
|
|
2469
|
+
})();
|
|
2276
2470
|
if (this.shouldReconnect()) {
|
|
2277
2471
|
queueMicrotask(() => {
|
|
2278
2472
|
void this.connect().catch(() => void 0);
|
|
2279
2473
|
});
|
|
2280
2474
|
}
|
|
2475
|
+
await notified;
|
|
2281
2476
|
}
|
|
2282
2477
|
shouldReconnect() {
|
|
2283
2478
|
return this.options.reconnect.enabled && !this.closeRequested;
|
|
2284
2479
|
}
|
|
2285
|
-
|
|
2286
|
-
|
|
2480
|
+
// Synchronous test-and-set, taken before any await, so the single disconnect
|
|
2481
|
+
// notification the spec promises is claimed by exactly one caller even when
|
|
2482
|
+
// the publishing itself is deferred.
|
|
2483
|
+
claimDisconnectedPublish() {
|
|
2484
|
+
if (this.disconnectedPublished) return false;
|
|
2287
2485
|
this.disconnectedPublished = true;
|
|
2486
|
+
return true;
|
|
2487
|
+
}
|
|
2488
|
+
async publishDisconnectedOnce(signal) {
|
|
2489
|
+
if (!this.claimDisconnectedPublish()) return;
|
|
2288
2490
|
await this.events.publishDisconnected(signal);
|
|
2289
2491
|
}
|
|
2492
|
+
/**
|
|
2493
|
+
* Spec stream-connector 32 §7: the connector runs the registered handler, it
|
|
2494
|
+
* does not wait for it to finish. `close` returns once its own work is done —
|
|
2495
|
+
* the drain, the transport close, the pending requests — having run the
|
|
2496
|
+
* disconnect handler but without looking at whether that handler has ended.
|
|
2497
|
+
*
|
|
2498
|
+
* Starting the publish without awaiting it still runs every handler right
|
|
2499
|
+
* here: an `async` function body runs synchronously up to its first `await`,
|
|
2500
|
+
* and `publishDisconnected` reaches each handler before that point. So the
|
|
2501
|
+
* handler has run by the time `close` returns, which is what the spec asks
|
|
2502
|
+
* for, while a handler that calls `close` no longer waits for the very
|
|
2503
|
+
* `closeTask` it is running inside. Java and C++ hand the handler to a queue
|
|
2504
|
+
* and return the same way.
|
|
2505
|
+
*
|
|
2506
|
+
* `claimDisconnectedPublish` is taken by the same call and before any await,
|
|
2507
|
+
* so deferring the completion never turns the one notification into two.
|
|
2508
|
+
*
|
|
2509
|
+
* Nothing awaits the promise, so a rejection would reach the process as an
|
|
2510
|
+
* unhandled rejection and kill it. Handler failures are already contained
|
|
2511
|
+
* where they were before this call stopped waiting — the `Promise.allSettled`
|
|
2512
|
+
* in `publishDisconnected` — and this `catch` covers what is left rather than
|
|
2513
|
+
* reporting the same failure a second time on the error surface.
|
|
2514
|
+
*/
|
|
2515
|
+
publishDisconnectedWithoutWaiting(signal) {
|
|
2516
|
+
void this.publishDisconnectedOnce(signal).catch(() => void 0);
|
|
2517
|
+
}
|
|
2290
2518
|
async setState(current, error, signal) {
|
|
2291
2519
|
const previous = this.currentState;
|
|
2292
2520
|
this.currentState = current;
|
|
@@ -2470,9 +2698,6 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2470
2698
|
this.readWaiter = void 0;
|
|
2471
2699
|
resolve();
|
|
2472
2700
|
};
|
|
2473
|
-
if (this.hasQueuedMessage() || this.closed) {
|
|
2474
|
-
this.wakeReader();
|
|
2475
|
-
}
|
|
2476
2701
|
});
|
|
2477
2702
|
}
|
|
2478
2703
|
wakeReader() {
|
|
@@ -2553,66 +2778,6 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2553
2778
|
throw connectorError("frameDecodeFailed" /* FrameDecodeFailed */, "WebSocket text messages are not supported.");
|
|
2554
2779
|
}
|
|
2555
2780
|
|
|
2556
|
-
// packages/stream-connector/src/Runtime/ZlinkStreamRuntimeMetrics.ts
|
|
2557
|
-
var ZlinkStreamRuntimeMetrics = class {
|
|
2558
|
-
constructor(options) {
|
|
2559
|
-
this.options = options;
|
|
2560
|
-
__publicField(this, "reconnects");
|
|
2561
|
-
__publicField(this, "handshakeDuration");
|
|
2562
|
-
__publicField(this, "handshakeFailures");
|
|
2563
|
-
__publicField(this, "inboundBytes");
|
|
2564
|
-
__publicField(this, "outboundBytes");
|
|
2565
|
-
var _a;
|
|
2566
|
-
const meter = (_a = options.meterProvider) == null ? void 0 : _a.getMeter("zlink.framework");
|
|
2567
|
-
this.reconnects = meter == null ? void 0 : meter.createCounter("zlink.stream.reconnects", { unit: "{event}" });
|
|
2568
|
-
this.handshakeDuration = meter == null ? void 0 : meter.createHistogram("zlink.stream.handshake.duration", { unit: "s" });
|
|
2569
|
-
this.handshakeFailures = meter == null ? void 0 : meter.createCounter("zlink.stream.handshake.failures", { unit: "{failure}" });
|
|
2570
|
-
this.inboundBytes = meter == null ? void 0 : meter.createCounter("zlink.stream.inbound.bytes", { unit: "By" });
|
|
2571
|
-
this.outboundBytes = meter == null ? void 0 : meter.createCounter("zlink.stream.outbound.bytes", { unit: "By" });
|
|
2572
|
-
}
|
|
2573
|
-
reconnect() {
|
|
2574
|
-
this.safe(() => {
|
|
2575
|
-
var _a;
|
|
2576
|
-
return (_a = this.reconnects) == null ? void 0 : _a.add(1, { transport: this.transportLabel() });
|
|
2577
|
-
});
|
|
2578
|
-
}
|
|
2579
|
-
handshakeCompleted(startedAt) {
|
|
2580
|
-
const seconds = (performance.now() - startedAt) / 1e3;
|
|
2581
|
-
this.safe(() => {
|
|
2582
|
-
var _a;
|
|
2583
|
-
return (_a = this.handshakeDuration) == null ? void 0 : _a.record(seconds, { transport: this.transportLabel() });
|
|
2584
|
-
});
|
|
2585
|
-
}
|
|
2586
|
-
handshakeFailed(error) {
|
|
2587
|
-
const reason = error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError") ? "canceled" : "transport_error";
|
|
2588
|
-
this.safe(() => {
|
|
2589
|
-
var _a;
|
|
2590
|
-
return (_a = this.handshakeFailures) == null ? void 0 : _a.add(1, { transport: this.transportLabel(), reason });
|
|
2591
|
-
});
|
|
2592
|
-
}
|
|
2593
|
-
inbound(byteCount) {
|
|
2594
|
-
this.safe(() => {
|
|
2595
|
-
var _a;
|
|
2596
|
-
return (_a = this.inboundBytes) == null ? void 0 : _a.add(byteCount, { transport: this.transportLabel() });
|
|
2597
|
-
});
|
|
2598
|
-
}
|
|
2599
|
-
outbound(byteCount) {
|
|
2600
|
-
this.safe(() => {
|
|
2601
|
-
var _a;
|
|
2602
|
-
return (_a = this.outboundBytes) == null ? void 0 : _a.add(byteCount, { transport: this.transportLabel() });
|
|
2603
|
-
});
|
|
2604
|
-
}
|
|
2605
|
-
transportLabel() {
|
|
2606
|
-
return this.options.endpoint.startsWith("wss:") ? "wss" : "ws";
|
|
2607
|
-
}
|
|
2608
|
-
safe(record) {
|
|
2609
|
-
try {
|
|
2610
|
-
record();
|
|
2611
|
-
} catch {
|
|
2612
|
-
}
|
|
2613
|
-
}
|
|
2614
|
-
};
|
|
2615
|
-
|
|
2616
2781
|
// packages/stream-connector/src/Runtime/ZlinkStreamConnector.ts
|
|
2617
2782
|
var DefaultZlinkStreamConnector = class {
|
|
2618
2783
|
constructor(options) {
|
|
@@ -2633,9 +2798,8 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2633
2798
|
configurable: true,
|
|
2634
2799
|
get: () => this.diagnosticsLevelCell.level
|
|
2635
2800
|
});
|
|
2636
|
-
const metrics = new ZlinkStreamRuntimeMetrics(this.options);
|
|
2637
2801
|
const protocol = new ZlinkStreamFrameProtocol(this.options);
|
|
2638
|
-
this.frameSender = new ZlinkStreamFrameSender(protocol, flowContext
|
|
2802
|
+
this.frameSender = new ZlinkStreamFrameSender(protocol, flowContext);
|
|
2639
2803
|
this.receivedMessages = new ZlinkStreamReceivedMessages(
|
|
2640
2804
|
this.events,
|
|
2641
2805
|
this.options.dispatchMode === "immediate" /* Immediate */
|
|
@@ -2647,7 +2811,6 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2647
2811
|
this.frameSender,
|
|
2648
2812
|
this.events,
|
|
2649
2813
|
flowContext,
|
|
2650
|
-
metrics,
|
|
2651
2814
|
(reason) => this.lifecycle.serverClosing(reason)
|
|
2652
2815
|
);
|
|
2653
2816
|
this.lifecycle = new ZlinkStreamConnectorLifecycle(
|
|
@@ -2656,8 +2819,7 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2656
2819
|
this.frameSender,
|
|
2657
2820
|
this.receiveDispatcher,
|
|
2658
2821
|
this.receivedMessages,
|
|
2659
|
-
this.events
|
|
2660
|
-
metrics
|
|
2822
|
+
this.events
|
|
2661
2823
|
);
|
|
2662
2824
|
}
|
|
2663
2825
|
get isConnected() {
|
|
@@ -2672,6 +2834,17 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2672
2834
|
get pendingDispatchCount() {
|
|
2673
2835
|
return this.pendingRequests.count;
|
|
2674
2836
|
}
|
|
2837
|
+
/**
|
|
2838
|
+
* Spec stream-connector 32 §10: how many packets carrying `name` arrived on
|
|
2839
|
+
* the current connection. Arrivals are what is counted, so a message a
|
|
2840
|
+
* handler dispatched or a wait surface consumed still counts, and `Manual`
|
|
2841
|
+
* and `Immediate` report the same number. Establishing a connection resets
|
|
2842
|
+
* the count to 0, a reconnect included.
|
|
2843
|
+
*/
|
|
2844
|
+
receivedCount(name) {
|
|
2845
|
+
validateName(name);
|
|
2846
|
+
return this.receivedMessages.receivedCount(name);
|
|
2847
|
+
}
|
|
2675
2848
|
/**
|
|
2676
2849
|
* Current diagnostics level (spec 26 §4.1, spec stream-connector 32 §13).
|
|
2677
2850
|
* Reflects the level set by the most recent {@link setDiagnosticsLevel}
|
|
@@ -2687,14 +2860,20 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2687
2860
|
* state update: it applies to processing points that read the level after
|
|
2688
2861
|
* this call returns and is never applied retroactively to frames already
|
|
2689
2862
|
* built. Rejects unknown values with {@link ZlinkStreamErrorCode.ConfigurationError}.
|
|
2690
|
-
*
|
|
2691
|
-
*
|
|
2863
|
+
* Spec stream-connector 32 §13: this surface changes the value without
|
|
2864
|
+
* waiting for anything; it is not a blocking call over the asynchronous pair,
|
|
2865
|
+
* which a receive callback would otherwise make wait for its own completion.
|
|
2692
2866
|
*/
|
|
2693
2867
|
setDiagnosticsLevel(level) {
|
|
2694
|
-
|
|
2868
|
+
this.diagnosticsLevelCell.set(level);
|
|
2695
2869
|
}
|
|
2870
|
+
/**
|
|
2871
|
+
* Asynchronous counterpart of {@link setDiagnosticsLevel} (spec
|
|
2872
|
+
* stream-connector 32 §13). It changes the same value; awaiting it is how a
|
|
2873
|
+
* caller observes the change, and it never replaces the synchronous surface.
|
|
2874
|
+
*/
|
|
2696
2875
|
setDiagnosticsLevelAsync(level) {
|
|
2697
|
-
this.
|
|
2876
|
+
this.setDiagnosticsLevel(level);
|
|
2698
2877
|
return Promise.resolve();
|
|
2699
2878
|
}
|
|
2700
2879
|
onErrorReceived(handler) {
|
|
@@ -2733,18 +2912,41 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2733
2912
|
}, signal);
|
|
2734
2913
|
return this.receivedMessages.on(name, encodedHandler);
|
|
2735
2914
|
}
|
|
2736
|
-
waitFor(
|
|
2737
|
-
|
|
2738
|
-
return new ZlinkStreamWaitBuilder(this, name);
|
|
2915
|
+
waitFor(nameOrType) {
|
|
2916
|
+
return new ZlinkStreamWaitBuilder(this, this.observedName(nameOrType));
|
|
2739
2917
|
}
|
|
2740
|
-
expectNone(
|
|
2741
|
-
|
|
2742
|
-
|
|
2918
|
+
expectNone(nameOrType) {
|
|
2919
|
+
return new ZlinkStreamExpectNoneBuilder(this, this.observedName(nameOrType));
|
|
2920
|
+
}
|
|
2921
|
+
waitForSequence(nameOrType) {
|
|
2922
|
+
return new ZlinkStreamSequenceBuilder(this, this.observedName(nameOrType));
|
|
2743
2923
|
}
|
|
2744
|
-
|
|
2924
|
+
/**
|
|
2925
|
+
* Spec stream-connector 32 §10.1: each wait surface offers both ways of
|
|
2926
|
+
* naming a packet. A string is the name the caller states; a constructor
|
|
2927
|
+
* goes through the options' `nameResolver`, which is the same resolver
|
|
2928
|
+
* `send` and `request` use, so both paths land on one name for one type.
|
|
2929
|
+
*/
|
|
2930
|
+
observedName(nameOrType) {
|
|
2931
|
+
const name = typeof nameOrType === "function" ? this.options.nameResolver.resolve(nameOrType) : nameOrType;
|
|
2932
|
+
if (typeof name !== "string") {
|
|
2933
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "Packet name must be a string or a payload constructor.");
|
|
2934
|
+
}
|
|
2745
2935
|
validateName(name);
|
|
2746
|
-
return
|
|
2936
|
+
return name;
|
|
2747
2937
|
}
|
|
2938
|
+
/**
|
|
2939
|
+
* Consumes the first message under `name` that `predicate` accepts, or
|
|
2940
|
+
* resolves `undefined` when `timeoutMs` elapses first.
|
|
2941
|
+
*
|
|
2942
|
+
* A timeout is not a failure here. Each wait surface decides what its own
|
|
2943
|
+
* timeout means — `waitFor` fails on it while `expectNone` succeeds — and
|
|
2944
|
+
* reports that decision as `ValidationFailed` (spec stream-connector 32
|
|
2945
|
+
* §10.1.1; .NET `ZlinkStreamReceivedMessages.WaitForAsync` parity). Losing
|
|
2946
|
+
* the connection the wait observes is the one ending decided here, because
|
|
2947
|
+
* the wait has no place left to observe: that is `Disconnected` for every
|
|
2948
|
+
* surface (§10.1.1).
|
|
2949
|
+
*/
|
|
2748
2950
|
waitForMessage(name, timeoutMs, predicate, signal) {
|
|
2749
2951
|
validateName(name);
|
|
2750
2952
|
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
|
@@ -2772,9 +2974,7 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2772
2974
|
resolve(message);
|
|
2773
2975
|
}
|
|
2774
2976
|
};
|
|
2775
|
-
timer = setTimeout(() =>
|
|
2776
|
-
finish(connectorError("requestTimeout" /* RequestTimeout */, "Wait for stream message timed out."));
|
|
2777
|
-
}, timeoutMs);
|
|
2977
|
+
timer = setTimeout(() => finish(), timeoutMs);
|
|
2778
2978
|
signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
|
|
2779
2979
|
disposable = this.receivedMessages.observe(name, (message) => {
|
|
2780
2980
|
if (done) {
|
|
@@ -2796,7 +2996,10 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2796
2996
|
finish(cause);
|
|
2797
2997
|
}
|
|
2798
2998
|
return true;
|
|
2799
|
-
})
|
|
2999
|
+
}, () => finish(connectorError(
|
|
3000
|
+
"disconnected" /* Disconnected */,
|
|
3001
|
+
`The connection this wait for '${name}' observed has ended.`
|
|
3002
|
+
)));
|
|
2800
3003
|
});
|
|
2801
3004
|
}
|
|
2802
3005
|
encodePayload(payload, messageType) {
|
|
@@ -2880,50 +3083,6 @@ var ZlinkStreamConnectorBundle = (() => {
|
|
|
2880
3083
|
return typeof candidate.codec === "number" && candidate.payload instanceof Uint8Array;
|
|
2881
3084
|
}
|
|
2882
3085
|
|
|
2883
|
-
// packages/stream-connector/src/Runtime/ZlinkStreamAssertions.ts
|
|
2884
|
-
var zlinkStreamAssert = {
|
|
2885
|
-
ensure(condition, message) {
|
|
2886
|
-
if (!condition) {
|
|
2887
|
-
throw connectorError("validationFailed" /* ValidationFailed */, message);
|
|
2888
|
-
}
|
|
2889
|
-
},
|
|
2890
|
-
async expectFailure(action, errorKind) {
|
|
2891
|
-
let failure;
|
|
2892
|
-
try {
|
|
2893
|
-
await action();
|
|
2894
|
-
} catch (error) {
|
|
2895
|
-
failure = error;
|
|
2896
|
-
}
|
|
2897
|
-
if (failure === void 0) {
|
|
2898
|
-
throw connectorError("validationFailed" /* ValidationFailed */, "Expected action to fail.");
|
|
2899
|
-
}
|
|
2900
|
-
const streamError = unwrapStreamError(failure);
|
|
2901
|
-
if (errorKind !== void 0 && streamError.code !== errorKind) {
|
|
2902
|
-
throw connectorError(
|
|
2903
|
-
"validationFailed" /* ValidationFailed */,
|
|
2904
|
-
`Expected failure kind '${errorKind}', got '${streamError.code}'.`,
|
|
2905
|
-
failure
|
|
2906
|
-
);
|
|
2907
|
-
}
|
|
2908
|
-
return streamError;
|
|
2909
|
-
},
|
|
2910
|
-
async expectTimeout(action) {
|
|
2911
|
-
let failure;
|
|
2912
|
-
try {
|
|
2913
|
-
await action();
|
|
2914
|
-
} catch (error) {
|
|
2915
|
-
failure = error;
|
|
2916
|
-
}
|
|
2917
|
-
if (failure === void 0) {
|
|
2918
|
-
throw connectorError("validationFailed" /* ValidationFailed */, "Expected action to time out.");
|
|
2919
|
-
}
|
|
2920
|
-
const code = unwrapStreamError(failure).code;
|
|
2921
|
-
if (code !== "requestTimeout" /* RequestTimeout */ && code !== "connectTimeout" /* ConnectTimeout */) {
|
|
2922
|
-
throw failure;
|
|
2923
|
-
}
|
|
2924
|
-
}
|
|
2925
|
-
};
|
|
2926
|
-
|
|
2927
3086
|
// packages/stream-connector/src/index.ts
|
|
2928
3087
|
var zlinkStreamConnectorFactory = {
|
|
2929
3088
|
create(options) {
|