@zlink-systems/stream-connector 0.14.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.
package/dist/browser/index.mjs
CHANGED
|
@@ -9,7 +9,8 @@ function lz4PickleUncompressed(payload) {
|
|
|
9
9
|
pickled.set(payload, 1);
|
|
10
10
|
return pickled;
|
|
11
11
|
}
|
|
12
|
-
function lz4UnpicklePayload(payload,
|
|
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,
|
|
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,
|
|
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
|
|
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);
|
|
@@ -1464,10 +1467,18 @@ var ZlinkStreamPendingRequests = class {
|
|
|
1464
1467
|
|
|
1465
1468
|
// packages/stream-connector/src/Runtime/ZlinkStreamReceivedMessages.ts
|
|
1466
1469
|
var ZlinkStreamReceivedMessages = class {
|
|
1467
|
-
|
|
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) {
|
|
1468
1477
|
this.events = events;
|
|
1478
|
+
this.deliverOnArrival = deliverOnArrival;
|
|
1469
1479
|
}
|
|
1470
1480
|
handlers = /* @__PURE__ */ new Map();
|
|
1481
|
+
observers = /* @__PURE__ */ new Map();
|
|
1471
1482
|
// A handler can be registered after messages for another name arrive, so the
|
|
1472
1483
|
// queue is not a simple FIFO. Tombstones let us remove a deliverable entry
|
|
1473
1484
|
// without shifting every later message on the hot receive path.
|
|
@@ -1483,7 +1494,7 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1483
1494
|
this.handlers.set(name, set);
|
|
1484
1495
|
}
|
|
1485
1496
|
set.add(handler);
|
|
1486
|
-
if (this.hasQueuedMessage(name)) {
|
|
1497
|
+
if (this.deliverOnArrival && this.hasQueuedMessage(name)) {
|
|
1487
1498
|
queueMicrotask(() => this.scheduleDrain());
|
|
1488
1499
|
}
|
|
1489
1500
|
return subscription(() => {
|
|
@@ -1493,10 +1504,66 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1493
1504
|
}
|
|
1494
1505
|
});
|
|
1495
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
|
+
}
|
|
1496
1535
|
enqueue(message, signal) {
|
|
1536
|
+
for (const observer of [...this.observers.get(message.name) ?? []]) {
|
|
1537
|
+
if (observer(message)) {
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1497
1541
|
this.queue.push({ message, signal });
|
|
1498
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() {
|
|
1499
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
|
+
}
|
|
1500
1567
|
}
|
|
1501
1568
|
scheduleDrain() {
|
|
1502
1569
|
if (this.drainTask !== void 0) {
|
|
@@ -1504,7 +1571,7 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1504
1571
|
}
|
|
1505
1572
|
this.drainTask = this.drain().finally(() => {
|
|
1506
1573
|
this.drainTask = void 0;
|
|
1507
|
-
if (this.findDeliverableIndex() >= 0) {
|
|
1574
|
+
if (this.deliverOnArrival && this.findDeliverableIndex() >= 0) {
|
|
1508
1575
|
this.scheduleDrain();
|
|
1509
1576
|
}
|
|
1510
1577
|
});
|
|
@@ -1513,10 +1580,7 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1513
1580
|
for (let index = this.findDeliverableIndex(); index >= 0; index = this.findDeliverableIndex()) {
|
|
1514
1581
|
const queued = this.queue[index];
|
|
1515
1582
|
if (queued === void 0) continue;
|
|
1516
|
-
this.
|
|
1517
|
-
this.queuedCount -= 1;
|
|
1518
|
-
this.advanceHead();
|
|
1519
|
-
this.compactQueue();
|
|
1583
|
+
this.removeAt(index);
|
|
1520
1584
|
const { message, signal } = queued;
|
|
1521
1585
|
const handlers = [...this.handlers.get(message.name)];
|
|
1522
1586
|
for (const handler of handlers) {
|
|
@@ -1532,6 +1596,12 @@ var ZlinkStreamReceivedMessages = class {
|
|
|
1532
1596
|
}
|
|
1533
1597
|
}
|
|
1534
1598
|
}
|
|
1599
|
+
removeAt(index) {
|
|
1600
|
+
this.queue[index] = void 0;
|
|
1601
|
+
this.queuedCount -= 1;
|
|
1602
|
+
this.advanceHead();
|
|
1603
|
+
this.compactQueue();
|
|
1604
|
+
}
|
|
1535
1605
|
findDeliverableIndex() {
|
|
1536
1606
|
for (let index = this.queueHead; index < this.queue.length; index += 1) {
|
|
1537
1607
|
const queued = this.queue[index];
|
|
@@ -1785,15 +1855,19 @@ function decodeRemoteError(protocol, header, payload) {
|
|
|
1785
1855
|
|
|
1786
1856
|
// packages/stream-connector/src/Runtime/ZlinkStreamConnectorLifecycle.ts
|
|
1787
1857
|
var ZlinkStreamConnectorLifecycle = class {
|
|
1788
|
-
constructor(options, pendingRequests, frameSender, receiveDispatcher, events, metrics) {
|
|
1858
|
+
constructor(options, pendingRequests, frameSender, receiveDispatcher, receivedMessages, events, metrics) {
|
|
1789
1859
|
this.options = options;
|
|
1790
1860
|
this.pendingRequests = pendingRequests;
|
|
1791
1861
|
this.frameSender = frameSender;
|
|
1792
1862
|
this.receiveDispatcher = receiveDispatcher;
|
|
1863
|
+
this.receivedMessages = receivedMessages;
|
|
1793
1864
|
this.events = events;
|
|
1794
1865
|
this.metrics = metrics;
|
|
1795
1866
|
}
|
|
1796
1867
|
receiveLoopAbort;
|
|
1868
|
+
receiveLoopSleeping = false;
|
|
1869
|
+
receiveLoopWake;
|
|
1870
|
+
receiveLoopSettled = [];
|
|
1797
1871
|
currentConnection;
|
|
1798
1872
|
connectionGeneration = 0;
|
|
1799
1873
|
currentState = "created" /* Created */;
|
|
@@ -1910,16 +1984,21 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
1910
1984
|
if (errors.length === 1) throw errors[0];
|
|
1911
1985
|
if (errors.length > 1) throw new AggregateError(errors, "Stream connector close failed.");
|
|
1912
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
|
+
*/
|
|
1913
1996
|
async dispatch(signal) {
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
await this.dispatchAvailable(connection, generation, signal);
|
|
1918
|
-
} catch (cause) {
|
|
1919
|
-
const error = toStreamError(cause, "frameDecodeFailed" /* FrameDecodeFailed */, "Stream dispatch failed.");
|
|
1920
|
-
await this.disconnectForTransportFailure(error, connection, generation);
|
|
1921
|
-
throw new ZlinkStreamException(error);
|
|
1997
|
+
throwIfAborted(signal);
|
|
1998
|
+
if (this.options.dispatchMode !== "immediate" /* Immediate */) {
|
|
1999
|
+
await this.settleReceiveLoop();
|
|
1922
2000
|
}
|
|
2001
|
+
await this.receivedMessages.pump();
|
|
1923
2002
|
}
|
|
1924
2003
|
connectionForSend() {
|
|
1925
2004
|
if (this.currentConnection === void 0 || this.currentState !== "connected" /* Connected */) {
|
|
@@ -1988,8 +2067,12 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
1988
2067
|
this.heartbeatTimer = void 0;
|
|
1989
2068
|
}
|
|
1990
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.
|
|
1991
2074
|
startReceiveLoop() {
|
|
1992
|
-
if (this.
|
|
2075
|
+
if (this.currentConnection?.read === void 0) {
|
|
1993
2076
|
return;
|
|
1994
2077
|
}
|
|
1995
2078
|
this.stopReceiveLoop();
|
|
@@ -2002,19 +2085,66 @@ var ZlinkStreamConnectorLifecycle = class {
|
|
|
2002
2085
|
stopReceiveLoop() {
|
|
2003
2086
|
this.receiveLoopAbort?.abort();
|
|
2004
2087
|
this.receiveLoopAbort = void 0;
|
|
2088
|
+
this.receiveLoopSleeping = false;
|
|
2089
|
+
this.releaseReceiveLoopSettled();
|
|
2005
2090
|
}
|
|
2006
2091
|
async runReceiveLoop(connection, generation, signal) {
|
|
2007
2092
|
try {
|
|
2008
2093
|
while (this.shouldContinueReceiveLoop(connection, generation, signal)) {
|
|
2009
2094
|
const dispatched = await this.dispatchAvailable(connection, generation, signal);
|
|
2010
2095
|
if (!dispatched && this.shouldContinueReceiveLoop(connection, generation, signal)) {
|
|
2011
|
-
await
|
|
2096
|
+
await this.sleepUntilWork(signal);
|
|
2012
2097
|
}
|
|
2013
2098
|
}
|
|
2014
2099
|
} catch (cause) {
|
|
2015
2100
|
if (signal.aborted) return;
|
|
2016
2101
|
const error = toStreamError(cause, "frameDecodeFailed" /* FrameDecodeFailed */, "Receive loop failed.");
|
|
2017
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();
|
|
2018
2148
|
}
|
|
2019
2149
|
}
|
|
2020
2150
|
shouldContinueReceiveLoop(connection, generation, signal) {
|
|
@@ -2417,7 +2547,10 @@ var DefaultZlinkStreamConnector = class {
|
|
|
2417
2547
|
const metrics = new ZlinkStreamRuntimeMetrics(this.options);
|
|
2418
2548
|
const protocol = new ZlinkStreamFrameProtocol(this.options);
|
|
2419
2549
|
this.frameSender = new ZlinkStreamFrameSender(protocol, flowContext, metrics);
|
|
2420
|
-
this.receivedMessages = new ZlinkStreamReceivedMessages(
|
|
2550
|
+
this.receivedMessages = new ZlinkStreamReceivedMessages(
|
|
2551
|
+
this.events,
|
|
2552
|
+
this.options.dispatchMode === "immediate" /* Immediate */
|
|
2553
|
+
);
|
|
2421
2554
|
this.receiveDispatcher = new ZlinkStreamReceiveDispatcher(
|
|
2422
2555
|
protocol,
|
|
2423
2556
|
this.pendingRequests,
|
|
@@ -2433,6 +2566,7 @@ var DefaultZlinkStreamConnector = class {
|
|
|
2433
2566
|
this.pendingRequests,
|
|
2434
2567
|
this.frameSender,
|
|
2435
2568
|
this.receiveDispatcher,
|
|
2569
|
+
this.receivedMessages,
|
|
2436
2570
|
this.events,
|
|
2437
2571
|
metrics
|
|
2438
2572
|
);
|
|
@@ -2553,7 +2687,10 @@ var DefaultZlinkStreamConnector = class {
|
|
|
2553
2687
|
finish(connectorError("requestTimeout" /* RequestTimeout */, "Wait for stream message timed out."));
|
|
2554
2688
|
}, timeoutMs);
|
|
2555
2689
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2556
|
-
disposable = this.receivedMessages.
|
|
2690
|
+
disposable = this.receivedMessages.observe(name, (message) => {
|
|
2691
|
+
if (done) {
|
|
2692
|
+
return false;
|
|
2693
|
+
}
|
|
2557
2694
|
try {
|
|
2558
2695
|
const decoded = {
|
|
2559
2696
|
name: message.name,
|
|
@@ -2562,12 +2699,14 @@ var DefaultZlinkStreamConnector = class {
|
|
|
2562
2699
|
flowId: message.flowId,
|
|
2563
2700
|
flowOrigin: message.flowOrigin
|
|
2564
2701
|
};
|
|
2565
|
-
if (predicate(decoded)) {
|
|
2566
|
-
|
|
2702
|
+
if (!predicate(decoded)) {
|
|
2703
|
+
return false;
|
|
2567
2704
|
}
|
|
2705
|
+
finish(void 0, decoded);
|
|
2568
2706
|
} catch (cause) {
|
|
2569
2707
|
finish(cause);
|
|
2570
2708
|
}
|
|
2709
|
+
return true;
|
|
2571
2710
|
});
|
|
2572
2711
|
});
|
|
2573
2712
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zlink-systems/stream-connector",
|
|
3
|
-
"version": "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.
|
|
24
|
+
"@zlink-systems/stream-wire": "0.15.0"
|
|
25
25
|
}
|
|
26
26
|
}
|