@zlink-systems/stream-connector 0.24.0 → 0.26.0
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/Runtime/Calls/ZlinkStreamCallBuilders.d.ts +2 -1
- package/dist/browser/Runtime/Transport/BrowserWebSocketConnection.d.ts +5 -0
- package/dist/browser/Runtime/ZlinkStreamActors.d.ts +0 -1
- package/dist/browser/Runtime/ZlinkStreamConnector.d.ts +1 -1
- package/dist/browser/Runtime/ZlinkStreamConnectorEvents.d.ts +18 -3
- package/dist/browser/Runtime/ZlinkStreamConnectorLifecycle.d.ts +34 -30
- package/dist/browser/Runtime/ZlinkStreamFrameSender.d.ts +21 -5
- package/dist/browser/Runtime/ZlinkStreamPendingRequests.d.ts +2 -2
- package/dist/browser/Runtime/ZlinkStreamReceiveDispatcher.d.ts +21 -5
- package/dist/browser/Runtime/ZlinkStreamReceivedMessages.d.ts +35 -14
- package/dist/browser/index.global.js +887 -611
- package/dist/browser/index.mjs +858 -578
- package/package.json +2 -2
package/dist/browser/index.mjs
CHANGED
|
@@ -9,11 +9,7 @@ function lz4PickleUncompressed(payload) {
|
|
|
9
9
|
pickled.set(payload, 1);
|
|
10
10
|
return pickled;
|
|
11
11
|
}
|
|
12
|
-
function
|
|
13
|
-
const maxDecompressedSize = maxSize ?? defaultMaxDecompressedPayloadSize;
|
|
14
|
-
if (payload.length === 0) {
|
|
15
|
-
return new Uint8Array();
|
|
16
|
-
}
|
|
12
|
+
function readLz4Pickle(payload) {
|
|
17
13
|
const header = payload[0];
|
|
18
14
|
if ((header & 7) !== 0) {
|
|
19
15
|
throw new Error("Unexpected LZ4 pickle version.");
|
|
@@ -25,7 +21,17 @@ function lz4UnpicklePayload(payload, maxSize) {
|
|
|
25
21
|
}
|
|
26
22
|
const data = payload.subarray(dataOffset);
|
|
27
23
|
const resultDiff = sizeOfDiff === 0 ? 0 : readLittleEndian(payload, 1, sizeOfDiff);
|
|
28
|
-
|
|
24
|
+
return { data, resultDiff, resultLength: data.length + resultDiff };
|
|
25
|
+
}
|
|
26
|
+
function lz4PickledLength(payload) {
|
|
27
|
+
return payload.length === 0 ? 0 : readLz4Pickle(payload).resultLength;
|
|
28
|
+
}
|
|
29
|
+
function lz4UnpicklePayload(payload, maxSize) {
|
|
30
|
+
const maxDecompressedSize = maxSize ?? defaultMaxDecompressedPayloadSize;
|
|
31
|
+
if (payload.length === 0) {
|
|
32
|
+
return new Uint8Array();
|
|
33
|
+
}
|
|
34
|
+
const { data, resultDiff, resultLength } = readLz4Pickle(payload);
|
|
29
35
|
if (resultLength > maxDecompressedSize) {
|
|
30
36
|
throw new Error("LZ4 decoded payload exceeds maximum stream payload size.");
|
|
31
37
|
}
|
|
@@ -376,6 +382,9 @@ function decodeStreamWireMetadata(metadata) {
|
|
|
376
382
|
function lz4PickleUncompressed2(payload) {
|
|
377
383
|
return lz4PickleUncompressed(payload);
|
|
378
384
|
}
|
|
385
|
+
function lz4PickledLength2(payload) {
|
|
386
|
+
return lz4PickledLength(payload);
|
|
387
|
+
}
|
|
379
388
|
function lz4UnpicklePayload2(payload, maxDecompressedSize) {
|
|
380
389
|
return lz4UnpicklePayload(payload, maxDecompressedSize ?? defaultMaxDecompressedPayloadSize);
|
|
381
390
|
}
|
|
@@ -817,9 +826,10 @@ var ZlinkStreamSendBuilder = class {
|
|
|
817
826
|
}
|
|
818
827
|
};
|
|
819
828
|
var ZlinkStreamRequestBuilder = class {
|
|
820
|
-
constructor(connector, name, payload, actorSlot, validateActor) {
|
|
829
|
+
constructor(connector, name, payload, enqueueCallback, actorSlot, validateActor) {
|
|
821
830
|
this.connector = connector;
|
|
822
831
|
this.payload = payload;
|
|
832
|
+
this.enqueueCallback = enqueueCallback;
|
|
823
833
|
this.state = new ZlinkStreamCallBuilderState(name, actorSlot, validateActor);
|
|
824
834
|
}
|
|
825
835
|
state;
|
|
@@ -853,8 +863,10 @@ var ZlinkStreamRequestBuilder = class {
|
|
|
853
863
|
);
|
|
854
864
|
if (typeof signalOrCallback === "function") {
|
|
855
865
|
operation.then(
|
|
856
|
-
(value) => signalOrCallback({ isSuccess: true, value }),
|
|
857
|
-
(error) =>
|
|
866
|
+
(value) => this.enqueueCallback(() => signalOrCallback({ isSuccess: true, value })),
|
|
867
|
+
(error) => this.enqueueCallback(
|
|
868
|
+
() => signalOrCallback({ isSuccess: false, error: unwrapStreamError(error) })
|
|
869
|
+
)
|
|
858
870
|
);
|
|
859
871
|
return;
|
|
860
872
|
}
|
|
@@ -1059,6 +1071,12 @@ var zlinkStreamLz4CompressionCodec = {
|
|
|
1059
1071
|
return lz4PickleUncompressed2(payload);
|
|
1060
1072
|
},
|
|
1061
1073
|
decompress(payload, maxDecompressedSize) {
|
|
1074
|
+
if (lz4PickledLength2(payload) > maxDecompressedSize) {
|
|
1075
|
+
throw connectorError(
|
|
1076
|
+
"frameTooLarge" /* FrameTooLarge */,
|
|
1077
|
+
"LZ4 decoded payload exceeds MaxReceivePayloadSize."
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1062
1080
|
return lz4UnpicklePayload2(payload, maxDecompressedSize);
|
|
1063
1081
|
}
|
|
1064
1082
|
};
|
|
@@ -1070,7 +1088,11 @@ function compressPayload(payload, compression, compressionCodec) {
|
|
|
1070
1088
|
"Compression codec is not configured."
|
|
1071
1089
|
);
|
|
1072
1090
|
}
|
|
1073
|
-
|
|
1091
|
+
try {
|
|
1092
|
+
return codec.compress(payload);
|
|
1093
|
+
} catch (cause) {
|
|
1094
|
+
throw connectorError("compressionFailed" /* CompressionFailed */, "Compression failed.", cause);
|
|
1095
|
+
}
|
|
1074
1096
|
}
|
|
1075
1097
|
function decompressIfNeeded(header, payload, compression, compressionCodec, maxDecompressedSize) {
|
|
1076
1098
|
if ((header.flags & 4 /* PayloadCompressed */) === 0) {
|
|
@@ -1083,15 +1105,20 @@ function decompressIfNeeded(header, payload, compression, compressionCodec, maxD
|
|
|
1083
1105
|
"Compression codec is not configured."
|
|
1084
1106
|
);
|
|
1085
1107
|
}
|
|
1108
|
+
let decompressed;
|
|
1086
1109
|
try {
|
|
1087
|
-
|
|
1088
|
-
if (decompressed.length > maxDecompressedSize) {
|
|
1089
|
-
throw new Error("Decoded payload exceeds MaxReceivePayloadSize.");
|
|
1090
|
-
}
|
|
1091
|
-
return decompressed;
|
|
1110
|
+
decompressed = codec.decompress(payload, maxDecompressedSize);
|
|
1092
1111
|
} catch (cause) {
|
|
1112
|
+
if (cause instanceof ZlinkStreamException) throw cause;
|
|
1093
1113
|
throw connectorError("decompressionFailed" /* DecompressionFailed */, "Decompression failed.", cause);
|
|
1094
1114
|
}
|
|
1115
|
+
if (decompressed.length > maxDecompressedSize) {
|
|
1116
|
+
throw connectorError(
|
|
1117
|
+
"frameTooLarge" /* FrameTooLarge */,
|
|
1118
|
+
"Decompressed payload exceeds MaxReceivePayloadSize."
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
return decompressed;
|
|
1095
1122
|
}
|
|
1096
1123
|
function resolveCompressionCodec(compression, compressionCodec) {
|
|
1097
1124
|
if (compression === "none" /* None */) {
|
|
@@ -1459,6 +1486,40 @@ function normalizeOptions(options, defaultTransportFactory) {
|
|
|
1459
1486
|
validatePositive(options.maxReceivePayloadSize ?? 64 * 1024, "MaxReceivePayloadSize");
|
|
1460
1487
|
validateHeartbeat(options.heartbeat);
|
|
1461
1488
|
validateReconnect(options.reconnect);
|
|
1489
|
+
if (options.heartbeat?.enabled !== void 0 && typeof options.heartbeat.enabled !== "boolean") {
|
|
1490
|
+
throw connectorError(
|
|
1491
|
+
"validationFailed" /* ValidationFailed */,
|
|
1492
|
+
"Heartbeat enabled must be boolean."
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
if (options.reconnect?.enabled !== void 0 && typeof options.reconnect.enabled !== "boolean") {
|
|
1496
|
+
throw connectorError(
|
|
1497
|
+
"validationFailed" /* ValidationFailed */,
|
|
1498
|
+
"Reconnect enabled must be boolean."
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
if (options.compressionCodec !== void 0 && !hasMethods(options.compressionCodec, "compress", "decompress")) {
|
|
1502
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "Compression codec is invalid.");
|
|
1503
|
+
}
|
|
1504
|
+
if (options.codec !== void 0 && !hasMethods(options.codec, "encode", "decode")) {
|
|
1505
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "Payload codec is invalid.");
|
|
1506
|
+
}
|
|
1507
|
+
if (options.nameResolver !== void 0 && !hasMethods(options.nameResolver, "resolve")) {
|
|
1508
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "Packet name resolver is invalid.");
|
|
1509
|
+
}
|
|
1510
|
+
if (options.transportFactory !== void 0 && !hasMethods(options.transportFactory, "connect")) {
|
|
1511
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "Transport factory is invalid.");
|
|
1512
|
+
}
|
|
1513
|
+
if (!Object.values(ZlinkStreamDispatchMode).includes(
|
|
1514
|
+
options.dispatchMode ?? "manual" /* Manual */
|
|
1515
|
+
)) {
|
|
1516
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "DispatchMode is invalid.");
|
|
1517
|
+
}
|
|
1518
|
+
if (!Object.values(ZlinkStreamCompression).includes(
|
|
1519
|
+
options.compression ?? "lz4" /* Lz4 */
|
|
1520
|
+
)) {
|
|
1521
|
+
throw connectorError("validationFailed" /* ValidationFailed */, "Compression is invalid.");
|
|
1522
|
+
}
|
|
1462
1523
|
return {
|
|
1463
1524
|
endpoint,
|
|
1464
1525
|
transport: inferredTransport,
|
|
@@ -1510,39 +1571,25 @@ function resolveCompressionCodec2(options) {
|
|
|
1510
1571
|
return options.compressionCodec;
|
|
1511
1572
|
}
|
|
1512
1573
|
function validatePositive(value, name) {
|
|
1513
|
-
if (value <= 0) {
|
|
1574
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
1514
1575
|
throw connectorError("validationFailed" /* ValidationFailed */, `${name} must be positive.`);
|
|
1515
1576
|
}
|
|
1516
1577
|
}
|
|
1578
|
+
function hasMethods(value, ...names) {
|
|
1579
|
+
if (value === null || typeof value !== "object") return false;
|
|
1580
|
+
const candidate = value;
|
|
1581
|
+
return names.every((name) => typeof candidate[name] === "function");
|
|
1582
|
+
}
|
|
1517
1583
|
function validateHeartbeat(options) {
|
|
1518
|
-
const enabled = options?.enabled ?? true;
|
|
1519
1584
|
const intervalMs = options?.intervalMs ?? 1e3;
|
|
1520
1585
|
const timeoutMs = options?.timeoutMs ?? 5e3;
|
|
1521
|
-
if (!enabled) {
|
|
1522
|
-
return;
|
|
1523
|
-
}
|
|
1524
1586
|
validatePositive(intervalMs, "Heartbeat interval");
|
|
1525
1587
|
validatePositive(timeoutMs, "Heartbeat timeout");
|
|
1526
|
-
if (timeoutMs <= intervalMs) {
|
|
1527
|
-
throw connectorError(
|
|
1528
|
-
"validationFailed" /* ValidationFailed */,
|
|
1529
|
-
"Heartbeat timeout must be greater than the heartbeat interval."
|
|
1530
|
-
);
|
|
1531
|
-
}
|
|
1532
1588
|
}
|
|
1533
1589
|
function validateReconnect(options) {
|
|
1534
|
-
const enabled = options?.enabled ?? true;
|
|
1535
|
-
if (!enabled) {
|
|
1536
|
-
return;
|
|
1537
|
-
}
|
|
1538
1590
|
validatePositive(options?.initialDelayMs ?? 250, "Reconnect InitialDelay");
|
|
1539
1591
|
validatePositive(options?.maxDelayMs ?? 5e3, "Reconnect MaxDelay");
|
|
1540
|
-
|
|
1541
|
-
throw connectorError(
|
|
1542
|
-
"validationFailed" /* ValidationFailed */,
|
|
1543
|
-
"Reconnect BackoffFactor must be at least 1.0."
|
|
1544
|
-
);
|
|
1545
|
-
}
|
|
1592
|
+
validatePositive(options?.backoffFactor ?? 2, "Reconnect BackoffFactor");
|
|
1546
1593
|
const maxAttempts = options?.maxAttempts === void 0 ? 3 : options.maxAttempts;
|
|
1547
1594
|
if (maxAttempts !== null && !(Number.isFinite(maxAttempts) && maxAttempts > 0)) {
|
|
1548
1595
|
throw connectorError(
|
|
@@ -1556,32 +1603,33 @@ function validateReconnect(options) {
|
|
|
1556
1603
|
var ZlinkStreamPendingRequests = class {
|
|
1557
1604
|
nextRequestSeq = 1n;
|
|
1558
1605
|
active = /* @__PURE__ */ new Map();
|
|
1559
|
-
get count() {
|
|
1560
|
-
return this.active.size;
|
|
1561
|
-
}
|
|
1562
1606
|
create(packetName, timeoutMs) {
|
|
1607
|
+
if (this.nextRequestSeq > 0xffffffffffffffffn) {
|
|
1608
|
+
throw connectorError("sendFailed" /* SendFailed */, "Request sequence is exhausted.");
|
|
1609
|
+
}
|
|
1563
1610
|
const requestSeq = this.nextRequestSeq++;
|
|
1564
1611
|
let timeout;
|
|
1565
1612
|
let resolvePending;
|
|
1566
1613
|
let rejectPending;
|
|
1567
1614
|
const promise = new Promise(
|
|
1568
1615
|
(resolve, reject) => {
|
|
1569
|
-
timeout = setTimeout(() => {
|
|
1570
|
-
this.active.delete(requestSeq);
|
|
1571
|
-
reject(
|
|
1572
|
-
connectorError(
|
|
1573
|
-
"requestTimeout" /* RequestTimeout */,
|
|
1574
|
-
`Request '${packetName}' timed out.`
|
|
1575
|
-
)
|
|
1576
|
-
);
|
|
1577
|
-
}, timeoutMs);
|
|
1578
1616
|
resolvePending = resolve;
|
|
1579
1617
|
rejectPending = (error) => reject(connectorError(error.code, error.message, error.cause));
|
|
1580
1618
|
}
|
|
1581
1619
|
);
|
|
1582
|
-
|
|
1620
|
+
const tracked = {
|
|
1583
1621
|
packetName,
|
|
1584
1622
|
promise,
|
|
1623
|
+
startTimeout: () => {
|
|
1624
|
+
if (timeout !== void 0 || this.active.get(requestSeq) !== tracked) return;
|
|
1625
|
+
timeout = setTimeout(() => {
|
|
1626
|
+
this.active.delete(requestSeq);
|
|
1627
|
+
rejectPending({
|
|
1628
|
+
code: "requestTimeout" /* RequestTimeout */,
|
|
1629
|
+
message: `Request '${packetName}' timed out.`
|
|
1630
|
+
});
|
|
1631
|
+
}, timeoutMs);
|
|
1632
|
+
},
|
|
1585
1633
|
resolve: (value) => {
|
|
1586
1634
|
if (timeout !== void 0) {
|
|
1587
1635
|
clearTimeout(timeout);
|
|
@@ -1599,8 +1647,9 @@ var ZlinkStreamPendingRequests = class {
|
|
|
1599
1647
|
clearTimeout(timeout);
|
|
1600
1648
|
}
|
|
1601
1649
|
}
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1650
|
+
};
|
|
1651
|
+
this.active.set(requestSeq, tracked);
|
|
1652
|
+
return { requestSeq, promise, startTimeout: tracked.startTimeout };
|
|
1604
1653
|
}
|
|
1605
1654
|
/* stream connector spec §5.2: a pending request is matched by request_seq alone. */
|
|
1606
1655
|
resolve(requestSeq, value, metadata) {
|
|
@@ -1608,8 +1657,9 @@ var ZlinkStreamPendingRequests = class {
|
|
|
1608
1657
|
if (pending === void 0) {
|
|
1609
1658
|
return false;
|
|
1610
1659
|
}
|
|
1660
|
+
const payload = value();
|
|
1611
1661
|
this.active.delete(requestSeq);
|
|
1612
|
-
pending.resolve({ name: pending.packetName, metadata, payload
|
|
1662
|
+
pending.resolve({ name: pending.packetName, metadata, payload });
|
|
1613
1663
|
return true;
|
|
1614
1664
|
}
|
|
1615
1665
|
reject(requestSeq, error) {
|
|
@@ -1637,7 +1687,164 @@ var ZlinkStreamPendingRequests = class {
|
|
|
1637
1687
|
}
|
|
1638
1688
|
};
|
|
1639
1689
|
|
|
1690
|
+
// packages/stream-connector/src/Runtime/ZlinkStreamActors.ts
|
|
1691
|
+
var ACTOR_BOUND = "$zlink.actor.bound";
|
|
1692
|
+
var ACTOR_UNBOUND = "$zlink.actor.unbound";
|
|
1693
|
+
var zlinkStreamActorBinding = Symbol("zlink.stream.actorBinding");
|
|
1694
|
+
var DefaultZlinkStreamActor = class {
|
|
1695
|
+
constructor(connector, actorId, slot) {
|
|
1696
|
+
this.connector = connector;
|
|
1697
|
+
this.actorId = actorId;
|
|
1698
|
+
this.slot = slot;
|
|
1699
|
+
}
|
|
1700
|
+
bound = true;
|
|
1701
|
+
get isBound() {
|
|
1702
|
+
return this.bound;
|
|
1703
|
+
}
|
|
1704
|
+
send(payload, messageType) {
|
|
1705
|
+
this.ensureBound();
|
|
1706
|
+
return this.connector.sendForActor(this, payload, messageType);
|
|
1707
|
+
}
|
|
1708
|
+
request(payload, messageType) {
|
|
1709
|
+
this.ensureBound();
|
|
1710
|
+
return this.connector.requestForActor(this, payload, messageType);
|
|
1711
|
+
}
|
|
1712
|
+
on(nameOrType, handler, messageType) {
|
|
1713
|
+
return this.connector.onActorMessage(this, nameOrType, handler, messageType);
|
|
1714
|
+
}
|
|
1715
|
+
ensureBound() {
|
|
1716
|
+
if (!this.bound) {
|
|
1717
|
+
throw connectorError(
|
|
1718
|
+
"validationFailed" /* ValidationFailed */,
|
|
1719
|
+
`Actor '${this.actorId}' is no longer bound.`
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
close() {
|
|
1724
|
+
this.bound = false;
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
var ZlinkStreamActors = class {
|
|
1728
|
+
constructor(connector, receivedMessages, events) {
|
|
1729
|
+
this.connector = connector;
|
|
1730
|
+
this.receivedMessages = receivedMessages;
|
|
1731
|
+
this.events = events;
|
|
1732
|
+
}
|
|
1733
|
+
bySlot = /* @__PURE__ */ new Map();
|
|
1734
|
+
byId = /* @__PURE__ */ new Map();
|
|
1735
|
+
issued = [];
|
|
1736
|
+
boundHandlers = /* @__PURE__ */ new Set();
|
|
1737
|
+
unboundHandlers = /* @__PURE__ */ new Set();
|
|
1738
|
+
get snapshot() {
|
|
1739
|
+
return Object.freeze(Array.from(this.bySlot.values()));
|
|
1740
|
+
}
|
|
1741
|
+
find(actorId) {
|
|
1742
|
+
return this.byId.get(actorId);
|
|
1743
|
+
}
|
|
1744
|
+
onBound(handler) {
|
|
1745
|
+
this.boundHandlers.add(handler);
|
|
1746
|
+
return subscription(() => this.boundHandlers.delete(handler));
|
|
1747
|
+
}
|
|
1748
|
+
onUnbound(handler) {
|
|
1749
|
+
this.unboundHandlers.add(handler);
|
|
1750
|
+
return subscription(() => this.unboundHandlers.delete(handler));
|
|
1751
|
+
}
|
|
1752
|
+
processControl(name, payload, signal) {
|
|
1753
|
+
if (name === ACTOR_BOUND) {
|
|
1754
|
+
this.bind(payload, signal);
|
|
1755
|
+
return true;
|
|
1756
|
+
}
|
|
1757
|
+
if (name === ACTOR_UNBOUND) {
|
|
1758
|
+
this.unbind(payload, signal);
|
|
1759
|
+
return true;
|
|
1760
|
+
}
|
|
1761
|
+
return false;
|
|
1762
|
+
}
|
|
1763
|
+
resolve(slot) {
|
|
1764
|
+
const actor = this.bySlot.get(slot);
|
|
1765
|
+
if (actor === void 0) {
|
|
1766
|
+
throw connectorError(
|
|
1767
|
+
"frameDecodeFailed" /* FrameDecodeFailed */,
|
|
1768
|
+
`Actor slot '${slot}' is not bound.`
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
return actor;
|
|
1772
|
+
}
|
|
1773
|
+
closeAll(signal) {
|
|
1774
|
+
for (const actor of this.issued) {
|
|
1775
|
+
if (!actor.isBound) continue;
|
|
1776
|
+
this.bySlot.delete(actor.slot);
|
|
1777
|
+
this.byId.delete(actor.actorId);
|
|
1778
|
+
actor.close();
|
|
1779
|
+
this.queue(this.unboundHandlers, actor, signal);
|
|
1780
|
+
}
|
|
1781
|
+
this.issued.length = 0;
|
|
1782
|
+
}
|
|
1783
|
+
bind(payload, signal) {
|
|
1784
|
+
if (payload.length < 5 || payload[0] !== 1) {
|
|
1785
|
+
throw invalidControl("Actor bound payload is invalid.");
|
|
1786
|
+
}
|
|
1787
|
+
const slot = payload[1] << 8 | payload[2];
|
|
1788
|
+
const idLength = payload[3];
|
|
1789
|
+
if (slot === 0 || idLength === 0 || payload.length !== 4 + idLength) {
|
|
1790
|
+
throw invalidControl("Actor bound payload is invalid.");
|
|
1791
|
+
}
|
|
1792
|
+
let actorId;
|
|
1793
|
+
try {
|
|
1794
|
+
actorId = new TextDecoder("utf-8", { fatal: true }).decode(payload.subarray(4));
|
|
1795
|
+
} catch (cause) {
|
|
1796
|
+
throw connectorError(
|
|
1797
|
+
"frameDecodeFailed" /* FrameDecodeFailed */,
|
|
1798
|
+
"Actor id is not valid UTF-8.",
|
|
1799
|
+
cause
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
if (actorId.length === 0 || this.bySlot.has(slot) || this.byId.has(actorId)) {
|
|
1803
|
+
throw invalidControl("Actor bound identity is already in use.");
|
|
1804
|
+
}
|
|
1805
|
+
const actor = new DefaultZlinkStreamActor(this.connector, actorId, slot);
|
|
1806
|
+
this.bySlot.set(slot, actor);
|
|
1807
|
+
this.byId.set(actorId, actor);
|
|
1808
|
+
this.issued.push(actor);
|
|
1809
|
+
this.queue(this.boundHandlers, actor, signal);
|
|
1810
|
+
}
|
|
1811
|
+
unbind(payload, signal) {
|
|
1812
|
+
if (payload.length !== 3 || payload[0] !== 1) {
|
|
1813
|
+
throw invalidControl("Actor unbound payload is invalid.");
|
|
1814
|
+
}
|
|
1815
|
+
const slot = payload[1] << 8 | payload[2];
|
|
1816
|
+
const actor = this.bySlot.get(slot);
|
|
1817
|
+
if (slot === 0 || actor === void 0) {
|
|
1818
|
+
throw invalidControl(`Actor slot '${slot}' is not bound.`);
|
|
1819
|
+
}
|
|
1820
|
+
this.bySlot.delete(slot);
|
|
1821
|
+
this.byId.delete(actor.actorId);
|
|
1822
|
+
actor.close();
|
|
1823
|
+
this.queue(this.unboundHandlers, actor, signal);
|
|
1824
|
+
}
|
|
1825
|
+
queue(handlers, actor, signal) {
|
|
1826
|
+
this.receivedMessages.enqueueCallback(
|
|
1827
|
+
() => {
|
|
1828
|
+
for (const handler of Array.from(handlers)) {
|
|
1829
|
+
this.events.runUserCallback(
|
|
1830
|
+
() => handler(actor, signal),
|
|
1831
|
+
"Actor lifecycle handler failed.",
|
|
1832
|
+
signal
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
},
|
|
1836
|
+
() => handlers.size
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
function invalidControl(message) {
|
|
1841
|
+
return connectorError("frameDecodeFailed" /* FrameDecodeFailed */, message);
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1640
1844
|
// packages/stream-connector/src/Runtime/ZlinkStreamReceivedMessages.ts
|
|
1845
|
+
function receives(registration, message) {
|
|
1846
|
+
return registration.actor === void 0 || registration.actor === message[zlinkStreamActorBinding];
|
|
1847
|
+
}
|
|
1641
1848
|
var ZlinkStreamReceivedMessages = class {
|
|
1642
1849
|
/**
|
|
1643
1850
|
* @param deliverOnArrival `Immediate` runs registered handlers on the receive
|
|
@@ -1655,10 +1862,10 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1655
1862
|
// queue is not a simple FIFO. Tombstones let us remove a deliverable entry
|
|
1656
1863
|
// without shifting every later message on the hot receive path.
|
|
1657
1864
|
queue = [];
|
|
1865
|
+
deliverable = [];
|
|
1658
1866
|
queueHead = 0;
|
|
1659
1867
|
queuedCount = 0;
|
|
1660
|
-
|
|
1661
|
-
// True for as long as `drain` is on the stack, handler awaits included. It
|
|
1868
|
+
// True for as long as `pump` is on the stack. It
|
|
1662
1869
|
// marks the execution context a registered handler runs in, so a `dispatch`
|
|
1663
1870
|
// made from inside a handler is recognised as re-entry rather than a fresh
|
|
1664
1871
|
// pump. It is not a lock: a single event loop admits no second thread, and
|
|
@@ -1668,19 +1875,32 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1668
1875
|
// connection. It is raised where a packet arrives, never where one is taken,
|
|
1669
1876
|
// so consuming does not lower it and the dispatch mode does not change it.
|
|
1670
1877
|
receivedCounts = /* @__PURE__ */ new Map();
|
|
1671
|
-
|
|
1878
|
+
/**
|
|
1879
|
+
* @param actor The Actor handle that registers the handler, if any. Its
|
|
1880
|
+
* handler receives only that Actor's packets.
|
|
1881
|
+
*/
|
|
1882
|
+
on(name, handler, actor) {
|
|
1672
1883
|
validateName(name);
|
|
1673
1884
|
let set = this.handlers.get(name);
|
|
1674
1885
|
if (set === void 0) {
|
|
1675
1886
|
set = /* @__PURE__ */ new Set();
|
|
1676
1887
|
this.handlers.set(name, set);
|
|
1677
1888
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1889
|
+
const registration = { handle: handler, actor };
|
|
1890
|
+
set.add(registration);
|
|
1891
|
+
let newlyDeliverable = false;
|
|
1892
|
+
for (let index = this.queueHead; index < this.queue.length; index += 1) {
|
|
1893
|
+
const queued = this.queue[index];
|
|
1894
|
+
if (queued?.kind === "message" && queued.indexed !== true && queued.message.name === name && receives(registration, queued.message)) {
|
|
1895
|
+
this.indexDeliverable(index);
|
|
1896
|
+
newlyDeliverable = true;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
if (this.deliverOnArrival && newlyDeliverable) {
|
|
1900
|
+
queueMicrotask(() => this.pump());
|
|
1681
1901
|
}
|
|
1682
1902
|
return subscription(() => {
|
|
1683
|
-
set.delete(
|
|
1903
|
+
set.delete(registration);
|
|
1684
1904
|
if (set.size === 0 && this.handlers.get(name) === set) {
|
|
1685
1905
|
this.handlers.delete(name);
|
|
1686
1906
|
}
|
|
@@ -1750,6 +1970,11 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1750
1970
|
this.queue.push(...submittedCallbacks);
|
|
1751
1971
|
this.queueHead = 0;
|
|
1752
1972
|
this.queuedCount = submittedCallbacks.length;
|
|
1973
|
+
this.deliverable.length = 0;
|
|
1974
|
+
for (let index = 0; index < this.queue.length; index += 1) {
|
|
1975
|
+
this.queue[index].indexed = false;
|
|
1976
|
+
this.indexDeliverable(index);
|
|
1977
|
+
}
|
|
1753
1978
|
}
|
|
1754
1979
|
/**
|
|
1755
1980
|
* Releases every registered wait surface because the connection it was
|
|
@@ -1780,60 +2005,38 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1780
2005
|
}
|
|
1781
2006
|
this.queue.push({ kind: "message", message, signal });
|
|
1782
2007
|
this.queuedCount += 1;
|
|
2008
|
+
this.indexDeliverable(this.queue.length - 1);
|
|
1783
2009
|
if (this.deliverOnArrival) {
|
|
1784
|
-
this.
|
|
2010
|
+
this.pump();
|
|
1785
2011
|
}
|
|
1786
2012
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
2013
|
+
/**
|
|
2014
|
+
* @param callbacks The number of handler calls `callback` makes when it runs
|
|
2015
|
+
* with the handlers registered at that moment; a callback that runs a set
|
|
2016
|
+
* of handlers reads the set's size. One callback by default.
|
|
2017
|
+
*/
|
|
2018
|
+
enqueueCallback(callback, callbacks = () => 1) {
|
|
2019
|
+
this.queue.push({ kind: "callback", callback, callbacks });
|
|
1789
2020
|
this.queuedCount += 1;
|
|
2021
|
+
this.indexDeliverable(this.queue.length - 1);
|
|
1790
2022
|
if (this.deliverOnArrival) {
|
|
1791
|
-
this.
|
|
2023
|
+
this.pump();
|
|
1792
2024
|
}
|
|
1793
2025
|
}
|
|
1794
2026
|
/**
|
|
1795
2027
|
* Runs the registered handlers the receive path left queued. `Manual` calls
|
|
1796
|
-
* this from `dispatch`; `Immediate`
|
|
2028
|
+
* this from `dispatch`; `Immediate` drains on arrival.
|
|
1797
2029
|
*
|
|
1798
|
-
*
|
|
1799
|
-
*
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
1802
|
-
*
|
|
1803
|
-
* would deliver and returning is the whole of the correct behaviour.
|
|
2030
|
+
* The drain is synchronous: the connector does not wait for a handler
|
|
2031
|
+
* (spec stream-connector 32 §7), so nothing inside it awaits. A handler that
|
|
2032
|
+
* calls `dispatch` arrives back here from inside the drain it was started by;
|
|
2033
|
+
* the running loop already takes every deliverable entry, so returning is the
|
|
2034
|
+
* whole of the correct behaviour.
|
|
1804
2035
|
*/
|
|
1805
|
-
|
|
2036
|
+
pump() {
|
|
1806
2037
|
if (this.draining) {
|
|
1807
2038
|
return;
|
|
1808
2039
|
}
|
|
1809
|
-
this.scheduleDrain();
|
|
1810
|
-
await this.drainTask;
|
|
1811
|
-
}
|
|
1812
|
-
offerQueued(name, registration) {
|
|
1813
|
-
for (let index = this.queueHead; index < this.queue.length; index += 1) {
|
|
1814
|
-
const queued = this.queue[index];
|
|
1815
|
-
if (queued === void 0 || queued.kind !== "message" || queued.message.name !== name) {
|
|
1816
|
-
continue;
|
|
1817
|
-
}
|
|
1818
|
-
if (!registration.consume(queued.message)) {
|
|
1819
|
-
continue;
|
|
1820
|
-
}
|
|
1821
|
-
this.removeAt(index);
|
|
1822
|
-
return;
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
scheduleDrain() {
|
|
1826
|
-
if (this.drainTask !== void 0) {
|
|
1827
|
-
return;
|
|
1828
|
-
}
|
|
1829
|
-
this.drainTask = this.drain().finally(() => {
|
|
1830
|
-
this.drainTask = void 0;
|
|
1831
|
-
if (this.deliverOnArrival && this.findDeliverableIndex() >= 0) {
|
|
1832
|
-
this.scheduleDrain();
|
|
1833
|
-
}
|
|
1834
|
-
});
|
|
1835
|
-
}
|
|
1836
|
-
async drain() {
|
|
1837
2040
|
this.draining = true;
|
|
1838
2041
|
try {
|
|
1839
2042
|
for (let index = this.findDeliverableIndex(); index >= 0; index = this.findDeliverableIndex()) {
|
|
@@ -1841,51 +2044,113 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1841
2044
|
if (queued === void 0) continue;
|
|
1842
2045
|
this.removeAt(index);
|
|
1843
2046
|
if (queued.kind === "callback") {
|
|
1844
|
-
|
|
2047
|
+
this.events.runUserCallback(queued.callback, "Connector callback failed.");
|
|
1845
2048
|
continue;
|
|
1846
2049
|
}
|
|
1847
2050
|
const { message, signal } = queued;
|
|
1848
|
-
const handlers =
|
|
2051
|
+
const handlers = this.receiversOf(message);
|
|
1849
2052
|
for (const handler of handlers) {
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
code: "userCallbackFailed" /* UserCallbackFailed */,
|
|
1856
|
-
message: "Typed message handler failed.",
|
|
1857
|
-
cause
|
|
1858
|
-
},
|
|
1859
|
-
signal
|
|
1860
|
-
);
|
|
1861
|
-
}
|
|
2053
|
+
this.events.runUserCallback(
|
|
2054
|
+
() => handler.handle(message, signal),
|
|
2055
|
+
"Typed message handler failed.",
|
|
2056
|
+
signal
|
|
2057
|
+
);
|
|
1862
2058
|
}
|
|
1863
2059
|
}
|
|
1864
2060
|
} finally {
|
|
1865
2061
|
this.draining = false;
|
|
1866
2062
|
}
|
|
1867
2063
|
}
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
2064
|
+
/**
|
|
2065
|
+
* True while a registered callback runs from {@link pump}: the execution
|
|
2066
|
+
* context spec stream-connector 32 §7 calls "inside a handler".
|
|
2067
|
+
*/
|
|
2068
|
+
get dispatching() {
|
|
2069
|
+
return this.draining;
|
|
1873
2070
|
}
|
|
1874
|
-
|
|
2071
|
+
get pendingCallbacks() {
|
|
2072
|
+
let count = 0;
|
|
1875
2073
|
for (let index = this.queueHead; index < this.queue.length; index += 1) {
|
|
1876
2074
|
const queued = this.queue[index];
|
|
1877
|
-
if (queued
|
|
2075
|
+
if (queued?.kind === "callback" && queued.callbacks() > 0) count += 1;
|
|
2076
|
+
if (queued?.kind === "message" && this.receiversOf(queued.message).length > 0) count += 1;
|
|
2077
|
+
}
|
|
2078
|
+
return count;
|
|
2079
|
+
}
|
|
2080
|
+
offerQueued(name, registration) {
|
|
2081
|
+
for (let index = this.queueHead; index < this.queue.length; index += 1) {
|
|
2082
|
+
const queued = this.queue[index];
|
|
2083
|
+
if (queued === void 0 || queued.kind !== "message" || queued.message.name !== name) {
|
|
2084
|
+
continue;
|
|
2085
|
+
}
|
|
2086
|
+
if (!registration.consume(queued.message)) {
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
this.removeAt(index);
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
removeAt(index) {
|
|
2094
|
+
this.queue[index] = void 0;
|
|
2095
|
+
this.queuedCount -= 1;
|
|
2096
|
+
this.advanceHead();
|
|
2097
|
+
this.compactQueue();
|
|
2098
|
+
}
|
|
2099
|
+
findDeliverableIndex() {
|
|
2100
|
+
while (this.deliverable.length > 0) {
|
|
2101
|
+
const index = this.deliverable[0];
|
|
2102
|
+
const queued = this.queue[index];
|
|
2103
|
+
if (this.isDeliverable(queued)) {
|
|
1878
2104
|
return index;
|
|
1879
2105
|
}
|
|
2106
|
+
if (queued !== void 0) queued.indexed = false;
|
|
2107
|
+
this.removeDeliverable();
|
|
1880
2108
|
}
|
|
1881
2109
|
return -1;
|
|
1882
2110
|
}
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
2111
|
+
indexDeliverable(index) {
|
|
2112
|
+
const queued = this.queue[index];
|
|
2113
|
+
if (queued === void 0 || !this.isDeliverable(queued) || queued.indexed === true) return;
|
|
2114
|
+
queued.indexed = true;
|
|
2115
|
+
let position = this.deliverable.length;
|
|
2116
|
+
this.deliverable.push(index);
|
|
2117
|
+
while (position > 0) {
|
|
2118
|
+
const parent = Math.floor((position - 1) / 2);
|
|
2119
|
+
if (this.deliverable[parent] <= index) break;
|
|
2120
|
+
this.deliverable[position] = this.deliverable[parent];
|
|
2121
|
+
position = parent;
|
|
1887
2122
|
}
|
|
1888
|
-
|
|
2123
|
+
this.deliverable[position] = index;
|
|
2124
|
+
}
|
|
2125
|
+
isDeliverable(queued) {
|
|
2126
|
+
return queued?.kind === "callback" || queued?.kind === "message" && this.receiversOf(queued.message).length > 0;
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* Spec stream-connector 32 §7 and §10: the handlers that receive `message`
|
|
2130
|
+
* are the connector handlers for its name and the handle handlers of the
|
|
2131
|
+
* Actor it carries. A packet no handler receives stays in the queue for the
|
|
2132
|
+
* wait surfaces. This is the one place that decides it.
|
|
2133
|
+
*/
|
|
2134
|
+
receiversOf(message) {
|
|
2135
|
+
const set = this.handlers.get(message.name);
|
|
2136
|
+
if (set === void 0) return [];
|
|
2137
|
+
return Array.from(set).filter((registration) => receives(registration, message));
|
|
2138
|
+
}
|
|
2139
|
+
removeDeliverable() {
|
|
2140
|
+
if (this.deliverable.length === 0) return;
|
|
2141
|
+
const last = this.deliverable.pop();
|
|
2142
|
+
if (this.deliverable.length === 0) return;
|
|
2143
|
+
let position = 0;
|
|
2144
|
+
for (; ; ) {
|
|
2145
|
+
const left = position * 2 + 1;
|
|
2146
|
+
if (left >= this.deliverable.length) break;
|
|
2147
|
+
const right = left + 1;
|
|
2148
|
+
const child = right < this.deliverable.length && this.deliverable[right] < this.deliverable[left] ? right : left;
|
|
2149
|
+
if (this.deliverable[child] >= last) break;
|
|
2150
|
+
this.deliverable[position] = this.deliverable[child];
|
|
2151
|
+
position = child;
|
|
2152
|
+
}
|
|
2153
|
+
this.deliverable[position] = last;
|
|
1889
2154
|
}
|
|
1890
2155
|
advanceHead() {
|
|
1891
2156
|
while (this.queueHead < this.queue.length && this.queue[this.queueHead] === void 0) {
|
|
@@ -1896,22 +2161,99 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1896
2161
|
if (this.queuedCount === 0) {
|
|
1897
2162
|
this.queue.length = 0;
|
|
1898
2163
|
this.queueHead = 0;
|
|
2164
|
+
this.deliverable.length = 0;
|
|
1899
2165
|
return;
|
|
1900
2166
|
}
|
|
1901
2167
|
if (this.queueHead >= 1024 && this.queueHead * 2 >= this.queue.length) {
|
|
1902
2168
|
this.queue.splice(0, this.queueHead);
|
|
1903
2169
|
this.queueHead = 0;
|
|
2170
|
+
this.deliverable.length = 0;
|
|
2171
|
+
for (let index = 0; index < this.queue.length; index += 1) {
|
|
2172
|
+
const queued = this.queue[index];
|
|
2173
|
+
if (queued === void 0) continue;
|
|
2174
|
+
queued.indexed = false;
|
|
2175
|
+
this.indexDeliverable(index);
|
|
2176
|
+
}
|
|
1904
2177
|
}
|
|
1905
2178
|
}
|
|
1906
2179
|
};
|
|
1907
2180
|
|
|
1908
2181
|
// packages/stream-connector/src/Runtime/ZlinkStreamFrameSender.ts
|
|
2182
|
+
var ZlinkStreamConnectionWriteQueue = class {
|
|
2183
|
+
/**
|
|
2184
|
+
* @param writeFailed Ends the connection whose transport write failed (spec
|
|
2185
|
+
* stream-connector 32 §9: a transport write failure ends the connection as
|
|
2186
|
+
* `TransportError`).
|
|
2187
|
+
*/
|
|
2188
|
+
constructor(connection, writeFailed) {
|
|
2189
|
+
this.connection = connection;
|
|
2190
|
+
this.writeFailed = writeFailed;
|
|
2191
|
+
}
|
|
2192
|
+
queue = /* @__PURE__ */ new Set();
|
|
2193
|
+
active;
|
|
2194
|
+
enqueue(operation) {
|
|
2195
|
+
this.queue.add(operation);
|
|
2196
|
+
this.advance();
|
|
2197
|
+
}
|
|
2198
|
+
cancel(operation, error) {
|
|
2199
|
+
if (this.queue.has(operation) || this.active === operation) operation.fail(error);
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* Spec stream-connector 32 §7: when the connection ends, frames that have not
|
|
2203
|
+
* reached the transport are not written and the frame being written is not
|
|
2204
|
+
* waited for. The operations of both fail with `error`.
|
|
2205
|
+
*/
|
|
2206
|
+
failAll(error) {
|
|
2207
|
+
const operations = this.active === void 0 ? [...this.queue] : [this.active, ...this.queue];
|
|
2208
|
+
this.queue.clear();
|
|
2209
|
+
this.active = void 0;
|
|
2210
|
+
for (const operation of operations) operation.fail(error);
|
|
2211
|
+
}
|
|
2212
|
+
advance() {
|
|
2213
|
+
if (this.active !== void 0) return;
|
|
2214
|
+
const next = this.queue.values().next().value;
|
|
2215
|
+
if (next === void 0) return;
|
|
2216
|
+
this.queue.delete(next);
|
|
2217
|
+
this.active = next;
|
|
2218
|
+
let write;
|
|
2219
|
+
try {
|
|
2220
|
+
write = Promise.resolve(this.connection.write(next.frame));
|
|
2221
|
+
} catch (error) {
|
|
2222
|
+
write = Promise.reject(error);
|
|
2223
|
+
}
|
|
2224
|
+
void write.then(
|
|
2225
|
+
() => next.complete(),
|
|
2226
|
+
(cause) => this.failWrite(next, cause)
|
|
2227
|
+
).finally(() => {
|
|
2228
|
+
if (this.active !== next) return;
|
|
2229
|
+
this.active = void 0;
|
|
2230
|
+
this.advance();
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Spec stream-connector 32 §9: a transport write failure is `SendFailed` for
|
|
2235
|
+
* the operation of that write, and it ends the connection. The ending fails
|
|
2236
|
+
* every other operation of the connection with `Disconnected`. A write that
|
|
2237
|
+
* fails after the connection already ended belongs to that ending, which has
|
|
2238
|
+
* already failed its operation with `Disconnected`.
|
|
2239
|
+
*/
|
|
2240
|
+
failWrite(operation, cause) {
|
|
2241
|
+
if (this.active !== operation) return;
|
|
2242
|
+
const error = {
|
|
2243
|
+
code: "sendFailed" /* SendFailed */,
|
|
2244
|
+
message: `Frame write failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
2245
|
+
cause
|
|
2246
|
+
};
|
|
2247
|
+
operation.fail(new ZlinkStreamException(error));
|
|
2248
|
+
this.writeFailed(error);
|
|
2249
|
+
}
|
|
2250
|
+
};
|
|
1909
2251
|
var ZlinkStreamFrameSender = class {
|
|
1910
2252
|
constructor(protocol) {
|
|
1911
2253
|
this.protocol = protocol;
|
|
1912
2254
|
}
|
|
1913
|
-
|
|
1914
|
-
async send(connection, kind, name, payload, metadata, compress, requestSeq, signal, correlationId, actorSlot) {
|
|
2255
|
+
queues = /* @__PURE__ */ new Map();
|
|
2256
|
+
async send(connection, kind, name, payload, metadata, compress, requestSeq, signal, correlationId, actorSlot, expiry, onAccepted) {
|
|
1915
2257
|
throwIfAborted(signal);
|
|
1916
2258
|
await this.write(
|
|
1917
2259
|
connection,
|
|
@@ -1925,26 +2267,78 @@ var ZlinkStreamFrameSender = class {
|
|
|
1925
2267
|
correlationId,
|
|
1926
2268
|
actorSlot
|
|
1927
2269
|
),
|
|
1928
|
-
signal
|
|
2270
|
+
signal,
|
|
2271
|
+
expiry,
|
|
2272
|
+
onAccepted
|
|
1929
2273
|
);
|
|
1930
2274
|
}
|
|
1931
|
-
|
|
1932
|
-
|
|
2275
|
+
/**
|
|
2276
|
+
* Writes a control frame (heartbeat ping or pong). A control frame belongs to
|
|
2277
|
+
* no operation: its write failure ends the connection through the write
|
|
2278
|
+
* queue like any other write (spec stream-connector 32 §9), and a connection
|
|
2279
|
+
* that has already ended does not write it. The promise settles when the
|
|
2280
|
+
* write has ended either way.
|
|
2281
|
+
*/
|
|
2282
|
+
sendControl(connection, name) {
|
|
2283
|
+
const queue = this.queues.get(connection);
|
|
2284
|
+
if (queue === void 0) return Promise.resolve();
|
|
2285
|
+
const frame = this.protocol.encodeControl(name);
|
|
2286
|
+
return new Promise((resolve) => {
|
|
2287
|
+
queue.enqueue({ frame, complete: resolve, fail: () => resolve() });
|
|
2288
|
+
});
|
|
1933
2289
|
}
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
2290
|
+
/**
|
|
2291
|
+
* Gives a connection that has just been established its own write queue.
|
|
2292
|
+
* `writeFailed` ends that connection when one of its transport writes fails.
|
|
2293
|
+
*/
|
|
2294
|
+
open(connection, writeFailed) {
|
|
2295
|
+
this.queues.set(connection, new ZlinkStreamConnectionWriteQueue(connection, writeFailed));
|
|
1939
2296
|
}
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
2297
|
+
/**
|
|
2298
|
+
* Ends the write queue of a connection that has ended, by close and by
|
|
2299
|
+
* transport loss alike. Every operation still in it fails with `error`.
|
|
2300
|
+
*/
|
|
2301
|
+
failUnwritten(connection, error) {
|
|
2302
|
+
const queue = this.queues.get(connection);
|
|
2303
|
+
if (queue === void 0) return;
|
|
2304
|
+
this.queues.delete(connection);
|
|
2305
|
+
queue.failAll(error);
|
|
2306
|
+
}
|
|
2307
|
+
write(connection, frame, signal, expiry, onAccepted) {
|
|
2308
|
+
throwIfAborted(signal);
|
|
2309
|
+
const queue = this.queues.get(connection);
|
|
2310
|
+
if (queue === void 0) {
|
|
2311
|
+
throw connectorError("disconnected" /* Disconnected */, "Connector is not connected.");
|
|
1947
2312
|
}
|
|
2313
|
+
let operation;
|
|
2314
|
+
let settled = false;
|
|
2315
|
+
const promise = new Promise((resolve, reject) => {
|
|
2316
|
+
const cleanup = () => signal?.removeEventListener("abort", onAbort);
|
|
2317
|
+
const onAbort = () => queue.cancel(
|
|
2318
|
+
operation,
|
|
2319
|
+
connectorError("disconnected" /* Disconnected */, "Operation canceled.")
|
|
2320
|
+
);
|
|
2321
|
+
operation = {
|
|
2322
|
+
frame,
|
|
2323
|
+
complete: () => {
|
|
2324
|
+
if (settled) return;
|
|
2325
|
+
settled = true;
|
|
2326
|
+
cleanup();
|
|
2327
|
+
resolve();
|
|
2328
|
+
},
|
|
2329
|
+
fail: (error) => {
|
|
2330
|
+
if (settled) return;
|
|
2331
|
+
settled = true;
|
|
2332
|
+
cleanup();
|
|
2333
|
+
reject(error);
|
|
2334
|
+
}
|
|
2335
|
+
};
|
|
2336
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2337
|
+
onAccepted?.();
|
|
2338
|
+
expiry?.then(void 0, (error) => queue.cancel(operation, error));
|
|
2339
|
+
queue.enqueue(operation);
|
|
2340
|
+
});
|
|
2341
|
+
return promise;
|
|
1948
2342
|
}
|
|
1949
2343
|
};
|
|
1950
2344
|
|
|
@@ -1970,177 +2364,16 @@ function decodeSessionClosing(payload) {
|
|
|
1970
2364
|
return { closeReason, diagnostic };
|
|
1971
2365
|
}
|
|
1972
2366
|
|
|
1973
|
-
// packages/stream-connector/src/Runtime/
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
var
|
|
1978
|
-
constructor(
|
|
1979
|
-
|
|
1980
|
-
this.
|
|
1981
|
-
this.slot = slot;
|
|
1982
|
-
}
|
|
1983
|
-
bound = true;
|
|
1984
|
-
get isBound() {
|
|
1985
|
-
return this.bound;
|
|
1986
|
-
}
|
|
1987
|
-
send(payload, messageType) {
|
|
1988
|
-
this.ensureBound();
|
|
1989
|
-
return this.connector.sendForActor(this, payload, messageType);
|
|
1990
|
-
}
|
|
1991
|
-
request(payload, messageType) {
|
|
1992
|
-
this.ensureBound();
|
|
1993
|
-
return this.connector.requestForActor(this, payload, messageType);
|
|
1994
|
-
}
|
|
1995
|
-
on(nameOrType, handler, messageType) {
|
|
1996
|
-
return this.connector.onActorMessage(this, nameOrType, handler, messageType);
|
|
1997
|
-
}
|
|
1998
|
-
ensureBound() {
|
|
1999
|
-
if (!this.bound) {
|
|
2000
|
-
throw connectorError(
|
|
2001
|
-
"validationFailed" /* ValidationFailed */,
|
|
2002
|
-
`Actor '${this.actorId}' is no longer bound.`
|
|
2003
|
-
);
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
close() {
|
|
2007
|
-
this.bound = false;
|
|
2008
|
-
}
|
|
2009
|
-
};
|
|
2010
|
-
var ZlinkStreamActors = class {
|
|
2011
|
-
constructor(connector, receivedMessages, events) {
|
|
2012
|
-
this.connector = connector;
|
|
2013
|
-
this.receivedMessages = receivedMessages;
|
|
2014
|
-
this.events = events;
|
|
2015
|
-
}
|
|
2016
|
-
bySlot = /* @__PURE__ */ new Map();
|
|
2017
|
-
byId = /* @__PURE__ */ new Map();
|
|
2018
|
-
issued = [];
|
|
2019
|
-
boundHandlers = /* @__PURE__ */ new Set();
|
|
2020
|
-
unboundHandlers = /* @__PURE__ */ new Set();
|
|
2021
|
-
get snapshot() {
|
|
2022
|
-
return Object.freeze(Array.from(this.bySlot.values()));
|
|
2023
|
-
}
|
|
2024
|
-
find(actorId) {
|
|
2025
|
-
return this.byId.get(actorId);
|
|
2026
|
-
}
|
|
2027
|
-
onBound(handler) {
|
|
2028
|
-
this.boundHandlers.add(handler);
|
|
2029
|
-
return subscription(() => this.boundHandlers.delete(handler));
|
|
2030
|
-
}
|
|
2031
|
-
onUnbound(handler) {
|
|
2032
|
-
this.unboundHandlers.add(handler);
|
|
2033
|
-
return subscription(() => this.unboundHandlers.delete(handler));
|
|
2034
|
-
}
|
|
2035
|
-
processControl(name, payload, signal) {
|
|
2036
|
-
if (name === ACTOR_BOUND) {
|
|
2037
|
-
this.bind(payload, signal);
|
|
2038
|
-
return true;
|
|
2039
|
-
}
|
|
2040
|
-
if (name === ACTOR_UNBOUND) {
|
|
2041
|
-
this.unbind(payload, signal);
|
|
2042
|
-
return true;
|
|
2043
|
-
}
|
|
2044
|
-
return false;
|
|
2045
|
-
}
|
|
2046
|
-
resolve(slot) {
|
|
2047
|
-
const actor = this.bySlot.get(slot);
|
|
2048
|
-
if (actor === void 0) {
|
|
2049
|
-
throw connectorError(
|
|
2050
|
-
"frameDecodeFailed" /* FrameDecodeFailed */,
|
|
2051
|
-
`Actor slot '${slot}' is not bound.`
|
|
2052
|
-
);
|
|
2053
|
-
}
|
|
2054
|
-
return actor;
|
|
2055
|
-
}
|
|
2056
|
-
closeAll(signal) {
|
|
2057
|
-
for (const actor of this.issued) {
|
|
2058
|
-
if (!actor.isBound) continue;
|
|
2059
|
-
this.bySlot.delete(actor.slot);
|
|
2060
|
-
this.byId.delete(actor.actorId);
|
|
2061
|
-
actor.close();
|
|
2062
|
-
this.queue(this.unboundHandlers, actor, signal);
|
|
2063
|
-
}
|
|
2064
|
-
this.issued.length = 0;
|
|
2065
|
-
}
|
|
2066
|
-
bind(payload, signal) {
|
|
2067
|
-
if (payload.length < 5 || payload[0] !== 1) {
|
|
2068
|
-
throw invalidControl("Actor bound payload is invalid.");
|
|
2069
|
-
}
|
|
2070
|
-
const slot = payload[1] << 8 | payload[2];
|
|
2071
|
-
const idLength = payload[3];
|
|
2072
|
-
if (slot === 0 || idLength === 0 || payload.length !== 4 + idLength) {
|
|
2073
|
-
throw invalidControl("Actor bound payload is invalid.");
|
|
2074
|
-
}
|
|
2075
|
-
let actorId;
|
|
2076
|
-
try {
|
|
2077
|
-
actorId = new TextDecoder("utf-8", { fatal: true }).decode(payload.subarray(4));
|
|
2078
|
-
} catch (cause) {
|
|
2079
|
-
throw connectorError(
|
|
2080
|
-
"frameDecodeFailed" /* FrameDecodeFailed */,
|
|
2081
|
-
"Actor id is not valid UTF-8.",
|
|
2082
|
-
cause
|
|
2083
|
-
);
|
|
2084
|
-
}
|
|
2085
|
-
if (actorId.length === 0 || this.bySlot.has(slot) || this.byId.has(actorId)) {
|
|
2086
|
-
throw invalidControl("Actor bound identity is already in use.");
|
|
2087
|
-
}
|
|
2088
|
-
const actor = new DefaultZlinkStreamActor(this.connector, actorId, slot);
|
|
2089
|
-
this.bySlot.set(slot, actor);
|
|
2090
|
-
this.byId.set(actorId, actor);
|
|
2091
|
-
this.issued.push(actor);
|
|
2092
|
-
this.queue(this.boundHandlers, actor, signal);
|
|
2093
|
-
}
|
|
2094
|
-
unbind(payload, signal) {
|
|
2095
|
-
if (payload.length !== 3 || payload[0] !== 1) {
|
|
2096
|
-
throw invalidControl("Actor unbound payload is invalid.");
|
|
2097
|
-
}
|
|
2098
|
-
const slot = payload[1] << 8 | payload[2];
|
|
2099
|
-
const actor = this.bySlot.get(slot);
|
|
2100
|
-
if (slot === 0 || actor === void 0) {
|
|
2101
|
-
throw invalidControl(`Actor slot '${slot}' is not bound.`);
|
|
2102
|
-
}
|
|
2103
|
-
this.bySlot.delete(slot);
|
|
2104
|
-
this.byId.delete(actor.actorId);
|
|
2105
|
-
actor.close();
|
|
2106
|
-
this.queue(this.unboundHandlers, actor, signal);
|
|
2107
|
-
}
|
|
2108
|
-
queue(handlers, actor, signal) {
|
|
2109
|
-
this.receivedMessages.enqueueCallback(() => {
|
|
2110
|
-
for (const handler of Array.from(handlers)) {
|
|
2111
|
-
this.invoke(handler, actor, signal);
|
|
2112
|
-
}
|
|
2113
|
-
});
|
|
2114
|
-
}
|
|
2115
|
-
invoke(handler, actor, signal) {
|
|
2116
|
-
try {
|
|
2117
|
-
Promise.resolve(handler(actor, signal)).catch((cause) => {
|
|
2118
|
-
void this.events.publishError(
|
|
2119
|
-
{
|
|
2120
|
-
code: "userCallbackFailed" /* UserCallbackFailed */,
|
|
2121
|
-
message: "Actor lifecycle handler failed.",
|
|
2122
|
-
cause
|
|
2123
|
-
},
|
|
2124
|
-
signal
|
|
2125
|
-
);
|
|
2126
|
-
});
|
|
2127
|
-
} catch (cause) {
|
|
2128
|
-
void this.events.publishError(
|
|
2129
|
-
{
|
|
2130
|
-
code: "userCallbackFailed" /* UserCallbackFailed */,
|
|
2131
|
-
message: "Actor lifecycle handler failed.",
|
|
2132
|
-
cause
|
|
2133
|
-
},
|
|
2134
|
-
signal
|
|
2135
|
-
);
|
|
2136
|
-
}
|
|
2367
|
+
// packages/stream-connector/src/Runtime/ZlinkStreamReceiveDispatcher.ts
|
|
2368
|
+
function closeReasonFor(error) {
|
|
2369
|
+
return error.code === "frameDecodeFailed" /* FrameDecodeFailed */ || error.code === "frameTooLarge" /* FrameTooLarge */ ? "ProtocolError" : "TransportError";
|
|
2370
|
+
}
|
|
2371
|
+
var ZlinkStreamConnectionEnd = class extends Error {
|
|
2372
|
+
constructor(error) {
|
|
2373
|
+
super(error.message);
|
|
2374
|
+
this.error = error;
|
|
2137
2375
|
}
|
|
2138
2376
|
};
|
|
2139
|
-
function invalidControl(message) {
|
|
2140
|
-
return connectorError("frameDecodeFailed" /* FrameDecodeFailed */, message);
|
|
2141
|
-
}
|
|
2142
|
-
|
|
2143
|
-
// packages/stream-connector/src/Runtime/ZlinkStreamReceiveDispatcher.ts
|
|
2144
2377
|
var ZlinkStreamReceiveDispatcher = class {
|
|
2145
2378
|
constructor(protocol, pendingRequests, receivedMessages, frameSender, events, actors, serverClosing) {
|
|
2146
2379
|
this.protocol = protocol;
|
|
@@ -2160,15 +2393,20 @@ var ZlinkStreamReceiveDispatcher = class {
|
|
|
2160
2393
|
* the recheck the rest of a dead connection's batch lands on the new
|
|
2161
2394
|
* connection's state, and the §10 arrival counts, which start at 0 when a
|
|
2162
2395
|
* connection is established, take the stale frames on top.
|
|
2163
|
-
* @param connectionForSend Resolves the connection a reply belongs on at the
|
|
2164
|
-
* moment it is written. The captured `connection` is the one the batch was
|
|
2165
|
-
* read from, which a reconnect may already have replaced.
|
|
2166
2396
|
*/
|
|
2167
|
-
async readAndDispatch(connection, signal, isCurrent
|
|
2397
|
+
async readAndDispatch(connection, signal, isCurrent) {
|
|
2168
2398
|
if (connection?.read === void 0) {
|
|
2169
2399
|
return { available: false, inbound: false };
|
|
2170
2400
|
}
|
|
2171
|
-
|
|
2401
|
+
let frameBytes;
|
|
2402
|
+
try {
|
|
2403
|
+
frameBytes = await connection.read(signal);
|
|
2404
|
+
} catch (cause) {
|
|
2405
|
+
if (signal?.aborted === true) throw cause;
|
|
2406
|
+
throw new ZlinkStreamConnectionEnd(
|
|
2407
|
+
toStreamError(cause, "disconnected" /* Disconnected */, "Transport read failed.")
|
|
2408
|
+
);
|
|
2409
|
+
}
|
|
2172
2410
|
if (isCurrent !== void 0 && !isCurrent()) {
|
|
2173
2411
|
return { available: false, inbound: false };
|
|
2174
2412
|
}
|
|
@@ -2179,87 +2417,102 @@ var ZlinkStreamReceiveDispatcher = class {
|
|
|
2179
2417
|
try {
|
|
2180
2418
|
frames = this.protocol.decodeFrames(frameBytes);
|
|
2181
2419
|
} catch (cause) {
|
|
2182
|
-
throw
|
|
2420
|
+
throw new ZlinkStreamConnectionEnd(
|
|
2421
|
+
toStreamError(cause, "frameDecodeFailed" /* FrameDecodeFailed */, "Frame decode failed.")
|
|
2422
|
+
);
|
|
2183
2423
|
}
|
|
2184
2424
|
for (const frame of frames) {
|
|
2185
2425
|
if (isCurrent !== void 0 && !isCurrent()) {
|
|
2186
2426
|
break;
|
|
2187
2427
|
}
|
|
2188
2428
|
try {
|
|
2189
|
-
await this.dispatch(connection, frame.header, frame.payload, signal
|
|
2429
|
+
await this.dispatch(connection, frame.header, frame.payload, signal);
|
|
2190
2430
|
} catch (cause) {
|
|
2191
|
-
if (
|
|
2192
|
-
throw cause;
|
|
2193
|
-
}
|
|
2431
|
+
if (cause instanceof ZlinkStreamConnectionEnd) throw cause;
|
|
2194
2432
|
const error = toStreamError(
|
|
2195
2433
|
cause,
|
|
2196
2434
|
"frameDecodeFailed" /* FrameDecodeFailed */,
|
|
2197
2435
|
"Frame dispatch failed."
|
|
2198
2436
|
);
|
|
2199
2437
|
if (error.code === "frameDecodeFailed" /* FrameDecodeFailed */) {
|
|
2200
|
-
throw
|
|
2438
|
+
throw new ZlinkStreamConnectionEnd(error);
|
|
2201
2439
|
}
|
|
2202
|
-
|
|
2440
|
+
this.events.publishError(error, signal);
|
|
2203
2441
|
}
|
|
2204
2442
|
}
|
|
2205
2443
|
return { available: true, inbound: true };
|
|
2206
2444
|
}
|
|
2207
|
-
|
|
2445
|
+
/**
|
|
2446
|
+
* Spec stream-connector 32 §4.7 and §9: a decompressed payload over the
|
|
2447
|
+
* receive limit is `FrameTooLarge` and ends the connection as a protocol
|
|
2448
|
+
* error, like a wire payload over the limit. A payload that does not
|
|
2449
|
+
* decompress fails only its packet.
|
|
2450
|
+
*/
|
|
2451
|
+
decodePayload(header, payload) {
|
|
2452
|
+
try {
|
|
2453
|
+
return this.protocol.decodePayload(header, payload);
|
|
2454
|
+
} catch (cause) {
|
|
2455
|
+
const error = toStreamError(
|
|
2456
|
+
cause,
|
|
2457
|
+
"decompressionFailed" /* DecompressionFailed */,
|
|
2458
|
+
"Decompression failed."
|
|
2459
|
+
);
|
|
2460
|
+
if (error.code === "frameTooLarge" /* FrameTooLarge */) {
|
|
2461
|
+
throw new ZlinkStreamConnectionEnd(error);
|
|
2462
|
+
}
|
|
2463
|
+
throw cause;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
async dispatch(connection, header, payload, signal) {
|
|
2208
2467
|
const actor = header.actorSlot === void 0 ? void 0 : this.actors.resolve(header.actorSlot);
|
|
2209
2468
|
if (header.kind === 3 /* Response */ && header.requestSeq !== void 0) {
|
|
2210
2469
|
try {
|
|
2211
|
-
|
|
2470
|
+
this.pendingRequests.resolve(
|
|
2212
2471
|
header.requestSeq,
|
|
2213
|
-
{
|
|
2472
|
+
() => ({
|
|
2214
2473
|
codec: header.codec,
|
|
2215
|
-
payload: this.
|
|
2216
|
-
},
|
|
2474
|
+
payload: this.decodePayload(header, payload)
|
|
2475
|
+
}),
|
|
2217
2476
|
header.metadata
|
|
2218
|
-
)
|
|
2219
|
-
await this.events.publishError(
|
|
2220
|
-
{
|
|
2221
|
-
code: "frameDecodeFailed" /* FrameDecodeFailed */,
|
|
2222
|
-
message: `Response request sequence '${header.requestSeq}' has no pending request.`
|
|
2223
|
-
},
|
|
2224
|
-
signal
|
|
2225
|
-
);
|
|
2226
|
-
}
|
|
2477
|
+
);
|
|
2227
2478
|
} catch (cause) {
|
|
2479
|
+
if (cause instanceof ZlinkStreamConnectionEnd) throw cause;
|
|
2228
2480
|
const decodeError = toStreamError(
|
|
2229
2481
|
cause,
|
|
2230
2482
|
"decompressionFailed" /* DecompressionFailed */,
|
|
2231
2483
|
"Decompression failed."
|
|
2232
2484
|
);
|
|
2233
2485
|
if (!this.pendingRequests.reject(header.requestSeq, decodeError)) {
|
|
2234
|
-
|
|
2486
|
+
this.events.publishError(decodeError, signal);
|
|
2235
2487
|
}
|
|
2236
2488
|
}
|
|
2237
2489
|
return;
|
|
2238
2490
|
}
|
|
2239
2491
|
if (header.kind === 4 /* Error */ && header.requestSeq !== void 0) {
|
|
2240
2492
|
try {
|
|
2241
|
-
const remoteError = decodeRemoteError(this.
|
|
2493
|
+
const remoteError = decodeRemoteError(this.decodePayload(header, payload));
|
|
2242
2494
|
if (!this.pendingRequests.reject(header.requestSeq, remoteError)) {
|
|
2243
|
-
|
|
2495
|
+
this.events.publishError(remoteError, signal);
|
|
2244
2496
|
}
|
|
2245
2497
|
} catch (cause) {
|
|
2498
|
+
if (cause instanceof ZlinkStreamConnectionEnd) throw cause;
|
|
2246
2499
|
const decodeError = toStreamError(
|
|
2247
2500
|
cause,
|
|
2248
2501
|
"frameDecodeFailed" /* FrameDecodeFailed */,
|
|
2249
2502
|
"Remote error payload is invalid."
|
|
2250
2503
|
);
|
|
2251
2504
|
if (!this.pendingRequests.reject(header.requestSeq, decodeError)) {
|
|
2252
|
-
|
|
2505
|
+
this.events.publishError(decodeError, signal);
|
|
2253
2506
|
}
|
|
2254
2507
|
}
|
|
2255
2508
|
return;
|
|
2256
2509
|
}
|
|
2257
2510
|
if (header.kind === 4 /* Error */) {
|
|
2258
|
-
|
|
2511
|
+
this.events.publishError(decodeRemoteError(this.decodePayload(header, payload)), signal);
|
|
2259
2512
|
return;
|
|
2260
2513
|
}
|
|
2261
2514
|
if (header.kind === 5 /* Control */) {
|
|
2262
|
-
await this.dispatchControl(connection, header, payload, signal
|
|
2515
|
+
await this.dispatchControl(connection, header, payload, signal);
|
|
2263
2516
|
return;
|
|
2264
2517
|
}
|
|
2265
2518
|
if (header.kind === 1 /* Send */) {
|
|
@@ -2267,7 +2520,7 @@ var ZlinkStreamReceiveDispatcher = class {
|
|
|
2267
2520
|
{
|
|
2268
2521
|
name: header.name,
|
|
2269
2522
|
metadata: header.metadata,
|
|
2270
|
-
payload: { codec: header.codec, payload: this.
|
|
2523
|
+
payload: { codec: header.codec, payload: this.decodePayload(header, payload) },
|
|
2271
2524
|
actorId: actor?.actorId,
|
|
2272
2525
|
[zlinkStreamActorBinding]: actor
|
|
2273
2526
|
},
|
|
@@ -2275,7 +2528,7 @@ var ZlinkStreamReceiveDispatcher = class {
|
|
|
2275
2528
|
);
|
|
2276
2529
|
}
|
|
2277
2530
|
}
|
|
2278
|
-
async dispatchControl(connection, header, payload, signal
|
|
2531
|
+
async dispatchControl(connection, header, payload, signal) {
|
|
2279
2532
|
if (this.actors.processControl(header.name, payload, signal)) {
|
|
2280
2533
|
return;
|
|
2281
2534
|
}
|
|
@@ -2291,18 +2544,7 @@ var ZlinkStreamReceiveDispatcher = class {
|
|
|
2291
2544
|
);
|
|
2292
2545
|
}
|
|
2293
2546
|
if (header.name === ZLINK_STREAM_HEARTBEAT_PING) {
|
|
2294
|
-
|
|
2295
|
-
await this.frameSender.sendControl(
|
|
2296
|
-
connectionForSend?.() ?? connection,
|
|
2297
|
-
ZLINK_STREAM_HEARTBEAT_PONG,
|
|
2298
|
-
signal
|
|
2299
|
-
);
|
|
2300
|
-
} catch (cause) {
|
|
2301
|
-
throw connectorError(
|
|
2302
|
-
"sendFailed" /* SendFailed */,
|
|
2303
|
-
cause instanceof Error ? cause.message : "Heartbeat pong send failed."
|
|
2304
|
-
);
|
|
2305
|
-
}
|
|
2547
|
+
await this.frameSender.sendControl(connection, ZLINK_STREAM_HEARTBEAT_PONG);
|
|
2306
2548
|
return;
|
|
2307
2549
|
}
|
|
2308
2550
|
if (header.name !== ZLINK_STREAM_HEARTBEAT_PONG) {
|
|
@@ -2310,8 +2552,7 @@ var ZlinkStreamReceiveDispatcher = class {
|
|
|
2310
2552
|
}
|
|
2311
2553
|
}
|
|
2312
2554
|
};
|
|
2313
|
-
function decodeRemoteError(
|
|
2314
|
-
const decodedPayload = protocol.decodePayload(header, payload);
|
|
2555
|
+
function decodeRemoteError(decodedPayload) {
|
|
2315
2556
|
let decoded;
|
|
2316
2557
|
try {
|
|
2317
2558
|
decoded = JSON.parse(utf8Decode2(decodedPayload));
|
|
@@ -2358,6 +2599,7 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2358
2599
|
lastInboundAt = 0;
|
|
2359
2600
|
closeTask;
|
|
2360
2601
|
connectTask;
|
|
2602
|
+
connectAbort;
|
|
2361
2603
|
disconnectTask;
|
|
2362
2604
|
closeRequested = false;
|
|
2363
2605
|
disconnectedPublished = false;
|
|
@@ -2389,10 +2631,20 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2389
2631
|
});
|
|
2390
2632
|
return await this.connectTask;
|
|
2391
2633
|
}
|
|
2634
|
+
/**
|
|
2635
|
+
* Spec stream-connector 32 §7: close does not wait for a connect attempt or a
|
|
2636
|
+
* reconnect delay to run its course. The attempts listen to one controller
|
|
2637
|
+
* the lifecycle owns; close aborts it, and the caller's signal is forwarded
|
|
2638
|
+
* to it.
|
|
2639
|
+
*/
|
|
2392
2640
|
async connectOnce(signal) {
|
|
2393
|
-
|
|
2641
|
+
const attempts = new AbortController();
|
|
2642
|
+
const forwardAbort = () => attempts.abort();
|
|
2643
|
+
signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
2644
|
+
this.connectAbort = attempts;
|
|
2645
|
+
this.setState("connecting" /* Connecting */, void 0, signal);
|
|
2394
2646
|
try {
|
|
2395
|
-
const connection = await this.connectWithReconnect(signal);
|
|
2647
|
+
const connection = await this.connectWithReconnect(attempts.signal);
|
|
2396
2648
|
if (this.closeRequested) {
|
|
2397
2649
|
try {
|
|
2398
2650
|
await connection.close(signal);
|
|
@@ -2407,41 +2659,61 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2407
2659
|
}
|
|
2408
2660
|
this.currentConnection = connection;
|
|
2409
2661
|
this.connectionGeneration += 1;
|
|
2662
|
+
const generation = this.connectionGeneration;
|
|
2663
|
+
this.frameSender.open(connection, (error) => {
|
|
2664
|
+
void this.disconnectForTransportFailure(error, connection, generation);
|
|
2665
|
+
});
|
|
2410
2666
|
this.disconnectedPublished = false;
|
|
2411
2667
|
this.receivedMessages.resetForNewConnection();
|
|
2412
2668
|
this.lastInboundAt = Date.now();
|
|
2413
|
-
|
|
2669
|
+
this.setState("connected" /* Connected */, void 0, signal);
|
|
2414
2670
|
this.startHeartbeat();
|
|
2415
2671
|
this.startReceiveLoop();
|
|
2416
2672
|
} catch (cause) {
|
|
2417
2673
|
if (this.closeRequested) {
|
|
2418
2674
|
const message = cause instanceof Error ? cause.message : "Connector closed while connecting.";
|
|
2419
|
-
|
|
2420
|
-
|
|
2675
|
+
throw connectorError("disconnected" /* Disconnected */, message, cause);
|
|
2676
|
+
}
|
|
2677
|
+
if (signal?.aborted === true) {
|
|
2678
|
+
this.setState("disconnected" /* Disconnected */, void 0, signal);
|
|
2679
|
+
throwIfAborted(signal);
|
|
2421
2680
|
}
|
|
2422
2681
|
const error = toStreamError(cause, "connectTimeout" /* ConnectTimeout */, "Connect failed.");
|
|
2423
|
-
this.closeReasonValue
|
|
2424
|
-
|
|
2425
|
-
this.
|
|
2682
|
+
this.closeReasonValue = "TransportError";
|
|
2683
|
+
this.setState("disconnected" /* Disconnected */, error, signal);
|
|
2684
|
+
this.disconnectedPublished = false;
|
|
2685
|
+
this.publishDisconnected(signal);
|
|
2426
2686
|
throw new ZlinkStreamException(error);
|
|
2687
|
+
} finally {
|
|
2688
|
+
signal?.removeEventListener("abort", forwardAbort);
|
|
2689
|
+
this.connectAbort = void 0;
|
|
2427
2690
|
}
|
|
2428
2691
|
}
|
|
2692
|
+
/**
|
|
2693
|
+
* Spec stream-connector 32 §7: a `close` called outside the registered
|
|
2694
|
+
* handlers and callbacks returns once the close work has ended. One called
|
|
2695
|
+
* from inside them returns right after starting it; the result goes to the
|
|
2696
|
+
* outside caller, so a callback never waits for the close of the path that
|
|
2697
|
+
* is running it.
|
|
2698
|
+
*/
|
|
2429
2699
|
async close(signal) {
|
|
2430
|
-
this.closeReasonValue
|
|
2431
|
-
|
|
2432
|
-
if (this.closeTask !== void 0) {
|
|
2433
|
-
return await this.closeTask;
|
|
2700
|
+
if (this.currentConnection !== void 0 || this.closeReasonValue === void 0) {
|
|
2701
|
+
this.closeReasonValue = "ClientClose";
|
|
2434
2702
|
}
|
|
2435
|
-
|
|
2703
|
+
this.closeRequested = true;
|
|
2704
|
+
if (this.closeTask === void 0 && this.currentState === "closed" /* Closed */) {
|
|
2436
2705
|
return;
|
|
2437
2706
|
}
|
|
2438
|
-
this.closeTask
|
|
2707
|
+
const closeTask = this.closeTask ??= this.closeOnce(signal).finally(() => {
|
|
2439
2708
|
this.closeTask = void 0;
|
|
2440
2709
|
});
|
|
2441
|
-
|
|
2710
|
+
if (this.receivedMessages.dispatching) {
|
|
2711
|
+
void closeTask.catch(() => void 0);
|
|
2712
|
+
return;
|
|
2713
|
+
}
|
|
2714
|
+
return await closeTask;
|
|
2442
2715
|
}
|
|
2443
2716
|
async serverClosing(reason) {
|
|
2444
|
-
this.closeReasonValue = reason;
|
|
2445
2717
|
const error = {
|
|
2446
2718
|
code: "disconnected" /* Disconnected */,
|
|
2447
2719
|
message: `Server closed the session: ${reason}.`
|
|
@@ -2449,39 +2721,29 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2449
2721
|
await this.disconnectForTransportFailure(
|
|
2450
2722
|
error,
|
|
2451
2723
|
this.currentConnection,
|
|
2452
|
-
this.connectionGeneration
|
|
2724
|
+
this.connectionGeneration,
|
|
2725
|
+
reason
|
|
2453
2726
|
);
|
|
2454
2727
|
}
|
|
2455
2728
|
async closeOnce(signal) {
|
|
2729
|
+
this.connectAbort?.abort();
|
|
2456
2730
|
await this.connectTask?.catch(() => void 0);
|
|
2457
2731
|
await this.disconnectTask?.catch(() => void 0);
|
|
2458
|
-
const connection = this.currentConnection;
|
|
2459
|
-
this.stopHeartbeat();
|
|
2460
|
-
this.stopReceiveLoop();
|
|
2461
|
-
this.currentConnection = void 0;
|
|
2462
2732
|
const errors = [];
|
|
2463
2733
|
if (this.lateConnectCleanupError !== void 0) {
|
|
2464
2734
|
errors.push(this.lateConnectCleanupError);
|
|
2465
2735
|
this.lateConnectCleanupError = void 0;
|
|
2466
2736
|
}
|
|
2467
2737
|
try {
|
|
2468
|
-
await this.
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
try {
|
|
2473
|
-
await connection?.close(signal);
|
|
2738
|
+
await this.tearDownConnection(
|
|
2739
|
+
{ code: "disconnected" /* Disconnected */, message: "Connector closed." },
|
|
2740
|
+
signal
|
|
2741
|
+
);
|
|
2474
2742
|
} catch (error) {
|
|
2475
2743
|
errors.push(error);
|
|
2476
2744
|
}
|
|
2477
|
-
this.
|
|
2478
|
-
|
|
2479
|
-
message: "Connector closed."
|
|
2480
|
-
});
|
|
2481
|
-
this.receivedMessages.connectionEnded();
|
|
2482
|
-
this.actors.closeAll(signal);
|
|
2483
|
-
this.queueState("closed" /* Closed */, void 0, signal);
|
|
2484
|
-
this.queueDisconnected(signal);
|
|
2745
|
+
this.setState("closed" /* Closed */, void 0, signal);
|
|
2746
|
+
this.publishDisconnected(signal);
|
|
2485
2747
|
if (errors.length === 1) throw errors[0];
|
|
2486
2748
|
if (errors.length > 1) throw new AggregateError(errors, "Stream connector close failed.");
|
|
2487
2749
|
}
|
|
@@ -2499,7 +2761,7 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2499
2761
|
if (this.options.dispatchMode !== "immediate" /* Immediate */) {
|
|
2500
2762
|
await this.settleReceiveLoop();
|
|
2501
2763
|
}
|
|
2502
|
-
|
|
2764
|
+
this.receivedMessages.pump();
|
|
2503
2765
|
}
|
|
2504
2766
|
connectionForSend() {
|
|
2505
2767
|
if (this.currentConnection === void 0 || this.currentState !== "connected" /* Connected */) {
|
|
@@ -2512,8 +2774,7 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2512
2774
|
const result = await this.receiveDispatcher.readAndDispatch(
|
|
2513
2775
|
connection,
|
|
2514
2776
|
signal,
|
|
2515
|
-
() => this.isCurrentConnection(connection, generation)
|
|
2516
|
-
() => this.connectionForSend()
|
|
2777
|
+
() => this.isCurrentConnection(connection, generation)
|
|
2517
2778
|
);
|
|
2518
2779
|
if (result.inbound && this.isCurrentConnection(connection, generation)) {
|
|
2519
2780
|
this.lastInboundAt = Date.now();
|
|
@@ -2522,7 +2783,10 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2522
2783
|
}
|
|
2523
2784
|
async connectWithReconnect(signal) {
|
|
2524
2785
|
let attempt = 0;
|
|
2525
|
-
let delayMs =
|
|
2786
|
+
let delayMs = Math.min(
|
|
2787
|
+
this.options.reconnect.initialDelayMs,
|
|
2788
|
+
this.options.reconnect.maxDelayMs
|
|
2789
|
+
);
|
|
2526
2790
|
let lastError;
|
|
2527
2791
|
const maxAttempts = this.options.reconnect.enabled ? this.options.reconnect.maxAttempts : 1;
|
|
2528
2792
|
const unlimited = maxAttempts === null;
|
|
@@ -2532,10 +2796,10 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2532
2796
|
return await this.options.transportFactory.connect(this.options, signal);
|
|
2533
2797
|
} catch (cause) {
|
|
2534
2798
|
lastError = toStreamError(cause, "connectTimeout" /* ConnectTimeout */, "Connect failed.");
|
|
2535
|
-
if (!this.options.reconnect.enabled || !unlimited && attempt >= maxAttempts) {
|
|
2799
|
+
if (this.closeRequested || !this.options.reconnect.enabled || !unlimited && attempt >= maxAttempts) {
|
|
2536
2800
|
break;
|
|
2537
2801
|
}
|
|
2538
|
-
|
|
2802
|
+
this.setState("reconnecting" /* Reconnecting */, lastError, signal);
|
|
2539
2803
|
await delay(randomizedDelay(delayMs), signal);
|
|
2540
2804
|
delayMs = Math.min(
|
|
2541
2805
|
this.options.reconnect.maxDelayMs,
|
|
@@ -2603,12 +2867,8 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2603
2867
|
}
|
|
2604
2868
|
} catch (cause) {
|
|
2605
2869
|
if (signal.aborted) return;
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
"frameDecodeFailed" /* FrameDecodeFailed */,
|
|
2609
|
-
"Receive loop failed."
|
|
2610
|
-
);
|
|
2611
|
-
await this.disconnectForTransportFailure(error, connection, generation);
|
|
2870
|
+
if (!(cause instanceof ZlinkStreamConnectionEnd)) throw cause;
|
|
2871
|
+
await this.disconnectForTransportFailure(cause.error, connection, generation);
|
|
2612
2872
|
} finally {
|
|
2613
2873
|
this.receiveLoopSleeping = false;
|
|
2614
2874
|
this.releaseReceiveLoopSettled();
|
|
@@ -2664,36 +2924,35 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2664
2924
|
return;
|
|
2665
2925
|
}
|
|
2666
2926
|
if (Date.now() - this.lastInboundAt > this.options.heartbeat.timeoutMs) {
|
|
2667
|
-
this.closeReasonValue = "HeartbeatTimeout";
|
|
2668
2927
|
const error = { code: "disconnected" /* Disconnected */, message: "Heartbeat timed out." };
|
|
2669
2928
|
await this.disconnectForTransportFailure(
|
|
2670
2929
|
error,
|
|
2671
2930
|
this.currentConnection,
|
|
2672
|
-
this.connectionGeneration
|
|
2931
|
+
this.connectionGeneration,
|
|
2932
|
+
"HeartbeatTimeout"
|
|
2673
2933
|
);
|
|
2674
2934
|
return;
|
|
2675
2935
|
}
|
|
2676
|
-
|
|
2677
|
-
const generation = this.connectionGeneration;
|
|
2678
|
-
try {
|
|
2679
|
-
await this.frameSender.sendControl(this.connectionForSend(), ZLINK_STREAM_HEARTBEAT_PING);
|
|
2680
|
-
} catch (cause) {
|
|
2681
|
-
const error = toStreamError(cause, "sendFailed" /* SendFailed */, "Heartbeat send failed.");
|
|
2682
|
-
await this.disconnectForTransportFailure(error, connection, generation);
|
|
2683
|
-
}
|
|
2936
|
+
await this.frameSender.sendControl(this.connectionForSend(), ZLINK_STREAM_HEARTBEAT_PING);
|
|
2684
2937
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2938
|
+
/**
|
|
2939
|
+
* @param reason Given only where the reason is not read from `error`: a
|
|
2940
|
+
* server `session-closing` and the heartbeat timeout. Every other ending
|
|
2941
|
+
* takes it from {@link closeReasonFor}.
|
|
2942
|
+
*/
|
|
2943
|
+
async disconnectForTransportFailure(error, origin, generation, reason = closeReasonFor(error)) {
|
|
2687
2944
|
if (this.closeRequested || this.currentState === "closed" /* Closed */) {
|
|
2688
2945
|
return;
|
|
2689
2946
|
}
|
|
2690
2947
|
if (origin !== void 0 && !this.isCurrentConnection(origin, generation)) {
|
|
2691
2948
|
return;
|
|
2692
2949
|
}
|
|
2950
|
+
this.closeReasonValue = reason;
|
|
2693
2951
|
if (this.disconnectTask !== void 0) {
|
|
2694
2952
|
return await this.disconnectTask;
|
|
2695
2953
|
}
|
|
2696
|
-
this.disconnectTask = this.tearDownConnection(error).
|
|
2954
|
+
this.disconnectTask = this.tearDownConnection(error).catch(() => {
|
|
2955
|
+
}).finally(() => {
|
|
2697
2956
|
this.disconnectTask = void 0;
|
|
2698
2957
|
});
|
|
2699
2958
|
await this.disconnectTask;
|
|
@@ -2702,19 +2961,30 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2702
2961
|
isCurrentConnection(connection, generation) {
|
|
2703
2962
|
return !this.closeRequested && this.currentConnection === connection && this.connectionGeneration === generation;
|
|
2704
2963
|
}
|
|
2705
|
-
/**
|
|
2706
|
-
|
|
2964
|
+
/**
|
|
2965
|
+
* Ends the current connection, for close and for transport loss alike. No
|
|
2966
|
+
* application callback runs from here. Spec stream-connector 32 §7 and §9:
|
|
2967
|
+
* every operation the ending connection fails (the frames it has not
|
|
2968
|
+
* written, the one it is writing and the pending requests) fails with
|
|
2969
|
+
* `Disconnected`, whatever ended it; the cause stays in the close reason.
|
|
2970
|
+
* The transport is closed without waiting for the frames.
|
|
2971
|
+
*/
|
|
2972
|
+
async tearDownConnection(error, signal) {
|
|
2707
2973
|
this.stopHeartbeat();
|
|
2708
2974
|
this.stopReceiveLoop();
|
|
2709
2975
|
const connection = this.currentConnection;
|
|
2710
2976
|
this.currentConnection = void 0;
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2977
|
+
const disconnected = { code: "disconnected" /* Disconnected */, message: error.message };
|
|
2978
|
+
if (connection !== void 0) {
|
|
2979
|
+
this.frameSender.failUnwritten(
|
|
2980
|
+
connection,
|
|
2981
|
+
connectorError(disconnected.code, disconnected.message)
|
|
2982
|
+
);
|
|
2717
2983
|
}
|
|
2984
|
+
this.pendingRequests.failAll(disconnected);
|
|
2985
|
+
this.receivedMessages.connectionEnded();
|
|
2986
|
+
this.actors.closeAll(signal);
|
|
2987
|
+
await connection?.close(signal);
|
|
2718
2988
|
}
|
|
2719
2989
|
/**
|
|
2720
2990
|
* Runs once the teardown promise has settled, so a handler reached from here
|
|
@@ -2726,8 +2996,8 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2726
2996
|
*/
|
|
2727
2997
|
async announceDisconnect(error) {
|
|
2728
2998
|
if (this.closeRequested) return;
|
|
2729
|
-
this.
|
|
2730
|
-
this.
|
|
2999
|
+
this.setState("disconnected" /* Disconnected */, error);
|
|
3000
|
+
this.publishDisconnected();
|
|
2731
3001
|
if (this.shouldReconnect()) {
|
|
2732
3002
|
queueMicrotask(() => {
|
|
2733
3003
|
void this.connect().catch(() => void 0);
|
|
@@ -2737,78 +3007,36 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2737
3007
|
shouldReconnect() {
|
|
2738
3008
|
return this.options.reconnect.enabled && !this.closeRequested;
|
|
2739
3009
|
}
|
|
2740
|
-
// Synchronous test-and-set, taken before any await, so the single disconnect
|
|
2741
|
-
// notification the spec promises is claimed by exactly one caller even when
|
|
2742
|
-
// the publishing itself is deferred.
|
|
2743
|
-
claimDisconnectedPublish() {
|
|
2744
|
-
if (this.disconnectedPublished) return false;
|
|
2745
|
-
this.disconnectedPublished = true;
|
|
2746
|
-
return true;
|
|
2747
|
-
}
|
|
2748
|
-
async publishDisconnectedOnce(signal) {
|
|
2749
|
-
if (!this.claimDisconnectedPublish()) return;
|
|
2750
|
-
await this.events.publishDisconnected(signal);
|
|
2751
|
-
}
|
|
2752
3010
|
/**
|
|
2753
|
-
* Spec stream-connector 32 §7:
|
|
2754
|
-
*
|
|
2755
|
-
*
|
|
2756
|
-
*
|
|
2757
|
-
*
|
|
2758
|
-
* Starting the publish without awaiting it still runs every handler right
|
|
2759
|
-
* here: an `async` function body runs synchronously up to its first `await`,
|
|
2760
|
-
* and `publishDisconnected` reaches each handler before that point. So the
|
|
2761
|
-
* handler has run by the time `close` returns, which is what the spec asks
|
|
2762
|
-
* for, while a handler that calls `close` no longer waits for the very
|
|
2763
|
-
* `closeTask` it is running inside. Java and C++ hand the handler to a queue
|
|
2764
|
-
* and return the same way.
|
|
2765
|
-
*
|
|
2766
|
-
* `claimDisconnectedPublish` is taken by the same call and before any await,
|
|
2767
|
-
* so deferring the completion never turns the one notification into two.
|
|
2768
|
-
*
|
|
2769
|
-
* Nothing awaits the promise, so a rejection would reach the process as an
|
|
2770
|
-
* unhandled rejection and kill it. Handler failures are already contained
|
|
2771
|
-
* where they were before this call stopped waiting — the `Promise.allSettled`
|
|
2772
|
-
* in `publishDisconnected` — and this `catch` covers what is left rather than
|
|
2773
|
-
* reporting the same failure a second time on the error surface.
|
|
3011
|
+
* Spec stream-connector 32 §6 and §7: one disconnect notification per
|
|
3012
|
+
* lifecycle event, handed to the dispatch queue. The connector does not wait
|
|
3013
|
+
* for the handler; `Manual` runs it when the application pumps dispatch.
|
|
3014
|
+
* The flag is tested and set synchronously, so concurrent callers for the
|
|
3015
|
+
* same event claim it once.
|
|
2774
3016
|
*/
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
if (!this.claimDisconnectedPublish()) return;
|
|
2780
|
-
this.receivedMessages.enqueueCallback(() => {
|
|
2781
|
-
void this.events.publishDisconnected(signal).catch(() => void 0);
|
|
2782
|
-
});
|
|
2783
|
-
}
|
|
2784
|
-
queueState(current, error, signal) {
|
|
2785
|
-
const change = this.transitionState(current, error);
|
|
2786
|
-
if (change === void 0) return;
|
|
2787
|
-
this.receivedMessages.enqueueCallback(() => {
|
|
2788
|
-
void this.publishStateChange(change, signal).catch(() => void 0);
|
|
2789
|
-
});
|
|
2790
|
-
}
|
|
2791
|
-
async setState(current, error, signal) {
|
|
2792
|
-
const change = this.transitionState(current, error);
|
|
2793
|
-
if (change === void 0) return;
|
|
2794
|
-
await this.publishStateChange(change, signal);
|
|
3017
|
+
publishDisconnected(signal) {
|
|
3018
|
+
if (this.disconnectedPublished) return;
|
|
3019
|
+
this.disconnectedPublished = true;
|
|
3020
|
+
this.events.publishDisconnected(signal);
|
|
2795
3021
|
}
|
|
2796
|
-
|
|
3022
|
+
setState(current, error, signal) {
|
|
2797
3023
|
const previous = this.currentState;
|
|
2798
3024
|
this.currentState = current;
|
|
2799
|
-
if (previous === current && error === void 0)
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
return { previous, current, error };
|
|
2803
|
-
}
|
|
2804
|
-
async publishStateChange(change, signal) {
|
|
2805
|
-
await this.events.publishStateChanged(change, signal);
|
|
2806
|
-
if (change.error !== void 0) await this.events.publishError(change.error, signal);
|
|
3025
|
+
if (previous === current && error === void 0) return;
|
|
3026
|
+
this.events.publishStateChanged({ previous, current, error }, signal);
|
|
3027
|
+
if (error !== void 0) this.events.publishError(error, signal);
|
|
2807
3028
|
}
|
|
2808
3029
|
};
|
|
2809
3030
|
|
|
2810
3031
|
// packages/stream-connector/src/Runtime/ZlinkStreamConnectorEvents.ts
|
|
2811
3032
|
var ZlinkStreamConnectorEvents = class {
|
|
3033
|
+
/**
|
|
3034
|
+
* @param enqueueCallback Queues a callback that calls the number of handlers
|
|
3035
|
+
* `callbacks` reports for the handlers registered at that moment.
|
|
3036
|
+
*/
|
|
3037
|
+
constructor(enqueueCallback) {
|
|
3038
|
+
this.enqueueCallback = enqueueCallback;
|
|
3039
|
+
}
|
|
2812
3040
|
errorHandlers = /* @__PURE__ */ new Set();
|
|
2813
3041
|
disconnectedHandlers = /* @__PURE__ */ new Set();
|
|
2814
3042
|
stateHandlers = /* @__PURE__ */ new Set();
|
|
@@ -2824,23 +3052,78 @@ var ZlinkStreamConnectorEvents = class {
|
|
|
2824
3052
|
this.stateHandlers.add(handler);
|
|
2825
3053
|
return subscription(() => this.stateHandlers.delete(handler));
|
|
2826
3054
|
}
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
3055
|
+
publishError(error, signal) {
|
|
3056
|
+
this.publish(this.errorHandlers, (handler) => handler(error, signal), signal);
|
|
3057
|
+
}
|
|
3058
|
+
publishDisconnected(signal) {
|
|
3059
|
+
this.publish(this.disconnectedHandlers, (handler) => handler(signal), signal);
|
|
3060
|
+
}
|
|
3061
|
+
publishStateChanged(change, signal) {
|
|
3062
|
+
this.publish(this.stateHandlers, (handler) => handler(change, signal), signal);
|
|
3063
|
+
}
|
|
3064
|
+
/**
|
|
3065
|
+
* Spec stream-connector 32 §7: the connector runs a registered callback and
|
|
3066
|
+
* does not wait for it to finish. A thrown or rejected failure reaches the
|
|
3067
|
+
* error handlers as `UserCallbackFailed`.
|
|
3068
|
+
*/
|
|
3069
|
+
runUserCallback(run, failureMessage, signal) {
|
|
3070
|
+
this.invoke(
|
|
3071
|
+
run,
|
|
3072
|
+
(cause) => this.publishError(
|
|
3073
|
+
{ code: "userCallbackFailed" /* UserCallbackFailed */, message: failureMessage, cause },
|
|
3074
|
+
signal
|
|
3075
|
+
)
|
|
2830
3076
|
);
|
|
2831
3077
|
}
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
3078
|
+
publish(handlers, invoke, signal) {
|
|
3079
|
+
this.enqueueCallback(
|
|
3080
|
+
() => {
|
|
3081
|
+
for (const handler of Array.from(handlers)) {
|
|
3082
|
+
const report = (cause) => this.reportFailure(
|
|
3083
|
+
cause,
|
|
3084
|
+
signal,
|
|
3085
|
+
handlers === this.errorHandlers ? handler : void 0
|
|
3086
|
+
);
|
|
3087
|
+
this.invoke(() => invoke(handler), report);
|
|
3088
|
+
}
|
|
3089
|
+
},
|
|
3090
|
+
() => handlers.size
|
|
2835
3091
|
);
|
|
2836
3092
|
}
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
3093
|
+
reportFailure(cause, signal, failedHandler) {
|
|
3094
|
+
const error = {
|
|
3095
|
+
code: "userCallbackFailed" /* UserCallbackFailed */,
|
|
3096
|
+
message: "Connector event handler failed.",
|
|
3097
|
+
cause
|
|
3098
|
+
};
|
|
3099
|
+
if (failedHandler === void 0) {
|
|
3100
|
+
this.publishError(error, signal);
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
this.reportToRemaining(error, signal, /* @__PURE__ */ new Set([failedHandler]));
|
|
3104
|
+
}
|
|
3105
|
+
reportToRemaining(error, signal, attempted) {
|
|
3106
|
+
const remaining = Array.from(this.errorHandlers).filter(
|
|
3107
|
+
(candidate) => !attempted.has(candidate)
|
|
3108
|
+
);
|
|
3109
|
+
if (remaining.length === 0) return;
|
|
3110
|
+
const nextAttempted = /* @__PURE__ */ new Set([...attempted, ...remaining]);
|
|
3111
|
+
this.enqueueCallback(
|
|
3112
|
+
() => {
|
|
3113
|
+
for (const handler of remaining) {
|
|
3114
|
+
const report = (cause) => this.reportToRemaining({ ...error, cause }, signal, nextAttempted);
|
|
3115
|
+
this.invoke(() => handler(error, signal), report);
|
|
3116
|
+
}
|
|
3117
|
+
},
|
|
3118
|
+
() => remaining.length
|
|
2840
3119
|
);
|
|
2841
3120
|
}
|
|
2842
|
-
|
|
2843
|
-
|
|
3121
|
+
invoke(run, report) {
|
|
3122
|
+
try {
|
|
3123
|
+
Promise.resolve(run()).catch(report);
|
|
3124
|
+
} catch (cause) {
|
|
3125
|
+
report(cause);
|
|
3126
|
+
}
|
|
2844
3127
|
}
|
|
2845
3128
|
};
|
|
2846
3129
|
|
|
@@ -2878,11 +3161,7 @@ var BrowserWebSocketConnection = class {
|
|
|
2878
3161
|
if (this.closed || this.socket.readyState !== 1) {
|
|
2879
3162
|
throw connectorError("disconnected" /* Disconnected */, "Remote stream is not connected.");
|
|
2880
3163
|
}
|
|
2881
|
-
|
|
2882
|
-
this.socket.send(frame);
|
|
2883
|
-
} catch (cause) {
|
|
2884
|
-
throw connectorError("sendFailed" /* SendFailed */, "Send failed.", cause);
|
|
2885
|
-
}
|
|
3164
|
+
this.socket.send(frame);
|
|
2886
3165
|
}
|
|
2887
3166
|
async read(signal) {
|
|
2888
3167
|
throwIfAborted(signal);
|
|
@@ -2900,18 +3179,19 @@ var BrowserWebSocketConnection = class {
|
|
|
2900
3179
|
await this.waitForMessage(signal);
|
|
2901
3180
|
}
|
|
2902
3181
|
}
|
|
3182
|
+
/**
|
|
3183
|
+
* Spec stream-connector 32 §7: closing does not wait for the peer to read or
|
|
3184
|
+
* answer. `WebSocket.close` sends the close frame after the data already
|
|
3185
|
+
* handed to `send`, so a frame whose write completed is not torn.
|
|
3186
|
+
*/
|
|
2903
3187
|
async close(signal) {
|
|
2904
3188
|
throwIfAborted(signal);
|
|
3189
|
+
this.removeListeners();
|
|
2905
3190
|
if (!this.closed) {
|
|
2906
3191
|
this.closed = true;
|
|
2907
3192
|
this.socket.close();
|
|
2908
|
-
this.wakeReader();
|
|
2909
|
-
}
|
|
2910
|
-
try {
|
|
2911
|
-
await waitForClose(this.socket, signal);
|
|
2912
|
-
} finally {
|
|
2913
|
-
this.removeListeners();
|
|
2914
3193
|
}
|
|
3194
|
+
this.wakeReader();
|
|
2915
3195
|
}
|
|
2916
3196
|
onMessage = (event) => {
|
|
2917
3197
|
try {
|
|
@@ -2989,21 +3269,6 @@ var BrowserWebSocketConnection = class {
|
|
|
2989
3269
|
return message;
|
|
2990
3270
|
}
|
|
2991
3271
|
};
|
|
2992
|
-
function waitForClose(socket, signal) {
|
|
2993
|
-
if (socket.readyState === 3) return Promise.resolve();
|
|
2994
|
-
return new Promise((resolve, reject) => {
|
|
2995
|
-
const onClose = () => finish();
|
|
2996
|
-
const onAbort = () => finish(connectorError("disconnected" /* Disconnected */, "Close canceled."));
|
|
2997
|
-
const finish = (error) => {
|
|
2998
|
-
socket.removeEventListener("close", onClose);
|
|
2999
|
-
signal?.removeEventListener("abort", onAbort);
|
|
3000
|
-
if (error === void 0) resolve();
|
|
3001
|
-
else reject(error);
|
|
3002
|
-
};
|
|
3003
|
-
socket.addEventListener("close", onClose, { once: true });
|
|
3004
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3005
|
-
});
|
|
3006
|
-
}
|
|
3007
3272
|
async function waitForOpen(socket, connectTimeoutMs, signal) {
|
|
3008
3273
|
throwIfAborted(signal);
|
|
3009
3274
|
await new Promise((resolve, reject) => {
|
|
@@ -3053,7 +3318,9 @@ var DefaultZlinkStreamConnector = class {
|
|
|
3053
3318
|
static heartbeatPongName = ZLINK_STREAM_HEARTBEAT_PONG;
|
|
3054
3319
|
receivedMessages;
|
|
3055
3320
|
lifecycle;
|
|
3056
|
-
events = new ZlinkStreamConnectorEvents(
|
|
3321
|
+
events = new ZlinkStreamConnectorEvents(
|
|
3322
|
+
(callback, callbacks) => this.receivedMessages.enqueueCallback(callback, callbacks)
|
|
3323
|
+
);
|
|
3057
3324
|
correlationCounter = 0n;
|
|
3058
3325
|
pendingRequests = new ZlinkStreamPendingRequests();
|
|
3059
3326
|
frameSender;
|
|
@@ -3100,7 +3367,7 @@ var DefaultZlinkStreamConnector = class {
|
|
|
3100
3367
|
return this.lifecycle.state;
|
|
3101
3368
|
}
|
|
3102
3369
|
get pendingDispatchCount() {
|
|
3103
|
-
return this.
|
|
3370
|
+
return this.receivedMessages.pendingCallbacks;
|
|
3104
3371
|
}
|
|
3105
3372
|
get actors() {
|
|
3106
3373
|
return this.boundActors.snapshot;
|
|
@@ -3157,7 +3424,12 @@ var DefaultZlinkStreamConnector = class {
|
|
|
3157
3424
|
}
|
|
3158
3425
|
request(payload, messageType) {
|
|
3159
3426
|
const encoded = this.encodePayload(payload, messageType);
|
|
3160
|
-
return new ZlinkStreamRequestBuilder(
|
|
3427
|
+
return new ZlinkStreamRequestBuilder(
|
|
3428
|
+
this,
|
|
3429
|
+
this.resolveNameOrDefault(encoded),
|
|
3430
|
+
encoded,
|
|
3431
|
+
(callback) => this.receivedMessages.enqueueCallback(callback)
|
|
3432
|
+
);
|
|
3161
3433
|
}
|
|
3162
3434
|
on(nameOrType, handler, messageType) {
|
|
3163
3435
|
const encodedHandler = (message, signal) => handler(
|
|
@@ -3190,29 +3462,25 @@ var DefaultZlinkStreamConnector = class {
|
|
|
3190
3462
|
this,
|
|
3191
3463
|
this.resolveNameOrDefault(encoded),
|
|
3192
3464
|
encoded,
|
|
3465
|
+
(callback) => this.receivedMessages.enqueueCallback(callback),
|
|
3193
3466
|
actor.slot,
|
|
3194
3467
|
() => actor.ensureBound()
|
|
3195
3468
|
);
|
|
3196
3469
|
}
|
|
3197
3470
|
onActorMessage(actor, nameOrType, handler, messageType) {
|
|
3198
|
-
const encodedHandler = (message, signal) =>
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
},
|
|
3212
|
-
signal
|
|
3213
|
-
);
|
|
3214
|
-
};
|
|
3215
|
-
return this.receivedMessages.on(this.observedName(nameOrType), encodedHandler);
|
|
3471
|
+
const encodedHandler = (message, signal) => handler(
|
|
3472
|
+
{
|
|
3473
|
+
name: message.name,
|
|
3474
|
+
metadata: message.metadata,
|
|
3475
|
+
payload: this.decodePayload(
|
|
3476
|
+
message.payload,
|
|
3477
|
+
messageType ?? (typeof nameOrType === "function" ? nameOrType : void 0)
|
|
3478
|
+
),
|
|
3479
|
+
actorId: message.actorId
|
|
3480
|
+
},
|
|
3481
|
+
signal
|
|
3482
|
+
);
|
|
3483
|
+
return this.receivedMessages.on(this.observedName(nameOrType), encodedHandler, actor);
|
|
3216
3484
|
}
|
|
3217
3485
|
waitFor(nameOrType) {
|
|
3218
3486
|
return new ZlinkStreamWaitBuilder(this, this.observedName(nameOrType));
|
|
@@ -3334,7 +3602,7 @@ var DefaultZlinkStreamConnector = class {
|
|
|
3334
3602
|
}
|
|
3335
3603
|
return payload;
|
|
3336
3604
|
}
|
|
3337
|
-
async sendEncoded(kind, name, payload, metadata, compress, requestSeq, signal, correlationId, actorSlot) {
|
|
3605
|
+
async sendEncoded(kind, name, payload, metadata, compress, requestSeq, signal, correlationId, actorSlot, expiry, onAccepted) {
|
|
3338
3606
|
await this.frameSender.send(
|
|
3339
3607
|
this.lifecycle.connectionForSend(),
|
|
3340
3608
|
kind,
|
|
@@ -3345,7 +3613,9 @@ var DefaultZlinkStreamConnector = class {
|
|
|
3345
3613
|
requestSeq,
|
|
3346
3614
|
signal,
|
|
3347
3615
|
correlationId,
|
|
3348
|
-
actorSlot
|
|
3616
|
+
actorSlot,
|
|
3617
|
+
expiry,
|
|
3618
|
+
onAccepted
|
|
3349
3619
|
);
|
|
3350
3620
|
}
|
|
3351
3621
|
/**
|
|
@@ -3368,37 +3638,48 @@ var DefaultZlinkStreamConnector = class {
|
|
|
3368
3638
|
}
|
|
3369
3639
|
};
|
|
3370
3640
|
let pending;
|
|
3641
|
+
let stopCancellation;
|
|
3371
3642
|
try {
|
|
3372
3643
|
throwIfAborted(signal);
|
|
3373
3644
|
for (const handler of Array.from(this.requestSendingHandlers)) {
|
|
3374
3645
|
try {
|
|
3375
3646
|
handler(sendingContext);
|
|
3376
3647
|
} catch (cause) {
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
);
|
|
3386
|
-
});
|
|
3648
|
+
this.events.publishError(
|
|
3649
|
+
{
|
|
3650
|
+
code: "userCallbackFailed" /* UserCallbackFailed */,
|
|
3651
|
+
message: "Request sending hook failed.",
|
|
3652
|
+
cause
|
|
3653
|
+
},
|
|
3654
|
+
signal
|
|
3655
|
+
);
|
|
3387
3656
|
}
|
|
3388
3657
|
}
|
|
3389
3658
|
pending = this.pendingRequests.create(name, timeoutMs);
|
|
3390
|
-
|
|
3659
|
+
const accepted = pending;
|
|
3660
|
+
const write = this.sendEncoded(
|
|
3391
3661
|
2 /* Request */,
|
|
3392
3662
|
name,
|
|
3393
3663
|
payload,
|
|
3394
3664
|
requestMetadata,
|
|
3395
3665
|
compress,
|
|
3396
|
-
|
|
3666
|
+
accepted.requestSeq,
|
|
3397
3667
|
signal,
|
|
3398
3668
|
this.nextCorrelationId(),
|
|
3399
|
-
actorSlot
|
|
3669
|
+
actorSlot,
|
|
3670
|
+
accepted.promise,
|
|
3671
|
+
accepted.startTimeout
|
|
3400
3672
|
);
|
|
3401
|
-
const
|
|
3673
|
+
const canceled = new Promise((_, reject) => {
|
|
3674
|
+
const onAbort = () => reject(connectorError("disconnected" /* Disconnected */, "Operation canceled."));
|
|
3675
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3676
|
+
stopCancellation = () => signal?.removeEventListener("abort", onAbort);
|
|
3677
|
+
});
|
|
3678
|
+
const reply = await Promise.race([
|
|
3679
|
+
write.then(() => accepted.promise),
|
|
3680
|
+
accepted.promise,
|
|
3681
|
+
canceled
|
|
3682
|
+
]);
|
|
3402
3683
|
this.publishReplyReceived(
|
|
3403
3684
|
{
|
|
3404
3685
|
requestPacketName: name,
|
|
@@ -3412,6 +3693,7 @@ var DefaultZlinkStreamConnector = class {
|
|
|
3412
3693
|
return reply.payload;
|
|
3413
3694
|
} catch (error) {
|
|
3414
3695
|
if (pending !== void 0) this.pendingRequests.cancel(pending.requestSeq);
|
|
3696
|
+
if (signal?.aborted === true) throw error;
|
|
3415
3697
|
this.publishReplyReceived(
|
|
3416
3698
|
{
|
|
3417
3699
|
requestPacketName: name,
|
|
@@ -3423,26 +3705,24 @@ var DefaultZlinkStreamConnector = class {
|
|
|
3423
3705
|
signal
|
|
3424
3706
|
);
|
|
3425
3707
|
throw error;
|
|
3708
|
+
} finally {
|
|
3709
|
+
stopCancellation?.();
|
|
3426
3710
|
}
|
|
3427
3711
|
}
|
|
3428
3712
|
publishReplyReceived(context, signal) {
|
|
3429
3713
|
if (this.replyReceivedHandlers.size === 0) return;
|
|
3430
|
-
this.receivedMessages.enqueueCallback(
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
toStreamError(
|
|
3437
|
-
cause,
|
|
3438
|
-
"userCallbackFailed" /* UserCallbackFailed */,
|
|
3439
|
-
"Reply received hook failed."
|
|
3440
|
-
),
|
|
3714
|
+
this.receivedMessages.enqueueCallback(
|
|
3715
|
+
() => {
|
|
3716
|
+
for (const handler of Array.from(this.replyReceivedHandlers)) {
|
|
3717
|
+
this.events.runUserCallback(
|
|
3718
|
+
() => handler(context, signal),
|
|
3719
|
+
"Reply received hook failed.",
|
|
3441
3720
|
signal
|
|
3442
3721
|
);
|
|
3443
3722
|
}
|
|
3444
|
-
}
|
|
3445
|
-
|
|
3723
|
+
},
|
|
3724
|
+
() => this.replyReceivedHandlers.size
|
|
3725
|
+
);
|
|
3446
3726
|
}
|
|
3447
3727
|
resolveNameOrDefault(payload) {
|
|
3448
3728
|
if (payload.messageType === void 0) {
|