@actuallab/rpc 13.0.25 → 13.0.167
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 +658 -177
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +171 -11
- package/dist/index.d.ts +171 -11
- package/dist/index.js +653 -176
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -165,6 +165,24 @@ function readVarUint(data, offset) {
|
|
|
165
165
|
} while (shift < 35);
|
|
166
166
|
return { value: value >>> 0, bytesRead };
|
|
167
167
|
}
|
|
168
|
+
function skipHeaders(data, pos, headerCount) {
|
|
169
|
+
for (let h = 0; h < headerCount; h++) {
|
|
170
|
+
const keyLen = data[pos++];
|
|
171
|
+
pos += keyLen;
|
|
172
|
+
const valLen = readVarUint(data, pos);
|
|
173
|
+
pos += valLen.bytesRead + valLen.value;
|
|
174
|
+
}
|
|
175
|
+
return pos;
|
|
176
|
+
}
|
|
177
|
+
function readPolymorphismMarker(data, offset) {
|
|
178
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
179
|
+
const length = view.getUint16(offset, true);
|
|
180
|
+
if (length === 0)
|
|
181
|
+
return { bytesRead: 2, isNull: false };
|
|
182
|
+
if (length === 1)
|
|
183
|
+
return { bytesRead: 2, isNull: true };
|
|
184
|
+
throw new Error("polymorphic payloads are not supported by the TS client");
|
|
185
|
+
}
|
|
168
186
|
function concatUint8Arrays(arrays) {
|
|
169
187
|
let totalLength = 0;
|
|
170
188
|
for (const arr of arrays) totalLength += arr.length;
|
|
@@ -228,14 +246,6 @@ function deserializeBinaryMessage(data, offset, decoder = defaultBinaryDecoder)
|
|
|
228
246
|
pos += methodLen.value;
|
|
229
247
|
const argDataLen = view.getInt32(pos, true);
|
|
230
248
|
pos += 4;
|
|
231
|
-
if (headerCount > 0) {
|
|
232
|
-
for (let h = 0; h < headerCount; h++) {
|
|
233
|
-
const keyLen = data[pos++];
|
|
234
|
-
pos += keyLen;
|
|
235
|
-
const valLen = readVarUint(data, pos);
|
|
236
|
-
pos += valLen.bytesRead + valLen.value;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
249
|
const args = [];
|
|
240
250
|
const argEnd = pos + argDataLen;
|
|
241
251
|
if (argDataLen > 0) {
|
|
@@ -243,8 +253,10 @@ function deserializeBinaryMessage(data, offset, decoder = defaultBinaryDecoder)
|
|
|
243
253
|
for (const decoded of decoder.decodeMulti(argSlice)) {
|
|
244
254
|
args.push(decoded);
|
|
245
255
|
}
|
|
246
|
-
pos = argEnd;
|
|
247
256
|
}
|
|
257
|
+
pos = argEnd;
|
|
258
|
+
if (headerCount > 0)
|
|
259
|
+
pos = skipHeaders(data, pos, headerCount);
|
|
248
260
|
const message = {
|
|
249
261
|
Method: method,
|
|
250
262
|
RelatedId: relId.value,
|
|
@@ -322,14 +334,6 @@ function deserializeCompactBinaryMessage(data, offset, registry, decoder = defau
|
|
|
322
334
|
const method = registry?.getName(methodHash | 0) ?? `<hash:0x${methodHash.toString(16).padStart(8, "0")}>`;
|
|
323
335
|
const argDataLen = view.getInt32(pos, true);
|
|
324
336
|
pos += 4;
|
|
325
|
-
if (headerCount > 0) {
|
|
326
|
-
for (let h = 0; h < headerCount; h++) {
|
|
327
|
-
const keyLen = data[pos++];
|
|
328
|
-
pos += keyLen;
|
|
329
|
-
const valLen = readVarUint(data, pos);
|
|
330
|
-
pos += valLen.bytesRead + valLen.value;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
337
|
const args = [];
|
|
334
338
|
const argEnd = pos + argDataLen;
|
|
335
339
|
if (argDataLen > 0) {
|
|
@@ -337,8 +341,10 @@ function deserializeCompactBinaryMessage(data, offset, registry, decoder = defau
|
|
|
337
341
|
for (const decoded of decoder.decodeMulti(argSlice)) {
|
|
338
342
|
args.push(decoded);
|
|
339
343
|
}
|
|
340
|
-
pos = argEnd;
|
|
341
344
|
}
|
|
345
|
+
pos = argEnd;
|
|
346
|
+
if (headerCount > 0)
|
|
347
|
+
pos = skipHeaders(data, pos, headerCount);
|
|
342
348
|
const message = {
|
|
343
349
|
Method: method,
|
|
344
350
|
RelatedId: relId.value,
|
|
@@ -608,7 +614,8 @@ function defineRpcService(name, methods) {
|
|
|
608
614
|
callTypeId: input.callTypeId ?? 0,
|
|
609
615
|
stream: input.returns === RpcType.stream,
|
|
610
616
|
noWait: isNoWait,
|
|
611
|
-
remoteExecutionMode: isNoWait ? 0 : input.remoteExecutionMode ?? RpcRemoteExecutionMode.Default
|
|
617
|
+
remoteExecutionMode: isNoWait ? 0 : input.remoteExecutionMode ?? RpcRemoteExecutionMode.Default,
|
|
618
|
+
timeouts: input.timeouts
|
|
612
619
|
});
|
|
613
620
|
}
|
|
614
621
|
return { name, methods: map };
|
|
@@ -642,6 +649,14 @@ var RpcOutboundCall = class {
|
|
|
642
649
|
removeOnOk = true;
|
|
643
650
|
/** The serialized wire data — stored for re-sending on reconnect. */
|
|
644
651
|
serializedWireData = "";
|
|
652
|
+
/** Per-call connect/run timeouts enforced by the peer's maintenance loop
|
|
653
|
+
* (R12). `undefined` means unbounded (the query default). */
|
|
654
|
+
timeouts;
|
|
655
|
+
/** `Date.now()` when this call was created (used for the connect timeout). */
|
|
656
|
+
startedAt = Date.now();
|
|
657
|
+
/** `Date.now()` when this call was last put on the wire, or 0 if it hasn't
|
|
658
|
+
* been sent yet (used for the run timeout). */
|
|
659
|
+
sentAt = 0;
|
|
645
660
|
/**
|
|
646
661
|
* Bitfield of {@link RpcCallStage} flags indicating how far this call
|
|
647
662
|
* has progressed. Used by the `$sys.Reconnect` protocol to tell the
|
|
@@ -731,20 +746,77 @@ var RpcInboundCall = class {
|
|
|
731
746
|
callId;
|
|
732
747
|
method;
|
|
733
748
|
args;
|
|
749
|
+
/** 0 = still processing, 1 = result computed and sent at least once. */
|
|
750
|
+
completedStage = 0;
|
|
751
|
+
_resend;
|
|
752
|
+
_abortController = new AbortController();
|
|
734
753
|
constructor(callId, method, args) {
|
|
735
754
|
this.callId = callId;
|
|
736
755
|
this.method = method;
|
|
737
756
|
this.args = args;
|
|
738
757
|
}
|
|
758
|
+
/** Aborted when the remote peer cancels this call via `$sys.Cancel` (R17).
|
|
759
|
+
* Threaded into the service handler so it can abort in-flight work. */
|
|
760
|
+
get signal() {
|
|
761
|
+
return this._abortController.signal;
|
|
762
|
+
}
|
|
763
|
+
/** True once the remote peer cancelled this call — the response is then
|
|
764
|
+
* suppressed even though the call may still be registered (R9 dedup). */
|
|
765
|
+
get isCancelled() {
|
|
766
|
+
return this._abortController.signal.aborted;
|
|
767
|
+
}
|
|
768
|
+
cancel() {
|
|
769
|
+
this._abortController.abort();
|
|
770
|
+
}
|
|
771
|
+
// Records the closure that re-sends this call's already-computed result;
|
|
772
|
+
// marks the call as completed. Mirrors .NET's post-`ProcessStage1Plus` state.
|
|
773
|
+
setResult(resend) {
|
|
774
|
+
this._resend = resend;
|
|
775
|
+
this.completedStage = 1;
|
|
776
|
+
}
|
|
777
|
+
// Re-sends the result to the remote peer if it was already computed.
|
|
778
|
+
// A resent frame for an in-flight call is a no-op — the original dispatch
|
|
779
|
+
// will send the result when it completes. Mirrors .NET
|
|
780
|
+
// `RpcInboundCall.TryReprocess`.
|
|
781
|
+
resendResult() {
|
|
782
|
+
if (this.completedStage >= 1)
|
|
783
|
+
this._resend?.();
|
|
784
|
+
}
|
|
739
785
|
};
|
|
740
786
|
var RpcInboundCallTracker = class {
|
|
741
787
|
_calls = /* @__PURE__ */ new Map();
|
|
788
|
+
_completedIds = [];
|
|
789
|
+
/** Cap on retained completed calls — see `RpcLimits.completedInboundCallsLimit`. */
|
|
790
|
+
completedCallsLimit = 1e3;
|
|
742
791
|
get size() {
|
|
743
792
|
return this._calls.size;
|
|
744
793
|
}
|
|
745
794
|
register(call) {
|
|
746
795
|
this._calls.set(call.callId, call);
|
|
747
796
|
}
|
|
797
|
+
// Registers `call` unless its id is already tracked, in which case the
|
|
798
|
+
// existing call is returned. Mirrors .NET `RpcInboundCallTracker.GetOrRegister`.
|
|
799
|
+
getOrRegister(call) {
|
|
800
|
+
const existing = this._calls.get(call.callId);
|
|
801
|
+
if (existing !== void 0)
|
|
802
|
+
return existing;
|
|
803
|
+
this._calls.set(call.callId, call);
|
|
804
|
+
return call;
|
|
805
|
+
}
|
|
806
|
+
// Keeps the completed `call` registered for duplicate-frame dedup, evicting
|
|
807
|
+
// the oldest completed calls beyond `completedCallsLimit`. Deviation from
|
|
808
|
+
// .NET (which unregisters a call once its result is sent): TS peers
|
|
809
|
+
// blind-resend after reconnect, so a completed call must stay resolvable
|
|
810
|
+
// to avoid re-executing the handler — but only within a bounded window.
|
|
811
|
+
markCompleted(call) {
|
|
812
|
+
if (this._calls.get(call.callId) !== call)
|
|
813
|
+
return;
|
|
814
|
+
this._completedIds.push(call.callId);
|
|
815
|
+
while (this._completedIds.length > this.completedCallsLimit) {
|
|
816
|
+
const id = this._completedIds.shift();
|
|
817
|
+
this._calls.delete(id);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
748
820
|
get(callId) {
|
|
749
821
|
return this._calls.get(callId);
|
|
750
822
|
}
|
|
@@ -755,6 +827,23 @@ var RpcInboundCallTracker = class {
|
|
|
755
827
|
}
|
|
756
828
|
clear() {
|
|
757
829
|
this._calls.clear();
|
|
830
|
+
this._completedIds.length = 0;
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
// src/rpc-call-timeouts.ts
|
|
835
|
+
var RpcCallTimeouts = class _RpcCallTimeouts {
|
|
836
|
+
/** Both timeouts unbounded — the query default (.NET `RpcCallTimeouts.None`). */
|
|
837
|
+
static None = new _RpcCallTimeouts(Infinity, Infinity);
|
|
838
|
+
/** Command default: 1.5 s connect / 10 s run (.NET `RpcCallTimeouts.Default.Command`). */
|
|
839
|
+
static Command = new _RpcCallTimeouts(1500, 1e4);
|
|
840
|
+
/** Max time a call may wait for the connection before it is sent. */
|
|
841
|
+
connectTimeoutMs;
|
|
842
|
+
/** Max time a sent call may stay unanswered before it is failed. */
|
|
843
|
+
runTimeoutMs;
|
|
844
|
+
constructor(connectTimeoutMs = Infinity, runTimeoutMs = Infinity) {
|
|
845
|
+
this.connectTimeoutMs = connectTimeoutMs;
|
|
846
|
+
this.runTimeoutMs = runTimeoutMs;
|
|
758
847
|
}
|
|
759
848
|
};
|
|
760
849
|
|
|
@@ -768,6 +857,12 @@ var RpcError = class extends Error {
|
|
|
768
857
|
this.typeName = typeName;
|
|
769
858
|
}
|
|
770
859
|
};
|
|
860
|
+
var REMOTE_EXCEPTION_TYPE_REF = "ActualLab.Serialization.RemoteException, ActualLab.Core";
|
|
861
|
+
function toExceptionInfo(error) {
|
|
862
|
+
const name = error instanceof Error ? error.name : "Error";
|
|
863
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
864
|
+
return { TypeRef: REMOTE_EXCEPTION_TYPE_REF, Message: `${name}: ${message}` };
|
|
865
|
+
}
|
|
771
866
|
|
|
772
867
|
// src/increasing-seq-compressor.ts
|
|
773
868
|
var IncreasingSeqCompressor = {
|
|
@@ -872,6 +967,7 @@ import { PromiseSource as PromiseSource3, RingBuffer } from "@actuallab/core";
|
|
|
872
967
|
var { warnLog: warnLog3 } = getLogs("RpcSharedStream");
|
|
873
968
|
var DEFAULT_ACK_PERIOD = 256;
|
|
874
969
|
var DEFAULT_ACK_ADVANCE = 128;
|
|
970
|
+
var RTT_WINDOW_MS = 2e4;
|
|
875
971
|
var RpcStreamSender = class _RpcStreamSender {
|
|
876
972
|
static _emptyGuid = "00000000-0000-0000-0000-000000000000";
|
|
877
973
|
id;
|
|
@@ -913,6 +1009,9 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
913
1009
|
_lastAckedIndex = 0;
|
|
914
1010
|
_abortController = new AbortController();
|
|
915
1011
|
_iterator = null;
|
|
1012
|
+
// -- RTT sampling (send → matching ack round trips, windowed minimum) --
|
|
1013
|
+
_pendingSendTimes = [];
|
|
1014
|
+
_rttSamples = [];
|
|
916
1015
|
// -- Recorder-controller metrics (Step 9.1) --
|
|
917
1016
|
/** Total items skipped via real-time `canSkipTo` compaction. */
|
|
918
1017
|
_skipCount = 0;
|
|
@@ -937,6 +1036,21 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
937
1036
|
get skipCount() {
|
|
938
1037
|
return this._skipCount;
|
|
939
1038
|
}
|
|
1039
|
+
// Windowed-MIN ack round trip (ms, -1 until sampled): converges on propagation
|
|
1040
|
+
// RTT — unlike a mean/EMA it is not inflated by self-induced queuing.
|
|
1041
|
+
get minRttMs() {
|
|
1042
|
+
const cutoffMs = Date.now() - RTT_WINDOW_MS;
|
|
1043
|
+
while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)
|
|
1044
|
+
this._rttSamples.shift();
|
|
1045
|
+
if (this._rttSamples.length === 0)
|
|
1046
|
+
return -1;
|
|
1047
|
+
let min = this._rttSamples[0].rttMs;
|
|
1048
|
+
for (const s of this._rttSamples) {
|
|
1049
|
+
if (s.rttMs < min)
|
|
1050
|
+
min = s.rttMs;
|
|
1051
|
+
}
|
|
1052
|
+
return min;
|
|
1053
|
+
}
|
|
940
1054
|
// -- ACK queue (analog of .NET's Channel<(long, bool)>) --
|
|
941
1055
|
_acks = [];
|
|
942
1056
|
_whenAckReady = null;
|
|
@@ -972,13 +1086,39 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
972
1086
|
*/
|
|
973
1087
|
onAck(nextIndex, hostId) {
|
|
974
1088
|
const mustReset = hostId !== "" && hostId !== _RpcStreamSender._emptyGuid;
|
|
975
|
-
if (
|
|
1089
|
+
if (mustReset && hostId !== this.id.hostId) {
|
|
1090
|
+
this._sendDisconnect();
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
if (!this._started.isCompleted) {
|
|
1094
|
+
if (mustReset && nextIndex === 0)
|
|
1095
|
+
this._started.resolve();
|
|
1096
|
+
else {
|
|
1097
|
+
this._sendDisconnect();
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
} else if (this._ended) {
|
|
1101
|
+
this._sendDisconnect();
|
|
1102
|
+
return;
|
|
1103
|
+
} else if (mustReset && !this.allowReconnect) {
|
|
1104
|
+
this._sendDisconnect();
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
this._sampleRtt(nextIndex, mustReset);
|
|
976
1108
|
this._acks.push({ nextIndex, mustReset });
|
|
977
1109
|
if (this._whenAckReady) {
|
|
978
1110
|
this._whenAckReady.resolve();
|
|
979
1111
|
this._whenAckReady = null;
|
|
980
1112
|
}
|
|
981
1113
|
}
|
|
1114
|
+
// Tells the consumer this shared stream is gone, then disposes it —
|
|
1115
|
+
// mirrors RpcSharedStream.SendDisconnect (RpcSharedStream.cs:284-289).
|
|
1116
|
+
_sendDisconnect() {
|
|
1117
|
+
const conn = this.peer.connection;
|
|
1118
|
+
if (conn)
|
|
1119
|
+
this.peer.hub.systemCallSender.disconnect(conn, this.peer.serializationFormat, [this.id.localId]);
|
|
1120
|
+
this.disconnect();
|
|
1121
|
+
}
|
|
982
1122
|
/** Called by system call handler when $sys.AckEnd is received from the client. */
|
|
983
1123
|
onAckEnd(_hostId) {
|
|
984
1124
|
this.disconnect();
|
|
@@ -1003,6 +1143,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1003
1143
|
this._nextIndex,
|
|
1004
1144
|
item
|
|
1005
1145
|
);
|
|
1146
|
+
this._pendingSendTimes.push({ index: this._nextIndex, sentAtMs: Date.now() });
|
|
1006
1147
|
}
|
|
1007
1148
|
this._nextIndex++;
|
|
1008
1149
|
}
|
|
@@ -1027,7 +1168,7 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1027
1168
|
this._ended = true;
|
|
1028
1169
|
const conn = this.peer.connection;
|
|
1029
1170
|
if (!conn) return;
|
|
1030
|
-
const errorInfo = error ?
|
|
1171
|
+
const errorInfo = error ? toExceptionInfo(error) : { TypeRef: "", Message: "" };
|
|
1031
1172
|
this.peer.hub.systemCallSender.end(
|
|
1032
1173
|
conn,
|
|
1033
1174
|
this.peer.serializationFormat,
|
|
@@ -1245,6 +1386,26 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1245
1386
|
await this._whenAckReady;
|
|
1246
1387
|
this._whenAckReady = null;
|
|
1247
1388
|
}
|
|
1389
|
+
// One RTT sample per ack: time from sending the newest acked item to the
|
|
1390
|
+
// ack's arrival (a slight upper bound — includes the server's ack cadence).
|
|
1391
|
+
_sampleRtt(ackNextIndex, mustReset) {
|
|
1392
|
+
if (mustReset) {
|
|
1393
|
+
this._pendingSendTimes.length = 0;
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
let sentAtMs = -1;
|
|
1397
|
+
while (this._pendingSendTimes.length > 0 && this._pendingSendTimes[0].index < ackNextIndex) {
|
|
1398
|
+
sentAtMs = this._pendingSendTimes[0].sentAtMs;
|
|
1399
|
+
this._pendingSendTimes.shift();
|
|
1400
|
+
}
|
|
1401
|
+
if (sentAtMs < 0)
|
|
1402
|
+
return;
|
|
1403
|
+
const nowMs = Date.now();
|
|
1404
|
+
const cutoffMs = nowMs - RTT_WINDOW_MS;
|
|
1405
|
+
while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)
|
|
1406
|
+
this._rttSamples.shift();
|
|
1407
|
+
this._rttSamples.push({ atMs: nowMs, rttMs: nowMs - sentAtMs });
|
|
1408
|
+
}
|
|
1248
1409
|
// -- IRpcObject --
|
|
1249
1410
|
reconnect() {
|
|
1250
1411
|
}
|
|
@@ -1469,11 +1630,7 @@ var RpcStream = class _RpcStream {
|
|
|
1469
1630
|
if (index > this._nextExpectedIndex) {
|
|
1470
1631
|
warnLog4?.log(`item index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);
|
|
1471
1632
|
if (!this.allowReconnect) {
|
|
1472
|
-
this.
|
|
1473
|
-
this._completionError = new Error(
|
|
1474
|
-
`Stream gap at index ${index} (expected ${this._nextExpectedIndex}); reconnect not allowed`
|
|
1475
|
-
);
|
|
1476
|
-
this._notifyConsumer();
|
|
1633
|
+
this._complete(this._gapError(index));
|
|
1477
1634
|
return;
|
|
1478
1635
|
}
|
|
1479
1636
|
this._sendAck(this._nextExpectedIndex, true);
|
|
@@ -1493,11 +1650,7 @@ var RpcStream = class _RpcStream {
|
|
|
1493
1650
|
if (index > this._nextExpectedIndex) {
|
|
1494
1651
|
warnLog4?.log(`batch index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);
|
|
1495
1652
|
if (!this.allowReconnect) {
|
|
1496
|
-
this.
|
|
1497
|
-
this._completionError = new Error(
|
|
1498
|
-
`Stream gap at index ${index} (expected ${this._nextExpectedIndex}); reconnect not allowed`
|
|
1499
|
-
);
|
|
1500
|
-
this._notifyConsumer();
|
|
1653
|
+
this._complete(this._gapError(index));
|
|
1501
1654
|
return;
|
|
1502
1655
|
}
|
|
1503
1656
|
this._sendAck(this._nextExpectedIndex, true);
|
|
@@ -1514,9 +1667,20 @@ var RpcStream = class _RpcStream {
|
|
|
1514
1667
|
/** Called by system call handler when the stream ends ($sys.End). */
|
|
1515
1668
|
onEnd(index, error) {
|
|
1516
1669
|
if (this._disposed || this._completed) return;
|
|
1517
|
-
this.
|
|
1518
|
-
|
|
1519
|
-
|
|
1670
|
+
if (index > this._nextExpectedIndex) {
|
|
1671
|
+
warnLog4?.log(`end index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);
|
|
1672
|
+
if (!this.allowReconnect) {
|
|
1673
|
+
this._complete(this._gapError(index));
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
this._sendAck(this._nextExpectedIndex, true);
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
if (index < this._nextExpectedIndex) {
|
|
1680
|
+
this._maybeSendAck(Math.min(index, this._nextConsumedIndex));
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
this._complete(error);
|
|
1520
1684
|
}
|
|
1521
1685
|
// -- IRpcObject --
|
|
1522
1686
|
reconnect() {
|
|
@@ -1550,6 +1714,7 @@ var RpcStream = class _RpcStream {
|
|
|
1550
1714
|
next: async () => {
|
|
1551
1715
|
if (!this._started) {
|
|
1552
1716
|
this._started = true;
|
|
1717
|
+
this.peer.remoteObjects.register(this);
|
|
1553
1718
|
this._sendAck(0, true);
|
|
1554
1719
|
}
|
|
1555
1720
|
this._maybeSendAck(this._nextConsumedIndex);
|
|
@@ -1560,6 +1725,7 @@ var RpcStream = class _RpcStream {
|
|
|
1560
1725
|
return { value, done: false };
|
|
1561
1726
|
}
|
|
1562
1727
|
if (this._completed) {
|
|
1728
|
+
this.dispose();
|
|
1563
1729
|
if (this._completionError) throw this._completionError;
|
|
1564
1730
|
return { value: void 0, done: true };
|
|
1565
1731
|
}
|
|
@@ -1568,7 +1734,8 @@ var RpcStream = class _RpcStream {
|
|
|
1568
1734
|
}
|
|
1569
1735
|
},
|
|
1570
1736
|
return: () => {
|
|
1571
|
-
this.
|
|
1737
|
+
if (this._started && !this._completed)
|
|
1738
|
+
this._sendAckEnd();
|
|
1572
1739
|
this.dispose();
|
|
1573
1740
|
return Promise.resolve({ value: void 0, done: true });
|
|
1574
1741
|
}
|
|
@@ -1584,6 +1751,22 @@ var RpcStream = class _RpcStream {
|
|
|
1584
1751
|
this._notifyConsumer();
|
|
1585
1752
|
}
|
|
1586
1753
|
// -- Private --
|
|
1754
|
+
// Mirrors .NET RpcStream.CloseFromLock: acknowledge the end to the sender
|
|
1755
|
+
// (so its RpcSharedStream is released) and unregister immediately.
|
|
1756
|
+
_complete(error) {
|
|
1757
|
+
if (this._completed) return;
|
|
1758
|
+
this._completed = true;
|
|
1759
|
+
this._completionError = error;
|
|
1760
|
+
this._sendAckEnd();
|
|
1761
|
+
if (this.kind === RpcObjectKind.Remote)
|
|
1762
|
+
this.peer.remoteObjects.unregister(this);
|
|
1763
|
+
this._notifyConsumer();
|
|
1764
|
+
}
|
|
1765
|
+
_gapError(index) {
|
|
1766
|
+
return new Error(
|
|
1767
|
+
`Stream gap at index ${index} (expected ${this._nextExpectedIndex}); reconnect not allowed`
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1587
1770
|
_notifyConsumer() {
|
|
1588
1771
|
if (this._consumerWaiting !== null) {
|
|
1589
1772
|
this._consumerWaiting.resolve();
|
|
@@ -1600,7 +1783,7 @@ var RpcStream = class _RpcStream {
|
|
|
1600
1783
|
static _emptyGuid = "00000000-0000-0000-0000-000000000000";
|
|
1601
1784
|
_sendAck(nextIndex, mustReset) {
|
|
1602
1785
|
this._ackSentUpTo = nextIndex;
|
|
1603
|
-
const conn = this.peer.connection;
|
|
1786
|
+
const conn = this.peer.isConnected ? this.peer.connection : void 0;
|
|
1604
1787
|
if (conn) {
|
|
1605
1788
|
const hostId = mustReset ? this.id.hostId : _RpcStream._emptyGuid;
|
|
1606
1789
|
this.peer.hub.systemCallSender.ack(
|
|
@@ -1613,7 +1796,7 @@ var RpcStream = class _RpcStream {
|
|
|
1613
1796
|
}
|
|
1614
1797
|
}
|
|
1615
1798
|
_sendAckEnd() {
|
|
1616
|
-
const conn = this.peer.connection;
|
|
1799
|
+
const conn = this.peer.isConnected ? this.peer.connection : void 0;
|
|
1617
1800
|
if (conn) {
|
|
1618
1801
|
this.peer.hub.systemCallSender.ackEnd(
|
|
1619
1802
|
conn,
|
|
@@ -1633,9 +1816,7 @@ function resolveStreamRefs(value, peer) {
|
|
|
1633
1816
|
if (typeof value === "string") {
|
|
1634
1817
|
const ref = parseStreamRef(value);
|
|
1635
1818
|
if (ref !== null) {
|
|
1636
|
-
|
|
1637
|
-
peer.remoteObjects.register(stream);
|
|
1638
|
-
return stream;
|
|
1819
|
+
return new RpcStream(ref, peer);
|
|
1639
1820
|
}
|
|
1640
1821
|
return value;
|
|
1641
1822
|
}
|
|
@@ -1688,6 +1869,11 @@ var RpcSystemCallHandler = class {
|
|
|
1688
1869
|
case RpcSystemCalls.ok: {
|
|
1689
1870
|
const call = peer.outboundCalls.get(relatedId);
|
|
1690
1871
|
if (call !== void 0) {
|
|
1872
|
+
if (args.length > 1) {
|
|
1873
|
+
peer.outboundCalls.remove(relatedId);
|
|
1874
|
+
call.result.reject(polymorphicPayloadError(method, args.length, 1));
|
|
1875
|
+
break;
|
|
1876
|
+
}
|
|
1691
1877
|
call.completedStage |= RpcCallStage.ResultReady;
|
|
1692
1878
|
if (call.removeOnOk) {
|
|
1693
1879
|
peer.outboundCalls.remove(relatedId);
|
|
@@ -1701,19 +1887,15 @@ var RpcSystemCallHandler = class {
|
|
|
1701
1887
|
break;
|
|
1702
1888
|
}
|
|
1703
1889
|
case RpcSystemCalls.error: {
|
|
1704
|
-
const call = peer.outboundCalls.
|
|
1890
|
+
const call = peer.outboundCalls.get(relatedId);
|
|
1705
1891
|
if (call !== void 0) {
|
|
1892
|
+
call.completedStage |= RpcCallStage.ResultReady;
|
|
1893
|
+
if (call.removeOnOk) {
|
|
1894
|
+
peer.outboundCalls.remove(relatedId);
|
|
1895
|
+
}
|
|
1706
1896
|
const errorInfo = args[0];
|
|
1707
1897
|
const msg = errorInfo?.Message ?? errorInfo?.message ?? "RPC error";
|
|
1708
|
-
const
|
|
1709
|
-
let typeName;
|
|
1710
|
-
if (typeof errorType === "string") {
|
|
1711
|
-
const commaIdx = errorType.indexOf(",");
|
|
1712
|
-
typeName = commaIdx >= 0 ? errorType.slice(0, commaIdx) : errorType;
|
|
1713
|
-
} else if (errorType !== null && typeof errorType === "object") {
|
|
1714
|
-
const t = errorType;
|
|
1715
|
-
typeName = t.TypeName ?? t.typeName;
|
|
1716
|
-
}
|
|
1898
|
+
const typeName = extractTypeName(errorInfo?.TypeRef ?? errorInfo?.typeRef);
|
|
1717
1899
|
if (typeName === "ActualLab.Rpc.RpcRerouteException")
|
|
1718
1900
|
warnLog5?.log("Got RpcRerouteException from remote peer:", msg);
|
|
1719
1901
|
call.result.reject(new RpcError(msg, typeName));
|
|
@@ -1721,17 +1903,29 @@ var RpcSystemCallHandler = class {
|
|
|
1721
1903
|
break;
|
|
1722
1904
|
}
|
|
1723
1905
|
case RpcSystemCalls.cancel: {
|
|
1724
|
-
peer.inboundCalls.remove(relatedId);
|
|
1906
|
+
const call = peer.inboundCalls.remove(relatedId);
|
|
1907
|
+
call?.cancel();
|
|
1725
1908
|
break;
|
|
1726
1909
|
}
|
|
1727
1910
|
case RpcSystemCalls.keepAlive: {
|
|
1728
1911
|
peer.notifyKeepAliveReceived();
|
|
1912
|
+
const keepAliveIds = args[0];
|
|
1913
|
+
if (Array.isArray(keepAliveIds) && keepAliveIds.length > 0 && peer.connection !== void 0) {
|
|
1914
|
+
const unknownIds = [];
|
|
1915
|
+
for (const id of keepAliveIds)
|
|
1916
|
+
if (peer.sharedObjects.get(id) === void 0)
|
|
1917
|
+
unknownIds.push(id);
|
|
1918
|
+
if (unknownIds.length > 0)
|
|
1919
|
+
peer.hub.systemCallSender.disconnect(peer.connection, peer.serializationFormat, unknownIds);
|
|
1920
|
+
}
|
|
1729
1921
|
break;
|
|
1730
1922
|
}
|
|
1731
1923
|
case RpcSystemCalls.item: {
|
|
1732
1924
|
const stream = peer.remoteObjects.get(relatedId);
|
|
1733
1925
|
if (!stream)
|
|
1734
1926
|
debugLog?.log(`$sys.I: no stream for relatedId=${relatedId}`);
|
|
1927
|
+
else if (args.length > 2)
|
|
1928
|
+
stream.onEnd(args[0], polymorphicPayloadError(method, args.length, 2));
|
|
1735
1929
|
else
|
|
1736
1930
|
stream.onItem(
|
|
1737
1931
|
args[0],
|
|
@@ -1743,6 +1937,8 @@ var RpcSystemCallHandler = class {
|
|
|
1743
1937
|
const stream = peer.remoteObjects.get(relatedId);
|
|
1744
1938
|
if (!stream)
|
|
1745
1939
|
debugLog?.log(`$sys.B: no stream for relatedId=${relatedId}`);
|
|
1940
|
+
else if (args.length > 2)
|
|
1941
|
+
stream.onEnd(args[0], polymorphicPayloadError(method, args.length, 2));
|
|
1746
1942
|
else if (Array.isArray(args[1])) {
|
|
1747
1943
|
const items = args[1];
|
|
1748
1944
|
for (let i = 0; i < items.length; i++)
|
|
@@ -1757,20 +1953,32 @@ var RpcSystemCallHandler = class {
|
|
|
1757
1953
|
debugLog?.log(`$sys.End: no stream for relatedId=${relatedId}`);
|
|
1758
1954
|
} else {
|
|
1759
1955
|
const errorInfo = args[1];
|
|
1956
|
+
const typeName = extractTypeName(errorInfo?.TypeRef ?? errorInfo?.typeRef);
|
|
1760
1957
|
const msg = errorInfo?.Message ?? errorInfo?.message;
|
|
1761
|
-
|
|
1958
|
+
let error = null;
|
|
1959
|
+
if (typeName || msg) {
|
|
1960
|
+
error = new Error(msg ?? typeName ?? "RPC stream error");
|
|
1961
|
+
if (typeName)
|
|
1962
|
+
error.name = typeName;
|
|
1963
|
+
}
|
|
1762
1964
|
stream.onEnd(args[0], error);
|
|
1763
1965
|
}
|
|
1764
1966
|
break;
|
|
1765
1967
|
}
|
|
1766
1968
|
case RpcSystemCalls.ack: {
|
|
1767
1969
|
const sender = peer.sharedObjects.get(relatedId);
|
|
1768
|
-
sender
|
|
1970
|
+
if (sender)
|
|
1971
|
+
sender.onAck(args[0], args[1]);
|
|
1972
|
+
else if (peer.connection !== void 0)
|
|
1973
|
+
peer.hub.systemCallSender.disconnect(peer.connection, peer.serializationFormat, [relatedId]);
|
|
1769
1974
|
break;
|
|
1770
1975
|
}
|
|
1771
1976
|
case RpcSystemCalls.ackEnd: {
|
|
1772
1977
|
const sender = peer.sharedObjects.get(relatedId);
|
|
1773
|
-
sender
|
|
1978
|
+
if (sender)
|
|
1979
|
+
sender.onAckEnd(args[0]);
|
|
1980
|
+
else if (peer.connection !== void 0)
|
|
1981
|
+
peer.hub.systemCallSender.disconnect(peer.connection, peer.serializationFormat, [relatedId]);
|
|
1774
1982
|
break;
|
|
1775
1983
|
}
|
|
1776
1984
|
case RpcSystemCalls.disconnect: {
|
|
@@ -1804,19 +2012,34 @@ var RpcSystemCallHandler = class {
|
|
|
1804
2012
|
*/
|
|
1805
2013
|
_handleReconnect(relatedId, args, peer) {
|
|
1806
2014
|
if (peer.connection === void 0) return;
|
|
2015
|
+
const handshakeIndex = args[0];
|
|
2016
|
+
if (typeof handshakeIndex === "number" && handshakeIndex !== peer.ownHandshakeIndex) {
|
|
2017
|
+
peer.hub.systemCallSender.error(
|
|
2018
|
+
peer.connection,
|
|
2019
|
+
peer.serializationFormat,
|
|
2020
|
+
relatedId,
|
|
2021
|
+
new Error(
|
|
2022
|
+
`TooLateToReconnect: own handshake index ${peer.ownHandshakeIndex} != ${handshakeIndex}`
|
|
2023
|
+
)
|
|
2024
|
+
);
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
1807
2027
|
let unknownIds = [];
|
|
1808
2028
|
try {
|
|
1809
2029
|
const stagesRaw = args[1];
|
|
1810
2030
|
if (stagesRaw && typeof stagesRaw === "object" && !Array.isArray(stagesRaw)) {
|
|
1811
2031
|
const unknownSet = /* @__PURE__ */ new Set();
|
|
1812
|
-
for (const [, rawValue] of Object.entries(stagesRaw)) {
|
|
2032
|
+
for (const [stageKey, rawValue] of Object.entries(stagesRaw)) {
|
|
1813
2033
|
const bytes = _toBytes(rawValue);
|
|
1814
2034
|
if (bytes === null) continue;
|
|
2035
|
+
const completedStage = parseInt(stageKey, 10) || 0;
|
|
1815
2036
|
const callIds = IncreasingSeqCompressor.deserialize(bytes);
|
|
1816
2037
|
for (const callId of callIds) {
|
|
1817
|
-
|
|
2038
|
+
const call = peer.inboundCalls.get(callId);
|
|
2039
|
+
if (call === void 0)
|
|
1818
2040
|
unknownSet.add(callId);
|
|
1819
|
-
|
|
2041
|
+
else if (completedStage < 1)
|
|
2042
|
+
call.resendResult();
|
|
1820
2043
|
}
|
|
1821
2044
|
}
|
|
1822
2045
|
unknownIds = [...unknownSet].sort((a, b) => a - b);
|
|
@@ -1828,6 +2051,29 @@ var RpcSystemCallHandler = class {
|
|
|
1828
2051
|
peer.hub.systemCallSender.ok(peer.connection, peer.serializationFormat, relatedId, responseValue);
|
|
1829
2052
|
}
|
|
1830
2053
|
};
|
|
2054
|
+
function polymorphicPayloadError(method, got, expected) {
|
|
2055
|
+
return new Error(
|
|
2056
|
+
`${method}: expected ${expected} value(s), got ${got} \u2014 polymorphic payloads are not supported by the TS client`
|
|
2057
|
+
);
|
|
2058
|
+
}
|
|
2059
|
+
function extractTypeName(typeRef) {
|
|
2060
|
+
if (typeof typeRef === "string") {
|
|
2061
|
+
if (typeRef.length === 0)
|
|
2062
|
+
return void 0;
|
|
2063
|
+
const commaIdx = typeRef.indexOf(",");
|
|
2064
|
+
return commaIdx >= 0 ? typeRef.slice(0, commaIdx) : typeRef;
|
|
2065
|
+
}
|
|
2066
|
+
if (typeRef !== null && typeof typeRef === "object") {
|
|
2067
|
+
const t = typeRef;
|
|
2068
|
+
const aqn = t.AssemblyQualifiedName ?? t.assemblyQualifiedName;
|
|
2069
|
+
if (typeof aqn === "string" && aqn.length > 0) {
|
|
2070
|
+
const commaIdx = aqn.indexOf(",");
|
|
2071
|
+
return commaIdx >= 0 ? aqn.slice(0, commaIdx) : aqn;
|
|
2072
|
+
}
|
|
2073
|
+
return t.TypeName ?? t.typeName;
|
|
2074
|
+
}
|
|
2075
|
+
return void 0;
|
|
2076
|
+
}
|
|
1831
2077
|
function _toBytes(value) {
|
|
1832
2078
|
if (value instanceof Uint8Array) return value;
|
|
1833
2079
|
if (typeof value === "string") {
|
|
@@ -1911,12 +2157,11 @@ var RpcSystemCallSender = class _RpcSystemCallSender {
|
|
|
1911
2157
|
}
|
|
1912
2158
|
}
|
|
1913
2159
|
error(conn, format, relatedId, error) {
|
|
1914
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1915
2160
|
this._send(
|
|
1916
2161
|
conn,
|
|
1917
2162
|
format,
|
|
1918
2163
|
{ Method: RpcSystemCalls.error, RelatedId: relatedId },
|
|
1919
|
-
[
|
|
2164
|
+
[toExceptionInfo(error)]
|
|
1920
2165
|
);
|
|
1921
2166
|
}
|
|
1922
2167
|
cancel(conn, format, relatedId) {
|
|
@@ -1930,6 +2175,14 @@ var RpcSystemCallSender = class _RpcSystemCallSender {
|
|
|
1930
2175
|
activeCallIds
|
|
1931
2176
|
]);
|
|
1932
2177
|
}
|
|
2178
|
+
// Tells the remote peer the listed shared-object ids are gone on our side,
|
|
2179
|
+
// so its matching remote objects (e.g. RpcStream consumers) fail fast
|
|
2180
|
+
// instead of hanging. Mirrors .NET RpcSystemCallSender.Disconnect.
|
|
2181
|
+
disconnect(conn, format, localIds) {
|
|
2182
|
+
this._send(conn, format, { Method: RpcSystemCalls.disconnect }, [
|
|
2183
|
+
localIds
|
|
2184
|
+
]);
|
|
2185
|
+
}
|
|
1933
2186
|
item(conn, format, localId, index, item) {
|
|
1934
2187
|
this._itemEnv.RelatedId = localId;
|
|
1935
2188
|
this._itemArgs[0] = index;
|
|
@@ -1967,7 +2220,13 @@ var RpcSystemCallSender = class _RpcSystemCallSender {
|
|
|
1967
2220
|
var RpcRemoteObjectTracker = class {
|
|
1968
2221
|
_objects = /* @__PURE__ */ new Map();
|
|
1969
2222
|
register(obj) {
|
|
1970
|
-
|
|
2223
|
+
const localId = obj.id.localId;
|
|
2224
|
+
const existing = this._objects.get(localId);
|
|
2225
|
+
if (existing === obj)
|
|
2226
|
+
return;
|
|
2227
|
+
if (existing !== void 0)
|
|
2228
|
+
existing.disconnect();
|
|
2229
|
+
this._objects.set(localId, obj);
|
|
1971
2230
|
}
|
|
1972
2231
|
get(localId) {
|
|
1973
2232
|
return this._objects.get(localId);
|
|
@@ -1976,7 +2235,9 @@ var RpcRemoteObjectTracker = class {
|
|
|
1976
2235
|
return this._objects.keys();
|
|
1977
2236
|
}
|
|
1978
2237
|
unregister(obj) {
|
|
1979
|
-
|
|
2238
|
+
const localId = obj.id.localId;
|
|
2239
|
+
if (this._objects.get(localId) === obj)
|
|
2240
|
+
this._objects.delete(localId);
|
|
1980
2241
|
}
|
|
1981
2242
|
disconnectAll() {
|
|
1982
2243
|
for (const obj of this._objects.values()) {
|
|
@@ -2000,7 +2261,10 @@ var RpcSharedObjectTracker = class {
|
|
|
2000
2261
|
return this._nextId++;
|
|
2001
2262
|
}
|
|
2002
2263
|
register(obj) {
|
|
2003
|
-
|
|
2264
|
+
const localId = obj.id.localId;
|
|
2265
|
+
if (this._objects.has(localId))
|
|
2266
|
+
throw new Error(`RPC shared object with localId ${localId} is already registered.`);
|
|
2267
|
+
this._objects.set(localId, obj);
|
|
2004
2268
|
}
|
|
2005
2269
|
get(localId) {
|
|
2006
2270
|
return this._objects.get(localId);
|
|
@@ -2009,7 +2273,9 @@ var RpcSharedObjectTracker = class {
|
|
|
2009
2273
|
return this._objects.keys();
|
|
2010
2274
|
}
|
|
2011
2275
|
unregister(obj) {
|
|
2012
|
-
|
|
2276
|
+
const localId = obj.id.localId;
|
|
2277
|
+
if (this._objects.get(localId) === obj)
|
|
2278
|
+
this._objects.delete(localId);
|
|
2013
2279
|
}
|
|
2014
2280
|
disconnectAll() {
|
|
2015
2281
|
for (const obj of this._objects.values()) {
|
|
@@ -2020,7 +2286,7 @@ var RpcSharedObjectTracker = class {
|
|
|
2020
2286
|
};
|
|
2021
2287
|
|
|
2022
2288
|
// src/rpc-peer.ts
|
|
2023
|
-
import { EventHandlerSet as EventHandlerSet3, PromiseSource as PromiseSource5, TimeoutError, withTimeout } from "@actuallab/core";
|
|
2289
|
+
import { EventHandlerSet as EventHandlerSet3, PromiseSource as PromiseSource5, TimeoutError, cancellationError, withTimeout } from "@actuallab/core";
|
|
2024
2290
|
|
|
2025
2291
|
// src/rpc-serialization-format.ts
|
|
2026
2292
|
import { Decoder as Decoder3 } from "@msgpack/msgpack";
|
|
@@ -2039,9 +2305,7 @@ var RpcSerializationFormat = class {
|
|
|
2039
2305
|
static NewtonsoftJsonV5;
|
|
2040
2306
|
static NewtonsoftJsonV5NP;
|
|
2041
2307
|
static MessagePackV6;
|
|
2042
|
-
static MemoryPackV6;
|
|
2043
2308
|
static MessagePackV6C;
|
|
2044
|
-
static MemoryPackV6C;
|
|
2045
2309
|
static All;
|
|
2046
2310
|
/** Convenience shortcut for RpcSerializationFormatResolver.Default.get(key). */
|
|
2047
2311
|
static get(key) {
|
|
@@ -2137,18 +2401,14 @@ RpcSerializationFormat.SystemJsonV5NP = new RpcJsonSerializationFormat("json5np"
|
|
|
2137
2401
|
RpcSerializationFormat.NewtonsoftJsonV5 = new RpcJsonSerializationFormat("njson5");
|
|
2138
2402
|
RpcSerializationFormat.NewtonsoftJsonV5NP = new RpcJsonSerializationFormat("njson5np");
|
|
2139
2403
|
RpcSerializationFormat.MessagePackV6 = new RpcMessagePackSerializationFormat("msgpack6");
|
|
2140
|
-
RpcSerializationFormat.MemoryPackV6 = new RpcMessagePackSerializationFormat("mempack6");
|
|
2141
2404
|
RpcSerializationFormat.MessagePackV6C = new RpcMessagePackCompactSerializationFormat("msgpack6c");
|
|
2142
|
-
RpcSerializationFormat.MemoryPackV6C = new RpcMessagePackCompactSerializationFormat("mempack6c");
|
|
2143
2405
|
RpcSerializationFormat.All = [
|
|
2144
2406
|
RpcSerializationFormat.SystemJsonV5,
|
|
2145
2407
|
RpcSerializationFormat.SystemJsonV5NP,
|
|
2146
2408
|
RpcSerializationFormat.NewtonsoftJsonV5,
|
|
2147
2409
|
RpcSerializationFormat.NewtonsoftJsonV5NP,
|
|
2148
2410
|
RpcSerializationFormat.MessagePackV6,
|
|
2149
|
-
RpcSerializationFormat.
|
|
2150
|
-
RpcSerializationFormat.MessagePackV6C,
|
|
2151
|
-
RpcSerializationFormat.MemoryPackV6C
|
|
2411
|
+
RpcSerializationFormat.MessagePackV6C
|
|
2152
2412
|
];
|
|
2153
2413
|
var RpcSerializationFormatResolver = class _RpcSerializationFormatResolver {
|
|
2154
2414
|
static DefaultFormats = RpcSerializationFormat.All;
|
|
@@ -2168,6 +2428,8 @@ var RpcSerializationFormatResolver = class _RpcSerializationFormatResolver {
|
|
|
2168
2428
|
this.formats = map;
|
|
2169
2429
|
}
|
|
2170
2430
|
get(key) {
|
|
2431
|
+
if (isMemoryPackKey(key))
|
|
2432
|
+
throw new Error(`Serialization format '${key}': MemoryPack formats are not supported by the TS client.`);
|
|
2171
2433
|
const format = this.tryGet(key);
|
|
2172
2434
|
if (!format)
|
|
2173
2435
|
throw new Error(`No format with key '${key}'.`);
|
|
@@ -2175,9 +2437,14 @@ var RpcSerializationFormatResolver = class _RpcSerializationFormatResolver {
|
|
|
2175
2437
|
}
|
|
2176
2438
|
tryGet(key) {
|
|
2177
2439
|
if (!key) key = this.defaultFormatKey;
|
|
2440
|
+
if (isMemoryPackKey(key))
|
|
2441
|
+
throw new Error(`Serialization format '${key}': MemoryPack formats are not supported by the TS client.`);
|
|
2178
2442
|
return this.formats.get(key);
|
|
2179
2443
|
}
|
|
2180
2444
|
};
|
|
2445
|
+
function isMemoryPackKey(key) {
|
|
2446
|
+
return key.startsWith("mempack");
|
|
2447
|
+
}
|
|
2181
2448
|
|
|
2182
2449
|
// src/rpc-peer.ts
|
|
2183
2450
|
function yieldToEventLoop() {
|
|
@@ -2231,6 +2498,10 @@ var RpcPeer = class {
|
|
|
2231
2498
|
* attempt (the reconnect delay is part of this phase); `Connected` is
|
|
2232
2499
|
* entered only after the handshake has succeeded. */
|
|
2233
2500
|
connectionStateChanged = new EventHandlerSet3();
|
|
2501
|
+
/** Index of the handshake THIS peer most recently sent. Used to reject a
|
|
2502
|
+
* stale `$sys.Reconnect` that references an older connection generation
|
|
2503
|
+
* (C# `RpcSystemCalls.Reconnect`'s `ownHandshake.Index` check). */
|
|
2504
|
+
_ownHandshakeIndex = 0;
|
|
2234
2505
|
_connection;
|
|
2235
2506
|
/** Mirror of `_connectionState === Connected`. True only after the
|
|
2236
2507
|
* handshake round-trip completes; outbound `call()` checks this to
|
|
@@ -2242,6 +2513,7 @@ var RpcPeer = class {
|
|
|
2242
2513
|
_connectionState = 0 /* Disconnected */;
|
|
2243
2514
|
_keepAliveTimer;
|
|
2244
2515
|
_keepAliveWatchdog;
|
|
2516
|
+
_maintainTimer;
|
|
2245
2517
|
/** Wall-clock time of the last inbound `$sys.KeepAlive`, or 0 if none yet
|
|
2246
2518
|
* for the current connection. Useful for diagnostics. */
|
|
2247
2519
|
_lastKeepAliveAt = 0;
|
|
@@ -2256,6 +2528,37 @@ var RpcPeer = class {
|
|
|
2256
2528
|
this.ref = ref;
|
|
2257
2529
|
this.keepAlivePeriodMs = hub.limits.keepAlivePeriodMs;
|
|
2258
2530
|
this.keepAliveTimeoutMs = hub.limits.keepAliveTimeoutMs;
|
|
2531
|
+
this.inboundCalls.completedCallsLimit = hub.limits.completedInboundCallsLimit;
|
|
2532
|
+
const checkPeriod = hub.limits.callTimeoutCheckPeriodMs;
|
|
2533
|
+
if (checkPeriod > 0 && Number.isFinite(checkPeriod)) {
|
|
2534
|
+
this._maintainTimer = setInterval(() => this._maintainOutboundCalls(), checkPeriod);
|
|
2535
|
+
this._maintainTimer.unref?.();
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
/** Fail outbound calls whose per-call {@link RpcCallTimeouts} elapsed —
|
|
2539
|
+
* the connect timeout while a call still awaits the wire, the run timeout
|
|
2540
|
+
* once it has been sent. Mirrors .NET `RpcOutboundCallTracker.Maintain`
|
|
2541
|
+
* (RpcCallTrackers.cs:108-207). Calls without timeouts (queries) never
|
|
2542
|
+
* time out here. */
|
|
2543
|
+
_maintainOutboundCalls() {
|
|
2544
|
+
const now = Date.now();
|
|
2545
|
+
const canRunTimeout = this._isConnected && (this._lastKeepAliveAt === 0 || now - this._lastKeepAliveAt <= this.keepAliveTimeoutMs);
|
|
2546
|
+
for (const call of [...this.outboundCalls.values()]) {
|
|
2547
|
+
const timeouts = call.timeouts;
|
|
2548
|
+
if (timeouts === void 0 || call.result.isCompleted)
|
|
2549
|
+
continue;
|
|
2550
|
+
let timedOut;
|
|
2551
|
+
if (call.sentAt === 0) {
|
|
2552
|
+
if (now - call.startedAt >= timeouts.connectTimeoutMs)
|
|
2553
|
+
timedOut = `connect timeout (${timeouts.connectTimeoutMs}ms)`;
|
|
2554
|
+
} else if (canRunTimeout && now - call.sentAt >= timeouts.runTimeoutMs)
|
|
2555
|
+
timedOut = `run timeout (${timeouts.runTimeoutMs}ms)`;
|
|
2556
|
+
if (timedOut === void 0)
|
|
2557
|
+
continue;
|
|
2558
|
+
this.outboundCalls.remove(call.callId);
|
|
2559
|
+
call.result.reject(new TimeoutError(`'${this.ref}': call ${call.method}#${call.callId} timed out \u2014 ${timedOut}`));
|
|
2560
|
+
call.onDisconnect();
|
|
2561
|
+
}
|
|
2259
2562
|
}
|
|
2260
2563
|
get connection() {
|
|
2261
2564
|
return this._connection;
|
|
@@ -2266,6 +2569,9 @@ var RpcPeer = class {
|
|
|
2266
2569
|
get isConnected() {
|
|
2267
2570
|
return this._isConnected;
|
|
2268
2571
|
}
|
|
2572
|
+
get ownHandshakeIndex() {
|
|
2573
|
+
return this._ownHandshakeIndex;
|
|
2574
|
+
}
|
|
2269
2575
|
_setConnectionState(value) {
|
|
2270
2576
|
if (this._connectionState === value) return;
|
|
2271
2577
|
const previousState = this._connectionState;
|
|
@@ -2337,14 +2643,17 @@ var RpcPeer = class {
|
|
|
2337
2643
|
this._connection?.encoder,
|
|
2338
2644
|
this.hub.registry
|
|
2339
2645
|
);
|
|
2646
|
+
outboundCall.timeouts = options?.timeouts;
|
|
2340
2647
|
this.outboundCalls.register(outboundCall);
|
|
2341
2648
|
if (this._isConnected) {
|
|
2649
|
+
outboundCall.sentAt = Date.now();
|
|
2342
2650
|
this._sendWireData(outboundCall.serializedWireData);
|
|
2343
2651
|
} else {
|
|
2344
2652
|
const stateHandler = (state) => {
|
|
2345
2653
|
if (state !== 3 /* Connected */) return;
|
|
2346
2654
|
this.connectionStateChanged.remove(stateHandler);
|
|
2347
2655
|
if (outboundCall.result.isCompleted) return;
|
|
2656
|
+
outboundCall.sentAt = Date.now();
|
|
2348
2657
|
this._sendWireData(outboundCall.serializedWireData);
|
|
2349
2658
|
};
|
|
2350
2659
|
this.connectionStateChanged.add(stateHandler);
|
|
@@ -2354,9 +2663,9 @@ var RpcPeer = class {
|
|
|
2354
2663
|
if (signal !== void 0) {
|
|
2355
2664
|
const onAbort = () => {
|
|
2356
2665
|
if (this.outboundCalls.remove(callId) !== void 0) {
|
|
2357
|
-
outboundCall.result.reject(
|
|
2666
|
+
outboundCall.result.reject(cancellationError("Call cancelled."));
|
|
2358
2667
|
outboundCall.onDisconnect();
|
|
2359
|
-
if (this._connection !== void 0)
|
|
2668
|
+
if (this._isConnected && this._connection !== void 0)
|
|
2360
2669
|
this.hub.systemCallSender.cancel(this._connection, this.serializationFormat, callId);
|
|
2361
2670
|
}
|
|
2362
2671
|
};
|
|
@@ -2386,10 +2695,15 @@ var RpcPeer = class {
|
|
|
2386
2695
|
}
|
|
2387
2696
|
close() {
|
|
2388
2697
|
infoLog?.log(`'${this.ref}': Stopping`);
|
|
2698
|
+
if (this._maintainTimer !== void 0) {
|
|
2699
|
+
clearInterval(this._maintainTimer);
|
|
2700
|
+
this._maintainTimer = void 0;
|
|
2701
|
+
}
|
|
2389
2702
|
this.remoteObjects.disconnectAll();
|
|
2390
2703
|
this.sharedObjects.disconnectAll();
|
|
2391
|
-
this.outboundCalls.rejectAll(
|
|
2704
|
+
this.outboundCalls.rejectAll(cancellationError("Peer closed."));
|
|
2392
2705
|
this.outboundCalls.invalidateAll();
|
|
2706
|
+
this.inboundCalls.clear();
|
|
2393
2707
|
this._connection?.close();
|
|
2394
2708
|
this.hub.peers.delete(this.ref);
|
|
2395
2709
|
this._setConnectionState(0 /* Disconnected */);
|
|
@@ -2453,57 +2767,85 @@ var RpcPeer = class {
|
|
|
2453
2767
|
_handleInbound(message, args) {
|
|
2454
2768
|
const method = message.Method ?? "";
|
|
2455
2769
|
const relatedId = message.RelatedId ?? 0;
|
|
2456
|
-
const call = new RpcInboundCall(relatedId, method, args);
|
|
2457
2770
|
const methodDef = this.hub.serviceHost.getMethodDef(method);
|
|
2458
2771
|
const isNoWait = methodDef?.noWait === true;
|
|
2459
|
-
if (!isNoWait) {
|
|
2460
|
-
this.inboundCalls.register(call);
|
|
2461
|
-
}
|
|
2462
2772
|
const serviceHost = this.hub.serviceHost;
|
|
2463
|
-
|
|
2773
|
+
const dispatch = async (call2) => {
|
|
2464
2774
|
try {
|
|
2465
2775
|
const context = this._connection !== void 0 ? {
|
|
2466
2776
|
__rpcDispatch: true,
|
|
2467
2777
|
callId: relatedId,
|
|
2468
|
-
|
|
2778
|
+
callType: message.CallType ?? 0,
|
|
2779
|
+
connection: this._connection,
|
|
2780
|
+
signal: call2?.signal,
|
|
2781
|
+
peer: this
|
|
2469
2782
|
} : void 0;
|
|
2470
|
-
const result = await serviceHost.dispatch(
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2783
|
+
const result = await serviceHost.dispatch(method, args, context);
|
|
2784
|
+
if (methodDef?.stream === true) {
|
|
2785
|
+
if (this._connection !== void 0) {
|
|
2786
|
+
const stream = result instanceof RpcStream ? result : new RpcStream(result);
|
|
2787
|
+
const ref = stream.toRef(this);
|
|
2788
|
+
const send = () => {
|
|
2789
|
+
if (call2?.isCancelled === true) return;
|
|
2790
|
+
if (this._connection !== void 0)
|
|
2791
|
+
this.hub.systemCallSender.ok(
|
|
2792
|
+
this._connection,
|
|
2793
|
+
this.serializationFormat,
|
|
2794
|
+
relatedId,
|
|
2795
|
+
ref
|
|
2796
|
+
);
|
|
2797
|
+
};
|
|
2798
|
+
if (call2 !== void 0) {
|
|
2799
|
+
call2.setResult(send);
|
|
2800
|
+
this.inboundCalls.markCompleted(call2);
|
|
2801
|
+
}
|
|
2802
|
+
send();
|
|
2803
|
+
}
|
|
2804
|
+
} else if (call2 !== void 0) {
|
|
2805
|
+
const send = () => {
|
|
2806
|
+
if (call2.isCancelled) return;
|
|
2807
|
+
if (this._connection !== void 0)
|
|
2808
|
+
this.hub.systemCallSender.ok(
|
|
2809
|
+
this._connection,
|
|
2810
|
+
this.serializationFormat,
|
|
2811
|
+
relatedId,
|
|
2812
|
+
result
|
|
2813
|
+
);
|
|
2814
|
+
};
|
|
2815
|
+
call2.setResult(send);
|
|
2816
|
+
this.inboundCalls.markCompleted(call2);
|
|
2817
|
+
send();
|
|
2490
2818
|
}
|
|
2491
2819
|
} catch (e) {
|
|
2492
|
-
if (
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
this.
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2820
|
+
if (call2 !== void 0) {
|
|
2821
|
+
const send = () => {
|
|
2822
|
+
if (call2.isCancelled) return;
|
|
2823
|
+
if (this._connection !== void 0)
|
|
2824
|
+
this.hub.systemCallSender.error(
|
|
2825
|
+
this._connection,
|
|
2826
|
+
this.serializationFormat,
|
|
2827
|
+
relatedId,
|
|
2828
|
+
e
|
|
2829
|
+
);
|
|
2830
|
+
};
|
|
2831
|
+
call2.setResult(send);
|
|
2832
|
+
this.inboundCalls.markCompleted(call2);
|
|
2833
|
+
send();
|
|
2499
2834
|
}
|
|
2500
2835
|
debugLog2?.log(`'${this.ref}': Inbound call failed: ${method}#${relatedId}`, e);
|
|
2501
|
-
} finally {
|
|
2502
|
-
if (!isNoWait) {
|
|
2503
|
-
this.inboundCalls.remove(relatedId);
|
|
2504
|
-
}
|
|
2505
2836
|
}
|
|
2506
|
-
}
|
|
2837
|
+
};
|
|
2838
|
+
if (isNoWait) {
|
|
2839
|
+
void dispatch(void 0);
|
|
2840
|
+
return;
|
|
2841
|
+
}
|
|
2842
|
+
const call = new RpcInboundCall(relatedId, method, args);
|
|
2843
|
+
const existing = this.inboundCalls.getOrRegister(call);
|
|
2844
|
+
if (existing !== call) {
|
|
2845
|
+
existing.resendResult();
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2848
|
+
void dispatch(call);
|
|
2507
2849
|
}
|
|
2508
2850
|
/** Start sending `$sys.KeepAlive` pings and arm the silence watchdog.
|
|
2509
2851
|
* Called when the peer transitions into `Connected` (after handshake).
|
|
@@ -2559,15 +2901,17 @@ var RpcConnectionState = /* @__PURE__ */ ((RpcConnectionState2) => {
|
|
|
2559
2901
|
})(RpcConnectionState || {});
|
|
2560
2902
|
var RpcClientPeer = class extends RpcPeer {
|
|
2561
2903
|
_disposed = false;
|
|
2904
|
+
/** Aborted when the peer is stopped/disposed — threaded into the reconnect
|
|
2905
|
+
* delay so a pending delay rejects promptly instead of parking. */
|
|
2906
|
+
_stopController = new AbortController();
|
|
2562
2907
|
_tryIndex = 0;
|
|
2563
|
-
_handshakeIndex = 0;
|
|
2564
2908
|
/** Index from the MOST RECENTLY RECEIVED server handshake — sent back
|
|
2565
2909
|
* to the server in `$sys.Reconnect:3` so the server can verify we're
|
|
2566
2910
|
* talking about the current connection. Mirrors .NET's
|
|
2567
2911
|
* `connectionState.Handshake.Index` path in
|
|
2568
2912
|
* `RpcOutboundCallTracker.TryReconnect`. */
|
|
2569
2913
|
_remoteHandshakeIndex = 0;
|
|
2570
|
-
|
|
2914
|
+
_lastRemotePeerId;
|
|
2571
2915
|
_pendingHandshake;
|
|
2572
2916
|
_reconnectsAt = 0;
|
|
2573
2917
|
/** Base64url-encoded peer ID — matches .NET's RpcClientPeer.ClientId (Guid.ToBase64Url). */
|
|
@@ -2588,6 +2932,10 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2588
2932
|
* the connection attempt. Initialized from `hub.limits` at construction;
|
|
2589
2933
|
* set directly to override on a single peer. */
|
|
2590
2934
|
handshakeTimeoutMs;
|
|
2935
|
+
/** A connection that lived less than this before dropping does NOT reset
|
|
2936
|
+
* `_tryIndex`, so a crash-looping server sees growing reconnect delays.
|
|
2937
|
+
* Initialized from `hub.limits` at construction; override per peer. */
|
|
2938
|
+
prematureDisconnectTimeoutMs;
|
|
2591
2939
|
peerChanged = new EventHandlerSet3();
|
|
2592
2940
|
/** Fired when the server rejects the connection due to an unsupported serialization format (close code 4010). */
|
|
2593
2941
|
unsupportedFormat = new EventHandlerSet3();
|
|
@@ -2620,6 +2968,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2620
2968
|
super(hub, url);
|
|
2621
2969
|
this.connectTimeoutMs = hub.limits.connectTimeoutMs;
|
|
2622
2970
|
this.handshakeTimeoutMs = hub.limits.handshakeTimeoutMs;
|
|
2971
|
+
this.prematureDisconnectTimeoutMs = hub.limits.prematureDisconnectTimeoutMs;
|
|
2623
2972
|
this.clientId = guidToBase64Url(this.id);
|
|
2624
2973
|
const resolver = RpcSerializationFormatResolver.Default;
|
|
2625
2974
|
this.serializationFormat = resolver.get(parseFormatFromUrl(url) ?? resolver.defaultFormatKey);
|
|
@@ -2656,7 +3005,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2656
3005
|
try {
|
|
2657
3006
|
while (!this._disposed) {
|
|
2658
3007
|
this._setConnectionState(1 /* Connecting */);
|
|
2659
|
-
const retryDelay = this.hub.reconnectDelayer.getDelay(this._tryIndex);
|
|
3008
|
+
const retryDelay = this.hub.reconnectDelayer.getDelay(this._tryIndex, this._stopController.signal);
|
|
2660
3009
|
if (retryDelay.isLimitExceeded) {
|
|
2661
3010
|
warnLog6?.log(`'${this.ref}': Reconnect limit exceeded - shutting down`);
|
|
2662
3011
|
this._disposed = true;
|
|
@@ -2665,12 +3014,14 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2665
3014
|
this._setReconnectsAt(retryDelay.endsAt);
|
|
2666
3015
|
try {
|
|
2667
3016
|
await retryDelay.promise;
|
|
3017
|
+
} catch {
|
|
2668
3018
|
} finally {
|
|
2669
3019
|
this._setReconnectsAt(0);
|
|
2670
3020
|
}
|
|
2671
3021
|
if (this._disposed) break;
|
|
2672
3022
|
let lastCloseCode = 0;
|
|
2673
3023
|
let lastCloseReason = "";
|
|
3024
|
+
let connectedAt = 0;
|
|
2674
3025
|
try {
|
|
2675
3026
|
const connUrl = await this.connectionUrlResolver(this);
|
|
2676
3027
|
infoLog?.log(`'${this.ref}': Connecting to ${sanitizeUrl(connUrl)}`);
|
|
@@ -2717,7 +3068,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2717
3068
|
this.serializationFormat,
|
|
2718
3069
|
this.id,
|
|
2719
3070
|
this.hub.hubId,
|
|
2720
|
-
++this.
|
|
3071
|
+
++this._ownHandshakeIndex
|
|
2721
3072
|
);
|
|
2722
3073
|
} catch (e) {
|
|
2723
3074
|
warnLog6?.log(`'${this.ref}': Failed to send handshake`, e);
|
|
@@ -2741,20 +3092,10 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2741
3092
|
this._pendingHandshake = void 0;
|
|
2742
3093
|
}
|
|
2743
3094
|
this._remoteHandshakeIndex = remoteHandshake.Index ?? 0;
|
|
2744
|
-
const
|
|
2745
|
-
let isPeerChanged = false;
|
|
2746
|
-
if (remoteHubId !== void 0) {
|
|
2747
|
-
isPeerChanged = this._lastRemoteHubId !== void 0 && this._lastRemoteHubId !== remoteHubId;
|
|
2748
|
-
if (isPeerChanged) {
|
|
2749
|
-
this.inboundCalls.clear();
|
|
2750
|
-
infoLog?.log(`'${this.ref}': Peer changed`);
|
|
2751
|
-
this.peerChanged.trigger();
|
|
2752
|
-
}
|
|
2753
|
-
this._lastRemoteHubId = remoteHubId;
|
|
2754
|
-
}
|
|
3095
|
+
const isPeerChanged = this._detectPeerChange(remoteHandshake);
|
|
2755
3096
|
infoLog?.log(`'${this.ref}': Handshake succeeded, peerChanged=${isPeerChanged}`);
|
|
2756
3097
|
this._setConnectionState(3 /* Connected */);
|
|
2757
|
-
|
|
3098
|
+
connectedAt = Date.now();
|
|
2758
3099
|
await this._reconnect(isPeerChanged, closedController.signal);
|
|
2759
3100
|
await whenClosed;
|
|
2760
3101
|
} catch (e) {
|
|
@@ -2768,6 +3109,8 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2768
3109
|
this._disposed = true;
|
|
2769
3110
|
break;
|
|
2770
3111
|
}
|
|
3112
|
+
if (connectedAt !== 0 && Date.now() - connectedAt >= this.prematureDisconnectTimeoutMs)
|
|
3113
|
+
this._tryIndex = 0;
|
|
2771
3114
|
this._tryIndex++;
|
|
2772
3115
|
}
|
|
2773
3116
|
} finally {
|
|
@@ -2823,6 +3166,8 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2823
3166
|
continue;
|
|
2824
3167
|
}
|
|
2825
3168
|
eligible.push(call);
|
|
3169
|
+
if (call.sentAt !== 0)
|
|
3170
|
+
call.sentAt = Date.now();
|
|
2826
3171
|
}
|
|
2827
3172
|
let unknownIds = null;
|
|
2828
3173
|
if (!isPeerChanged && eligible.length > 0) {
|
|
@@ -2839,6 +3184,7 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2839
3184
|
if (unknownIds !== null && !unknownIds.has(call.callId)) {
|
|
2840
3185
|
continue;
|
|
2841
3186
|
}
|
|
3187
|
+
call.sentAt = Date.now();
|
|
2842
3188
|
this._sendWireData(call.serializedWireData);
|
|
2843
3189
|
resentSinceYield++;
|
|
2844
3190
|
if (resentSinceYield >= YIELD_BATCH) {
|
|
@@ -2904,30 +3250,80 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
2904
3250
|
return new Set(eligible.map((c) => c.callId));
|
|
2905
3251
|
}
|
|
2906
3252
|
}
|
|
3253
|
+
/** Detects a server-peer identity change by comparing the incoming
|
|
3254
|
+
* handshake's `RemotePeerId` with the last one — C#
|
|
3255
|
+
* `RpcHandshake.GetPeerChangeKind` keys off `RemotePeerId`, not
|
|
3256
|
+
* `RemoteHubId` (an idle server peer can be replaced while keeping the
|
|
3257
|
+
* same hub id). On a change it clears the inbound tracker and fires
|
|
3258
|
+
* `peerChanged`; the very first handshake is never a change. */
|
|
3259
|
+
_detectPeerChange(handshake) {
|
|
3260
|
+
const remotePeerId = handshake.RemotePeerId;
|
|
3261
|
+
if (remotePeerId === void 0)
|
|
3262
|
+
return false;
|
|
3263
|
+
const isPeerChanged = this._lastRemotePeerId !== void 0 && this._lastRemotePeerId !== remotePeerId;
|
|
3264
|
+
this._lastRemotePeerId = remotePeerId;
|
|
3265
|
+
if (isPeerChanged) {
|
|
3266
|
+
this.inboundCalls.clear();
|
|
3267
|
+
infoLog?.log(`'${this.ref}': Peer changed`);
|
|
3268
|
+
this.peerChanged.trigger();
|
|
3269
|
+
}
|
|
3270
|
+
return isPeerChanged;
|
|
3271
|
+
}
|
|
2907
3272
|
_onHandshakeReceived(handshake) {
|
|
2908
3273
|
this._pendingHandshake?.resolve(handshake);
|
|
2909
3274
|
}
|
|
2910
3275
|
close() {
|
|
2911
3276
|
this._disposed = true;
|
|
3277
|
+
this._stopController.abort();
|
|
2912
3278
|
super.close();
|
|
2913
3279
|
}
|
|
2914
3280
|
};
|
|
2915
3281
|
var RpcServerPeer = class extends RpcPeer {
|
|
2916
|
-
|
|
2917
|
-
|
|
3282
|
+
/** Grace period before an idle (disconnected) server peer is stopped and
|
|
3283
|
+
* removed from `hub.peers`; a re-`accept()` within it cancels removal.
|
|
3284
|
+
* Snapshotted from `hub.limits` at construction (F11). */
|
|
3285
|
+
serverPeerCloseTimeoutMs;
|
|
3286
|
+
_closeTimer;
|
|
3287
|
+
_isClosed = false;
|
|
2918
3288
|
constructor(hub, ref) {
|
|
2919
3289
|
super(hub, ref);
|
|
3290
|
+
this.serverPeerCloseTimeoutMs = hub.limits.serverPeerCloseTimeoutMs;
|
|
2920
3291
|
}
|
|
2921
3292
|
/** Accept an incoming connection — sets up message handling and handshake response. */
|
|
2922
3293
|
accept(conn) {
|
|
3294
|
+
this._clearCloseTimer();
|
|
2923
3295
|
this._setConnectionState(1 /* Connecting */);
|
|
2924
3296
|
this.setupConnection(conn);
|
|
2925
3297
|
this._setConnectionState(2 /* Handshaking */);
|
|
2926
3298
|
conn.closed.add(() => {
|
|
2927
3299
|
if (this._connection !== conn && this._connection !== void 0) return;
|
|
2928
3300
|
this.sharedObjects.disconnectAll();
|
|
3301
|
+
this._armCloseTimer();
|
|
2929
3302
|
});
|
|
2930
3303
|
}
|
|
3304
|
+
close() {
|
|
3305
|
+
this._isClosed = true;
|
|
3306
|
+
this._clearCloseTimer();
|
|
3307
|
+
super.close();
|
|
3308
|
+
}
|
|
3309
|
+
_armCloseTimer() {
|
|
3310
|
+
this._clearCloseTimer();
|
|
3311
|
+
if (this._isClosed) return;
|
|
3312
|
+
const timeout = this.serverPeerCloseTimeoutMs;
|
|
3313
|
+
if (!(timeout > 0) || !Number.isFinite(timeout)) return;
|
|
3314
|
+
this._closeTimer = setTimeout(() => {
|
|
3315
|
+
this._closeTimer = void 0;
|
|
3316
|
+
if (this._connection === void 0)
|
|
3317
|
+
this.close();
|
|
3318
|
+
}, timeout);
|
|
3319
|
+
this._closeTimer.unref?.();
|
|
3320
|
+
}
|
|
3321
|
+
_clearCloseTimer() {
|
|
3322
|
+
if (this._closeTimer !== void 0) {
|
|
3323
|
+
clearTimeout(this._closeTimer);
|
|
3324
|
+
this._closeTimer = void 0;
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
2931
3327
|
_onHandshakeReceived(_handshake) {
|
|
2932
3328
|
if (this._connection !== void 0) {
|
|
2933
3329
|
this.hub.systemCallSender.handshake(
|
|
@@ -2935,7 +3331,7 @@ var RpcServerPeer = class extends RpcPeer {
|
|
|
2935
3331
|
this.serializationFormat,
|
|
2936
3332
|
this.id,
|
|
2937
3333
|
this.hub.hubId,
|
|
2938
|
-
|
|
3334
|
+
++this._ownHandshakeIndex
|
|
2939
3335
|
);
|
|
2940
3336
|
this._setConnectionState(3 /* Connected */);
|
|
2941
3337
|
}
|
|
@@ -2974,6 +3370,12 @@ var RpcLimits = class _RpcLimits {
|
|
|
2974
3370
|
/** Process-wide default. Hubs read from this when they're constructed
|
|
2975
3371
|
* unless caller assigns a custom instance to `hub.limits`. */
|
|
2976
3372
|
static Default = new _RpcLimits();
|
|
3373
|
+
/** Dev preset with a relaxed keep-alive timeout (5 min), matching .NET's
|
|
3374
|
+
* debugger-attached `KeepAlivePeriod` (RpcLimits.cs:46-54). Activate
|
|
3375
|
+
* explicitly — e.g. `RpcLimits.Default = RpcLimits.Debug` or per-hub
|
|
3376
|
+
* `hub.limits = RpcLimits.Debug`. Never switched on automatically: the
|
|
3377
|
+
* TS side can't detect a debugger on the server (decision D4). */
|
|
3378
|
+
static Debug = new _RpcLimits({ keepAliveTimeoutMs: 3e5 });
|
|
2977
3379
|
/** Max time to wait for the WebSocket to enter the OPEN state. On a
|
|
2978
3380
|
* hung connect (mobile after network change, half-open after sleep)
|
|
2979
3381
|
* the browser can take ~2 min to emit `onerror`/`onclose`; without
|
|
@@ -2981,6 +3383,22 @@ var RpcLimits = class _RpcLimits {
|
|
|
2981
3383
|
connectTimeoutMs = 1e4;
|
|
2982
3384
|
/** Max time to wait for the server's handshake response after WS opens. */
|
|
2983
3385
|
handshakeTimeoutMs = 1e4;
|
|
3386
|
+
/** If a connection lived less than this before dropping, a graceful close
|
|
3387
|
+
* still counts as a failed attempt — the client keeps its growing
|
|
3388
|
+
* `_tryIndex` instead of resetting it, so a crash-looping server sees
|
|
3389
|
+
* increasing reconnect delays via `RetryDelaySeq`. Mirrors .NET
|
|
3390
|
+
* `RpcLimits.PrematureDisconnectTimeout` (RpcLimits.cs:18). */
|
|
3391
|
+
prematureDisconnectTimeoutMs = 15e3;
|
|
3392
|
+
/** Max completed inbound calls retained for duplicate-frame dedup
|
|
3393
|
+
* (a resent call id re-sends the computed result instead of
|
|
3394
|
+
* re-executing the handler). Deviation from .NET, which unregisters a
|
|
3395
|
+
* call once its result is sent: TS clients blind-resend on reconnect,
|
|
3396
|
+
* so completed calls are retained — bounded by this limit (oldest
|
|
3397
|
+
* evicted first) to cap memory on long-lived connections. */
|
|
3398
|
+
completedInboundCallsLimit = 1e3;
|
|
3399
|
+
/** How often each peer's maintenance loop scans outbound calls for
|
|
3400
|
+
* timeouts (R12). Mirrors .NET `RpcLimits.CallTimeoutCheckPeriod`. */
|
|
3401
|
+
callTimeoutCheckPeriodMs = 1e3;
|
|
2984
3402
|
/** Outbound `$sys.KeepAlive` send period. */
|
|
2985
3403
|
keepAlivePeriodMs = 1e4;
|
|
2986
3404
|
/** Reaper threshold — connection is force-closed (and the reconnect
|
|
@@ -2990,6 +3408,13 @@ var RpcLimits = class _RpcLimits {
|
|
|
2990
3408
|
* `keepAlivePeriodMs + stall_duration`, so with these defaults the
|
|
2991
3409
|
* tolerated stall is ~15 s. */
|
|
2992
3410
|
keepAliveTimeoutMs = 25e3;
|
|
3411
|
+
/** How long a server peer stays in `hub.peers` after its connection closes
|
|
3412
|
+
* before it's stopped and removed. A same-peer reconnect (`accept()`)
|
|
3413
|
+
* within this window cancels the pending removal, so brief drops don't
|
|
3414
|
+
* discard the peer's trackers. Mirrors .NET's server-peer shutdown timeout
|
|
3415
|
+
* (`RpcPeerOptions.ServerPeerShutdownTimeoutProvider`, 3-15 min); the TS
|
|
3416
|
+
* port uses a fixed floor-matching default. */
|
|
3417
|
+
serverPeerCloseTimeoutMs = 18e4;
|
|
2993
3418
|
constructor(overrides) {
|
|
2994
3419
|
if (overrides) Object.assign(this, overrides);
|
|
2995
3420
|
}
|
|
@@ -3000,7 +3425,7 @@ import { RetryDelaySeq, RetryDelayer } from "@actuallab/core";
|
|
|
3000
3425
|
var RpcClientPeerReconnectDelayer = class extends RetryDelayer {
|
|
3001
3426
|
constructor() {
|
|
3002
3427
|
super();
|
|
3003
|
-
this.delays = RetryDelaySeq.
|
|
3428
|
+
this.delays = RetryDelaySeq.exp(100, 1e4);
|
|
3004
3429
|
}
|
|
3005
3430
|
};
|
|
3006
3431
|
|
|
@@ -3008,13 +3433,14 @@ var RpcClientPeerReconnectDelayer = class extends RetryDelayer {
|
|
|
3008
3433
|
var { warnLog: warnLog7 } = getLogs("RpcServiceHost");
|
|
3009
3434
|
var RpcServiceHost = class {
|
|
3010
3435
|
_methods = /* @__PURE__ */ new Map();
|
|
3011
|
-
register(def, impl) {
|
|
3436
|
+
register(def, impl, receiver = impl) {
|
|
3012
3437
|
for (const methodDef of def.methods.values()) {
|
|
3013
3438
|
const fn = impl[methodDef.name];
|
|
3014
3439
|
if (!fn) continue;
|
|
3015
3440
|
this._methods.set(wireMethodName(methodDef), {
|
|
3016
3441
|
def: methodDef,
|
|
3017
|
-
fn
|
|
3442
|
+
fn,
|
|
3443
|
+
receiver
|
|
3018
3444
|
});
|
|
3019
3445
|
}
|
|
3020
3446
|
}
|
|
@@ -3025,9 +3451,9 @@ var RpcServiceHost = class {
|
|
|
3025
3451
|
throw new Error(`Method not found: ${wireMethod}`);
|
|
3026
3452
|
}
|
|
3027
3453
|
if (context !== void 0 && entry.def.callTypeId !== 0) {
|
|
3028
|
-
return await entry.fn(...args, context);
|
|
3454
|
+
return await entry.fn.call(entry.receiver, ...args, context);
|
|
3029
3455
|
}
|
|
3030
|
-
return await entry.fn(...args);
|
|
3456
|
+
return await entry.fn.call(entry.receiver, ...args);
|
|
3031
3457
|
}
|
|
3032
3458
|
getMethodDef(wireMethod) {
|
|
3033
3459
|
return this._methods.get(wireMethod)?.def;
|
|
@@ -3035,6 +3461,7 @@ var RpcServiceHost = class {
|
|
|
3035
3461
|
};
|
|
3036
3462
|
|
|
3037
3463
|
// src/rpc-decorators.ts
|
|
3464
|
+
import { ownMetadata, resolveArgCount } from "@actuallab/core";
|
|
3038
3465
|
var METHODS_META = /* @__PURE__ */ Symbol.for("actuallab.methods");
|
|
3039
3466
|
var SERVICE_META = /* @__PURE__ */ Symbol.for("actuallab.service");
|
|
3040
3467
|
function getMethodsMeta(cls) {
|
|
@@ -3045,17 +3472,17 @@ function getServiceMeta(cls) {
|
|
|
3045
3472
|
}
|
|
3046
3473
|
function rpcService(serviceName) {
|
|
3047
3474
|
return function(_target, context) {
|
|
3048
|
-
const meta = context.metadata
|
|
3475
|
+
const meta = ownMetadata(context.metadata, SERVICE_META);
|
|
3049
3476
|
meta.name = serviceName;
|
|
3050
3477
|
};
|
|
3051
3478
|
}
|
|
3052
3479
|
function rpcMethod(options) {
|
|
3053
3480
|
return function(target, context) {
|
|
3054
3481
|
const methodName = String(context.name);
|
|
3055
|
-
const methods = context.metadata
|
|
3482
|
+
const methods = ownMetadata(context.metadata, METHODS_META);
|
|
3056
3483
|
methods[methodName] = {
|
|
3057
3484
|
...methods[methodName],
|
|
3058
|
-
argCount: target
|
|
3485
|
+
argCount: resolveArgCount(target, options?.argCount, methodName),
|
|
3059
3486
|
returns: options?.returns,
|
|
3060
3487
|
remoteExecutionMode: options?.remoteExecutionMode
|
|
3061
3488
|
};
|
|
@@ -3601,7 +4028,7 @@ var RpcHub = class {
|
|
|
3601
4028
|
hubId;
|
|
3602
4029
|
peers = /* @__PURE__ */ new Map();
|
|
3603
4030
|
serviceHost;
|
|
3604
|
-
systemCallSender
|
|
4031
|
+
systemCallSender;
|
|
3605
4032
|
systemCallHandler = new RpcSystemCallHandler();
|
|
3606
4033
|
/** Method registry for compact format hash ↔ name resolution.
|
|
3607
4034
|
* Created lazily, populated by addService/addClient. */
|
|
@@ -3609,6 +4036,14 @@ var RpcHub = class {
|
|
|
3609
4036
|
/** Shared reconnect delayer used by every client peer in this hub. Swap in
|
|
3610
4037
|
* a custom subclass (e.g. app-level signal-gated) before peers start. */
|
|
3611
4038
|
reconnectDelayer = new RpcClientPeerReconnectDelayer();
|
|
4039
|
+
/** Cache of client proxies keyed by peer, then by service name — so
|
|
4040
|
+
* repeated {@link addClient} calls for the same service+peer return one
|
|
4041
|
+
* proxy. All consumers then share a single computed/call/invalidation
|
|
4042
|
+
* stream per logical value, matching .NET's singleton client proxies (F8).
|
|
4043
|
+
* A later def registered under the same name extends the existing proxy
|
|
4044
|
+
* with its extra methods (superset), so no consumer ever receives a proxy
|
|
4045
|
+
* missing methods the def declares. */
|
|
4046
|
+
_clientProxies = /* @__PURE__ */ new WeakMap();
|
|
3612
4047
|
/** Connection-lifecycle timing limits. Peers constructed against this hub
|
|
3613
4048
|
* read their initial `*Ms` field values from this instance; later
|
|
3614
4049
|
* mutations affect only peers built afterward (existing peers can be
|
|
@@ -3626,6 +4061,7 @@ var RpcHub = class {
|
|
|
3626
4061
|
constructor(hubId) {
|
|
3627
4062
|
this.hubId = hubId ?? crypto.randomUUID();
|
|
3628
4063
|
this.serviceHost = new RpcServiceHost();
|
|
4064
|
+
this.systemCallSender = this._createSystemCallSender();
|
|
3629
4065
|
this.systemCallSender.registry = this.registry;
|
|
3630
4066
|
for (const methodName of Object.values(RpcSystemCalls)) {
|
|
3631
4067
|
this.registry.register(methodName);
|
|
@@ -3686,50 +4122,38 @@ var RpcHub = class {
|
|
|
3686
4122
|
if (!fn) continue;
|
|
3687
4123
|
wrappedImpl[methodDef.name] = methodDef.callTypeId !== 0 ? this._wrapServerMethod(methodDef, fn, impl) : fn;
|
|
3688
4124
|
}
|
|
3689
|
-
this.serviceHost.register(def, wrappedImpl);
|
|
4125
|
+
this.serviceHost.register(def, wrappedImpl, impl);
|
|
3690
4126
|
this.registry.registerService(def.name, def.methods);
|
|
3691
4127
|
}
|
|
3692
|
-
/** Create a typed client proxy for a service on a remote peer.
|
|
4128
|
+
/** Create a typed client proxy for a service on a remote peer. Repeated calls
|
|
4129
|
+
* for the same (peer, service name) return one shared proxy; a def carrying
|
|
4130
|
+
* extra methods extends it (F8). */
|
|
3693
4131
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T is used for caller-specified proxy type
|
|
3694
4132
|
addClient(peer, defOrContract) {
|
|
3695
4133
|
const def = this._resolveServiceDef(defOrContract);
|
|
3696
4134
|
this.registry.registerService(def.name, def.methods);
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
byArgCount = /* @__PURE__ */ new Map();
|
|
3702
|
-
overloads.set(methodDef.name, byArgCount);
|
|
3703
|
-
}
|
|
3704
|
-
byArgCount.set(
|
|
3705
|
-
methodDef.argCount,
|
|
3706
|
-
this._createClientMethod(peer, methodDef)
|
|
3707
|
-
);
|
|
4135
|
+
let byService = this._clientProxies.get(peer);
|
|
4136
|
+
if (byService === void 0) {
|
|
4137
|
+
byService = /* @__PURE__ */ new Map();
|
|
4138
|
+
this._clientProxies.set(peer, byService);
|
|
3708
4139
|
}
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
methods.set(name, singleFn);
|
|
3714
|
-
} else {
|
|
3715
|
-
methods.set(name, (...args) => {
|
|
3716
|
-
const fn = byArgCount.get(args.length);
|
|
3717
|
-
if (!fn)
|
|
3718
|
-
throw new Error(
|
|
3719
|
-
`No overload of ${name} accepts ${args.length} arguments`
|
|
3720
|
-
);
|
|
3721
|
-
return fn(...args);
|
|
3722
|
-
});
|
|
3723
|
-
}
|
|
4140
|
+
let entry = byService.get(def.name);
|
|
4141
|
+
if (entry === void 0) {
|
|
4142
|
+
entry = this._createClientProxy();
|
|
4143
|
+
byService.set(def.name, entry);
|
|
3724
4144
|
}
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
});
|
|
4145
|
+
this._extendClientProxy(peer, entry, def);
|
|
4146
|
+
return entry.proxy;
|
|
3728
4147
|
}
|
|
3729
4148
|
close() {
|
|
3730
4149
|
for (const peer of this.peers.values()) peer.close();
|
|
3731
4150
|
this.peers.clear();
|
|
3732
4151
|
}
|
|
4152
|
+
/** Create the hub's system-call sender. Override in FusionHub to add
|
|
4153
|
+
* Fusion-specific system calls (e.g. $sys-c.Invalidate). */
|
|
4154
|
+
_createSystemCallSender() {
|
|
4155
|
+
return new RpcSystemCallSender();
|
|
4156
|
+
}
|
|
3733
4157
|
/** Build an RpcServiceDef from decorator metadata on a contract class. Override in FusionHub for compute support. */
|
|
3734
4158
|
_buildServiceDef(cls) {
|
|
3735
4159
|
const svcMeta = getServiceMeta(cls);
|
|
@@ -3787,12 +4211,10 @@ var RpcHub = class {
|
|
|
3787
4211
|
throw new Error(
|
|
3788
4212
|
`Expected stream reference, got: ${JSON.stringify(result)}`
|
|
3789
4213
|
);
|
|
3790
|
-
|
|
3791
|
-
peer.remoteObjects.register(stream);
|
|
3792
|
-
return stream;
|
|
4214
|
+
return new RpcStream(ref, peer);
|
|
3793
4215
|
};
|
|
3794
4216
|
}
|
|
3795
|
-
const callOptions = methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode } : void 0;
|
|
4217
|
+
const callOptions = methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default || methodDef.timeouts !== void 0 ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode, timeouts: methodDef.timeouts } : void 0;
|
|
3796
4218
|
return async (...args) => {
|
|
3797
4219
|
if (mustCheckConnection && !peer.isConnected)
|
|
3798
4220
|
throw new Error("Outbound call failed: not connected and AwaitForConnection is not set.");
|
|
@@ -3802,7 +4224,58 @@ var RpcHub = class {
|
|
|
3802
4224
|
return resolveStreamRefs(result, peer);
|
|
3803
4225
|
};
|
|
3804
4226
|
}
|
|
4227
|
+
// Private methods
|
|
4228
|
+
_createClientProxy() {
|
|
4229
|
+
const methods = /* @__PURE__ */ new Map();
|
|
4230
|
+
const proxy = new Proxy({}, {
|
|
4231
|
+
get: (_target, prop) => typeof prop === "string" ? methods.get(prop) : void 0
|
|
4232
|
+
});
|
|
4233
|
+
return { proxy, methods, overloads: /* @__PURE__ */ new Map() };
|
|
4234
|
+
}
|
|
4235
|
+
_extendClientProxy(peer, entry, def) {
|
|
4236
|
+
for (const methodDef of def.methods.values()) {
|
|
4237
|
+
const existing = entry.overloads.get(methodDef.name)?.get(methodDef.argCount);
|
|
4238
|
+
if (existing !== void 0 && !isSameWireMethod(existing.def, methodDef))
|
|
4239
|
+
throw new Error(
|
|
4240
|
+
`RpcHub.addClient: conflicting definition of '${wireMethodName(methodDef)}' under service '${def.name}' \u2014 a different signature is already registered.`
|
|
4241
|
+
);
|
|
4242
|
+
}
|
|
4243
|
+
const changed = /* @__PURE__ */ new Set();
|
|
4244
|
+
for (const methodDef of def.methods.values()) {
|
|
4245
|
+
let byArgCount = entry.overloads.get(methodDef.name);
|
|
4246
|
+
if (byArgCount === void 0) {
|
|
4247
|
+
byArgCount = /* @__PURE__ */ new Map();
|
|
4248
|
+
entry.overloads.set(methodDef.name, byArgCount);
|
|
4249
|
+
}
|
|
4250
|
+
if (byArgCount.has(methodDef.argCount))
|
|
4251
|
+
continue;
|
|
4252
|
+
byArgCount.set(methodDef.argCount, {
|
|
4253
|
+
def: methodDef,
|
|
4254
|
+
fn: this._createClientMethod(peer, methodDef)
|
|
4255
|
+
});
|
|
4256
|
+
changed.add(methodDef.name);
|
|
4257
|
+
}
|
|
4258
|
+
for (const name of changed) {
|
|
4259
|
+
const byArgCount = entry.overloads.get(name);
|
|
4260
|
+
entry.methods.set(name, buildOverloadFn(name, byArgCount));
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
3805
4263
|
};
|
|
4264
|
+
function buildOverloadFn(name, byArgCount) {
|
|
4265
|
+
if (byArgCount.size === 1) {
|
|
4266
|
+
const [[, only]] = byArgCount;
|
|
4267
|
+
return only.fn;
|
|
4268
|
+
}
|
|
4269
|
+
return (...args) => {
|
|
4270
|
+
const overload = byArgCount.get(args.length);
|
|
4271
|
+
if (overload === void 0)
|
|
4272
|
+
throw new Error(`No overload of ${name} accepts ${args.length} arguments`);
|
|
4273
|
+
return overload.fn(...args);
|
|
4274
|
+
};
|
|
4275
|
+
}
|
|
4276
|
+
function isSameWireMethod(a, b) {
|
|
4277
|
+
return a.wireArgCount === b.wireArgCount && a.callTypeId === b.callTypeId && a.stream === b.stream && a.noWait === b.noWait && a.remoteExecutionMode === b.remoteExecutionMode;
|
|
4278
|
+
}
|
|
3806
4279
|
|
|
3807
4280
|
// src/rpc-peer-ref-builder.ts
|
|
3808
4281
|
var RpcPeerRefBuilder = class {
|
|
@@ -3869,7 +4342,7 @@ function createMethod(peer, methodDef) {
|
|
|
3869
4342
|
throw new Error("Outbound call failed: not connected and AwaitForConnection is not set.");
|
|
3870
4343
|
const callArgs = args.slice(0, methodDef.argCount);
|
|
3871
4344
|
const wireName = wireMethodName(methodDef);
|
|
3872
|
-
const options = methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode } : void 0;
|
|
4345
|
+
const options = methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default || methodDef.timeouts !== void 0 ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode, timeouts: methodDef.timeouts } : void 0;
|
|
3873
4346
|
const outboundCall = peer.call(wireName, callArgs, options);
|
|
3874
4347
|
return outboundCall.result.promise;
|
|
3875
4348
|
};
|
|
@@ -4064,8 +4537,10 @@ export {
|
|
|
4064
4537
|
ENVELOPE_DELIMITER,
|
|
4065
4538
|
FRAME_DELIMITER,
|
|
4066
4539
|
IncreasingSeqCompressor,
|
|
4540
|
+
REMOTE_EXCEPTION_TYPE_REF,
|
|
4067
4541
|
RPC_CLOSE_CODE_UNSUPPORTED_FORMAT,
|
|
4068
4542
|
RpcCallStage,
|
|
4543
|
+
RpcCallTimeouts,
|
|
4069
4544
|
RpcCallTypeId,
|
|
4070
4545
|
RpcClientPeer,
|
|
4071
4546
|
RpcClientPeerReconnectDelayer,
|
|
@@ -4120,6 +4595,7 @@ export {
|
|
|
4120
4595
|
isConnected,
|
|
4121
4596
|
likelyConnected,
|
|
4122
4597
|
parseStreamRef,
|
|
4598
|
+
readPolymorphismMarker,
|
|
4123
4599
|
resolveStreamRefs,
|
|
4124
4600
|
rpcMethod,
|
|
4125
4601
|
rpcService,
|
|
@@ -4131,6 +4607,7 @@ export {
|
|
|
4131
4607
|
splitBinaryFrame,
|
|
4132
4608
|
splitCompactBinaryFrame,
|
|
4133
4609
|
splitFrame,
|
|
4610
|
+
toExceptionInfo,
|
|
4134
4611
|
wireMethodName,
|
|
4135
4612
|
xxh3_64,
|
|
4136
4613
|
xxh3_64str
|