@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
package/dist/browser/index.mjs
CHANGED
|
@@ -659,6 +659,53 @@ function utf8Decode2(value) {
|
|
|
659
659
|
return new TextDecoder().decode(value);
|
|
660
660
|
}
|
|
661
661
|
|
|
662
|
+
// packages/stream-connector/src/Runtime/ZlinkStreamAssertions.ts
|
|
663
|
+
var zlinkStreamAssert = {
|
|
664
|
+
ensure(condition, message) {
|
|
665
|
+
if (typeof message !== "string" || message.trim().length === 0) {
|
|
666
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "zlinkStreamAssert.ensure requires a non-empty diagnostic message.");
|
|
667
|
+
}
|
|
668
|
+
if (!condition) {
|
|
669
|
+
throw connectorError("validationFailed" /* ValidationFailed */, message);
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
async expectFailure(action, errorKind) {
|
|
673
|
+
let failure;
|
|
674
|
+
try {
|
|
675
|
+
await action();
|
|
676
|
+
} catch (error) {
|
|
677
|
+
failure = error;
|
|
678
|
+
}
|
|
679
|
+
if (failure === void 0) {
|
|
680
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "Expected action to fail.");
|
|
681
|
+
}
|
|
682
|
+
const streamError = unwrapStreamError(failure);
|
|
683
|
+
if (errorKind !== void 0 && streamError.code !== errorKind) {
|
|
684
|
+
throw connectorError(
|
|
685
|
+
"validationFailed" /* ValidationFailed */,
|
|
686
|
+
`Expected failure kind '${errorKind}', got '${streamError.code}'.`,
|
|
687
|
+
failure
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
return streamError;
|
|
691
|
+
},
|
|
692
|
+
async expectTimeout(action) {
|
|
693
|
+
let failure;
|
|
694
|
+
try {
|
|
695
|
+
await action();
|
|
696
|
+
} catch (error) {
|
|
697
|
+
failure = error;
|
|
698
|
+
}
|
|
699
|
+
if (failure === void 0) {
|
|
700
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "Expected action to time out.");
|
|
701
|
+
}
|
|
702
|
+
const code = unwrapStreamError(failure).code;
|
|
703
|
+
if (code !== "requestTimeout" /* RequestTimeout */ && code !== "connectTimeout" /* ConnectTimeout */) {
|
|
704
|
+
throw failure;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
|
|
662
709
|
// packages/stream-connector/src/Runtime/Protocol/ZlinkStreamPacketNameValidator.ts
|
|
663
710
|
function validateName(name, allowReserved = false) {
|
|
664
711
|
if (name.length === 0) {
|
|
@@ -814,14 +861,17 @@ var ZlinkStreamWaitBuilder = class {
|
|
|
814
861
|
this.timeoutMs = timeoutMs;
|
|
815
862
|
return this;
|
|
816
863
|
}
|
|
817
|
-
submit(signal) {
|
|
864
|
+
async submit(signal) {
|
|
818
865
|
this.markExecuted();
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
866
|
+
const timeoutMs = this.timeoutMs ?? this.connector.options.waitTimeoutMs;
|
|
867
|
+
const message = await this.connector.waitForMessage(this.name, timeoutMs, this.predicate, signal);
|
|
868
|
+
if (message === void 0) {
|
|
869
|
+
throw connectorError(
|
|
870
|
+
"validationFailed" /* ValidationFailed */,
|
|
871
|
+
`No '${this.name}' message arrived within ${timeoutMs}ms.`
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
return message;
|
|
825
875
|
}
|
|
826
876
|
ensureConfigurable() {
|
|
827
877
|
if (this.executed) {
|
|
@@ -853,13 +903,9 @@ var ZlinkStreamExpectNoneBuilder = class {
|
|
|
853
903
|
if (this.windowMs === void 0) {
|
|
854
904
|
throw connectorError("validationFailed" /* ValidationFailed */, "expectNone requires within(windowMs).");
|
|
855
905
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
if (unwrapStreamError(error).code === "requestTimeout" /* RequestTimeout */) {
|
|
860
|
-
return;
|
|
861
|
-
}
|
|
862
|
-
throw error;
|
|
906
|
+
const message = await this.connector.waitForMessage(this.name, this.windowMs, () => true, signal);
|
|
907
|
+
if (message === void 0) {
|
|
908
|
+
return;
|
|
863
909
|
}
|
|
864
910
|
throw connectorError(
|
|
865
911
|
"validationFailed" /* ValidationFailed */,
|
|
@@ -895,6 +941,9 @@ var ZlinkStreamSequenceBuilder = class {
|
|
|
895
941
|
this.timeoutMs = timeoutMs;
|
|
896
942
|
return this;
|
|
897
943
|
}
|
|
944
|
+
// Spec stream-connector 32 §10.1: the predicate reads the whole message, and
|
|
945
|
+
// the call answers with the messages themselves, so a caller can assert on the
|
|
946
|
+
// packet name and the metadata and not only on the payload.
|
|
898
947
|
async run(signal) {
|
|
899
948
|
this.markExecuted();
|
|
900
949
|
if (this.predicates.length === 0) {
|
|
@@ -902,13 +951,13 @@ var ZlinkStreamSequenceBuilder = class {
|
|
|
902
951
|
}
|
|
903
952
|
const timeoutMs = this.timeoutMs ?? this.connector.options.waitTimeoutMs;
|
|
904
953
|
const deadline = Date.now() + timeoutMs;
|
|
905
|
-
const
|
|
954
|
+
const messages = [];
|
|
906
955
|
for (const predicate of this.predicates) {
|
|
907
956
|
const message = await this.connector.waitForMessage(
|
|
908
957
|
this.name,
|
|
909
958
|
Math.max(0, deadline - Date.now()),
|
|
910
959
|
(candidate) => {
|
|
911
|
-
if (!predicate(candidate
|
|
960
|
+
if (!predicate(candidate)) {
|
|
912
961
|
throw connectorError(
|
|
913
962
|
"validationFailed" /* ValidationFailed */,
|
|
914
963
|
`Message '${this.name}' arrived out of the expected sequence.`
|
|
@@ -918,9 +967,15 @@ var ZlinkStreamSequenceBuilder = class {
|
|
|
918
967
|
},
|
|
919
968
|
signal
|
|
920
969
|
);
|
|
921
|
-
|
|
970
|
+
if (message === void 0) {
|
|
971
|
+
throw connectorError(
|
|
972
|
+
"validationFailed" /* ValidationFailed */,
|
|
973
|
+
`The '${this.name}' sequence did not complete within ${timeoutMs}ms.`
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
messages.push(message);
|
|
922
977
|
}
|
|
923
|
-
return
|
|
978
|
+
return messages;
|
|
924
979
|
}
|
|
925
980
|
ensureConfigurable() {
|
|
926
981
|
if (this.executed) {
|
|
@@ -1021,7 +1076,7 @@ function splitZlinkStreamFrames(chunk) {
|
|
|
1021
1076
|
}
|
|
1022
1077
|
function validatePayload(payloadLength, maxPayloadSize) {
|
|
1023
1078
|
if (payloadLength > maxPayloadSize) {
|
|
1024
|
-
throw connectorError("
|
|
1079
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "Payload exceeds MaxSendPayloadSize.");
|
|
1025
1080
|
}
|
|
1026
1081
|
}
|
|
1027
1082
|
|
|
@@ -1303,22 +1358,30 @@ function normalizeOptions(options, defaultTransportFactory) {
|
|
|
1303
1358
|
initialDelayMs: options.reconnect?.initialDelayMs ?? 250,
|
|
1304
1359
|
maxDelayMs: options.reconnect?.maxDelayMs ?? 5e3,
|
|
1305
1360
|
backoffFactor: options.reconnect?.backoffFactor ?? 2,
|
|
1306
|
-
maxAttempts: options.reconnect?.maxAttempts
|
|
1361
|
+
maxAttempts: options.reconnect?.maxAttempts === void 0 ? 3 : options.reconnect.maxAttempts
|
|
1307
1362
|
},
|
|
1308
1363
|
maxSendPayloadSize: options.maxSendPayloadSize ?? 64 * 1024,
|
|
1309
1364
|
maxReceivePayloadSize: options.maxReceivePayloadSize ?? 64 * 1024,
|
|
1310
1365
|
dispatchMode: options.dispatchMode ?? "manual" /* Manual */,
|
|
1311
1366
|
compression: options.compression ?? "lz4" /* Lz4 */,
|
|
1312
1367
|
compressionCodec: resolveCompressionCodec2(options),
|
|
1313
|
-
nameResolver: options.nameResolver ??
|
|
1368
|
+
nameResolver: options.nameResolver ?? defaultPacketNameResolver,
|
|
1314
1369
|
transportFactory: options.transportFactory ?? defaultTransportFactory,
|
|
1315
1370
|
codec: options.codec,
|
|
1316
|
-
meterProvider: options.meterProvider,
|
|
1317
1371
|
// Spec 26 §4: the default diagnostics level is Errors, which preserves
|
|
1318
1372
|
// the connector's established wire behavior.
|
|
1319
1373
|
diagnosticsLevel: options.diagnosticsLevel ?? "errors" /* Errors */
|
|
1320
1374
|
};
|
|
1321
1375
|
}
|
|
1376
|
+
var defaultPacketNameResolver = {
|
|
1377
|
+
resolve(payloadType) {
|
|
1378
|
+
const declared = payloadType.packetName;
|
|
1379
|
+
if (typeof declared === "string" && declared.length > 0) {
|
|
1380
|
+
return declared;
|
|
1381
|
+
}
|
|
1382
|
+
return payloadType.name;
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1322
1385
|
function resolveCompressionCodec2(options) {
|
|
1323
1386
|
const compression = options.compression ?? "lz4" /* Lz4 */;
|
|
1324
1387
|
if (compression === "none" /* None */) {
|
|
@@ -1362,7 +1425,8 @@ function validateReconnect(options) {
|
|
|
1362
1425
|
if ((options?.backoffFactor ?? 2) < 1) {
|
|
1363
1426
|
throw connectorError("validationFailed" /* ValidationFailed */, "Reconnect BackoffFactor must be at least 1.0.");
|
|
1364
1427
|
}
|
|
1365
|
-
|
|
1428
|
+
const maxAttempts = options?.maxAttempts === void 0 ? 3 : options.maxAttempts;
|
|
1429
|
+
if (maxAttempts !== null && !(Number.isFinite(maxAttempts) && maxAttempts > 0)) {
|
|
1366
1430
|
throw connectorError("validationFailed" /* ValidationFailed */, "Reconnect MaxAttempts must be null or positive.");
|
|
1367
1431
|
}
|
|
1368
1432
|
}
|
|
@@ -1486,6 +1550,16 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1486
1550
|
queueHead = 0;
|
|
1487
1551
|
queuedCount = 0;
|
|
1488
1552
|
drainTask;
|
|
1553
|
+
// True for as long as `drain` is on the stack, handler awaits included. It
|
|
1554
|
+
// marks the execution context a registered handler runs in, so a `dispatch`
|
|
1555
|
+
// made from inside a handler is recognised as re-entry rather than a fresh
|
|
1556
|
+
// pump. It is not a lock: a single event loop admits no second thread, and
|
|
1557
|
+
// nothing ever waits for this flag to fall.
|
|
1558
|
+
draining = false;
|
|
1559
|
+
// Spec stream-connector 32 §10: arrivals per packet name on the current
|
|
1560
|
+
// connection. It is raised where a packet arrives, never where one is taken,
|
|
1561
|
+
// so consuming does not lower it and the dispatch mode does not change it.
|
|
1562
|
+
receivedCounts = /* @__PURE__ */ new Map();
|
|
1489
1563
|
on(name, handler) {
|
|
1490
1564
|
validateName(name);
|
|
1491
1565
|
let set = this.handlers.get(name);
|
|
@@ -1511,30 +1585,86 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1511
1585
|
* `Manual` needs no dispatch pump to complete a wait. The queue is scanned in
|
|
1512
1586
|
* a microtask so a message that arrived before the wait started is still
|
|
1513
1587
|
* observed, and so the caller has its subscription in hand by then.
|
|
1588
|
+
*
|
|
1589
|
+
* @param onConnectionEnded Called, instead of {@link observer}, when
|
|
1590
|
+
* {@link connectionEnded} abandons this registration because the
|
|
1591
|
+
* connection it was watching ended before a message matched (spec
|
|
1592
|
+
* stream-connector 32 §10.1.1: "연결이 끝나 대기를 이어갈 수 없으면
|
|
1593
|
+
* `Disconnected`다", released when that connection ends).
|
|
1514
1594
|
*/
|
|
1515
|
-
observe(name, observer) {
|
|
1595
|
+
observe(name, observer, onConnectionEnded) {
|
|
1516
1596
|
validateName(name);
|
|
1517
1597
|
let set = this.observers.get(name);
|
|
1518
1598
|
if (set === void 0) {
|
|
1519
1599
|
set = /* @__PURE__ */ new Set();
|
|
1520
1600
|
this.observers.set(name, set);
|
|
1521
1601
|
}
|
|
1522
|
-
|
|
1602
|
+
const registration = { consume: observer, onConnectionEnded };
|
|
1603
|
+
set.add(registration);
|
|
1523
1604
|
queueMicrotask(() => {
|
|
1524
|
-
if (this.observers.get(name)?.has(
|
|
1525
|
-
this.offerQueued(name,
|
|
1605
|
+
if (this.observers.get(name)?.has(registration) === true) {
|
|
1606
|
+
this.offerQueued(name, registration);
|
|
1526
1607
|
}
|
|
1527
1608
|
});
|
|
1528
1609
|
return subscription(() => {
|
|
1529
|
-
set.delete(
|
|
1610
|
+
set.delete(registration);
|
|
1530
1611
|
if (set.size === 0 && this.observers.get(name) === set) {
|
|
1531
1612
|
this.observers.delete(name);
|
|
1532
1613
|
}
|
|
1533
1614
|
});
|
|
1534
1615
|
}
|
|
1616
|
+
/** Spec stream-connector 32 §10: arrivals under `name` on this connection. */
|
|
1617
|
+
receivedCount(name) {
|
|
1618
|
+
return this.receivedCounts.get(name) ?? 0;
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Rebaselines the queue for a connection that was just established. Spec
|
|
1622
|
+
* stream-connector 32 §10 (line ~649): the reference point is the moment a
|
|
1623
|
+
* connection is established, so counts restart at 0 and whatever the
|
|
1624
|
+
* previous connection left unconsumed goes with it — keeping the queue
|
|
1625
|
+
* while only the counts reset would let counts and queue describe two
|
|
1626
|
+
* different connections, and let `waitFor` hand back a packet from before
|
|
1627
|
+
* the drop as if the new connection had delivered it.
|
|
1628
|
+
*
|
|
1629
|
+
* `ZlinkStreamMessage`/`ZlinkStreamEncodedPayload` are plain data (name,
|
|
1630
|
+
* metadata, a `Uint8Array` payload) with no dispose/close of their own —
|
|
1631
|
+
* unlike Java's queued frames, which `closeMessage` releases — so dropping
|
|
1632
|
+
* the queue's references is the whole of the release here.
|
|
1633
|
+
*
|
|
1634
|
+
* Wait surfaces are not touched here. The ones of the previous connection
|
|
1635
|
+
* were released by {@link connectionEnded} when that connection ended, and
|
|
1636
|
+
* one registered since then is waiting for this connection.
|
|
1637
|
+
*/
|
|
1638
|
+
resetForNewConnection() {
|
|
1639
|
+
this.receivedCounts.clear();
|
|
1640
|
+
this.queue.length = 0;
|
|
1641
|
+
this.queueHead = 0;
|
|
1642
|
+
this.queuedCount = 0;
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* Releases every registered wait surface because the connection it was
|
|
1646
|
+
* watching has ended — a transport loss, a server close, or `close()`.
|
|
1647
|
+
* Spec stream-connector 32 §10.1.1: "푸는 시점은 연결이 끝난 때이지 다음
|
|
1648
|
+
* 연결이 성립한 때가 아니다". The release belongs to the ending, so a wait
|
|
1649
|
+
* does not hang until its own timeout when no next connection comes
|
|
1650
|
+
* (reconnect off, attempts spent) and does not silently rebind to the next
|
|
1651
|
+
* one when it does. The queue and the counts stay: they are rebaselined by
|
|
1652
|
+
* the next {@link resetForNewConnection}, not by the ending (§10).
|
|
1653
|
+
*/
|
|
1654
|
+
connectionEnded() {
|
|
1655
|
+
if (this.observers.size === 0) {
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
const abandoned = [...this.observers.values()].flatMap((set) => [...set]);
|
|
1659
|
+
this.observers.clear();
|
|
1660
|
+
for (const registration of abandoned) {
|
|
1661
|
+
registration.onConnectionEnded();
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1535
1664
|
enqueue(message, signal) {
|
|
1536
|
-
|
|
1537
|
-
|
|
1665
|
+
this.receivedCounts.set(message.name, (this.receivedCounts.get(message.name) ?? 0) + 1);
|
|
1666
|
+
for (const registration of [...this.observers.get(message.name) ?? []]) {
|
|
1667
|
+
if (registration.consume(message)) {
|
|
1538
1668
|
return;
|
|
1539
1669
|
}
|
|
1540
1670
|
}
|
|
@@ -1547,18 +1677,28 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1547
1677
|
/**
|
|
1548
1678
|
* Runs the registered handlers the receive path left queued. `Manual` calls
|
|
1549
1679
|
* this from `dispatch`; `Immediate` has already drained on arrival.
|
|
1680
|
+
*
|
|
1681
|
+
* A handler that calls `dispatch` arrives back here from inside the drain it
|
|
1682
|
+
* was started by. `scheduleDrain` would find `drainTask` already set and
|
|
1683
|
+
* return, and the await below would then be the drain waiting on itself —
|
|
1684
|
+
* a deadlock with neither timeout nor error. The drain loop already takes
|
|
1685
|
+
* every message a handler exists for, so there is nothing a second drain
|
|
1686
|
+
* would deliver and returning is the whole of the correct behaviour.
|
|
1550
1687
|
*/
|
|
1551
1688
|
async pump() {
|
|
1689
|
+
if (this.draining) {
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1552
1692
|
this.scheduleDrain();
|
|
1553
1693
|
await this.drainTask;
|
|
1554
1694
|
}
|
|
1555
|
-
offerQueued(name,
|
|
1695
|
+
offerQueued(name, registration) {
|
|
1556
1696
|
for (let index = this.queueHead; index < this.queue.length; index += 1) {
|
|
1557
1697
|
const queued = this.queue[index];
|
|
1558
1698
|
if (queued === void 0 || queued.message.name !== name) {
|
|
1559
1699
|
continue;
|
|
1560
1700
|
}
|
|
1561
|
-
if (!
|
|
1701
|
+
if (!registration.consume(queued.message)) {
|
|
1562
1702
|
continue;
|
|
1563
1703
|
}
|
|
1564
1704
|
this.removeAt(index);
|
|
@@ -1577,23 +1717,28 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1577
1717
|
});
|
|
1578
1718
|
}
|
|
1579
1719
|
async drain() {
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1720
|
+
this.draining = true;
|
|
1721
|
+
try {
|
|
1722
|
+
for (let index = this.findDeliverableIndex(); index >= 0; index = this.findDeliverableIndex()) {
|
|
1723
|
+
const queued = this.queue[index];
|
|
1724
|
+
if (queued === void 0) continue;
|
|
1725
|
+
this.removeAt(index);
|
|
1726
|
+
const { message, signal } = queued;
|
|
1727
|
+
const handlers = [...this.handlers.get(message.name)];
|
|
1728
|
+
for (const handler of handlers) {
|
|
1729
|
+
try {
|
|
1730
|
+
await handler(message, signal);
|
|
1731
|
+
} catch (cause) {
|
|
1732
|
+
await this.events.publishError({
|
|
1733
|
+
code: "userCallbackFailed" /* UserCallbackFailed */,
|
|
1734
|
+
message: "Typed message handler failed.",
|
|
1735
|
+
cause
|
|
1736
|
+
}, signal);
|
|
1737
|
+
}
|
|
1595
1738
|
}
|
|
1596
1739
|
}
|
|
1740
|
+
} finally {
|
|
1741
|
+
this.draining = false;
|
|
1597
1742
|
}
|
|
1598
1743
|
}
|
|
1599
1744
|
removeAt(index) {
|
|
@@ -1637,10 +1782,9 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1637
1782
|
|
|
1638
1783
|
// packages/stream-connector/src/Runtime/ZlinkStreamFrameSender.ts
|
|
1639
1784
|
var ZlinkStreamFrameSender = class {
|
|
1640
|
-
constructor(protocol, flowContext
|
|
1785
|
+
constructor(protocol, flowContext) {
|
|
1641
1786
|
this.protocol = protocol;
|
|
1642
1787
|
this.flowContext = flowContext;
|
|
1643
|
-
this.metrics = metrics;
|
|
1644
1788
|
}
|
|
1645
1789
|
pendingWrites = /* @__PURE__ */ new Set();
|
|
1646
1790
|
async send(connection, kind, name, payload, metadata, compress, requestSeq, signal, correlationId, explicitFlow) {
|
|
@@ -1666,7 +1810,6 @@ var ZlinkStreamFrameSender = class {
|
|
|
1666
1810
|
this.pendingWrites.add(write);
|
|
1667
1811
|
try {
|
|
1668
1812
|
await write;
|
|
1669
|
-
this.metrics.outbound(frame.byteLength);
|
|
1670
1813
|
} finally {
|
|
1671
1814
|
this.pendingWrites.delete(write);
|
|
1672
1815
|
}
|
|
@@ -1695,17 +1838,29 @@ function decodeSessionClosing(payload) {
|
|
|
1695
1838
|
|
|
1696
1839
|
// packages/stream-connector/src/Runtime/ZlinkStreamReceiveDispatcher.ts
|
|
1697
1840
|
var ZlinkStreamReceiveDispatcher = class {
|
|
1698
|
-
constructor(protocol, pendingRequests, receivedMessages, frameSender, events, flowContext,
|
|
1841
|
+
constructor(protocol, pendingRequests, receivedMessages, frameSender, events, flowContext, serverClosing) {
|
|
1699
1842
|
this.protocol = protocol;
|
|
1700
1843
|
this.pendingRequests = pendingRequests;
|
|
1701
1844
|
this.receivedMessages = receivedMessages;
|
|
1702
1845
|
this.frameSender = frameSender;
|
|
1703
1846
|
this.events = events;
|
|
1704
1847
|
this.flowContext = flowContext;
|
|
1705
|
-
this.metrics = metrics;
|
|
1706
1848
|
this.serverClosing = serverClosing;
|
|
1707
1849
|
}
|
|
1708
|
-
|
|
1850
|
+
/**
|
|
1851
|
+
* @param isCurrent Tells whether the connection this batch was read from is
|
|
1852
|
+
* still the connector's connection. It is asked again at the head of every
|
|
1853
|
+
* frame, not only after the read: `dispatch` awaits application code —
|
|
1854
|
+
* registered handlers in `Immediate`, the error handler on a failed frame —
|
|
1855
|
+
* and a disconnect and reconnect can complete inside that await. Without
|
|
1856
|
+
* the recheck the rest of a dead connection's batch lands on the new
|
|
1857
|
+
* connection's state, and the §10 arrival counts, which start at 0 when a
|
|
1858
|
+
* connection is established, take the stale frames on top.
|
|
1859
|
+
* @param connectionForSend Resolves the connection a reply belongs on at the
|
|
1860
|
+
* moment it is written. The captured `connection` is the one the batch was
|
|
1861
|
+
* read from, which a reconnect may already have replaced.
|
|
1862
|
+
*/
|
|
1863
|
+
async readAndDispatch(connection, signal, isCurrent, connectionForSend) {
|
|
1709
1864
|
if (connection?.read === void 0) {
|
|
1710
1865
|
return { available: false, inbound: false };
|
|
1711
1866
|
}
|
|
@@ -1716,7 +1871,6 @@ var ZlinkStreamReceiveDispatcher = class {
|
|
|
1716
1871
|
if (frameBytes === void 0) {
|
|
1717
1872
|
return { available: false, inbound: false };
|
|
1718
1873
|
}
|
|
1719
|
-
this.metrics.inbound(frameBytes.byteLength);
|
|
1720
1874
|
const flowEnabled = this.protocol.flowEnabled();
|
|
1721
1875
|
let frames;
|
|
1722
1876
|
try {
|
|
@@ -1729,8 +1883,11 @@ var ZlinkStreamReceiveDispatcher = class {
|
|
|
1729
1883
|
return { available: true, inbound: false };
|
|
1730
1884
|
}
|
|
1731
1885
|
for (const frame of frames) {
|
|
1886
|
+
if (isCurrent !== void 0 && !isCurrent()) {
|
|
1887
|
+
break;
|
|
1888
|
+
}
|
|
1732
1889
|
try {
|
|
1733
|
-
await this.dispatch(connection, frame.header, frame.payload, signal, flowEnabled);
|
|
1890
|
+
await this.dispatch(connection, frame.header, frame.payload, signal, flowEnabled, connectionForSend);
|
|
1734
1891
|
} catch (cause) {
|
|
1735
1892
|
if (frame.header.kind === 5 /* Control */ && frame.header.name === ZLINK_STREAM_HEARTBEAT_PING) {
|
|
1736
1893
|
throw cause;
|
|
@@ -1743,7 +1900,7 @@ var ZlinkStreamReceiveDispatcher = class {
|
|
|
1743
1900
|
}
|
|
1744
1901
|
return { available: true, inbound: true };
|
|
1745
1902
|
}
|
|
1746
|
-
async dispatch(connection, header, payload, signal, flowEnabled) {
|
|
1903
|
+
async dispatch(connection, header, payload, signal, flowEnabled, connectionForSend) {
|
|
1747
1904
|
if (header.kind === 3 /* Response */ && header.requestSeq !== void 0) {
|
|
1748
1905
|
try {
|
|
1749
1906
|
if (!this.pendingRequests.resolve(header.requestSeq, {
|
|
@@ -1796,7 +1953,7 @@ var ZlinkStreamReceiveDispatcher = class {
|
|
|
1796
1953
|
return;
|
|
1797
1954
|
}
|
|
1798
1955
|
if (header.kind === 5 /* Control */) {
|
|
1799
|
-
await this.dispatchControl(connection, header, payload, signal);
|
|
1956
|
+
await this.dispatchControl(connection, header, payload, signal, connectionForSend);
|
|
1800
1957
|
return;
|
|
1801
1958
|
}
|
|
1802
1959
|
if (header.kind === 1 /* Send */) {
|
|
@@ -1810,7 +1967,7 @@ var ZlinkStreamReceiveDispatcher = class {
|
|
|
1810
1967
|
}, signal);
|
|
1811
1968
|
}
|
|
1812
1969
|
}
|
|
1813
|
-
async dispatchControl(connection, header, payload, signal) {
|
|
1970
|
+
async dispatchControl(connection, header, payload, signal, connectionForSend) {
|
|
1814
1971
|
if (header.name === ZLINK_SESSION_CLOSING) {
|
|
1815
1972
|
const closing = decodeSessionClosing(payload);
|
|
1816
1973
|
await this.serverClosing?.(closing.closeReason);
|
|
@@ -1821,7 +1978,11 @@ var ZlinkStreamReceiveDispatcher = class {
|
|
|
1821
1978
|
}
|
|
1822
1979
|
if (header.name === ZLINK_STREAM_HEARTBEAT_PING) {
|
|
1823
1980
|
try {
|
|
1824
|
-
await this.frameSender.sendControl(
|
|
1981
|
+
await this.frameSender.sendControl(
|
|
1982
|
+
connectionForSend?.() ?? connection,
|
|
1983
|
+
ZLINK_STREAM_HEARTBEAT_PONG,
|
|
1984
|
+
signal
|
|
1985
|
+
);
|
|
1825
1986
|
} catch (cause) {
|
|
1826
1987
|
throw connectorError(
|
|
1827
1988
|
"sendFailed" /* SendFailed */,
|
|
@@ -1854,15 +2015,17 @@ function decodeRemoteError(protocol, header, payload) {
|
|
|
1854
2015
|
}
|
|
1855
2016
|
|
|
1856
2017
|
// packages/stream-connector/src/Runtime/ZlinkStreamConnectorLifecycle.ts
|
|
2018
|
+
function randomizedDelay(baseDelayMs) {
|
|
2019
|
+
return Math.round(baseDelayMs * (0.5 + Math.random() * 0.5));
|
|
2020
|
+
}
|
|
1857
2021
|
var ZlinkStreamConnectorLifecycle = class {
|
|
1858
|
-
constructor(options, pendingRequests, frameSender, receiveDispatcher, receivedMessages, events
|
|
2022
|
+
constructor(options, pendingRequests, frameSender, receiveDispatcher, receivedMessages, events) {
|
|
1859
2023
|
this.options = options;
|
|
1860
2024
|
this.pendingRequests = pendingRequests;
|
|
1861
2025
|
this.frameSender = frameSender;
|
|
1862
2026
|
this.receiveDispatcher = receiveDispatcher;
|
|
1863
2027
|
this.receivedMessages = receivedMessages;
|
|
1864
2028
|
this.events = events;
|
|
1865
|
-
this.metrics = metrics;
|
|
1866
2029
|
}
|
|
1867
2030
|
receiveLoopAbort;
|
|
1868
2031
|
receiveLoopSleeping = false;
|
|
@@ -1872,6 +2035,7 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
1872
2035
|
connectionGeneration = 0;
|
|
1873
2036
|
currentState = "created" /* Created */;
|
|
1874
2037
|
heartbeatTimer;
|
|
2038
|
+
heartbeatTickRunning = false;
|
|
1875
2039
|
lastInboundAt = 0;
|
|
1876
2040
|
closeTask;
|
|
1877
2041
|
connectTask;
|
|
@@ -1922,6 +2086,7 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
1922
2086
|
this.currentConnection = connection;
|
|
1923
2087
|
this.connectionGeneration += 1;
|
|
1924
2088
|
this.disconnectedPublished = false;
|
|
2089
|
+
this.receivedMessages.resetForNewConnection();
|
|
1925
2090
|
this.lastInboundAt = Date.now();
|
|
1926
2091
|
await this.setState("connected" /* Connected */, void 0, signal);
|
|
1927
2092
|
this.startHeartbeat();
|
|
@@ -1933,7 +2098,9 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
1933
2098
|
throw new ZlinkStreamException(error2);
|
|
1934
2099
|
}
|
|
1935
2100
|
const error = toStreamError(cause, "connectTimeout" /* ConnectTimeout */, "Connect failed.");
|
|
2101
|
+
this.closeReasonValue ??= "TransportError";
|
|
1936
2102
|
await this.setState("disconnected" /* Disconnected */, error, signal);
|
|
2103
|
+
await this.publishDisconnectedOnce(signal);
|
|
1937
2104
|
throw new ZlinkStreamException(error);
|
|
1938
2105
|
}
|
|
1939
2106
|
}
|
|
@@ -1979,8 +2146,9 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
1979
2146
|
errors.push(error);
|
|
1980
2147
|
}
|
|
1981
2148
|
this.pendingRequests.failAll({ code: "disconnected" /* Disconnected */, message: "Connector closed." });
|
|
2149
|
+
this.receivedMessages.connectionEnded();
|
|
1982
2150
|
await this.setState("closed" /* Closed */, void 0, signal);
|
|
1983
|
-
|
|
2151
|
+
this.publishDisconnectedWithoutWaiting(signal);
|
|
1984
2152
|
if (errors.length === 1) throw errors[0];
|
|
1985
2153
|
if (errors.length > 1) throw new AggregateError(errors, "Stream connector close failed.");
|
|
1986
2154
|
}
|
|
@@ -2011,7 +2179,8 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2011
2179
|
const result = await this.receiveDispatcher.readAndDispatch(
|
|
2012
2180
|
connection,
|
|
2013
2181
|
signal,
|
|
2014
|
-
() => this.isCurrentConnection(connection, generation)
|
|
2182
|
+
() => this.isCurrentConnection(connection, generation),
|
|
2183
|
+
() => this.connectionForSend()
|
|
2015
2184
|
);
|
|
2016
2185
|
if (result.inbound && this.isCurrentConnection(connection, generation)) {
|
|
2017
2186
|
this.lastInboundAt = Date.now();
|
|
@@ -2023,25 +2192,18 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2023
2192
|
let delayMs = this.options.reconnect.initialDelayMs;
|
|
2024
2193
|
let lastError;
|
|
2025
2194
|
const maxAttempts = this.options.reconnect.enabled ? this.options.reconnect.maxAttempts : 1;
|
|
2026
|
-
|
|
2195
|
+
const unlimited = maxAttempts === null;
|
|
2196
|
+
while (unlimited || attempt < maxAttempts) {
|
|
2027
2197
|
attempt += 1;
|
|
2028
|
-
if (attempt > 1) {
|
|
2029
|
-
this.metrics.reconnect();
|
|
2030
|
-
}
|
|
2031
|
-
const handshakeStartedAt = performance.now();
|
|
2032
2198
|
try {
|
|
2033
|
-
|
|
2034
|
-
this.metrics.handshakeCompleted(handshakeStartedAt);
|
|
2035
|
-
return connection;
|
|
2199
|
+
return await this.options.transportFactory.connect(this.options, signal);
|
|
2036
2200
|
} catch (cause) {
|
|
2037
|
-
this.metrics.handshakeCompleted(handshakeStartedAt);
|
|
2038
|
-
this.metrics.handshakeFailed(cause);
|
|
2039
2201
|
lastError = toStreamError(cause, "connectTimeout" /* ConnectTimeout */, "Connect failed.");
|
|
2040
|
-
if (!this.options.reconnect.enabled || attempt >= maxAttempts) {
|
|
2202
|
+
if (!this.options.reconnect.enabled || !unlimited && attempt >= maxAttempts) {
|
|
2041
2203
|
break;
|
|
2042
2204
|
}
|
|
2043
2205
|
await this.setState("reconnecting" /* Reconnecting */, lastError, signal);
|
|
2044
|
-
await delay(delayMs, signal);
|
|
2206
|
+
await delay(randomizedDelay(delayMs), signal);
|
|
2045
2207
|
delayMs = Math.min(
|
|
2046
2208
|
this.options.reconnect.maxDelayMs,
|
|
2047
2209
|
Math.ceil(delayMs * this.options.reconnect.backoffFactor)
|
|
@@ -2057,15 +2219,25 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2057
2219
|
if (!this.options.heartbeat.enabled) {
|
|
2058
2220
|
return;
|
|
2059
2221
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2222
|
+
const timer = setInterval(() => {
|
|
2223
|
+
if (this.heartbeatTickRunning) {
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
this.heartbeatTickRunning = true;
|
|
2227
|
+
void this.runHeartbeatTick().finally(() => {
|
|
2228
|
+
if (this.heartbeatTimer === timer) {
|
|
2229
|
+
this.heartbeatTickRunning = false;
|
|
2230
|
+
}
|
|
2231
|
+
});
|
|
2062
2232
|
}, this.options.heartbeat.intervalMs);
|
|
2233
|
+
this.heartbeatTimer = timer;
|
|
2063
2234
|
}
|
|
2064
2235
|
stopHeartbeat() {
|
|
2065
2236
|
if (this.heartbeatTimer !== void 0) {
|
|
2066
2237
|
clearInterval(this.heartbeatTimer);
|
|
2067
2238
|
this.heartbeatTimer = void 0;
|
|
2068
2239
|
}
|
|
2240
|
+
this.heartbeatTickRunning = false;
|
|
2069
2241
|
}
|
|
2070
2242
|
// Spec stream-connector 32 §7: the receive loop runs in both dispatch modes.
|
|
2071
2243
|
// `Manual` only changes what the loop does with a frame — it queues the
|
|
@@ -2180,41 +2352,96 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2180
2352
|
if (this.disconnectTask !== void 0) {
|
|
2181
2353
|
return await this.disconnectTask;
|
|
2182
2354
|
}
|
|
2183
|
-
this.disconnectTask = this.
|
|
2355
|
+
this.disconnectTask = this.tearDownConnection(error).finally(() => {
|
|
2184
2356
|
this.disconnectTask = void 0;
|
|
2185
2357
|
});
|
|
2186
|
-
|
|
2358
|
+
await this.disconnectTask;
|
|
2359
|
+
await this.announceDisconnect(error);
|
|
2187
2360
|
}
|
|
2188
2361
|
isCurrentConnection(connection, generation) {
|
|
2189
2362
|
return !this.closeRequested && this.currentConnection === connection && this.connectionGeneration === generation;
|
|
2190
2363
|
}
|
|
2191
|
-
|
|
2364
|
+
/** Transport teardown only — no application callback runs from here. */
|
|
2365
|
+
async tearDownConnection(error) {
|
|
2192
2366
|
this.stopHeartbeat();
|
|
2193
2367
|
this.stopReceiveLoop();
|
|
2194
2368
|
const connection = this.currentConnection;
|
|
2195
2369
|
this.currentConnection = void 0;
|
|
2196
2370
|
this.pendingRequests.failAll(error);
|
|
2371
|
+
this.receivedMessages.connectionEnded();
|
|
2197
2372
|
try {
|
|
2198
2373
|
await connection?.close();
|
|
2199
2374
|
} catch {
|
|
2200
2375
|
}
|
|
2376
|
+
}
|
|
2377
|
+
/**
|
|
2378
|
+
* Runs once the teardown promise has settled, so a handler reached from here
|
|
2379
|
+
* may call `connect` without waiting for a task its own caller still holds.
|
|
2380
|
+
* The reconnect is queued before the notification is awaited for the same
|
|
2381
|
+
* reason: spec stream-connector 32 §6 has reconnect on by default, and a
|
|
2382
|
+
* handler that is slow — or whose promise never settles at all — must not
|
|
2383
|
+
* cost the connector the attempt. The state is already `Disconnected` and the
|
|
2384
|
+
* state handlers are already invoked by the time the queued microtask runs,
|
|
2385
|
+
* because `setState` records the state and hands the change to the handlers
|
|
2386
|
+
* before it awaits any of them.
|
|
2387
|
+
*/
|
|
2388
|
+
async announceDisconnect(error) {
|
|
2201
2389
|
if (this.closeRequested) return;
|
|
2202
|
-
|
|
2203
|
-
|
|
2390
|
+
const announce = this.claimDisconnectedPublish();
|
|
2391
|
+
const notified = (async () => {
|
|
2392
|
+
await this.setState("disconnected" /* Disconnected */, error);
|
|
2393
|
+
if (announce) {
|
|
2394
|
+
await this.events.publishDisconnected();
|
|
2395
|
+
}
|
|
2396
|
+
})();
|
|
2204
2397
|
if (this.shouldReconnect()) {
|
|
2205
2398
|
queueMicrotask(() => {
|
|
2206
2399
|
void this.connect().catch(() => void 0);
|
|
2207
2400
|
});
|
|
2208
2401
|
}
|
|
2402
|
+
await notified;
|
|
2209
2403
|
}
|
|
2210
2404
|
shouldReconnect() {
|
|
2211
2405
|
return this.options.reconnect.enabled && !this.closeRequested;
|
|
2212
2406
|
}
|
|
2213
|
-
|
|
2214
|
-
|
|
2407
|
+
// Synchronous test-and-set, taken before any await, so the single disconnect
|
|
2408
|
+
// notification the spec promises is claimed by exactly one caller even when
|
|
2409
|
+
// the publishing itself is deferred.
|
|
2410
|
+
claimDisconnectedPublish() {
|
|
2411
|
+
if (this.disconnectedPublished) return false;
|
|
2215
2412
|
this.disconnectedPublished = true;
|
|
2413
|
+
return true;
|
|
2414
|
+
}
|
|
2415
|
+
async publishDisconnectedOnce(signal) {
|
|
2416
|
+
if (!this.claimDisconnectedPublish()) return;
|
|
2216
2417
|
await this.events.publishDisconnected(signal);
|
|
2217
2418
|
}
|
|
2419
|
+
/**
|
|
2420
|
+
* Spec stream-connector 32 §7: the connector runs the registered handler, it
|
|
2421
|
+
* does not wait for it to finish. `close` returns once its own work is done —
|
|
2422
|
+
* the drain, the transport close, the pending requests — having run the
|
|
2423
|
+
* disconnect handler but without looking at whether that handler has ended.
|
|
2424
|
+
*
|
|
2425
|
+
* Starting the publish without awaiting it still runs every handler right
|
|
2426
|
+
* here: an `async` function body runs synchronously up to its first `await`,
|
|
2427
|
+
* and `publishDisconnected` reaches each handler before that point. So the
|
|
2428
|
+
* handler has run by the time `close` returns, which is what the spec asks
|
|
2429
|
+
* for, while a handler that calls `close` no longer waits for the very
|
|
2430
|
+
* `closeTask` it is running inside. Java and C++ hand the handler to a queue
|
|
2431
|
+
* and return the same way.
|
|
2432
|
+
*
|
|
2433
|
+
* `claimDisconnectedPublish` is taken by the same call and before any await,
|
|
2434
|
+
* so deferring the completion never turns the one notification into two.
|
|
2435
|
+
*
|
|
2436
|
+
* Nothing awaits the promise, so a rejection would reach the process as an
|
|
2437
|
+
* unhandled rejection and kill it. Handler failures are already contained
|
|
2438
|
+
* where they were before this call stopped waiting — the `Promise.allSettled`
|
|
2439
|
+
* in `publishDisconnected` — and this `catch` covers what is left rather than
|
|
2440
|
+
* reporting the same failure a second time on the error surface.
|
|
2441
|
+
*/
|
|
2442
|
+
publishDisconnectedWithoutWaiting(signal) {
|
|
2443
|
+
void this.publishDisconnectedOnce(signal).catch(() => void 0);
|
|
2444
|
+
}
|
|
2218
2445
|
async setState(current, error, signal) {
|
|
2219
2446
|
const previous = this.currentState;
|
|
2220
2447
|
this.currentState = current;
|
|
@@ -2396,9 +2623,6 @@ var BrowserWebSocketConnection = class {
|
|
|
2396
2623
|
this.readWaiter = void 0;
|
|
2397
2624
|
resolve();
|
|
2398
2625
|
};
|
|
2399
|
-
if (this.hasQueuedMessage() || this.closed) {
|
|
2400
|
-
this.wakeReader();
|
|
2401
|
-
}
|
|
2402
2626
|
});
|
|
2403
2627
|
}
|
|
2404
2628
|
wakeReader() {
|
|
@@ -2478,50 +2702,6 @@ function toUint8Array(data) {
|
|
|
2478
2702
|
throw connectorError("frameDecodeFailed" /* FrameDecodeFailed */, "WebSocket text messages are not supported.");
|
|
2479
2703
|
}
|
|
2480
2704
|
|
|
2481
|
-
// packages/stream-connector/src/Runtime/ZlinkStreamRuntimeMetrics.ts
|
|
2482
|
-
var ZlinkStreamRuntimeMetrics = class {
|
|
2483
|
-
constructor(options) {
|
|
2484
|
-
this.options = options;
|
|
2485
|
-
const meter = options.meterProvider?.getMeter("zlink.framework");
|
|
2486
|
-
this.reconnects = meter?.createCounter("zlink.stream.reconnects", { unit: "{event}" });
|
|
2487
|
-
this.handshakeDuration = meter?.createHistogram("zlink.stream.handshake.duration", { unit: "s" });
|
|
2488
|
-
this.handshakeFailures = meter?.createCounter("zlink.stream.handshake.failures", { unit: "{failure}" });
|
|
2489
|
-
this.inboundBytes = meter?.createCounter("zlink.stream.inbound.bytes", { unit: "By" });
|
|
2490
|
-
this.outboundBytes = meter?.createCounter("zlink.stream.outbound.bytes", { unit: "By" });
|
|
2491
|
-
}
|
|
2492
|
-
reconnects;
|
|
2493
|
-
handshakeDuration;
|
|
2494
|
-
handshakeFailures;
|
|
2495
|
-
inboundBytes;
|
|
2496
|
-
outboundBytes;
|
|
2497
|
-
reconnect() {
|
|
2498
|
-
this.safe(() => this.reconnects?.add(1, { transport: this.transportLabel() }));
|
|
2499
|
-
}
|
|
2500
|
-
handshakeCompleted(startedAt) {
|
|
2501
|
-
const seconds = (performance.now() - startedAt) / 1e3;
|
|
2502
|
-
this.safe(() => this.handshakeDuration?.record(seconds, { transport: this.transportLabel() }));
|
|
2503
|
-
}
|
|
2504
|
-
handshakeFailed(error) {
|
|
2505
|
-
const reason = error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError") ? "canceled" : "transport_error";
|
|
2506
|
-
this.safe(() => this.handshakeFailures?.add(1, { transport: this.transportLabel(), reason }));
|
|
2507
|
-
}
|
|
2508
|
-
inbound(byteCount) {
|
|
2509
|
-
this.safe(() => this.inboundBytes?.add(byteCount, { transport: this.transportLabel() }));
|
|
2510
|
-
}
|
|
2511
|
-
outbound(byteCount) {
|
|
2512
|
-
this.safe(() => this.outboundBytes?.add(byteCount, { transport: this.transportLabel() }));
|
|
2513
|
-
}
|
|
2514
|
-
transportLabel() {
|
|
2515
|
-
return this.options.endpoint.startsWith("wss:") ? "wss" : "ws";
|
|
2516
|
-
}
|
|
2517
|
-
safe(record) {
|
|
2518
|
-
try {
|
|
2519
|
-
record();
|
|
2520
|
-
} catch {
|
|
2521
|
-
}
|
|
2522
|
-
}
|
|
2523
|
-
};
|
|
2524
|
-
|
|
2525
2705
|
// packages/stream-connector/src/Runtime/ZlinkStreamConnector.ts
|
|
2526
2706
|
var DefaultZlinkStreamConnector = class {
|
|
2527
2707
|
static heartbeatPingName = ZLINK_STREAM_HEARTBEAT_PING;
|
|
@@ -2544,9 +2724,8 @@ var DefaultZlinkStreamConnector = class {
|
|
|
2544
2724
|
configurable: true,
|
|
2545
2725
|
get: () => this.diagnosticsLevelCell.level
|
|
2546
2726
|
});
|
|
2547
|
-
const metrics = new ZlinkStreamRuntimeMetrics(this.options);
|
|
2548
2727
|
const protocol = new ZlinkStreamFrameProtocol(this.options);
|
|
2549
|
-
this.frameSender = new ZlinkStreamFrameSender(protocol, flowContext
|
|
2728
|
+
this.frameSender = new ZlinkStreamFrameSender(protocol, flowContext);
|
|
2550
2729
|
this.receivedMessages = new ZlinkStreamReceivedMessages(
|
|
2551
2730
|
this.events,
|
|
2552
2731
|
this.options.dispatchMode === "immediate" /* Immediate */
|
|
@@ -2558,7 +2737,6 @@ var DefaultZlinkStreamConnector = class {
|
|
|
2558
2737
|
this.frameSender,
|
|
2559
2738
|
this.events,
|
|
2560
2739
|
flowContext,
|
|
2561
|
-
metrics,
|
|
2562
2740
|
(reason) => this.lifecycle.serverClosing(reason)
|
|
2563
2741
|
);
|
|
2564
2742
|
this.lifecycle = new ZlinkStreamConnectorLifecycle(
|
|
@@ -2567,8 +2745,7 @@ var DefaultZlinkStreamConnector = class {
|
|
|
2567
2745
|
this.frameSender,
|
|
2568
2746
|
this.receiveDispatcher,
|
|
2569
2747
|
this.receivedMessages,
|
|
2570
|
-
this.events
|
|
2571
|
-
metrics
|
|
2748
|
+
this.events
|
|
2572
2749
|
);
|
|
2573
2750
|
}
|
|
2574
2751
|
get isConnected() {
|
|
@@ -2583,6 +2760,17 @@ var DefaultZlinkStreamConnector = class {
|
|
|
2583
2760
|
get pendingDispatchCount() {
|
|
2584
2761
|
return this.pendingRequests.count;
|
|
2585
2762
|
}
|
|
2763
|
+
/**
|
|
2764
|
+
* Spec stream-connector 32 §10: how many packets carrying `name` arrived on
|
|
2765
|
+
* the current connection. Arrivals are what is counted, so a message a
|
|
2766
|
+
* handler dispatched or a wait surface consumed still counts, and `Manual`
|
|
2767
|
+
* and `Immediate` report the same number. Establishing a connection resets
|
|
2768
|
+
* the count to 0, a reconnect included.
|
|
2769
|
+
*/
|
|
2770
|
+
receivedCount(name) {
|
|
2771
|
+
validateName(name);
|
|
2772
|
+
return this.receivedMessages.receivedCount(name);
|
|
2773
|
+
}
|
|
2586
2774
|
/**
|
|
2587
2775
|
* Current diagnostics level (spec 26 §4.1, spec stream-connector 32 §13).
|
|
2588
2776
|
* Reflects the level set by the most recent {@link setDiagnosticsLevel}
|
|
@@ -2598,14 +2786,20 @@ var DefaultZlinkStreamConnector = class {
|
|
|
2598
2786
|
* state update: it applies to processing points that read the level after
|
|
2599
2787
|
* this call returns and is never applied retroactively to frames already
|
|
2600
2788
|
* built. Rejects unknown values with {@link ZlinkStreamErrorCode.ConfigurationError}.
|
|
2601
|
-
*
|
|
2602
|
-
*
|
|
2789
|
+
* Spec stream-connector 32 §13: this surface changes the value without
|
|
2790
|
+
* waiting for anything; it is not a blocking call over the asynchronous pair,
|
|
2791
|
+
* which a receive callback would otherwise make wait for its own completion.
|
|
2603
2792
|
*/
|
|
2604
2793
|
setDiagnosticsLevel(level) {
|
|
2605
|
-
|
|
2794
|
+
this.diagnosticsLevelCell.set(level);
|
|
2606
2795
|
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Asynchronous counterpart of {@link setDiagnosticsLevel} (spec
|
|
2798
|
+
* stream-connector 32 §13). It changes the same value; awaiting it is how a
|
|
2799
|
+
* caller observes the change, and it never replaces the synchronous surface.
|
|
2800
|
+
*/
|
|
2607
2801
|
setDiagnosticsLevelAsync(level) {
|
|
2608
|
-
this.
|
|
2802
|
+
this.setDiagnosticsLevel(level);
|
|
2609
2803
|
return Promise.resolve();
|
|
2610
2804
|
}
|
|
2611
2805
|
onErrorReceived(handler) {
|
|
@@ -2644,18 +2838,41 @@ var DefaultZlinkStreamConnector = class {
|
|
|
2644
2838
|
}, signal);
|
|
2645
2839
|
return this.receivedMessages.on(name, encodedHandler);
|
|
2646
2840
|
}
|
|
2647
|
-
waitFor(
|
|
2648
|
-
|
|
2649
|
-
return new ZlinkStreamWaitBuilder(this, name);
|
|
2841
|
+
waitFor(nameOrType) {
|
|
2842
|
+
return new ZlinkStreamWaitBuilder(this, this.observedName(nameOrType));
|
|
2650
2843
|
}
|
|
2651
|
-
expectNone(
|
|
2652
|
-
|
|
2653
|
-
|
|
2844
|
+
expectNone(nameOrType) {
|
|
2845
|
+
return new ZlinkStreamExpectNoneBuilder(this, this.observedName(nameOrType));
|
|
2846
|
+
}
|
|
2847
|
+
waitForSequence(nameOrType) {
|
|
2848
|
+
return new ZlinkStreamSequenceBuilder(this, this.observedName(nameOrType));
|
|
2654
2849
|
}
|
|
2655
|
-
|
|
2850
|
+
/**
|
|
2851
|
+
* Spec stream-connector 32 §10.1: each wait surface offers both ways of
|
|
2852
|
+
* naming a packet. A string is the name the caller states; a constructor
|
|
2853
|
+
* goes through the options' `nameResolver`, which is the same resolver
|
|
2854
|
+
* `send` and `request` use, so both paths land on one name for one type.
|
|
2855
|
+
*/
|
|
2856
|
+
observedName(nameOrType) {
|
|
2857
|
+
const name = typeof nameOrType === "function" ? this.options.nameResolver.resolve(nameOrType) : nameOrType;
|
|
2858
|
+
if (typeof name !== "string") {
|
|
2859
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "Packet name must be a string or a payload constructor.");
|
|
2860
|
+
}
|
|
2656
2861
|
validateName(name);
|
|
2657
|
-
return
|
|
2862
|
+
return name;
|
|
2658
2863
|
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Consumes the first message under `name` that `predicate` accepts, or
|
|
2866
|
+
* resolves `undefined` when `timeoutMs` elapses first.
|
|
2867
|
+
*
|
|
2868
|
+
* A timeout is not a failure here. Each wait surface decides what its own
|
|
2869
|
+
* timeout means — `waitFor` fails on it while `expectNone` succeeds — and
|
|
2870
|
+
* reports that decision as `ValidationFailed` (spec stream-connector 32
|
|
2871
|
+
* §10.1.1; .NET `ZlinkStreamReceivedMessages.WaitForAsync` parity). Losing
|
|
2872
|
+
* the connection the wait observes is the one ending decided here, because
|
|
2873
|
+
* the wait has no place left to observe: that is `Disconnected` for every
|
|
2874
|
+
* surface (§10.1.1).
|
|
2875
|
+
*/
|
|
2659
2876
|
waitForMessage(name, timeoutMs, predicate, signal) {
|
|
2660
2877
|
validateName(name);
|
|
2661
2878
|
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
|
@@ -2683,9 +2900,7 @@ var DefaultZlinkStreamConnector = class {
|
|
|
2683
2900
|
resolve(message);
|
|
2684
2901
|
}
|
|
2685
2902
|
};
|
|
2686
|
-
timer = setTimeout(() =>
|
|
2687
|
-
finish(connectorError("requestTimeout" /* RequestTimeout */, "Wait for stream message timed out."));
|
|
2688
|
-
}, timeoutMs);
|
|
2903
|
+
timer = setTimeout(() => finish(), timeoutMs);
|
|
2689
2904
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2690
2905
|
disposable = this.receivedMessages.observe(name, (message) => {
|
|
2691
2906
|
if (done) {
|
|
@@ -2707,7 +2922,10 @@ var DefaultZlinkStreamConnector = class {
|
|
|
2707
2922
|
finish(cause);
|
|
2708
2923
|
}
|
|
2709
2924
|
return true;
|
|
2710
|
-
})
|
|
2925
|
+
}, () => finish(connectorError(
|
|
2926
|
+
"disconnected" /* Disconnected */,
|
|
2927
|
+
`The connection this wait for '${name}' observed has ended.`
|
|
2928
|
+
)));
|
|
2711
2929
|
});
|
|
2712
2930
|
}
|
|
2713
2931
|
encodePayload(payload, messageType) {
|
|
@@ -2786,50 +3004,6 @@ function isEncodedPayload(value) {
|
|
|
2786
3004
|
return typeof candidate.codec === "number" && candidate.payload instanceof Uint8Array;
|
|
2787
3005
|
}
|
|
2788
3006
|
|
|
2789
|
-
// packages/stream-connector/src/Runtime/ZlinkStreamAssertions.ts
|
|
2790
|
-
var zlinkStreamAssert = {
|
|
2791
|
-
ensure(condition, message) {
|
|
2792
|
-
if (!condition) {
|
|
2793
|
-
throw connectorError("validationFailed" /* ValidationFailed */, message);
|
|
2794
|
-
}
|
|
2795
|
-
},
|
|
2796
|
-
async expectFailure(action, errorKind) {
|
|
2797
|
-
let failure;
|
|
2798
|
-
try {
|
|
2799
|
-
await action();
|
|
2800
|
-
} catch (error) {
|
|
2801
|
-
failure = error;
|
|
2802
|
-
}
|
|
2803
|
-
if (failure === void 0) {
|
|
2804
|
-
throw connectorError("validationFailed" /* ValidationFailed */, "Expected action to fail.");
|
|
2805
|
-
}
|
|
2806
|
-
const streamError = unwrapStreamError(failure);
|
|
2807
|
-
if (errorKind !== void 0 && streamError.code !== errorKind) {
|
|
2808
|
-
throw connectorError(
|
|
2809
|
-
"validationFailed" /* ValidationFailed */,
|
|
2810
|
-
`Expected failure kind '${errorKind}', got '${streamError.code}'.`,
|
|
2811
|
-
failure
|
|
2812
|
-
);
|
|
2813
|
-
}
|
|
2814
|
-
return streamError;
|
|
2815
|
-
},
|
|
2816
|
-
async expectTimeout(action) {
|
|
2817
|
-
let failure;
|
|
2818
|
-
try {
|
|
2819
|
-
await action();
|
|
2820
|
-
} catch (error) {
|
|
2821
|
-
failure = error;
|
|
2822
|
-
}
|
|
2823
|
-
if (failure === void 0) {
|
|
2824
|
-
throw connectorError("validationFailed" /* ValidationFailed */, "Expected action to time out.");
|
|
2825
|
-
}
|
|
2826
|
-
const code = unwrapStreamError(failure).code;
|
|
2827
|
-
if (code !== "requestTimeout" /* RequestTimeout */ && code !== "connectTimeout" /* ConnectTimeout */) {
|
|
2828
|
-
throw failure;
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
};
|
|
2832
|
-
|
|
2833
3007
|
// packages/stream-connector/src/index.ts
|
|
2834
3008
|
var zlinkStreamConnectorFactory = {
|
|
2835
3009
|
create(options) {
|
|
@@ -2837,7 +3011,6 @@ var zlinkStreamConnectorFactory = {
|
|
|
2837
3011
|
}
|
|
2838
3012
|
};
|
|
2839
3013
|
export {
|
|
2840
|
-
DefaultZlinkStreamConnector,
|
|
2841
3014
|
ZlinkStreamCodec,
|
|
2842
3015
|
ZlinkStreamCompression,
|
|
2843
3016
|
ZlinkStreamConnectionState,
|