@actuallab/rpc 12.2.21 → 12.3.2
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 +352 -225
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -20
- package/dist/index.d.ts +74 -20
- package/dist/index.js +351 -224
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -645,12 +645,200 @@ var RpcInboundCallTracker = class {
|
|
|
645
645
|
|
|
646
646
|
// src/rpc-stream.ts
|
|
647
647
|
import Denque from "denque";
|
|
648
|
-
import { PromiseSource as
|
|
648
|
+
import { PromiseSource as PromiseSource4 } from "@actuallab/core";
|
|
649
649
|
|
|
650
650
|
// src/rpc-object.ts
|
|
651
651
|
var RpcObjectKind = { Local: 0, Remote: 1 };
|
|
652
652
|
|
|
653
|
+
// src/rpc-stream-sender.ts
|
|
654
|
+
import { PromiseSource as PromiseSource3 } from "@actuallab/core";
|
|
655
|
+
var DEFAULT_ACK_PERIOD = 256;
|
|
656
|
+
var DEFAULT_ACK_ADVANCE = 128;
|
|
657
|
+
var RpcStreamSender = class _RpcStreamSender {
|
|
658
|
+
static _emptyGuid = "00000000-0000-0000-0000-000000000000";
|
|
659
|
+
id;
|
|
660
|
+
kind = RpcObjectKind.Local;
|
|
661
|
+
allowReconnect;
|
|
662
|
+
isRealTime;
|
|
663
|
+
canSkipTo;
|
|
664
|
+
peer;
|
|
665
|
+
ackPeriod;
|
|
666
|
+
ackAdvance;
|
|
667
|
+
_nextIndex = 0;
|
|
668
|
+
_ended = false;
|
|
669
|
+
_started = new PromiseSource3();
|
|
670
|
+
_lastAckedIndex = 0;
|
|
671
|
+
_ackWaiting = null;
|
|
672
|
+
_resetRequested = false;
|
|
673
|
+
constructor(peer, ackPeriod = DEFAULT_ACK_PERIOD, ackAdvance = DEFAULT_ACK_ADVANCE, allowReconnect = true, isRealTime = false, canSkipTo = () => true) {
|
|
674
|
+
const localId = peer.sharedObjects.nextId();
|
|
675
|
+
this.id = { hostId: peer.hub.hubId, localId };
|
|
676
|
+
this.allowReconnect = allowReconnect;
|
|
677
|
+
this.isRealTime = isRealTime;
|
|
678
|
+
this.canSkipTo = canSkipTo;
|
|
679
|
+
this.peer = peer;
|
|
680
|
+
this.ackPeriod = ackPeriod;
|
|
681
|
+
this.ackAdvance = ackAdvance;
|
|
682
|
+
}
|
|
683
|
+
/** Returns the stream reference string for the $sys.Ok response. */
|
|
684
|
+
toRef() {
|
|
685
|
+
return `${this.id.hostId},${this.id.localId},${this.ackPeriod},${this.ackAdvance},${this.allowReconnect ? "1" : "0"},${this.isRealTime ? "1" : "0"}`;
|
|
686
|
+
}
|
|
687
|
+
/** Called by system call handler when $sys.Ack is received from the client. */
|
|
688
|
+
onAck(nextIndex, hostId) {
|
|
689
|
+
const mustReset = hostId !== "" && hostId !== _RpcStreamSender._emptyGuid;
|
|
690
|
+
if (!this._started.isCompleted) {
|
|
691
|
+
this._started.resolve();
|
|
692
|
+
}
|
|
693
|
+
if (mustReset && this.isRealTime) {
|
|
694
|
+
this._nextIndex = nextIndex;
|
|
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;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
/** Called by system call handler when $sys.AckEnd is received from the client. */
|
|
708
|
+
onAckEnd(_hostId) {
|
|
709
|
+
this._ended = true;
|
|
710
|
+
this.peer.sharedObjects.unregister(this);
|
|
711
|
+
}
|
|
712
|
+
/** Send a single item to the client. */
|
|
713
|
+
sendItem(item) {
|
|
714
|
+
if (this._ended) return;
|
|
715
|
+
const conn = this.peer.connection;
|
|
716
|
+
if (!conn) return;
|
|
717
|
+
this.peer.hub.systemCallSender.item(
|
|
718
|
+
conn,
|
|
719
|
+
this.peer.format,
|
|
720
|
+
this.id.localId,
|
|
721
|
+
this._nextIndex,
|
|
722
|
+
item
|
|
723
|
+
);
|
|
724
|
+
this._nextIndex++;
|
|
725
|
+
}
|
|
726
|
+
/** Send a batch of items to the client. */
|
|
727
|
+
sendBatch(items) {
|
|
728
|
+
if (this._ended || items.length === 0) return;
|
|
729
|
+
const conn = this.peer.connection;
|
|
730
|
+
if (!conn) return;
|
|
731
|
+
this.peer.hub.systemCallSender.batch(
|
|
732
|
+
conn,
|
|
733
|
+
this.peer.format,
|
|
734
|
+
this.id.localId,
|
|
735
|
+
this._nextIndex,
|
|
736
|
+
items
|
|
737
|
+
);
|
|
738
|
+
this._nextIndex += items.length;
|
|
739
|
+
}
|
|
740
|
+
/** Signal stream completion to the client. */
|
|
741
|
+
sendEnd(error) {
|
|
742
|
+
if (this._ended) return;
|
|
743
|
+
this._ended = true;
|
|
744
|
+
const conn = this.peer.connection;
|
|
745
|
+
if (!conn) return;
|
|
746
|
+
const errorInfo = error ? { TypeRef: "System.Exception", Message: error.message } : { TypeRef: "", Message: "" };
|
|
747
|
+
this.peer.hub.systemCallSender.end(
|
|
748
|
+
conn,
|
|
749
|
+
this.peer.format,
|
|
750
|
+
this.id.localId,
|
|
751
|
+
this._nextIndex,
|
|
752
|
+
errorInfo
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Consume an AsyncIterable and send all items to the client.
|
|
757
|
+
* Waits for the client's initial Ack before starting to pump items.
|
|
758
|
+
*
|
|
759
|
+
* When `isRealTime` is true, applies flow control based on `ackAdvance`:
|
|
760
|
+
* if the sender is `ackAdvance` items ahead of the last ACK, it enters
|
|
761
|
+
* skip mode — draining the source and discarding items until it finds
|
|
762
|
+
* one where `canSkipTo` returns true.
|
|
763
|
+
*/
|
|
764
|
+
async writeFrom(source) {
|
|
765
|
+
await this._started.promise;
|
|
766
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
767
|
+
try {
|
|
768
|
+
while (true) {
|
|
769
|
+
const next = await iterator.next();
|
|
770
|
+
if (next.done || this._ended) break;
|
|
771
|
+
const item = next.value;
|
|
772
|
+
if (this._resetRequested) {
|
|
773
|
+
if (!this.canSkipTo(item)) continue;
|
|
774
|
+
this._resetRequested = false;
|
|
775
|
+
this.sendItem(item);
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
if (this._nextIndex < this._lastAckedIndex + this.ackAdvance) {
|
|
779
|
+
this.sendItem(item);
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
if (!this.isRealTime) {
|
|
783
|
+
await this._waitForAckBudget();
|
|
784
|
+
if (this._ended) return;
|
|
785
|
+
this.sendItem(item);
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
let latestSkipTarget = this.canSkipTo(item) ? item : void 0;
|
|
789
|
+
let sourceExhausted = false;
|
|
790
|
+
while (this._nextIndex >= this._lastAckedIndex + this.ackAdvance) {
|
|
791
|
+
if (this._ended) return;
|
|
792
|
+
const n = await iterator.next();
|
|
793
|
+
if (n.done) {
|
|
794
|
+
sourceExhausted = true;
|
|
795
|
+
break;
|
|
796
|
+
}
|
|
797
|
+
if (this.canSkipTo(n.value)) {
|
|
798
|
+
latestSkipTarget = n.value;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
if (this._ended) return;
|
|
802
|
+
if (latestSkipTarget !== void 0) {
|
|
803
|
+
this.sendItem(latestSkipTarget);
|
|
804
|
+
}
|
|
805
|
+
if (sourceExhausted) {
|
|
806
|
+
if (!this._ended) this.sendEnd();
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (!this._ended) {
|
|
811
|
+
this.sendEnd();
|
|
812
|
+
}
|
|
813
|
+
} catch (e) {
|
|
814
|
+
if (!this._ended) {
|
|
815
|
+
this.sendEnd(e instanceof Error ? e : new Error(String(e)));
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/** Wait until _nextIndex < _lastAckedIndex + ackAdvance. */
|
|
820
|
+
async _waitForAckBudget() {
|
|
821
|
+
while (this._nextIndex >= this._lastAckedIndex + this.ackAdvance) {
|
|
822
|
+
if (this._ended) return;
|
|
823
|
+
this._ackWaiting = new PromiseSource3();
|
|
824
|
+
await this._ackWaiting.promise;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
// -- IRpcObject --
|
|
828
|
+
reconnect() {
|
|
829
|
+
}
|
|
830
|
+
disconnect() {
|
|
831
|
+
this._ended = true;
|
|
832
|
+
if (!this._started.isCompleted) {
|
|
833
|
+
this._started.resolve();
|
|
834
|
+
}
|
|
835
|
+
this.peer.sharedObjects.unregister(this);
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
|
|
653
839
|
// src/rpc-stream.ts
|
|
840
|
+
var DEFAULT_ACK_PERIOD2 = 30;
|
|
841
|
+
var DEFAULT_ACK_ADVANCE2 = 61;
|
|
654
842
|
function parseStreamRef(value) {
|
|
655
843
|
if (typeof value === "string") {
|
|
656
844
|
const parts = value.split(",");
|
|
@@ -683,17 +871,21 @@ function parseStreamRef(value) {
|
|
|
683
871
|
}
|
|
684
872
|
var RpcStream = class _RpcStream {
|
|
685
873
|
id;
|
|
686
|
-
kind
|
|
874
|
+
kind;
|
|
687
875
|
allowReconnect;
|
|
688
|
-
|
|
876
|
+
isRealTime;
|
|
877
|
+
canSkipTo;
|
|
689
878
|
ackPeriod;
|
|
690
879
|
ackAdvance;
|
|
691
|
-
//
|
|
692
|
-
|
|
693
|
-
//
|
|
694
|
-
|
|
695
|
-
//
|
|
696
|
-
|
|
880
|
+
// Local (origin) mode
|
|
881
|
+
localSource;
|
|
882
|
+
// Remote (target) mode
|
|
883
|
+
peer;
|
|
884
|
+
// Local-only state
|
|
885
|
+
_sender;
|
|
886
|
+
_toRefValue;
|
|
887
|
+
_whenSent;
|
|
888
|
+
// Remote-only state
|
|
697
889
|
_buffer = new Denque();
|
|
698
890
|
_nextExpectedIndex = 0;
|
|
699
891
|
_consumerWaiting = null;
|
|
@@ -703,12 +895,76 @@ var RpcStream = class _RpcStream {
|
|
|
703
895
|
_started = false;
|
|
704
896
|
_iterating = false;
|
|
705
897
|
_ackSentUpTo = 0;
|
|
706
|
-
constructor(
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
898
|
+
constructor(sourceOrRef, optionsOrPeer) {
|
|
899
|
+
if (_isAsyncIterable(sourceOrRef)) {
|
|
900
|
+
const options = optionsOrPeer ?? {};
|
|
901
|
+
this.kind = RpcObjectKind.Local;
|
|
902
|
+
this.localSource = sourceOrRef;
|
|
903
|
+
this.id = { hostId: "", localId: 0 };
|
|
904
|
+
this.allowReconnect = options.allowReconnect ?? true;
|
|
905
|
+
this.isRealTime = options.isRealTime ?? false;
|
|
906
|
+
this.canSkipTo = options.canSkipTo ?? (() => true);
|
|
907
|
+
this.ackPeriod = options.ackPeriod ?? DEFAULT_ACK_PERIOD2;
|
|
908
|
+
this.ackAdvance = options.ackAdvance ?? DEFAULT_ACK_ADVANCE2;
|
|
909
|
+
} else {
|
|
910
|
+
const ref = sourceOrRef;
|
|
911
|
+
const peer = optionsOrPeer;
|
|
912
|
+
this.kind = RpcObjectKind.Remote;
|
|
913
|
+
this.id = { hostId: ref.hostId, localId: ref.localId };
|
|
914
|
+
this.peer = peer;
|
|
915
|
+
this.allowReconnect = ref.allowReconnect;
|
|
916
|
+
this.isRealTime = ref.isRealTime;
|
|
917
|
+
this.canSkipTo = () => true;
|
|
918
|
+
this.ackPeriod = ref.ackPeriod;
|
|
919
|
+
this.ackAdvance = ref.ackAdvance;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* For local streams: resolves when the sender finishes pumping (after `toRef()` was called).
|
|
924
|
+
* Rejects if the source throws. Throws if `toRef()` hasn't been called yet.
|
|
925
|
+
*/
|
|
926
|
+
get whenSent() {
|
|
927
|
+
if (!this._whenSent)
|
|
928
|
+
throw new Error("toRef() must be called before awaiting whenSent.");
|
|
929
|
+
return this._whenSent;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* For local streams: bind to a peer, create and register an `RpcStreamSender`,
|
|
933
|
+
* start pumping items from the local source in the background, and return the
|
|
934
|
+
* stream reference.
|
|
935
|
+
*
|
|
936
|
+
* For binary peers the ref is a MessagePack-compatible object
|
|
937
|
+
* (`{ SerializedId, AckPeriod, AckAdvance, AllowReconnect }`);
|
|
938
|
+
* for text peers it is a comma-separated string.
|
|
939
|
+
*
|
|
940
|
+
* Can only be called once per stream.
|
|
941
|
+
*/
|
|
942
|
+
toRef(peer) {
|
|
943
|
+
if (this.kind !== RpcObjectKind.Local)
|
|
944
|
+
throw new Error("toRef() is only valid for local streams.");
|
|
945
|
+
if (this._sender) {
|
|
946
|
+
if (this._sender.peer !== peer)
|
|
947
|
+
throw new Error("toRef() was already called with a different peer.");
|
|
948
|
+
return this._toRefValue;
|
|
949
|
+
}
|
|
950
|
+
const sender = new RpcStreamSender(
|
|
951
|
+
peer,
|
|
952
|
+
this.ackPeriod,
|
|
953
|
+
this.ackAdvance,
|
|
954
|
+
this.allowReconnect,
|
|
955
|
+
this.isRealTime,
|
|
956
|
+
this.canSkipTo
|
|
957
|
+
);
|
|
958
|
+
peer.sharedObjects.register(sender);
|
|
959
|
+
this._sender = sender;
|
|
960
|
+
this._whenSent = sender.writeFrom(this.localSource);
|
|
961
|
+
this._toRefValue = peer.format.isBinary ? {
|
|
962
|
+
SerializedId: [sender.id.hostId, sender.id.localId],
|
|
963
|
+
AckPeriod: sender.ackPeriod,
|
|
964
|
+
AckAdvance: sender.ackAdvance,
|
|
965
|
+
AllowReconnect: sender.allowReconnect
|
|
966
|
+
} : sender.toRef();
|
|
967
|
+
return this._toRefValue;
|
|
712
968
|
}
|
|
713
969
|
/** Called by system call handler when a single item arrives ($sys.I). */
|
|
714
970
|
onItem(index, item) {
|
|
@@ -773,6 +1029,7 @@ var RpcStream = class _RpcStream {
|
|
|
773
1029
|
}
|
|
774
1030
|
// -- IRpcObject --
|
|
775
1031
|
reconnect() {
|
|
1032
|
+
if (this.kind === RpcObjectKind.Local) return;
|
|
776
1033
|
if (this.allowReconnect) {
|
|
777
1034
|
this._sendAck(this._nextExpectedIndex, true);
|
|
778
1035
|
} else {
|
|
@@ -780,6 +1037,7 @@ var RpcStream = class _RpcStream {
|
|
|
780
1037
|
}
|
|
781
1038
|
}
|
|
782
1039
|
disconnect() {
|
|
1040
|
+
if (this.kind === RpcObjectKind.Local) return;
|
|
783
1041
|
if (this._completed) return;
|
|
784
1042
|
this._completed = true;
|
|
785
1043
|
this._completionError = new Error("Peer disconnected.");
|
|
@@ -787,6 +1045,9 @@ var RpcStream = class _RpcStream {
|
|
|
787
1045
|
}
|
|
788
1046
|
// -- AsyncIterable --
|
|
789
1047
|
[Symbol.asyncIterator]() {
|
|
1048
|
+
if (this.kind === RpcObjectKind.Local) {
|
|
1049
|
+
return this.localSource[Symbol.asyncIterator]();
|
|
1050
|
+
}
|
|
790
1051
|
if (this._iterating)
|
|
791
1052
|
throw new Error("RpcStream can only be iterated once.");
|
|
792
1053
|
this._iterating = true;
|
|
@@ -806,7 +1067,7 @@ var RpcStream = class _RpcStream {
|
|
|
806
1067
|
if (self._completionError) throw self._completionError;
|
|
807
1068
|
return { value: void 0, done: true };
|
|
808
1069
|
}
|
|
809
|
-
self._consumerWaiting = new
|
|
1070
|
+
self._consumerWaiting = new PromiseSource4();
|
|
810
1071
|
await self._consumerWaiting.promise;
|
|
811
1072
|
}
|
|
812
1073
|
},
|
|
@@ -821,7 +1082,9 @@ var RpcStream = class _RpcStream {
|
|
|
821
1082
|
if (this._disposed) return;
|
|
822
1083
|
this._disposed = true;
|
|
823
1084
|
this._completed = true;
|
|
824
|
-
this.
|
|
1085
|
+
if (this.kind === RpcObjectKind.Remote) {
|
|
1086
|
+
this.peer.remoteObjects.unregister(this);
|
|
1087
|
+
}
|
|
825
1088
|
this._notifyConsumer();
|
|
826
1089
|
}
|
|
827
1090
|
// -- Private --
|
|
@@ -865,6 +1128,9 @@ var RpcStream = class _RpcStream {
|
|
|
865
1128
|
}
|
|
866
1129
|
}
|
|
867
1130
|
};
|
|
1131
|
+
function _isAsyncIterable(value) {
|
|
1132
|
+
return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
|
|
1133
|
+
}
|
|
868
1134
|
function resolveStreamRefs(value, peer) {
|
|
869
1135
|
if (value === null || value === void 0) return value;
|
|
870
1136
|
if (typeof value === "string") {
|
|
@@ -1119,177 +1385,6 @@ var RpcSystemCallSender = class _RpcSystemCallSender {
|
|
|
1119
1385
|
}
|
|
1120
1386
|
};
|
|
1121
1387
|
|
|
1122
|
-
// src/rpc-stream-sender.ts
|
|
1123
|
-
import { PromiseSource as PromiseSource4 } from "@actuallab/core";
|
|
1124
|
-
var DEFAULT_ACK_PERIOD = 256;
|
|
1125
|
-
var DEFAULT_ACK_ADVANCE = 128;
|
|
1126
|
-
var RpcStreamSender = class {
|
|
1127
|
-
id;
|
|
1128
|
-
kind = RpcObjectKind.Local;
|
|
1129
|
-
allowReconnect;
|
|
1130
|
-
isRealTime;
|
|
1131
|
-
canSkipTo;
|
|
1132
|
-
peer;
|
|
1133
|
-
ackPeriod;
|
|
1134
|
-
ackAdvance;
|
|
1135
|
-
_nextIndex = 0;
|
|
1136
|
-
_ended = false;
|
|
1137
|
-
_started = new PromiseSource4();
|
|
1138
|
-
_lastAckedIndex = 0;
|
|
1139
|
-
_ackWaiting = null;
|
|
1140
|
-
constructor(peer, ackPeriod = DEFAULT_ACK_PERIOD, ackAdvance = DEFAULT_ACK_ADVANCE, allowReconnect = true, isRealTime = false, canSkipTo = () => true) {
|
|
1141
|
-
const localId = peer.sharedObjects.nextId();
|
|
1142
|
-
this.id = { hostId: peer.hub.hubId, localId };
|
|
1143
|
-
this.allowReconnect = allowReconnect;
|
|
1144
|
-
this.isRealTime = isRealTime;
|
|
1145
|
-
this.canSkipTo = canSkipTo;
|
|
1146
|
-
this.peer = peer;
|
|
1147
|
-
this.ackPeriod = ackPeriod;
|
|
1148
|
-
this.ackAdvance = ackAdvance;
|
|
1149
|
-
}
|
|
1150
|
-
/** Returns the stream reference string for the $sys.Ok response. */
|
|
1151
|
-
toRef() {
|
|
1152
|
-
return `${this.id.hostId},${this.id.localId},${this.ackPeriod},${this.ackAdvance},${this.allowReconnect ? "1" : "0"},${this.isRealTime ? "1" : "0"}`;
|
|
1153
|
-
}
|
|
1154
|
-
/** Called by system call handler when $sys.Ack is received from the client. */
|
|
1155
|
-
onAck(nextIndex, _hostId) {
|
|
1156
|
-
if (!this._started.isCompleted) {
|
|
1157
|
-
this._started.resolve();
|
|
1158
|
-
}
|
|
1159
|
-
if (nextIndex > this._lastAckedIndex) {
|
|
1160
|
-
this._lastAckedIndex = nextIndex;
|
|
1161
|
-
}
|
|
1162
|
-
if (this._ackWaiting) {
|
|
1163
|
-
this._ackWaiting.resolve();
|
|
1164
|
-
this._ackWaiting = null;
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
/** Called by system call handler when $sys.AckEnd is received from the client. */
|
|
1168
|
-
onAckEnd(_hostId) {
|
|
1169
|
-
this._ended = true;
|
|
1170
|
-
this.peer.sharedObjects.unregister(this);
|
|
1171
|
-
}
|
|
1172
|
-
/** Send a single item to the client. */
|
|
1173
|
-
sendItem(item) {
|
|
1174
|
-
if (this._ended) return;
|
|
1175
|
-
const conn = this.peer.connection;
|
|
1176
|
-
if (!conn) return;
|
|
1177
|
-
this.peer.hub.systemCallSender.item(
|
|
1178
|
-
conn,
|
|
1179
|
-
this.peer.format,
|
|
1180
|
-
this.id.localId,
|
|
1181
|
-
this._nextIndex,
|
|
1182
|
-
item
|
|
1183
|
-
);
|
|
1184
|
-
this._nextIndex++;
|
|
1185
|
-
}
|
|
1186
|
-
/** Send a batch of items to the client. */
|
|
1187
|
-
sendBatch(items) {
|
|
1188
|
-
if (this._ended || items.length === 0) return;
|
|
1189
|
-
const conn = this.peer.connection;
|
|
1190
|
-
if (!conn) return;
|
|
1191
|
-
this.peer.hub.systemCallSender.batch(
|
|
1192
|
-
conn,
|
|
1193
|
-
this.peer.format,
|
|
1194
|
-
this.id.localId,
|
|
1195
|
-
this._nextIndex,
|
|
1196
|
-
items
|
|
1197
|
-
);
|
|
1198
|
-
this._nextIndex += items.length;
|
|
1199
|
-
}
|
|
1200
|
-
/** Signal stream completion to the client. */
|
|
1201
|
-
sendEnd(error) {
|
|
1202
|
-
if (this._ended) return;
|
|
1203
|
-
this._ended = true;
|
|
1204
|
-
const conn = this.peer.connection;
|
|
1205
|
-
if (!conn) return;
|
|
1206
|
-
const errorInfo = error ? { TypeRef: "System.Exception", Message: error.message } : { TypeRef: "", Message: "" };
|
|
1207
|
-
this.peer.hub.systemCallSender.end(
|
|
1208
|
-
conn,
|
|
1209
|
-
this.peer.format,
|
|
1210
|
-
this.id.localId,
|
|
1211
|
-
this._nextIndex,
|
|
1212
|
-
errorInfo
|
|
1213
|
-
);
|
|
1214
|
-
}
|
|
1215
|
-
/**
|
|
1216
|
-
* Consume an AsyncIterable and send all items to the client.
|
|
1217
|
-
* Waits for the client's initial Ack before starting to pump items.
|
|
1218
|
-
*
|
|
1219
|
-
* When `isRealTime` is true, applies flow control based on `ackAdvance`:
|
|
1220
|
-
* if the sender is `ackAdvance` items ahead of the last ACK, it enters
|
|
1221
|
-
* skip mode — draining the source and discarding items until it finds
|
|
1222
|
-
* one where `canSkipTo` returns true.
|
|
1223
|
-
*/
|
|
1224
|
-
async writeFrom(source) {
|
|
1225
|
-
await this._started.promise;
|
|
1226
|
-
const iterator = source[Symbol.asyncIterator]();
|
|
1227
|
-
try {
|
|
1228
|
-
while (true) {
|
|
1229
|
-
const next = await iterator.next();
|
|
1230
|
-
if (next.done || this._ended) break;
|
|
1231
|
-
const item = next.value;
|
|
1232
|
-
if (this._nextIndex < this._lastAckedIndex + this.ackAdvance) {
|
|
1233
|
-
this.sendItem(item);
|
|
1234
|
-
continue;
|
|
1235
|
-
}
|
|
1236
|
-
if (!this.isRealTime) {
|
|
1237
|
-
await this._waitForAckBudget();
|
|
1238
|
-
if (this._ended) return;
|
|
1239
|
-
this.sendItem(item);
|
|
1240
|
-
continue;
|
|
1241
|
-
}
|
|
1242
|
-
let latestSkipTarget = this.canSkipTo(item) ? item : void 0;
|
|
1243
|
-
let sourceExhausted = false;
|
|
1244
|
-
while (this._nextIndex >= this._lastAckedIndex + this.ackAdvance) {
|
|
1245
|
-
if (this._ended) return;
|
|
1246
|
-
const n = await iterator.next();
|
|
1247
|
-
if (n.done) {
|
|
1248
|
-
sourceExhausted = true;
|
|
1249
|
-
break;
|
|
1250
|
-
}
|
|
1251
|
-
if (this.canSkipTo(n.value)) {
|
|
1252
|
-
latestSkipTarget = n.value;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
if (this._ended) return;
|
|
1256
|
-
if (latestSkipTarget !== void 0) {
|
|
1257
|
-
this.sendItem(latestSkipTarget);
|
|
1258
|
-
}
|
|
1259
|
-
if (sourceExhausted) {
|
|
1260
|
-
if (!this._ended) this.sendEnd();
|
|
1261
|
-
return;
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
if (!this._ended) {
|
|
1265
|
-
this.sendEnd();
|
|
1266
|
-
}
|
|
1267
|
-
} catch (e) {
|
|
1268
|
-
if (!this._ended) {
|
|
1269
|
-
this.sendEnd(e instanceof Error ? e : new Error(String(e)));
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
/** Wait until _nextIndex < _lastAckedIndex + ackAdvance. */
|
|
1274
|
-
async _waitForAckBudget() {
|
|
1275
|
-
while (this._nextIndex >= this._lastAckedIndex + this.ackAdvance) {
|
|
1276
|
-
if (this._ended) return;
|
|
1277
|
-
this._ackWaiting = new PromiseSource4();
|
|
1278
|
-
await this._ackWaiting.promise;
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
// -- IRpcObject --
|
|
1282
|
-
reconnect() {
|
|
1283
|
-
}
|
|
1284
|
-
disconnect() {
|
|
1285
|
-
this._ended = true;
|
|
1286
|
-
if (!this._started.isCompleted) {
|
|
1287
|
-
this._started.resolve();
|
|
1288
|
-
}
|
|
1289
|
-
this.peer.sharedObjects.unregister(this);
|
|
1290
|
-
}
|
|
1291
|
-
};
|
|
1292
|
-
|
|
1293
1388
|
// src/rpc-remote-object-tracker.ts
|
|
1294
1389
|
var RpcRemoteObjectTracker = class {
|
|
1295
1390
|
_objects = /* @__PURE__ */ new Map();
|
|
@@ -1360,7 +1455,7 @@ var RpcClientPeerReconnectDelayer = class extends RetryDelayer {
|
|
|
1360
1455
|
|
|
1361
1456
|
// src/rpc-serialization-format.ts
|
|
1362
1457
|
import { Decoder as Decoder3 } from "@msgpack/msgpack";
|
|
1363
|
-
var RpcSerializationFormat = class
|
|
1458
|
+
var RpcSerializationFormat = class {
|
|
1364
1459
|
/** Create a reusable binary encoder for this format. Returns undefined for text formats. */
|
|
1365
1460
|
createEncoder() {
|
|
1366
1461
|
return void 0;
|
|
@@ -1369,19 +1464,23 @@ var RpcSerializationFormat = class _RpcSerializationFormat {
|
|
|
1369
1464
|
createDecoder() {
|
|
1370
1465
|
return void 0;
|
|
1371
1466
|
}
|
|
1372
|
-
//
|
|
1373
|
-
static
|
|
1374
|
-
static
|
|
1375
|
-
|
|
1376
|
-
|
|
1467
|
+
// Named format instances — initialized in initFormats() below (after subclasses are defined)
|
|
1468
|
+
static SystemJsonV5;
|
|
1469
|
+
static SystemJsonV5NP;
|
|
1470
|
+
static NewtonsoftJsonV5;
|
|
1471
|
+
static NewtonsoftJsonV5NP;
|
|
1472
|
+
static MessagePackV6;
|
|
1473
|
+
static MemoryPackV6;
|
|
1474
|
+
static MessagePackV6C;
|
|
1475
|
+
static MemoryPackV6C;
|
|
1476
|
+
static All;
|
|
1477
|
+
/** Convenience shortcut for RpcSerializationFormatResolver.Default.get(key). */
|
|
1377
1478
|
static get(key) {
|
|
1378
|
-
|
|
1379
|
-
if (!format)
|
|
1380
|
-
throw new Error(`Unknown RPC serialization format: "${key}"`);
|
|
1381
|
-
return format;
|
|
1479
|
+
return RpcSerializationFormatResolver.Default.get(key);
|
|
1382
1480
|
}
|
|
1481
|
+
/** Convenience shortcut for RpcSerializationFormatResolver.Default.tryGet(key). */
|
|
1383
1482
|
static tryGet(key) {
|
|
1384
|
-
return
|
|
1483
|
+
return RpcSerializationFormatResolver.Default.tryGet(key);
|
|
1385
1484
|
}
|
|
1386
1485
|
};
|
|
1387
1486
|
var RpcJsonSerializationFormat = class extends RpcSerializationFormat {
|
|
@@ -1464,25 +1563,54 @@ var RpcMessagePackCompactSerializationFormat = class extends RpcSerializationFor
|
|
|
1464
1563
|
return new Decoder3();
|
|
1465
1564
|
}
|
|
1466
1565
|
};
|
|
1467
|
-
RpcSerializationFormat.
|
|
1468
|
-
RpcSerializationFormat.
|
|
1469
|
-
RpcSerializationFormat.
|
|
1470
|
-
RpcSerializationFormat.
|
|
1471
|
-
RpcSerializationFormat.
|
|
1472
|
-
|
|
1473
|
-
);
|
|
1474
|
-
RpcSerializationFormat.
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
RpcSerializationFormat.
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
RpcSerializationFormat.
|
|
1481
|
-
|
|
1482
|
-
|
|
1566
|
+
RpcSerializationFormat.SystemJsonV5 = new RpcJsonSerializationFormat("json5");
|
|
1567
|
+
RpcSerializationFormat.SystemJsonV5NP = new RpcJsonSerializationFormat("json5np");
|
|
1568
|
+
RpcSerializationFormat.NewtonsoftJsonV5 = new RpcJsonSerializationFormat("njson5");
|
|
1569
|
+
RpcSerializationFormat.NewtonsoftJsonV5NP = new RpcJsonSerializationFormat("njson5np");
|
|
1570
|
+
RpcSerializationFormat.MessagePackV6 = new RpcMessagePackSerializationFormat("msgpack6");
|
|
1571
|
+
RpcSerializationFormat.MemoryPackV6 = new RpcMessagePackSerializationFormat("mempack6");
|
|
1572
|
+
RpcSerializationFormat.MessagePackV6C = new RpcMessagePackCompactSerializationFormat("msgpack6c");
|
|
1573
|
+
RpcSerializationFormat.MemoryPackV6C = new RpcMessagePackCompactSerializationFormat("mempack6c");
|
|
1574
|
+
RpcSerializationFormat.All = [
|
|
1575
|
+
RpcSerializationFormat.SystemJsonV5,
|
|
1576
|
+
RpcSerializationFormat.SystemJsonV5NP,
|
|
1577
|
+
RpcSerializationFormat.NewtonsoftJsonV5,
|
|
1578
|
+
RpcSerializationFormat.NewtonsoftJsonV5NP,
|
|
1579
|
+
RpcSerializationFormat.MessagePackV6,
|
|
1580
|
+
RpcSerializationFormat.MemoryPackV6,
|
|
1581
|
+
RpcSerializationFormat.MessagePackV6C,
|
|
1582
|
+
RpcSerializationFormat.MemoryPackV6C
|
|
1583
|
+
];
|
|
1584
|
+
var RpcSerializationFormatResolver = class _RpcSerializationFormatResolver {
|
|
1585
|
+
static DefaultFormats = RpcSerializationFormat.All;
|
|
1586
|
+
static Default = new _RpcSerializationFormatResolver("json5np");
|
|
1587
|
+
defaultFormatKey;
|
|
1588
|
+
formats;
|
|
1589
|
+
get defaultFormat() {
|
|
1590
|
+
return this.get(this.defaultFormatKey);
|
|
1591
|
+
}
|
|
1592
|
+
constructor(defaultFormatKey, formats) {
|
|
1593
|
+
const map = /* @__PURE__ */ new Map();
|
|
1594
|
+
for (const f of formats ?? _RpcSerializationFormatResolver.DefaultFormats)
|
|
1595
|
+
map.set(f.key, f);
|
|
1596
|
+
if (!map.has(defaultFormatKey))
|
|
1597
|
+
throw new Error(`No format with key '${defaultFormatKey}'.`);
|
|
1598
|
+
this.defaultFormatKey = defaultFormatKey;
|
|
1599
|
+
this.formats = map;
|
|
1600
|
+
}
|
|
1601
|
+
get(key) {
|
|
1602
|
+
const format = this.tryGet(key);
|
|
1603
|
+
if (!format)
|
|
1604
|
+
throw new Error(`No format with key '${key}'.`);
|
|
1605
|
+
return format;
|
|
1606
|
+
}
|
|
1607
|
+
tryGet(key) {
|
|
1608
|
+
if (!key) key = this.defaultFormatKey;
|
|
1609
|
+
return this.formats.get(key);
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1483
1612
|
|
|
1484
1613
|
// src/rpc-peer.ts
|
|
1485
|
-
var DEFAULT_SERIALIZATION_FORMAT = "json5np";
|
|
1486
1614
|
var RPC_CLOSE_CODE_UNSUPPORTED_FORMAT = 4001;
|
|
1487
1615
|
var defaultConnectionUrlResolver = (peer) => {
|
|
1488
1616
|
const sep = peer.ref.includes("?") ? "&" : "?";
|
|
@@ -1531,7 +1659,7 @@ var RpcPeer = class {
|
|
|
1531
1659
|
}
|
|
1532
1660
|
/** The serialization format used by this peer. Override in subclasses. */
|
|
1533
1661
|
get format() {
|
|
1534
|
-
return
|
|
1662
|
+
return RpcSerializationFormatResolver.Default.defaultFormat;
|
|
1535
1663
|
}
|
|
1536
1664
|
call(method, args, options) {
|
|
1537
1665
|
const callId = this.outbound.nextId();
|
|
@@ -1677,15 +1805,13 @@ var RpcPeer = class {
|
|
|
1677
1805
|
context
|
|
1678
1806
|
);
|
|
1679
1807
|
if (methodDef?.stream === true && this._connection !== void 0) {
|
|
1680
|
-
const
|
|
1681
|
-
this.sharedObjects.register(sender);
|
|
1808
|
+
const stream = result instanceof RpcStream ? result : new RpcStream(result);
|
|
1682
1809
|
this._hub.systemCallSender.ok(
|
|
1683
1810
|
this._connection,
|
|
1684
1811
|
this.format,
|
|
1685
1812
|
relatedId,
|
|
1686
|
-
|
|
1813
|
+
stream.toRef(this)
|
|
1687
1814
|
);
|
|
1688
|
-
void sender.writeFrom(result);
|
|
1689
1815
|
} else if (!isNoWait && this._connection !== void 0) {
|
|
1690
1816
|
this._hub.systemCallSender.ok(
|
|
1691
1817
|
this._connection,
|
|
@@ -1768,8 +1894,9 @@ var RpcClientPeer = class extends RpcPeer {
|
|
|
1768
1894
|
constructor(hub, url, serializationFormat) {
|
|
1769
1895
|
super(url, hub);
|
|
1770
1896
|
this.clientId = guidToBase64Url(this.id);
|
|
1771
|
-
|
|
1772
|
-
this.
|
|
1897
|
+
const resolver = RpcSerializationFormatResolver.Default;
|
|
1898
|
+
this.serializationFormat = serializationFormat ?? resolver.defaultFormatKey;
|
|
1899
|
+
this._format = resolver.get(this.serializationFormat);
|
|
1773
1900
|
}
|
|
1774
1901
|
get format() {
|
|
1775
1902
|
return this._format;
|
|
@@ -2916,7 +3043,6 @@ var RpcPeerStateMonitor = class {
|
|
|
2916
3043
|
};
|
|
2917
3044
|
export {
|
|
2918
3045
|
ARG_DELIMITER,
|
|
2919
|
-
DEFAULT_SERIALIZATION_FORMAT,
|
|
2920
3046
|
ENVELOPE_DELIMITER,
|
|
2921
3047
|
FRAME_DELIMITER,
|
|
2922
3048
|
RPC_CLOSE_CODE_UNSUPPORTED_FORMAT,
|
|
@@ -2940,6 +3066,7 @@ export {
|
|
|
2940
3066
|
RpcPeerStateMonitor,
|
|
2941
3067
|
RpcRemoteObjectTracker,
|
|
2942
3068
|
RpcSerializationFormat,
|
|
3069
|
+
RpcSerializationFormatResolver,
|
|
2943
3070
|
RpcServerPeer,
|
|
2944
3071
|
RpcServiceHost,
|
|
2945
3072
|
RpcSharedObjectTracker,
|