@zlink-systems/stream-connector 0.13.0 → 0.15.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.
@@ -9,7 +9,8 @@ function lz4PickleUncompressed(payload) {
9
9
  pickled.set(payload, 1);
10
10
  return pickled;
11
11
  }
12
- function lz4UnpicklePayload(payload, maxDecompressedSize = defaultMaxDecompressedPayloadSize) {
12
+ function lz4UnpicklePayload(payload, maxSize) {
13
+ const maxDecompressedSize = maxSize ?? defaultMaxDecompressedPayloadSize;
13
14
  if (payload.length === 0) {
14
15
  return new Uint8Array();
15
16
  }
@@ -152,7 +153,8 @@ function decodeStreamWireFrame(frame) {
152
153
  payload: frame.slice(6 + headerLength)
153
154
  };
154
155
  }
155
- function encodeStreamWireHeader(header, flags = defaultHeaderFlags) {
156
+ function encodeStreamWireHeader(header, flagOverrides) {
157
+ const flags = flagOverrides ?? defaultHeaderFlags;
156
158
  const reply = isReplyKind(header.kind);
157
159
  const packetName = reply ? "" : header.name;
158
160
  if (!reply) validateStreamWirePacketName(packetName);
@@ -213,7 +215,8 @@ function encodeStreamWireHeader(header, flags = defaultHeaderFlags) {
213
215
  }
214
216
  return buffer;
215
217
  }
216
- function decodeStreamWireHeader(header, flags = defaultHeaderFlags, includeFlow = true) {
218
+ function decodeStreamWireHeader(header, flagOverrides, includeFlow = true) {
219
+ const flags = flagOverrides ?? defaultHeaderFlags;
217
220
  let offset = 0;
218
221
  if (header.length < 5) {
219
222
  throw new Error("Stream header is incomplete.");
@@ -351,8 +354,8 @@ function decodeStreamWireMetadata(metadata) {
351
354
  function lz4PickleUncompressed2(payload) {
352
355
  return lz4PickleUncompressed(payload);
353
356
  }
354
- function lz4UnpicklePayload2(payload, maxDecompressedSize = defaultMaxDecompressedPayloadSize) {
355
- return lz4UnpicklePayload(payload, maxDecompressedSize);
357
+ function lz4UnpicklePayload2(payload, maxDecompressedSize) {
358
+ return lz4UnpicklePayload(payload, maxDecompressedSize ?? defaultMaxDecompressedPayloadSize);
356
359
  }
357
360
  function utf8Encode(value) {
358
361
  return new TextEncoder().encode(value);
@@ -497,20 +500,17 @@ var ZlinkStreamErrorCode = /* @__PURE__ */ ((ZlinkStreamErrorCode2) => {
497
500
  ZlinkStreamErrorCode2["CompressionFailed"] = "compressionFailed";
498
501
  ZlinkStreamErrorCode2["DecompressionFailed"] = "decompressionFailed";
499
502
  ZlinkStreamErrorCode2["UserCallbackFailed"] = "userCallbackFailed";
500
- ZlinkStreamErrorCode2["ObserverFailed"] = "observer-failed";
501
- ZlinkStreamErrorCode2["ObserverDropped"] = "observer-dropped";
502
- ZlinkStreamErrorCode2["ReceivedMessageDropped"] = "received-message-dropped";
503
503
  ZlinkStreamErrorCode2["RemoteError"] = "remoteError";
504
504
  return ZlinkStreamErrorCode2;
505
505
  })(ZlinkStreamErrorCode || {});
506
- var ZlinkStreamConnectionState = /* @__PURE__ */ ((ZlinkStreamConnectionState2) => {
507
- ZlinkStreamConnectionState2["Created"] = "created";
508
- ZlinkStreamConnectionState2["Connecting"] = "connecting";
509
- ZlinkStreamConnectionState2["Connected"] = "connected";
510
- ZlinkStreamConnectionState2["Reconnecting"] = "reconnecting";
511
- ZlinkStreamConnectionState2["Disconnected"] = "disconnected";
512
- ZlinkStreamConnectionState2["Closed"] = "closed";
513
- return ZlinkStreamConnectionState2;
506
+ var ZlinkStreamConnectionState = /* @__PURE__ */ ((ZlinkStreamConnectionState3) => {
507
+ ZlinkStreamConnectionState3["Created"] = "created";
508
+ ZlinkStreamConnectionState3["Connecting"] = "connecting";
509
+ ZlinkStreamConnectionState3["Connected"] = "connected";
510
+ ZlinkStreamConnectionState3["Reconnecting"] = "reconnecting";
511
+ ZlinkStreamConnectionState3["Disconnected"] = "disconnected";
512
+ ZlinkStreamConnectionState3["Closed"] = "closed";
513
+ return ZlinkStreamConnectionState3;
514
514
  })(ZlinkStreamConnectionState || {});
515
515
 
516
516
  // packages/stream-connector/src/Contracts/ZlinkStreamMetadata.ts
@@ -1284,11 +1284,6 @@ function normalizeOptions(options, defaultTransportFactory) {
1284
1284
  validatePositive(options.waitTimeoutMs ?? 5e3, "WaitTimeout");
1285
1285
  validatePositive(options.maxSendPayloadSize ?? 64 * 1024, "MaxSendPayloadSize");
1286
1286
  validatePositive(options.maxReceivePayloadSize ?? 64 * 1024, "MaxReceivePayloadSize");
1287
- validatePositive(options.maxReceivedMessages ?? 1024, "MaxReceivedMessages");
1288
- validatePositive(options.maxInboundObserverNotifications ?? 1024, "MaxInboundObserverNotifications");
1289
- if ((options.maxInboundObserverPayloadPreviewBytes ?? 0) < 0) {
1290
- throw connectorError("validationFailed" /* ValidationFailed */, "MaxInboundObserverPayloadPreviewBytes must not be negative.");
1291
- }
1292
1287
  validateHeartbeat(options.heartbeat);
1293
1288
  validateReconnect(options.reconnect);
1294
1289
  validateDiagnosticsLevel(options.diagnosticsLevel);
@@ -1312,9 +1307,6 @@ function normalizeOptions(options, defaultTransportFactory) {
1312
1307
  },
1313
1308
  maxSendPayloadSize: options.maxSendPayloadSize ?? 64 * 1024,
1314
1309
  maxReceivePayloadSize: options.maxReceivePayloadSize ?? 64 * 1024,
1315
- maxReceivedMessages: options.maxReceivedMessages ?? 1024,
1316
- maxInboundObserverNotifications: options.maxInboundObserverNotifications ?? 1024,
1317
- maxInboundObserverPayloadPreviewBytes: options.maxInboundObserverPayloadPreviewBytes ?? 0,
1318
1310
  dispatchMode: options.dispatchMode ?? "manual" /* Manual */,
1319
1311
  compression: options.compression ?? "lz4" /* Lz4 */,
1320
1312
  compressionCodec: resolveCompressionCodec2(options),
@@ -1475,11 +1467,18 @@ var ZlinkStreamPendingRequests = class {
1475
1467
 
1476
1468
  // packages/stream-connector/src/Runtime/ZlinkStreamReceivedMessages.ts
1477
1469
  var ZlinkStreamReceivedMessages = class {
1478
- constructor(capacity, events) {
1479
- this.capacity = capacity;
1470
+ /**
1471
+ * @param deliverOnArrival `Immediate` runs registered handlers on the receive
1472
+ * path; `Manual` leaves them queued until {@link pump} runs them on the
1473
+ * caller's thread (spec stream-connector 32 §7). Wait surfaces observe the
1474
+ * queue in both modes, so they never depend on this flag.
1475
+ */
1476
+ constructor(events, deliverOnArrival) {
1480
1477
  this.events = events;
1478
+ this.deliverOnArrival = deliverOnArrival;
1481
1479
  }
1482
1480
  handlers = /* @__PURE__ */ new Map();
1481
+ observers = /* @__PURE__ */ new Map();
1483
1482
  // A handler can be registered after messages for another name arrive, so the
1484
1483
  // queue is not a simple FIFO. Tombstones let us remove a deliverable entry
1485
1484
  // without shifting every later message on the hot receive path.
@@ -1487,7 +1486,6 @@ var ZlinkStreamReceivedMessages = class {
1487
1486
  queueHead = 0;
1488
1487
  queuedCount = 0;
1489
1488
  drainTask;
1490
- dropReportPending = false;
1491
1489
  on(name, handler) {
1492
1490
  validateName(name);
1493
1491
  let set = this.handlers.get(name);
@@ -1496,7 +1494,7 @@ var ZlinkStreamReceivedMessages = class {
1496
1494
  this.handlers.set(name, set);
1497
1495
  }
1498
1496
  set.add(handler);
1499
- if (this.hasQueuedMessage(name)) {
1497
+ if (this.deliverOnArrival && this.hasQueuedMessage(name)) {
1500
1498
  queueMicrotask(() => this.scheduleDrain());
1501
1499
  }
1502
1500
  return subscription(() => {
@@ -1506,14 +1504,66 @@ var ZlinkStreamReceivedMessages = class {
1506
1504
  }
1507
1505
  });
1508
1506
  }
1507
+ /**
1508
+ * Registers a wait surface over the receive queue. Spec stream-connector 32
1509
+ * §7: these are not registered callbacks — they observe and consume the
1510
+ * packets the queue has not delivered yet, in both dispatch modes, so
1511
+ * `Manual` needs no dispatch pump to complete a wait. The queue is scanned in
1512
+ * a microtask so a message that arrived before the wait started is still
1513
+ * observed, and so the caller has its subscription in hand by then.
1514
+ */
1515
+ observe(name, observer) {
1516
+ validateName(name);
1517
+ let set = this.observers.get(name);
1518
+ if (set === void 0) {
1519
+ set = /* @__PURE__ */ new Set();
1520
+ this.observers.set(name, set);
1521
+ }
1522
+ set.add(observer);
1523
+ queueMicrotask(() => {
1524
+ if (this.observers.get(name)?.has(observer) === true) {
1525
+ this.offerQueued(name, observer);
1526
+ }
1527
+ });
1528
+ return subscription(() => {
1529
+ set.delete(observer);
1530
+ if (set.size === 0 && this.observers.get(name) === set) {
1531
+ this.observers.delete(name);
1532
+ }
1533
+ });
1534
+ }
1509
1535
  enqueue(message, signal) {
1510
- if (this.queuedCount >= this.capacity) {
1511
- this.reportDropped(signal);
1512
- return;
1536
+ for (const observer of [...this.observers.get(message.name) ?? []]) {
1537
+ if (observer(message)) {
1538
+ return;
1539
+ }
1513
1540
  }
1514
1541
  this.queue.push({ message, signal });
1515
1542
  this.queuedCount += 1;
1543
+ if (this.deliverOnArrival) {
1544
+ this.scheduleDrain();
1545
+ }
1546
+ }
1547
+ /**
1548
+ * Runs the registered handlers the receive path left queued. `Manual` calls
1549
+ * this from `dispatch`; `Immediate` has already drained on arrival.
1550
+ */
1551
+ async pump() {
1516
1552
  this.scheduleDrain();
1553
+ await this.drainTask;
1554
+ }
1555
+ offerQueued(name, observer) {
1556
+ for (let index = this.queueHead; index < this.queue.length; index += 1) {
1557
+ const queued = this.queue[index];
1558
+ if (queued === void 0 || queued.message.name !== name) {
1559
+ continue;
1560
+ }
1561
+ if (!observer(queued.message)) {
1562
+ continue;
1563
+ }
1564
+ this.removeAt(index);
1565
+ return;
1566
+ }
1517
1567
  }
1518
1568
  scheduleDrain() {
1519
1569
  if (this.drainTask !== void 0) {
@@ -1521,7 +1571,7 @@ var ZlinkStreamReceivedMessages = class {
1521
1571
  }
1522
1572
  this.drainTask = this.drain().finally(() => {
1523
1573
  this.drainTask = void 0;
1524
- if (this.findDeliverableIndex() >= 0) {
1574
+ if (this.deliverOnArrival && this.findDeliverableIndex() >= 0) {
1525
1575
  this.scheduleDrain();
1526
1576
  }
1527
1577
  });
@@ -1530,10 +1580,7 @@ var ZlinkStreamReceivedMessages = class {
1530
1580
  for (let index = this.findDeliverableIndex(); index >= 0; index = this.findDeliverableIndex()) {
1531
1581
  const queued = this.queue[index];
1532
1582
  if (queued === void 0) continue;
1533
- this.queue[index] = void 0;
1534
- this.queuedCount -= 1;
1535
- this.advanceHead();
1536
- this.compactQueue();
1583
+ this.removeAt(index);
1537
1584
  const { message, signal } = queued;
1538
1585
  const handlers = [...this.handlers.get(message.name)];
1539
1586
  for (const handler of handlers) {
@@ -1549,6 +1596,12 @@ var ZlinkStreamReceivedMessages = class {
1549
1596
  }
1550
1597
  }
1551
1598
  }
1599
+ removeAt(index) {
1600
+ this.queue[index] = void 0;
1601
+ this.queuedCount -= 1;
1602
+ this.advanceHead();
1603
+ this.compactQueue();
1604
+ }
1552
1605
  findDeliverableIndex() {
1553
1606
  for (let index = this.queueHead; index < this.queue.length; index += 1) {
1554
1607
  const queued = this.queue[index];
@@ -1580,144 +1633,6 @@ var ZlinkStreamReceivedMessages = class {
1580
1633
  this.queueHead = 0;
1581
1634
  }
1582
1635
  }
1583
- reportDropped(signal) {
1584
- if (this.dropReportPending) {
1585
- return;
1586
- }
1587
- this.dropReportPending = true;
1588
- queueMicrotask(() => {
1589
- void this.events.publishError({
1590
- code: "received-message-dropped" /* ReceivedMessageDropped */,
1591
- message: "Received stream message was dropped because the received-message queue is full."
1592
- }, signal).finally(() => {
1593
- this.dropReportPending = false;
1594
- }).catch(() => {
1595
- });
1596
- });
1597
- }
1598
- };
1599
-
1600
- // packages/stream-connector/src/Runtime/ZlinkStreamInboundObservers.ts
1601
- var ZlinkStreamInboundObservers = class {
1602
- constructor(maxNotifications, maxPayloadPreviewBytes, events) {
1603
- this.maxNotifications = maxNotifications;
1604
- this.maxPayloadPreviewBytes = maxPayloadPreviewBytes;
1605
- this.events = events;
1606
- }
1607
- observers = /* @__PURE__ */ new Set();
1608
- queue = [];
1609
- queueHead = 0;
1610
- queuedCount = 0;
1611
- draining = false;
1612
- dropReportPending = false;
1613
- add(observer) {
1614
- this.observers.add(observer);
1615
- return subscription(() => this.observers.delete(observer));
1616
- }
1617
- enqueue(header, payload, signal) {
1618
- if (this.observers.size === 0) {
1619
- return;
1620
- }
1621
- if (this.queuedCount >= this.maxNotifications) {
1622
- this.reportDropped(signal);
1623
- return;
1624
- }
1625
- this.queue.push(this.createObservation(header, payload));
1626
- this.queuedCount += 1;
1627
- this.scheduleDrain(signal);
1628
- }
1629
- createObservation(header, payload) {
1630
- const previewLength = Math.min(this.maxPayloadPreviewBytes, payload.length);
1631
- return Object.freeze({
1632
- kind: header.kind,
1633
- name: header.name,
1634
- codec: header.codec,
1635
- requestSeq: header.requestSeq,
1636
- flowId: header.flowId,
1637
- flowOrigin: header.flowOrigin,
1638
- metadata: ZlinkStreamMetadataMap.from(header.metadata.values),
1639
- payloadLength: payload.length,
1640
- isCompressed: (header.flags & 4 /* PayloadCompressed */) !== 0,
1641
- receivedAt: /* @__PURE__ */ new Date(),
1642
- payloadPreview: previewLength === 0 ? new Uint8Array() : payload.slice(0, previewLength)
1643
- });
1644
- }
1645
- scheduleDrain(signal) {
1646
- if (this.draining) {
1647
- return;
1648
- }
1649
- this.draining = true;
1650
- queueMicrotask(() => {
1651
- void this.drain(signal);
1652
- });
1653
- }
1654
- async drain(signal) {
1655
- try {
1656
- while (this.queuedCount > 0) {
1657
- const observation = this.takeObservation();
1658
- if (observation === void 0) continue;
1659
- const observers = [...this.observers];
1660
- for (const observer of observers) {
1661
- if (!this.observers.has(observer)) {
1662
- continue;
1663
- }
1664
- try {
1665
- await observer(observation, signal);
1666
- } catch (cause) {
1667
- await this.reportObserverFailed(cause, signal);
1668
- }
1669
- }
1670
- }
1671
- } finally {
1672
- this.draining = false;
1673
- if (this.queuedCount > 0) {
1674
- this.scheduleDrain(signal);
1675
- }
1676
- }
1677
- }
1678
- reportDropped(signal) {
1679
- if (this.dropReportPending) {
1680
- return;
1681
- }
1682
- this.dropReportPending = true;
1683
- queueMicrotask(() => {
1684
- void this.events.publishError({
1685
- code: "observer-dropped" /* ObserverDropped */,
1686
- message: "Inbound observer notification was dropped because the observer queue is full."
1687
- }, signal).finally(() => {
1688
- this.dropReportPending = false;
1689
- }).catch(() => {
1690
- });
1691
- });
1692
- }
1693
- async reportObserverFailed(cause, signal) {
1694
- try {
1695
- await this.events.publishError({
1696
- code: "observer-failed" /* ObserverFailed */,
1697
- message: "Inbound observer callback failed.",
1698
- cause
1699
- }, signal);
1700
- } catch {
1701
- }
1702
- }
1703
- takeObservation() {
1704
- while (this.queueHead < this.queue.length && this.queue[this.queueHead] === void 0) {
1705
- this.queueHead += 1;
1706
- }
1707
- const observation = this.queue[this.queueHead];
1708
- if (observation === void 0) return void 0;
1709
- this.queue[this.queueHead] = void 0;
1710
- this.queueHead += 1;
1711
- this.queuedCount -= 1;
1712
- if (this.queuedCount === 0) {
1713
- this.queue.length = 0;
1714
- this.queueHead = 0;
1715
- } else if (this.queueHead >= 1024 && this.queueHead * 2 >= this.queue.length) {
1716
- this.queue.splice(0, this.queueHead);
1717
- this.queueHead = 0;
1718
- }
1719
- return observation;
1720
- }
1721
1636
  };
1722
1637
 
1723
1638
  // packages/stream-connector/src/Runtime/ZlinkStreamFrameSender.ts
@@ -1780,10 +1695,9 @@ function decodeSessionClosing(payload) {
1780
1695
 
1781
1696
  // packages/stream-connector/src/Runtime/ZlinkStreamReceiveDispatcher.ts
1782
1697
  var ZlinkStreamReceiveDispatcher = class {
1783
- constructor(protocol, pendingRequests, inboundObservers, receivedMessages, frameSender, events, flowContext, metrics, serverClosing) {
1698
+ constructor(protocol, pendingRequests, receivedMessages, frameSender, events, flowContext, metrics, serverClosing) {
1784
1699
  this.protocol = protocol;
1785
1700
  this.pendingRequests = pendingRequests;
1786
- this.inboundObservers = inboundObservers;
1787
1701
  this.receivedMessages = receivedMessages;
1788
1702
  this.frameSender = frameSender;
1789
1703
  this.events = events;
@@ -1830,7 +1744,6 @@ var ZlinkStreamReceiveDispatcher = class {
1830
1744
  return { available: true, inbound: true };
1831
1745
  }
1832
1746
  async dispatch(connection, header, payload, signal, flowEnabled) {
1833
- this.inboundObservers.enqueue(header, payload, signal);
1834
1747
  if (header.kind === 3 /* Response */ && header.requestSeq !== void 0) {
1835
1748
  try {
1836
1749
  if (!this.pendingRequests.resolve(header.requestSeq, {
@@ -1942,15 +1855,19 @@ function decodeRemoteError(protocol, header, payload) {
1942
1855
 
1943
1856
  // packages/stream-connector/src/Runtime/ZlinkStreamConnectorLifecycle.ts
1944
1857
  var ZlinkStreamConnectorLifecycle = class {
1945
- constructor(options, pendingRequests, frameSender, receiveDispatcher, events, metrics) {
1858
+ constructor(options, pendingRequests, frameSender, receiveDispatcher, receivedMessages, events, metrics) {
1946
1859
  this.options = options;
1947
1860
  this.pendingRequests = pendingRequests;
1948
1861
  this.frameSender = frameSender;
1949
1862
  this.receiveDispatcher = receiveDispatcher;
1863
+ this.receivedMessages = receivedMessages;
1950
1864
  this.events = events;
1951
1865
  this.metrics = metrics;
1952
1866
  }
1953
1867
  receiveLoopAbort;
1868
+ receiveLoopSleeping = false;
1869
+ receiveLoopWake;
1870
+ receiveLoopSettled = [];
1954
1871
  currentConnection;
1955
1872
  connectionGeneration = 0;
1956
1873
  currentState = "created" /* Created */;
@@ -2067,16 +1984,21 @@ var ZlinkStreamConnectorLifecycle = class {
2067
1984
  if (errors.length === 1) throw errors[0];
2068
1985
  if (errors.length > 1) throw new AggregateError(errors, "Stream connector close failed.");
2069
1986
  }
1987
+ /**
1988
+ * Spec stream-connector 32 §7: `dispatch` runs the callbacks the receive loop
1989
+ * queued, it does not drive the transport. Receiving is the receive loop's
1990
+ * job in both dispatch modes, which is what lets a `Manual` consumer complete
1991
+ * a `waitFor` without pumping, and what keeps this call from blocking on an
1992
+ * idle connection. In `Manual` it first lets the loop settle whatever has
1993
+ * already arrived, so a packet the transport is holding is delivered by this
1994
+ * pump rather than the next one.
1995
+ */
2070
1996
  async dispatch(signal) {
2071
- const connection = this.currentConnection;
2072
- const generation = this.connectionGeneration;
2073
- try {
2074
- await this.dispatchAvailable(connection, generation, signal);
2075
- } catch (cause) {
2076
- const error = toStreamError(cause, "frameDecodeFailed" /* FrameDecodeFailed */, "Stream dispatch failed.");
2077
- await this.disconnectForTransportFailure(error, connection, generation);
2078
- throw new ZlinkStreamException(error);
1997
+ throwIfAborted(signal);
1998
+ if (this.options.dispatchMode !== "immediate" /* Immediate */) {
1999
+ await this.settleReceiveLoop();
2079
2000
  }
2001
+ await this.receivedMessages.pump();
2080
2002
  }
2081
2003
  connectionForSend() {
2082
2004
  if (this.currentConnection === void 0 || this.currentState !== "connected" /* Connected */) {
@@ -2145,8 +2067,12 @@ var ZlinkStreamConnectorLifecycle = class {
2145
2067
  this.heartbeatTimer = void 0;
2146
2068
  }
2147
2069
  }
2070
+ // Spec stream-connector 32 §7: the receive loop runs in both dispatch modes.
2071
+ // `Manual` only changes what the loop does with a frame — it queues the
2072
+ // registered callbacks instead of running them — never whether frames are
2073
+ // read off the transport.
2148
2074
  startReceiveLoop() {
2149
- if (this.options.dispatchMode !== "immediate" /* Immediate */ || this.currentConnection?.read === void 0) {
2075
+ if (this.currentConnection?.read === void 0) {
2150
2076
  return;
2151
2077
  }
2152
2078
  this.stopReceiveLoop();
@@ -2159,19 +2085,66 @@ var ZlinkStreamConnectorLifecycle = class {
2159
2085
  stopReceiveLoop() {
2160
2086
  this.receiveLoopAbort?.abort();
2161
2087
  this.receiveLoopAbort = void 0;
2088
+ this.receiveLoopSleeping = false;
2089
+ this.releaseReceiveLoopSettled();
2162
2090
  }
2163
2091
  async runReceiveLoop(connection, generation, signal) {
2164
2092
  try {
2165
2093
  while (this.shouldContinueReceiveLoop(connection, generation, signal)) {
2166
2094
  const dispatched = await this.dispatchAvailable(connection, generation, signal);
2167
2095
  if (!dispatched && this.shouldContinueReceiveLoop(connection, generation, signal)) {
2168
- await delay(1, signal);
2096
+ await this.sleepUntilWork(signal);
2169
2097
  }
2170
2098
  }
2171
2099
  } catch (cause) {
2172
2100
  if (signal.aborted) return;
2173
2101
  const error = toStreamError(cause, "frameDecodeFailed" /* FrameDecodeFailed */, "Receive loop failed.");
2174
2102
  await this.disconnectForTransportFailure(error, connection, generation);
2103
+ } finally {
2104
+ this.receiveLoopSleeping = false;
2105
+ this.releaseReceiveLoopSettled();
2106
+ }
2107
+ }
2108
+ // A transport whose read resolves only when a frame arrives parks the loop
2109
+ // inside that read; one that reports "nothing available" instead parks it
2110
+ // here. Both are the loop waiting for new data, and `dispatch` treats them
2111
+ // the same way.
2112
+ async sleepUntilWork(signal) {
2113
+ this.receiveLoopSleeping = true;
2114
+ this.releaseReceiveLoopSettled();
2115
+ try {
2116
+ await new Promise((resolve) => {
2117
+ const finish = () => {
2118
+ clearTimeout(timer);
2119
+ signal.removeEventListener("abort", onAbort);
2120
+ this.receiveLoopWake = void 0;
2121
+ resolve();
2122
+ };
2123
+ const onAbort = () => finish();
2124
+ const timer = setTimeout(finish, 1);
2125
+ signal.addEventListener("abort", onAbort, { once: true });
2126
+ this.receiveLoopWake = finish;
2127
+ });
2128
+ } finally {
2129
+ this.receiveLoopSleeping = false;
2130
+ }
2131
+ }
2132
+ // Returns once the loop has consumed everything the transport already had.
2133
+ // A loop that is mid-batch, or parked inside a read that has not produced a
2134
+ // frame, is already caught up, so only a sleeping loop is woken and awaited.
2135
+ async settleReceiveLoop() {
2136
+ if (this.receiveLoopAbort === void 0 || !this.receiveLoopSleeping) {
2137
+ return;
2138
+ }
2139
+ const settled = new Promise((resolve) => {
2140
+ this.receiveLoopSettled.push(resolve);
2141
+ });
2142
+ this.receiveLoopWake?.();
2143
+ await settled;
2144
+ }
2145
+ releaseReceiveLoopSettled() {
2146
+ for (const resolve of this.receiveLoopSettled.splice(0)) {
2147
+ resolve();
2175
2148
  }
2176
2149
  }
2177
2150
  shouldContinueReceiveLoop(connection, generation, signal) {
@@ -2560,7 +2533,6 @@ var DefaultZlinkStreamConnector = class {
2560
2533
  pendingRequests = new ZlinkStreamPendingRequests();
2561
2534
  frameSender;
2562
2535
  receiveDispatcher;
2563
- inboundObservers;
2564
2536
  diagnosticsLevelCell;
2565
2537
  options;
2566
2538
  constructor(options) {
@@ -2575,19 +2547,13 @@ var DefaultZlinkStreamConnector = class {
2575
2547
  const metrics = new ZlinkStreamRuntimeMetrics(this.options);
2576
2548
  const protocol = new ZlinkStreamFrameProtocol(this.options);
2577
2549
  this.frameSender = new ZlinkStreamFrameSender(protocol, flowContext, metrics);
2578
- this.inboundObservers = new ZlinkStreamInboundObservers(
2579
- this.options.maxInboundObserverNotifications,
2580
- this.options.maxInboundObserverPayloadPreviewBytes,
2581
- this.events
2582
- );
2583
2550
  this.receivedMessages = new ZlinkStreamReceivedMessages(
2584
- this.options.maxReceivedMessages,
2585
- this.events
2551
+ this.events,
2552
+ this.options.dispatchMode === "immediate" /* Immediate */
2586
2553
  );
2587
2554
  this.receiveDispatcher = new ZlinkStreamReceiveDispatcher(
2588
2555
  protocol,
2589
2556
  this.pendingRequests,
2590
- this.inboundObservers,
2591
2557
  this.receivedMessages,
2592
2558
  this.frameSender,
2593
2559
  this.events,
@@ -2600,6 +2566,7 @@ var DefaultZlinkStreamConnector = class {
2600
2566
  this.pendingRequests,
2601
2567
  this.frameSender,
2602
2568
  this.receiveDispatcher,
2569
+ this.receivedMessages,
2603
2570
  this.events,
2604
2571
  metrics
2605
2572
  );
@@ -2667,12 +2634,6 @@ var DefaultZlinkStreamConnector = class {
2667
2634
  const encoded = this.encodePayload(payload, messageType);
2668
2635
  return new ZlinkStreamRequestBuilder(this, this.resolveNameOrDefault(encoded), encoded);
2669
2636
  }
2670
- observeInbound(observer) {
2671
- if (this.lifecycle.state !== "created" /* Created */) {
2672
- throw connectorError("validationFailed" /* ValidationFailed */, "Inbound observers must be registered before connecting.");
2673
- }
2674
- return this.inboundObservers.add(observer);
2675
- }
2676
2637
  on(name, handler, messageType) {
2677
2638
  const encodedHandler = (message, signal) => handler({
2678
2639
  name: message.name,
@@ -2726,7 +2687,10 @@ var DefaultZlinkStreamConnector = class {
2726
2687
  finish(connectorError("requestTimeout" /* RequestTimeout */, "Wait for stream message timed out."));
2727
2688
  }, timeoutMs);
2728
2689
  signal?.addEventListener("abort", onAbort, { once: true });
2729
- disposable = this.receivedMessages.on(name, (message) => {
2690
+ disposable = this.receivedMessages.observe(name, (message) => {
2691
+ if (done) {
2692
+ return false;
2693
+ }
2730
2694
  try {
2731
2695
  const decoded = {
2732
2696
  name: message.name,
@@ -2735,12 +2699,14 @@ var DefaultZlinkStreamConnector = class {
2735
2699
  flowId: message.flowId,
2736
2700
  flowOrigin: message.flowOrigin
2737
2701
  };
2738
- if (predicate(decoded)) {
2739
- finish(void 0, decoded);
2702
+ if (!predicate(decoded)) {
2703
+ return false;
2740
2704
  }
2705
+ finish(void 0, decoded);
2741
2706
  } catch (cause) {
2742
2707
  finish(cause);
2743
2708
  }
2709
+ return true;
2744
2710
  });
2745
2711
  });
2746
2712
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zlink-systems/stream-connector",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,6 +21,6 @@
21
21
  }
22
22
  },
23
23
  "dependencies": {
24
- "@zlink-systems/stream-wire": "0.13.0"
24
+ "@zlink-systems/stream-wire": "0.15.0"
25
25
  }
26
26
  }
@@ -1,25 +0,0 @@
1
- import { Disposable, ZlinkStreamInboundObservation } from '../Contracts';
2
- import type { ZlinkStreamHeader } from '../Contracts/ZlinkStreamModels';
3
- import type { ZlinkStreamConnectorEvents } from './ZlinkStreamConnectorEvents';
4
- type InboundObserver = (observation: ZlinkStreamInboundObservation, signal?: AbortSignal) => Promise<void> | void;
5
- export declare class ZlinkStreamInboundObservers {
6
- private readonly maxNotifications;
7
- private readonly maxPayloadPreviewBytes;
8
- private readonly events;
9
- private readonly observers;
10
- private readonly queue;
11
- private queueHead;
12
- private queuedCount;
13
- private draining;
14
- private dropReportPending;
15
- constructor(maxNotifications: number, maxPayloadPreviewBytes: number, events: ZlinkStreamConnectorEvents);
16
- add(observer: InboundObserver): Disposable;
17
- enqueue(header: ZlinkStreamHeader, payload: Uint8Array, signal?: AbortSignal): void;
18
- private createObservation;
19
- private scheduleDrain;
20
- private drain;
21
- private reportDropped;
22
- private reportObserverFailed;
23
- private takeObservation;
24
- }
25
- export {};