@statewalker/webrun-rpc 0.5.0 → 0.7.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/README.md +206 -12
- package/dist/async-dispose.d.ts +10 -0
- package/dist/async-dispose.d.ts.map +1 -0
- package/dist/connect-serve.d.ts +14 -1
- package/dist/connect-serve.d.ts.map +1 -1
- package/dist/duplex-over-port.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +626 -60
- package/dist/listen-port.d.ts.map +1 -1
- package/dist/message-target.d.ts +14 -1
- package/dist/message-target.d.ts.map +1 -1
- package/dist/multiplex-port.d.ts +11 -2
- package/dist/multiplex-port.d.ts.map +1 -1
- package/dist/port-credit.d.ts +27 -0
- package/dist/port-credit.d.ts.map +1 -0
- package/dist/port-types.d.ts +140 -6
- package/dist/port-types.d.ts.map +1 -1
- package/dist/return-at-once.d.ts +17 -0
- package/dist/return-at-once.d.ts.map +1 -0
- package/dist/structured-codec.d.ts.map +1 -1
- package/dist/transfer-port-mux.d.ts.map +1 -1
- package/dist/virtual-port.d.ts +8 -2
- package/dist/virtual-port.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/async-dispose.ts +22 -0
- package/src/close-signal.ts +1 -1
- package/src/connect-serve.ts +30 -4
- package/src/duplex-over-port.ts +9 -1
- package/src/index.ts +9 -1
- package/src/listen-port.ts +9 -2
- package/src/message-target.ts +14 -1
- package/src/multiplex-port.ts +610 -33
- package/src/port-credit.ts +61 -0
- package/src/port-types.ts +124 -8
- package/src/return-at-once.ts +51 -0
- package/src/structured-codec.ts +9 -1
- package/src/transfer-port-mux.ts +32 -8
- package/src/virtual-port.ts +41 -11
package/dist/index.js
CHANGED
|
@@ -238,8 +238,11 @@ function listenPort(port, handler, { channelName = "", log = () => {} } = {}) {
|
|
|
238
238
|
error
|
|
239
239
|
});
|
|
240
240
|
};
|
|
241
|
-
|
|
242
|
-
|
|
241
|
+
const listener = (event) => {
|
|
242
|
+
onMessage(event);
|
|
243
|
+
};
|
|
244
|
+
port.addEventListener("message", listener);
|
|
245
|
+
return () => port.removeEventListener("message", listener);
|
|
243
246
|
}
|
|
244
247
|
//#endregion
|
|
245
248
|
//#region src/recieve.ts
|
|
@@ -468,6 +471,73 @@ async function* callBidi(port, input, { options = {}, ...params } = {}) {
|
|
|
468
471
|
if (outerError !== void 0) throw outerError;
|
|
469
472
|
}
|
|
470
473
|
//#endregion
|
|
474
|
+
//#region src/async-dispose.ts
|
|
475
|
+
/**
|
|
476
|
+
* Makes `target` disposable with `await using`: installs `dispose` as its
|
|
477
|
+
* `[Symbol.asyncDispose]`, where the runtime defines the symbol. Hand-written
|
|
478
|
+
* iterators lose what a native generator gets from `AsyncIteratorPrototype`;
|
|
479
|
+
* this gives it back. A no-op where the symbol is missing. Kept off the
|
|
480
|
+
* declared types, which would require consumers to compile with the
|
|
481
|
+
* `esnext.disposable` lib.
|
|
482
|
+
*/
|
|
483
|
+
function asyncDisposable(target, dispose) {
|
|
484
|
+
const asyncDispose = Symbol.asyncDispose;
|
|
485
|
+
if (asyncDispose && !(asyncDispose in target)) Object.defineProperty(target, asyncDispose, {
|
|
486
|
+
value: async () => {
|
|
487
|
+
await dispose();
|
|
488
|
+
},
|
|
489
|
+
configurable: true,
|
|
490
|
+
writable: true
|
|
491
|
+
});
|
|
492
|
+
return target;
|
|
493
|
+
}
|
|
494
|
+
//#endregion
|
|
495
|
+
//#region src/return-at-once.ts
|
|
496
|
+
/**
|
|
497
|
+
* `gen`, except that `return()` and `throw()` take effect at once.
|
|
498
|
+
*
|
|
499
|
+
* On an async generator both are queued behind a `next()` that is still
|
|
500
|
+
* pending: a consumer that gives up on a stream while it waits for the peer's
|
|
501
|
+
* next chunk — an aborted fetch whose response head has not come, say — cannot
|
|
502
|
+
* end it until that chunk arrives, which for a peer that never answers is
|
|
503
|
+
* never. The stream's `finally` (which closes its port and tells the peer)
|
|
504
|
+
* then never runs, and on a multiplexer the port is kept for good.
|
|
505
|
+
*
|
|
506
|
+
* So `abandon` runs first, synchronously: it must make the pending `next()`
|
|
507
|
+
* settle (abort the stream), which then resolves done, after which the
|
|
508
|
+
* generator's own `return()` / `throw()` proceeds as usual. On a generator
|
|
509
|
+
* suspended at a `yield` it is the same ending, a step earlier. `abandon` runs once, whichever comes first.
|
|
510
|
+
*/
|
|
511
|
+
function returningAtOnce(gen, abandon) {
|
|
512
|
+
let abandoned = false;
|
|
513
|
+
const giveUp = (reason) => {
|
|
514
|
+
if (abandoned) return;
|
|
515
|
+
abandoned = true;
|
|
516
|
+
abandon(reason);
|
|
517
|
+
};
|
|
518
|
+
const wrapped = {
|
|
519
|
+
next: (...args) => gen.next(...args).catch((error) => {
|
|
520
|
+
if (abandoned) return {
|
|
521
|
+
done: true,
|
|
522
|
+
value: void 0
|
|
523
|
+
};
|
|
524
|
+
throw error;
|
|
525
|
+
}),
|
|
526
|
+
return(value) {
|
|
527
|
+
giveUp();
|
|
528
|
+
return gen.return(value);
|
|
529
|
+
},
|
|
530
|
+
throw(error) {
|
|
531
|
+
giveUp(error);
|
|
532
|
+
return gen.throw(error);
|
|
533
|
+
},
|
|
534
|
+
[Symbol.asyncIterator]() {
|
|
535
|
+
return wrapped;
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
return asyncDisposable(wrapped, () => wrapped.return(void 0));
|
|
539
|
+
}
|
|
540
|
+
//#endregion
|
|
471
541
|
//#region src/duplex-over-port.ts
|
|
472
542
|
/**
|
|
473
543
|
* The `type` of the out-of-band notice a side posts when it abandons a stream.
|
|
@@ -565,7 +635,7 @@ function runCallerSide(port, input, options) {
|
|
|
565
635
|
const inbound = receiveChunks(port, CHANNEL_OUT, controller, clock.touch);
|
|
566
636
|
const pump = sendChunks(port, CHANNEL_IN, input, options, controller.signal, clock.touch);
|
|
567
637
|
pump.catch(() => {});
|
|
568
|
-
return (async function* () {
|
|
638
|
+
return returningAtOnce((async function* () {
|
|
569
639
|
try {
|
|
570
640
|
yield* inbound.stream;
|
|
571
641
|
} finally {
|
|
@@ -579,7 +649,9 @@ function runCallerSide(port, input, options) {
|
|
|
579
649
|
await port.close?.();
|
|
580
650
|
} catch {}
|
|
581
651
|
}
|
|
582
|
-
})()
|
|
652
|
+
})(), () => {
|
|
653
|
+
if (!controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("webrun-rpc: the caller abandoned the stream"));
|
|
654
|
+
});
|
|
583
655
|
}
|
|
584
656
|
/**
|
|
585
657
|
* Listens for the peer's abort notice, and can post our own. The notice is a
|
|
@@ -780,6 +852,50 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
|
|
|
780
852
|
}
|
|
781
853
|
}
|
|
782
854
|
//#endregion
|
|
855
|
+
//#region src/port-credit.ts
|
|
856
|
+
/**
|
|
857
|
+
* Runs `fn` on a later task, never a microtask: a producer looping on
|
|
858
|
+
* `whenPortReady` must let the transport's own events run. `setImmediate`
|
|
859
|
+
* where it exists; elsewhere a `MessageChannel`, because nested `setTimeout`s
|
|
860
|
+
* are clamped to 4 ms in browsers.
|
|
861
|
+
*/
|
|
862
|
+
const setImmediateFn = globalThis.setImmediate;
|
|
863
|
+
/** @internal */
|
|
864
|
+
const nextTask = setImmediateFn ? (fn) => {
|
|
865
|
+
setImmediateFn(fn);
|
|
866
|
+
} : (() => {
|
|
867
|
+
let channel;
|
|
868
|
+
const queue = [];
|
|
869
|
+
return (fn) => {
|
|
870
|
+
if (!channel) {
|
|
871
|
+
channel = new MessageChannel();
|
|
872
|
+
channel.port1.onmessage = () => queue.shift()?.();
|
|
873
|
+
}
|
|
874
|
+
queue.push(fn);
|
|
875
|
+
channel.port2.postMessage(0);
|
|
876
|
+
};
|
|
877
|
+
})();
|
|
878
|
+
const states = /* @__PURE__ */ new WeakMap();
|
|
879
|
+
/** @internal Registered by the multiplexer for each port with a window. */
|
|
880
|
+
function setPortSendState(port, state) {
|
|
881
|
+
states.set(port, state);
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Bytes queued on `port` because its window is spent (0 for a port without
|
|
885
|
+
* flow control). A sender that should not queue without bound waits for
|
|
886
|
+
* `whenPortReady` before posting more.
|
|
887
|
+
*/
|
|
888
|
+
function portBufferedAmount(port) {
|
|
889
|
+
return states.get(port)?.bufferedAmount() ?? 0;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Resolves once at most `below` bytes are queued on `port`, always on a later
|
|
893
|
+
* task (for a port without flow control, on the next one).
|
|
894
|
+
*/
|
|
895
|
+
function whenPortReady(port, below = 0) {
|
|
896
|
+
return states.get(port)?.ready(below) ?? new Promise((resolve) => nextTask(resolve));
|
|
897
|
+
}
|
|
898
|
+
//#endregion
|
|
783
899
|
//#region src/virtual-port.ts
|
|
784
900
|
/**
|
|
785
901
|
* One virtual port.
|
|
@@ -796,6 +912,14 @@ function newVirtualPort(send, requestClose) {
|
|
|
796
912
|
if (closing.signal.aborted) return;
|
|
797
913
|
closing.abort(reason ?? /* @__PURE__ */ new Error("webrun-rpc: the port was closed"));
|
|
798
914
|
};
|
|
915
|
+
const close = (reason) => {
|
|
916
|
+
if (closed) return;
|
|
917
|
+
closed = true;
|
|
918
|
+
const notify = requestClose;
|
|
919
|
+
listeners.clear();
|
|
920
|
+
notify(reason);
|
|
921
|
+
abort(reason);
|
|
922
|
+
};
|
|
799
923
|
const port = {
|
|
800
924
|
addEventListener(_type, listener) {
|
|
801
925
|
listeners.add(listener);
|
|
@@ -807,24 +931,28 @@ function newVirtualPort(send, requestClose) {
|
|
|
807
931
|
if (closed) return;
|
|
808
932
|
send(message, transfer);
|
|
809
933
|
},
|
|
810
|
-
close
|
|
811
|
-
if (closed) return;
|
|
812
|
-
closed = true;
|
|
813
|
-
const notify = requestClose;
|
|
814
|
-
listeners.clear();
|
|
815
|
-
notify();
|
|
816
|
-
abort();
|
|
817
|
-
}
|
|
934
|
+
close
|
|
818
935
|
};
|
|
819
936
|
setPortCloseSignal(port, closing.signal);
|
|
937
|
+
const dispose = Symbol.dispose;
|
|
938
|
+
if (dispose) Object.defineProperty(port, dispose, {
|
|
939
|
+
value: () => close(),
|
|
940
|
+
configurable: true
|
|
941
|
+
});
|
|
820
942
|
return {
|
|
821
943
|
port,
|
|
822
944
|
deliver(payload) {
|
|
823
945
|
if (closed) return;
|
|
824
946
|
const event = new MessageEvent("message", { data: payload });
|
|
947
|
+
let pending;
|
|
825
948
|
for (const listener of [...listeners]) try {
|
|
826
|
-
listener(event);
|
|
949
|
+
const result = listener(event);
|
|
950
|
+
if (result && typeof result.then === "function") {
|
|
951
|
+
pending ??= [];
|
|
952
|
+
pending.push(result);
|
|
953
|
+
}
|
|
827
954
|
} catch {}
|
|
955
|
+
return pending ? Promise.allSettled(pending) : void 0;
|
|
828
956
|
},
|
|
829
957
|
markClosed(reason) {
|
|
830
958
|
closed = true;
|
|
@@ -833,6 +961,9 @@ function newVirtualPort(send, requestClose) {
|
|
|
833
961
|
},
|
|
834
962
|
isClosed() {
|
|
835
963
|
return closed;
|
|
964
|
+
},
|
|
965
|
+
hasListeners() {
|
|
966
|
+
return listeners.size > 0;
|
|
836
967
|
}
|
|
837
968
|
};
|
|
838
969
|
}
|
|
@@ -843,45 +974,303 @@ function newVirtualPort(send, requestClose) {
|
|
|
843
974
|
* multiplexer when it is closed (`PortMux.close()`).
|
|
844
975
|
*/
|
|
845
976
|
const MUX_CLOSED_MESSAGE = "webrun-rpc: the multiplexer was closed";
|
|
977
|
+
/**
|
|
978
|
+
* The id of keepalive's `ping` / `pong`: one no port ever takes. An older mux
|
|
979
|
+
* whose codec passes unknown envelopes through reads them as a close of that
|
|
980
|
+
* id, which must therefore not be a real port (0 is the initiator's first).
|
|
981
|
+
*/
|
|
982
|
+
const KEEPALIVE_ID = Number.MAX_SAFE_INTEGER;
|
|
983
|
+
/** The longest delay a timer holds; past it `setInterval` fires at once. */
|
|
984
|
+
const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
846
985
|
/** Ceiling on concurrently open virtual ports. Bounds the id table only. */
|
|
847
986
|
const DEFAULT_MAX_PORTS = 1024;
|
|
848
987
|
/**
|
|
988
|
+
* What a message costs a port's window when the codec cannot say
|
|
989
|
+
* (`PortCodec.encode`): its bytes and string lengths, found in its values up
|
|
990
|
+
* to 4 levels deep (at most 256 values), and at least 64. Both ends compute it
|
|
991
|
+
* from the same structured value, so they agree.
|
|
992
|
+
*/
|
|
993
|
+
function estimateMessageSize(payload) {
|
|
994
|
+
let budget = 256;
|
|
995
|
+
const walk = (v, depth) => {
|
|
996
|
+
if (budget-- <= 0) return 0;
|
|
997
|
+
if (typeof v === "string") return v.length;
|
|
998
|
+
if (v instanceof ArrayBuffer) return v.byteLength;
|
|
999
|
+
if (ArrayBuffer.isView(v)) return v.byteLength;
|
|
1000
|
+
if (v === null || typeof v !== "object" || depth >= 4) return 8;
|
|
1001
|
+
let n = 16;
|
|
1002
|
+
if (Array.isArray(v)) for (const x of v) n += walk(x, depth + 1);
|
|
1003
|
+
else for (const k in v) n += k.length + walk(v[k], depth + 1);
|
|
1004
|
+
return n;
|
|
1005
|
+
};
|
|
1006
|
+
return Math.max(64, walk(payload, 0));
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
849
1009
|
* The default `PortMux`: emulates multiplexing over a single port.
|
|
850
1010
|
*
|
|
851
1011
|
* A transport that already multiplexes natively supplies its own `PortMux`
|
|
852
1012
|
* instead — this implementation is for transports that offer one pipe.
|
|
853
1013
|
*/
|
|
854
1014
|
function multiplexPort(port, options) {
|
|
855
|
-
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize } = options;
|
|
1015
|
+
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize, signal, window, frameSize, keepalive, onLeak } = options;
|
|
1016
|
+
const misses = keepalive?.misses ?? 3;
|
|
1017
|
+
if (keepalive && !(Number.isFinite(keepalive.intervalMs) && keepalive.intervalMs > 0 && keepalive.intervalMs <= MAX_TIMER_MS && Number.isSafeInteger(misses) && misses > 0)) throw new RangeError(`webrun-rpc: keepalive needs an intervalMs in (0, ${MAX_TIMER_MS}] and a positive integer misses`);
|
|
1018
|
+
if (frameSize != null && !(Number.isSafeInteger(frameSize) && frameSize > 0)) throw new RangeError(`webrun-rpc: frameSize must be a positive integer, got ${frameSize}`);
|
|
856
1019
|
const open = /* @__PURE__ */ new Map();
|
|
1020
|
+
const leaks = onLeak ? new FinalizationRegistry(({ id, record, release }) => {
|
|
1021
|
+
const entry = open.get(id);
|
|
1022
|
+
if (entry?.handle !== record) return;
|
|
1023
|
+
const { handle: _, credit: __, partial: ___, ...info } = entry;
|
|
1024
|
+
if (!record.hasListeners()) {
|
|
1025
|
+
release("leaked");
|
|
1026
|
+
record.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the port was dropped without close()"));
|
|
1027
|
+
}
|
|
1028
|
+
try {
|
|
1029
|
+
onLeak?.(info);
|
|
1030
|
+
} catch {}
|
|
1031
|
+
}) : void 0;
|
|
1032
|
+
const touch = (id) => {
|
|
1033
|
+
const entry = open.get(id);
|
|
1034
|
+
if (entry) entry.lastActivity = Date.now();
|
|
1035
|
+
};
|
|
857
1036
|
let nextId = side === "initiator" ? 0 : 1;
|
|
858
1037
|
let muxClosed = false;
|
|
1038
|
+
/** This end counts encoded bytes and reassembles frames. */
|
|
1039
|
+
const exactCodec = codec.encode != null && codec.decode != null;
|
|
859
1040
|
const post = (envelope, transfer) => {
|
|
860
1041
|
if (muxClosed) return;
|
|
1042
|
+
codecPost(envelope, transfer);
|
|
1043
|
+
};
|
|
1044
|
+
/** Like `post`, also while closing: the mux's own close notices. */
|
|
1045
|
+
const codecPost = (envelope, transfer) => {
|
|
861
1046
|
try {
|
|
862
1047
|
codec.post(port, envelope, transfer);
|
|
863
1048
|
} catch {}
|
|
864
1049
|
};
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
1050
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
1051
|
+
const ring = [];
|
|
1052
|
+
let pumping = false;
|
|
1053
|
+
const pump = async () => {
|
|
1054
|
+
pumping = true;
|
|
1055
|
+
try {
|
|
1056
|
+
while (ring.length && !muxClosed) {
|
|
1057
|
+
if (port.whenDrained) await port.whenDrained();
|
|
1058
|
+
if (muxClosed) break;
|
|
1059
|
+
const id = ring.shift();
|
|
1060
|
+
const queue = outgoing.get(id);
|
|
1061
|
+
const item = queue?.shift();
|
|
1062
|
+
if (queue?.length) ring.push(id);
|
|
1063
|
+
else outgoing.delete(id);
|
|
1064
|
+
item?.();
|
|
1065
|
+
}
|
|
1066
|
+
} catch (err) {
|
|
1067
|
+
close(err).catch(() => {});
|
|
1068
|
+
} finally {
|
|
1069
|
+
pumping = false;
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
const sendOnPort = (id, item) => {
|
|
1073
|
+
if (frameSize == null) {
|
|
1074
|
+
item();
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
let queue = outgoing.get(id);
|
|
1078
|
+
if (!queue) {
|
|
1079
|
+
queue = [];
|
|
1080
|
+
outgoing.set(id, queue);
|
|
1081
|
+
ring.push(id);
|
|
1082
|
+
}
|
|
1083
|
+
queue.push(item);
|
|
1084
|
+
if (!pumping) pump();
|
|
1085
|
+
};
|
|
1086
|
+
const dropOutgoing = (id) => {
|
|
1087
|
+
if (!outgoing.delete(id)) return;
|
|
1088
|
+
const at = ring.indexOf(id);
|
|
1089
|
+
if (at >= 0) ring.splice(at, 1);
|
|
1090
|
+
};
|
|
1091
|
+
const postBytes = (bytes) => {
|
|
1092
|
+
if (muxClosed) return;
|
|
1093
|
+
try {
|
|
1094
|
+
port.postMessage(bytes);
|
|
1095
|
+
} catch {}
|
|
1096
|
+
};
|
|
1097
|
+
const releaseWaiters = (c) => {
|
|
1098
|
+
for (const w of c?.waiters.splice(0) ?? []) w.resolve();
|
|
1099
|
+
};
|
|
1100
|
+
const flush = (c) => {
|
|
1101
|
+
while (c.queue.length) {
|
|
1102
|
+
const next = c.queue[0];
|
|
1103
|
+
if (c.enforced && c.outstanding > 0 && c.outstanding + next.size > c.window) break;
|
|
1104
|
+
c.queue.shift();
|
|
1105
|
+
c.queued -= next.size;
|
|
1106
|
+
c.outstanding += next.size;
|
|
1107
|
+
next.send();
|
|
1108
|
+
}
|
|
1109
|
+
for (const w of [...c.waiters]) if (c.queued <= w.below) {
|
|
1110
|
+
c.waiters.splice(c.waiters.indexOf(w), 1);
|
|
1111
|
+
w.resolve();
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
const grant = (id, c, bytes) => {
|
|
1115
|
+
c.unconsumed -= bytes;
|
|
1116
|
+
c.toGrant += bytes;
|
|
1117
|
+
const send = () => {
|
|
1118
|
+
c.grantScheduled = false;
|
|
1119
|
+
if (!c.toGrant || !open.has(id)) return;
|
|
872
1120
|
post({
|
|
1121
|
+
type: "credit",
|
|
1122
|
+
id,
|
|
1123
|
+
bytes: c.toGrant
|
|
1124
|
+
});
|
|
1125
|
+
c.toGrant = 0;
|
|
1126
|
+
};
|
|
1127
|
+
if (c.toGrant >= c.window / 4) {
|
|
1128
|
+
send();
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
if (c.grantScheduled) return;
|
|
1132
|
+
c.grantScheduled = true;
|
|
1133
|
+
setTimeout(send, 0);
|
|
1134
|
+
};
|
|
1135
|
+
const attach = (id, meta, portWindow, opener, exact) => {
|
|
1136
|
+
const c = typeof portWindow === "number" && Number.isSafeInteger(portWindow) && portWindow > 0 ? {
|
|
1137
|
+
window: portWindow,
|
|
1138
|
+
enforced: false,
|
|
1139
|
+
opener,
|
|
1140
|
+
exact,
|
|
1141
|
+
outstanding: 0,
|
|
1142
|
+
queue: [],
|
|
1143
|
+
queued: 0,
|
|
1144
|
+
unconsumed: 0,
|
|
1145
|
+
waiters: [],
|
|
1146
|
+
toGrant: 0,
|
|
1147
|
+
grantScheduled: false
|
|
1148
|
+
} : void 0;
|
|
1149
|
+
const self = newVirtualPort((payload, transfer) => {
|
|
1150
|
+
touch(id);
|
|
1151
|
+
const envelope = {
|
|
1152
|
+
type: "message",
|
|
1153
|
+
id,
|
|
1154
|
+
payload
|
|
1155
|
+
};
|
|
1156
|
+
if (!c) {
|
|
1157
|
+
post(envelope, transfer);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
const bytes = c.exact ? codec.encode?.(envelope) : void 0;
|
|
1161
|
+
const size = bytes?.byteLength ?? estimateMessageSize(payload);
|
|
1162
|
+
const waits = frameSize != null || c.queue.length > 0 || c.enforced && c.outstanding > 0 && c.outstanding + size > c.window;
|
|
1163
|
+
const held = !bytes && waits ? {
|
|
1164
|
+
...envelope,
|
|
1165
|
+
payload: structuredClone(payload, { transfer })
|
|
1166
|
+
} : void 0;
|
|
1167
|
+
const send = () => {
|
|
1168
|
+
if (!bytes) {
|
|
1169
|
+
sendOnPort(id, held ? () => post(held) : () => post(envelope, transfer));
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
if (frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
1173
|
+
for (let at = 0; at < bytes.byteLength; at += frameSize) {
|
|
1174
|
+
const data = bytes.subarray(at, at + frameSize);
|
|
1175
|
+
const frame = at + frameSize >= bytes.byteLength ? {
|
|
1176
|
+
type: "frame",
|
|
1177
|
+
id,
|
|
1178
|
+
data,
|
|
1179
|
+
last: true
|
|
1180
|
+
} : {
|
|
1181
|
+
type: "frame",
|
|
1182
|
+
id,
|
|
1183
|
+
data
|
|
1184
|
+
};
|
|
1185
|
+
sendOnPort(id, () => post(frame));
|
|
1186
|
+
}
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
sendOnPort(id, () => postBytes(bytes));
|
|
1190
|
+
};
|
|
1191
|
+
c.queue.push({
|
|
1192
|
+
send,
|
|
1193
|
+
size
|
|
1194
|
+
});
|
|
1195
|
+
c.queued += size;
|
|
1196
|
+
flush(c);
|
|
1197
|
+
}, (reason) => {
|
|
1198
|
+
release(reason);
|
|
1199
|
+
record.markClosed(reason);
|
|
1200
|
+
});
|
|
1201
|
+
function release(reason) {
|
|
1202
|
+
open.delete(id);
|
|
1203
|
+
leaks?.unregister(record);
|
|
1204
|
+
if (c?.queue.length) {
|
|
1205
|
+
for (const item of c.queue.splice(0)) item.send();
|
|
1206
|
+
c.queued = 0;
|
|
1207
|
+
}
|
|
1208
|
+
sendOnPort(id, () => post({
|
|
873
1209
|
type: "close",
|
|
874
1210
|
id,
|
|
875
1211
|
reason
|
|
1212
|
+
}));
|
|
1213
|
+
releaseWaiters(c);
|
|
1214
|
+
}
|
|
1215
|
+
const { markClosed, isClosed, hasListeners } = self;
|
|
1216
|
+
if (c) {
|
|
1217
|
+
const deliver = self.deliver;
|
|
1218
|
+
self.deliver = (payload, size = estimateMessageSize(payload)) => {
|
|
1219
|
+
c.unconsumed += size;
|
|
1220
|
+
const done = deliver(payload);
|
|
1221
|
+
if (done) done.then(() => grant(id, c, size));
|
|
1222
|
+
else grant(id, c, size);
|
|
1223
|
+
return done;
|
|
1224
|
+
};
|
|
1225
|
+
setPortSendState(self.port, {
|
|
1226
|
+
bufferedAmount: () => c.queued,
|
|
1227
|
+
ready: (below = 0) => new Promise((resolve) => {
|
|
1228
|
+
if (c.queued <= below || isClosed()) nextTask(resolve);
|
|
1229
|
+
else c.waiters.push({
|
|
1230
|
+
below,
|
|
1231
|
+
resolve: () => nextTask(resolve)
|
|
1232
|
+
});
|
|
1233
|
+
})
|
|
876
1234
|
});
|
|
877
|
-
|
|
878
|
-
|
|
1235
|
+
}
|
|
1236
|
+
const record = {
|
|
1237
|
+
deliver: self.deliver,
|
|
1238
|
+
markClosed,
|
|
1239
|
+
isClosed,
|
|
1240
|
+
hasListeners,
|
|
1241
|
+
close: (reason) => {
|
|
1242
|
+
if (isClosed()) return;
|
|
1243
|
+
release(reason);
|
|
1244
|
+
markClosed(reason);
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
leaks?.register(self.port, {
|
|
1248
|
+
id,
|
|
1249
|
+
record,
|
|
1250
|
+
release
|
|
1251
|
+
}, record);
|
|
1252
|
+
const now = Date.now();
|
|
1253
|
+
open.set(id, {
|
|
1254
|
+
id,
|
|
1255
|
+
meta,
|
|
1256
|
+
openedAt: now,
|
|
1257
|
+
lastActivity: now,
|
|
1258
|
+
handle: record,
|
|
1259
|
+
credit: c
|
|
879
1260
|
});
|
|
880
|
-
open.set(id, self);
|
|
881
1261
|
return self;
|
|
882
1262
|
};
|
|
883
|
-
const handleEnvelope = (envelope) => {
|
|
1263
|
+
const handleEnvelope = (envelope, event) => {
|
|
884
1264
|
if (muxClosed) return;
|
|
1265
|
+
heardSincePing = true;
|
|
1266
|
+
if (envelope.type === "ping" || envelope.type === "pong") {
|
|
1267
|
+
peerKeepsAlive = true;
|
|
1268
|
+
if (envelope.type === "ping") post({
|
|
1269
|
+
type: "pong",
|
|
1270
|
+
id: KEEPALIVE_ID
|
|
1271
|
+
});
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
885
1274
|
if (envelope.type === "open") {
|
|
886
1275
|
if (open.has(envelope.id)) return;
|
|
887
1276
|
if (open.size >= maxPorts) {
|
|
@@ -892,7 +1281,18 @@ function multiplexPort(port, options) {
|
|
|
892
1281
|
});
|
|
893
1282
|
return;
|
|
894
1283
|
}
|
|
895
|
-
const handle = attach(envelope.id);
|
|
1284
|
+
const handle = attach(envelope.id, envelope.meta, envelope.window, false, envelope.exact === true && exactCodec);
|
|
1285
|
+
const credit = open.get(envelope.id)?.credit;
|
|
1286
|
+
if (credit) post(credit.exact ? {
|
|
1287
|
+
type: "credit",
|
|
1288
|
+
id: envelope.id,
|
|
1289
|
+
bytes: 0,
|
|
1290
|
+
exact: true
|
|
1291
|
+
} : {
|
|
1292
|
+
type: "credit",
|
|
1293
|
+
id: envelope.id,
|
|
1294
|
+
bytes: 0
|
|
1295
|
+
});
|
|
896
1296
|
let accepted = false;
|
|
897
1297
|
if (onPort) try {
|
|
898
1298
|
accepted = onPort(handle.port, envelope.meta) !== false;
|
|
@@ -907,57 +1307,191 @@ function multiplexPort(port, options) {
|
|
|
907
1307
|
id: envelope.id,
|
|
908
1308
|
reason: "rejected"
|
|
909
1309
|
});
|
|
1310
|
+
return;
|
|
910
1311
|
}
|
|
911
1312
|
return;
|
|
912
1313
|
}
|
|
913
|
-
const
|
|
1314
|
+
const entry = open.get(envelope.id);
|
|
1315
|
+
if (envelope.type === "credit") {
|
|
1316
|
+
const c = entry?.credit;
|
|
1317
|
+
if (!c || !(envelope.bytes >= 0)) return;
|
|
1318
|
+
if (!c.enforced && c.opener) {
|
|
1319
|
+
if (c.exact && envelope.exact !== true) {
|
|
1320
|
+
c.exact = false;
|
|
1321
|
+
c.outstanding = 0;
|
|
1322
|
+
}
|
|
1323
|
+
post({
|
|
1324
|
+
type: "credit",
|
|
1325
|
+
id: envelope.id,
|
|
1326
|
+
bytes: 0
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
c.enforced = true;
|
|
1330
|
+
c.outstanding = Math.max(0, c.outstanding - envelope.bytes);
|
|
1331
|
+
flush(c);
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
914
1334
|
if (envelope.type === "message") {
|
|
915
|
-
|
|
916
|
-
|
|
1335
|
+
const sizeOf = (c) => (c.exact ? byteLengthOf(event.data) : void 0) ?? estimateMessageSize(envelope.payload);
|
|
1336
|
+
if (!entry) return;
|
|
1337
|
+
touch(envelope.id);
|
|
1338
|
+
entry.handle.deliver(envelope.payload, entry.credit ? sizeOf(entry.credit) : void 0);
|
|
917
1339
|
return;
|
|
918
1340
|
}
|
|
919
|
-
if (
|
|
1341
|
+
if (envelope.type === "frame") {
|
|
1342
|
+
if (!entry?.credit?.exact || !codec.decode || !(envelope.data instanceof Uint8Array)) return;
|
|
1343
|
+
touch(envelope.id);
|
|
1344
|
+
const partial = entry.partial ?? {
|
|
1345
|
+
chunks: [],
|
|
1346
|
+
bytes: 0
|
|
1347
|
+
};
|
|
1348
|
+
entry.partial = partial;
|
|
1349
|
+
partial.chunks.push(envelope.data);
|
|
1350
|
+
partial.bytes += envelope.data.byteLength;
|
|
1351
|
+
if (!envelope.last) return;
|
|
1352
|
+
entry.partial = void 0;
|
|
1353
|
+
const whole = new Uint8Array(partial.bytes);
|
|
1354
|
+
let at = 0;
|
|
1355
|
+
for (const chunk of partial.chunks) {
|
|
1356
|
+
whole.set(chunk, at);
|
|
1357
|
+
at += chunk.byteLength;
|
|
1358
|
+
}
|
|
1359
|
+
const inner = codec.decode(whole);
|
|
1360
|
+
if (inner?.type !== "message" || inner.id !== envelope.id) return;
|
|
1361
|
+
entry.handle.deliver(inner.payload, whole.byteLength);
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
dropOutgoing(envelope.id);
|
|
1365
|
+
if (!entry) return;
|
|
920
1366
|
open.delete(envelope.id);
|
|
921
|
-
|
|
1367
|
+
releaseWaiters(entry.credit);
|
|
1368
|
+
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the peer closed the port"));
|
|
922
1369
|
};
|
|
923
1370
|
const listener = (event) => {
|
|
924
1371
|
const envelope = codec.read(event);
|
|
925
|
-
if (envelope) handleEnvelope(envelope);
|
|
1372
|
+
if (envelope) handleEnvelope(envelope, event);
|
|
1373
|
+
};
|
|
1374
|
+
let peerKeepsAlive = false;
|
|
1375
|
+
let heardSincePing = true;
|
|
1376
|
+
let unanswered = 0;
|
|
1377
|
+
let pinger;
|
|
1378
|
+
const tick = () => {
|
|
1379
|
+
if (!keepalive) return;
|
|
1380
|
+
const backlogged = (port.bufferedAmount ?? 0) > 0;
|
|
1381
|
+
if (heardSincePing) unanswered = 0;
|
|
1382
|
+
else if (peerKeepsAlive && !backlogged) unanswered++;
|
|
1383
|
+
if (unanswered >= misses) {
|
|
1384
|
+
close(/* @__PURE__ */ new Error(`webrun-rpc: the peer did not answer ${misses} keepalive pings, ${keepalive.intervalMs} ms apart`)).catch(() => {});
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
heardSincePing = false;
|
|
1388
|
+
post({
|
|
1389
|
+
type: "ping",
|
|
1390
|
+
id: KEEPALIVE_ID
|
|
1391
|
+
});
|
|
1392
|
+
};
|
|
1393
|
+
const close = async (reason) => {
|
|
1394
|
+
if (muxClosed) return;
|
|
1395
|
+
clearInterval(pinger);
|
|
1396
|
+
muxClosed = true;
|
|
1397
|
+
signal?.removeEventListener("abort", onTransportAbort);
|
|
1398
|
+
port.removeEventListener("message", listener);
|
|
1399
|
+
const closedNow = /* @__PURE__ */ new Set();
|
|
1400
|
+
for (const [id, { handle, credit }] of [...open]) {
|
|
1401
|
+
closedNow.add(id);
|
|
1402
|
+
codecPost({
|
|
1403
|
+
type: "close",
|
|
1404
|
+
id
|
|
1405
|
+
});
|
|
1406
|
+
open.delete(id);
|
|
1407
|
+
releaseWaiters(credit);
|
|
1408
|
+
handle.markClosed(reason ?? /* @__PURE__ */ new Error("webrun-rpc: the multiplexer was closed"));
|
|
1409
|
+
}
|
|
1410
|
+
for (const id of outgoing.keys()) if (!closedNow.has(id)) codecPost({
|
|
1411
|
+
type: "close",
|
|
1412
|
+
id
|
|
1413
|
+
});
|
|
1414
|
+
outgoing.clear();
|
|
1415
|
+
ring.length = 0;
|
|
1416
|
+
await port.close?.();
|
|
1417
|
+
};
|
|
1418
|
+
const onTransportAbort = () => {
|
|
1419
|
+
close(signal?.reason).catch(() => {});
|
|
926
1420
|
};
|
|
927
1421
|
port.addEventListener("message", listener);
|
|
928
1422
|
port.start?.();
|
|
1423
|
+
if (keepalive) {
|
|
1424
|
+
pinger = setInterval(tick, keepalive.intervalMs);
|
|
1425
|
+
pinger.unref?.();
|
|
1426
|
+
}
|
|
1427
|
+
if (signal?.aborted) close(signal.reason).catch(() => {});
|
|
1428
|
+
else signal?.addEventListener("abort", onTransportAbort, { once: true });
|
|
929
1429
|
return {
|
|
930
1430
|
maxMessageSize,
|
|
931
|
-
async openPort(meta) {
|
|
1431
|
+
async openPort(meta, { signal: portSignal } = {}) {
|
|
932
1432
|
if (muxClosed) throw new Error("webrun-rpc: the multiplexer is closed");
|
|
933
|
-
if (
|
|
1433
|
+
if (portSignal?.aborted) throw portSignal.reason;
|
|
1434
|
+
if (open.size >= maxPorts) throw new RangeError(`webrun-rpc: maxPorts (${maxPorts}) reached; open ports by kind: ${describeKinds(open.values())}`);
|
|
934
1435
|
while (open.has(nextId)) nextId += 2;
|
|
935
1436
|
const id = nextId;
|
|
936
1437
|
nextId += 2;
|
|
937
|
-
const handle = attach(id);
|
|
938
|
-
post({
|
|
1438
|
+
const handle = attach(id, meta, window, true, exactCodec);
|
|
1439
|
+
post(window ? exactCodec ? {
|
|
1440
|
+
type: "open",
|
|
1441
|
+
id,
|
|
1442
|
+
meta,
|
|
1443
|
+
window,
|
|
1444
|
+
exact: true
|
|
1445
|
+
} : {
|
|
1446
|
+
type: "open",
|
|
1447
|
+
id,
|
|
1448
|
+
meta,
|
|
1449
|
+
window
|
|
1450
|
+
} : {
|
|
939
1451
|
type: "open",
|
|
940
1452
|
id,
|
|
941
1453
|
meta
|
|
942
1454
|
});
|
|
1455
|
+
if (portSignal) {
|
|
1456
|
+
const record = open.get(id)?.handle;
|
|
1457
|
+
const onAbort = () => {
|
|
1458
|
+
if (record && open.get(id)?.handle === record) record.close(portSignal.reason);
|
|
1459
|
+
};
|
|
1460
|
+
portSignal.addEventListener("abort", onAbort, { once: true });
|
|
1461
|
+
getPortCloseSignal(handle.port)?.addEventListener("abort", () => portSignal.removeEventListener("abort", onAbort), { once: true });
|
|
1462
|
+
}
|
|
943
1463
|
return handle.port;
|
|
944
1464
|
},
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
}
|
|
955
|
-
muxClosed = true;
|
|
956
|
-
port.removeEventListener("message", listener);
|
|
957
|
-
await port.close?.();
|
|
1465
|
+
close,
|
|
1466
|
+
ports() {
|
|
1467
|
+
return [...open.values()].map(({ handle: _, credit, ...info }) => credit ? {
|
|
1468
|
+
...info,
|
|
1469
|
+
...credit.enforced ? { window: credit.window } : {},
|
|
1470
|
+
queued: credit.queued,
|
|
1471
|
+
outstanding: credit.outstanding,
|
|
1472
|
+
unconsumed: credit.unconsumed
|
|
1473
|
+
} : info);
|
|
958
1474
|
}
|
|
959
1475
|
};
|
|
960
1476
|
}
|
|
1477
|
+
/** The byte length of a received message's data, or `undefined` if it is not bytes. */
|
|
1478
|
+
function byteLengthOf(data) {
|
|
1479
|
+
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) return data.byteLength;
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* `"http: 1000, ws: 24"`: the three commonest `meta.kind`s among `entries`, so
|
|
1483
|
+
* a full table names who filled it. A port without a kind counts as `"?"`.
|
|
1484
|
+
*/
|
|
1485
|
+
function describeKinds(entries) {
|
|
1486
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1487
|
+
for (const { meta } of entries) {
|
|
1488
|
+
const kind = meta?.kind;
|
|
1489
|
+
const name = typeof kind === "string" ? kind : "?";
|
|
1490
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
1491
|
+
}
|
|
1492
|
+
if (!counts.size) return "none";
|
|
1493
|
+
return [...counts].sort((x, y) => y[1] - x[1]).slice(0, 3).map(([name, n]) => `${name}: ${n}`).join(", ");
|
|
1494
|
+
}
|
|
961
1495
|
//#endregion
|
|
962
1496
|
//#region src/connect-serve.ts
|
|
963
1497
|
/**
|
|
@@ -981,9 +1515,22 @@ const connect = async ({ mux: factory, timeout }) => {
|
|
|
981
1515
|
maxMessageSize: mux.maxMessageSize,
|
|
982
1516
|
timeout
|
|
983
1517
|
};
|
|
984
|
-
const call = (input) =>
|
|
985
|
-
|
|
986
|
-
|
|
1518
|
+
const call = (input) => {
|
|
1519
|
+
let stream;
|
|
1520
|
+
let abandoned = false;
|
|
1521
|
+
return returningAtOnce((async function* () {
|
|
1522
|
+
const port = await mux.openPort(STREAM_META);
|
|
1523
|
+
if (abandoned) {
|
|
1524
|
+
await port.close?.();
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
stream = duplexOverPort(port, streamOptions)(input);
|
|
1528
|
+
yield* stream;
|
|
1529
|
+
})(), () => {
|
|
1530
|
+
abandoned = true;
|
|
1531
|
+
stream?.return(void 0).catch(() => {});
|
|
1532
|
+
});
|
|
1533
|
+
};
|
|
987
1534
|
return {
|
|
988
1535
|
call,
|
|
989
1536
|
async close() {
|
|
@@ -1119,7 +1666,7 @@ function isEnvelope(value) {
|
|
|
1119
1666
|
const candidate = value;
|
|
1120
1667
|
if (typeof candidate.id !== "number") return false;
|
|
1121
1668
|
if (!Number.isInteger(candidate.id) || candidate.id < 0) return false;
|
|
1122
|
-
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close";
|
|
1669
|
+
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close" || candidate.type === "credit" || candidate.type === "frame" || candidate.type === "ping" || candidate.type === "pong";
|
|
1123
1670
|
}
|
|
1124
1671
|
/**
|
|
1125
1672
|
* For ports whose messages are structured values — a real `MessagePort`, a
|
|
@@ -1165,7 +1712,7 @@ const PORT_TRANSFER = "webrun-rpc:port-transfer";
|
|
|
1165
1712
|
*/
|
|
1166
1713
|
function transferPortMux(target, options = {}) {
|
|
1167
1714
|
const { onPort, maxMessageSize } = options;
|
|
1168
|
-
const issued = /* @__PURE__ */ new
|
|
1715
|
+
const issued = /* @__PURE__ */ new Map();
|
|
1169
1716
|
let closed = false;
|
|
1170
1717
|
const listener = (event) => {
|
|
1171
1718
|
if (closed) return;
|
|
@@ -1184,28 +1731,47 @@ function transferPortMux(target, options = {}) {
|
|
|
1184
1731
|
port.close();
|
|
1185
1732
|
return;
|
|
1186
1733
|
}
|
|
1187
|
-
issued.
|
|
1734
|
+
const forget = () => issued.delete(port);
|
|
1735
|
+
issued.set(port, forget);
|
|
1736
|
+
const close = port.close.bind(port);
|
|
1737
|
+
port.close = () => {
|
|
1738
|
+
forget();
|
|
1739
|
+
close();
|
|
1740
|
+
};
|
|
1188
1741
|
};
|
|
1189
1742
|
target.addEventListener("message", listener);
|
|
1190
1743
|
target.start?.();
|
|
1191
1744
|
return {
|
|
1192
1745
|
maxMessageSize,
|
|
1193
|
-
async openPort(meta) {
|
|
1746
|
+
async openPort(meta, { signal } = {}) {
|
|
1194
1747
|
if (closed) throw new Error("webrun-rpc: the multiplexer is closed");
|
|
1748
|
+
if (signal?.aborted) throw signal.reason;
|
|
1195
1749
|
const channel = new MessageChannel();
|
|
1196
1750
|
channel.port1.start();
|
|
1197
1751
|
target.postMessage({
|
|
1198
1752
|
type: PORT_TRANSFER,
|
|
1199
1753
|
meta
|
|
1200
1754
|
}, [channel.port2]);
|
|
1201
|
-
|
|
1202
|
-
|
|
1755
|
+
const port = channel.port1;
|
|
1756
|
+
const onAbort = () => port.close();
|
|
1757
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1758
|
+
const forget = () => {
|
|
1759
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1760
|
+
issued.delete(port);
|
|
1761
|
+
};
|
|
1762
|
+
issued.set(port, forget);
|
|
1763
|
+
const close = port.close.bind(port);
|
|
1764
|
+
port.close = () => {
|
|
1765
|
+
forget();
|
|
1766
|
+
close();
|
|
1767
|
+
};
|
|
1768
|
+
return port;
|
|
1203
1769
|
},
|
|
1204
1770
|
async close() {
|
|
1205
1771
|
if (closed) return;
|
|
1206
1772
|
closed = true;
|
|
1207
1773
|
target.removeEventListener("message", listener);
|
|
1208
|
-
for (const port of issued) try {
|
|
1774
|
+
for (const port of [...issued.keys()]) try {
|
|
1209
1775
|
port.close();
|
|
1210
1776
|
} catch {}
|
|
1211
1777
|
issued.clear();
|
|
@@ -1214,4 +1780,4 @@ function transferPortMux(target, options = {}) {
|
|
|
1214
1780
|
};
|
|
1215
1781
|
}
|
|
1216
1782
|
//#endregion
|
|
1217
|
-
export { CANCEL_CHANNEL_TYPE, DEFAULT_MAX_PORTS, MUX_CLOSED_MESSAGE, NO_TIMEOUT, PORT_TRANSFER, STREAM_ABORT, byteChannelFromMessagePort, callBidi, callPort, connect, duplexOverPort, getPortCloseSignal, ioHandle, ioSend, listenBidi, listenCancelChannel, listenPort, multiplexPort, overPipe, overPorts, postCancelChannel, recieve, send, serve, serveDuplexOverPort, setPortCloseSignal, structuredCodec, transferPortMux };
|
|
1783
|
+
export { CANCEL_CHANNEL_TYPE, DEFAULT_MAX_PORTS, MUX_CLOSED_MESSAGE, NO_TIMEOUT, PORT_TRANSFER, STREAM_ABORT, byteChannelFromMessagePort, callBidi, callPort, connect, duplexOverPort, getPortCloseSignal, ioHandle, ioSend, listenBidi, listenCancelChannel, listenPort, multiplexPort, overPipe, overPorts, portBufferedAmount, postCancelChannel, recieve, send, serve, serveDuplexOverPort, setPortCloseSignal, structuredCodec, transferPortMux, whenPortReady };
|