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