@actuallab/rpc 12.4.6 → 12.4.9
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 +42 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +115 -102
- package/dist/index.d.ts +115 -102
- package/dist/index.js +42 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -990,6 +990,118 @@ declare class RpcSystemCallHandler {
|
|
|
990
990
|
private _handleReconnect;
|
|
991
991
|
}
|
|
992
992
|
|
|
993
|
+
/**
|
|
994
|
+
* Server-side RPC stream producer — sends items to a remote consumer via
|
|
995
|
+
* $sys.I / $sys.B / $sys.End system calls.
|
|
996
|
+
*
|
|
997
|
+
* Wire protocol (server perspective):
|
|
998
|
+
* - Server returns the stream reference string as $sys.Ok
|
|
999
|
+
* - Client sends Ack(0, hostId) to start the stream
|
|
1000
|
+
* - Server sends I (single item) / B (batch) messages
|
|
1001
|
+
* - Client acks every ackPeriod items for flow control
|
|
1002
|
+
* - Server sends End to signal completion (with optional error)
|
|
1003
|
+
* - Client sends AckEnd to acknowledge completion
|
|
1004
|
+
*
|
|
1005
|
+
* The pump is ACK-driven: the main loop blocks on `_whenAckReady` until a
|
|
1006
|
+
* client ACK is queued, processes it (updating `_lastAckedIndex`, rewinding
|
|
1007
|
+
* `_index` on mustReset, trimming the replay buffer), then sends items up
|
|
1008
|
+
* to `ack.nextIndex + bufferSize`. While the peer is disconnected no ACKs
|
|
1009
|
+
* arrive, so the source is never pulled — the sender naturally pauses
|
|
1010
|
+
* instead of spinning.
|
|
1011
|
+
*
|
|
1012
|
+
* Direct port of .NET `RpcSharedStream<T>` at
|
|
1013
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.
|
|
1014
|
+
*/
|
|
1015
|
+
declare class RpcStreamSender<T> implements IRpcObject {
|
|
1016
|
+
private static readonly _emptyGuid;
|
|
1017
|
+
readonly id: RpcObjectId;
|
|
1018
|
+
readonly kind: 0;
|
|
1019
|
+
readonly allowReconnect: boolean;
|
|
1020
|
+
readonly isRealTime: boolean;
|
|
1021
|
+
readonly canSkipTo: (item: T) => boolean;
|
|
1022
|
+
readonly peer: RpcPeer;
|
|
1023
|
+
readonly ackPeriod: number;
|
|
1024
|
+
readonly bufferSize: number;
|
|
1025
|
+
/**
|
|
1026
|
+
* If true, the source was produced by a factory `(abortSignal) => AsyncIterable<T>`
|
|
1027
|
+
* and may honor the AbortSignal to exit gracefully. `disconnect()` first aborts
|
|
1028
|
+
* the signal and gives the source up to `RpcStream.disconnectGracePeriodMs` ms
|
|
1029
|
+
* before force-closing via `iterator.return()`.
|
|
1030
|
+
*
|
|
1031
|
+
* If false, the source is a plain AsyncIterable that cannot observe the signal,
|
|
1032
|
+
* so `disconnect()` force-closes immediately.
|
|
1033
|
+
*/
|
|
1034
|
+
readonly sourceUsesAbortSignal: boolean;
|
|
1035
|
+
/** Next stream index that will be sent (matches .NET's `index`). */
|
|
1036
|
+
private _nextIndex;
|
|
1037
|
+
private _ended;
|
|
1038
|
+
private _started;
|
|
1039
|
+
/** Highest `nextIndex` observed in an ACK so far (matches .NET's `lastAckNextIndex` idiom). */
|
|
1040
|
+
private _lastAckedIndex;
|
|
1041
|
+
private _abortController;
|
|
1042
|
+
private _iterator;
|
|
1043
|
+
/** Total items skipped via real-time `canSkipTo` compaction. */
|
|
1044
|
+
private _skipCount;
|
|
1045
|
+
/** Fires after a $sys.Ack has been processed (post-compaction). The
|
|
1046
|
+
* recorder's quality controller uses this to bump a "last ACK at"
|
|
1047
|
+
* watchdog so it can distinguish "stuck" (no ACK > N s) from
|
|
1048
|
+
* "throttled" (ACKs flowing). */
|
|
1049
|
+
onAckProcessed?: () => void;
|
|
1050
|
+
get nextIndex(): number;
|
|
1051
|
+
get lastAckIndex(): number;
|
|
1052
|
+
get skipCount(): number;
|
|
1053
|
+
private _acks;
|
|
1054
|
+
private _whenAckReady;
|
|
1055
|
+
constructor(peer: RpcPeer, ackPeriod?: number, bufferSize?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean, sourceUsesAbortSignal?: boolean);
|
|
1056
|
+
/** AbortSignal that is aborted when the sender is disconnected. */
|
|
1057
|
+
get abortSignal(): AbortSignal;
|
|
1058
|
+
/** Returns the stream reference string for the $sys.Ok response. */
|
|
1059
|
+
toRef(): string;
|
|
1060
|
+
/**
|
|
1061
|
+
* Called by system call handler when $sys.Ack is received from the client.
|
|
1062
|
+
*
|
|
1063
|
+
* Mirrors .NET `RpcSharedStream<T>.OnAck` at
|
|
1064
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:68-101.
|
|
1065
|
+
*/
|
|
1066
|
+
onAck(nextIndex: number, hostId: string): void;
|
|
1067
|
+
/** Called by system call handler when $sys.AckEnd is received from the client. */
|
|
1068
|
+
onAckEnd(_hostId: string): void;
|
|
1069
|
+
/**
|
|
1070
|
+
* Send a single item to the client at the current `_nextIndex`.
|
|
1071
|
+
*
|
|
1072
|
+
* Advances `_nextIndex` unconditionally — even if the peer is disconnected
|
|
1073
|
+
* the item is considered "sent" from the pump's perspective so the replay
|
|
1074
|
+
* buffer indexing stays consistent. If a connection is absent, the wire
|
|
1075
|
+
* message is dropped; the item will be resent from the replay buffer
|
|
1076
|
+
* after a reconnect (client sends `Ack(N, mustReset=true)`).
|
|
1077
|
+
*/
|
|
1078
|
+
sendItem(item: T): void;
|
|
1079
|
+
/** Send a batch of items to the client. See {@link sendItem} for disconnect semantics. */
|
|
1080
|
+
sendBatch(items: T[]): void;
|
|
1081
|
+
/** Signal stream completion to the client. */
|
|
1082
|
+
sendEnd(error?: Error | null): void;
|
|
1083
|
+
/**
|
|
1084
|
+
* Consume an AsyncIterable and send all items to the client.
|
|
1085
|
+
*
|
|
1086
|
+
* Matches .NET `RpcSharedStream<T>.OnRun` at
|
|
1087
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.
|
|
1088
|
+
*/
|
|
1089
|
+
writeFrom(source: AsyncIterable<T>): Promise<void>;
|
|
1090
|
+
/**
|
|
1091
|
+
* ACK-driven pump. Non-real-time streams apply normal backpressure at
|
|
1092
|
+
* `bufferSize`; real-time streams may compact the already-buffered unsent
|
|
1093
|
+
* suffix to the latest buffered `canSkipTo` item after the consumer ACKs.
|
|
1094
|
+
*/
|
|
1095
|
+
private _run;
|
|
1096
|
+
/** Drain all queued ACKs, returning the most recent one (or null if none). */
|
|
1097
|
+
private _tryProcessAcks;
|
|
1098
|
+
/** Wait for the ACK queue to have at least one entry (or `_ended`). */
|
|
1099
|
+
private _waitAckReady;
|
|
1100
|
+
reconnect(): void;
|
|
1101
|
+
disconnect(): void;
|
|
1102
|
+
private _forceCloseIterator;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
993
1105
|
/** Parsed stream reference from the server's result payload. */
|
|
994
1106
|
interface RpcStreamRef {
|
|
995
1107
|
readonly hostId: string;
|
|
@@ -1072,6 +1184,9 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
|
|
|
1072
1184
|
* Rejects if the source throws. Throws if `toRef()` hasn't been called yet.
|
|
1073
1185
|
*/
|
|
1074
1186
|
get whenSent(): Promise<void>;
|
|
1187
|
+
/** Local sender backing this stream after `toRef()` has been called.
|
|
1188
|
+
* `undefined` for remote streams or before `toRef()`. */
|
|
1189
|
+
get sender(): RpcStreamSender<T> | undefined;
|
|
1075
1190
|
/**
|
|
1076
1191
|
* For local streams: bind to a peer, create and register an `RpcStreamSender`,
|
|
1077
1192
|
* start pumping items from the local source in the background, and return the
|
|
@@ -1108,108 +1223,6 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
|
|
|
1108
1223
|
*/
|
|
1109
1224
|
declare function resolveStreamRefs(value: unknown, peer: RpcPeer): unknown;
|
|
1110
1225
|
|
|
1111
|
-
/**
|
|
1112
|
-
* Server-side RPC stream producer — sends items to a remote consumer via
|
|
1113
|
-
* $sys.I / $sys.B / $sys.End system calls.
|
|
1114
|
-
*
|
|
1115
|
-
* Wire protocol (server perspective):
|
|
1116
|
-
* - Server returns the stream reference string as $sys.Ok
|
|
1117
|
-
* - Client sends Ack(0, hostId) to start the stream
|
|
1118
|
-
* - Server sends I (single item) / B (batch) messages
|
|
1119
|
-
* - Client acks every ackPeriod items for flow control
|
|
1120
|
-
* - Server sends End to signal completion (with optional error)
|
|
1121
|
-
* - Client sends AckEnd to acknowledge completion
|
|
1122
|
-
*
|
|
1123
|
-
* The pump is ACK-driven: the main loop blocks on `_whenAckReady` until a
|
|
1124
|
-
* client ACK is queued, processes it (updating `_lastAckedIndex`, rewinding
|
|
1125
|
-
* `_index` on mustReset, trimming the replay buffer), then sends items up
|
|
1126
|
-
* to `ack.nextIndex + bufferSize`. While the peer is disconnected no ACKs
|
|
1127
|
-
* arrive, so the source is never pulled — the sender naturally pauses
|
|
1128
|
-
* instead of spinning.
|
|
1129
|
-
*
|
|
1130
|
-
* Direct port of .NET `RpcSharedStream<T>` at
|
|
1131
|
-
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.
|
|
1132
|
-
*/
|
|
1133
|
-
declare class RpcStreamSender<T> implements IRpcObject {
|
|
1134
|
-
private static readonly _emptyGuid;
|
|
1135
|
-
readonly id: RpcObjectId;
|
|
1136
|
-
readonly kind: 0;
|
|
1137
|
-
readonly allowReconnect: boolean;
|
|
1138
|
-
readonly isRealTime: boolean;
|
|
1139
|
-
readonly canSkipTo: (item: T) => boolean;
|
|
1140
|
-
readonly peer: RpcPeer;
|
|
1141
|
-
readonly ackPeriod: number;
|
|
1142
|
-
readonly bufferSize: number;
|
|
1143
|
-
/**
|
|
1144
|
-
* If true, the source was produced by a factory `(abortSignal) => AsyncIterable<T>`
|
|
1145
|
-
* and may honor the AbortSignal to exit gracefully. `disconnect()` first aborts
|
|
1146
|
-
* the signal and gives the source up to `RpcStream.disconnectGracePeriodMs` ms
|
|
1147
|
-
* before force-closing via `iterator.return()`.
|
|
1148
|
-
*
|
|
1149
|
-
* If false, the source is a plain AsyncIterable that cannot observe the signal,
|
|
1150
|
-
* so `disconnect()` force-closes immediately.
|
|
1151
|
-
*/
|
|
1152
|
-
readonly sourceUsesAbortSignal: boolean;
|
|
1153
|
-
/** Next stream index that will be sent (matches .NET's `index`). */
|
|
1154
|
-
private _nextIndex;
|
|
1155
|
-
private _ended;
|
|
1156
|
-
private _started;
|
|
1157
|
-
/** Highest `nextIndex` observed in an ACK so far (matches .NET's `lastAckNextIndex` idiom). */
|
|
1158
|
-
private _lastAckedIndex;
|
|
1159
|
-
private _abortController;
|
|
1160
|
-
private _iterator;
|
|
1161
|
-
private _acks;
|
|
1162
|
-
private _whenAckReady;
|
|
1163
|
-
constructor(peer: RpcPeer, ackPeriod?: number, bufferSize?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean, sourceUsesAbortSignal?: boolean);
|
|
1164
|
-
/** AbortSignal that is aborted when the sender is disconnected. */
|
|
1165
|
-
get abortSignal(): AbortSignal;
|
|
1166
|
-
/** Returns the stream reference string for the $sys.Ok response. */
|
|
1167
|
-
toRef(): string;
|
|
1168
|
-
/**
|
|
1169
|
-
* Called by system call handler when $sys.Ack is received from the client.
|
|
1170
|
-
*
|
|
1171
|
-
* Mirrors .NET `RpcSharedStream<T>.OnAck` at
|
|
1172
|
-
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:68-101.
|
|
1173
|
-
*/
|
|
1174
|
-
onAck(nextIndex: number, hostId: string): void;
|
|
1175
|
-
/** Called by system call handler when $sys.AckEnd is received from the client. */
|
|
1176
|
-
onAckEnd(_hostId: string): void;
|
|
1177
|
-
/**
|
|
1178
|
-
* Send a single item to the client at the current `_nextIndex`.
|
|
1179
|
-
*
|
|
1180
|
-
* Advances `_nextIndex` unconditionally — even if the peer is disconnected
|
|
1181
|
-
* the item is considered "sent" from the pump's perspective so the replay
|
|
1182
|
-
* buffer indexing stays consistent. If a connection is absent, the wire
|
|
1183
|
-
* message is dropped; the item will be resent from the replay buffer
|
|
1184
|
-
* after a reconnect (client sends `Ack(N, mustReset=true)`).
|
|
1185
|
-
*/
|
|
1186
|
-
sendItem(item: T): void;
|
|
1187
|
-
/** Send a batch of items to the client. See {@link sendItem} for disconnect semantics. */
|
|
1188
|
-
sendBatch(items: T[]): void;
|
|
1189
|
-
/** Signal stream completion to the client. */
|
|
1190
|
-
sendEnd(error?: Error | null): void;
|
|
1191
|
-
/**
|
|
1192
|
-
* Consume an AsyncIterable and send all items to the client.
|
|
1193
|
-
*
|
|
1194
|
-
* Matches .NET `RpcSharedStream<T>.OnRun` at
|
|
1195
|
-
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.
|
|
1196
|
-
*/
|
|
1197
|
-
writeFrom(source: AsyncIterable<T>): Promise<void>;
|
|
1198
|
-
/**
|
|
1199
|
-
* ACK-driven pump. Non-real-time streams apply normal backpressure at
|
|
1200
|
-
* `bufferSize`; real-time streams may compact the already-buffered unsent
|
|
1201
|
-
* suffix to the latest buffered `canSkipTo` item after the consumer ACKs.
|
|
1202
|
-
*/
|
|
1203
|
-
private _run;
|
|
1204
|
-
/** Drain all queued ACKs, returning the most recent one (or null if none). */
|
|
1205
|
-
private _tryProcessAcks;
|
|
1206
|
-
/** Wait for the ACK queue to have at least one entry (or `_ended`). */
|
|
1207
|
-
private _waitAckReady;
|
|
1208
|
-
reconnect(): void;
|
|
1209
|
-
disconnect(): void;
|
|
1210
|
-
private _forceCloseIterator;
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
1226
|
declare class RpcPeerRefBuilder {
|
|
1214
1227
|
/** Build a client peer ref from a WebSocket URL, optionally baking in the
|
|
1215
1228
|
* serialization format as a `f=` query parameter.
|
package/dist/index.d.ts
CHANGED
|
@@ -990,6 +990,118 @@ declare class RpcSystemCallHandler {
|
|
|
990
990
|
private _handleReconnect;
|
|
991
991
|
}
|
|
992
992
|
|
|
993
|
+
/**
|
|
994
|
+
* Server-side RPC stream producer — sends items to a remote consumer via
|
|
995
|
+
* $sys.I / $sys.B / $sys.End system calls.
|
|
996
|
+
*
|
|
997
|
+
* Wire protocol (server perspective):
|
|
998
|
+
* - Server returns the stream reference string as $sys.Ok
|
|
999
|
+
* - Client sends Ack(0, hostId) to start the stream
|
|
1000
|
+
* - Server sends I (single item) / B (batch) messages
|
|
1001
|
+
* - Client acks every ackPeriod items for flow control
|
|
1002
|
+
* - Server sends End to signal completion (with optional error)
|
|
1003
|
+
* - Client sends AckEnd to acknowledge completion
|
|
1004
|
+
*
|
|
1005
|
+
* The pump is ACK-driven: the main loop blocks on `_whenAckReady` until a
|
|
1006
|
+
* client ACK is queued, processes it (updating `_lastAckedIndex`, rewinding
|
|
1007
|
+
* `_index` on mustReset, trimming the replay buffer), then sends items up
|
|
1008
|
+
* to `ack.nextIndex + bufferSize`. While the peer is disconnected no ACKs
|
|
1009
|
+
* arrive, so the source is never pulled — the sender naturally pauses
|
|
1010
|
+
* instead of spinning.
|
|
1011
|
+
*
|
|
1012
|
+
* Direct port of .NET `RpcSharedStream<T>` at
|
|
1013
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.
|
|
1014
|
+
*/
|
|
1015
|
+
declare class RpcStreamSender<T> implements IRpcObject {
|
|
1016
|
+
private static readonly _emptyGuid;
|
|
1017
|
+
readonly id: RpcObjectId;
|
|
1018
|
+
readonly kind: 0;
|
|
1019
|
+
readonly allowReconnect: boolean;
|
|
1020
|
+
readonly isRealTime: boolean;
|
|
1021
|
+
readonly canSkipTo: (item: T) => boolean;
|
|
1022
|
+
readonly peer: RpcPeer;
|
|
1023
|
+
readonly ackPeriod: number;
|
|
1024
|
+
readonly bufferSize: number;
|
|
1025
|
+
/**
|
|
1026
|
+
* If true, the source was produced by a factory `(abortSignal) => AsyncIterable<T>`
|
|
1027
|
+
* and may honor the AbortSignal to exit gracefully. `disconnect()` first aborts
|
|
1028
|
+
* the signal and gives the source up to `RpcStream.disconnectGracePeriodMs` ms
|
|
1029
|
+
* before force-closing via `iterator.return()`.
|
|
1030
|
+
*
|
|
1031
|
+
* If false, the source is a plain AsyncIterable that cannot observe the signal,
|
|
1032
|
+
* so `disconnect()` force-closes immediately.
|
|
1033
|
+
*/
|
|
1034
|
+
readonly sourceUsesAbortSignal: boolean;
|
|
1035
|
+
/** Next stream index that will be sent (matches .NET's `index`). */
|
|
1036
|
+
private _nextIndex;
|
|
1037
|
+
private _ended;
|
|
1038
|
+
private _started;
|
|
1039
|
+
/** Highest `nextIndex` observed in an ACK so far (matches .NET's `lastAckNextIndex` idiom). */
|
|
1040
|
+
private _lastAckedIndex;
|
|
1041
|
+
private _abortController;
|
|
1042
|
+
private _iterator;
|
|
1043
|
+
/** Total items skipped via real-time `canSkipTo` compaction. */
|
|
1044
|
+
private _skipCount;
|
|
1045
|
+
/** Fires after a $sys.Ack has been processed (post-compaction). The
|
|
1046
|
+
* recorder's quality controller uses this to bump a "last ACK at"
|
|
1047
|
+
* watchdog so it can distinguish "stuck" (no ACK > N s) from
|
|
1048
|
+
* "throttled" (ACKs flowing). */
|
|
1049
|
+
onAckProcessed?: () => void;
|
|
1050
|
+
get nextIndex(): number;
|
|
1051
|
+
get lastAckIndex(): number;
|
|
1052
|
+
get skipCount(): number;
|
|
1053
|
+
private _acks;
|
|
1054
|
+
private _whenAckReady;
|
|
1055
|
+
constructor(peer: RpcPeer, ackPeriod?: number, bufferSize?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean, sourceUsesAbortSignal?: boolean);
|
|
1056
|
+
/** AbortSignal that is aborted when the sender is disconnected. */
|
|
1057
|
+
get abortSignal(): AbortSignal;
|
|
1058
|
+
/** Returns the stream reference string for the $sys.Ok response. */
|
|
1059
|
+
toRef(): string;
|
|
1060
|
+
/**
|
|
1061
|
+
* Called by system call handler when $sys.Ack is received from the client.
|
|
1062
|
+
*
|
|
1063
|
+
* Mirrors .NET `RpcSharedStream<T>.OnAck` at
|
|
1064
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:68-101.
|
|
1065
|
+
*/
|
|
1066
|
+
onAck(nextIndex: number, hostId: string): void;
|
|
1067
|
+
/** Called by system call handler when $sys.AckEnd is received from the client. */
|
|
1068
|
+
onAckEnd(_hostId: string): void;
|
|
1069
|
+
/**
|
|
1070
|
+
* Send a single item to the client at the current `_nextIndex`.
|
|
1071
|
+
*
|
|
1072
|
+
* Advances `_nextIndex` unconditionally — even if the peer is disconnected
|
|
1073
|
+
* the item is considered "sent" from the pump's perspective so the replay
|
|
1074
|
+
* buffer indexing stays consistent. If a connection is absent, the wire
|
|
1075
|
+
* message is dropped; the item will be resent from the replay buffer
|
|
1076
|
+
* after a reconnect (client sends `Ack(N, mustReset=true)`).
|
|
1077
|
+
*/
|
|
1078
|
+
sendItem(item: T): void;
|
|
1079
|
+
/** Send a batch of items to the client. See {@link sendItem} for disconnect semantics. */
|
|
1080
|
+
sendBatch(items: T[]): void;
|
|
1081
|
+
/** Signal stream completion to the client. */
|
|
1082
|
+
sendEnd(error?: Error | null): void;
|
|
1083
|
+
/**
|
|
1084
|
+
* Consume an AsyncIterable and send all items to the client.
|
|
1085
|
+
*
|
|
1086
|
+
* Matches .NET `RpcSharedStream<T>.OnRun` at
|
|
1087
|
+
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.
|
|
1088
|
+
*/
|
|
1089
|
+
writeFrom(source: AsyncIterable<T>): Promise<void>;
|
|
1090
|
+
/**
|
|
1091
|
+
* ACK-driven pump. Non-real-time streams apply normal backpressure at
|
|
1092
|
+
* `bufferSize`; real-time streams may compact the already-buffered unsent
|
|
1093
|
+
* suffix to the latest buffered `canSkipTo` item after the consumer ACKs.
|
|
1094
|
+
*/
|
|
1095
|
+
private _run;
|
|
1096
|
+
/** Drain all queued ACKs, returning the most recent one (or null if none). */
|
|
1097
|
+
private _tryProcessAcks;
|
|
1098
|
+
/** Wait for the ACK queue to have at least one entry (or `_ended`). */
|
|
1099
|
+
private _waitAckReady;
|
|
1100
|
+
reconnect(): void;
|
|
1101
|
+
disconnect(): void;
|
|
1102
|
+
private _forceCloseIterator;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
993
1105
|
/** Parsed stream reference from the server's result payload. */
|
|
994
1106
|
interface RpcStreamRef {
|
|
995
1107
|
readonly hostId: string;
|
|
@@ -1072,6 +1184,9 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
|
|
|
1072
1184
|
* Rejects if the source throws. Throws if `toRef()` hasn't been called yet.
|
|
1073
1185
|
*/
|
|
1074
1186
|
get whenSent(): Promise<void>;
|
|
1187
|
+
/** Local sender backing this stream after `toRef()` has been called.
|
|
1188
|
+
* `undefined` for remote streams or before `toRef()`. */
|
|
1189
|
+
get sender(): RpcStreamSender<T> | undefined;
|
|
1075
1190
|
/**
|
|
1076
1191
|
* For local streams: bind to a peer, create and register an `RpcStreamSender`,
|
|
1077
1192
|
* start pumping items from the local source in the background, and return the
|
|
@@ -1108,108 +1223,6 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
|
|
|
1108
1223
|
*/
|
|
1109
1224
|
declare function resolveStreamRefs(value: unknown, peer: RpcPeer): unknown;
|
|
1110
1225
|
|
|
1111
|
-
/**
|
|
1112
|
-
* Server-side RPC stream producer — sends items to a remote consumer via
|
|
1113
|
-
* $sys.I / $sys.B / $sys.End system calls.
|
|
1114
|
-
*
|
|
1115
|
-
* Wire protocol (server perspective):
|
|
1116
|
-
* - Server returns the stream reference string as $sys.Ok
|
|
1117
|
-
* - Client sends Ack(0, hostId) to start the stream
|
|
1118
|
-
* - Server sends I (single item) / B (batch) messages
|
|
1119
|
-
* - Client acks every ackPeriod items for flow control
|
|
1120
|
-
* - Server sends End to signal completion (with optional error)
|
|
1121
|
-
* - Client sends AckEnd to acknowledge completion
|
|
1122
|
-
*
|
|
1123
|
-
* The pump is ACK-driven: the main loop blocks on `_whenAckReady` until a
|
|
1124
|
-
* client ACK is queued, processes it (updating `_lastAckedIndex`, rewinding
|
|
1125
|
-
* `_index` on mustReset, trimming the replay buffer), then sends items up
|
|
1126
|
-
* to `ack.nextIndex + bufferSize`. While the peer is disconnected no ACKs
|
|
1127
|
-
* arrive, so the source is never pulled — the sender naturally pauses
|
|
1128
|
-
* instead of spinning.
|
|
1129
|
-
*
|
|
1130
|
-
* Direct port of .NET `RpcSharedStream<T>` at
|
|
1131
|
-
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.
|
|
1132
|
-
*/
|
|
1133
|
-
declare class RpcStreamSender<T> implements IRpcObject {
|
|
1134
|
-
private static readonly _emptyGuid;
|
|
1135
|
-
readonly id: RpcObjectId;
|
|
1136
|
-
readonly kind: 0;
|
|
1137
|
-
readonly allowReconnect: boolean;
|
|
1138
|
-
readonly isRealTime: boolean;
|
|
1139
|
-
readonly canSkipTo: (item: T) => boolean;
|
|
1140
|
-
readonly peer: RpcPeer;
|
|
1141
|
-
readonly ackPeriod: number;
|
|
1142
|
-
readonly bufferSize: number;
|
|
1143
|
-
/**
|
|
1144
|
-
* If true, the source was produced by a factory `(abortSignal) => AsyncIterable<T>`
|
|
1145
|
-
* and may honor the AbortSignal to exit gracefully. `disconnect()` first aborts
|
|
1146
|
-
* the signal and gives the source up to `RpcStream.disconnectGracePeriodMs` ms
|
|
1147
|
-
* before force-closing via `iterator.return()`.
|
|
1148
|
-
*
|
|
1149
|
-
* If false, the source is a plain AsyncIterable that cannot observe the signal,
|
|
1150
|
-
* so `disconnect()` force-closes immediately.
|
|
1151
|
-
*/
|
|
1152
|
-
readonly sourceUsesAbortSignal: boolean;
|
|
1153
|
-
/** Next stream index that will be sent (matches .NET's `index`). */
|
|
1154
|
-
private _nextIndex;
|
|
1155
|
-
private _ended;
|
|
1156
|
-
private _started;
|
|
1157
|
-
/** Highest `nextIndex` observed in an ACK so far (matches .NET's `lastAckNextIndex` idiom). */
|
|
1158
|
-
private _lastAckedIndex;
|
|
1159
|
-
private _abortController;
|
|
1160
|
-
private _iterator;
|
|
1161
|
-
private _acks;
|
|
1162
|
-
private _whenAckReady;
|
|
1163
|
-
constructor(peer: RpcPeer, ackPeriod?: number, bufferSize?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean, sourceUsesAbortSignal?: boolean);
|
|
1164
|
-
/** AbortSignal that is aborted when the sender is disconnected. */
|
|
1165
|
-
get abortSignal(): AbortSignal;
|
|
1166
|
-
/** Returns the stream reference string for the $sys.Ok response. */
|
|
1167
|
-
toRef(): string;
|
|
1168
|
-
/**
|
|
1169
|
-
* Called by system call handler when $sys.Ack is received from the client.
|
|
1170
|
-
*
|
|
1171
|
-
* Mirrors .NET `RpcSharedStream<T>.OnAck` at
|
|
1172
|
-
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:68-101.
|
|
1173
|
-
*/
|
|
1174
|
-
onAck(nextIndex: number, hostId: string): void;
|
|
1175
|
-
/** Called by system call handler when $sys.AckEnd is received from the client. */
|
|
1176
|
-
onAckEnd(_hostId: string): void;
|
|
1177
|
-
/**
|
|
1178
|
-
* Send a single item to the client at the current `_nextIndex`.
|
|
1179
|
-
*
|
|
1180
|
-
* Advances `_nextIndex` unconditionally — even if the peer is disconnected
|
|
1181
|
-
* the item is considered "sent" from the pump's perspective so the replay
|
|
1182
|
-
* buffer indexing stays consistent. If a connection is absent, the wire
|
|
1183
|
-
* message is dropped; the item will be resent from the replay buffer
|
|
1184
|
-
* after a reconnect (client sends `Ack(N, mustReset=true)`).
|
|
1185
|
-
*/
|
|
1186
|
-
sendItem(item: T): void;
|
|
1187
|
-
/** Send a batch of items to the client. See {@link sendItem} for disconnect semantics. */
|
|
1188
|
-
sendBatch(items: T[]): void;
|
|
1189
|
-
/** Signal stream completion to the client. */
|
|
1190
|
-
sendEnd(error?: Error | null): void;
|
|
1191
|
-
/**
|
|
1192
|
-
* Consume an AsyncIterable and send all items to the client.
|
|
1193
|
-
*
|
|
1194
|
-
* Matches .NET `RpcSharedStream<T>.OnRun` at
|
|
1195
|
-
* src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.
|
|
1196
|
-
*/
|
|
1197
|
-
writeFrom(source: AsyncIterable<T>): Promise<void>;
|
|
1198
|
-
/**
|
|
1199
|
-
* ACK-driven pump. Non-real-time streams apply normal backpressure at
|
|
1200
|
-
* `bufferSize`; real-time streams may compact the already-buffered unsent
|
|
1201
|
-
* suffix to the latest buffered `canSkipTo` item after the consumer ACKs.
|
|
1202
|
-
*/
|
|
1203
|
-
private _run;
|
|
1204
|
-
/** Drain all queued ACKs, returning the most recent one (or null if none). */
|
|
1205
|
-
private _tryProcessAcks;
|
|
1206
|
-
/** Wait for the ACK queue to have at least one entry (or `_ended`). */
|
|
1207
|
-
private _waitAckReady;
|
|
1208
|
-
reconnect(): void;
|
|
1209
|
-
disconnect(): void;
|
|
1210
|
-
private _forceCloseIterator;
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
1226
|
declare class RpcPeerRefBuilder {
|
|
1214
1227
|
/** Build a client peer ref from a WebSocket URL, optionally baking in the
|
|
1215
1228
|
* serialization format as a `f=` query parameter.
|
package/dist/index.js
CHANGED
|
@@ -900,6 +900,23 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
900
900
|
_lastAckedIndex = 0;
|
|
901
901
|
_abortController = new AbortController();
|
|
902
902
|
_iterator = null;
|
|
903
|
+
// -- Recorder-controller metrics (Step 9.1) --
|
|
904
|
+
/** Total items skipped via real-time `canSkipTo` compaction. */
|
|
905
|
+
_skipCount = 0;
|
|
906
|
+
/** Fires after a $sys.Ack has been processed (post-compaction). The
|
|
907
|
+
* recorder's quality controller uses this to bump a "last ACK at"
|
|
908
|
+
* watchdog so it can distinguish "stuck" (no ACK > N s) from
|
|
909
|
+
* "throttled" (ACKs flowing). */
|
|
910
|
+
onAckProcessed;
|
|
911
|
+
get nextIndex() {
|
|
912
|
+
return this._nextIndex;
|
|
913
|
+
}
|
|
914
|
+
get lastAckIndex() {
|
|
915
|
+
return this._lastAckedIndex;
|
|
916
|
+
}
|
|
917
|
+
get skipCount() {
|
|
918
|
+
return this._skipCount;
|
|
919
|
+
}
|
|
903
920
|
// -- ACK queue (analog of .NET's Channel<(long, bool)>) --
|
|
904
921
|
_acks = [];
|
|
905
922
|
_whenAckReady = null;
|
|
@@ -1134,8 +1151,16 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1134
1151
|
if (bufferIndex >= buffer.count && !isFullyBuffered && buffer.hasRemainingCapacity && this._acks.length === 0) {
|
|
1135
1152
|
await bufferNext(false, true);
|
|
1136
1153
|
}
|
|
1137
|
-
if (ack.nextIndex > 0)
|
|
1138
|
-
|
|
1154
|
+
if (ack.nextIndex > 0) {
|
|
1155
|
+
const result = _compactBufferedUnsentSuffix(
|
|
1156
|
+
buffer,
|
|
1157
|
+
bufferIndex,
|
|
1158
|
+
this.canSkipTo
|
|
1159
|
+
);
|
|
1160
|
+
bufferIndex = result.newIndex;
|
|
1161
|
+
if (result.skipped > 0)
|
|
1162
|
+
this._skipCount += result.skipped;
|
|
1163
|
+
}
|
|
1139
1164
|
if (this._acks.length > 0) break;
|
|
1140
1165
|
}
|
|
1141
1166
|
while (this._nextIndex < maxIndex && bufferIndex < buffer.count) {
|
|
@@ -1171,6 +1196,10 @@ var RpcStreamSender = class _RpcStreamSender {
|
|
|
1171
1196
|
this._nextIndex = a.nextIndex;
|
|
1172
1197
|
}
|
|
1173
1198
|
}
|
|
1199
|
+
try {
|
|
1200
|
+
this.onAckProcessed?.();
|
|
1201
|
+
} catch {
|
|
1202
|
+
}
|
|
1174
1203
|
return last;
|
|
1175
1204
|
}
|
|
1176
1205
|
/** Wait for the ACK queue to have at least one entry (or `_ended`). */
|
|
@@ -1230,14 +1259,16 @@ function _compactBufferedUnsentSuffix(buffer, firstUnsentIndex, canSkipTo) {
|
|
|
1230
1259
|
skipToIndex = i;
|
|
1231
1260
|
}
|
|
1232
1261
|
if (skipToIndex <= firstUnsentIndex)
|
|
1233
|
-
return firstUnsentIndex;
|
|
1262
|
+
return { newIndex: firstUnsentIndex, skipped: 0 };
|
|
1263
|
+
const cutFrom = firstUnsentIndex;
|
|
1264
|
+
const cutTo = skipToIndex;
|
|
1234
1265
|
const items = buffer.toArray();
|
|
1235
1266
|
buffer.clear();
|
|
1236
|
-
for (let i = 0; i <
|
|
1267
|
+
for (let i = 0; i < cutFrom; i++)
|
|
1237
1268
|
buffer.pushTail(items[i]);
|
|
1238
|
-
for (let i =
|
|
1269
|
+
for (let i = cutTo; i < items.length; i++)
|
|
1239
1270
|
buffer.pushTail(items[i]);
|
|
1240
|
-
return firstUnsentIndex;
|
|
1271
|
+
return { newIndex: firstUnsentIndex, skipped: skipToIndex - firstUnsentIndex };
|
|
1241
1272
|
}
|
|
1242
1273
|
|
|
1243
1274
|
// src/rpc-stream.ts
|
|
@@ -1343,6 +1374,11 @@ var RpcStream = class _RpcStream {
|
|
|
1343
1374
|
throw new Error("toRef() must be called before awaiting whenSent.");
|
|
1344
1375
|
return this._whenSent;
|
|
1345
1376
|
}
|
|
1377
|
+
/** Local sender backing this stream after `toRef()` has been called.
|
|
1378
|
+
* `undefined` for remote streams or before `toRef()`. */
|
|
1379
|
+
get sender() {
|
|
1380
|
+
return this._sender;
|
|
1381
|
+
}
|
|
1346
1382
|
/**
|
|
1347
1383
|
* For local streams: bind to a peer, create and register an `RpcStreamSender`,
|
|
1348
1384
|
* start pumping items from the local source in the background, and return the
|