@actuallab/rpc 12.3.6 → 12.3.29
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/index.cjs +816 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +245 -31
- package/dist/index.d.ts +245 -31
- package/dist/index.js +809 -132
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -23,9 +23,45 @@ var FRAME_DELIMITER = "\n";
|
|
|
23
23
|
|
|
24
24
|
// src/rpc-serialization.ts
|
|
25
25
|
import {
|
|
26
|
-
Encoder,
|
|
26
|
+
Encoder as Encoder2,
|
|
27
27
|
Decoder
|
|
28
28
|
} from "@msgpack/msgpack";
|
|
29
|
+
|
|
30
|
+
// src/msgpack-map-patch.ts
|
|
31
|
+
import { Encoder } from "@msgpack/msgpack";
|
|
32
|
+
function patchMsgpackEncoderForMaps() {
|
|
33
|
+
const proto = Encoder.prototype;
|
|
34
|
+
if (proto._hasMapPatch) return;
|
|
35
|
+
proto._hasMapPatch = true;
|
|
36
|
+
const originalDoEncode = proto.doEncode;
|
|
37
|
+
proto.doEncode = function(object, depth) {
|
|
38
|
+
if (object instanceof Map) {
|
|
39
|
+
_encodeMapInstance.call(this, object, depth);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
originalDoEncode.call(this, object, depth);
|
|
43
|
+
};
|
|
44
|
+
function _encodeMapInstance(m, depth) {
|
|
45
|
+
const self = this;
|
|
46
|
+
const size = m.size;
|
|
47
|
+
if (size < 16) {
|
|
48
|
+
self.writeU8(128 + size);
|
|
49
|
+
} else if (size < 65536) {
|
|
50
|
+
self.writeU8(222);
|
|
51
|
+
self.writeU16(size);
|
|
52
|
+
} else {
|
|
53
|
+
self.writeU8(223);
|
|
54
|
+
self.writeU32(size);
|
|
55
|
+
}
|
|
56
|
+
for (const [key, value] of m) {
|
|
57
|
+
self.doEncode(key, depth + 1);
|
|
58
|
+
self.doEncode(value, depth + 1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
patchMsgpackEncoderForMaps();
|
|
63
|
+
|
|
64
|
+
// src/rpc-serialization.ts
|
|
29
65
|
function serializeMessage(message, args) {
|
|
30
66
|
const envelope = JSON.stringify(message);
|
|
31
67
|
if (args === void 0 || args.length === 0) {
|
|
@@ -66,7 +102,7 @@ function deserializeMessage(raw) {
|
|
|
66
102
|
var textEncoder = new TextEncoder();
|
|
67
103
|
var textDecoder = new TextDecoder();
|
|
68
104
|
var INITIAL_ENCODER_BUFFER_SIZE = 32 * 1024;
|
|
69
|
-
var defaultBinaryEncoder = new
|
|
105
|
+
var defaultBinaryEncoder = new Encoder2(
|
|
70
106
|
void 0,
|
|
71
107
|
void 0,
|
|
72
108
|
void 0,
|
|
@@ -74,7 +110,7 @@ var defaultBinaryEncoder = new Encoder(
|
|
|
74
110
|
);
|
|
75
111
|
var defaultBinaryDecoder = new Decoder();
|
|
76
112
|
function createBinaryEncoder() {
|
|
77
|
-
return new
|
|
113
|
+
return new Encoder2(
|
|
78
114
|
void 0,
|
|
79
115
|
void 0,
|
|
80
116
|
void 0,
|
|
@@ -515,6 +551,18 @@ function createMessageChannelPair() {
|
|
|
515
551
|
}
|
|
516
552
|
|
|
517
553
|
// src/rpc-service-def.ts
|
|
554
|
+
var RpcRemoteExecutionMode = {
|
|
555
|
+
/** Wait for connection if disconnected when the call is made. */
|
|
556
|
+
AwaitForConnection: 1,
|
|
557
|
+
/** Resend on reconnection to the same peer. */
|
|
558
|
+
AllowReconnect: 2,
|
|
559
|
+
/** Resend on reconnection to a different peer (implies AllowReconnect). */
|
|
560
|
+
AllowResend: 6,
|
|
561
|
+
// 4 | AllowReconnect
|
|
562
|
+
/** Default: wait + reconnect + resend. */
|
|
563
|
+
Default: 7
|
|
564
|
+
// AwaitForConnection | AllowResend
|
|
565
|
+
};
|
|
518
566
|
var RpcType = {
|
|
519
567
|
object: /* @__PURE__ */ Symbol("object"),
|
|
520
568
|
stream: /* @__PURE__ */ Symbol("stream"),
|
|
@@ -531,6 +579,7 @@ function defineRpcService(name, methods) {
|
|
|
531
579
|
const cleanName = colonIndex >= 0 ? key.substring(0, colonIndex) : key;
|
|
532
580
|
const wireArgCount = input.wireArgCount ?? input.args.length + 1;
|
|
533
581
|
const mapKey = `${cleanName}:${wireArgCount}`;
|
|
582
|
+
const isNoWait = input.returns === RpcType.noWait;
|
|
534
583
|
map.set(mapKey, {
|
|
535
584
|
name: cleanName,
|
|
536
585
|
serviceName: name,
|
|
@@ -538,7 +587,8 @@ function defineRpcService(name, methods) {
|
|
|
538
587
|
wireArgCount,
|
|
539
588
|
callTypeId: input.callTypeId ?? 0,
|
|
540
589
|
stream: input.returns === RpcType.stream,
|
|
541
|
-
noWait:
|
|
590
|
+
noWait: isNoWait,
|
|
591
|
+
remoteExecutionMode: isNoWait ? 0 : input.remoteExecutionMode ?? RpcRemoteExecutionMode.Default
|
|
542
592
|
});
|
|
543
593
|
}
|
|
544
594
|
return { name, methods: map };
|
|
@@ -546,18 +596,60 @@ function defineRpcService(name, methods) {
|
|
|
546
596
|
|
|
547
597
|
// src/rpc-call-tracker.ts
|
|
548
598
|
import { PromiseSource as PromiseSource2 } from "@actuallab/core";
|
|
599
|
+
|
|
600
|
+
// src/rpc-call-stage.ts
|
|
601
|
+
var RpcCallStage = {
|
|
602
|
+
/** The call's result has been received from the peer. */
|
|
603
|
+
ResultReady: 1,
|
|
604
|
+
/** The compute call's result has been invalidated (compute-call specific). */
|
|
605
|
+
Invalidated: 3,
|
|
606
|
+
/**
|
|
607
|
+
* Bit flag added on top of ResultReady/Invalidated when the outbound
|
|
608
|
+
* call has been removed from the tracker (unregistered).
|
|
609
|
+
*/
|
|
610
|
+
Unregistered: 4096
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// src/rpc-call-tracker.ts
|
|
549
614
|
var RpcOutboundCall = class {
|
|
550
615
|
callId;
|
|
551
616
|
method;
|
|
552
617
|
result = new PromiseSource2();
|
|
618
|
+
/** Bitfield of RpcRemoteExecutionMode flags controlling reconnect/resend behavior. */
|
|
619
|
+
remoteExecutionMode;
|
|
553
620
|
/** Whether to remove this call from the tracker on $sys.Ok. Default: true.
|
|
554
621
|
* Subclasses (e.g. compute calls) override to false to stay in tracker for invalidation. */
|
|
555
622
|
removeOnOk = true;
|
|
556
623
|
/** The serialized wire data — stored for re-sending on reconnect. */
|
|
557
624
|
serializedWireData = "";
|
|
558
|
-
|
|
625
|
+
/**
|
|
626
|
+
* Bitfield of {@link RpcCallStage} flags indicating how far this call
|
|
627
|
+
* has progressed. Used by the `$sys.Reconnect` protocol to tell the
|
|
628
|
+
* remote peer which calls are still pending its attention.
|
|
629
|
+
*
|
|
630
|
+
* - 0: brand new, no result yet (the peer is expected to be actively processing).
|
|
631
|
+
* - `ResultReady` (1): `$sys.Ok` received for this call.
|
|
632
|
+
* - `Invalidated` (3): result was invalidated (compute calls only).
|
|
633
|
+
* - `Unregistered` (0x1000): removed from the tracker.
|
|
634
|
+
*/
|
|
635
|
+
completedStage = 0;
|
|
636
|
+
constructor(callId, method, remoteExecutionMode = 7) {
|
|
559
637
|
this.callId = callId;
|
|
560
638
|
this.method = method;
|
|
639
|
+
this.remoteExecutionMode = remoteExecutionMode;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Returns the stage to report to the remote peer via `$sys.Reconnect`,
|
|
643
|
+
* or `null` if this call should not be reconciled and must be aborted.
|
|
644
|
+
*
|
|
645
|
+
* Mirrors .NET `RpcOutboundCall.GetReconnectStage` at
|
|
646
|
+
* src/ActualLab.Rpc/Infrastructure/RpcOutboundCall.cs.
|
|
647
|
+
*/
|
|
648
|
+
getReconnectStage(isPeerChanged) {
|
|
649
|
+
const mode = this.remoteExecutionMode;
|
|
650
|
+
if (!(mode & RpcRemoteExecutionMode.AllowReconnect)) return null;
|
|
651
|
+
if (isPeerChanged && !(mode & RpcRemoteExecutionMode.AllowResend)) return null;
|
|
652
|
+
return this.completedStage;
|
|
561
653
|
}
|
|
562
654
|
/** Called when the connection is lost. Subclasses can override to resolve invalidation promises. */
|
|
563
655
|
onDisconnect() {
|
|
@@ -580,7 +672,10 @@ var RpcOutboundCallTracker = class {
|
|
|
580
672
|
}
|
|
581
673
|
remove(callId) {
|
|
582
674
|
const call = this._calls.get(callId);
|
|
583
|
-
if (call !== void 0)
|
|
675
|
+
if (call !== void 0) {
|
|
676
|
+
this._calls.delete(callId);
|
|
677
|
+
call.completedStage |= RpcCallStage.Unregistered;
|
|
678
|
+
}
|
|
584
679
|
return call;
|
|
585
680
|
}
|
|
586
681
|
values() {
|
|
@@ -643,6 +738,97 @@ var RpcInboundCallTracker = class {
|
|
|
643
738
|
}
|
|
644
739
|
};
|
|
645
740
|
|
|
741
|
+
// src/increasing-seq-compressor.ts
|
|
742
|
+
var IncreasingSeqCompressor = {
|
|
743
|
+
/**
|
|
744
|
+
* Serialize a non-decreasing sequence of non-negative integers as
|
|
745
|
+
* LEB128-encoded deltas.
|
|
746
|
+
*
|
|
747
|
+
* Throws `RangeError` if the input is not monotonically non-decreasing,
|
|
748
|
+
* or if a value exceeds `Number.MAX_SAFE_INTEGER`.
|
|
749
|
+
*/
|
|
750
|
+
serialize(values) {
|
|
751
|
+
const deltas = [];
|
|
752
|
+
let last = 0;
|
|
753
|
+
let totalBytes = 0;
|
|
754
|
+
for (const value of values) {
|
|
755
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
756
|
+
throw new RangeError(
|
|
757
|
+
`IncreasingSeqCompressor: value must be a non-negative safe integer (got ${value}).`
|
|
758
|
+
);
|
|
759
|
+
const delta = value - last;
|
|
760
|
+
if (delta < 0)
|
|
761
|
+
throw new RangeError(
|
|
762
|
+
`IncreasingSeqCompressor: sequence must be non-decreasing (saw ${value} after ${last}).`
|
|
763
|
+
);
|
|
764
|
+
deltas.push(delta);
|
|
765
|
+
last = value;
|
|
766
|
+
totalBytes += _varUintByteLength(delta);
|
|
767
|
+
}
|
|
768
|
+
const out = new Uint8Array(totalBytes);
|
|
769
|
+
let offset = 0;
|
|
770
|
+
for (const delta of deltas) {
|
|
771
|
+
offset = _writeVarUint(out, delta, offset);
|
|
772
|
+
}
|
|
773
|
+
return out;
|
|
774
|
+
},
|
|
775
|
+
/**
|
|
776
|
+
* Decode bytes produced by {@link serialize} back into the original
|
|
777
|
+
* sorted sequence of non-negative integers.
|
|
778
|
+
*/
|
|
779
|
+
deserialize(data) {
|
|
780
|
+
const result = [];
|
|
781
|
+
let last = 0;
|
|
782
|
+
let offset = 0;
|
|
783
|
+
while (offset < data.length) {
|
|
784
|
+
const [delta, next] = _readVarUint(data, offset);
|
|
785
|
+
offset = next;
|
|
786
|
+
last += delta;
|
|
787
|
+
result.push(last);
|
|
788
|
+
}
|
|
789
|
+
return result;
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
var LOW_BITS = 127;
|
|
793
|
+
var HIGH_BIT = 128;
|
|
794
|
+
function _varUintByteLength(value) {
|
|
795
|
+
let n = 0;
|
|
796
|
+
let v = value;
|
|
797
|
+
while (v >= HIGH_BIT) {
|
|
798
|
+
n++;
|
|
799
|
+
v = Math.floor(v / 128);
|
|
800
|
+
}
|
|
801
|
+
return n + 1;
|
|
802
|
+
}
|
|
803
|
+
function _writeVarUint(out, value, offset) {
|
|
804
|
+
let v = value;
|
|
805
|
+
while (v >= HIGH_BIT) {
|
|
806
|
+
out[offset++] = (HIGH_BIT | v & LOW_BITS) & 255;
|
|
807
|
+
v = Math.floor(v / 128);
|
|
808
|
+
}
|
|
809
|
+
out[offset++] = v & 255;
|
|
810
|
+
return offset;
|
|
811
|
+
}
|
|
812
|
+
function _readVarUint(data, offset) {
|
|
813
|
+
let value = 0;
|
|
814
|
+
let shift = 0;
|
|
815
|
+
while (shift < 63) {
|
|
816
|
+
if (offset >= data.length)
|
|
817
|
+
throw new RangeError("IncreasingSeqCompressor: truncated VarUInt.");
|
|
818
|
+
const b2 = data[offset++];
|
|
819
|
+
value += (b2 & LOW_BITS) * Math.pow(2, shift);
|
|
820
|
+
if (b2 <= LOW_BITS) return [value, offset];
|
|
821
|
+
shift += 7;
|
|
822
|
+
}
|
|
823
|
+
if (offset >= data.length)
|
|
824
|
+
throw new RangeError("IncreasingSeqCompressor: truncated VarUInt.");
|
|
825
|
+
const b = data[offset++];
|
|
826
|
+
if (b > 1)
|
|
827
|
+
throw new RangeError("IncreasingSeqCompressor: VarUInt value exceeds 64 bits.");
|
|
828
|
+
value += b * Math.pow(2, 63);
|
|
829
|
+
return [value, offset];
|
|
830
|
+
}
|
|
831
|
+
|
|
646
832
|
// src/rpc-stream.ts
|
|
647
833
|
import Denque from "denque";
|
|
648
834
|
import { PromiseSource as PromiseSource4 } from "@actuallab/core";
|
|
@@ -651,7 +837,7 @@ import { PromiseSource as PromiseSource4 } from "@actuallab/core";
|
|
|
651
837
|
var RpcObjectKind = { Local: 0, Remote: 1 };
|
|
652
838
|
|
|
653
839
|
// src/rpc-stream-sender.ts
|
|
654
|
-
import { PromiseSource as PromiseSource3 } from "@actuallab/core";
|
|
840
|
+
import { PromiseSource as PromiseSource3, RingBuffer } from "@actuallab/core";
|
|
655
841
|
var DEFAULT_ACK_PERIOD = 256;
|
|
656
842
|
var DEFAULT_ACK_ADVANCE = 128;
|
|
657
843
|
var RpcStreamSender = class _RpcStreamSender {
|
|
@@ -664,13 +850,29 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
664
850
|
peer;
|
|
665
851
|
ackPeriod;
|
|
666
852
|
ackAdvance;
|
|
853
|
+
/**
|
|
854
|
+
* If true, the source was produced by a factory `(abortSignal) => AsyncIterable<T>`
|
|
855
|
+
* and may honor the AbortSignal to exit gracefully. `disconnect()` first aborts
|
|
856
|
+
* the signal and gives the source up to `RpcStream.disconnectGracePeriodMs` ms
|
|
857
|
+
* before force-closing via `iterator.return()`.
|
|
858
|
+
*
|
|
859
|
+
* If false, the source is a plain AsyncIterable that cannot observe the signal,
|
|
860
|
+
* so `disconnect()` force-closes immediately.
|
|
861
|
+
*/
|
|
862
|
+
sourceUsesAbortSignal;
|
|
863
|
+
// -- Pump state --
|
|
864
|
+
/** Next stream index that will be sent (matches .NET's `index`). */
|
|
667
865
|
_nextIndex = 0;
|
|
668
866
|
_ended = false;
|
|
669
867
|
_started = new PromiseSource3();
|
|
868
|
+
/** Highest `nextIndex` observed in an ACK so far (matches .NET's `lastAckNextIndex` idiom). */
|
|
670
869
|
_lastAckedIndex = 0;
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
870
|
+
_abortController = new AbortController();
|
|
871
|
+
_iterator = null;
|
|
872
|
+
// -- ACK queue (analog of .NET's Channel<(long, bool)>) --
|
|
873
|
+
_acks = [];
|
|
874
|
+
_whenAckReady = null;
|
|
875
|
+
constructor(peer, ackPeriod = DEFAULT_ACK_PERIOD, ackAdvance = DEFAULT_ACK_ADVANCE, allowReconnect = true, isRealTime = false, canSkipTo = () => true, sourceUsesAbortSignal = false) {
|
|
674
876
|
const localId = peer.sharedObjects.nextId();
|
|
675
877
|
this.id = { hostId: peer.hub.hubId, localId };
|
|
676
878
|
this.allowReconnect = allowReconnect;
|
|
@@ -679,62 +881,71 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
679
881
|
this.peer = peer;
|
|
680
882
|
this.ackPeriod = ackPeriod;
|
|
681
883
|
this.ackAdvance = ackAdvance;
|
|
884
|
+
this.sourceUsesAbortSignal = sourceUsesAbortSignal;
|
|
885
|
+
}
|
|
886
|
+
/** AbortSignal that is aborted when the sender is disconnected. */
|
|
887
|
+
get abortSignal() {
|
|
888
|
+
return this._abortController.signal;
|
|
682
889
|
}
|
|
683
890
|
/** Returns the stream reference string for the $sys.Ok response. */
|
|
684
891
|
toRef() {
|
|
685
892
|
return `${this.id.hostId},${this.id.localId},${this.ackPeriod},${this.ackAdvance},${this.allowReconnect ? "1" : "0"},${this.isRealTime ? "1" : "0"}`;
|
|
686
893
|
}
|
|
687
|
-
/**
|
|
894
|
+
/**
|
|
895
|
+
* Called by system call handler when $sys.Ack is received from the client.
|
|
896
|
+
*
|
|
897
|
+
* Mirrors .NET `RpcSharedStream<T>.OnAck` at
|
|
898
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:68-101.
|
|
899
|
+
*/
|
|
688
900
|
onAck(nextIndex, hostId) {
|
|
689
901
|
const mustReset = hostId !== "" && hostId !== _RpcStreamSender._emptyGuid;
|
|
690
|
-
if (!this._started.isCompleted)
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
this.
|
|
695
|
-
this._lastAckedIndex = nextIndex;
|
|
696
|
-
this._resetRequested = true;
|
|
697
|
-
} else {
|
|
698
|
-
if (mustReset || nextIndex > this._lastAckedIndex) {
|
|
699
|
-
this._lastAckedIndex = nextIndex;
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
if (this._ackWaiting) {
|
|
703
|
-
this._ackWaiting.resolve();
|
|
704
|
-
this._ackWaiting = null;
|
|
902
|
+
if (!this._started.isCompleted) this._started.resolve();
|
|
903
|
+
this._acks.push({ nextIndex, mustReset });
|
|
904
|
+
if (this._whenAckReady) {
|
|
905
|
+
this._whenAckReady.resolve();
|
|
906
|
+
this._whenAckReady = null;
|
|
705
907
|
}
|
|
706
908
|
}
|
|
707
909
|
/** Called by system call handler when $sys.AckEnd is received from the client. */
|
|
708
910
|
onAckEnd(_hostId) {
|
|
709
|
-
this.
|
|
710
|
-
this.peer.sharedObjects.unregister(this);
|
|
911
|
+
this.disconnect();
|
|
711
912
|
}
|
|
712
|
-
/**
|
|
913
|
+
/**
|
|
914
|
+
* Send a single item to the client at the current `_nextIndex`.
|
|
915
|
+
*
|
|
916
|
+
* Advances `_nextIndex` unconditionally — even if the peer is disconnected
|
|
917
|
+
* the item is considered "sent" from the pump's perspective so the replay
|
|
918
|
+
* buffer indexing stays consistent. If a connection is absent, the wire
|
|
919
|
+
* message is dropped; the item will be resent from the replay buffer
|
|
920
|
+
* after a reconnect (client sends `Ack(N, mustReset=true)`).
|
|
921
|
+
*/
|
|
713
922
|
sendItem(item) {
|
|
714
923
|
if (this._ended) return;
|
|
715
924
|
const conn = this.peer.connection;
|
|
716
|
-
if (
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
925
|
+
if (conn) {
|
|
926
|
+
this.peer.hub.systemCallSender.item(
|
|
927
|
+
conn,
|
|
928
|
+
this.peer.format,
|
|
929
|
+
this.id.localId,
|
|
930
|
+
this._nextIndex,
|
|
931
|
+
item
|
|
932
|
+
);
|
|
933
|
+
}
|
|
724
934
|
this._nextIndex++;
|
|
725
935
|
}
|
|
726
|
-
/** Send a batch of items to the client. */
|
|
936
|
+
/** Send a batch of items to the client. See {@link sendItem} for disconnect semantics. */
|
|
727
937
|
sendBatch(items) {
|
|
728
938
|
if (this._ended || items.length === 0) return;
|
|
729
939
|
const conn = this.peer.connection;
|
|
730
|
-
if (
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
940
|
+
if (conn) {
|
|
941
|
+
this.peer.hub.systemCallSender.batch(
|
|
942
|
+
conn,
|
|
943
|
+
this.peer.format,
|
|
944
|
+
this.id.localId,
|
|
945
|
+
this._nextIndex,
|
|
946
|
+
items
|
|
947
|
+
);
|
|
948
|
+
}
|
|
738
949
|
this._nextIndex += items.length;
|
|
739
950
|
}
|
|
740
951
|
/** Signal stream completion to the client. */
|
|
@@ -754,87 +965,313 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
754
965
|
}
|
|
755
966
|
/**
|
|
756
967
|
* Consume an AsyncIterable and send all items to the client.
|
|
757
|
-
* Waits for the client's initial Ack before starting to pump items.
|
|
758
968
|
*
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
* one where `canSkipTo` returns true.
|
|
969
|
+
* Dispatches to the non-real-time or real-time pump, matching .NET
|
|
970
|
+
* `RpcSharedStream<T>.OnRun` at
|
|
971
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:105-108.
|
|
763
972
|
*/
|
|
764
973
|
async writeFrom(source) {
|
|
765
974
|
await this._started.promise;
|
|
975
|
+
if (this._ended) return;
|
|
766
976
|
const iterator = source[Symbol.asyncIterator]();
|
|
977
|
+
this._iterator = iterator;
|
|
978
|
+
const state = { iteratorDone: false };
|
|
767
979
|
try {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
980
|
+
if (this.isRealTime) {
|
|
981
|
+
await this._runRealTime(iterator, state);
|
|
982
|
+
} else {
|
|
983
|
+
await this._runNonRealTime(iterator, state);
|
|
984
|
+
}
|
|
985
|
+
} finally {
|
|
986
|
+
if (state.iteratorDone && this._iterator === iterator) {
|
|
987
|
+
this._iterator = null;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Non-real-time pump — back-pressure through buffered ACKs.
|
|
993
|
+
*
|
|
994
|
+
* Direct port of .NET `OnRunNonRealTime` at
|
|
995
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:110-217.
|
|
996
|
+
*/
|
|
997
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition -- _ended changes across awaits */
|
|
998
|
+
async _runNonRealTime(iterator, state) {
|
|
999
|
+
const buffer = new RingBuffer(this.ackAdvance + 1);
|
|
1000
|
+
let bufferStart = 0;
|
|
1001
|
+
let isFullyBuffered = false;
|
|
1002
|
+
while (true) {
|
|
1003
|
+
let ack = this._tryProcessAcks();
|
|
1004
|
+
if (!ack) {
|
|
1005
|
+
await this._waitAckReady();
|
|
1006
|
+
if (this._ended) return;
|
|
1007
|
+
ack = this._tryProcessAcks();
|
|
1008
|
+
if (!ack) {
|
|
1009
|
+
return;
|
|
777
1010
|
}
|
|
778
|
-
|
|
779
|
-
|
|
1011
|
+
}
|
|
1012
|
+
this._lastAckedIndex = ack.nextIndex;
|
|
1013
|
+
const shift = _clamp(ack.nextIndex - bufferStart, 0, buffer.count);
|
|
1014
|
+
if (shift > 0) {
|
|
1015
|
+
buffer.moveHead(shift);
|
|
1016
|
+
bufferStart += shift;
|
|
1017
|
+
}
|
|
1018
|
+
if (this._nextIndex < bufferStart) {
|
|
1019
|
+
if (!this._ended)
|
|
1020
|
+
this.sendEnd(new Error("Stream position unavailable."));
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
let bufferIndex = this._nextIndex - bufferStart;
|
|
1024
|
+
const maxIndex = ack.nextIndex + this.ackAdvance;
|
|
1025
|
+
while (this._nextIndex < maxIndex) {
|
|
1026
|
+
if (this._ended) return;
|
|
1027
|
+
if (bufferIndex < buffer.count) {
|
|
1028
|
+
const item = buffer.get(bufferIndex++);
|
|
1029
|
+
if (item.kind === "value") {
|
|
1030
|
+
this.sendItem(item.value);
|
|
1031
|
+
} else if (item.kind === "end") {
|
|
1032
|
+
if (!this._ended) this.sendEnd();
|
|
1033
|
+
return;
|
|
1034
|
+
} else {
|
|
1035
|
+
if (!this._ended) this.sendEnd(item.error);
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
780
1038
|
continue;
|
|
781
1039
|
}
|
|
782
|
-
if (
|
|
783
|
-
|
|
1040
|
+
if (isFullyBuffered) break;
|
|
1041
|
+
if (!buffer.hasRemainingCapacity) break;
|
|
1042
|
+
let pulled;
|
|
1043
|
+
try {
|
|
1044
|
+
const r = await iterator.next();
|
|
784
1045
|
if (this._ended) return;
|
|
785
|
-
|
|
786
|
-
|
|
1046
|
+
if (r.done) {
|
|
1047
|
+
pulled = _endItem;
|
|
1048
|
+
isFullyBuffered = true;
|
|
1049
|
+
state.iteratorDone = true;
|
|
1050
|
+
} else {
|
|
1051
|
+
pulled = { kind: "value", value: r.value };
|
|
1052
|
+
}
|
|
1053
|
+
} catch (e) {
|
|
1054
|
+
state.iteratorDone = true;
|
|
1055
|
+
isFullyBuffered = true;
|
|
1056
|
+
pulled = {
|
|
1057
|
+
kind: "error",
|
|
1058
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
1059
|
+
};
|
|
787
1060
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1061
|
+
buffer.pushTail(pulled);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Real-time pump — drops stale items to stay close to the source's head.
|
|
1067
|
+
*
|
|
1068
|
+
* Direct port of .NET `OnRunRealTime` at
|
|
1069
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:219-420.
|
|
1070
|
+
*/
|
|
1071
|
+
async _runRealTime(iterator, state) {
|
|
1072
|
+
const buffer = new RingBuffer(this.ackAdvance + 1);
|
|
1073
|
+
let bufferStart = 0;
|
|
1074
|
+
let isFullyBuffered = false;
|
|
1075
|
+
while (true) {
|
|
1076
|
+
let ack = this._tryProcessAcks();
|
|
1077
|
+
if (!ack) {
|
|
1078
|
+
await this._waitAckReady();
|
|
1079
|
+
if (this._ended) return;
|
|
1080
|
+
ack = this._tryProcessAcks();
|
|
1081
|
+
if (!ack) return;
|
|
1082
|
+
}
|
|
1083
|
+
this._lastAckedIndex = ack.nextIndex;
|
|
1084
|
+
if (ack.mustReset && !isFullyBuffered) {
|
|
1085
|
+
buffer.clear();
|
|
1086
|
+
bufferStart = this._nextIndex;
|
|
1087
|
+
while (true) {
|
|
791
1088
|
if (this._ended) return;
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1089
|
+
let item;
|
|
1090
|
+
let accepted = false;
|
|
1091
|
+
try {
|
|
1092
|
+
const r = await iterator.next();
|
|
1093
|
+
if (this._ended) return;
|
|
1094
|
+
if (r.done) {
|
|
1095
|
+
item = _endItem;
|
|
1096
|
+
isFullyBuffered = true;
|
|
1097
|
+
state.iteratorDone = true;
|
|
1098
|
+
} else if (this.canSkipTo(r.value)) {
|
|
1099
|
+
item = { kind: "value", value: r.value };
|
|
1100
|
+
accepted = true;
|
|
1101
|
+
} else {
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
} catch (e) {
|
|
1105
|
+
state.iteratorDone = true;
|
|
1106
|
+
isFullyBuffered = true;
|
|
1107
|
+
item = {
|
|
1108
|
+
kind: "error",
|
|
1109
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
1110
|
+
};
|
|
799
1111
|
}
|
|
1112
|
+
buffer.pushTail(item);
|
|
1113
|
+
if (accepted || isFullyBuffered) break;
|
|
800
1114
|
}
|
|
1115
|
+
}
|
|
1116
|
+
const shift = _clamp(ack.nextIndex - bufferStart, 0, buffer.count);
|
|
1117
|
+
if (shift > 0) {
|
|
1118
|
+
buffer.moveHead(shift);
|
|
1119
|
+
bufferStart += shift;
|
|
1120
|
+
}
|
|
1121
|
+
if (this._nextIndex < bufferStart) {
|
|
1122
|
+
if (!this._ended)
|
|
1123
|
+
this.sendEnd(new Error("Stream position unavailable."));
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
let bufferIndex = this._nextIndex - bufferStart;
|
|
1127
|
+
const maxIndex = ack.nextIndex + this.ackAdvance;
|
|
1128
|
+
while (this._nextIndex < maxIndex) {
|
|
801
1129
|
if (this._ended) return;
|
|
802
|
-
if (
|
|
803
|
-
|
|
1130
|
+
if (this._acks.length > 0) break;
|
|
1131
|
+
if (bufferIndex < buffer.count) {
|
|
1132
|
+
const item = buffer.get(bufferIndex++);
|
|
1133
|
+
if (item.kind === "value") {
|
|
1134
|
+
this.sendItem(item.value);
|
|
1135
|
+
} else if (item.kind === "end") {
|
|
1136
|
+
if (!this._ended) this.sendEnd();
|
|
1137
|
+
return;
|
|
1138
|
+
} else {
|
|
1139
|
+
if (!this._ended) this.sendEnd(item.error);
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
continue;
|
|
804
1143
|
}
|
|
805
|
-
if (
|
|
806
|
-
|
|
807
|
-
|
|
1144
|
+
if (isFullyBuffered) break;
|
|
1145
|
+
if (!buffer.hasRemainingCapacity) break;
|
|
1146
|
+
let pulled;
|
|
1147
|
+
try {
|
|
1148
|
+
const r = await iterator.next();
|
|
1149
|
+
if (this._ended) return;
|
|
1150
|
+
if (r.done) {
|
|
1151
|
+
pulled = _endItem;
|
|
1152
|
+
isFullyBuffered = true;
|
|
1153
|
+
state.iteratorDone = true;
|
|
1154
|
+
} else {
|
|
1155
|
+
pulled = { kind: "value", value: r.value };
|
|
1156
|
+
}
|
|
1157
|
+
} catch (e) {
|
|
1158
|
+
state.iteratorDone = true;
|
|
1159
|
+
isFullyBuffered = true;
|
|
1160
|
+
pulled = {
|
|
1161
|
+
kind: "error",
|
|
1162
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
1163
|
+
};
|
|
808
1164
|
}
|
|
1165
|
+
buffer.pushTail(pulled);
|
|
809
1166
|
}
|
|
810
|
-
if (!this._ended) {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
1167
|
+
if (this._nextIndex >= maxIndex && !isFullyBuffered && !this._ended) {
|
|
1168
|
+
let latestValue;
|
|
1169
|
+
let haveLatest = false;
|
|
1170
|
+
let exitReason = "ack";
|
|
1171
|
+
let exitError = null;
|
|
1172
|
+
drain: while (!this._ended) {
|
|
1173
|
+
if (this._acks.length > 0) {
|
|
1174
|
+
exitReason = "ack";
|
|
1175
|
+
break drain;
|
|
1176
|
+
}
|
|
1177
|
+
let r;
|
|
1178
|
+
try {
|
|
1179
|
+
r = await iterator.next();
|
|
1180
|
+
} catch (e) {
|
|
1181
|
+
state.iteratorDone = true;
|
|
1182
|
+
isFullyBuffered = true;
|
|
1183
|
+
exitReason = "error";
|
|
1184
|
+
exitError = e instanceof Error ? e : new Error(String(e));
|
|
1185
|
+
break drain;
|
|
1186
|
+
}
|
|
1187
|
+
if (this._ended) return;
|
|
1188
|
+
if (r.done) {
|
|
1189
|
+
state.iteratorDone = true;
|
|
1190
|
+
isFullyBuffered = true;
|
|
1191
|
+
exitReason = "end";
|
|
1192
|
+
break drain;
|
|
1193
|
+
}
|
|
1194
|
+
if (this.canSkipTo(r.value)) {
|
|
1195
|
+
latestValue = r.value;
|
|
1196
|
+
haveLatest = true;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
if (exitReason === "end" || exitReason === "error") {
|
|
1200
|
+
if (haveLatest && !this._ended) this.sendItem(latestValue);
|
|
1201
|
+
if (!this._ended) this.sendEnd(exitError);
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
if (haveLatest) {
|
|
1205
|
+
const wasFull = buffer.isFull;
|
|
1206
|
+
buffer.pushTailAndMoveHeadIfFull({ kind: "value", value: latestValue });
|
|
1207
|
+
if (wasFull) bufferStart++;
|
|
1208
|
+
}
|
|
816
1209
|
}
|
|
817
1210
|
}
|
|
818
1211
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1212
|
+
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
|
1213
|
+
/** Drain all queued ACKs, returning the most recent one (or null if none). */
|
|
1214
|
+
_tryProcessAcks() {
|
|
1215
|
+
if (this._acks.length === 0) return null;
|
|
1216
|
+
let last = null;
|
|
1217
|
+
while (this._acks.length > 0) {
|
|
1218
|
+
const a = this._acks.shift();
|
|
1219
|
+
last = a;
|
|
1220
|
+
if (a.mustReset || this._nextIndex < a.nextIndex) {
|
|
1221
|
+
this._nextIndex = a.nextIndex;
|
|
1222
|
+
}
|
|
825
1223
|
}
|
|
1224
|
+
return last;
|
|
1225
|
+
}
|
|
1226
|
+
/** Wait for the ACK queue to have at least one entry (or `_ended`). */
|
|
1227
|
+
async _waitAckReady() {
|
|
1228
|
+
if (this._acks.length > 0 || this._ended) return;
|
|
1229
|
+
this._whenAckReady = new PromiseSource3();
|
|
1230
|
+
await this._whenAckReady.promise;
|
|
1231
|
+
this._whenAckReady = null;
|
|
826
1232
|
}
|
|
827
1233
|
// -- IRpcObject --
|
|
828
1234
|
reconnect() {
|
|
829
1235
|
}
|
|
830
1236
|
disconnect() {
|
|
1237
|
+
if (this._ended) return;
|
|
831
1238
|
this._ended = true;
|
|
1239
|
+
this._abortController.abort();
|
|
832
1240
|
if (!this._started.isCompleted) {
|
|
833
1241
|
this._started.resolve();
|
|
834
1242
|
}
|
|
1243
|
+
if (this._whenAckReady) {
|
|
1244
|
+
this._whenAckReady.resolve();
|
|
1245
|
+
this._whenAckReady = null;
|
|
1246
|
+
}
|
|
835
1247
|
this.peer.sharedObjects.unregister(this);
|
|
1248
|
+
const iterator = this._iterator;
|
|
1249
|
+
if (!iterator) return;
|
|
1250
|
+
if (!this.sourceUsesAbortSignal) {
|
|
1251
|
+
this._iterator = null;
|
|
1252
|
+
this._forceCloseIterator(iterator);
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
setTimeout(() => {
|
|
1256
|
+
if (this._iterator === iterator) {
|
|
1257
|
+
this._iterator = null;
|
|
1258
|
+
this._forceCloseIterator(iterator);
|
|
1259
|
+
}
|
|
1260
|
+
}, RpcStream.disconnectGracePeriodMs);
|
|
1261
|
+
}
|
|
1262
|
+
_forceCloseIterator(iterator) {
|
|
1263
|
+
try {
|
|
1264
|
+
const ret = iterator.return?.(void 0);
|
|
1265
|
+
if (ret) void ret.catch(() => {
|
|
1266
|
+
});
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
836
1269
|
}
|
|
837
1270
|
};
|
|
1271
|
+
var _endItem = { kind: "end" };
|
|
1272
|
+
function _clamp(value, min, max) {
|
|
1273
|
+
return value < min ? min : value > max ? max : value;
|
|
1274
|
+
}
|
|
838
1275
|
|
|
839
1276
|
// src/rpc-stream.ts
|
|
840
1277
|
var DEFAULT_ACK_PERIOD2 = 30;
|
|
@@ -870,6 +1307,15 @@ function parseStreamRef(value) {
|
|
|
870
1307
|
return null;
|
|
871
1308
|
}
|
|
872
1309
|
var RpcStream = class _RpcStream {
|
|
1310
|
+
/**
|
|
1311
|
+
* Grace period (ms) given to a factory-form local source to honor its
|
|
1312
|
+
* AbortSignal and exit naturally before the sender force-closes the
|
|
1313
|
+
* iterator via `iterator.return()`.
|
|
1314
|
+
*
|
|
1315
|
+
* Only applies to sources provided as `(abortSignal) => AsyncIterable<T>`.
|
|
1316
|
+
* Plain `AsyncIterable<T>` sources are force-closed immediately on disconnect.
|
|
1317
|
+
*/
|
|
1318
|
+
static disconnectGracePeriodMs = 100;
|
|
873
1319
|
id;
|
|
874
1320
|
kind;
|
|
875
1321
|
allowReconnect;
|
|
@@ -896,7 +1342,7 @@ var RpcStream = class _RpcStream {
|
|
|
896
1342
|
_iterating = false;
|
|
897
1343
|
_ackSentUpTo = 0;
|
|
898
1344
|
constructor(sourceOrRef, optionsOrPeer) {
|
|
899
|
-
if (
|
|
1345
|
+
if (_isStreamSource(sourceOrRef)) {
|
|
900
1346
|
const options = optionsOrPeer ?? {};
|
|
901
1347
|
this.kind = RpcObjectKind.Local;
|
|
902
1348
|
this.localSource = sourceOrRef;
|
|
@@ -947,17 +1393,20 @@ var RpcStream = class _RpcStream {
|
|
|
947
1393
|
throw new Error("toRef() was already called with a different peer.");
|
|
948
1394
|
return this._toRefValue;
|
|
949
1395
|
}
|
|
1396
|
+
const isFactory = typeof this.localSource === "function";
|
|
950
1397
|
const sender = new RpcStreamSender(
|
|
951
1398
|
peer,
|
|
952
1399
|
this.ackPeriod,
|
|
953
1400
|
this.ackAdvance,
|
|
954
1401
|
this.allowReconnect,
|
|
955
1402
|
this.isRealTime,
|
|
956
|
-
this.canSkipTo
|
|
1403
|
+
this.canSkipTo,
|
|
1404
|
+
isFactory
|
|
957
1405
|
);
|
|
958
1406
|
peer.sharedObjects.register(sender);
|
|
959
1407
|
this._sender = sender;
|
|
960
|
-
|
|
1408
|
+
const source = isFactory ? this.localSource(sender.abortSignal) : this.localSource;
|
|
1409
|
+
this._whenSent = sender.writeFrom(source);
|
|
961
1410
|
this._toRefValue = peer.format.isBinary ? {
|
|
962
1411
|
SerializedId: [sender.id.hostId, sender.id.localId],
|
|
963
1412
|
AckPeriod: sender.ackPeriod,
|
|
@@ -1037,7 +1486,10 @@ var RpcStream = class _RpcStream {
|
|
|
1037
1486
|
}
|
|
1038
1487
|
}
|
|
1039
1488
|
disconnect() {
|
|
1040
|
-
if (this.kind === RpcObjectKind.Local)
|
|
1489
|
+
if (this.kind === RpcObjectKind.Local) {
|
|
1490
|
+
this._sender?.disconnect();
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1041
1493
|
if (this._completed) return;
|
|
1042
1494
|
this._completed = true;
|
|
1043
1495
|
this._completionError = new Error("Peer disconnected.");
|
|
@@ -1046,7 +1498,8 @@ var RpcStream = class _RpcStream {
|
|
|
1046
1498
|
// -- AsyncIterable --
|
|
1047
1499
|
[Symbol.asyncIterator]() {
|
|
1048
1500
|
if (this.kind === RpcObjectKind.Local) {
|
|
1049
|
-
|
|
1501
|
+
const source = typeof this.localSource === "function" ? this.localSource(new AbortController().signal) : this.localSource;
|
|
1502
|
+
return source[Symbol.asyncIterator]();
|
|
1050
1503
|
}
|
|
1051
1504
|
if (this._iterating)
|
|
1052
1505
|
throw new Error("RpcStream can only be iterated once.");
|
|
@@ -1127,7 +1580,8 @@ var RpcStream = class _RpcStream {
|
|
|
1127
1580
|
}
|
|
1128
1581
|
}
|
|
1129
1582
|
};
|
|
1130
|
-
function
|
|
1583
|
+
function _isStreamSource(value) {
|
|
1584
|
+
if (typeof value === "function") return true;
|
|
1131
1585
|
return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
|
|
1132
1586
|
}
|
|
1133
1587
|
function resolveStreamRefs(value, peer) {
|
|
@@ -1161,6 +1615,25 @@ function resolveStreamRefs(value, peer) {
|
|
|
1161
1615
|
return value;
|
|
1162
1616
|
}
|
|
1163
1617
|
|
|
1618
|
+
// src/base64.ts
|
|
1619
|
+
function base64Encode(bytes) {
|
|
1620
|
+
const chunkSize = 32768;
|
|
1621
|
+
let binary = "";
|
|
1622
|
+
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
1623
|
+
binary += String.fromCharCode.apply(
|
|
1624
|
+
null,
|
|
1625
|
+
bytes.subarray(i, Math.min(bytes.length, i + chunkSize))
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
return btoa(binary);
|
|
1629
|
+
}
|
|
1630
|
+
function base64Decode(encoded) {
|
|
1631
|
+
const binary = atob(encoded);
|
|
1632
|
+
const out = new Uint8Array(binary.length);
|
|
1633
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
1634
|
+
return out;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1164
1637
|
// src/rpc-system-call-handler.ts
|
|
1165
1638
|
var RpcSystemCallHandler = class {
|
|
1166
1639
|
handle(message, args, peer) {
|
|
@@ -1170,6 +1643,7 @@ var RpcSystemCallHandler = class {
|
|
|
1170
1643
|
case RpcSystemCalls.ok: {
|
|
1171
1644
|
const call = peer.outbound.get(relatedId);
|
|
1172
1645
|
if (call !== void 0) {
|
|
1646
|
+
call.completedStage |= RpcCallStage.ResultReady;
|
|
1173
1647
|
if (call.removeOnOk) {
|
|
1174
1648
|
peer.outbound.remove(relatedId);
|
|
1175
1649
|
}
|
|
@@ -1177,6 +1651,10 @@ var RpcSystemCallHandler = class {
|
|
|
1177
1651
|
}
|
|
1178
1652
|
break;
|
|
1179
1653
|
}
|
|
1654
|
+
case RpcSystemCalls.reconnect: {
|
|
1655
|
+
this._handleReconnect(relatedId, args, peer);
|
|
1656
|
+
break;
|
|
1657
|
+
}
|
|
1180
1658
|
case RpcSystemCalls.error: {
|
|
1181
1659
|
const call = peer.outbound.remove(relatedId);
|
|
1182
1660
|
if (call !== void 0) {
|
|
@@ -1265,7 +1743,54 @@ var RpcSystemCallHandler = class {
|
|
|
1265
1743
|
}
|
|
1266
1744
|
}
|
|
1267
1745
|
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Server-side handler for the `$sys.Reconnect` call. Parses the peer's
|
|
1748
|
+
* report of in-flight call IDs (grouped by stage, IncreasingSeqCompressor-
|
|
1749
|
+
* encoded, base64-wrapped for JSON transport), computes the subset this
|
|
1750
|
+
* peer does not recognize, and replies with a compressed byte[] of the
|
|
1751
|
+
* unknown IDs wrapped in `$sys.Ok`.
|
|
1752
|
+
*
|
|
1753
|
+
* @param relatedId The outbound call ID the client used for this
|
|
1754
|
+
* `$sys.Reconnect` invocation — we send `$sys.Ok` back
|
|
1755
|
+
* with the same `RelatedId`.
|
|
1756
|
+
*/
|
|
1757
|
+
_handleReconnect(relatedId, args, peer) {
|
|
1758
|
+
if (peer.connection === void 0) return;
|
|
1759
|
+
let unknownIds = [];
|
|
1760
|
+
try {
|
|
1761
|
+
const stagesRaw = args[1];
|
|
1762
|
+
if (stagesRaw && typeof stagesRaw === "object" && !Array.isArray(stagesRaw)) {
|
|
1763
|
+
const unknownSet = /* @__PURE__ */ new Set();
|
|
1764
|
+
for (const [, rawValue] of Object.entries(stagesRaw)) {
|
|
1765
|
+
const bytes = _toBytes(rawValue);
|
|
1766
|
+
if (bytes === null) continue;
|
|
1767
|
+
const callIds = IncreasingSeqCompressor.deserialize(bytes);
|
|
1768
|
+
for (const callId of callIds) {
|
|
1769
|
+
if (peer.inbound.get(callId) === void 0) {
|
|
1770
|
+
unknownSet.add(callId);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
unknownIds = [...unknownSet].sort((a, b) => a - b);
|
|
1775
|
+
}
|
|
1776
|
+
} catch {
|
|
1777
|
+
}
|
|
1778
|
+
const responseBytes = IncreasingSeqCompressor.serialize(unknownIds);
|
|
1779
|
+
const responseValue = peer.format.isBinary ? responseBytes : base64Encode(responseBytes);
|
|
1780
|
+
peer.hub.systemCallSender.ok(peer.connection, peer.format, relatedId, responseValue);
|
|
1781
|
+
}
|
|
1268
1782
|
};
|
|
1783
|
+
function _toBytes(value) {
|
|
1784
|
+
if (value instanceof Uint8Array) return value;
|
|
1785
|
+
if (typeof value === "string") {
|
|
1786
|
+
try {
|
|
1787
|
+
return base64Decode(value);
|
|
1788
|
+
} catch {
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
return null;
|
|
1793
|
+
}
|
|
1269
1794
|
|
|
1270
1795
|
// src/rpc-system-call-sender.ts
|
|
1271
1796
|
var RpcSystemCallSender = class _RpcSystemCallSender {
|
|
@@ -1656,13 +2181,21 @@ var RpcPeer = class {
|
|
|
1656
2181
|
this._startKeepAlive();
|
|
1657
2182
|
});
|
|
1658
2183
|
}
|
|
1659
|
-
/**
|
|
2184
|
+
/**
|
|
2185
|
+
* The serialization format used by this peer for outgoing messages.
|
|
2186
|
+
* If not set explicitly, defaults to the resolver's default (JSON /
|
|
2187
|
+
* json5np). Subclasses can pre-assign in their constructor.
|
|
2188
|
+
*/
|
|
2189
|
+
_format;
|
|
1660
2190
|
get format() {
|
|
1661
|
-
return RpcSerializationFormatResolver.Default.defaultFormat;
|
|
2191
|
+
return this._format ?? RpcSerializationFormatResolver.Default.defaultFormat;
|
|
2192
|
+
}
|
|
2193
|
+
set format(value) {
|
|
2194
|
+
this._format = value;
|
|
1662
2195
|
}
|
|
1663
2196
|
call(method, args, options) {
|
|
1664
2197
|
const callId = this.outbound.nextId();
|
|
1665
|
-
const outboundCall = options?.outboundCallFactory ? options.outboundCallFactory(callId, method) : new RpcOutboundCall(callId, method);
|
|
2198
|
+
const outboundCall = options?.outboundCallFactory ? options.outboundCallFactory(callId, method) : new RpcOutboundCall(callId, method, options?.remoteExecutionMode ?? 7);
|
|
1666
2199
|
const envelope = {
|
|
1667
2200
|
Method: method,
|
|
1668
2201
|
RelatedId: callId,
|
|
@@ -1767,8 +2300,16 @@ var RpcPeer = class {
|
|
|
1767
2300
|
}
|
|
1768
2301
|
const method = message.Method ?? "";
|
|
1769
2302
|
if (method === RpcSystemCalls.handshake) {
|
|
1770
|
-
const
|
|
1771
|
-
if (
|
|
2303
|
+
const raw = args[0];
|
|
2304
|
+
if (raw !== void 0) {
|
|
2305
|
+
const handshake = Array.isArray(raw) ? {
|
|
2306
|
+
RemotePeerId: raw[0],
|
|
2307
|
+
RemoteHubId: raw[2],
|
|
2308
|
+
ProtocolVersion: raw[3],
|
|
2309
|
+
Index: raw[4]
|
|
2310
|
+
} : raw;
|
|
2311
|
+
this._onHandshakeReceived(handshake);
|
|
2312
|
+
}
|
|
1772
2313
|
return;
|
|
1773
2314
|
}
|
|
1774
2315
|
if (method.startsWith("$sys")) {
|
|
@@ -1858,6 +2399,12 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
1858
2399
|
_connectionKind = 0 /* Disconnected */;
|
|
1859
2400
|
_tryIndex = 0;
|
|
1860
2401
|
_handshakeIndex = 0;
|
|
2402
|
+
/** Index from the MOST RECENTLY RECEIVED server handshake — sent back
|
|
2403
|
+
* to the server in `$sys.Reconnect:3` so the server can verify we're
|
|
2404
|
+
* talking about the current connection. Mirrors .NET's
|
|
2405
|
+
* `connectionState.Handshake.Index` path in
|
|
2406
|
+
* `RpcOutboundCallTracker.TryReconnect`. */
|
|
2407
|
+
_remoteHandshakeIndex = 0;
|
|
1861
2408
|
_lastRemoteHubId;
|
|
1862
2409
|
_pendingHandshake;
|
|
1863
2410
|
_reconnectsAt = 0;
|
|
@@ -1884,24 +2431,24 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
1884
2431
|
this._reconnectsAt = value;
|
|
1885
2432
|
this.reconnectsAtChanged.trigger();
|
|
1886
2433
|
}
|
|
1887
|
-
_format;
|
|
1888
2434
|
constructor(hub, url, serializationFormat) {
|
|
1889
2435
|
super(url, hub);
|
|
1890
2436
|
this.clientId = guidToBase64Url(this.id);
|
|
1891
2437
|
const resolver = RpcSerializationFormatResolver.Default;
|
|
1892
2438
|
this.serializationFormat = serializationFormat ?? resolver.defaultFormatKey;
|
|
1893
|
-
this.
|
|
1894
|
-
}
|
|
1895
|
-
get format() {
|
|
1896
|
-
return this._format;
|
|
2439
|
+
this.format = resolver.get(this.serializationFormat);
|
|
1897
2440
|
}
|
|
1898
2441
|
get connectionKind() {
|
|
1899
2442
|
return this._connectionKind;
|
|
1900
2443
|
}
|
|
1901
|
-
/** One-shot connection for tests — no reconnection loop, no handshake.
|
|
1902
|
-
|
|
2444
|
+
/** One-shot connection for tests — no reconnection loop, no handshake.
|
|
2445
|
+
* @param isPeerChanged Whether to treat this as a peer-identity change
|
|
2446
|
+
* (default `true` — no handshake was exchanged, so stage-3 compute calls
|
|
2447
|
+
* self-invalidate and remote/shared objects are disposed). Pass `false`
|
|
2448
|
+
* to simulate a same-peer reconnect in tests. */
|
|
2449
|
+
connectWith(conn, isPeerChanged = true) {
|
|
1903
2450
|
this.setupConnection(conn);
|
|
1904
|
-
this._reconnect(
|
|
2451
|
+
void this._reconnect(isPeerChanged);
|
|
1905
2452
|
}
|
|
1906
2453
|
/** Start the reconnection loop — runs until disposed. */
|
|
1907
2454
|
async run(wsFactory) {
|
|
@@ -1945,6 +2492,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
1945
2492
|
closedRejection
|
|
1946
2493
|
]);
|
|
1947
2494
|
this._pendingHandshake = void 0;
|
|
2495
|
+
this._remoteHandshakeIndex = remoteHandshake.Index ?? 0;
|
|
1948
2496
|
const remoteHubId = remoteHandshake.RemoteHubId;
|
|
1949
2497
|
let isPeerChanged = false;
|
|
1950
2498
|
if (remoteHubId !== void 0) {
|
|
@@ -1958,7 +2506,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
1958
2506
|
this._connection = conn;
|
|
1959
2507
|
this._connectionKind = 2 /* Connected */;
|
|
1960
2508
|
this._tryIndex = 0;
|
|
1961
|
-
this._reconnect(isPeerChanged);
|
|
2509
|
+
await this._reconnect(isPeerChanged);
|
|
1962
2510
|
await new Promise((r) => conn.closed.add(() => r()));
|
|
1963
2511
|
} catch {
|
|
1964
2512
|
}
|
|
@@ -1983,31 +2531,129 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
1983
2531
|
}
|
|
1984
2532
|
}
|
|
1985
2533
|
}
|
|
1986
|
-
/**
|
|
1987
|
-
*
|
|
1988
|
-
*
|
|
1989
|
-
*
|
|
1990
|
-
*
|
|
1991
|
-
*
|
|
1992
|
-
*
|
|
1993
|
-
|
|
2534
|
+
/**
|
|
2535
|
+
* Reconcile outbound calls after reconnection, then flush pending sends.
|
|
2536
|
+
*
|
|
2537
|
+
* On peer change (different server hubId) or with binary wire format —
|
|
2538
|
+
* we blind-resend every eligible call.
|
|
2539
|
+
*
|
|
2540
|
+
* On a same-peer reconnect with JSON transport, first call
|
|
2541
|
+
* `$sys.Reconnect` to ask the server which call IDs it no longer
|
|
2542
|
+
* recognizes (e.g. inbound tracker was cleared, or the call never
|
|
2543
|
+
* reached the server). Only those get resent; the rest stay in the
|
|
2544
|
+
* tracker awaiting their $sys.Ok. This prevents duplicate server-side
|
|
2545
|
+
* invocation of in-flight streaming calls on every transient
|
|
2546
|
+
* reconnect — mirrors .NET `RpcOutboundCallTracker.Reconnect` at
|
|
2547
|
+
* src/ActualLab.Rpc/Infrastructure/RpcCallTrackers.cs:209-279.
|
|
2548
|
+
*
|
|
2549
|
+
* Stage-3 compute calls (result already resolved, awaiting invalidation)
|
|
2550
|
+
* are always self-invalidated — TS doesn't track compute-call stages
|
|
2551
|
+
* across reconnect, so the safest behavior is to force a fresh compute.
|
|
2552
|
+
*/
|
|
2553
|
+
async _reconnect(isPeerChanged) {
|
|
1994
2554
|
if (this._connection === void 0) return;
|
|
1995
|
-
if (
|
|
2555
|
+
if (isPeerChanged) {
|
|
1996
2556
|
this.remoteObjects.disconnectAll();
|
|
2557
|
+
this.sharedObjects.disconnectAll();
|
|
1997
2558
|
} else {
|
|
1998
2559
|
this.remoteObjects.reconnectAll();
|
|
1999
2560
|
}
|
|
2000
|
-
const
|
|
2001
|
-
for (const call of
|
|
2561
|
+
const eligible = [];
|
|
2562
|
+
for (const call of [...this.outbound.values()]) {
|
|
2563
|
+
const mode = call.remoteExecutionMode;
|
|
2564
|
+
if (!(mode & 2)) {
|
|
2565
|
+
if (!call.result.isCompleted)
|
|
2566
|
+
call.result.reject(new Error(
|
|
2567
|
+
"Outbound call failed: disconnected and AllowReconnect is not set."
|
|
2568
|
+
));
|
|
2569
|
+
call.onDisconnect();
|
|
2570
|
+
this.outbound.remove(call.callId);
|
|
2571
|
+
continue;
|
|
2572
|
+
}
|
|
2573
|
+
if (isPeerChanged && !(mode & 4)) {
|
|
2574
|
+
if (!call.result.isCompleted)
|
|
2575
|
+
call.result.reject(new Error(
|
|
2576
|
+
"Outbound call failed: reconnected to a different peer and AllowResend is not set."
|
|
2577
|
+
));
|
|
2578
|
+
call.onDisconnect();
|
|
2579
|
+
this.outbound.remove(call.callId);
|
|
2580
|
+
continue;
|
|
2581
|
+
}
|
|
2582
|
+
eligible.push(call);
|
|
2583
|
+
}
|
|
2584
|
+
let unknownIds = null;
|
|
2585
|
+
if (!isPeerChanged && eligible.length > 0) {
|
|
2586
|
+
unknownIds = await this._reconcileReconnect(eligible);
|
|
2587
|
+
}
|
|
2588
|
+
for (const call of eligible) {
|
|
2002
2589
|
if (!call.removeOnOk && call.result.isCompleted) {
|
|
2003
2590
|
call.onDisconnect();
|
|
2004
2591
|
this.outbound.remove(call.callId);
|
|
2005
|
-
|
|
2006
|
-
|
|
2592
|
+
continue;
|
|
2593
|
+
}
|
|
2594
|
+
if (unknownIds !== null && !unknownIds.has(call.callId)) {
|
|
2595
|
+
continue;
|
|
2007
2596
|
}
|
|
2597
|
+
this._sendWireData(call.serializedWireData);
|
|
2008
2598
|
}
|
|
2009
2599
|
this._flushPendingSends();
|
|
2010
2600
|
}
|
|
2601
|
+
/**
|
|
2602
|
+
* Ask the server which call IDs in `eligible` it no longer recognizes
|
|
2603
|
+
* via `$sys.Reconnect:3`. Returns the set of call IDs that need to be
|
|
2604
|
+
* resent. On any failure, falls back to "resend everything".
|
|
2605
|
+
*
|
|
2606
|
+
* Wire format of the `completedStages` argument is chosen per wire format:
|
|
2607
|
+
* - JSON (json5np): plain object `{"<stage>": "<base64-of-bytes>"}` —
|
|
2608
|
+
* matches .NET `System.Text.Json`'s default for `Dictionary<int, byte[]>`.
|
|
2609
|
+
* - MessagePack (msgpack6/msgpack6c): `Map<number, Uint8Array>` —
|
|
2610
|
+
* the patched encoder (see `./msgpack-map-patch.ts`) writes this as a
|
|
2611
|
+
* native msgpack `map<int, bin>`, matching .NET MessagePack-CSharp's
|
|
2612
|
+
* default for `Dictionary<int, byte[]>`.
|
|
2613
|
+
*/
|
|
2614
|
+
async _reconcileReconnect(eligible) {
|
|
2615
|
+
try {
|
|
2616
|
+
const byStage = /* @__PURE__ */ new Map();
|
|
2617
|
+
for (const call of eligible) {
|
|
2618
|
+
const stage = call.getReconnectStage(false);
|
|
2619
|
+
if (stage === null) continue;
|
|
2620
|
+
let arr = byStage.get(stage);
|
|
2621
|
+
if (!arr) {
|
|
2622
|
+
arr = [];
|
|
2623
|
+
byStage.set(stage, arr);
|
|
2624
|
+
}
|
|
2625
|
+
arr.push(call.callId);
|
|
2626
|
+
}
|
|
2627
|
+
if (byStage.size === 0) {
|
|
2628
|
+
return new Set(eligible.map((c) => c.callId));
|
|
2629
|
+
}
|
|
2630
|
+
const isBinary = this.format.isBinary;
|
|
2631
|
+
const stagesArg = isBinary ? /* @__PURE__ */ new Map() : {};
|
|
2632
|
+
for (const [stage, ids] of byStage) {
|
|
2633
|
+
ids.sort((a, b) => a - b);
|
|
2634
|
+
const bytes2 = IncreasingSeqCompressor.serialize(ids);
|
|
2635
|
+
if (isBinary) {
|
|
2636
|
+
stagesArg.set(stage, bytes2);
|
|
2637
|
+
} else {
|
|
2638
|
+
stagesArg[String(stage)] = base64Encode(bytes2);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
const reconnectCall = this.call(
|
|
2642
|
+
RpcSystemCalls.reconnect,
|
|
2643
|
+
[this._remoteHandshakeIndex, stagesArg],
|
|
2644
|
+
{
|
|
2645
|
+
remoteExecutionMode: 1
|
|
2646
|
+
/* AwaitForConnection only; must not itself reconnect */
|
|
2647
|
+
}
|
|
2648
|
+
);
|
|
2649
|
+
const result = await reconnectCall.result.promise;
|
|
2650
|
+
const bytes = _toBytes2(result);
|
|
2651
|
+
if (bytes === null) return new Set(eligible.map((c) => c.callId));
|
|
2652
|
+
return new Set(IncreasingSeqCompressor.deserialize(bytes));
|
|
2653
|
+
} catch {
|
|
2654
|
+
return new Set(eligible.map((c) => c.callId));
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2011
2657
|
_onHandshakeReceived(handshake) {
|
|
2012
2658
|
this._pendingHandshake?.resolve(handshake);
|
|
2013
2659
|
}
|
|
@@ -2023,6 +2669,9 @@ var RpcServerPeer = class extends RpcPeer {
|
|
|
2023
2669
|
/** Accept an incoming connection — sets up message handling and handshake response. */
|
|
2024
2670
|
accept(conn) {
|
|
2025
2671
|
this.setupConnection(conn);
|
|
2672
|
+
conn.closed.add(() => {
|
|
2673
|
+
this.sharedObjects.disconnectAll();
|
|
2674
|
+
});
|
|
2026
2675
|
}
|
|
2027
2676
|
_onHandshakeReceived(_handshake) {
|
|
2028
2677
|
if (this._connection !== void 0) {
|
|
@@ -2052,6 +2701,17 @@ function guidToBase64Url(uuid) {
|
|
|
2052
2701
|
const binary = String.fromCharCode(...bytes);
|
|
2053
2702
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
2054
2703
|
}
|
|
2704
|
+
function _toBytes2(value) {
|
|
2705
|
+
if (value instanceof Uint8Array) return value;
|
|
2706
|
+
if (typeof value === "string") {
|
|
2707
|
+
try {
|
|
2708
|
+
return base64Decode(value);
|
|
2709
|
+
} catch {
|
|
2710
|
+
return null;
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
return null;
|
|
2714
|
+
}
|
|
2055
2715
|
|
|
2056
2716
|
// src/rpc-service-host.ts
|
|
2057
2717
|
var RpcServiceHost = class {
|
|
@@ -2102,7 +2762,8 @@ function rpcMethod(options) {
|
|
|
2102
2762
|
methods[methodName] = {
|
|
2103
2763
|
...methods[methodName],
|
|
2104
2764
|
argCount: target.length,
|
|
2105
|
-
returns: options?.returns
|
|
2765
|
+
returns: options?.returns,
|
|
2766
|
+
remoteExecutionMode: options?.remoteExecutionMode
|
|
2106
2767
|
};
|
|
2107
2768
|
return target;
|
|
2108
2769
|
};
|
|
@@ -2717,7 +3378,8 @@ var RpcHub = class {
|
|
|
2717
3378
|
const methods = /* @__PURE__ */ new Map();
|
|
2718
3379
|
for (const [name, byArgCount] of overloads) {
|
|
2719
3380
|
if (byArgCount.size === 1) {
|
|
2720
|
-
|
|
3381
|
+
const [[, singleFn]] = byArgCount;
|
|
3382
|
+
methods.set(name, singleFn);
|
|
2721
3383
|
} else {
|
|
2722
3384
|
methods.set(name, (...args) => {
|
|
2723
3385
|
const fn = byArgCount.get(args.length);
|
|
@@ -2747,6 +3409,7 @@ var RpcHub = class {
|
|
|
2747
3409
|
for (const [name, meta] of Object.entries(methodsMeta)) {
|
|
2748
3410
|
const wireArgCount = meta.argCount + 1;
|
|
2749
3411
|
const mapKey = `${name}:${wireArgCount}`;
|
|
3412
|
+
const isNoWait = meta.returns === RpcType.noWait;
|
|
2750
3413
|
methods.set(mapKey, {
|
|
2751
3414
|
name,
|
|
2752
3415
|
serviceName: svcMeta.name,
|
|
@@ -2754,7 +3417,8 @@ var RpcHub = class {
|
|
|
2754
3417
|
wireArgCount,
|
|
2755
3418
|
callTypeId: 0,
|
|
2756
3419
|
stream: meta.returns === RpcType.stream,
|
|
2757
|
-
noWait:
|
|
3420
|
+
noWait: isNoWait,
|
|
3421
|
+
remoteExecutionMode: isNoWait ? 0 : meta.remoteExecutionMode ?? RpcRemoteExecutionMode.Default
|
|
2758
3422
|
});
|
|
2759
3423
|
}
|
|
2760
3424
|
return { name: svcMeta.name, methods };
|
|
@@ -2772,6 +3436,8 @@ var RpcHub = class {
|
|
|
2772
3436
|
/** Create a client method for the given methodDef. Override in FusionHub for custom call types. */
|
|
2773
3437
|
_createClientMethod(peer, methodDef) {
|
|
2774
3438
|
const wireName = wireMethodName(methodDef);
|
|
3439
|
+
const mode = methodDef.remoteExecutionMode;
|
|
3440
|
+
const mustCheckConnection = !(mode & RpcRemoteExecutionMode.AwaitForConnection);
|
|
2775
3441
|
if (methodDef.noWait) {
|
|
2776
3442
|
return (...args) => {
|
|
2777
3443
|
const callArgs = args.slice(0, methodDef.argCount);
|
|
@@ -2780,6 +3446,8 @@ var RpcHub = class {
|
|
|
2780
3446
|
}
|
|
2781
3447
|
if (methodDef.stream) {
|
|
2782
3448
|
return async (...args) => {
|
|
3449
|
+
if (mustCheckConnection && !peer.isConnected)
|
|
3450
|
+
throw new Error("Outbound call failed: not connected and AwaitForConnection is not set.");
|
|
2783
3451
|
const callArgs = args.slice(0, methodDef.argCount);
|
|
2784
3452
|
const outboundCall = peer.call(wireName, callArgs);
|
|
2785
3453
|
const result = await outboundCall.result.promise;
|
|
@@ -2793,8 +3461,10 @@ var RpcHub = class {
|
|
|
2793
3461
|
return stream;
|
|
2794
3462
|
};
|
|
2795
3463
|
}
|
|
2796
|
-
const callOptions = methodDef.callTypeId !== 0 ? { callTypeId: methodDef.callTypeId } : void 0;
|
|
3464
|
+
const callOptions = methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode } : void 0;
|
|
2797
3465
|
return async (...args) => {
|
|
3466
|
+
if (mustCheckConnection && !peer.isConnected)
|
|
3467
|
+
throw new Error("Outbound call failed: not connected and AwaitForConnection is not set.");
|
|
2798
3468
|
const callArgs = args.slice(0, methodDef.argCount);
|
|
2799
3469
|
const outboundCall = peer.call(wireName, callArgs, callOptions);
|
|
2800
3470
|
const result = await outboundCall.result.promise;
|
|
@@ -2817,7 +3487,7 @@ function createRpcClient(peer, serviceDef) {
|
|
|
2817
3487
|
const methods = /* @__PURE__ */ new Map();
|
|
2818
3488
|
for (const [name, byArgCount] of overloads) {
|
|
2819
3489
|
if (byArgCount.size === 1) {
|
|
2820
|
-
const methodDef = byArgCount
|
|
3490
|
+
const [[, methodDef]] = byArgCount;
|
|
2821
3491
|
methods.set(name, createMethod(peer, methodDef));
|
|
2822
3492
|
} else {
|
|
2823
3493
|
const fns = /* @__PURE__ */ new Map();
|
|
@@ -2842,10 +3512,14 @@ function createRpcClient(peer, serviceDef) {
|
|
|
2842
3512
|
});
|
|
2843
3513
|
}
|
|
2844
3514
|
function createMethod(peer, methodDef) {
|
|
3515
|
+
const mode = methodDef.remoteExecutionMode;
|
|
3516
|
+
const mustCheckConnection = !(mode & RpcRemoteExecutionMode.AwaitForConnection);
|
|
2845
3517
|
return async (...args) => {
|
|
3518
|
+
if (mustCheckConnection && !peer.isConnected)
|
|
3519
|
+
throw new Error("Outbound call failed: not connected and AwaitForConnection is not set.");
|
|
2846
3520
|
const callArgs = args.slice(0, methodDef.argCount);
|
|
2847
3521
|
const wireName = wireMethodName(methodDef);
|
|
2848
|
-
const options = methodDef.callTypeId !== 0 ? { callTypeId: methodDef.callTypeId } : void 0;
|
|
3522
|
+
const options = methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode } : void 0;
|
|
2849
3523
|
const outboundCall = peer.call(wireName, callArgs, options);
|
|
2850
3524
|
return outboundCall.result.promise;
|
|
2851
3525
|
};
|
|
@@ -3040,7 +3714,9 @@ export {
|
|
|
3040
3714
|
ARG_DELIMITER,
|
|
3041
3715
|
ENVELOPE_DELIMITER,
|
|
3042
3716
|
FRAME_DELIMITER,
|
|
3717
|
+
IncreasingSeqCompressor,
|
|
3043
3718
|
RPC_CLOSE_CODE_UNSUPPORTED_FORMAT,
|
|
3719
|
+
RpcCallStage,
|
|
3044
3720
|
RpcCallTypeId,
|
|
3045
3721
|
RpcClientPeer,
|
|
3046
3722
|
RpcClientPeerReconnectDelayer,
|
|
@@ -3059,6 +3735,7 @@ export {
|
|
|
3059
3735
|
RpcPeerConnectionKind,
|
|
3060
3736
|
RpcPeerStateKind,
|
|
3061
3737
|
RpcPeerStateMonitor,
|
|
3738
|
+
RpcRemoteExecutionMode,
|
|
3062
3739
|
RpcRemoteObjectTracker,
|
|
3063
3740
|
RpcSerializationFormat,
|
|
3064
3741
|
RpcSerializationFormatResolver,
|