@actuallab/rpc 14.4.3 → 14.4.14

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/logging.ts","../src/rpc-message.ts","../src/rpc-serialization.ts","../src/msgpack-map-patch.ts","../src/rpc-connection.ts","../src/rpc-message-channel-connection.ts","../src/rpc-service-def.ts","../src/rpc-call-tracker.ts","../src/rpc-call-stage.ts","../src/rpc-call-timeouts.ts","../src/rpc-error.ts","../src/increasing-seq-compressor.ts","../src/base64.ts","../src/rpc-system-call-handler.ts","../src/rpc-system-call-sender.ts","../src/rpc-object.ts","../src/rpc-stream.ts","../src/rpc-stream-sender.ts","../src/rpc-remote-object-tracker.ts","../src/rpc-shared-object-tracker.ts","../src/rpc-peer.ts","../src/rpc-serialization-format.ts","../src/reconnect-proof.ts","../src/rpc-limits.ts","../src/rpc-client-peer-reconnect-delayer.ts","../src/rpc-service-host.ts","../src/rpc-decorators.ts","../src/rpc-xxhash3.ts","../src/rpc-method-registry.ts","../src/rpc-hub.ts","../src/rpc-ref-builder.ts","../src/rpc-client.ts","../src/rpc-peer-state.ts","../src/rpc-peer-state-monitor.ts"],"sourcesContent":["export type { RpcLogScope } from './logging.js';\nexport { getLogs } from './logging.js';\n\nexport {\n RpcCallTypeId,\n RpcSystemCalls,\n ENVELOPE_DELIMITER,\n ARG_DELIMITER,\n FRAME_DELIMITER,\n} from './rpc-message.js';\nexport type { RpcMessage } from './rpc-message.js';\n\nexport {\n serializeMessage,\n serializeFrame,\n splitFrame,\n deserializeMessage,\n serializeBinaryMessage,\n deserializeBinaryMessage,\n splitBinaryFrame,\n serializeBinaryFrame,\n createBinaryEncoder,\n defaultBinaryEncoder,\n defaultBinaryDecoder,\n readPolymorphismMarker,\n} from './rpc-serialization.js';\n\nexport type {\n WebSocketLike,\n RpcConnection,\n RpcReceivedMessage,\n} from './rpc-connection.js';\nexport { WebSocketState, RpcWebSocketConnection } from './rpc-connection.js';\n\nexport {\n RpcMessageChannelConnection,\n createMessageChannelPair,\n} from './rpc-message-channel-connection.js';\n\nexport type {\n RpcMethodDef,\n RpcServiceDef,\n RpcMethodDefInput,\n} from './rpc-service-def.js';\nexport {\n RpcType,\n RpcRemoteExecutionMode,\n defineRpcService,\n wireMethodName,\n} from './rpc-service-def.js';\n\nexport {\n RpcOutboundCall,\n RpcOutboundCallTracker,\n RpcInboundCall,\n RpcInboundCallTracker,\n} from './rpc-call-tracker.js';\n\nexport { RpcCallStage } from './rpc-call-stage.js';\nexport { RpcCallTimeouts } from './rpc-call-timeouts.js';\nexport {\n RpcError,\n REMOTE_EXCEPTION_TYPE_REF,\n toExceptionInfo,\n genericErrorFilter,\n} from './rpc-error.js';\nexport type { RpcExceptionInfo, RpcErrorFilter } from './rpc-error.js';\nexport { IncreasingSeqCompressor } from './increasing-seq-compressor.js';\n\nexport { RpcSystemCallHandler } from './rpc-system-call-handler.js';\nexport { RpcSystemCallSender } from './rpc-system-call-sender.js';\n\nexport type { RpcObjectId, IRpcObject } from './rpc-object.js';\nexport { RpcObjectKind } from './rpc-object.js';\nexport { RpcStream, parseStreamRef, toRpcStream } from './rpc-stream.js';\nexport type { RpcStreamRef, RpcStreamOptions, RpcStreamSource } from './rpc-stream.js';\nexport { RpcStreamSender } from './rpc-stream-sender.js';\nexport { RpcRemoteObjectTracker } from './rpc-remote-object-tracker.js';\nexport { RpcSharedObjectTracker } from './rpc-shared-object-tracker.js';\n\nexport {\n RpcPeer,\n RpcConnectionState,\n RpcPeerChangeKind,\n RpcClientPeer,\n RpcServerPeer,\n RPC_CLOSE_CODE_UNSUPPORTED_FORMAT,\n defaultConnectionUrlResolver,\n} from './rpc-peer.js';\nexport { RpcLimits } from './rpc-limits.js';\nexport type {\n RemoteHandshake,\n RpcCallOptions,\n RpcConnectionUrlResolver,\n} from './rpc-peer.js';\nexport { computeReconnectProof, isReconnectProofSupported } from './reconnect-proof.js';\nexport type { RpcReconnectProofParameters } from './reconnect-proof.js';\nexport { RpcHub } from './rpc-hub.js';\nexport type { RpcPeerFactory } from './rpc-hub.js';\nexport { RpcRefBuilder } from './rpc-ref-builder.js';\nexport { RpcServiceHost } from './rpc-service-host.js';\nexport type { RpcServiceImpl, RpcDispatchContext } from './rpc-service-host.js';\nexport { createRpcClient } from './rpc-client.js';\nexport type { WebSocketServer } from './rpc-server.js';\n\nexport {\n rpcService,\n rpcMethod,\n getServiceMeta,\n getMethodsMeta,\n} from './rpc-decorators.js';\nexport type { MethodMeta, ServiceMeta } from './rpc-decorators.js';\n\nexport { RpcClientPeerReconnectDelayer } from './rpc-client-peer-reconnect-delayer.js';\nexport type { RpcPeerState } from './rpc-peer-state.js';\nexport {\n RpcPeerStateKind,\n isConnected,\n likelyConnected,\n getStateDescription,\n} from './rpc-peer-state.js';\nexport { RpcPeerStateMonitor } from './rpc-peer-state-monitor.js';\n\nexport {\n RpcSerializationFormat,\n RpcSerializationFormatResolver,\n RpcJsonSerializationFormat,\n RpcMessagePackSerializationFormat,\n RpcMessagePackCompactSerializationFormat,\n} from './rpc-serialization-format.js';\nexport type {\n RpcDeserializedMessage,\n RpcWireData,\n} from './rpc-serialization-format.js';\n\nexport { RpcMethodRegistry } from './rpc-method-registry.js';\nexport { xxh3_64, xxh3_64str, computeMethodHash } from './rpc-xxhash3.js';\n\nexport {\n serializeCompactBinaryMessage,\n deserializeCompactBinaryMessage,\n splitCompactBinaryFrame,\n} from './rpc-serialization.js';\n","import { createLogProvider, LogLevel } from '@actuallab/core';\n\n// Per-package log scopes. Use createLogProvider to get a typed helper:\n// const { debugLog, warnLog } = getLogs('RpcPeer');\n// Final scope name is 'rpc.RpcPeer' — the prefix isolates this package's\n// scopes from other packages' scopes when overriding levels at runtime.\nexport type RpcLogScope =\n | 'RpcCallTracker'\n | 'RpcClient'\n | 'RpcClientPeerReconnectDelayer'\n | 'RpcConnection'\n | 'RpcHub'\n | 'RpcInterceptor'\n | 'RpcMessageChannelConnection'\n | 'RpcMethodRegistry'\n | 'RpcOutboundCall'\n | 'RpcPeer'\n | 'RpcPeerStateMonitor'\n | 'RpcRemoteObjectTracker'\n | 'RpcSerialization'\n | 'RpcServiceHost'\n | 'RpcSharedObjectTracker'\n | 'RpcSharedStream'\n | 'RpcStream'\n | 'RpcStreamSender'\n | 'RpcSystemCallHandler'\n | 'RpcSystemCallSender'\n | 'RpcWebSocketClient';\n\n// Per-scope defaults. All Warn — output goes to the browser console, so the\n// out-of-the-box experience must be quiet. Users opt in to Info-level\n// connectivity tracing manually:\n// logLevels.override('rpc.*', LogLevel.Info)\n// or for a single scope:\n// logLevels.override('rpc.RpcPeer', LogLevel.Info)\nconst scopeDefaults: Record<RpcLogScope, LogLevel> = {\n RpcCallTracker: LogLevel.Warn,\n RpcClient: LogLevel.Warn,\n RpcClientPeerReconnectDelayer: LogLevel.Warn,\n RpcConnection: LogLevel.Warn,\n RpcHub: LogLevel.Warn,\n RpcInterceptor: LogLevel.Warn,\n RpcMessageChannelConnection: LogLevel.Warn,\n RpcMethodRegistry: LogLevel.Warn,\n RpcOutboundCall: LogLevel.Warn,\n RpcPeer: LogLevel.Info,\n RpcPeerStateMonitor: LogLevel.Warn,\n RpcRemoteObjectTracker: LogLevel.Warn,\n RpcSerialization: LogLevel.Warn,\n RpcServiceHost: LogLevel.Warn,\n RpcSharedObjectTracker: LogLevel.Warn,\n RpcSharedStream: LogLevel.Warn,\n RpcStream: LogLevel.Warn,\n RpcStreamSender: LogLevel.Warn,\n RpcSystemCallHandler: LogLevel.Warn,\n RpcSystemCallSender: LogLevel.Warn,\n RpcWebSocketClient: LogLevel.Warn,\n};\n\nexport const getLogs = createLogProvider<RpcLogScope>('rpc.', scopeDefaults);\n","// .NET counterparts:\n// RpcMessage — .NET has no single \"message\" type; the wire format is built by\n// RpcMessageSerializer / RpcOutboundMessage / RpcInboundMessage. In .NET the\n// envelope carries MethodRef, RelatedId, Headers, CallTypeId, and serialized\n// argument data as binary or text segments. In TS we use a simpler JSON\n// envelope + delimiter-separated args.\n//\n// Omitted from .NET:\n// - RpcCallTypeId Reliable (stage-based reconnection protocol) — the TS client\n// is browser-only; reconnection replays entire calls rather than resuming at\n// a stage, so Reliable call type adds no value.\n// - RpcHeader / RpcHeaders — .NET passes typed header arrays on every message\n// (Hash, ActivityContext, etc.). TS has no RPC caching, OpenTelemetry\n// propagation, or middleware pipeline, so headers aren't needed yet. The\n// Headers field on RpcMessage is reserved for future use.\n// - $sys.M (Match) / $sys.NotFound / $sys.Disconnect / Stream calls\n// (Ack, AckEnd, I, B, End) — Match is for hash-based response caching\n// (bandwidth optimisation); NotFound is a server-side \"service not registered\"\n// response; Disconnect is for RpcSharedObject lifetime management; the stream\n// calls are for RpcStream (server→client IAsyncEnumerable). None of these\n// are needed in the TS client today because:\n// * No response caching → no Match.\n// * Service-not-found is sent as $sys.Error.\n// * No shared-object tracking (server-side concept) → no Disconnect.\n// * No RpcStream yet → no Ack/AckEnd/I/B/End.\n// They can be added when streaming or caching is ported.\n// - Binary serialization variants (RpcByteMessageSerializer) — TS uses\n// text (JSON) exclusively; binary MessagePack/MemoryPack require native\n// codecs that aren't available in browsers.\n\n/** Identifies the type of an RPC call on the wire. */\nexport const enum RpcCallTypeId {\n Regular = 0,\n Reliable = 1,\n}\n\n/** Identifies system call methods — names include :argCount suffix for .NET interop. */\nexport const RpcSystemCalls = {\n handshake: '$sys.Handshake:1',\n ok: '$sys.Ok:1',\n error: '$sys.Error:1',\n cancel: '$sys.Cancel:0',\n keepAlive: '$sys.KeepAlive:1',\n reconnect: '$sys.Reconnect:3',\n ack: '$sys.Ack:2',\n ackEnd: '$sys.AckEnd:1',\n item: '$sys.I:2',\n batch: '$sys.B:2',\n end: '$sys.End:2',\n disconnect: '$sys.Disconnect:1',\n} as const;\n\n/** Wire-format RPC message envelope. */\nexport interface RpcMessage {\n CallType?: number;\n RelatedId?: number;\n Method?: string;\n Headers?: unknown[];\n}\n\n// Delimiters used in the wire format\nexport const ENVELOPE_DELIMITER = '\\n'; // \\x0A between envelope and args\nexport const ARG_DELIMITER = '\\x1F'; // Unit Separator between args\nexport const FRAME_DELIMITER = '\\n\\x1E'; // \\x0A\\x1E between messages in a frame\n","// Serialization for the RPC wire format.\r\n//\r\n// Supports two formats:\r\n// - Text (json5np): JSON envelope + delimiter-separated JSON args (V3 wire format)\r\n// - Binary (msgpack6np): V5 binary envelope + MessagePack-encoded args\r\n//\r\n// The format is selected at connection time via the `f=` query parameter.\r\n\r\nimport {\r\n Encoder,\r\n Decoder,\r\n} from '@msgpack/msgpack';\r\n// Side-effect import: patches Encoder.prototype to handle JS Map instances\r\n// as proper msgpack maps with typed keys (.NET-wire-compatible).\r\nimport './msgpack-map-patch.js';\r\nimport { getLogs } from './logging.js';\r\nimport type { RpcMessage } from './rpc-message.js';\r\nimport {\r\n ENVELOPE_DELIMITER,\r\n ARG_DELIMITER,\r\n FRAME_DELIMITER,\r\n} from './rpc-message.js';\r\n\r\nconst { warnLog } = getLogs('RpcSerialization');\r\n\r\n// ============================================================\r\n// Text format (json5np) — V3 wire format\r\n// ============================================================\r\n\r\n/** Serializes an RpcMessage + args into the json5 wire format. */\r\nexport function serializeMessage(\r\n message: RpcMessage,\r\n args?: unknown[]\r\n): string {\r\n const envelope = JSON.stringify(message);\r\n if (args === undefined || args.length === 0) {\r\n return envelope + ENVELOPE_DELIMITER;\r\n }\r\n const argsStr = args.map(a => JSON.stringify(a)).join(ARG_DELIMITER);\r\n return envelope + ENVELOPE_DELIMITER + argsStr;\r\n}\r\n\r\n/** Serializes multiple messages into a single WebSocket frame. */\r\nexport function serializeFrame(messages: string[]): string {\r\n return messages.join(FRAME_DELIMITER);\r\n}\r\n\r\n/** Splits a WebSocket text frame into individual message strings. */\r\nexport function splitFrame(frame: string): string[] {\r\n return frame.split(FRAME_DELIMITER);\r\n}\r\n\r\n/** Deserializes a single message string into envelope + args. */\r\nexport function deserializeMessage(raw: string): {\r\n message: RpcMessage;\r\n args: unknown[];\r\n} {\r\n const nlIndex = raw.indexOf(ENVELOPE_DELIMITER);\r\n if (nlIndex === -1) {\r\n return { message: JSON.parse(raw) as RpcMessage, args: [] };\r\n }\r\n\r\n const envelopeStr = raw.substring(0, nlIndex);\r\n const argsStr = raw.substring(nlIndex + 1);\r\n const message = JSON.parse(envelopeStr) as RpcMessage;\r\n\r\n if (argsStr.length === 0) {\r\n return { message, args: [] };\r\n }\r\n\r\n const args = argsStr.split(ARG_DELIMITER).map(s => {\r\n try {\r\n return JSON.parse(s) as unknown;\r\n } catch {\r\n warnLog?.log(`failed to parse arg as JSON: ${s.substring(0, 100)}`);\r\n return s; // Return raw string if not valid JSON\r\n }\r\n });\r\n\r\n return { message, args };\r\n}\r\n\r\n// ============================================================\r\n// Binary format (msgpack6np) — V5 wire format\r\n// ============================================================\r\n\r\nconst textEncoder = new TextEncoder();\r\nconst textDecoder = new TextDecoder();\r\n\r\n/**\r\n * Default module-level encoder/decoder. RpcWebSocketConnection passes its own\r\n * per-connection instances for the hot path; these defaults cover callers\r\n * that don't bother (tests, one-shot uses).\r\n *\r\n * Reusing an Encoder across calls is critical at high message rates because\r\n * - construction cost (internal resolvers, buffer allocation) is nontrivial;\r\n * - the internal write buffer stays at its largest observed size after the\r\n * first few encodes, eliminating `resizeBuffer` growth churn (which was\r\n * ~5% self time in the 300-pull profile).\r\n *\r\n * NOT thread-safe and NOT re-entrant. Safe under Node's single-threaded event\r\n * loop as long as `serializeBinaryMessage` is called synchronously (which it\r\n * always is — no awaits inside).\r\n *\r\n * Sized large enough to fit a typical video frame (~11 KB) + envelope\r\n * overhead without any `resizeBuffer` growth.\r\n *\r\n * `useBigInt64: true` makes the encoder emit JS `bigint` values as msgpack\r\n * int64/uint64 (0xd3/0xcf) instead of float64. Required for .NET `long`\r\n * fields whose 2026-era values (e.g. `Moment.EpochOffsetTicks` ~1.8×10¹⁶)\r\n * exceed `Number.MAX_SAFE_INTEGER` — a float64 payload would be rejected by\r\n * the server's `ReadInt64` path. Callers must pass a real `bigint` (not a\r\n * `number`) for such fields; `number` values follow the standard\r\n * int-or-float branching regardless of this flag.\r\n */\r\nconst INITIAL_ENCODER_BUFFER_SIZE = 32 * 1024;\r\nexport const defaultBinaryEncoder = new Encoder({ initialBufferSize: INITIAL_ENCODER_BUFFER_SIZE, useBigInt64: true });\r\nexport const defaultBinaryDecoder = new Decoder();\r\n/** Exposed so connection code can instantiate a matching Encoder\r\n * with the same initial buffer size without needing to reach into\r\n * this module's private constants. */\r\nexport function createBinaryEncoder(): Encoder {\r\n return new Encoder({ initialBufferSize: INITIAL_ENCODER_BUFFER_SIZE, useBigInt64: true });\r\n}\r\n\r\n// --- LEB128 VarUInt helpers ---\r\n\r\n/** Number of bytes a VarUint encoding of `value` will occupy. */\r\nfunction varUintByteLen(value: number): number {\r\n if (value < 0) value = 0;\r\n let n = 0;\r\n do {\r\n n++;\r\n value >>>= 7;\r\n } while (value > 0);\r\n return n;\r\n}\r\n\r\n/** Write VarUint directly into a `Uint8Array` at `pos`. Returns new pos. */\r\nfunction writeVarUintInto(out: Uint8Array, pos: number, value: number): number {\r\n if (value < 0) value = 0;\r\n do {\r\n let byte = value & 0x7f;\r\n value >>>= 7;\r\n if (value > 0) byte |= 0x80;\r\n out[pos++] = byte;\r\n } while (value > 0);\r\n return pos;\r\n}\r\n\r\nfunction readVarUint(\r\n data: Uint8Array,\r\n offset: number\r\n): { value: number; bytesRead: number } {\r\n let value = 0,\r\n shift = 0,\r\n bytesRead = 0;\r\n do {\r\n if (offset + bytesRead >= data.length) break;\r\n const byte = data[offset + bytesRead];\r\n value |= (byte & 0x7f) << shift;\r\n bytesRead++;\r\n if ((byte & 0x80) === 0) break;\r\n shift += 7;\r\n } while (shift < 35); // Max 5 bytes for uint32\r\n return { value: value >>> 0, bytesRead };\r\n}\r\n\r\n// --- Binary helpers ---\r\n\r\n// Smallest V5 envelope: flags byte + 1-byte VarUint relatedId + 1-byte VarUint\r\n// method length + 4-byte argument data length. V5Compact swaps the LVar method\r\n// name for a 4-byte hash. Both are used as the minimum per-message advance the\r\n// frame splitters accept, so a malformed envelope can never make them reparse\r\n// bytes they've already consumed.\r\nconst MIN_BINARY_ENVELOPE_SIZE = 7;\r\nconst MIN_COMPACT_BINARY_ENVELOPE_SIZE = 10;\r\n\r\n// Every wire-supplied length must pass through here before it's used to slice or\r\n// advance: an unvalidated one lets a hostile peer read past the frame or (when\r\n// read as a signed int) move the cursor backwards.\r\nfunction requireInBounds(data: Uint8Array, pos: number, length: number, what: string): number {\r\n const end = pos + length;\r\n if (!Number.isSafeInteger(length) || length < 0 || end > data.length)\r\n throw new Error(`Invalid binary RPC message: ${what} length ${length} at ${pos} is out of bounds.`);\r\n\r\n return end;\r\n}\r\n\r\n// Skips `headerCount` V5 headers (key: L1Memory, value: LVarSpan) starting at\r\n// `pos`, returning the position past them. The TS client never emits headers,\r\n// but .NET does (e.g. Activity injection), and per the V5 layout they follow\r\n// the argument data — so they must be accounted for in `bytesRead`.\r\nfunction skipHeaders(data: Uint8Array, pos: number, headerCount: number): number {\r\n for (let h = 0; h < headerCount; h++) {\r\n if (pos >= data.length)\r\n throw new Error('Invalid binary RPC message: truncated header.');\r\n\r\n const keyLen = data[pos++];\r\n pos = requireInBounds(data, pos, keyLen, 'header key');\r\n const valLen = readVarUint(data, pos);\r\n if (valLen.bytesRead === 0)\r\n throw new Error('Invalid binary RPC message: truncated header value length.');\r\n\r\n pos = requireInBounds(data, pos + valLen.bytesRead, valLen.value, 'header value');\r\n }\r\n return pos;\r\n}\r\n\r\n/**\r\n * Parses a polymorphism marker (as written by .NET `ByteTypeSerializer`) at\r\n * `offset`. The msgpack6 format prefixes every *polymorphic* argument/result\r\n * with a 2-byte little-endian length:\r\n * - 0 → expected (non-polymorphic) type — the `\\0\\0` fast path\r\n * - 1 → NullValue (the value itself is absent)\r\n * - >= 2 → a derived type (2-byte length + 2-byte hash + UTF-8 name)\r\n *\r\n * Returns how many bytes the marker occupies and whether the value is a\r\n * NullValue. Derived-type markers are unsupported by the TS client and throw a\r\n * clear error instead of letting the marker bytes corrupt the msgpack frame.\r\n */\r\nexport function readPolymorphismMarker(\r\n data: Uint8Array,\r\n offset: number\r\n): { bytesRead: number; isNull: boolean } {\r\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\r\n const length = view.getUint16(offset, true);\r\n if (length === 0)\r\n return { bytesRead: 2, isNull: false };\r\n if (length === 1)\r\n return { bytesRead: 2, isNull: true };\r\n\r\n throw new Error('polymorphic payloads are not supported by the TS client');\r\n}\r\n\r\nfunction concatUint8Arrays(arrays: Uint8Array[]): Uint8Array {\r\n let totalLength = 0;\r\n for (const arr of arrays) totalLength += arr.length;\r\n const result = new Uint8Array(totalLength);\r\n let offset = 0;\r\n for (const arr of arrays) {\r\n result.set(arr, offset);\r\n offset += arr.length;\r\n }\r\n return result;\r\n}\r\n\r\n/**\r\n * Serializes an RpcMessage + args into V5 binary format (msgpack6np).\r\n *\r\n * Layout (NO frame-level size prefix; V5 with PersistsMessageSize=false):\r\n * envelope + argData\r\n * Envelope:\r\n * [byte 0: (CallTypeId<<5) | HeaderCount] [VarUint relatedId]\r\n * [LVarSpan methodRef] [4-byte LE argLen]\r\n * ArgData: concatenated MessagePack-encoded arguments\r\n *\r\n * Single-allocation build: pre-encodes args with the supplied reusable\r\n * `Encoder`, computes the total envelope size, then writes everything\r\n * into one `Uint8Array`. Eliminates the previous build's intermediate\r\n * `headerParts` number array + four `Uint8Array` concats per call,\r\n * which showed up as ~2% self time in the 300-pull profile and\r\n * pulled the msgpack `resizeBuffer` + `Encoder` constructor along\r\n * with it for another ~6%.\r\n *\r\n * NOTE: `@msgpack/msgpack` `Encoder.encode()` already returns a fresh\r\n * copy of the internal buffer (there's a separate `encodeSharedRef()`\r\n * for zero-copy access), so we can push the results straight into\r\n * `argBufs` without an extra slice. The benefit of reusing the Encoder\r\n * is that its internal write buffer stays at its high-water mark —\r\n * once the first ~11 KB frame has grown it to the full size, no\r\n * subsequent `resizeBuffer` calls are needed.\r\n */\r\nexport function serializeBinaryMessage(\r\n message: RpcMessage,\r\n args?: unknown[],\r\n encoder: Encoder = defaultBinaryEncoder\r\n): Uint8Array {\r\n // 1. Encode each arg. encode() returns a fresh Uint8Array copy per\r\n // call, so the results are safe to retain across subsequent encodes.\r\n let argsDataLen = 0;\r\n let argBufs: Uint8Array[] | null = null;\r\n if (args && args.length > 0) {\r\n argBufs = new Array<Uint8Array>(args.length);\r\n for (let i = 0; i < args.length; i++) {\r\n const buf = encoder.encode(args[i]);\r\n argBufs[i] = buf;\r\n argsDataLen += buf.length;\r\n }\r\n }\r\n\r\n // 2. Compute header sizes without intermediate allocations.\r\n const callType = message.CallType ?? 0;\r\n const relatedId = message.RelatedId ?? 0;\r\n const methodBytes = textEncoder.encode(message.Method ?? '');\r\n const relatedIdLen = varUintByteLen(relatedId);\r\n const methodLenVarintLen = varUintByteLen(methodBytes.length);\r\n const headerSize =\r\n 1 + relatedIdLen + methodLenVarintLen + methodBytes.length;\r\n const totalSize = headerSize + 4 + argsDataLen;\r\n\r\n // 3. Allocate the final wire buffer once and fill it in place.\r\n const out = new Uint8Array(totalSize);\r\n let pos = 0;\r\n out[pos++] = (callType << 5) & 0xe0;\r\n pos = writeVarUintInto(out, pos, relatedId);\r\n pos = writeVarUintInto(out, pos, methodBytes.length);\r\n out.set(methodBytes, pos);\r\n pos += methodBytes.length;\r\n // argLen as int32 LE\r\n out[pos++] = argsDataLen & 0xff;\r\n out[pos++] = (argsDataLen >>> 8) & 0xff;\r\n out[pos++] = (argsDataLen >>> 16) & 0xff;\r\n out[pos++] = (argsDataLen >>> 24) & 0xff;\r\n if (argBufs !== null) {\r\n for (const buf of argBufs) {\r\n out.set(buf, pos);\r\n pos += buf.length;\r\n }\r\n }\r\n return out;\r\n}\r\n\r\n/**\r\n * Deserializes a single V5 binary message starting at `offset`.\r\n * Returns parsed message, args, and number of bytes consumed.\r\n *\r\n * Accepts an optional `Decoder` for reuse across calls on the same\r\n * connection — avoids constructing a new `@msgpack/msgpack.Decoder`\r\n * (and its internal resolver/table state) on every inbound message.\r\n */\r\nexport function deserializeBinaryMessage(\r\n data: Uint8Array,\r\n offset: number,\r\n decoder: Decoder = defaultBinaryDecoder\r\n): { message: RpcMessage; args: unknown[]; bytesRead: number } {\r\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\r\n if (offset + MIN_BINARY_ENVELOPE_SIZE > data.length)\r\n throw new Error(`Invalid binary RPC message: truncated envelope at ${offset}.`);\r\n\r\n // V5: no frame-level size prefix. Parse envelope directly.\r\n let pos = offset;\r\n\r\n // Byte 0: upper 3 bits = CallTypeId, lower 5 bits = HeaderCount\r\n const byte0 = data[pos++];\r\n const callTypeId = (byte0 >> 5) & 0x7;\r\n const headerCount = byte0 & 0x1f;\r\n\r\n // RelatedId as VarUint\r\n const relId = readVarUint(data, pos);\r\n pos += relId.bytesRead;\r\n\r\n // MethodRef as LVarSpan\r\n const methodLen = readVarUint(data, pos);\r\n pos += methodLen.bytesRead;\r\n const methodEnd = requireInBounds(data, pos, methodLen.value, 'method');\r\n const method = textDecoder.decode(data.subarray(pos, methodEnd));\r\n pos = methodEnd;\r\n\r\n // ArgData length as fixed 4-byte LE, immediately followed by argData (V5\r\n // writes headers AFTER the argument data — see RpcByteMessageSerializerV5).\r\n // Must be read unsigned: as a signed int it can be negative, which moves the\r\n // cursor backwards and makes the frame splitter reparse the same bytes.\r\n if (pos + 4 > data.length)\r\n throw new Error(`Invalid binary RPC message: truncated argument data length at ${pos}.`);\r\n\r\n const argDataLen = view.getUint32(pos, true);\r\n pos += 4;\r\n\r\n // Deserialize arguments from argData — multiple concatenated MessagePack values\r\n const args: unknown[] = [];\r\n const argEnd = requireInBounds(data, pos, argDataLen, 'argument data');\r\n if (argDataLen > 0) {\r\n const argSlice = data.subarray(pos, argEnd);\r\n // Reuse the provided decoder across calls; `decodeMulti` is a generator\r\n // so each yield completes before the next `decode` begins, which is\r\n // safe on a single-threaded event loop.\r\n for (const decoded of decoder.decodeMulti(argSlice)) {\r\n args.push(decoded);\r\n }\r\n }\r\n pos = argEnd;\r\n\r\n // Headers (if any) follow the argument data. The TS client ignores their\r\n // content but must skip them so `bytesRead` covers the whole message.\r\n if (headerCount > 0)\r\n pos = skipHeaders(data, pos, headerCount);\r\n\r\n const message: RpcMessage = {\r\n Method: method,\r\n RelatedId: relId.value,\r\n CallType: callTypeId,\r\n };\r\n\r\n return { message, args, bytesRead: pos - offset };\r\n}\r\n\r\n/**\r\n * Splits a binary WebSocket frame into individual deserialized messages.\r\n * V5 envelopes are self-delimiting (header + method + argLen + argData), so\r\n * multiple envelopes concatenated in a single WebSocket frame can be decoded\r\n * by iterating with the bytesRead returned from deserializeBinaryMessage.\r\n *\r\n * This must stay symmetric with serializeBinaryFrame() (which concatenates\r\n * multiple envelopes). The .NET server batches outbound RPC messages into a\r\n * single WebSocket frame via WebSocketChannel/WriteDelayer, so any assumption\r\n * that a frame is a single envelope silently drops every trailing message.\r\n *\r\n * Accepts an optional `Decoder` for reuse across calls on the same\r\n * connection — forwarded to `deserializeBinaryMessage`.\r\n */\r\nexport function splitBinaryFrame(\r\n frame: Uint8Array,\r\n decoder: Decoder = defaultBinaryDecoder\r\n): { message: RpcMessage; args: unknown[] }[] {\r\n const results: { message: RpcMessage; args: unknown[] }[] = [];\r\n let offset = 0;\r\n while (offset < frame.length) {\r\n let parsed: { message: RpcMessage; args: unknown[]; bytesRead: number };\r\n try {\r\n parsed = deserializeBinaryMessage(frame, offset, decoder);\r\n }\r\n catch (e) {\r\n // A malformed envelope leaves no resync point, but everything decoded\r\n // before it is still valid - and .NET batches many messages per frame,\r\n // so dropping them all would strand the $sys.Ok replies among them\r\n // (per-call timeouts are unbounded by default, i.e. those calls hang).\r\n warnLog?.log(`Failed to parse a binary message at offset ${offset}:`, e);\r\n break;\r\n }\r\n const { message, args, bytesRead } = parsed;\r\n // Anything below one minimal envelope means the parse went wrong; advancing\r\n // by it would reparse bytes already consumed and invent extra \"messages\".\r\n if (!Number.isFinite(bytesRead) || bytesRead < MIN_BINARY_ENVELOPE_SIZE)\r\n break;\r\n\r\n results.push({ message, args });\r\n offset += bytesRead;\r\n }\r\n return results;\r\n}\r\n\r\n/**\r\n * Serializes multiple binary messages into a single WebSocket frame.\r\n * Simply concatenates the already size-prefixed messages.\r\n */\r\nexport function serializeBinaryFrame(messages: Uint8Array[]): Uint8Array {\r\n return concatUint8Arrays(messages);\r\n}\r\n\r\n// ============================================================\r\n// Compact binary format (V5Compact) — 4-byte method hash\r\n// ============================================================\r\n\r\nimport type { RpcMethodRegistry } from './rpc-method-registry.js';\r\n\r\n/**\r\n * Serializes an RpcMessage + args into V5Compact binary format.\r\n * Same as V5 except the method is encoded as a 4-byte LE hash\r\n * instead of an LVar-prefixed UTF-8 string.\r\n *\r\n * Layout:\r\n * [byte 0: (CallTypeId<<5) | HeaderCount] [VarUint relatedId]\r\n * [4-byte LE methodHash] [4-byte LE argLen]\r\n * [argData]\r\n */\r\nexport function serializeCompactBinaryMessage(\r\n message: RpcMessage,\r\n args?: unknown[],\r\n registry?: RpcMethodRegistry,\r\n encoder: Encoder = defaultBinaryEncoder\r\n): Uint8Array {\r\n // 1. Encode args\r\n let argsDataLen = 0;\r\n let argBufs: Uint8Array[] | null = null;\r\n if (args && args.length > 0) {\r\n argBufs = new Array<Uint8Array>(args.length);\r\n for (let i = 0; i < args.length; i++) {\r\n const buf = encoder.encode(args[i]);\r\n argBufs[i] = buf;\r\n argsDataLen += buf.length;\r\n }\r\n }\r\n\r\n // 2. Get method hash\r\n const methodName = message.Method ?? '';\r\n const methodHash = registry?.requireHash(methodName) ?? 0;\r\n\r\n // 3. Compute sizes\r\n const callType = message.CallType ?? 0;\r\n const relatedId = message.RelatedId ?? 0;\r\n const relatedIdLen = varUintByteLen(relatedId);\r\n // Compact: 4 bytes for hash (instead of LVar method name)\r\n const headerSize = 1 + relatedIdLen + 4;\r\n const totalSize = headerSize + 4 + argsDataLen;\r\n\r\n // 4. Build wire buffer\r\n const out = new Uint8Array(totalSize);\r\n let pos = 0;\r\n out[pos++] = (callType << 5) & 0xe0;\r\n pos = writeVarUintInto(out, pos, relatedId);\r\n // Method hash as 4-byte LE uint32\r\n const hashU32 = methodHash >>> 0;\r\n out[pos++] = hashU32 & 0xff;\r\n out[pos++] = (hashU32 >>> 8) & 0xff;\r\n out[pos++] = (hashU32 >>> 16) & 0xff;\r\n out[pos++] = (hashU32 >>> 24) & 0xff;\r\n // argLen as int32 LE\r\n out[pos++] = argsDataLen & 0xff;\r\n out[pos++] = (argsDataLen >>> 8) & 0xff;\r\n out[pos++] = (argsDataLen >>> 16) & 0xff;\r\n out[pos++] = (argsDataLen >>> 24) & 0xff;\r\n if (argBufs !== null) {\r\n for (const buf of argBufs) {\r\n out.set(buf, pos);\r\n pos += buf.length;\r\n }\r\n }\r\n return out;\r\n}\r\n\r\n/**\r\n * Deserializes a single V5Compact binary message starting at `offset`.\r\n * Reads a 4-byte LE method hash and resolves it via the registry.\r\n */\r\nexport function deserializeCompactBinaryMessage(\r\n data: Uint8Array,\r\n offset: number,\r\n registry?: RpcMethodRegistry,\r\n decoder: Decoder = defaultBinaryDecoder\r\n): { message: RpcMessage; args: unknown[]; bytesRead: number } {\r\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\r\n if (offset + MIN_COMPACT_BINARY_ENVELOPE_SIZE > data.length)\r\n throw new Error(`Invalid compact binary RPC message: truncated envelope at ${offset}.`);\r\n\r\n let pos = offset;\r\n\r\n // Byte 0: upper 3 bits = CallTypeId, lower 5 bits = HeaderCount\r\n const byte0 = data[pos++];\r\n const callTypeId = (byte0 >> 5) & 0x7;\r\n const headerCount = byte0 & 0x1f;\r\n\r\n // RelatedId as VarUint\r\n const relId = readVarUint(data, pos);\r\n pos += relId.bytesRead;\r\n\r\n // Method hash as fixed 4-byte LE uint32\r\n if (pos + 8 > data.length)\r\n throw new Error(`Invalid compact binary RPC message: truncated envelope at ${offset}.`);\r\n\r\n const methodHash = view.getUint32(pos, true);\r\n pos += 4;\r\n const method =\r\n registry?.getName(methodHash | 0) ??\r\n `<hash:0x${methodHash.toString(16).padStart(8, '0')}>`;\r\n\r\n // ArgData length as fixed 4-byte LE, immediately followed by argData\r\n // (V5Compact writes headers AFTER the argument data, same as V5).\r\n // Unsigned for the same reason as in deserializeBinaryMessage.\r\n const argDataLen = view.getUint32(pos, true);\r\n pos += 4;\r\n\r\n // Deserialize arguments\r\n const args: unknown[] = [];\r\n const argEnd = requireInBounds(data, pos, argDataLen, 'argument data');\r\n if (argDataLen > 0) {\r\n const argSlice = data.subarray(pos, argEnd);\r\n for (const decoded of decoder.decodeMulti(argSlice)) {\r\n args.push(decoded);\r\n }\r\n }\r\n pos = argEnd;\r\n\r\n // Headers (if any) follow the argument data.\r\n if (headerCount > 0)\r\n pos = skipHeaders(data, pos, headerCount);\r\n\r\n const message: RpcMessage = {\r\n Method: method,\r\n RelatedId: relId.value,\r\n CallType: callTypeId,\r\n };\r\n\r\n return { message, args, bytesRead: pos - offset };\r\n}\r\n\r\n/**\r\n * Splits a compact binary frame into individual deserialized messages.\r\n */\r\nexport function splitCompactBinaryFrame(\r\n frame: Uint8Array,\r\n registry?: RpcMethodRegistry,\r\n decoder: Decoder = defaultBinaryDecoder\r\n): { message: RpcMessage; args: unknown[] }[] {\r\n const results: { message: RpcMessage; args: unknown[] }[] = [];\r\n let offset = 0;\r\n while (offset < frame.length) {\r\n let parsed: { message: RpcMessage; args: unknown[]; bytesRead: number };\r\n try {\r\n parsed = deserializeCompactBinaryMessage(frame, offset, registry, decoder);\r\n }\r\n catch (e) {\r\n warnLog?.log(`Failed to parse a compact binary message at offset ${offset}:`, e);\r\n break;\r\n }\r\n const { message, args, bytesRead } = parsed;\r\n if (!Number.isFinite(bytesRead) || bytesRead < MIN_COMPACT_BINARY_ENVELOPE_SIZE)\r\n break;\r\n\r\n results.push({ message, args });\r\n offset += bytesRead;\r\n }\r\n return results;\r\n}\r\n","import { Encoder } from '@msgpack/msgpack';\r\n\r\n/**\r\n * Patches `@msgpack/msgpack`'s `Encoder` so JS `Map` instances are encoded\r\n * as proper msgpack `map` types with typed keys (matching .NET's\r\n * `Dictionary<K, V>` wire format). Without this patch the encoder treats\r\n * `Map` as a plain object — since `Object.keys(new Map())` returns `[]`,\r\n * any `Map` is silently serialized as an empty `{}`.\r\n *\r\n * Idempotent: the patch is applied at most once per process.\r\n *\r\n * This is needed by the `$sys.Reconnect:3` protocol to send\r\n * `Dictionary<int, byte[]>` over msgpack in a way that a .NET server can\r\n * deserialize. The JSON side is naturally compatible (keys become strings\r\n * in JSON regardless) — this patch fixes the binary path only.\r\n */\r\n/* eslint-disable @typescript-eslint/no-explicit-any,\r\n @typescript-eslint/no-unsafe-member-access,\r\n @typescript-eslint/no-unsafe-call,\r\n @typescript-eslint/no-unsafe-assignment\r\n -- the entire patch reaches into private Encoder internals to make Map\r\n encoding work; the type annotations on `@msgpack/msgpack` don't expose\r\n these methods so all access is necessarily untyped. */\r\nexport function patchMsgpackEncoderForMaps(): void {\r\n const proto = Encoder.prototype as any;\r\n if (proto._hasMapPatch) return;\r\n proto._hasMapPatch = true;\r\n\r\n const originalDoEncode = proto.doEncode as (object: unknown, depth: number) => void;\r\n\r\n proto.doEncode = function (this: unknown, object: unknown, depth: number): void {\r\n if (object instanceof Map) {\r\n _encodeMapInstance.call(this, object, depth);\r\n return;\r\n }\r\n originalDoEncode.call(this, object, depth);\r\n };\r\n\r\n function _encodeMapInstance(this: unknown, m: Map<unknown, unknown>, depth: number): void {\r\n const self = this as any;\r\n const size = m.size;\r\n if (size < 16) {\r\n self.writeU8(0x80 + size);\r\n } else if (size < 0x10000) {\r\n self.writeU8(0xde);\r\n self.writeU16(size);\r\n } else {\r\n self.writeU8(0xdf);\r\n self.writeU32(size);\r\n }\r\n for (const [key, value] of m) {\r\n // Recurse via the (now patched) doEncode so nested Maps work too.\r\n self.doEncode(key, depth + 1);\r\n self.doEncode(value, depth + 1);\r\n }\r\n }\r\n}\r\n/* eslint-enable @typescript-eslint/no-explicit-any,\r\n @typescript-eslint/no-unsafe-member-access,\r\n @typescript-eslint/no-unsafe-call,\r\n @typescript-eslint/no-unsafe-assignment */\r\n\r\n// Apply at module load — idempotent.\r\npatchMsgpackEncoderForMaps();\r\n","// .NET counterparts:\r\n// RpcConnection — thin wrapper around RpcTransport + PropertyBag. RpcTransport\r\n// is an abstract IAsyncEnumerable<RpcInboundMessage> + Send() that can be\r\n// WebSocket-based or in-memory. The transport also handles frame batching and\r\n// back-pressure via a Channel<T> send queue.\r\n//\r\n// Omitted from .NET:\r\n// - PropertyBag on connection — used in .NET for per-connection metadata (e.g.\r\n// authentication info attached by middleware). TS has no middleware pipeline,\r\n// so no need for an extensible property bag.\r\n// - IsLocal flag — .NET distinguishes local loopback connections (same-process\r\n// server) from remote ones. TS is always a remote client.\r\n// - RpcTransport as IAsyncEnumerable — .NET reads inbound messages via\r\n// async iteration with cancellation. TS uses event-based delivery\r\n// (messageReceived handler) because browser WebSocket API is event-driven.\r\n// - Channel<RpcOutboundMessage> send queue with backpressure — .NET buffers\r\n// outbound messages in an async channel that the transport drains. TS sends\r\n// synchronously on the WebSocket; if the socket isn't ready, messages are\r\n// buffered in _sendBuffer (CONNECTING) or silently dropped (CLOSING/CLOSED).\r\n// JS is single-threaded so there's no contention, and WebSocket.send() itself\r\n// handles buffering at the OS level.\r\n// - IAsyncDisposable — .NET transports are disposable resources. TS connections\r\n// are closed via close() and garbage-collected.\r\n\r\nimport { Encoder, Decoder } from '@msgpack/msgpack';\r\nimport { PromiseSource, EventHandlerSet } from '@actuallab/core';\r\nimport { getLogs } from './logging.js';\r\nimport {\r\n splitFrame,\r\n serializeFrame,\r\n splitBinaryFrame as splitBinaryFrameFn,\r\n serializeBinaryFrame,\r\n createBinaryEncoder,\r\n} from './rpc-serialization.js';\r\nimport type { RpcMessage } from './rpc-message.js';\r\nimport type { RpcSerializationFormat } from './rpc-serialization-format.js';\r\nimport type { RpcMethodRegistry } from './rpc-method-registry.js';\r\n\r\nconst { warnLog } = getLogs('RpcConnection');\r\n\r\n/** Abstract WebSocket interface — works with both browser WebSocket and Node.js ws. */\r\nexport interface WebSocketLike {\r\n readonly readyState: number;\r\n binaryType?: string;\r\n send(data: string | ArrayBufferLike | Uint8Array | ArrayBufferView): void;\r\n close(code?: number, reason?: string): void;\r\n onopen: ((ev: unknown) => void) | null;\r\n onmessage: ((ev: { data: unknown }) => void) | null;\r\n onclose: ((ev: { code: number; reason: string }) => void) | null;\r\n onerror: ((ev: unknown) => void) | null;\r\n}\r\n\r\nexport const WebSocketState = {\r\n CONNECTING: 0,\r\n OPEN: 1,\r\n CLOSING: 2,\r\n CLOSED: 3,\r\n} as const;\r\n\r\n/** Received message — either text (string) or binary (already parsed). */\r\nexport type RpcReceivedMessage =\r\n | { kind: 'text'; raw: string }\r\n | { kind: 'binary'; message: RpcMessage; args: unknown[] };\r\n\r\n/** Abstract RPC connection — transport-agnostic interface for sending/receiving messages. */\r\nexport interface RpcConnection {\r\n readonly isOpen: boolean;\r\n readonly binaryMode: boolean;\r\n readonly whenConnected: Promise<void>;\r\n readonly messageReceived: EventHandlerSet<RpcReceivedMessage>;\r\n readonly closed: EventHandlerSet<{ code: number; reason: string }>;\r\n /** Optional reusable msgpack encoder for outbound binary messages.\r\n * Callers passing this to `serializeBinaryMessage` skip the per-call\r\n * Encoder construction + resizeBuffer growth. Implementations that\r\n * don't support binary serialization can leave it undefined. */\r\n readonly encoder?: Encoder;\r\n send(serializedMessage: string): void;\r\n sendBinary(data: Uint8Array): void;\r\n close(code?: number, reason?: string): void;\r\n}\r\n\r\n/** WebSocket-based RpcConnection — handles frame splitting, binary/text modes, and message queueing. */\r\nexport class RpcWebSocketConnection implements RpcConnection {\r\n private _ws: WebSocketLike;\r\n private _sendBuffer: (string | Uint8Array)[] = [];\r\n private _connected = new PromiseSource<void>();\r\n\r\n /** Per-connection msgpack encoder — reused across outbound messages to\r\n * amortise construction cost and to keep the internal write buffer at\r\n * its largest observed size (avoids repeated `resizeBuffer` growth on\r\n * large video frames). See serializeBinaryMessage() for details. */\r\n private _encoder: Encoder = createBinaryEncoder();\r\n\r\n /** Per-connection msgpack decoder — reused across inbound messages\r\n * rather than reconstructing one for every `decodeMulti` call. */\r\n private _decoder: Decoder = new Decoder();\r\n\r\n /** Exposed for callers who need to encode outbound binary messages\r\n * through this connection (e.g. `serializeBinaryMessage`). Safe to\r\n * pass to synchronous encode calls on the same event-loop turn. */\r\n get encoder(): Encoder {\r\n return this._encoder;\r\n }\r\n get decoder(): Decoder {\r\n return this._decoder;\r\n }\r\n\r\n private _format: RpcSerializationFormat | undefined;\r\n private _methodRegistry: RpcMethodRegistry | undefined;\r\n\r\n readonly binaryMode: boolean;\r\n readonly messageReceived = new EventHandlerSet<RpcReceivedMessage>();\r\n readonly closed = new EventHandlerSet<{ code: number; reason: string }>();\r\n readonly error = new EventHandlerSet<unknown>();\r\n\r\n constructor(\r\n ws: WebSocketLike,\r\n binaryMode = false,\r\n format?: RpcSerializationFormat,\r\n methodRegistry?: RpcMethodRegistry\r\n ) {\r\n this._ws = ws;\r\n this.binaryMode = binaryMode;\r\n this._format = format;\r\n this._methodRegistry = methodRegistry;\r\n\r\n if (binaryMode && ws.binaryType !== undefined)\r\n ws.binaryType = 'arraybuffer';\r\n\r\n if (ws.readyState === WebSocketState.OPEN) {\r\n this._connected.resolve();\r\n this._flush();\r\n }\r\n\r\n ws.onopen = () => {\r\n this._connected.resolve();\r\n this._flush();\r\n };\r\n\r\n ws.onmessage = ev => {\r\n // Binary frame — V5 self-delimiting envelopes (one or more per WS frame).\r\n //\r\n // Accept BOTH `ArrayBuffer` (browser WebSocket when `binaryType` is\r\n // 'arraybuffer') AND `Uint8Array`-shaped views (Node `ws` delivering a\r\n // `Buffer`, which is a `Uint8Array` subclass). The node-ws adapter\r\n // intentionally passes Buffer through without an ArrayBuffer copy —\r\n // at 300 concurrent video streams that copy was ~100 MB/s of pure\r\n // memcpy + allocation pressure. Matching `ArrayBuffer.isView` here\r\n // keeps the zero-copy path alive all the way to `splitBinaryFrame`.\r\n if (\r\n ev.data instanceof Uint8Array ||\r\n ev.data instanceof ArrayBuffer\r\n ) {\r\n // Node `Buffer` is a `Uint8Array` subclass, so we can hand it to\r\n // `splitBinaryFrame` directly with no view wrapping or copying.\r\n // Browsers deliver `ArrayBuffer`, which needs a single zero-copy\r\n // view wrap.\r\n const frame =\r\n ev.data instanceof Uint8Array\r\n ? ev.data\r\n : new Uint8Array(ev.data);\r\n let messages: { message: RpcMessage; args: unknown[] }[];\r\n try {\r\n messages = this._splitBinary(frame);\r\n } catch (e) {\r\n warnLog?.log('Failed to split binary frame:', e);\r\n this.error.trigger(e);\r\n return;\r\n }\r\n for (const { message, args } of messages)\r\n this._dispatchBinary(message, args);\r\n } else if (\r\n typeof Blob !== 'undefined' &&\r\n ev.data instanceof Blob &&\r\n this.binaryMode\r\n ) {\r\n // Fallback: some Chromium builds deliver the first frames as Blob even\r\n // after setting binaryType='arraybuffer'. Convert and route through\r\n // the binary path so we don't silently drop them.\r\n void ev.data.arrayBuffer().then(ab => {\r\n const frame = new Uint8Array(ab);\r\n let messages: { message: RpcMessage; args: unknown[] }[];\r\n try {\r\n messages = this._splitBinary(frame);\r\n } catch (e) {\r\n warnLog?.log('Failed to split binary blob frame:', e);\r\n this.error.trigger(e);\r\n return;\r\n }\r\n for (const { message, args } of messages)\r\n this._dispatchBinary(message, args);\r\n });\r\n } else {\r\n // Text frame — JSON delimited messages\r\n const data =\r\n typeof ev.data === 'string' ? ev.data : String(ev.data);\r\n for (const msg of splitFrame(data))\r\n if (msg.length > 0)\r\n this.messageReceived.triggerSafe(\r\n { kind: 'text', raw: msg },\r\n e => warnLog?.log('Failed to handle a text message:', e));\r\n }\r\n };\r\n\r\n ws.onclose = ev => {\r\n this.closed.trigger({ code: ev.code, reason: ev.reason });\r\n };\r\n\r\n ws.onerror = ev => {\r\n this.error.trigger(ev);\r\n };\r\n }\r\n\r\n get isOpen(): boolean {\r\n return this._ws.readyState === WebSocketState.OPEN;\r\n }\r\n\r\n get whenConnected(): Promise<void> {\r\n return this._connected;\r\n }\r\n\r\n send(serializedMessage: string): void {\r\n this._sendRaw(serializedMessage);\r\n }\r\n\r\n sendBinary(data: Uint8Array): void {\r\n this._sendRaw(data);\r\n }\r\n\r\n sendTextBatch(messages: string[]): void {\r\n this._sendRaw(serializeFrame(messages));\r\n }\r\n\r\n sendBinaryBatch(messages: Uint8Array[]): void {\r\n this._sendRaw(serializeBinaryFrame(messages));\r\n }\r\n\r\n /** Split a binary frame using the format (compact-aware) or default splitter. */\r\n private _splitBinary(\r\n frame: Uint8Array\r\n ): { message: RpcMessage; args: unknown[] }[] {\r\n if (this._format) {\r\n return this._format.splitBinaryFrame(\r\n frame,\r\n this._decoder,\r\n this._methodRegistry\r\n );\r\n }\r\n return splitBinaryFrameFn(frame, this._decoder);\r\n }\r\n\r\n // Containment matters here: .NET batches many RPC messages per WebSocket frame,\r\n // so letting one bad message escape would drop every other message in the frame -\r\n // including $sys.Ok replies, whose per-call timeouts are unbounded by default.\r\n private _dispatchBinary(message: RpcMessage, args: unknown[]): void {\r\n this.messageReceived.triggerSafe(\r\n { kind: 'binary', message, args },\r\n e => warnLog?.log('Failed to handle a binary message:', e));\r\n }\r\n\r\n close(code?: number, reason?: string): void {\r\n this._ws.close(code, reason);\r\n }\r\n\r\n private _sendRaw(data: string | Uint8Array): void {\r\n try {\r\n if (this._ws.readyState === WebSocketState.OPEN) {\r\n this._ws.send(data);\r\n } else if (this._ws.readyState === WebSocketState.CONNECTING)\r\n this._sendBuffer.push(data);\r\n // CLOSING/CLOSED: silently drop\r\n } catch {\r\n // Swallow — disconnect event handles cleanup\r\n }\r\n }\r\n\r\n private _flush(): void {\r\n if (this._sendBuffer.length === 0) return;\r\n const buffer = this._sendBuffer;\r\n this._sendBuffer = [];\r\n try {\r\n for (const item of buffer) this._ws.send(item);\r\n } catch {\r\n // Swallow — disconnect event handles cleanup\r\n }\r\n }\r\n}\r\n","// .NET counterpart: none — this is a TS-only test transport.\r\n//\r\n// .NET tests use a different mechanism: RpcTestClient creates an in-memory\r\n// Channel<RpcMessage> pair wrapped in an RpcTransport. We use the browser-\r\n// standard MessageChannel API instead, which gives us a synchronous in-process\r\n// transport without requiring WebSocket mocks.\r\n\r\nimport { EventHandlerSet } from '@actuallab/core';\r\nimport { getLogs } from './logging.js';\r\nimport { splitFrame } from './rpc-serialization.js';\r\nimport type { RpcConnection, RpcReceivedMessage } from './rpc-connection.js';\r\n\r\nconst { warnLog } = getLogs('RpcMessageChannelConnection');\r\n\r\n/** MessagePort-based RpcConnection — for in-process testing without WebSocket mocks. */\r\nexport class RpcMessageChannelConnection implements RpcConnection {\r\n private _port: MessagePort;\r\n private _open = true;\r\n\r\n readonly binaryMode = false;\r\n readonly messageReceived = new EventHandlerSet<RpcReceivedMessage>();\r\n readonly closed = new EventHandlerSet<{ code: number; reason: string }>();\r\n readonly whenConnected: Promise<void> = Promise.resolve(); // immediately connected\r\n\r\n constructor(port: MessagePort) {\r\n this._port = port;\r\n port.onmessage = (ev: MessageEvent) => {\r\n const data =\r\n typeof ev.data === 'string' ? ev.data : String(ev.data);\r\n // Isolated per message: one bad message must not drop its frame-mates,\r\n // and an escaping error would be an uncaught exception under Node.\r\n for (const msg of splitFrame(data))\r\n if (msg.length > 0)\r\n this.messageReceived.triggerSafe(\r\n { kind: 'text', raw: msg },\r\n e => warnLog?.log('Failed to handle a text message:', e));\r\n };\r\n }\r\n\r\n get isOpen(): boolean {\r\n return this._open;\r\n }\r\n\r\n send(serializedMessage: string): void {\r\n if (!this._open) return;\r\n try {\r\n this._port.postMessage(serializedMessage);\r\n } catch {\r\n // never fail\r\n }\r\n }\r\n\r\n sendBinary(data: Uint8Array): void {\r\n if (!this._open) return;\r\n try {\r\n this._port.postMessage(data, [data.buffer]);\r\n } catch {\r\n // never fail\r\n }\r\n }\r\n\r\n close(code?: number, reason?: string): void {\r\n if (!this._open) return;\r\n this._open = false;\r\n this._port.close();\r\n this.closed.trigger({ code: code ?? 1000, reason: reason ?? '' });\r\n }\r\n}\r\n\r\n/** Creates a pair of connected RpcMessageChannelConnections for testing. */\r\nexport function createMessageChannelPair(): [\r\n RpcMessageChannelConnection,\r\n RpcMessageChannelConnection,\r\n ] {\r\n const channel = new MessageChannel();\r\n return [\r\n new RpcMessageChannelConnection(channel.port1),\r\n new RpcMessageChannelConnection(channel.port2),\r\n ];\r\n}\r\n","// .NET counterparts:\r\n// RpcServiceDef (Configuration/) — 162 lines. Describes a registered service\r\n// including its .NET Type, Mode (Local/Client/Server/Hybrid), ServerResolver\r\n// (DI-based lazy resolution), ClientType, IsSystem, IsBackend flags, Scope,\r\n// LegacyNames, PropertyBag, and a method dictionary built via reflection.\r\n// RpcMethodDef (Configuration/) — 189 lines. Describes a single method:\r\n// FullName (ServiceName.MethodName:ArgCount), CallType (Regular/Reliable),\r\n// NoWait, HasPolymorphicArguments/Result, RpcMethodKind (Query/Command/System),\r\n// RpcMethodAttribute, LegacyNames, Tracer, outbound/inbound call pipeline\r\n// (factories, invokers, middleware chains, routers, timeouts).\r\n//\r\n// Omitted from .NET:\r\n// - Reflection-based method discovery (BuildMethods) — .NET scans interface\r\n// methods via reflection, filters generics, builds call pipelines. TS uses\r\n// explicit defineRpcService() or decorator metadata — no runtime reflection.\r\n// - RpcMethodKind (Query/Command/System/Other) — .NET classifies methods to\r\n// route Commands through ICommander. TS has no command bus; the distinction\r\n// is unnecessary.\r\n// - IsBackend / IBackendService — .NET separates \"backend-only\" services that\r\n// require backend auth. TS client never exposes backend services.\r\n// - RpcMethodAttribute / LegacyNames — .NET supports custom wire names and\r\n// version-specific legacy name mappings for rolling upgrades. TS has a\r\n// single version; wire names = \"ServiceName.MethodName:wireArgCount\".\r\n// - Call pipeline (OutboundCallFactory, InboundCallFactory, InboundCallInvoker,\r\n// MiddlewareFilter, OutboundCallRouter, OutboundCallTimeouts) — .NET builds a\r\n// per-method middleware chain at startup via generic factory types. TS dispatches\r\n// directly from RpcServiceHost; no middleware or routing layer.\r\n// - CallType (Regular/Reliable) — Reliable calls track stages for reconnect\r\n// resumption. TS replays entire calls on reconnect.\r\n// - HasPolymorphicArguments/Result — governs serializer behavior for abstract\r\n// argument types. TS uses JSON (inherently polymorphic); not applicable.\r\n// - Tracer / RpcCallTracer — per-method distributed tracing. Not ported.\r\n// - PropertyBag — extensible metadata on service/method def. Not needed.\r\n// - Mode (ServiceMode) / Scope / ServerResolver / ClientType — DI wiring.\r\n\r\nimport type { RpcCallTimeouts } from './rpc-call-timeouts.js';\r\n\r\n/** Controls outbound RPC call behavior regarding connection waiting, reconnection, and resending. */\r\nexport const RpcRemoteExecutionMode = {\r\n /** Wait for connection if disconnected when the call is made. */\r\n AwaitForConnection: 1,\r\n /** Resend on reconnection to the same peer. */\r\n AllowReconnect: 2,\r\n /** Resend on reconnection to a different peer (implies AllowReconnect). */\r\n AllowResend: 6, // 4 | AllowReconnect\r\n /** Default: wait + reconnect + resend. */\r\n Default: 7, // AwaitForConnection | AllowResend\r\n} as const;\r\n/** Bitfield combination of RpcRemoteExecutionMode flags. */\r\nexport type RpcRemoteExecutionMode = number;\r\n\r\n/** Describes a single method on an RPC service. */\r\nexport interface RpcMethodDef {\r\n readonly name: string;\r\n readonly serviceName: string;\r\n readonly argCount: number;\r\n readonly wireArgCount: number;\r\n readonly callTypeId: number;\r\n readonly stream: boolean;\r\n readonly noWait: boolean;\r\n readonly remoteExecutionMode: RpcRemoteExecutionMode;\r\n /** Per-method connect/run timeouts (R12). `undefined` means unbounded. */\r\n readonly timeouts?: RpcCallTimeouts;\r\n}\r\n\r\n/** Describes an RPC service — its name and method definitions. */\r\nexport interface RpcServiceDef {\r\n readonly name: string;\r\n readonly methods: ReadonlyMap<string, RpcMethodDef>;\r\n}\r\n\r\nexport interface RpcMethodDefInput {\r\n args: unknown[];\r\n returns?: symbol;\r\n callTypeId?: number;\r\n /** Override for wireArgCount. Default: args.length + 1 (assumes a CancellationToken slot). */\r\n wireArgCount?: number;\r\n /** Override for remoteExecutionMode. Default: RpcRemoteExecutionMode.Default (7). NoWait methods always use 0. */\r\n remoteExecutionMode?: RpcRemoteExecutionMode;\r\n /** Per-method connect/run timeouts (R12). Default: unbounded. */\r\n timeouts?: RpcCallTimeouts;\r\n}\r\n\r\nexport const RpcType = {\r\n object: Symbol('object'),\r\n stream: Symbol('stream'),\r\n noWait: Symbol('noWait'),\r\n void: Symbol('void'),\r\n} as const;\r\n\r\n/** Returns the full wire method name: \"ServiceName.MethodName:wireArgCount\". */\r\nexport function wireMethodName(def: RpcMethodDef): string {\r\n return `${def.serviceName}.${def.name}:${def.wireArgCount}`;\r\n}\r\n\r\nexport function defineRpcService(\r\n name: string,\r\n methods: Record<string, RpcMethodDefInput>\r\n): RpcServiceDef {\r\n const map = new Map<string, RpcMethodDef>();\r\n for (const [key, input] of Object.entries(methods)) {\r\n // If the key contains \":\", the part before \":\" is the clean name\r\n // (the suffix is for JS object key disambiguation of overloads).\r\n // Otherwise the key IS the clean name.\r\n const colonIndex = key.indexOf(':');\r\n const cleanName = colonIndex >= 0 ? key.substring(0, colonIndex) : key;\r\n const wireArgCount = input.wireArgCount ?? input.args.length + 1;\r\n const mapKey = `${cleanName}:${wireArgCount}`;\r\n const isNoWait = input.returns === RpcType.noWait;\r\n map.set(mapKey, {\r\n name: cleanName,\r\n serviceName: name,\r\n argCount: input.args.length,\r\n wireArgCount,\r\n callTypeId: input.callTypeId ?? 0,\r\n stream: input.returns === RpcType.stream,\r\n noWait: isNoWait,\r\n remoteExecutionMode: isNoWait\r\n ? 0\r\n : (input.remoteExecutionMode ?? RpcRemoteExecutionMode.Default),\r\n timeouts: input.timeouts,\r\n });\r\n }\r\n return { name, methods: map };\r\n}\r\n","// .NET counterparts:\r\n// RpcCall (29 lines) — base class: MethodDef, Id, NoWait flag, Lock.\r\n// RpcOutboundCall (437 lines) — tracks an outbound call's full lifecycle:\r\n// result via AsyncTaskMethodBuilder, CacheInfoCapture, CancellationHandler,\r\n// CompletedStage (for Reliable reconnection), StartedAt (for timeouts),\r\n// hashing for Match, tracing, routing, and retry logic.\r\n// RpcInboundCall (298 lines) — tracks an inbound call: deserialization,\r\n// middleware-chain invocation, cancellation via linked CTS, stage-based\r\n// re-processing for reconnection, sending the result back.\r\n// RpcOutboundCallTracker — thread-safe ConcurrentDictionary of outbound calls with:\r\n// HandleDisconnect() (per-outage watcher owning ConnectTimeout), Maintain() loop\r\n// (RunTimeout/DelayTimeout + logging), Reconnect() protocol (stage-based call\r\n// resumption), Abort(), TryReroute().\r\n// RpcInboundCallTracker (64 lines) — ConcurrentDictionary, GetOrRegister,\r\n// Unregister, Clear.\r\n//\r\n// Omitted from .NET:\r\n// - AsyncTaskMethodBuilder / typed RpcOutboundCall<T> — .NET creates a typed\r\n// task builder for each call's return type. TS uses PromiseSource<unknown>\r\n// (untyped) because TypeScript erases generics at runtime.\r\n// - CancellationHandler (CancellationToken.Register → Cancel) — .NET registers\r\n// a callback per outbound call. TS implements equivalent via optional\r\n// AbortSignal on RpcPeer.call(); the cancel handler rejects the promise,\r\n// removes from tracker, and sends $sys.Cancel — same behavior as .NET's\r\n// Cancel() + NotifyCancelled().\r\n// (Timeout monitoring IS ported — see RpcPeer._maintainOutboundCalls. .NET splits it\r\n// between RpcOutboundCallTracker.HandleDisconnect, a per-outage watcher that owns\r\n// ConnectTimeout, and Maintain, which owns RunTimeout while connected; TS runs both\r\n// rules from one timer.)\r\n// - CompletedStage / RpcCallStage — supports Reliable call type's stage-based\r\n// reconnection protocol where the server tells the client which stage each\r\n// call reached, avoiding re-execution of completed stages. TS replays entire\r\n// calls on reconnect (simpler, adequate for browser client).\r\n// - Reconnect() protocol — outbound tracker sends $sys.Reconnect with compressed\r\n// call IDs grouped by stage; server responds with \"unknown\" IDs that need\r\n// re-sending. Not ported; TS uses full replay.\r\n// - Abort()'s final-error latch — .NET latches the error before its (single) sweep, so a\r\n// call registered afterwards is completed by Register rather than left pending. TS does a\r\n// single-pass rejectAll() without the latch; the exposure is much smaller, since close()\r\n// drops the peer from the hub map synchronously instead of keeping it for PeerRemoveDelay.\r\n// - TryReroute() — checks if the call's peer route has changed (load-balancer\r\n// rerouting) and sets RpcRerouteException. TS has no routing layer.\r\n// - IsLongLiving / _longLivingCalls — .NET sets this on compute calls only; it governs\r\n// whether completing a call releases its cancellation handler and trace\r\n// (CompleteAndUnregister vs CompleteKeepRegistered), not whether timeouts apply -\r\n// HandleDisconnect and Maintain both iterate every call. TS has no equivalent.\r\n// - RpcInboundCall: middleware-chain invocation, deserialization with polymorphic\r\n// argument handling, stage-based TryReprocess, CancellationTokenSource per\r\n// call. TS inbound calls are dispatched directly by RpcServiceHost; no\r\n// middleware, no per-call CTS, no stage tracking.\r\n// - RpcCall.Lock (monitor-based synchronisation) — .NET uses lock(this) for\r\n// thread-safe result setting. TS is single-threaded; no locking needed.\r\n\r\nimport { PromiseSource } from '@actuallab/core';\r\nimport { RpcCallStage } from './rpc-call-stage.js';\r\nimport { RpcRemoteExecutionMode } from './rpc-service-def.js';\r\nimport type { RpcCallTimeouts } from './rpc-call-timeouts.js';\r\n\r\n/** Tracks a pending outbound RPC call. */\r\nexport class RpcOutboundCall {\r\n readonly callId: number;\r\n readonly method: string;\r\n readonly result = new PromiseSource<unknown>();\r\n /** Bitfield of RpcRemoteExecutionMode flags controlling reconnect/resend behavior. */\r\n readonly remoteExecutionMode: number;\r\n\r\n /** Whether to remove this call from the tracker on $sys.Ok. Default: true.\r\n * Subclasses (e.g. compute calls) override to false to stay in tracker for invalidation. */\r\n readonly removeOnOk: boolean = true;\r\n\r\n /** The serialized wire data — stored for re-sending on reconnect. */\r\n serializedWireData: string | Uint8Array = '';\r\n\r\n /** Per-call connect/run timeouts enforced by the peer's maintenance loop\r\n * (R12). `undefined` means unbounded (the query default). */\r\n timeouts: RpcCallTimeouts | undefined;\r\n\r\n /** `Date.now()` when this call was created (used for the connect timeout). */\r\n readonly startedAt = Date.now();\r\n\r\n /** `Date.now()` when this call was last put on the wire, or 0 if it hasn't\r\n * been sent yet (used for the run timeout). */\r\n sentAt = 0;\r\n\r\n /**\r\n * Bitfield of {@link RpcCallStage} flags indicating how far this call\r\n * has progressed. Used by the `$sys.Reconnect` protocol to tell the\r\n * remote peer which calls are still pending its attention.\r\n *\r\n * - 0: brand new, no result yet (the peer is expected to be actively processing).\r\n * - `ResultReady` (1): `$sys.Ok` received for this call.\r\n * - `Invalidated` (3): result was invalidated (compute calls only).\r\n * - `Unregistered` (0x1000): removed from the tracker.\r\n */\r\n completedStage = 0;\r\n\r\n constructor(callId: number, method: string, remoteExecutionMode = 7) {\r\n this.callId = callId;\r\n this.method = method;\r\n this.remoteExecutionMode = remoteExecutionMode;\r\n }\r\n\r\n /**\r\n * Returns the stage to report to the remote peer via `$sys.Reconnect`,\r\n * or `null` if this call should not be reconciled and must be aborted.\r\n *\r\n * Mirrors .NET `RpcOutboundCall.GetReconnectStage` at\r\n * src/ActualLab.Rpc/Infrastructure/RpcOutboundCall.cs.\r\n */\r\n getReconnectStage(isPeerChanged: boolean): number | null {\r\n const mode = this.remoteExecutionMode;\r\n if (!(mode & RpcRemoteExecutionMode.AllowReconnect)) return null;\r\n if (isPeerChanged && !(mode & RpcRemoteExecutionMode.AllowResend)) return null;\r\n return this.completedStage;\r\n }\r\n\r\n /** Called when the connection is lost. Subclasses can override to resolve invalidation promises. */\r\n onDisconnect(): void {\r\n // no-op by default\r\n }\r\n}\r\n\r\n/** Manages outbound calls by their RelatedId. */\r\nexport class RpcOutboundCallTracker {\r\n private _calls = new Map<number, RpcOutboundCall>();\r\n private _nextId = 1;\r\n\r\n get size(): number {\r\n return this._calls.size;\r\n }\r\n\r\n nextId(): number {\r\n return this._nextId++;\r\n }\r\n\r\n register(call: RpcOutboundCall): void {\r\n this._calls.set(call.callId, call);\r\n }\r\n\r\n get(callId: number): RpcOutboundCall | undefined {\r\n return this._calls.get(callId);\r\n }\r\n\r\n remove(callId: number): RpcOutboundCall | undefined {\r\n const call = this._calls.get(callId);\r\n if (call !== undefined) {\r\n this._calls.delete(callId);\r\n call.completedStage |= RpcCallStage.Unregistered;\r\n }\r\n return call;\r\n }\r\n\r\n values(): IterableIterator<RpcOutboundCall> {\r\n return this._calls.values();\r\n }\r\n\r\n activeCallIds(): number[] {\r\n return [...this._calls.keys()];\r\n }\r\n\r\n /** Calls handed to the transport at least once. Must be snapshotted BEFORE the\r\n * peer enters `Connected` — that flushes every queued call, making queued and\r\n * in-flight calls indistinguishable. Mirrors .NET's `GetSentCalls`. */\r\n getSentCalls(): RpcOutboundCall[] {\r\n const calls: RpcOutboundCall[] = [];\r\n for (const call of this._calls.values())\r\n if (call.sentAt !== 0)\r\n calls.push(call);\r\n return calls;\r\n }\r\n\r\n /** Reject all pending calls with the given error.\r\n * Stage-3 compute calls (result resolved, awaiting invalidation) are kept in the tracker. */\r\n rejectAll(error: Error): void {\r\n for (const [id, call] of this._calls) {\r\n if (!call.removeOnOk && call.result.isCompleted) {\r\n // Stage-3 compute call — keep it for later invalidation on reconnect/stop\r\n continue;\r\n }\r\n call.result.reject(error);\r\n call.onDisconnect();\r\n this._calls.delete(id);\r\n }\r\n }\r\n\r\n /** Invalidate all remaining stage-3 calls (on reconnect or peer stop). */\r\n invalidateAll(): void {\r\n for (const call of this._calls.values()) {\r\n call.onDisconnect();\r\n }\r\n this._calls.clear();\r\n }\r\n\r\n clear(): void {\r\n this._calls.clear();\r\n }\r\n}\r\n\r\n/** Tracks an incoming inbound RPC call. */\r\nexport class RpcInboundCall {\r\n readonly callId: number;\r\n readonly method: string;\r\n readonly args: unknown[];\r\n /** 0 = still processing, 1 = result computed and sent at least once. */\r\n completedStage = 0;\r\n private _resend: (() => void) | undefined;\r\n private readonly _abortController = new AbortController();\r\n\r\n constructor(callId: number, method: string, args: unknown[]) {\r\n this.callId = callId;\r\n this.method = method;\r\n this.args = args;\r\n }\r\n\r\n /** Aborted when the remote peer cancels this call via `$sys.Cancel` (R17).\r\n * Threaded into the service handler so it can abort in-flight work. */\r\n get signal(): AbortSignal {\r\n return this._abortController.signal;\r\n }\r\n\r\n /** True once the remote peer cancelled this call — the response is then\r\n * suppressed even though the call may still be registered (R9 dedup). */\r\n get isCancelled(): boolean {\r\n return this._abortController.signal.aborted;\r\n }\r\n\r\n cancel(): void {\r\n this._abortController.abort();\r\n }\r\n\r\n // Records the closure that re-sends this call's already-computed result;\r\n // marks the call as completed. Mirrors .NET's post-`ProcessStage1Plus` state.\r\n setResult(resend: () => void): void {\r\n this._resend = resend;\r\n this.completedStage = 1;\r\n }\r\n\r\n // Re-sends the result to the remote peer if it was already computed.\r\n // A resent frame for an in-flight call is a no-op — the original dispatch\r\n // will send the result when it completes. Mirrors .NET\r\n // `RpcInboundCall.TryReprocess`.\r\n resendResult(): void {\r\n if (this.completedStage >= 1)\r\n this._resend?.();\r\n }\r\n}\r\n\r\n/** Manages inbound calls by their RelatedId. */\r\nexport class RpcInboundCallTracker {\r\n private _calls = new Map<number, RpcInboundCall>();\r\n private _completedIds: number[] = [];\r\n\r\n /** Cap on retained completed calls — see `RpcLimits.completedInboundCallsLimit`. */\r\n completedCallsLimit = 1000;\r\n\r\n get size(): number {\r\n return this._calls.size;\r\n }\r\n\r\n register(call: RpcInboundCall): void {\r\n this._calls.set(call.callId, call);\r\n }\r\n\r\n // Registers `call` unless its id is already tracked, in which case the\r\n // existing call is returned. Mirrors .NET `RpcInboundCallTracker.GetOrRegister`.\r\n getOrRegister(call: RpcInboundCall): RpcInboundCall {\r\n const existing = this._calls.get(call.callId);\r\n if (existing !== undefined)\r\n return existing;\r\n\r\n this._calls.set(call.callId, call);\r\n return call;\r\n }\r\n\r\n // Keeps the completed `call` registered for duplicate-frame dedup, evicting\r\n // the oldest completed calls beyond `completedCallsLimit`. Deviation from\r\n // .NET (which unregisters a call once its result is sent): TS peers\r\n // blind-resend after reconnect, so a completed call must stay resolvable\r\n // to avoid re-executing the handler — but only within a bounded window.\r\n markCompleted(call: RpcInboundCall): void {\r\n if (this._calls.get(call.callId) !== call)\r\n return;\r\n\r\n this._completedIds.push(call.callId);\r\n while (this._completedIds.length > this.completedCallsLimit) {\r\n const id = this._completedIds.shift()!;\r\n this._calls.delete(id);\r\n }\r\n }\r\n\r\n get(callId: number): RpcInboundCall | undefined {\r\n return this._calls.get(callId);\r\n }\r\n\r\n remove(callId: number): RpcInboundCall | undefined {\r\n const call = this._calls.get(callId);\r\n if (call !== undefined) this._calls.delete(callId);\r\n return call;\r\n }\r\n\r\n clear(): void {\r\n this._calls.clear();\r\n this._completedIds.length = 0;\r\n }\r\n}\r\n","/**\n * Well-known RPC call completion stage constants, used by the\n * `$sys.Reconnect` protocol to tell the peer which calls have already\n * reached which stage and therefore don't need to be re-processed.\n *\n * Direct port of .NET `ActualLab.Rpc.Infrastructure.RpcCallStage` at\n * src/ActualLab.Rpc/Infrastructure/RpcCallStage.cs.\n */\nexport const RpcCallStage = {\n /** The call's result has been received from the peer. */\n ResultReady: 1,\n /** The compute call's result has been invalidated (compute-call specific). */\n Invalidated: 3,\n /**\n * Bit flag added on top of ResultReady/Invalidated when the outbound\n * call has been removed from the tracker (unregistered).\n */\n Unregistered: 0x1_000,\n} as const;\n\nexport type RpcCallStage = number;\n","// .NET counterpart: ActualLab.Rpc.RpcCallTimeouts (+ RpcCallTimeouts.Default).\r\n// TS omits the query/command classification .NET derives from method metadata\r\n// (the port has no RpcMethodKind), so timeouts are opt-in per call or per method\r\n// def; the default is unbounded (the .NET query default).\r\n\r\n/**\r\n * Connect/run timeouts for an outbound RPC call, enforced by the peer's\r\n * maintenance loop. `Infinity` means \"no timeout\".\r\n */\r\nexport class RpcCallTimeouts {\r\n /** Both timeouts unbounded — the query default (.NET `RpcCallTimeouts.None`). */\r\n static readonly None = new RpcCallTimeouts(Infinity, Infinity);\r\n /** Command default: 1.5 s connect / 10 s run (.NET `RpcCallTimeouts.Default.Command`). */\r\n static readonly Command = new RpcCallTimeouts(1_500, 10_000);\r\n\r\n /** Max time a call may wait for a connection - before it is sent, and again for the\r\n * duration of any outage it is still pending through. */\r\n readonly connectTimeoutMs: number;\r\n /** Max time a sent call may stay unanswered before it is failed. */\r\n readonly runTimeoutMs: number;\r\n\r\n constructor(connectTimeoutMs = Infinity, runTimeoutMs = Infinity) {\r\n this.connectTimeoutMs = connectTimeoutMs;\r\n this.runTimeoutMs = runTimeoutMs;\r\n }\r\n}\r\n","/** Error thrown for a failed remote RPC call — carries the remote exception's type name when available. */\r\nexport class RpcError extends Error {\r\n readonly typeName?: string;\r\n\r\n constructor(message: string, typeName?: string) {\r\n super(message);\r\n this.name = 'RpcError';\r\n if (typeName !== undefined)\r\n this.typeName = typeName;\r\n }\r\n}\r\n\r\n// Assembly-qualified TypeRef of ActualLab's RemoteException — the type every\r\n// ActualLab peer can reconstruct from a single (string message) ctor. TS uses\r\n// it whenever it reports a JS error to a .NET peer ($sys.Error, stream $sys.End),\r\n// folding the JS error name into the message for provenance (see decision D3).\r\nexport const REMOTE_EXCEPTION_TYPE_REF = 'ActualLab.Serialization.RemoteException, ActualLab.Core';\r\n\r\n/** Wire shape of .NET `ExceptionInfo` for a JS error sent to a .NET peer. */\r\nexport interface RpcExceptionInfo {\r\n TypeRef: string;\r\n Message: string;\r\n}\r\n\r\n/** Reshapes what a remote peer gets to see of a local error. */\r\nexport type RpcErrorFilter = (error: unknown, info: RpcExceptionInfo) => RpcExceptionInfo;\r\n\r\n/** Opt-in {@link RpcErrorFilter} that hides the original message — Node error\r\n * text routinely embeds absolute paths, connection strings and SQL. */\r\nexport const genericErrorFilter: RpcErrorFilter = () => ({\r\n TypeRef: REMOTE_EXCEPTION_TYPE_REF,\r\n Message: 'Error: An error occurred while processing the request.',\r\n});\r\n\r\nexport function toExceptionInfo(error: unknown, filter?: RpcErrorFilter): RpcExceptionInfo {\r\n const name = error instanceof Error ? error.name : 'Error';\r\n const message = error instanceof Error ? error.message : String(error);\r\n const info: RpcExceptionInfo = {\r\n TypeRef: REMOTE_EXCEPTION_TYPE_REF,\r\n Message: `${name}: ${message}`,\r\n };\r\n return filter === undefined ? info : filter(error, info);\r\n}\r\n","/**\n * Compresses and decompresses monotonically increasing sequences of\n * integers using variable-length LEB128 encoding of successive deltas.\n *\n * Wire-compatible with .NET `ActualLab.Rpc.Internal.IncreasingSeqCompressor`\n * (src/ActualLab.Rpc/Internal/IncreasingSeqCompressor.cs) — which\n * underlies the `$sys.Reconnect` protocol.\n *\n * Inputs MUST be sorted non-decreasing. Maximum supported value is\n * 2^53 - 1 (JavaScript `Number` safe-integer limit); call IDs in TS\n * never exceed this because they're generated from a 32-bit counter.\n */\nexport const IncreasingSeqCompressor = {\n /**\n * Serialize a non-decreasing sequence of non-negative integers as\n * LEB128-encoded deltas.\n *\n * Throws `RangeError` if the input is not monotonically non-decreasing,\n * or if a value exceeds `Number.MAX_SAFE_INTEGER`.\n */\n serialize(values: Iterable<number>): Uint8Array {\n // First pass: compute total byte length; second pass: write.\n // (A growable buffer would also work, but values are small here.)\n const deltas: number[] = [];\n let last = 0;\n let totalBytes = 0;\n for (const value of values) {\n if (!Number.isSafeInteger(value) || value < 0)\n throw new RangeError(\n `IncreasingSeqCompressor: value must be a non-negative safe integer (got ${value}).`);\n const delta = value - last;\n if (delta < 0)\n throw new RangeError(\n `IncreasingSeqCompressor: sequence must be non-decreasing (saw ${value} after ${last}).`);\n deltas.push(delta);\n last = value;\n totalBytes += _varUintByteLength(delta);\n }\n\n const out = new Uint8Array(totalBytes);\n let offset = 0;\n for (const delta of deltas) {\n offset = _writeVarUint(out, delta, offset);\n }\n return out;\n },\n\n /**\n * Decode bytes produced by {@link serialize} back into the original\n * sorted sequence of non-negative integers.\n */\n deserialize(data: Uint8Array): number[] {\n const result: number[] = [];\n let last = 0;\n let offset = 0;\n while (offset < data.length) {\n const [delta, next] = _readVarUint(data, offset);\n offset = next;\n last += delta;\n result.push(last);\n }\n return result;\n },\n} as const;\n\n// -- LEB128 (variable-length unsigned) encoding -----------------------------\n// Kept in sync with .NET SpanExt.WriteVarUInt64 / ReadVarUInt64 at\n// src/ActualLab.Core/Collections/SpanExt.ReadWriteVarUInt.cs:22-91.\n// We use `Number` arithmetic up to the 53-bit safe-integer ceiling; values\n// beyond that would need BigInt, which is not required for TS call IDs.\n\nconst LOW_BITS = 0x7f;\nconst HIGH_BIT = 0x80;\n\nfunction _varUintByteLength(value: number): number {\n let n = 0;\n let v = value;\n while (v >= HIGH_BIT) {\n n++;\n v = Math.floor(v / 128);\n }\n return n + 1;\n}\n\nfunction _writeVarUint(out: Uint8Array, value: number, offset: number): number {\n let v = value;\n while (v >= HIGH_BIT) {\n out[offset++] = (HIGH_BIT | (v & LOW_BITS)) & 0xff;\n v = Math.floor(v / 128);\n }\n out[offset++] = v & 0xff;\n return offset;\n}\n\nfunction _readVarUint(data: Uint8Array, offset: number): [number, number] {\n let value = 0;\n let shift = 0;\n // Up to 9 bytes contribute 7 bits each = 63 bits.\n while (shift < 63) {\n if (offset >= data.length)\n throw new RangeError('IncreasingSeqCompressor: truncated VarUInt.');\n const b = data[offset++];\n // Multiply by 2^shift using Math.pow to stay in Number arithmetic.\n // For shift up to 49 (7*7) the intermediate `(b & LOW_BITS) * 2^shift`\n // stays within Number.MAX_SAFE_INTEGER; beyond that the caller is\n // expected to keep call IDs within 2^53.\n value += (b & LOW_BITS) * Math.pow(2, shift);\n if (b <= LOW_BITS) return [value, offset];\n shift += 7;\n }\n // 10th byte — matches .NET's 1-bit allowance.\n if (offset >= data.length)\n throw new RangeError('IncreasingSeqCompressor: truncated VarUInt.');\n const b = data[offset++];\n if (b > 1)\n throw new RangeError('IncreasingSeqCompressor: VarUInt value exceeds 64 bits.');\n value += b * Math.pow(2, 63);\n return [value, offset];\n}\n","/**\r\n * Minimal base64 ↔ Uint8Array helpers used by the `$sys.Reconnect` protocol\r\n * (byte arrays are encoded as base64 strings on the JSON wire to match\r\n * .NET's default `System.Text.Json` serialization of `byte[]`).\r\n */\r\n\r\nexport function base64Encode(bytes: Uint8Array): string {\r\n // btoa works on latin1 strings; chunk to avoid \"Maximum call stack size exceeded\"\r\n // when building the intermediate string for large inputs.\r\n const chunkSize = 0x8000;\r\n let binary = '';\r\n for (let i = 0; i < bytes.length; i += chunkSize) {\r\n binary += String.fromCharCode(\r\n ...(bytes.subarray(i, Math.min(bytes.length, i + chunkSize)) as unknown as number[]),\r\n );\r\n }\r\n return btoa(binary);\r\n}\r\n\r\n/** RFC 4648 §5 base64url, unpadded — matches .NET's `Base64UrlEncoder.Encode`. */\r\nexport function base64UrlEncode(bytes: Uint8Array): string {\r\n return base64Encode(bytes)\r\n .replace(/\\+/g, '-')\r\n .replace(/\\//g, '_')\r\n .replace(/=+$/, '');\r\n}\r\n\r\nexport function base64Decode(encoded: string): Uint8Array {\r\n const binary = atob(encoded);\r\n const out = new Uint8Array(binary.length);\r\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\r\n return out;\r\n}\r\n","// .NET counterpart:\r\n// RpcSystemCalls (239 lines) — implements IRpcSystemCalls interface; handles\r\n// all inbound system messages as regular RPC method calls dispatched through\r\n// the same inbound-call pipeline as user calls.\r\n//\r\n// Omitted from .NET:\r\n// - Reconnect() handler — receives compressed call-ID sets grouped by stage,\r\n// looks up each inbound call, calls TryReprocess(), and returns the IDs of\r\n// unknown calls. TS doesn't implement the Reliable reconnection protocol.\r\n// - Cancel() handler — .NET finds the inbound call and cancels its\r\n// CancellationTokenSource, aborting the running handler. TS removes the\r\n// inbound call from the tracker but does not yet propagate cancellation\r\n// to the service handler (no AbortSignal threading through dispatch).\r\n// - M() (Match) handler — tells an outbound call to use its cached result\r\n// instead of the response payload. TS has no response caching.\r\n// - NotFound() — throws an EndpointNotFound error. TS sends this as a regular\r\n// $sys.Error; no separate system call type.\r\n// - KeepAlive() / Disconnect() — manage RpcSharedObject lifetimes. TS has no\r\n// shared-object tracker.\r\n// - IRpcPolymorphicArgumentHandler.IsValidCall — resolves the concrete\r\n// deserialization type for polymorphic Ok/Item/Batch arguments by looking up\r\n// the related outbound call's return type. TS deserializes all args as\r\n// unknown via JSON.parse (inherently polymorphic).\r\n// - DI / IServiceProvider / RpcServiceBase — .NET system calls are resolved via\r\n// DI. TS uses a class with a handle() method.\r\n\r\nimport { getLogs } from './logging.js';\r\nimport { RpcError } from './rpc-error.js';\r\nimport { RpcSystemCalls, type RpcMessage } from './rpc-message.js';\r\nimport type { RpcPeer } from './rpc-peer.js';\r\nimport type { RpcStream } from './rpc-stream.js';\r\nimport type { RpcStreamSender } from './rpc-stream-sender.js';\r\nimport { RpcCallStage } from './rpc-call-stage.js';\r\nimport { IncreasingSeqCompressor } from './increasing-seq-compressor.js';\r\nimport { base64Decode, base64Encode } from './base64.js';\r\n\r\nconst { debugLog, warnLog } = getLogs('RpcSystemCallHandler');\r\n\r\n/** Handles incoming system call messages — class-based equivalent of the former standalone function. */\r\nexport class RpcSystemCallHandler {\r\n handle(message: RpcMessage, args: unknown[], peer: RpcPeer): void {\r\n const method = message.Method;\r\n const relatedId = message.RelatedId ?? 0;\r\n debugLog?.log(`'${peer.ref}': handle ${method}#${relatedId}`);\r\n\r\n switch (method) {\r\n case RpcSystemCalls.ok: {\r\n const call = peer.outboundCalls.get(relatedId);\r\n if (call !== undefined) {\r\n if (args.length > 1) {\r\n peer.outboundCalls.remove(relatedId);\r\n call.result.reject(polymorphicPayloadError(method, args.length, 1));\r\n break;\r\n }\r\n call.completedStage |= RpcCallStage.ResultReady;\r\n if (call.removeOnOk) {\r\n peer.outboundCalls.remove(relatedId);\r\n }\r\n call.result.resolve(args[0]);\r\n }\r\n break;\r\n }\r\n case RpcSystemCalls.reconnect: {\r\n // Server-side `$sys.Reconnect` handler. Computes the set of call\r\n // IDs the inbound tracker doesn't know about, replies with a\r\n // byte[] result via $sys.Ok. Mirrors .NET `RpcSystemCalls.Reconnect`\r\n // at src/ActualLab.Rpc/Infrastructure/RpcSystemCalls.cs:51-85.\r\n //\r\n // Validates `handshakeIndex` against this peer's own handshake\r\n // (stale generation → TooLateToReconnect error). Does not perform\r\n // per-stage re-processing of compute calls (TS has no compute-\r\n // invalidation tracking on the inbound side); a known call id is\r\n // re-sent its result if already computed, else reported \"unknown\".\r\n this._handleReconnect(relatedId, args, peer);\r\n break;\r\n }\r\n case RpcSystemCalls.error: {\r\n const call = peer.outboundCalls.get(relatedId);\r\n if (call !== undefined) {\r\n // Honor removeOnOk like the Ok case: an errored compute call\r\n // stays tracked so its error computed still observes\r\n // $sys-c.Invalidate — C# RpcOutboundComputeCall.SetError\r\n // (CompleteKeepRegistered). Local invalidation or reconnect\r\n // cleans it up.\r\n call.completedStage |= RpcCallStage.ResultReady;\r\n if (call.removeOnOk) {\r\n peer.outboundCalls.remove(relatedId);\r\n }\r\n const errorInfo = args[0] as\r\n | Record<string, unknown>\r\n | undefined;\r\n const msg = (errorInfo?.Message ??\r\n errorInfo?.message ??\r\n 'RPC error') as string;\r\n const typeName = extractTypeName(errorInfo?.TypeRef ?? errorInfo?.typeRef);\r\n // Mirrors RpcSystemCalls.cs:102 — surface RpcRerouteException.\r\n if (typeName === 'ActualLab.Rpc.RpcRerouteException')\r\n warnLog?.log('Got RpcRerouteException from remote peer:', msg);\r\n call.result.reject(new RpcError(msg, typeName));\r\n }\r\n break;\r\n }\r\n case RpcSystemCalls.cancel: {\r\n // Remote peer is cancelling a call it asked us to process. Abort the\r\n // call's signal (so the running handler can bail) and unregister it;\r\n // the dispatch's result send is gated on `isCancelled`, so no\r\n // response goes out. Mirrors RpcInboundCall.Cancel + the\r\n // \"!CallCancelToken.IsCancellationRequested\" guard on SendResult\r\n // (RpcInboundCall.cs:209-210, 246-247).\r\n const call = peer.inboundCalls.remove(relatedId);\r\n call?.cancel();\r\n break;\r\n }\r\n case RpcSystemCalls.keepAlive: {\r\n // Remote keep-alive — mark the peer as \"alive\" so the peer's\r\n // watchdog doesn't force-close the connection. Mirrors .NET\r\n // `RpcObjectTrackers.KeepAlive` which sets `LastKeepAliveAt`.\r\n peer.notifyKeepAliveReceived();\r\n // The id list advertises the remote peer's remote objects, which\r\n // map to our shared objects. Any id we no longer track is stale on\r\n // its side — reply with $sys.Disconnect so it stops advertising it.\r\n // Mirrors RpcObjectTrackers.KeepAlive (RpcObjectTrackers.cs:300-317).\r\n const keepAliveIds = args[0] as number[] | undefined;\r\n if (Array.isArray(keepAliveIds) && keepAliveIds.length > 0 && peer.connection !== undefined) {\r\n const unknownIds: number[] = [];\r\n for (const id of keepAliveIds)\r\n if (peer.sharedObjects.get(id) === undefined)\r\n unknownIds.push(id);\r\n\r\n if (unknownIds.length > 0)\r\n peer.hub.systemCallSender.disconnect(peer.connection, peer.serializationFormat, unknownIds);\r\n }\r\n break;\r\n }\r\n case RpcSystemCalls.item: {\r\n const stream = peer.remoteObjects.get(relatedId) as\r\n | RpcStream<unknown>\r\n | undefined;\r\n // Silent no-op when no stream — mirrors RpcSystemCalls.cs:164-170.\r\n // This is normal during reconnect: the server may send items for\r\n // streams the client has already disposed.\r\n if (!stream)\r\n debugLog?.log(`$sys.I: no stream for relatedId=${relatedId}`);\r\n else if (args.length > 2)\r\n stream.onEnd(args[0] as number, polymorphicPayloadError(method, args.length, 2));\r\n else\r\n stream.onItem(args[0] as number, args[1]);\r\n break;\r\n }\r\n case RpcSystemCalls.batch: {\r\n const stream = peer.remoteObjects.get(relatedId) as\r\n | RpcStream<unknown>\r\n | undefined;\r\n if (!stream)\r\n debugLog?.log(`$sys.B: no stream for relatedId=${relatedId}`);\r\n else if (args.length > 2)\r\n stream.onEnd(args[0] as number, polymorphicPayloadError(method, args.length, 2));\r\n else if (Array.isArray(args[1]))\r\n stream.onBatch(args[0] as number, args[1] as unknown[]);\r\n break;\r\n }\r\n case RpcSystemCalls.end: {\r\n const stream = peer.remoteObjects.get(relatedId) as\r\n | RpcStream<unknown>\r\n | undefined;\r\n if (!stream) {\r\n debugLog?.log(`$sys.End: no stream for relatedId=${relatedId}`);\r\n } else {\r\n // .NET ExceptionInfo is a struct — even for normal completion it serializes\r\n // as a non-null object with empty fields. C# keys \"is this an error?\" on\r\n // TypeRef presence (ExceptionInfo.IsNone); we mirror that and fall back to a\r\n // non-empty Message. The TypeRef's type name becomes the Error's name.\r\n const errorInfo = args[1] as Record<string, unknown> | null;\r\n const typeName = extractTypeName(errorInfo?.TypeRef ?? errorInfo?.typeRef);\r\n const msg = (errorInfo?.Message ?? errorInfo?.message) as\r\n | string\r\n | undefined;\r\n let error: Error | null = null;\r\n if (typeName || msg) {\r\n error = new Error(msg ?? typeName ?? 'RPC stream error');\r\n if (typeName)\r\n error.name = typeName;\r\n }\r\n stream.onEnd(args[0] as number, error);\r\n }\r\n break;\r\n }\r\n case RpcSystemCalls.ack: {\r\n const sender = peer.sharedObjects.get(relatedId) as\r\n | RpcStreamSender<unknown>\r\n | undefined;\r\n // Unknown shared object — reply with $sys.Disconnect so the remote\r\n // stream fails fast (RpcStreamNotFoundException analog) instead of\r\n // hanging. Mirrors RpcSystemCalls.Ack (RpcSystemCalls.cs:149-159).\r\n if (sender)\r\n sender.onAck(args[0] as number, args[1] as string);\r\n else if (peer.connection !== undefined)\r\n peer.hub.systemCallSender.disconnect(peer.connection, peer.serializationFormat, [relatedId]);\r\n break;\r\n }\r\n case RpcSystemCalls.ackEnd: {\r\n const sender = peer.sharedObjects.get(relatedId) as\r\n | RpcStreamSender<unknown>\r\n | undefined;\r\n if (sender)\r\n sender.onAckEnd(args[0] as string);\r\n else if (peer.connection !== undefined)\r\n peer.hub.systemCallSender.disconnect(peer.connection, peer.serializationFormat, [relatedId]);\r\n break;\r\n }\r\n case RpcSystemCalls.disconnect: {\r\n // Server is telling us the listed remote object IDs have been torn down\r\n // on its side (e.g. shared stream closed). Propagate disconnect() to the\r\n // matching remote objects so any pending consumers (for-await loops) exit\r\n // cleanly instead of hanging.\r\n //\r\n // The ids come from the sender's shared-object namespace, which is our\r\n // *remote* one — they must never be resolved against `peer.sharedObjects`.\r\n // Both id counters start at 1, so such a lookup would routinely abort an\r\n // unrelated outgoing sender. Mirrors RpcSystemCalls.cs:139, which\r\n // likewise touches RemoteObjects only.\r\n const ids = args[0] as number[] | undefined;\r\n if (Array.isArray(ids)) {\r\n for (const id of ids) {\r\n const remoteObj = peer.remoteObjects.get(id);\r\n if (\r\n remoteObj &&\r\n typeof remoteObj.disconnect === 'function'\r\n ) {\r\n remoteObj.disconnect();\r\n }\r\n }\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Server-side handler for the `$sys.Reconnect` call. Parses the peer's\r\n * report of in-flight call IDs (grouped by stage, IncreasingSeqCompressor-\r\n * encoded, base64-wrapped for JSON transport), computes the subset this\r\n * peer does not recognize, and replies with a compressed byte[] of the\r\n * unknown IDs wrapped in `$sys.Ok`.\r\n *\r\n * @param relatedId The outbound call ID the client used for this\r\n * `$sys.Reconnect` invocation — we send `$sys.Ok` back\r\n * with the same `RelatedId`.\r\n */\r\n private _handleReconnect(relatedId: number, args: unknown[], peer: RpcPeer): void {\r\n if (peer.connection === undefined) return;\r\n\r\n // args[0]: handshakeIndex — the server's OWN handshake index as the\r\n // client last saw it. If it no longer matches, this $sys.Reconnect\r\n // targets a stale connection generation; reject it so the caller falls\r\n // back to blind resend (absorbed by the inbound-call dedup). Mirrors\r\n // C# RpcSystemCalls.Reconnect's ownHandshake check (RpcSystemCalls.cs:59-62).\r\n // A non-numeric or absent index is rejected rather than waved through:\r\n // C#'s parameter is a typed `int`, so there is no shape that skips the\r\n // comparison there either.\r\n const handshakeIndex = args[0];\r\n if (typeof handshakeIndex !== 'number' || handshakeIndex !== peer.ownHandshakeIndex) {\r\n const got = typeof handshakeIndex === 'number' ? handshakeIndex : `<${typeof handshakeIndex}>`;\r\n peer.hub.systemCallSender.error(\r\n peer.connection, peer.serializationFormat, relatedId,\r\n new Error(\r\n `TooLateToReconnect: own handshake index ${peer.ownHandshakeIndex} != ${got}`));\r\n return;\r\n }\r\n\r\n // A conforming peer sends this once per connection, so a repeat is a replay -\r\n // reject it rather than paying for the reconciliation again. Mirrors C#\r\n // RpcSystemCalls.Reconnect's TryClaimReconnect gate.\r\n if (!peer.tryClaimReconnect()) {\r\n peer.hub.systemCallSender.error(\r\n peer.connection, peer.serializationFormat, relatedId,\r\n new Error('TooLateToReconnect: this connection was already reconnected'));\r\n return;\r\n }\r\n\r\n // args[1]: completedStagesData — shape depends on wire format.\r\n // JSON: { [stage: string]: base64-string }\r\n // msgpack: Map-like with int keys and Uint8Array values (unsupported in TS).\r\n let unknownIds: number[] = [];\r\n try {\r\n const stagesRaw = args[1];\r\n if (stagesRaw && typeof stagesRaw === 'object' && !Array.isArray(stagesRaw)) {\r\n const unknownSet = new Set<number>();\r\n for (const [stageKey, rawValue] of Object.entries(stagesRaw as Record<string, unknown>)) {\r\n const bytes = _toBytes(rawValue);\r\n if (bytes === null) continue;\r\n const completedStage = parseInt(stageKey, 10) || 0;\r\n const callIds = IncreasingSeqCompressor.deserialize(bytes);\r\n for (const callId of callIds) {\r\n const call = peer.inboundCalls.get(callId);\r\n if (call === undefined)\r\n unknownSet.add(callId);\r\n else if (completedStage < 1)\r\n // Known call the client has no result for — re-send it\r\n // if already computed (an in-flight call sends on\r\n // completion). At stage >= 1 the client already has\r\n // the result, so nothing is re-sent. Mirrors C#\r\n // RpcInboundCall.TryReprocess.\r\n call.resendResult();\r\n }\r\n }\r\n unknownIds = [...unknownSet].sort((a, b) => a - b);\r\n }\r\n } catch {\r\n // If parsing fails, report all as unknown (safest behavior — client\r\n // will resend everything).\r\n // Client handles this via its own try/catch around the reconcile call.\r\n }\r\n\r\n const responseBytes = IncreasingSeqCompressor.serialize(unknownIds);\r\n const responseValue = peer.serializationFormat.isBinary ? responseBytes : base64Encode(responseBytes);\r\n peer.hub.systemCallSender.ok(peer.connection, peer.serializationFormat, relatedId, responseValue);\r\n }\r\n}\r\n\r\n// A polymorphic .NET payload ($sys.Ok/I/B with needsPolymorphism) prefixes the\r\n// value with a type marker whose bytes decode as extra msgpack values — see\r\n// readPolymorphismMarker. Detected by arity: these calls carry a fixed value count.\r\nfunction polymorphicPayloadError(method: string, got: number, expected: number): Error {\r\n return new Error(\r\n `${method}: expected ${expected} value(s), got ${got} — ` +\r\n 'polymorphic payloads are not supported by the TS client');\r\n}\r\n\r\n// Extracts the .NET type name from a serialized TypeRef. TypeRef is serialized\r\n// as its assembly-qualified name string (e.g.\r\n// \"System.InvalidOperationException, System.Private.CoreLib\"); the type name is\r\n// the substring before the first ','. Some binary shapes send it as an object.\r\nfunction extractTypeName(typeRef: unknown): string | undefined {\r\n if (typeof typeRef === 'string') {\r\n if (typeRef.length === 0)\r\n return undefined;\r\n\r\n const commaIdx = typeRef.indexOf(',');\r\n return commaIdx >= 0 ? typeRef.slice(0, commaIdx) : typeRef;\r\n }\r\n if (typeRef !== null && typeof typeRef === 'object') {\r\n const t = typeRef as Record<string, unknown>;\r\n const aqn = (t.AssemblyQualifiedName ?? t.assemblyQualifiedName) as string | undefined;\r\n if (typeof aqn === 'string' && aqn.length > 0) {\r\n const commaIdx = aqn.indexOf(',');\r\n return commaIdx >= 0 ? aqn.slice(0, commaIdx) : aqn;\r\n }\r\n return (t.TypeName ?? t.typeName) as string | undefined;\r\n }\r\n return undefined;\r\n}\r\n\r\n/**\r\n * Coerce a wire value carrying a byte[] into a Uint8Array.\r\n * - JSON format sends byte[] as a base64 string.\r\n * - msgpack format sends byte[] as native `bin` (Uint8Array).\r\n * Returns `null` if the value is neither.\r\n */\r\nfunction _toBytes(value: unknown): Uint8Array | null {\r\n if (value instanceof Uint8Array) return value;\r\n if (typeof value === 'string') {\r\n try { return base64Decode(value); } catch { return null; }\r\n }\r\n return null;\r\n}\r\n","// .NET counterpart:\r\n// RpcSystemCallSender (202 lines) — a DI-registered service that sends system\r\n// calls by creating RpcOutboundContext + calling PrepareCallForSendNoWait +\r\n// serialising via the full outbound message pipeline.\r\n//\r\n// Omitted from .NET:\r\n// - Complete<TResult>() / typed Ok<TResult>() — .NET serialises the Ok result\r\n// using the method's unwrapped return type for proper polymorphic handling.\r\n// TS uses JSON.stringify(result) which handles any type uniformly.\r\n// - Match() — sends $sys.M when the response hash matches a cached entry,\r\n// avoiding re-sending the full payload. TS has no response caching.\r\n// - NotFound() — TS sends \"Service not found\" / \"Method not found\" as $sys.Error.\r\n// - Ack() / AckEnd() / Item<T>() / Batch<T>() / End() — stream control\r\n// messages for RpcStream. TS has no streaming support yet.\r\n// - RpcOutboundContext / PrepareCallForSendNoWait pipeline — .NET creates a full\r\n// outbound context with MethodDef, ArgumentList, etc., then serialises via the\r\n// peer's MessageSerializer. TS directly calls serializeMessage() (JSON) since\r\n// there's only one serialization format.\r\n// - Tracing / CallLogger integration — .NET logs each system call through the\r\n// peer's CallLogger. TS has no tracing infrastructure.\r\n// - StopMode-aware Error() — .NET checks peer.StopMode to decide whether to\r\n// suppress error responses when the peer is shutting down. TS has no stop\r\n// mode concept.\r\n\r\nimport { getLogs } from './logging.js';\r\nimport { toExceptionInfo, type RpcErrorFilter } from './rpc-error.js';\r\nimport { RpcSystemCalls, type RpcMessage } from './rpc-message.js';\r\nimport type { RpcConnection } from './rpc-connection.js';\r\nimport type { RpcSerializationFormat } from './rpc-serialization-format.js';\r\nimport type { RpcMethodRegistry } from './rpc-method-registry.js';\r\n\r\nconst { errorLog } = getLogs('RpcSystemCallSender');\r\n\r\n/**\r\n * Sends system RPC messages — like .NET's RpcSystemCallSender.\r\n *\r\n * Hot-path allocation notes: `item`, `batch`, `ack`, and `end` are called\r\n * once per stream frame (or once per ack window). Naively building a fresh\r\n * `{ Method, RelatedId }` envelope object plus a fresh two-element arg array\r\n * on every call showed up as ~2-4% self time + non-trivial GC pressure in\r\n * the 300-pull video-load-test profile.\r\n *\r\n * The sender itself is a per-Hub singleton and `_send`/format.serializeMessage\r\n * are fully synchronous (no awaits, no re-entrancy on a single-threaded event\r\n * loop), so we reuse a small pool of preallocated envelope + arg-array slots\r\n * per hot method. Mutate fields just before the send call; the encoder reads\r\n * them synchronously and produces a detached copy, after which the slot is\r\n * free to be mutated again on the next call.\r\n */\r\nexport class RpcSystemCallSender {\r\n // Reusable envelopes — one per hot-path method.\r\n private readonly _itemEnv: RpcMessage = {\r\n Method: RpcSystemCalls.item,\r\n RelatedId: 0,\r\n };\r\n private readonly _batchEnv: RpcMessage = {\r\n Method: RpcSystemCalls.batch,\r\n RelatedId: 0,\r\n };\r\n private readonly _endEnv: RpcMessage = {\r\n Method: RpcSystemCalls.end,\r\n RelatedId: 0,\r\n };\r\n private readonly _ackEnv: RpcMessage = {\r\n Method: RpcSystemCalls.ack,\r\n RelatedId: 0,\r\n };\r\n private readonly _ackEndEnv: RpcMessage = {\r\n Method: RpcSystemCalls.ackEnd,\r\n RelatedId: 0,\r\n };\r\n\r\n // Reusable arg arrays.\r\n private readonly _itemArgs: [number, unknown] = [0, null];\r\n private readonly _batchArgs: [number, unknown] = [0, null];\r\n private readonly _endArgs: [number, { TypeRef: string; Message: string }] =\r\n [0, { TypeRef: '', Message: '' }];\r\n private readonly _ackArgs: [number, string] = [0, ''];\r\n private readonly _ackEndArgs: [string] = [''];\r\n\r\n private static readonly NO_ERROR: { TypeRef: string; Message: string } =\r\n Object.freeze({ TypeRef: '', Message: '' }) as unknown as {\r\n TypeRef: string;\r\n Message: string;\r\n };\r\n\r\n registry?: RpcMethodRegistry;\r\n\r\n /** Applied to every error this hub sends to a remote peer. Undefined means\r\n * pass through, i.e. the peer sees the original message — set it (e.g. to\r\n * {@link genericErrorFilter}) on a TS-hosted server, where handler errors\r\n * would otherwise disclose internal details to unauthenticated callers. */\r\n errorFilter?: RpcErrorFilter;\r\n\r\n protected _send(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n envelope: RpcMessage,\r\n args?: unknown[]\r\n ): void {\r\n const wireData = format.serializeMessage(\r\n envelope,\r\n args,\r\n conn.encoder,\r\n this.registry\r\n );\r\n if (typeof wireData === 'string') {\r\n conn.send(wireData);\r\n } else {\r\n conn.sendBinary(wireData);\r\n }\r\n }\r\n\r\n handshake(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n peerId: string,\r\n hubId: string,\r\n index: number\r\n ): void {\r\n // RpcHandshake uses [MessagePackObject] array mode for binary.\r\n const handshakeArg = format.isBinary\r\n ? [peerId, null, hubId, 2, index]\r\n : {\r\n RemotePeerId: peerId,\r\n RemoteApiVersionSet: null,\r\n RemoteHubId: hubId,\r\n ProtocolVersion: 2,\r\n Index: index,\r\n };\r\n this._send(conn, format, { Method: RpcSystemCalls.handshake }, [\r\n handshakeArg,\r\n ]);\r\n }\r\n\r\n ok(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n relatedId: number,\r\n result: unknown\r\n ): void {\r\n try {\r\n this._send(\r\n conn,\r\n format,\r\n { Method: RpcSystemCalls.ok, RelatedId: relatedId },\r\n [result]\r\n );\r\n }\r\n catch (e) {\r\n // Mirrors RpcSystemCallSender.cs:104 — \"Failed to send Ok response\".\r\n errorLog?.log(`Failed to send Ok response for call #${relatedId}`, e);\r\n this.error(conn, format, relatedId, e);\r\n }\r\n }\r\n\r\n error(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n relatedId: number,\r\n error: unknown\r\n ): void {\r\n // A TypeRef-less ExceptionInfo makes .NET's ExceptionInfo.ToException()\r\n // return null → the caller's task hangs. Send RemoteException instead\r\n // (decision D3).\r\n this._send(\r\n conn,\r\n format,\r\n { Method: RpcSystemCalls.error, RelatedId: relatedId },\r\n [toExceptionInfo(error, this.errorFilter)]\r\n );\r\n }\r\n\r\n cancel(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n relatedId: number\r\n ): void {\r\n this._send(conn, format, {\r\n Method: RpcSystemCalls.cancel,\r\n RelatedId: relatedId,\r\n });\r\n }\r\n\r\n keepAlive(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n activeCallIds: number[]\r\n ): void {\r\n this._send(conn, format, { Method: RpcSystemCalls.keepAlive }, [\r\n activeCallIds,\r\n ]);\r\n }\r\n\r\n // Tells the remote peer the listed shared-object ids are gone on our side,\r\n // so its matching remote objects (e.g. RpcStream consumers) fail fast\r\n // instead of hanging. Mirrors .NET RpcSystemCallSender.Disconnect.\r\n disconnect(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n localIds: number[]\r\n ): void {\r\n this._send(conn, format, { Method: RpcSystemCalls.disconnect }, [\r\n localIds,\r\n ]);\r\n }\r\n\r\n item(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n localId: number,\r\n index: number,\r\n item: unknown\r\n ): void {\r\n this._itemEnv.RelatedId = localId;\r\n this._itemArgs[0] = index;\r\n this._itemArgs[1] = item;\r\n this._send(conn, format, this._itemEnv, this._itemArgs);\r\n this._itemArgs[1] = null;\r\n }\r\n\r\n batch(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n localId: number,\r\n index: number,\r\n items: unknown[]\r\n ): void {\r\n this._batchEnv.RelatedId = localId;\r\n this._batchArgs[0] = index;\r\n this._batchArgs[1] = items;\r\n this._send(conn, format, this._batchEnv, this._batchArgs);\r\n this._batchArgs[1] = null;\r\n }\r\n\r\n end(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n localId: number,\r\n index: number,\r\n error: {\r\n TypeRef: string;\r\n Message: string;\r\n } = RpcSystemCallSender.NO_ERROR\r\n ): void {\r\n this._endEnv.RelatedId = localId;\r\n this._endArgs[0] = index;\r\n this._endArgs[1] = error;\r\n this._send(conn, format, this._endEnv, this._endArgs);\r\n }\r\n\r\n ack(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n localId: number,\r\n nextIndex: number,\r\n hostId: string\r\n ): void {\r\n this._ackEnv.RelatedId = localId;\r\n this._ackArgs[0] = nextIndex;\r\n this._ackArgs[1] = hostId;\r\n this._send(conn, format, this._ackEnv, this._ackArgs);\r\n }\r\n\r\n ackEnd(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n localId: number,\r\n hostId: string\r\n ): void {\r\n this._ackEndEnv.RelatedId = localId;\r\n this._ackEndArgs[0] = hostId;\r\n this._send(conn, format, this._ackEndEnv, this._ackEndArgs);\r\n }\r\n}\r\n","export interface RpcObjectId {\r\n readonly hostId: string;\r\n readonly localId: number;\r\n}\r\n\r\nexport const RpcObjectKind = { Local: 0, Remote: 1 } as const;\r\nexport type RpcObjectKind = (typeof RpcObjectKind)[keyof typeof RpcObjectKind];\r\n\r\nexport interface IRpcObject {\r\n readonly id: RpcObjectId;\r\n readonly kind: RpcObjectKind;\r\n readonly allowReconnect: boolean;\r\n reconnect(): void;\r\n disconnect(): void;\r\n}\r\n","import Denque from 'denque';\r\nimport { PromiseSource } from '@actuallab/core';\r\nimport { getLogs } from './logging.js';\r\nimport type { RpcObjectId, IRpcObject } from './rpc-object.js';\r\nimport { RpcObjectKind } from './rpc-object.js';\r\nimport type { RpcPeer } from './rpc-peer.js';\r\nimport { RpcStreamSender } from './rpc-stream-sender.js';\r\n\r\nconst { warnLog } = getLogs('RpcStream');\r\n\r\n/** Default ack period (matches .NET RpcStream defaults). */\r\nconst DEFAULT_ACK_PERIOD = 30;\r\n/** Default ack advance window (matches .NET RpcStream defaults). */\r\nconst DEFAULT_ACK_ADVANCE = 61;\r\n/** Largest $sys.B batch a conforming sender emits (matches .NET RpcStream.MaxBatchSize). */\r\nconst MAX_BATCH_SIZE = 1024;\r\n\r\n/** Parsed stream reference from the server's result payload. */\r\nexport interface RpcStreamRef {\r\n readonly hostId: string;\r\n readonly localId: number;\r\n readonly ackPeriod: number;\r\n readonly ackAdvance: number;\r\n readonly allowReconnect: boolean;\r\n readonly isRealTime: boolean;\r\n}\r\n\r\n/**\r\n * A stream source is either a plain async iterable or a factory function\r\n * that receives an AbortSignal and returns an async iterable.\r\n * The AbortSignal is aborted when the sender is disconnected, allowing\r\n * the source to release resources (e.g., camera, microphone) promptly.\r\n */\r\nexport type RpcStreamSource<T> = AsyncIterable<T> | ((abortSignal: AbortSignal) => AsyncIterable<T>);\r\n\r\n/** Configuration options for creating a local (origin-side) RpcStream. */\r\nexport interface RpcStreamOptions<T> {\r\n ackPeriod?: number;\r\n /**\r\n * Wire-level flow-control window: the maximum index the sender may advance\r\n * past the last acknowledged index. Mirrors .NET `RpcStream.AckAdvance`.\r\n */\r\n ackAdvance?: number;\r\n /**\r\n * Local-only sender ring buffer capacity. When undefined (or smaller than\r\n * `ackAdvance`), `ackAdvance` is used as the buffer size.\r\n * Set this larger than `ackAdvance` to let the sender pre-buffer items\r\n * beyond the in-flight window. Not transmitted on the wire.\r\n */\r\n bufferSize?: number;\r\n allowReconnect?: boolean;\r\n isRealTime?: boolean;\r\n canSkipTo?: (item: T) => boolean;\r\n}\r\n\r\n/**\r\n * Parses a stream reference string of the form \"{hostId},{localId},{ackPeriod},{ackAdvance}\".\r\n * Returns null if the value is not a valid stream reference.\r\n */\r\nexport function parseStreamRef(value: unknown): RpcStreamRef | null {\r\n // Text format: \"hostId,localId,ackPeriod,ackAdvance[,allowReconnect[,isRealTime]]\"\r\n if (typeof value === 'string') {\r\n const parts = value.split(',');\r\n if (parts.length < 4 || parts.length > 6) return null;\r\n const hostId = parts[0];\r\n const localId = parseInt(parts[1], 10);\r\n const ackPeriod = parseInt(parts[2], 10);\r\n const ackAdvance = parseInt(parts[3], 10);\r\n if (isNaN(localId) || isNaN(ackPeriod) || isNaN(ackAdvance))\r\n return null;\r\n const allowReconnect = parts.length < 5 || parts[4] !== '0';\r\n const isRealTime = parts.length >= 6 && parts[5] === '1';\r\n return { hostId, localId, ackPeriod, ackAdvance, allowReconnect, isRealTime };\r\n }\r\n // Binary (MessagePack) format: { SerializedId: [hostId, localId], AckPeriod, AckAdvance, AllowReconnect }\r\n if (typeof value === 'object' && value !== null) {\r\n const obj = value as Record<string, unknown>;\r\n const serializedId = obj.SerializedId as unknown[];\r\n if (!Array.isArray(serializedId) || serializedId.length < 2)\r\n return null;\r\n const rawHostId = serializedId[0];\r\n const hostId = String(rawHostId);\r\n const localId = Number(serializedId[1]);\r\n const ackPeriod = Number(obj.AckPeriod ?? 256);\r\n const ackAdvance = Number(obj.AckAdvance ?? 128);\r\n const allowReconnect = obj.AllowReconnect !== false;\r\n const isRealTime = obj.IsRealTime === true;\r\n return { hostId, localId, ackPeriod, ackAdvance, allowReconnect, isRealTime };\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Dual-mode RPC stream — works as both local (origin-side) and remote (target-side).\r\n *\r\n * **Local mode**: wraps an AsyncIterable and carries streaming configuration\r\n * (IsRealTime, CanSkipTo, etc.). Created by service methods and sent to the\r\n * remote peer via RpcStreamSender.\r\n *\r\n * **Remote mode**: consumes items sent by the server via system calls\r\n * ($sys.I, $sys.B, $sys.End) and implements AsyncIterable for for-await-of consumption.\r\n *\r\n * This mirrors the .NET RpcStream<T> design where the same type serves both roles.\r\n */\r\nexport class RpcStream<T> implements AsyncIterable<T>, IRpcObject {\r\n /**\r\n * Grace period (ms) given to a factory-form local source to honor its\r\n * AbortSignal and exit naturally before the sender force-closes the\r\n * iterator via `iterator.return()`.\r\n *\r\n * Only applies to sources provided as `(abortSignal) => AsyncIterable<T>`.\r\n * Plain `AsyncIterable<T>` sources are force-closed immediately on disconnect.\r\n */\r\n public static disconnectGracePeriodMs = 100;\r\n\r\n readonly id: RpcObjectId;\r\n readonly kind: RpcObjectKind;\r\n readonly allowReconnect: boolean;\r\n readonly isRealTime: boolean;\r\n readonly canSkipTo: (item: T) => boolean;\r\n readonly ackPeriod: number;\r\n readonly ackAdvance: number;\r\n /**\r\n * Local-only ring buffer capacity hint for the sender. Undefined means\r\n * \"use ackAdvance as the buffer size\". Not part of the wire ref.\r\n */\r\n readonly bufferSize?: number;\r\n\r\n // Local (origin) mode\r\n readonly localSource?: RpcStreamSource<T>;\r\n\r\n // Remote (target) mode\r\n readonly peer!: RpcPeer;\r\n\r\n // Local-only state\r\n private _sender?: RpcStreamSender<T>;\r\n private _toRefValue?: unknown;\r\n private _whenSent?: Promise<void>;\r\n\r\n // Remote-only state\r\n private _buffer: Denque<T> = new Denque<T>();\r\n private _nextExpectedIndex = 0;\r\n private _consumerWaiting: PromiseSource<void> | null = null;\r\n private _completed = false;\r\n private _completionError: Error | null = null;\r\n private _disposed = false;\r\n private _started = false;\r\n private _iterating = false;\r\n private _ackSentUpTo = 0;\r\n private _nextConsumedIndex = 0;\r\n\r\n /** Create a local (origin-side) stream wrapping an async iterable or source factory with optional configuration. */\r\n constructor(source: RpcStreamSource<T>, options?: RpcStreamOptions<T>);\r\n /** Create a remote (target-side) stream from a parsed stream reference and peer. */\r\n constructor(ref: RpcStreamRef, peer: RpcPeer);\r\n constructor(\r\n sourceOrRef: RpcStreamSource<T> | RpcStreamRef,\r\n optionsOrPeer?: RpcStreamOptions<T> | RpcPeer,\r\n ) {\r\n if (_isStreamSource(sourceOrRef)) {\r\n // Local (origin) mode\r\n const options = (optionsOrPeer as RpcStreamOptions<T> | undefined) ?? {};\r\n this.kind = RpcObjectKind.Local;\r\n this.localSource = sourceOrRef;\r\n this.id = { hostId: '', localId: 0 };\r\n this.allowReconnect = options.allowReconnect ?? true;\r\n this.isRealTime = options.isRealTime ?? false;\r\n this.canSkipTo = options.canSkipTo ?? (() => true);\r\n this.ackPeriod = options.ackPeriod ?? DEFAULT_ACK_PERIOD;\r\n this.ackAdvance = options.ackAdvance ?? DEFAULT_ACK_ADVANCE;\r\n this.bufferSize = options.bufferSize;\r\n } else {\r\n // Remote (target) mode\r\n const ref = sourceOrRef;\r\n const peer = optionsOrPeer as RpcPeer;\r\n this.kind = RpcObjectKind.Remote;\r\n this.id = { hostId: ref.hostId, localId: ref.localId };\r\n this.peer = peer;\r\n this.allowReconnect = ref.allowReconnect;\r\n this.isRealTime = ref.isRealTime;\r\n this.canSkipTo = () => true; // not serialized\r\n this.ackPeriod = ref.ackPeriod;\r\n this.ackAdvance = ref.ackAdvance;\r\n // bufferSize is local-only; not present on the wire ref\r\n }\r\n }\r\n\r\n /**\r\n * For local streams: resolves when the sender finishes pumping (after `toRef()` was called).\r\n * Rejects if the source throws. Throws if `toRef()` hasn't been called yet.\r\n */\r\n get whenSent(): Promise<void> {\r\n if (!this._whenSent)\r\n throw new Error('toRef() must be called before awaiting whenSent.');\r\n return this._whenSent;\r\n }\r\n\r\n /** Local sender backing this stream after `toRef()` has been called.\r\n * `undefined` for remote streams or before `toRef()`. */\r\n get sender(): RpcStreamSender<T> | undefined {\r\n return this._sender;\r\n }\r\n\r\n /**\r\n * For local streams: bind to a peer, create and register an `RpcStreamSender`,\r\n * start pumping items from the local source in the background, and return the\r\n * stream reference.\r\n *\r\n * For binary peers the ref is a MessagePack-compatible object\r\n * (`{ SerializedId, AckPeriod, AckAdvance, AllowReconnect }`);\r\n * for text peers it is a comma-separated string.\r\n *\r\n * Can only be called once per stream.\r\n */\r\n toRef(peer: RpcPeer): unknown {\r\n if (this.kind !== RpcObjectKind.Local)\r\n throw new Error('toRef() is only valid for local streams.');\r\n if (this._sender) {\r\n if (this._sender.peer !== peer)\r\n throw new Error('toRef() was already called with a different peer.');\r\n // Same peer — return the cached ref\r\n return this._toRefValue!;\r\n }\r\n\r\n const isFactory = typeof this.localSource === 'function';\r\n const sender = new RpcStreamSender<T>(\r\n peer, this.ackPeriod, this.ackAdvance,\r\n this.allowReconnect, this.isRealTime, this.canSkipTo,\r\n isFactory, this.bufferSize,\r\n );\r\n peer.sharedObjects.register(sender);\r\n this._sender = sender;\r\n\r\n // Resolve source: if it's a factory, call it with the sender's AbortSignal\r\n const source = isFactory\r\n ? (this.localSource as (s: AbortSignal) => AsyncIterable<T>)(sender.abortSignal)\r\n : this.localSource as AsyncIterable<T>;\r\n\r\n // Start pumping in background (writeFrom awaits the initial Ack)\r\n this._whenSent = sender.writeFrom(source);\r\n\r\n // Cache and return ref in the format appropriate for the peer's serialization\r\n this._toRefValue = peer.serializationFormat.isBinary\r\n ? {\r\n SerializedId: [sender.id.hostId, sender.id.localId],\r\n AckPeriod: sender.ackPeriod,\r\n AckAdvance: sender.ackAdvance,\r\n AllowReconnect: sender.allowReconnect,\r\n }\r\n : sender.toRef();\r\n return this._toRefValue;\r\n }\r\n\r\n /** Called by system call handler when a single item arrives ($sys.I). */\r\n onItem(index: number, item: T): void {\r\n if (this._disposed || this._completed) return;\r\n\r\n if (!_isValidIndex(index)) {\r\n this._failProtocol(`invalid $sys.I index: ${String(index)}`);\r\n return;\r\n }\r\n if (index > this._nextExpectedIndex) {\r\n warnLog?.log(`item index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);\r\n if (!this.allowReconnect) {\r\n this._complete(this._gapError(index));\r\n return;\r\n }\r\n this._sendAck(this._nextExpectedIndex, true);\r\n return;\r\n }\r\n if (index < this._nextExpectedIndex) {\r\n // Duplicate — ack and ignore\r\n this._maybeSendAck(Math.min(index + 1, this._nextConsumedIndex));\r\n return;\r\n }\r\n if (this._isBeyondAckWindow(index + 1)) {\r\n this._failProtocol(this._ackWindowError(index + 1));\r\n return;\r\n }\r\n\r\n this._buffer.push(item);\r\n this._nextExpectedIndex = index + 1;\r\n this._notifyConsumer();\r\n }\r\n\r\n /** Called by system call handler when a batch arrives ($sys.B). */\r\n onBatch(index: number, items: T[]): void {\r\n if (this._disposed || this._completed) return;\r\n\r\n if (!_isValidIndex(index) || !_isValidIndex(items.length)) {\r\n this._failProtocol(`invalid $sys.B index/length: ${String(index)}/${String(items.length)}`);\r\n return;\r\n }\r\n if (items.length > MAX_BATCH_SIZE) {\r\n this._failProtocol(`$sys.B batch is too large: ${items.length} > ${MAX_BATCH_SIZE}`);\r\n return;\r\n }\r\n if (index > this._nextExpectedIndex) {\r\n warnLog?.log(`batch index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);\r\n if (!this.allowReconnect) {\r\n this._complete(this._gapError(index));\r\n return;\r\n }\r\n this._sendAck(this._nextExpectedIndex, true);\r\n return;\r\n }\r\n if (index < this._nextExpectedIndex) {\r\n this._maybeSendAck(Math.min(index + items.length, this._nextConsumedIndex));\r\n return;\r\n }\r\n if (this._isBeyondAckWindow(index + items.length)) {\r\n this._failProtocol(this._ackWindowError(index + items.length));\r\n return;\r\n }\r\n\r\n for (const item of items) this._buffer.push(item);\r\n this._nextExpectedIndex = index + items.length;\r\n this._notifyConsumer();\r\n }\r\n\r\n /** Called by system call handler when the stream ends ($sys.End). */\r\n onEnd(index: number, error: Error | null): void {\r\n if (this._disposed || this._completed) return;\r\n\r\n if (!_isValidIndex(index)) {\r\n this._failProtocol(`invalid $sys.End index: ${String(index)}`);\r\n return;\r\n }\r\n if (index > this._nextExpectedIndex) {\r\n warnLog?.log(`end index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);\r\n if (!this.allowReconnect) {\r\n this._complete(this._gapError(index));\r\n return;\r\n }\r\n this._sendAck(this._nextExpectedIndex, true);\r\n return;\r\n }\r\n if (index < this._nextExpectedIndex) {\r\n // Stale/duplicate End — ack and ignore\r\n this._maybeSendAck(Math.min(index, this._nextConsumedIndex));\r\n return;\r\n }\r\n\r\n this._complete(error);\r\n }\r\n\r\n // -- IRpcObject --\r\n\r\n reconnect(): void {\r\n if (this.kind === RpcObjectKind.Local) return; // no-op for local streams\r\n if (this.allowReconnect) {\r\n this._sendAck(this._nextExpectedIndex, true);\r\n } else {\r\n this.disconnect();\r\n }\r\n }\r\n\r\n disconnect(): void {\r\n if (this.kind === RpcObjectKind.Local) {\r\n // Propagate to sender — aborts the AbortSignal and terminates the iterator\r\n this._sender?.disconnect();\r\n return;\r\n }\r\n if (this._completed) return;\r\n this._completed = true;\r\n this._completionError = new Error('Peer disconnected.');\r\n this._notifyConsumer();\r\n }\r\n\r\n // -- AsyncIterable --\r\n\r\n [Symbol.asyncIterator](): AsyncIterator<T> {\r\n // Local mode: delegate to the local source\r\n if (this.kind === RpcObjectKind.Local) {\r\n const source = typeof this.localSource === 'function'\r\n ? this.localSource(new AbortController().signal)\r\n : this.localSource!;\r\n return source[Symbol.asyncIterator]();\r\n }\r\n\r\n // Remote mode: buffer-based consumption\r\n if (this._iterating)\r\n throw new Error('RpcStream can only be iterated once.');\r\n this._iterating = true;\r\n\r\n return {\r\n next: async (): Promise<IteratorResult<T>> => {\r\n // Lazy start: register with the peer and send the initial ack on\r\n // the first next() call — a never-enumerated remote stream must\r\n // create no lease on either peer (RpcStream.GetAsyncEnumerator parity).\r\n if (!this._started) {\r\n this._started = true;\r\n this.peer.remoteObjects.register(this);\r\n this._sendAck(0, true);\r\n }\r\n this._maybeSendAck(this._nextConsumedIndex);\r\n\r\n // Read from buffer or wait for new data\r\n for (;;) {\r\n if (!this._buffer.isEmpty()) {\r\n // shift() is O(1) on Denque and releases the slot so the\r\n // ring buffer can be reclaimed as the consumer drains.\r\n const value = this._buffer.shift()!;\r\n this._nextConsumedIndex++;\r\n return { value, done: false };\r\n }\r\n\r\n if (this._completed) {\r\n this.dispose();\r\n if (this._completionError) throw this._completionError;\r\n return { value: undefined as T, done: true };\r\n }\r\n\r\n // Wait for more data\r\n this._consumerWaiting = new PromiseSource<void>();\r\n await this._consumerWaiting;\r\n }\r\n },\r\n\r\n return: (): Promise<IteratorResult<T>> => {\r\n // Before the lazy start nothing was sent or registered, so\r\n // disposal must stay a local no-op (C#: CloseFromLock sends\r\n // nothing when the remote channel was never created).\r\n if (this._started && !this._completed)\r\n this._sendAckEnd();\r\n\r\n this.dispose();\r\n return Promise.resolve({ value: undefined as T, done: true });\r\n },\r\n };\r\n }\r\n\r\n dispose(): void {\r\n if (this._disposed) return;\r\n this._disposed = true;\r\n this._completed = true;\r\n if (this.kind === RpcObjectKind.Remote) {\r\n this.peer.remoteObjects.unregister(this);\r\n }\r\n this._notifyConsumer();\r\n }\r\n\r\n // -- Private --\r\n\r\n // Mirrors .NET RpcStream.CloseFromLock: acknowledge the end to the sender\r\n // (so its RpcSharedStream is released) and unregister immediately.\r\n private _complete(error: Error | null): void {\r\n if (this._completed) return;\r\n this._completed = true;\r\n this._completionError = error;\r\n this._sendAckEnd();\r\n if (this.kind === RpcObjectKind.Remote)\r\n this.peer.remoteObjects.unregister(this);\r\n this._notifyConsumer();\r\n }\r\n\r\n // Terminates the stream the way the non-reconnectable gap path does: the\r\n // consumer sees the error and the sender gets $sys.AckEnd, so nothing is\r\n // left pumping into a stream we no longer read.\r\n private _failProtocol(message: string): void {\r\n warnLog?.log(`localId=${this.id.localId}: ${message}`);\r\n this._complete(new Error(message));\r\n }\r\n\r\n // The remote advertised `ackAdvance` as the furthest it may run ahead of our\r\n // last acknowledged index. The sending side is the only place that window\r\n // was ever enforced, which left a flooding remote free to grow our buffer\r\n // without bound (I6) — so we enforce our half of the contract here.\r\n //\r\n // The window is credited by consumption, never by `_ackSentUpTo`: a reset ack\r\n // re-bases to what we've *received*, so crediting it would let a peer ratchet\r\n // its own limit up by alternating \"fill the window\" with a forced gap.\r\n // Twice ackAdvance is what a conforming sender can leave buffered (ackAdvance\r\n // in steady state, plus ackAdvance more after a reset re-bases it), and\r\n // ackPeriod covers the lag of acks behind consumption.\r\n private _isBeyondAckWindow(nextExpectedIndex: number): boolean {\r\n if (this.ackAdvance <= 0)\r\n return false;\r\n\r\n return nextExpectedIndex > this._nextConsumedIndex + this._ackWindowSize();\r\n }\r\n\r\n private _ackWindowSize(): number {\r\n return 2 * this.ackAdvance + this.ackPeriod;\r\n }\r\n\r\n private _ackWindowError(nextExpectedIndex: number): string {\r\n return `Stream ran past its ack window: reached ${nextExpectedIndex}, `\r\n + `consumed ${this._nextConsumedIndex}, ackAdvance ${this.ackAdvance}`;\r\n }\r\n\r\n private _gapError(index: number): Error {\r\n return new Error(\r\n `Stream gap at index ${index} (expected ${this._nextExpectedIndex}); reconnect not allowed`\r\n );\r\n }\r\n\r\n private _notifyConsumer(): void {\r\n if (this._consumerWaiting !== null) {\r\n this._consumerWaiting.resolve();\r\n this._consumerWaiting = null;\r\n }\r\n }\r\n\r\n private _maybeSendAck(nextIndex: number): void {\r\n if (this.ackPeriod <= 0) return;\r\n const threshold = this._ackSentUpTo + this.ackPeriod;\r\n if (nextIndex >= threshold) {\r\n this._sendAck(nextIndex, false);\r\n }\r\n }\r\n\r\n private static readonly _emptyGuid = '00000000-0000-0000-0000-000000000000';\r\n\r\n private _sendAck(nextIndex: number, mustReset: boolean): void {\r\n this._ackSentUpTo = nextIndex;\r\n // Gated on `isConnected`, not just `connection` — during connect and\r\n // handshake `connection` is already set, and a buffered ack would hit\r\n // the wire before $sys.Handshake, corrupting the remote handshake\r\n // (R5; C#'s null-Transport-until-handshake). A dropped ack is\r\n // recovered by the reset-ack `reconnect()` sends once connected.\r\n const conn = this.peer.isConnected ? this.peer.connection : undefined;\r\n if (conn) {\r\n const hostId = mustReset ? this.id.hostId : RpcStream._emptyGuid;\r\n this.peer.hub.systemCallSender.ack(\r\n conn,\r\n this.peer.serializationFormat,\r\n this.id.localId,\r\n nextIndex,\r\n hostId\r\n );\r\n }\r\n }\r\n\r\n private _sendAckEnd(): void {\r\n // Same pre-handshake gate as `_sendAck`; a dropped AckEnd just means\r\n // the sender terminates via disconnect/keep-alive instead.\r\n const conn = this.peer.isConnected ? this.peer.connection : undefined;\r\n if (conn) {\r\n this.peer.hub.systemCallSender.ackEnd(\r\n conn,\r\n this.peer.serializationFormat,\r\n this.id.localId,\r\n this.id.hostId\r\n );\r\n }\r\n }\r\n}\r\n\r\nfunction _isValidIndex(index: number): boolean {\r\n return Number.isSafeInteger(index) && index >= 0;\r\n}\r\n\r\n/** @internal Type guard for RpcStreamSource (AsyncIterable or factory function). */\r\nfunction _isStreamSource<T>(value: unknown): value is RpcStreamSource<T> {\r\n if (typeof value === 'function') return true;\r\n return (\r\n value !== null &&\r\n typeof value === 'object' &&\r\n Symbol.asyncIterator in (value)\r\n );\r\n}\r\n\r\n/**\r\n * Converts a wire value that *is* a stream reference — the comma-separated\r\n * string the text formats use, or the object shape the binary ones use — into a\r\n * live remote {@link RpcStream}. Returns `null` when the value is not a\r\n * reference.\r\n *\r\n * Call results are deliberately **not** scanned for stream references. A\r\n * TypeScript client has no typed method definitions, so \"is this string a\r\n * stream ref?\" can only be a shape heuristic, and ordinary data matches it\r\n * constantly (\"1,2,3,4\", a CSV row, a coordinate tuple). Deciding that a value\r\n * is a stream is therefore the caller's job — it is the only side that knows\r\n * which positions carry streams.\r\n */\r\nexport function toRpcStream<T>(value: unknown, peer: RpcPeer): RpcStream<T> | null {\r\n const ref = parseStreamRef(value);\r\n if (ref === null)\r\n return null;\r\n\r\n // Registration is deferred to the first iteration (see RpcStream's\r\n // lazy-start), so a stream that is never enumerated leases nothing on\r\n // either peer.\r\n return new RpcStream<T>(ref, peer);\r\n}\r\n","import { PromiseSource, RingBuffer } from '@actuallab/core';\r\nimport { getLogs } from './logging.js';\r\nimport { toExceptionInfo } from './rpc-error.js';\r\nimport type { RpcObjectId, IRpcObject } from './rpc-object.js';\r\nimport { RpcObjectKind } from './rpc-object.js';\r\nimport type { RpcPeer } from './rpc-peer.js';\r\nimport { RpcStream } from './rpc-stream.js';\r\n\r\nconst { warnLog } = getLogs('RpcSharedStream');\r\n\r\n/** Default ack period for server-side streams. */\r\nconst DEFAULT_ACK_PERIOD = 256;\r\n/** Default ack advance window for server-side streams. */\r\nconst DEFAULT_ACK_ADVANCE = 128;\r\n/** Sliding window over which minRttMs is the minimum of ack round-trip samples. */\r\nconst RTT_WINDOW_MS = 20_000;\r\n/** Hard cap on unmatched send timestamps. They are normally drained by the next\r\n * ACK, but nothing drains them while the peer is disconnected — and RTT\r\n * sampling is diagnostics, so dropping the oldest half is free. */\r\nconst MAX_PENDING_SEND_TIMES = 4096;\r\n\r\n/**\r\n * Server-side RPC stream producer — sends items to a remote consumer via\r\n * $sys.I / $sys.B / $sys.End system calls.\r\n *\r\n * Wire protocol (server perspective):\r\n * - Server returns the stream reference string as $sys.Ok\r\n * - Client sends Ack(0, hostId) to start the stream\r\n * - Server sends I (single item) / B (batch) messages\r\n * - Client acks every ackPeriod items for flow control\r\n * - Server sends End to signal completion (with optional error)\r\n * - Client sends AckEnd to acknowledge completion\r\n *\r\n * The sender uses a single ACK-driven loop:\r\n * - On each ACK, drain the source synchronously into the ring buffer up to\r\n * `effectiveBufferSize` (this lets us pre-buffer past `ackAdvance`).\r\n * - For real-time, optionally compact the unsent suffix to the latest\r\n * buffered `canSkipTo` item.\r\n * - Send items up to the `ackAdvance` flow-control window.\r\n * - Wait for the next ACK.\r\n *\r\n * Setting `bufferSize > ackAdvance` lets the sender pre-buffer items past\r\n * the in-flight window so a fresh ACK can be served from RAM instead of\r\n * waiting on the source.\r\n *\r\n * Direct port of .NET `RpcSharedStream<T>` at\r\n * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.\r\n */\r\nexport class RpcStreamSender<T> implements IRpcObject {\r\n private static readonly _emptyGuid = '00000000-0000-0000-0000-000000000000';\r\n\r\n readonly id: RpcObjectId;\r\n readonly kind = RpcObjectKind.Local;\r\n readonly allowReconnect: boolean;\r\n readonly isRealTime: boolean;\r\n readonly canSkipTo: (item: T) => boolean;\r\n readonly peer: RpcPeer;\r\n readonly ackPeriod: number;\r\n /**\r\n * Wire-level flow-control window: the maximum index the sender may\r\n * advance past the most recently acknowledged index. Mirrors .NET\r\n * `RpcStream.AckAdvance`.\r\n */\r\n readonly ackAdvance: number;\r\n /**\r\n * Effective local ring buffer capacity used by the pump. Set larger\r\n * than `ackAdvance` to pre-buffer items beyond the in-flight window;\r\n * values below `ackAdvance` are clamped up (with a warning) at\r\n * construction time. Not transmitted on the wire.\r\n */\r\n readonly bufferSize: number;\r\n /**\r\n * If true, the source was produced by a factory `(abortSignal) => AsyncIterable<T>`\r\n * and may honor the AbortSignal to exit gracefully. `disconnect()` first aborts\r\n * the signal and gives the source up to `RpcStream.disconnectGracePeriodMs` ms\r\n * before force-closing via `iterator.return()`.\r\n *\r\n * If false, the source is a plain AsyncIterable that cannot observe the signal,\r\n * so `disconnect()` force-closes immediately.\r\n */\r\n readonly sourceUsesAbortSignal: boolean;\r\n\r\n // -- Pump state --\r\n /** Next stream index that will be sent (matches .NET's `index`). */\r\n private _nextIndex = 0;\r\n private _ended = false;\r\n private _started = new PromiseSource<void>();\r\n /** Highest `nextIndex` observed in an ACK so far (matches .NET's `lastAckNextIndex` idiom). */\r\n private _lastAckedIndex = 0;\r\n private _abortController = new AbortController();\r\n private _iterator: AsyncIterator<T> | null = null;\r\n\r\n // -- RTT sampling (send → matching ack round trips, windowed minimum) --\r\n private _pendingSendTimes: { index: number; sentAtMs: number }[] = [];\r\n private _rttSamples: { atMs: number; rttMs: number }[] = [];\r\n\r\n // -- Recorder-controller metrics (Step 9.1) --\r\n /** Total items skipped via real-time `canSkipTo` compaction. */\r\n private _skipCount = 0;\r\n /** Fires after a $sys.Ack has been processed (post-compaction). The\r\n * recorder's quality controller uses this to bump a \"last ACK at\"\r\n * watchdog so it can distinguish \"stuck\" (no ACK > N s) from\r\n * \"throttled\" (ACKs flowing). */\r\n onAckProcessed?: () => void;\r\n /** Fires after each item is pushed onto the local ring buffer. The\r\n * argument is the buffer count *after* the push. Together with\r\n * `onAckProcessed` (which is the only place items leave the buffer),\r\n * this gives controllers a complete picture of buffer utilization.\r\n * When `bufferedCount === bufferSize`, the source pull is paused\r\n * until the next ACK frees space. Listener errors are swallowed. */\r\n onBuffered?: (bufferedCount: number) => void;\r\n\r\n get nextIndex(): number {\r\n return this._nextIndex;\r\n }\r\n get lastAckIndex(): number {\r\n return this._lastAckedIndex;\r\n }\r\n get skipCount(): number {\r\n return this._skipCount;\r\n }\r\n // Windowed-MIN ack round trip (ms, -1 until sampled): converges on propagation\r\n // RTT — unlike a mean/EMA it is not inflated by self-induced queuing.\r\n get minRttMs(): number {\r\n const cutoffMs = Date.now() - RTT_WINDOW_MS;\r\n while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)\r\n this._rttSamples.shift();\r\n if (this._rttSamples.length === 0)\r\n return -1;\r\n\r\n let min = this._rttSamples[0].rttMs;\r\n for (const s of this._rttSamples) {\r\n if (s.rttMs < min)\r\n min = s.rttMs;\r\n }\r\n return min;\r\n }\r\n\r\n // -- Coalesced ACK state (analog of .NET's Channel<(long, bool)>) --\r\n // The pump reduces any queued ACKs to the newest index plus the OR of\r\n // `mustReset` anyway, so we accumulate exactly that at receipt: an ACK flood\r\n // then costs O(1) memory and O(1) work instead of an unbounded array drained\r\n // with repeated `shift()`.\r\n /** Newest unprocessed ACK index; -1 means \"nothing pending\". */\r\n private _ackIndex = -1;\r\n private _ackMustReset = false;\r\n /** Highest ACK index accepted so far — the monotonicity baseline a\r\n * non-reset ACK may not regress below. */\r\n private _maxAckIndex = 0;\r\n private _whenAckReady: PromiseSource<void> | null = null;\r\n\r\n constructor(\r\n peer: RpcPeer,\r\n ackPeriod = DEFAULT_ACK_PERIOD,\r\n ackAdvance = DEFAULT_ACK_ADVANCE,\r\n allowReconnect = true,\r\n isRealTime = false,\r\n canSkipTo: (item: T) => boolean = () => true,\r\n sourceUsesAbortSignal = false,\r\n bufferSize?: number,\r\n ) {\r\n const localId = peer.sharedObjects.nextId();\r\n this.id = { hostId: peer.hub.hubId, localId };\r\n this.allowReconnect = allowReconnect;\r\n this.isRealTime = isRealTime;\r\n this.canSkipTo = canSkipTo;\r\n this.peer = peer;\r\n this.ackPeriod = ackPeriod;\r\n this.ackAdvance = ackAdvance;\r\n this.sourceUsesAbortSignal = sourceUsesAbortSignal;\r\n\r\n if (bufferSize !== undefined && bufferSize !== 0 && bufferSize < ackAdvance)\r\n warnLog?.log(\r\n `RpcStream bufferSize (${bufferSize}) is below ackAdvance (${ackAdvance}); using ackAdvance as buffer size.`,\r\n );\r\n this.bufferSize = bufferSize !== undefined && bufferSize >= ackAdvance ? bufferSize : ackAdvance;\r\n }\r\n\r\n /** AbortSignal that is aborted when the sender is disconnected. */\r\n get abortSignal(): AbortSignal {\r\n return this._abortController.signal;\r\n }\r\n\r\n /** Returns the stream reference string for the $sys.Ok response. */\r\n toRef(): string {\r\n return `${this.id.hostId},${this.id.localId},${this.ackPeriod},${this.ackAdvance},${this.allowReconnect ? '1' : '0'},${this.isRealTime ? '1' : '0'}`;\r\n }\r\n\r\n /**\r\n * Called by system call handler when $sys.Ack is received from the client.\r\n *\r\n * Mirrors .NET `RpcSharedStream<T>.OnAck` at\r\n * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:68-101.\r\n */\r\n onAck(nextIndex: number, hostId: string): void {\r\n const mustReset = hostId !== '' && hostId !== RpcStreamSender._emptyGuid;\r\n\r\n // Host mismatch — the consumer reconnected to a different server\r\n // instance, so our stream can never satisfy its acks (RpcSharedStream.cs:82-86).\r\n if (mustReset && hostId !== this.id.hostId) {\r\n this._sendDisconnect();\r\n return;\r\n }\r\n\r\n // A consumer can only acknowledge indexes we actually sent. A NaN,\r\n // fractional, negative or run-ahead index is a protocol violation, and\r\n // accepting one would push `_nextIndex` past the replay buffer — after\r\n // which the pump drains the source without ever sending again.\r\n if (!Number.isSafeInteger(nextIndex) || nextIndex < 0 || nextIndex > this._nextIndex) {\r\n this._sendDisconnect();\r\n return;\r\n }\r\n\r\n if (!this._started.isCompleted) {\r\n // A not-yet-started stream only accepts the initial connect ack.\r\n if (mustReset && nextIndex === 0)\r\n this._started.resolve();\r\n else {\r\n this._sendDisconnect();\r\n return;\r\n }\r\n } else if (this._ended) {\r\n // Ack for an already-completed stream — nothing left to serve.\r\n this._sendDisconnect();\r\n return;\r\n } else if (mustReset && !this.allowReconnect) {\r\n // Reset (reconnect) ack on a non-reconnectable stream — reject.\r\n this._sendDisconnect();\r\n return;\r\n }\r\n\r\n // Only a reset may move the acknowledged position backwards; a plain\r\n // regression carries no window information the pump doesn't already have.\r\n if (!mustReset && nextIndex < this._maxAckIndex)\r\n return;\r\n\r\n this._maxAckIndex = nextIndex;\r\n this._sampleRtt(nextIndex, mustReset);\r\n this._ackIndex = nextIndex;\r\n this._ackMustReset ||= mustReset;\r\n if (this._whenAckReady) {\r\n this._whenAckReady.resolve();\r\n this._whenAckReady = null;\r\n }\r\n }\r\n\r\n // Tells the consumer this shared stream is gone, then disposes it —\r\n // mirrors RpcSharedStream.SendDisconnect (RpcSharedStream.cs:284-289).\r\n private _sendDisconnect(): void {\r\n const conn = this.peer.connection;\r\n if (conn)\r\n this.peer.hub.systemCallSender.disconnect(conn, this.peer.serializationFormat, [this.id.localId]);\r\n\r\n this.disconnect();\r\n }\r\n\r\n /** Called by system call handler when $sys.AckEnd is received from the client. */\r\n onAckEnd(_hostId: string): void {\r\n this.disconnect();\r\n }\r\n\r\n /**\r\n * Send a single item to the client at the current `_nextIndex`.\r\n *\r\n * Advances `_nextIndex` unconditionally — even if the peer is disconnected\r\n * the item is considered \"sent\" from the pump's perspective so the replay\r\n * buffer indexing stays consistent. If a connection is absent, the wire\r\n * message is dropped; the item will be resent from the replay buffer\r\n * after a reconnect (client sends `Ack(N, mustReset=true)`).\r\n */\r\n sendItem(item: T): void {\r\n if (this._ended) return;\r\n const conn = this.peer.connection;\r\n if (conn) {\r\n this.peer.hub.systemCallSender.item(\r\n conn, this.peer.serializationFormat, this.id.localId, this._nextIndex, item,\r\n );\r\n this._pendingSendTimes.push({ index: this._nextIndex, sentAtMs: Date.now() });\r\n if (this._pendingSendTimes.length > MAX_PENDING_SEND_TIMES)\r\n this._pendingSendTimes.splice(0, MAX_PENDING_SEND_TIMES >> 1);\r\n }\r\n this._nextIndex++;\r\n }\r\n\r\n /** Send a batch of items to the client. See {@link sendItem} for disconnect semantics. */\r\n sendBatch(items: T[]): void {\r\n if (this._ended || items.length === 0) return;\r\n const conn = this.peer.connection;\r\n if (conn) {\r\n this.peer.hub.systemCallSender.batch(\r\n conn, this.peer.serializationFormat, this.id.localId, this._nextIndex, items,\r\n );\r\n }\r\n this._nextIndex += items.length;\r\n }\r\n\r\n /** Signal stream completion to the client. */\r\n sendEnd(error?: Error | null): void {\r\n if (this._ended) return;\r\n this._ended = true;\r\n const conn = this.peer.connection;\r\n if (!conn) return;\r\n // .NET ExceptionInfo is a non-nullable value type, so we must always\r\n // emit a valid map shape (empty TypeRef+Message for the \"no error\" case).\r\n const errorInfo = error\r\n ? toExceptionInfo(error)\r\n : { TypeRef: '', Message: '' };\r\n this.peer.hub.systemCallSender.end(\r\n conn, this.peer.serializationFormat, this.id.localId, this._nextIndex, errorInfo,\r\n );\r\n }\r\n\r\n /**\r\n * Consume an AsyncIterable and send all items to the client.\r\n *\r\n * Matches .NET `RpcSharedStream<T>.OnRun` at\r\n * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.\r\n */\r\n async writeFrom(source: AsyncIterable<T>): Promise<void> {\r\n await this._started;\r\n if (this._ended) return;\r\n\r\n const iterator = source[Symbol.asyncIterator]();\r\n this._iterator = iterator;\r\n // Tracks whether the generator has reached a terminal state (done=true\r\n // or thrown). When true, its finally block has already run and the\r\n // iterator reference can be cleared. When false, disconnect()'s\r\n // scheduled iterator.return() is what drives it to its finally.\r\n const state: _PumpState = { iteratorDone: false };\r\n try {\r\n await this._run(iterator, state);\r\n } finally {\r\n if (state.iteratorDone && this._iterator === iterator) {\r\n this._iterator = null;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * ACK-driven pump. Non-real-time streams apply normal backpressure at\r\n * `ackAdvance`; real-time streams may compact the already-buffered unsent\r\n * suffix to the latest buffered `canSkipTo` item after the consumer ACKs.\r\n * With `bufferSize > ackAdvance` the local ring buffer holds more than the\r\n * in-flight window — real-time mode uses the extra space to pre-buffer.\r\n */\r\n /* eslint-disable @typescript-eslint/no-unnecessary-condition -- _ended changes across awaits */\r\n private async _run(\r\n iterator: AsyncIterator<T>,\r\n state: _PumpState,\r\n ): Promise<void> {\r\n const buffer = new RingBuffer<_StreamItem<T>>(this.bufferSize + 1);\r\n const isRealTime = this.isRealTime;\r\n let bufferStart = 0;\r\n let isFullyBuffered = false;\r\n let whenMovedNext: Promise<IteratorResult<T>> | null = null;\r\n const pending = Symbol('pending');\r\n\r\n const readNext = async (): Promise<IteratorResult<T>> => {\r\n const next = whenMovedNext ?? iterator.next();\r\n whenMovedNext = null;\r\n return await next;\r\n };\r\n\r\n const tryReadReady = async (): Promise<IteratorResult<T> | typeof pending> => {\r\n whenMovedNext ??= iterator.next();\r\n return await Promise.race([\r\n whenMovedNext,\r\n Promise.resolve(pending),\r\n ]);\r\n };\r\n\r\n const fireBuffered = (): void => {\r\n try {\r\n this.onBuffered?.(buffer.count);\r\n } catch { /* listener errors don't break the pump */ }\r\n };\r\n\r\n const bufferNext = async (onlyIfReady: boolean, prefetchNext: boolean): Promise<boolean> => {\r\n let r: IteratorResult<T> | typeof pending;\r\n try {\r\n r = onlyIfReady ? await tryReadReady() : await readNext();\r\n if (r === pending)\r\n return false;\r\n whenMovedNext = null;\r\n if (this._ended) return false;\r\n if (r.done) {\r\n buffer.pushTail(_endItem);\r\n isFullyBuffered = true;\r\n state.iteratorDone = true;\r\n } else {\r\n buffer.pushTail({ kind: 'value', value: r.value });\r\n if (prefetchNext)\r\n whenMovedNext = iterator.next();\r\n }\r\n fireBuffered();\r\n return true;\r\n } catch (e) {\r\n whenMovedNext = null;\r\n state.iteratorDone = true;\r\n isFullyBuffered = true;\r\n buffer.pushTail({\r\n kind: 'error',\r\n error: e instanceof Error ? e : new Error(String(e)),\r\n });\r\n fireBuffered();\r\n return true;\r\n }\r\n };\r\n\r\n while (true) {\r\n // ---- nextAck ----\r\n // 1. Await for an acknowledgement & process accumulated ACKs.\r\n let ack = this._tryProcessAcks();\r\n if (!ack) {\r\n await this._waitAckReady();\r\n if (this._ended) return;\r\n ack = this._tryProcessAcks();\r\n if (!ack) {\r\n warnLog?.log(\"Something is off: couldn't read an acknowledgement\");\r\n return;\r\n }\r\n }\r\n this._lastAckedIndex = ack.nextIndex;\r\n\r\n // 1.1. Reconnect: real-time streams can clear stale buffered data\r\n // and restart from the next source item accepted by canSkipTo.\r\n if (isRealTime && ack.mustReset && !isFullyBuffered) {\r\n buffer.clear();\r\n bufferStart = this._nextIndex;\r\n while (true) {\r\n if (this._ended) return;\r\n let item: _StreamItem<T>;\r\n let accepted = false;\r\n try {\r\n const r = await readNext();\r\n if (this._ended) return;\r\n if (r.done) {\r\n item = _endItem;\r\n isFullyBuffered = true;\r\n state.iteratorDone = true;\r\n } else if (this.canSkipTo(r.value)) {\r\n item = { kind: 'value', value: r.value };\r\n accepted = true;\r\n } else {\r\n continue; // discard; keep draining\r\n }\r\n } catch (e) {\r\n state.iteratorDone = true;\r\n isFullyBuffered = true;\r\n item = {\r\n kind: 'error',\r\n error: e instanceof Error ? e : new Error(String(e)),\r\n };\r\n }\r\n buffer.pushTail(item);\r\n fireBuffered();\r\n if (accepted || isFullyBuffered) break;\r\n }\r\n }\r\n\r\n // 2. Remove what's useless from the buffer.\r\n const shift = _clamp(ack.nextIndex - bufferStart, 0, buffer.count);\r\n if (shift > 0) {\r\n buffer.moveHead(shift);\r\n bufferStart += shift;\r\n }\r\n\r\n // 3. Recalculate the next range to send.\r\n if (this._nextIndex < bufferStart) {\r\n if (!this._ended)\r\n this.sendEnd(new Error('Stream position unavailable.'));\r\n return;\r\n }\r\n let bufferIndex = this._nextIndex - bufferStart;\r\n\r\n // 3. Send as much as the current ACK window allows.\r\n const maxIndex = ack.nextIndex + this.ackAdvance;\r\n while (this._nextIndex < maxIndex) {\r\n if (this._ended) return;\r\n\r\n if (isRealTime) {\r\n // Real-time: pre-buffer aggressively up to the ring's\r\n // capacity (which may exceed ackAdvance when bufferSize\r\n // is set), so a freshly arrived ACK is served from RAM.\r\n while (buffer.hasRemainingCapacity && !isFullyBuffered && !this._hasPendingAck) {\r\n if (!await bufferNext(true, true))\r\n break;\r\n }\r\n if (bufferIndex >= buffer.count\r\n && !isFullyBuffered\r\n && buffer.hasRemainingCapacity\r\n && !this._hasPendingAck) {\r\n await bufferNext(false, true);\r\n }\r\n if (ack.nextIndex > 0) {\r\n const result = _compactBufferedUnsentSuffix(\r\n buffer, bufferIndex, this.canSkipTo);\r\n bufferIndex = result.newIndex;\r\n if (result.skipped > 0)\r\n this._skipCount += result.skipped;\r\n }\r\n\r\n // Bail out so step 1 can process a mustReset ACK immediately.\r\n if (this._hasPendingAck) break;\r\n }\r\n\r\n // Drain buffered items until the window is full.\r\n while (this._nextIndex < maxIndex && bufferIndex < buffer.count) {\r\n const item = buffer.get(bufferIndex++);\r\n if (item.kind === 'value') {\r\n this.sendItem(item.value);\r\n } else if (item.kind === 'end') {\r\n if (!this._ended) this.sendEnd();\r\n return;\r\n } else {\r\n if (!this._ended) this.sendEnd(item.error);\r\n return;\r\n }\r\n }\r\n if (this._nextIndex >= maxIndex)\r\n break;\r\n if (isFullyBuffered) break;\r\n if (!buffer.hasRemainingCapacity) break;\r\n\r\n if (!isRealTime)\r\n await bufferNext(false, false);\r\n }\r\n }\r\n }\r\n /* eslint-enable @typescript-eslint/no-unnecessary-condition */\r\n\r\n private get _hasPendingAck(): boolean {\r\n return this._ackIndex >= 0;\r\n }\r\n\r\n /** Consume the coalesced ACK state (or null if nothing is pending).\r\n * A reconnect's reset ACK may be coalesced with the regular consumption\r\n * ACKs the client sends right after it, so `mustReset` accumulates across\r\n * the whole batch — the newest ACK alone can't represent it. */\r\n private _tryProcessAcks(): { nextIndex: number; mustReset: boolean } | null {\r\n const nextIndex = this._ackIndex;\r\n if (nextIndex < 0)\r\n return null;\r\n\r\n const mustReset = this._ackMustReset;\r\n this._ackIndex = -1;\r\n this._ackMustReset = false;\r\n if (mustReset || this._nextIndex < nextIndex)\r\n this._nextIndex = nextIndex;\r\n\r\n try {\r\n this.onAckProcessed?.();\r\n } catch { /* listener errors don't break the pump */ }\r\n return { nextIndex, mustReset };\r\n }\r\n\r\n /** Wait until an ACK is pending (or `_ended`). */\r\n private async _waitAckReady(): Promise<void> {\r\n if (this._hasPendingAck || this._ended)\r\n return;\r\n\r\n this._whenAckReady = new PromiseSource<void>();\r\n await this._whenAckReady;\r\n this._whenAckReady = null;\r\n }\r\n\r\n // One RTT sample per ack: time from sending the newest acked item to the\r\n // ack's arrival (a slight upper bound — includes the server's ack cadence).\r\n private _sampleRtt(ackNextIndex: number, mustReset: boolean): void {\r\n if (mustReset) {\r\n this._pendingSendTimes.length = 0;\r\n return;\r\n }\r\n\r\n let sentAtMs = -1;\r\n while (this._pendingSendTimes.length > 0 && this._pendingSendTimes[0].index < ackNextIndex) {\r\n sentAtMs = this._pendingSendTimes[0].sentAtMs;\r\n this._pendingSendTimes.shift();\r\n }\r\n if (sentAtMs < 0)\r\n return;\r\n\r\n const nowMs = Date.now();\r\n const cutoffMs = nowMs - RTT_WINDOW_MS;\r\n while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)\r\n this._rttSamples.shift();\r\n this._rttSamples.push({ atMs: nowMs, rttMs: nowMs - sentAtMs });\r\n }\r\n\r\n // -- IRpcObject --\r\n\r\n reconnect(): void {\r\n // Server-side streams don't support reconnection — the client\r\n // creates a new stream on reconnect.\r\n }\r\n\r\n disconnect(): void {\r\n if (this._ended) return;\r\n this._ended = true;\r\n this._abortController.abort();\r\n if (!this._started.isCompleted) {\r\n this._started.resolve();\r\n }\r\n if (this._whenAckReady) {\r\n this._whenAckReady.resolve();\r\n this._whenAckReady = null;\r\n }\r\n this.peer.sharedObjects.unregister(this);\r\n\r\n // Iterator force-close policy:\r\n // - Plain AsyncIterable: abortSignal is unobserved, so force-close now.\r\n // - Factory source (abortSignal-aware): give it a grace period to exit\r\n // gracefully, then force-close if still pumping.\r\n const iterator = this._iterator;\r\n if (!iterator) return;\r\n if (!this.sourceUsesAbortSignal) {\r\n this._iterator = null;\r\n this._forceCloseIterator(iterator);\r\n return;\r\n }\r\n setTimeout(() => {\r\n if (this._iterator === iterator) {\r\n this._iterator = null;\r\n this._forceCloseIterator(iterator);\r\n }\r\n }, RpcStream.disconnectGracePeriodMs);\r\n }\r\n\r\n private _forceCloseIterator(iterator: AsyncIterator<T>): void {\r\n try {\r\n const ret = iterator.return?.(undefined);\r\n if (ret) void ret.catch(() => { /* ignore */ });\r\n } catch { /* ignore */ }\r\n }\r\n}\r\n\r\n// -- Internal helpers (file-local) --\r\n\r\n/** Wrapper matching .NET's `Result<T>` — value, end-of-stream, or error. */\r\ntype _StreamItem<T> =\r\n | { kind: 'value'; value: T }\r\n | { kind: 'end' }\r\n | { kind: 'error'; error: Error };\r\n\r\n/** Singleton \"source exhausted\" marker (matches .NET's `NoMoreItemsTag`). */\r\nconst _endItem: _StreamItem<never> = { kind: 'end' };\r\n\r\n/** Shared mutable state between writeFrom and its per-mode pump. */\r\ninterface _PumpState {\r\n iteratorDone: boolean;\r\n}\r\n\r\nfunction _clamp(value: number, min: number, max: number): number {\r\n return value < min ? min : value > max ? max : value;\r\n}\r\n\r\nfunction _compactBufferedUnsentSuffix<T>(\r\n buffer: RingBuffer<_StreamItem<T>>,\r\n firstUnsentIndex: number,\r\n canSkipTo: (item: T) => boolean,\r\n): { newIndex: number; skipped: number } {\r\n // Keep items that have already been assigned RPC stream indexes, then\r\n // collapse the unsent suffix to the latest buffered restart point. The\r\n // surviving suffix is intentionally sent under fresh RPC stream indexes;\r\n // item-level timestamps/frame indexes are the caller's responsibility.\r\n let skipToIndex = -1;\r\n for (let i = firstUnsentIndex; i < buffer.count; i++) {\r\n const item = buffer.get(i);\r\n if (item.kind === 'value' && canSkipTo(item.value))\r\n skipToIndex = i;\r\n }\r\n if (skipToIndex <= firstUnsentIndex)\r\n return { newIndex: firstUnsentIndex, skipped: 0 };\r\n\r\n const cutFrom = firstUnsentIndex;\r\n const cutTo = skipToIndex;\r\n\r\n const items = buffer.toArray();\r\n buffer.clear();\r\n for (let i = 0; i < cutFrom; i++)\r\n buffer.pushTail(items[i]);\r\n for (let i = cutTo; i < items.length; i++)\r\n buffer.pushTail(items[i]);\r\n return { newIndex: firstUnsentIndex, skipped: skipToIndex - firstUnsentIndex };\r\n}\r\n","import type { IRpcObject } from './rpc-object.js';\r\n\r\nexport class RpcRemoteObjectTracker {\r\n private _objects = new Map<number, IRpcObject>();\r\n\r\n register(obj: IRpcObject): void {\r\n const localId = obj.id.localId;\r\n const existing = this._objects.get(localId);\r\n if (existing === obj)\r\n return;\r\n\r\n // A different live object shares this localId — happens after switching\r\n // to another peer instance (e.g. via LB). Disconnect the stale one first.\r\n if (existing !== undefined)\r\n existing.disconnect();\r\n\r\n this._objects.set(localId, obj);\r\n }\r\n\r\n get(localId: number): IRpcObject | undefined {\r\n return this._objects.get(localId);\r\n }\r\n\r\n keys(): IterableIterator<number> {\r\n return this._objects.keys();\r\n }\r\n\r\n unregister(obj: IRpcObject): void {\r\n const localId = obj.id.localId;\r\n if (this._objects.get(localId) === obj)\r\n this._objects.delete(localId);\r\n }\r\n\r\n disconnectAll(): void {\r\n for (const obj of this._objects.values()) {\r\n obj.disconnect();\r\n }\r\n this._objects.clear();\r\n }\r\n\r\n reconnectAll(): void {\r\n for (const obj of this._objects.values()) {\r\n if (obj.allowReconnect) obj.reconnect();\r\n else obj.disconnect();\r\n }\r\n }\r\n}\r\n","import type { IRpcObject } from './rpc-object.js';\r\n\r\nexport class RpcSharedObjectTracker {\r\n private _nextId = 1;\r\n private _objects = new Map<number, IRpcObject>();\r\n\r\n nextId(): number {\r\n return this._nextId++;\r\n }\r\n\r\n register(obj: IRpcObject): void {\r\n const localId = obj.id.localId;\r\n if (this._objects.has(localId))\r\n throw new Error(`RPC shared object with localId ${localId} is already registered.`);\r\n\r\n this._objects.set(localId, obj);\r\n }\r\n\r\n get(localId: number): IRpcObject | undefined {\r\n return this._objects.get(localId);\r\n }\r\n\r\n keys(): IterableIterator<number> {\r\n return this._objects.keys();\r\n }\r\n\r\n unregister(obj: IRpcObject): void {\r\n const localId = obj.id.localId;\r\n if (this._objects.get(localId) === obj)\r\n this._objects.delete(localId);\r\n }\r\n\r\n disconnectAll(): void {\r\n for (const obj of this._objects.values()) {\r\n obj.disconnect();\r\n }\r\n this._objects.clear();\r\n }\r\n}\r\n","// .NET counterparts:\r\n// RpcPeer (529 lines) — abstract WorkerBase that runs a reconnection loop in\r\n// OnRun(). Manages AsyncState<RpcPeerConnectionState> (a linked list of\r\n// immutable connection snapshots), handshake exchange, peerChanged detection,\r\n// message processing loop, maintenance tasks, Reset(), and SetConnectionState()\r\n// with thread-safe locking.\r\n// RpcClientPeer (56 lines) — calls GetConnection → WebSocket transport.\r\n// RpcServerPeer (54 lines) — receives connection via RpcServerConnectionHandler.\r\n// RpcPeerConnectionKind (48 lines) — None/Remote/Loopback/Local enum.\r\n// RpcPeerConnectionState (42 lines) — immutable record: Connection, Transport,\r\n// Handshake, OwnHandshake, ReaderTokenSource, Error, TryIndex.\r\n// RpcPeerChangeKind (11 lines) — Unchanged/ChangedToVeryFirst/Changed enum.\r\n// RpcHandshake (28 lines) — PeerId, ApiVersionSet, HubId, ProtocolVersion,\r\n// Index, Secret. Has GetPeerChangeKind(lastHandshake) comparison method.\r\n// `Secret` (index 5, optional) is the server's per-peer reconnect secret;\r\n// it flows server→client only, and the client echoes possession of it via\r\n// the `c` (counter) and `p` (HMAC proof) connect-URL parameters — see\r\n// `reconnect-proof.ts`. The TS client never issues a secret, so the\r\n// handshake it sends stays a 5-element array / 5-property object.\r\n// RpcClientPeerReconnectDelayer (35 lines) — exponential backoff profiles:\r\n// test (50ms), server (250ms), client (1s) with 1.5x growth, max 10s.\r\n//\r\n// Ported from .NET:\r\n// - Handshake exchange — run() sends $sys.Handshake on connect, waits for the\r\n// server's response. RpcServerPeer._onHandshakeReceived() auto-replies.\r\n// Like .NET's OnRun() handshake sequence.\r\n// - Peer change detection — run() compares RemotePeerId from successive\r\n// handshakes to detect server restarts, triggering peerChanged + inbound.clear().\r\n// Like .NET's RpcHandshake.GetPeerChangeKind().\r\n// - Outbound call cancellation via AbortSignal — call(signal) registers an\r\n// abort listener that rejects the call, removes it from the tracker, and sends\r\n// $sys.Cancel to the server. Like .NET's CancellationHandler + NotifyCancelled().\r\n//\r\n// Omitted from .NET:\r\n// - AsyncState<RpcPeerConnectionState> — immutable linked-list of connection\r\n// states that consumers can await via WhenConnected/WhenDisconnected. TS uses\r\n// simpler EventHandlerSet events (connected/disconnected). AsyncState enables\r\n// callers to block until a specific state transition, which is essential in\r\n// .NET's multi-threaded world but unnecessary in TS's single-threaded model.\r\n// - RpcPeerConnectionState record — snapshots Connection + Handshake + OwnHandshake\r\n// + ReaderTokenSource + Error + TryIndex. TS tracks these as mutable fields\r\n// on the peer directly.\r\n// - Maintenance tasks (SharedObjects.Maintain, RemoteObjects.Maintain,\r\n// OutboundCalls.Maintain) — background loops that run while connected,\r\n// checking timeouts and managing object lifetimes. TS has simpler keep-alive\r\n// timer; no shared/remote object tracking, no call timeout monitoring.\r\n// - Reconnect protocol (OutboundCalls.Reconnect) — stage-based call resumption\r\n// after reconnection. TS uses transparent reconnect: outbound calls survive\r\n// disconnect, are re-sent on reconnect (stage-3 compute calls self-invalidate\r\n// on peer change instead of being re-sent).\r\n// - peerChangedToken / CancellationTokenSource — .NET threads a cancellation\r\n// token through all inbound calls that gets cancelled when the remote peer\r\n// changes identity. TS clears the inbound tracker on peer change;\r\n// outbound calls survive disconnect and are re-sent on reconnect.\r\n// - Reset() — aborts RemoteObjects, SharedObjects, OutboundCalls, and clears\r\n// InboundCalls. TS clears inbound tracker on peer change; outbound calls\r\n// are never rejected on disconnect (only on close/stop).\r\n// - ServerMethodResolver / GetServerMethodResolver(handshake) — resolves method\r\n// defs using the remote peer's API version set + legacy names. TS has no\r\n// versioning.\r\n// - ProcessMessage() — creates RpcInboundContext per message, dispatches via\r\n// MethodDef.InboundCallInvoker. TS dispatches via _handleMessage →\r\n// handleSystemCall or RpcServiceHost.dispatch.\r\n// - RpcRef / RpcRoute / routing — .NET peers are bound to an RpcRoute (one route\r\n// generation of a stable RpcRef, which encodes client/server, versions,\r\n// serialization format). TS peers are keyed by string ref (URL for clients,\r\n// \"server://{uuid}\" for servers).\r\n// - StopMode / ComputeAutoStopMode — controls behavior of inbound calls when\r\n// peer stops (cancel vs keep-incomplete). TS has no stop mode.\r\n// - ConnectionKind detector (Remote/Loopback/Local/None) — .NET detects if peer\r\n// is in-process. TS is always remote.\r\n// - Versions / VersionSet / API version negotiation — .NET exchanges version\r\n// sets during handshake for backward compatibility. TS has no versioning.\r\n// - Diagnostics (CallLogger, CallLogLevel, DebugLog) — per-peer logging/tracing.\r\n// Ported since: per-call timeout monitoring — see `_maintainOutboundCalls`, which\r\n// applies RpcCallTimeouts the way .NET splits them between `HandleDisconnect`\r\n// (ConnectTimeout, while the peer is away) and `Maintain` (RunTimeout, while connected).\r\n// - Inbound call cancellation propagation — when $sys.Cancel is received, .NET\r\n// cancels the inbound call's CancellationTokenSource, aborting the running\r\n// service method. TS removes the call from the inbound tracker but does not\r\n// yet propagate cancellation to the service handler (would require AbortSignal\r\n// threading through RpcServiceHost.dispatch).\r\n\r\nimport { EventHandlerSet, PromiseSource, TimeoutError, cancellationError, withTimeout } from '@actuallab/core';\r\nimport type { RpcCallTimeouts } from './rpc-call-timeouts.js';\r\nimport { getLogs } from './logging.js';\r\nimport {\r\n RpcWebSocketConnection,\r\n type RpcConnection,\r\n type RpcReceivedMessage,\r\n type WebSocketLike,\r\n} from './rpc-connection.js';\r\nimport {\r\n RpcOutboundCall,\r\n RpcOutboundCallTracker,\r\n RpcInboundCall,\r\n RpcInboundCallTracker,\r\n} from './rpc-call-tracker.js';\r\nimport { RpcSystemCalls, type RpcMessage } from './rpc-message.js';\r\nimport { deserializeMessage } from './rpc-serialization.js';\r\nimport type { RpcHub } from './rpc-hub.js';\r\nimport { RpcRemoteObjectTracker } from './rpc-remote-object-tracker.js';\r\nimport { RpcSharedObjectTracker } from './rpc-shared-object-tracker.js';\r\nimport { RpcStream } from './rpc-stream.js';\r\nimport { RpcSerializationFormat, RpcSerializationFormatResolver } from './rpc-serialization-format.js';\r\nimport { IncreasingSeqCompressor } from './increasing-seq-compressor.js';\r\nimport { base64Decode, base64Encode, base64UrlEncode } from './base64.js';\r\nimport {\r\n computeReconnectProof,\r\n isReconnectProofSupported,\r\n type RpcReconnectProofParameters,\r\n} from './reconnect-proof.js';\r\n\r\n// Yields to the event loop. Used by the reconnect-resend loop to chunk\r\n// large bursts so they don't spike main-thread time post-handshake.\r\n// setTimeout(0) is a macrotask (queueMicrotask would not yield to other\r\n// tasks); 0 ms still gets bumped to 4 ms by the browser for nested\r\n// setTimeouts but the slack is fine — we want a yield, not zero latency.\r\nfunction yieldToEventLoop(): Promise<void> {\r\n return new Promise<void>(resolve => setTimeout(resolve, 0));\r\n}\r\n\r\n/** WebSocket close code sent by the server when the client's serialization format is unsupported. */\r\n\r\nexport const RPC_CLOSE_CODE_UNSUPPORTED_FORMAT = 4001;\r\n\r\n// Connect / handshake / keepalive timing limits now live on `RpcLimits`,\r\n// resolved via `hub.limits` at peer construction. See `rpc-limits.ts` for\r\n// the override paths (process-wide / per-hub / per-peer).\r\n\r\nconst { debugLog, infoLog, warnLog, errorLog } = getLogs('RpcPeer');\r\n\r\n/** Builds the WebSocket connection URL for an RpcClientPeer. */\r\nexport type RpcConnectionUrlResolver = (peer: RpcClientPeer) => string | Promise<string>;\r\n\r\n/** Default connection URL provider — sets `clientId` and `f` query parameters,\r\n * replacing any existing values to keep the wire format consistent with the\r\n * peer's actual `serializationFormat`. Adds the reconnect proof (`c` + `p`)\r\n * once the peer holds a server-issued secret. */\r\nexport const defaultConnectionUrlResolver: RpcConnectionUrlResolver = async peer => {\r\n const formatKey = peer.serializationFormat.key;\r\n // Neither value needs percent-encoding: `c` is decimal digits and `p` is\r\n // base64url, and `URLSearchParams` encodes neither `-` nor `_`.\r\n const proof = await peer.computeReconnectProof();\r\n try {\r\n const url = new URL(peer.ref);\r\n url.searchParams.set('clientId', peer.clientId);\r\n url.searchParams.set('f', formatKey);\r\n if (proof !== undefined) {\r\n url.searchParams.set('c', proof.counter);\r\n url.searchParams.set('p', proof.proof);\r\n }\r\n return url.toString();\r\n } catch {\r\n // Fallback for non-URL refs (tests/harnesses that pass \"ws://test\" etc.)\r\n const sep = peer.ref.includes('?') ? '&' : '?';\r\n let url = peer.ref + sep + `clientId=${peer.clientId}&f=${formatKey}`;\r\n if (proof !== undefined)\r\n url += `&c=${proof.counter}&p=${proof.proof}`;\r\n return url;\r\n }\r\n};\r\n\r\n/** Query parameters replaced with `<redacted>` before a URL reaches the log.\r\n * `clientId` is here because it selects the server-side peer, and `p` because\r\n * it proves possession of that peer's secret. */\r\nconst RedactedParameterNames = ['session', 'clientId', 'p'];\r\n\r\n// eslint-disable-next-line prefer-const -- intentionally `let` so consumers can swap the implementation\r\nexport let sanitizeUrl = (url: string): string => {\r\n try {\r\n const parsed = new URL(url);\r\n let isRedacted = false;\r\n for (const name of RedactedParameterNames) {\r\n if (!parsed.searchParams.has(name))\r\n continue;\r\n\r\n parsed.searchParams.set(name, '<redacted>');\r\n isRedacted = true;\r\n }\r\n return isRedacted ? parsed.toString() : url;\r\n } catch {\r\n return url.replace(/([?&](?:session|clientId|p)=)[^&]*/gi, '$1<redacted>');\r\n }\r\n};\r\n\r\n/** Extract the serialization format key from an `f=` query parameter, if any. */\r\nfunction parseFormatFromUrl(url: string): string | undefined {\r\n try {\r\n const f = new URL(url).searchParams.get('f');\r\n return f ?? undefined;\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/** Options for RpcPeer.call() — allows custom call types and cancellation. */\r\nexport interface RpcCallOptions {\r\n /** Wire CallType field (0 = regular). */\r\n callTypeId?: number;\r\n /** Factory for creating custom outbound call instances (e.g. compute calls). */\r\n outboundCallFactory?: (id: number, method: string) => RpcOutboundCall;\r\n /** AbortSignal for caller-initiated cancellation. */\r\n signal?: AbortSignal;\r\n /** Bitfield of RpcRemoteExecutionMode flags (default: 7 = Default). */\r\n remoteExecutionMode?: number;\r\n /** Per-call connect/run timeouts (R12). Default: unbounded. */\r\n timeouts?: RpcCallTimeouts;\r\n}\r\n\r\n/** Data extracted from an inbound $sys.Handshake message. */\r\nexport interface RemoteHandshake {\r\n RemotePeerId?: string;\r\n RemoteHubId?: string;\r\n ProtocolVersion?: number;\r\n Index?: number;\r\n /** Server-issued reconnect secret. Server→client only — a client never sends one. */\r\n Secret?: string;\r\n}\r\n\r\n/** Base class for RPC peers — handles bidirectional message dispatch. */\r\nexport abstract class RpcPeer {\r\n readonly hub: RpcHub;\r\n /** Routing/addressing key — used as key in hub.peers (URL for clients, \"server://{uuid}\" for servers). */\r\n readonly ref: string;\r\n /** Auto-generated GUID — used in handshakes only. */\r\n readonly id: string = crypto.randomUUID();\r\n readonly outboundCalls = new RpcOutboundCallTracker();\r\n readonly inboundCalls = new RpcInboundCallTracker();\r\n readonly remoteObjects = new RpcRemoteObjectTracker();\r\n readonly sharedObjects = new RpcSharedObjectTracker();\r\n\r\n /** Fired on every state transition with the new value. Initial state is\r\n * `Disconnected`; `Connecting` is entered when the peer starts an\r\n * attempt (the reconnect delay is part of this phase); `Connected` is\r\n * entered only after the handshake has succeeded. */\r\n readonly connectionStateChanged = new EventHandlerSet<RpcConnectionState>();\r\n\r\n /** Index of the handshake THIS peer most recently sent. Used to reject a\r\n * stale `$sys.Reconnect` that references an older connection generation\r\n * (C# `RpcSystemCalls.Reconnect`'s `ownHandshake.Index` check). */\r\n protected _ownHandshakeIndex = 0;\r\n /** Whether the current connection generation has already used its single\r\n * `$sys.Reconnect`. Mirrors C# `RpcPeerConnectionState.TryClaimReconnect`. */\r\n protected _isReconnectClaimed = false;\r\n protected _connection: RpcConnection | undefined;\r\n /** Mirror of `_connectionState === Connected`. True only after the\r\n * handshake round-trip completes; outbound `call()` checks this to\r\n * decide between sending now and deferring until the next `Connected`\r\n * transition. Kept as its own field so it can stay false through the\r\n * Handshaking phase without exposing the run loop's `_connection`\r\n * bookkeeping. */\r\n protected _isConnected = false;\r\n protected _connectionState: RpcConnectionState = RpcConnectionState.Disconnected;\r\n private _keepAliveTimer: ReturnType<typeof setInterval> | undefined;\r\n private _keepAliveWatchdog: ReturnType<typeof setTimeout> | undefined;\r\n private _maintainTimer: ReturnType<typeof setInterval> | undefined;\r\n /** Wall-clock time of the last inbound `$sys.KeepAlive`, or 0 if none yet\r\n * for the current connection. Useful for diagnostics. */\r\n private _lastKeepAliveAt = 0;\r\n /** When the current outage began; 0 until the peer has been connected at least once. */\r\n private _disconnectedAt = 0;\r\n /** Outbound keep-alive send period. Initialized from `hub.limits` at\r\n * construction; set directly to override on a single peer. */\r\n keepAlivePeriodMs: number;\r\n /** Inbound keep-alive silence tolerance. Initialized from `hub.limits`\r\n * at construction; set directly to override on a single peer. */\r\n keepAliveTimeoutMs: number;\r\n\r\n protected constructor(hub: RpcHub, ref: string) {\r\n this.hub = hub;\r\n this.ref = ref;\r\n this.keepAlivePeriodMs = hub.limits.keepAlivePeriodMs;\r\n this.keepAliveTimeoutMs = hub.limits.keepAliveTimeoutMs;\r\n this.inboundCalls.completedCallsLimit = hub.limits.completedInboundCallsLimit;\r\n const checkPeriod = hub.limits.callTimeoutCheckPeriodMs;\r\n if (checkPeriod > 0 && Number.isFinite(checkPeriod)) {\r\n this._maintainTimer = setInterval(() => this._maintainOutboundCalls(), checkPeriod);\r\n // Don't keep a Node process alive just for the maintenance timer.\r\n (this._maintainTimer as { unref?: () => void }).unref?.();\r\n }\r\n }\r\n\r\n /** Fail outbound calls whose per-call {@link RpcCallTimeouts} elapsed —\r\n * the connect timeout while a call still awaits the wire, the run timeout\r\n * once it has been sent, and the connect timeout again for any pending call while the\r\n * peer is away. Mirrors .NET `RpcOutboundCallTracker.HandleDisconnect` (ConnectTimeout,\r\n * per outage) plus `Maintain` (RunTimeout, per connection). Calls without timeouts\r\n * (queries) never time out here. */\r\n private _maintainOutboundCalls(): void {\r\n const now = Date.now();\r\n // Run timeouts apply only while the link looks alive: .NET's `Maintain` loop is\r\n // per-connection and skips timeout checks when keep-alive is stale — otherwise calls\r\n // sent before a disconnect/stall would be failed instead of being resent on reconnect.\r\n const canRunTimeout = this._isConnected\r\n && (this._lastKeepAliveAt === 0 || now - this._lastKeepAliveAt <= this.keepAliveTimeoutMs);\r\n for (const call of [...this.outboundCalls.values()]) {\r\n const timeouts = call.timeouts;\r\n if (timeouts === undefined || call.result.isCompleted)\r\n continue;\r\n\r\n let timedOut: string | undefined;\r\n if (!this._isConnected) {\r\n // While the peer is away, ConnectTimeout caps every pending call, sent or not:\r\n // a sent call is waiting for the same reconnect an unsent one is. It runs from\r\n // the later of the disconnect and the call, so a call issued mid-outage still\r\n // gets its full timeout. Mirrors .NET `RpcOutboundCallTracker.HandleDisconnect`.\r\n const since = Math.max(this._disconnectedAt, call.startedAt);\r\n if (now - since >= timeouts.connectTimeoutMs)\r\n timedOut = `connect timeout (${timeouts.connectTimeoutMs}ms)`;\r\n } else if (call.sentAt === 0) {\r\n if (now - call.startedAt >= timeouts.connectTimeoutMs)\r\n timedOut = `connect timeout (${timeouts.connectTimeoutMs}ms)`;\r\n } else if (canRunTimeout && now - call.sentAt >= timeouts.runTimeoutMs)\r\n timedOut = `run timeout (${timeouts.runTimeoutMs}ms)`;\r\n if (timedOut === undefined)\r\n continue;\r\n\r\n this.outboundCalls.remove(call.callId);\r\n call.result.reject(new TimeoutError(`'${this.ref}': call ${call.method}#${call.callId} timed out — ${timedOut}`));\r\n call.onDisconnect();\r\n }\r\n }\r\n\r\n get connection(): RpcConnection | undefined {\r\n return this._connection;\r\n }\r\n\r\n get connectionState(): RpcConnectionState {\r\n return this._connectionState;\r\n }\r\n\r\n get isConnected(): boolean {\r\n return this._isConnected;\r\n }\r\n\r\n get ownHandshakeIndex(): number {\r\n return this._ownHandshakeIndex;\r\n }\r\n\r\n // A conforming peer sends `$sys.Reconnect` at most once per connection, so anything\r\n // beyond that is a replay - and the reconciliation it performs is proportional to the\r\n // call ids it carries, with nothing else capping how often it can arrive.\r\n public tryClaimReconnect(): boolean {\r\n if (this._isReconnectClaimed)\r\n return false;\r\n\r\n this._isReconnectClaimed = true;\r\n return true;\r\n }\r\n\r\n protected nextOwnHandshakeIndex(): number {\r\n this._isReconnectClaimed = false;\r\n return ++this._ownHandshakeIndex;\r\n }\r\n\r\n protected _setConnectionState(value: RpcConnectionState): void {\r\n if (this._connectionState === value) return;\r\n const previousState = this._connectionState;\r\n this._connectionState = value;\r\n // `_isConnected` flips on entering `Connected`, and off only when\r\n // leaving `Connected`. Failed-handshake transitions (Connecting →\r\n // Disconnected) leave it alone — it was already false in production,\r\n // and tests that set it directly via `connectWith` keep it true while\r\n // the run loop spins through retry iterations without ever reaching\r\n // `Connected`.\r\n if (value === RpcConnectionState.Connected) {\r\n this._isConnected = true;\r\n this._disconnectedAt = 0;\r\n this._armKeepAliveWatchdog();\r\n } else if (value === RpcConnectionState.Disconnected) {\r\n if (previousState === RpcConnectionState.Connected) {\r\n this._isConnected = false;\r\n this._disconnectedAt = Date.now();\r\n }\r\n this._disarmKeepAliveWatchdog();\r\n }\r\n this.connectionStateChanged.trigger(value);\r\n }\r\n\r\n /** Resolves on the next transition into `Connected` — or immediately if\r\n * already in that state. */\r\n whenConnected(): Promise<void> {\r\n if (this._connectionState === RpcConnectionState.Connected)\r\n return Promise.resolve();\r\n return new Promise<void>(resolve => {\r\n const handler = (state: RpcConnectionState): void => {\r\n if (state !== RpcConnectionState.Connected) return;\r\n this.connectionStateChanged.remove(handler);\r\n resolve();\r\n };\r\n this.connectionStateChanged.add(handler);\r\n });\r\n }\r\n\r\n protected setupConnection(conn: RpcConnection): void {\r\n this._connection = conn;\r\n\r\n conn.messageReceived.add(raw => this._handleMessage(raw));\r\n conn.closed.add(ev => {\r\n // Bail if this conn has already been replaced (e.g. another\r\n // setupConnection() call ran in between). Without this guard,\r\n // a late-firing close event from the old socket would clobber\r\n // the live `_connection` field and force the state machine\r\n // back to Disconnected mid-session.\r\n if (this._connection !== conn) return;\r\n this._connection = undefined;\r\n // A live conn going away always drops us out of \"connected\",\r\n // regardless of where the state machine thought we were. The\r\n // `_setConnectionState(Disconnected)` below only flips\r\n // `_isConnected` off if we were coming from `Connected`, which\r\n // misses the case where the conn closes mid-handshake (state\r\n // still `Connecting`) or was set up via `connectWith` (state\r\n // still `Disconnected`).\r\n if (this._isConnected)\r\n this._disconnectedAt = Date.now(); // The outage starts here, not in _setConnectionState\r\n this._isConnected = false;\r\n // Mirrors RpcPeer.cs:524-526 — log disconnect with reason if present.\r\n if (ev.reason)\r\n infoLog?.log(`'${this.ref}': Disconnected: ${ev.reason} (code=${ev.code})`);\r\n else\r\n infoLog?.log(`'${this.ref}': Disconnected`);\r\n // Flipping state to Disconnected tears down the keep-alive watchdog.\r\n this._setConnectionState(RpcConnectionState.Disconnected);\r\n });\r\n\r\n void conn.whenConnected.then(() => {\r\n // The state transition to `Connected` happens after the handshake —\r\n // that's where keep-alive arming happens too.\r\n infoLog?.log(`'${this.ref}': WS open`);\r\n });\r\n }\r\n\r\n /**\r\n * The serialization format used by this peer for outgoing messages.\r\n * If not set explicitly, defaults to the resolver's default (JSON /\r\n * json5np). Subclasses can pre-assign in their constructor.\r\n */\r\n private _serializationFormat: RpcSerializationFormat | undefined;\r\n\r\n get serializationFormat(): RpcSerializationFormat {\r\n return this._serializationFormat ?? RpcSerializationFormatResolver.Default.defaultFormat;\r\n }\r\n\r\n set serializationFormat(value: RpcSerializationFormat) {\r\n this._serializationFormat = value;\r\n }\r\n\r\n call(\r\n method: string,\r\n args?: unknown[],\r\n options?: RpcCallOptions\r\n ): RpcOutboundCall {\r\n const callId = this.outboundCalls.nextId();\r\n const outboundCall = options?.outboundCallFactory\r\n ? options.outboundCallFactory(callId, method)\r\n : new RpcOutboundCall(callId, method, options?.remoteExecutionMode ?? 7);\r\n\r\n const envelope: RpcMessage = {\r\n Method: method,\r\n RelatedId: callId,\r\n CallType: options?.callTypeId ?? 0,\r\n };\r\n outboundCall.serializedWireData = this.serializationFormat.serializeMessage(\r\n envelope,\r\n args,\r\n this._connection?.encoder,\r\n this.hub.registry\r\n );\r\n outboundCall.timeouts = options?.timeouts;\r\n\r\n // Mirror .NET `RpcOutboundCall.SendAsync`: register the call up front,\r\n // then either send now (fast path) or wait for `Connected` and send.\r\n // No central \"pending\" queue — each call self-manages its wait.\r\n this.outboundCalls.register(outboundCall);\r\n if (this._isConnected) {\r\n outboundCall.sentAt = Date.now();\r\n this._sendWireData(outboundCall.serializedWireData);\r\n } else {\r\n // Defer: send when the peer transitions to `Connected`. If the\r\n // call's result is completed before that (cancel, peer close,\r\n // error), the registered handler skips the send.\r\n const stateHandler = (state: RpcConnectionState): void => {\r\n if (state !== RpcConnectionState.Connected) return;\r\n this.connectionStateChanged.remove(stateHandler);\r\n if (outboundCall.result.isCompleted) return;\r\n outboundCall.sentAt = Date.now();\r\n this._sendWireData(outboundCall.serializedWireData);\r\n };\r\n this.connectionStateChanged.add(stateHandler);\r\n // Detach the handler if the call is completed before we ever\r\n // reach `Connected` — prevents leaking the listener.\r\n outboundCall.result\r\n .then(() => this.connectionStateChanged.remove(stateHandler))\r\n .catch(() => this.connectionStateChanged.remove(stateHandler));\r\n }\r\n\r\n // Wire up caller-initiated cancellation → sends $sys.Cancel to remote peer\r\n const signal = options?.signal;\r\n if (signal !== undefined) {\r\n const onAbort = () => {\r\n if (this.outboundCalls.remove(callId) !== undefined) {\r\n outboundCall.result.reject(cancellationError('Call cancelled.'));\r\n outboundCall.onDisconnect();\r\n // Only send $sys.Cancel once the handshake has completed\r\n // (`_isConnected`). Sends are gated on `_isConnected`, so a\r\n // call aborted before then was never put on the wire — a\r\n // pre-handshake Cancel would corrupt the remote handshake\r\n // (C#'s null-Transport-until-handshake, RpcPeer.cs:29-52).\r\n if (this._isConnected && this._connection !== undefined)\r\n this.hub.systemCallSender.cancel(this._connection, this.serializationFormat, callId);\r\n }\r\n };\r\n signal.addEventListener('abort', onAbort, { once: true });\r\n // Clean up listener when the call completes normally\r\n outboundCall.result\r\n .then(() => signal.removeEventListener('abort', onAbort))\r\n .catch(() => signal.removeEventListener('abort', onAbort));\r\n }\r\n\r\n return outboundCall;\r\n }\r\n\r\n callNoWait(method: string, args?: unknown[]): void {\r\n if (!this._isConnected) return; // silently drop\r\n // Invariant: `_isConnected` is only true while `_connection` is set\r\n // (they both flip together in `_setConnectionState` / `conn.closed`).\r\n const conn = this._connection!;\r\n const envelope: RpcMessage = { Method: method, RelatedId: 0 };\r\n const wireData = this.serializationFormat.serializeMessage(\r\n envelope,\r\n args,\r\n conn.encoder,\r\n this.hub.registry\r\n );\r\n this._sendWireData(wireData);\r\n }\r\n\r\n /** Close the current WS connection without disposing the peer. The\r\n * `conn.closed` handler flips state to `Disconnected`; for\r\n * `RpcClientPeer` the run loop then iterates and reopens. The peer\r\n * stays in the hub and all client proxies bound to it remain valid. */\r\n disconnect(): void {\r\n this._connection?.close();\r\n }\r\n\r\n close(): void {\r\n // Mirrors RpcPeer.cs:397 — \"Stopping\".\r\n infoLog?.log(`'${this.ref}': Stopping`);\r\n // `_setConnectionState(Disconnected)` below will tear down the keep-alive\r\n // watchdog + send timer. Calls deferred via `whenConnected` self-detach\r\n // when their result rejects (see `call()`).\r\n if (this._maintainTimer !== undefined) {\r\n clearInterval(this._maintainTimer);\r\n this._maintainTimer = undefined;\r\n }\r\n this.remoteObjects.disconnectAll();\r\n this.sharedObjects.disconnectAll();\r\n this.outboundCalls.rejectAll(cancellationError('Peer closed.'));\r\n this.outboundCalls.invalidateAll();\r\n this.inboundCalls.clear();\r\n this._connection?.close();\r\n this.hub.peers.delete(this.ref);\r\n this._setConnectionState(RpcConnectionState.Disconnected);\r\n // Mirrors RpcPeer.cs:439 — \"Stopped\".\r\n infoLog?.log(`'${this.ref}': Stopped`);\r\n }\r\n\r\n /** Send pre-serialized wire data through the current connection. */\r\n protected _sendWireData(data: string | Uint8Array): void {\r\n if (this._connection === undefined) return;\r\n if (typeof data === 'string') {\r\n this._connection.send(data);\r\n } else {\r\n this._connection.sendBinary(data);\r\n }\r\n }\r\n\r\n /** Override in subclasses to handle the remote peer's handshake response. */\r\n protected _onHandshakeReceived(_handshake: RemoteHandshake): void {\r\n // Default: no-op. RpcClientPeer resolves a pending promise;\r\n // RpcServerPeer sends its own handshake back.\r\n }\r\n\r\n // The try covers dispatch too, not just deserialization: a handler that throws\r\n // here would otherwise escape into the transport's onmessage - dropping every\r\n // remaining message of the frame, or killing a Node process outright.\r\n private _handleMessage(received: RpcReceivedMessage): void {\r\n try {\r\n this._processMessage(received);\r\n }\r\n catch (e) {\r\n // Mirrors RpcPeer.cs:453 — \"Failed to process inbound message\".\r\n errorLog?.log(`'${this.ref}': Failed to process inbound message:`, received, e);\r\n }\r\n }\r\n\r\n private _processMessage(received: RpcReceivedMessage): void {\r\n let message: RpcMessage;\r\n let args: unknown[];\r\n if (received.kind === 'binary') {\r\n message = received.message;\r\n args = received.args;\r\n } else {\r\n const parsed = deserializeMessage(received.raw);\r\n message = parsed.message;\r\n args = parsed.args;\r\n }\r\n const method = message.Method ?? '';\r\n debugLog?.log(`'${this.ref}': <- ${method}#${message.RelatedId ?? 0}`);\r\n\r\n // Handshake — dispatch to subclass handler\r\n if (method === RpcSystemCalls.handshake) {\r\n const raw = args[0];\r\n if (raw !== undefined) {\r\n // .NET sends a positional array over MessagePack, but a\r\n // System.Text.Json object (Web defaults → camelCase keys) over the\r\n // text transport. Accept the array, PascalCase, and camelCase.\r\n const obj = (raw ?? {}) as Record<string, unknown>;\r\n const handshake: RemoteHandshake = Array.isArray(raw)\r\n ? {\r\n RemotePeerId: raw[0] as string | undefined,\r\n RemoteHubId: raw[2] as string | undefined,\r\n ProtocolVersion: raw[3] as number | undefined,\r\n Index: raw[4] as number | undefined,\r\n Secret: raw[5] as string | undefined,\r\n }\r\n : {\r\n RemotePeerId: (obj.RemotePeerId ?? obj.remotePeerId) as string | undefined,\r\n RemoteHubId: (obj.RemoteHubId ?? obj.remoteHubId) as string | undefined,\r\n ProtocolVersion: (obj.ProtocolVersion ?? obj.protocolVersion) as number | undefined,\r\n Index: (obj.Index ?? obj.index) as number | undefined,\r\n Secret: (obj.Secret ?? obj.secret) as string | undefined,\r\n };\r\n this._onHandshakeReceived(handshake);\r\n }\r\n return;\r\n }\r\n\r\n // Other system calls — delegate to hub's system call handler\r\n if (method.startsWith('$sys')) {\r\n this.hub.systemCallHandler.handle(message, args, this);\r\n return;\r\n }\r\n\r\n // Regular inbound call — dispatch to service host\r\n this._handleInbound(message, args);\r\n }\r\n\r\n private _handleInbound(message: RpcMessage, args: unknown[]): void {\r\n const method = message.Method ?? '';\r\n const relatedId = message.RelatedId ?? 0;\r\n const methodDef = this.hub.serviceHost.getMethodDef(method);\r\n const isNoWait = methodDef?.noWait === true;\r\n const serviceHost = this.hub.serviceHost;\r\n\r\n const dispatch = async (call: RpcInboundCall | undefined): Promise<void> => {\r\n try {\r\n const context =\r\n this._connection !== undefined\r\n ? {\r\n __rpcDispatch: true as const,\r\n callId: relatedId,\r\n callType: message.CallType ?? 0,\r\n connection: this._connection,\r\n signal: call?.signal,\r\n peer: this,\r\n }\r\n : undefined;\r\n const result = await serviceHost.dispatch(method, args, context);\r\n if (methodDef?.stream === true) {\r\n if (this._connection !== undefined) {\r\n // Stream method — wrap result in RpcStream if needed, then let\r\n // toRef() create the sender, register it, and start pumping.\r\n const stream = result instanceof RpcStream\r\n ? result as RpcStream<unknown>\r\n : new RpcStream<unknown>(result as AsyncIterable<unknown>);\r\n const ref = stream.toRef(this);\r\n const send = (): void => {\r\n // R17: a cancelled call sends no response, even though\r\n // it may still be registered for R9 dedup.\r\n if (call?.isCancelled === true) return;\r\n if (this._connection !== undefined)\r\n this.hub.systemCallSender.ok(\r\n this._connection, this.serializationFormat, relatedId, ref);\r\n };\r\n if (call !== undefined) {\r\n call.setResult(send);\r\n this.inboundCalls.markCompleted(call);\r\n }\r\n send();\r\n }\r\n } else if (call !== undefined) {\r\n const send = (): void => {\r\n if (call.isCancelled) return;\r\n if (this._connection !== undefined)\r\n this.hub.systemCallSender.ok(\r\n this._connection, this.serializationFormat, relatedId, result);\r\n };\r\n call.setResult(send);\r\n this.inboundCalls.markCompleted(call);\r\n send();\r\n }\r\n } catch (e) {\r\n if (call !== undefined) {\r\n const send = (): void => {\r\n if (call.isCancelled) return;\r\n if (this._connection !== undefined)\r\n this.hub.systemCallSender.error(\r\n this._connection, this.serializationFormat, relatedId, e);\r\n };\r\n call.setResult(send);\r\n this.inboundCalls.markCompleted(call);\r\n send();\r\n }\r\n debugLog?.log(`'${this.ref}': Inbound call failed: ${method}#${relatedId}`, e);\r\n }\r\n };\r\n\r\n // For noWait calls: don't track, don't respond.\r\n if (isNoWait) {\r\n void dispatch(undefined);\r\n return;\r\n }\r\n\r\n // Dedup resent frames: a known call id attaches to the existing dispatch\r\n // and re-sends its result once computed, instead of re-invoking the\r\n // handler. Mirrors .NET GetOrRegister + RpcInboundCall.TryReprocess.\r\n const call = new RpcInboundCall(relatedId, method, args);\r\n const existing = this.inboundCalls.getOrRegister(call);\r\n if (existing !== call) {\r\n existing.resendResult();\r\n return;\r\n }\r\n void dispatch(call);\r\n }\r\n\r\n /** Start sending `$sys.KeepAlive` pings and arm the silence watchdog.\r\n * Called when the peer transitions into `Connected` (after handshake).\r\n * Each inbound `$sys.KeepAlive` re-arms the watchdog; if it fires, the\r\n * WebSocket is force-closed so the normal disconnect path runs\r\n * (`conn.closed` → `connectionState = Disconnected` → UI).\r\n * Mirrors .NET `RpcObjectTrackers.Maintain`'s `KeepAliveTimeout` branch. */\r\n private _armKeepAliveWatchdog(): void {\r\n // Start the send timer once per connected session.\r\n this._keepAliveTimer ??= setInterval(() => {\r\n if (this._connection !== undefined) {\r\n // Send remote object IDs so the server's SharedObjectTracker keeps them alive.\r\n // Must NOT send outbound call IDs — those are a different ID namespace and would\r\n // cause the server to send $sys.Disconnect for IDs it doesn't recognize, which\r\n // the client may misinterpret as a disconnect of its own shared objects (e.g.\r\n // RpcStreamSender) when the IDs collide numerically.\r\n this.hub.systemCallSender.keepAlive(this._connection, this.serializationFormat, [\r\n ...this.remoteObjects.keys(),\r\n ]);\r\n }\r\n }, this.keepAlivePeriodMs);\r\n // (Re)arm the silence watchdog.\r\n if (this._keepAliveWatchdog !== undefined)\r\n clearTimeout(this._keepAliveWatchdog);\r\n this._keepAliveWatchdog = setTimeout(() => {\r\n this._keepAliveWatchdog = undefined;\r\n if (this._connection === undefined) return;\r\n warnLog?.log(\r\n `'${this.ref}': no keep-alive for ${this.keepAliveTimeoutMs}ms — force-closing connection`);\r\n this._connection.close();\r\n }, this.keepAliveTimeoutMs);\r\n }\r\n\r\n /** Stop sending keep-alives and clear the silence watchdog. Called when\r\n * the peer transitions into `Disconnected`. */\r\n private _disarmKeepAliveWatchdog(): void {\r\n if (this._keepAliveTimer !== undefined) {\r\n clearInterval(this._keepAliveTimer);\r\n this._keepAliveTimer = undefined;\r\n }\r\n if (this._keepAliveWatchdog !== undefined) {\r\n clearTimeout(this._keepAliveWatchdog);\r\n this._keepAliveWatchdog = undefined;\r\n }\r\n }\r\n\r\n /** Called by `RpcSystemCallHandler` when a `$sys.KeepAlive` message\r\n * arrives — records the timestamp and re-arms the silence watchdog. */\r\n notifyKeepAliveReceived(): void {\r\n this._lastKeepAliveAt = Date.now();\r\n if (this._keepAliveWatchdog !== undefined)\r\n this._armKeepAliveWatchdog();\r\n }\r\n}\r\n\r\n/** Connection state for RpcPeer. Mirrors .NET `RpcPeerConnectionState`'s\r\n * observable phases: the transport is `Connecting` until the WebSocket\r\n * opens, `Handshaking` from WS-open until the server's handshake reply\r\n * arrives, and `Connected` once the handshake completes. Outbound calls\r\n * are gated on `Connected`. */\r\nexport const enum RpcConnectionState {\r\n Disconnected = 0,\r\n Connecting = 1,\r\n Handshaking = 2,\r\n Connected = 3,\r\n}\r\n\r\n/** Outcome of comparing an incoming handshake with the previous one. Mirrors\r\n * .NET `RpcPeerChangeKind` — `ChangedToVeryFirst` lets the first handshake skip\r\n * reconnect processing, since there's no prior connection to reconcile with. */\r\nexport const enum RpcPeerChangeKind {\r\n Unchanged = 0,\r\n ChangedToVeryFirst = 1,\r\n Changed = 2,\r\n}\r\n\r\nconst peerChangeKindNames = ['Unchanged', 'ChangedToVeryFirst', 'Changed'];\r\n\r\n/** Client-side RPC peer — initiates WebSocket connection. ref = URL. */\r\nexport class RpcClientPeer extends RpcPeer {\r\n private _disposed = false;\r\n /** Aborted when the peer is stopped/disposed — threaded into the reconnect\r\n * delay so a pending delay rejects promptly instead of parking. */\r\n private readonly _stopController = new AbortController();\r\n private _tryIndex = 0;\r\n /** Index from the MOST RECENTLY RECEIVED server handshake — sent back\r\n * to the server in `$sys.Reconnect:3` so the server can verify we're\r\n * talking about the current connection. Mirrors .NET's\r\n * `connectionState.Handshake.Index` path in\r\n * `RpcOutboundCallTracker.TryReconnect`. */\r\n private _remoteHandshakeIndex = 0;\r\n /** Mirrors .NET's `lastHandshake` local in `RpcPeer.OnRun`: first-ness is keyed\r\n * off \"was there a previous handshake\", not off `RemotePeerId` — a legacy server\r\n * sends none, so every one of its handshakes would look like the first. */\r\n private _lastHandshake: RemoteHandshake | undefined;\r\n private _pendingHandshake: PromiseSource<RemoteHandshake> | undefined;\r\n private _reconnectsAt = 0;\r\n /** Server-issued reconnect secret, from the most recent handshake that\r\n * carried one. In-memory only — never localStorage/sessionStorage/cookie.\r\n * A tab reload mints a new `clientId`, so a persisted secret would be\r\n * useless anyway. */\r\n private _reconnectSecret: string | undefined;\r\n /** Incremented once per *connect attempt*, in `computeReconnectProof()`. A\r\n * failed attempt still burns its value: the server may have advanced its\r\n * own counter at the gate before the connection died, and reusing the\r\n * value would then be rejected forever. */\r\n private _reconnectCounter = 0;\r\n\r\n /** Base64url-encoded peer ID — matches .NET's RpcClientPeer.ClientId (Guid.ToBase64Url). */\r\n readonly clientId: string;\r\n /** Builds the WebSocket URL for this peer. Replace to customize URL construction. */\r\n connectionUrlResolver: RpcConnectionUrlResolver =\r\n defaultConnectionUrlResolver;\r\n /** Optional WebSocket factory. When set, `run()` uses this instead of the\r\n * global `new WebSocket(url)` constructor. Used by tests (to inject mock\r\n * sockets) and by the Node load-test harness (to attach `Session` /\r\n * `Cookie` headers to the WS upgrade, which the browser `WebSocket`\r\n * constructor can't do). Must be set before `start()`. */\r\n webSocketFactory: ((url: string) => WebSocketLike) | undefined;\r\n /** Max time to wait for the WebSocket to reach OPEN before aborting the\r\n * connection attempt. Initialized from `hub.limits` at construction;\r\n * set directly to override on a single peer. */\r\n connectTimeoutMs: number;\r\n /** Max time to wait for the server's handshake response before aborting\r\n * the connection attempt. Initialized from `hub.limits` at construction;\r\n * set directly to override on a single peer. */\r\n handshakeTimeoutMs: number;\r\n /** A connection that lived less than this before dropping does NOT reset\r\n * `_tryIndex`, so a crash-looping server sees growing reconnect delays.\r\n * Initialized from `hub.limits` at construction; override per peer. */\r\n prematureDisconnectTimeoutMs: number;\r\n readonly peerChanged = new EventHandlerSet<void>();\r\n /** Fired when the server rejects the connection due to an unsupported serialization format (close code 4010). */\r\n readonly unsupportedFormat = new EventHandlerSet<{ reason: string }>();\r\n readonly reconnectsAtChanged = new EventHandlerSet<void>();\r\n\r\n get reconnectsAt(): number {\r\n return this._reconnectsAt;\r\n }\r\n\r\n private _setReconnectsAt(value: number): void {\r\n if (this._reconnectsAt === value) return;\r\n this._reconnectsAt = value;\r\n this.reconnectsAtChanged.trigger();\r\n }\r\n\r\n /** The promise returned by the reconnect loop. `undefined` until\r\n * `start()` is invoked (which happens automatically when\r\n * `mustStart = true` is passed to the ctor). */\r\n whenRunning: Promise<void> | undefined;\r\n\r\n /**\r\n * @param hub RpcHub instance this peer will be tracked by\r\n * @param url Full WebSocket URL — the serialization format is taken from\r\n * the `f=` query parameter (e.g. `wss://host/rpc/ws?f=msgpack6`);\r\n * falls back to the resolver's default when absent. Use\r\n * {@link RpcRefBuilder.forClient} to compose URLs with a format.\r\n * @param mustStart Kick off the reconnect loop automatically at the end of\r\n * construction (default `true`). Pass `false` if you need to\r\n * tweak `reconnectDelayer`, `handshakeTimeoutMs`, or similar\r\n * options before the loop starts — then call `peer.start()`\r\n * manually.\r\n */\r\n constructor(hub: RpcHub, url: string, mustStart = true) {\r\n super(hub, url);\r\n this.connectTimeoutMs = hub.limits.connectTimeoutMs;\r\n this.handshakeTimeoutMs = hub.limits.handshakeTimeoutMs;\r\n this.prematureDisconnectTimeoutMs = hub.limits.prematureDisconnectTimeoutMs;\r\n this.clientId = guidToBase64Url(this.id);\r\n const resolver = RpcSerializationFormatResolver.Default;\r\n this.serializationFormat = resolver.get(parseFormatFromUrl(url) ?? resolver.defaultFormatKey);\r\n if (mustStart)\r\n this.start();\r\n }\r\n\r\n /** One-shot connection for tests — no reconnection loop, no handshake.\r\n * @param conn Rpc connection\r\n * @param isPeerChanged Whether to treat this as a peer-identity change\r\n * (default `true` — no handshake was exchanged, so stage-3 compute calls\r\n * self-invalidate and remote/shared objects are disposed). Pass `false`\r\n * to simulate a same-peer reconnect in tests. */\r\n connectWith(conn: RpcConnection, isPeerChanged = true): void {\r\n const sentCalls = this.outboundCalls.getSentCalls();\r\n this.setupConnection(conn);\r\n // Test-only: synthesize the \"ready for calls\" state without running\r\n // the handshake. Stays off the public state machine on purpose —\r\n // tests don't drive the run loop, and entering `Connected` via\r\n // `_setConnectionState` would arm the keep-alive watchdog against a\r\n // mock transport that never replies to keep-alives. We still need\r\n // to release any calls deferred via `whenConnected`, so we trigger\r\n // the listener set directly.\r\n this._isConnected = true;\r\n this.connectionStateChanged.trigger(RpcConnectionState.Connected);\r\n void this._reconnect(sentCalls, isPeerChanged);\r\n }\r\n\r\n /** Kick off the reconnect loop. Idempotent — subsequent calls are\r\n * no-ops; await `peer.whenRunning` if you need the loop's completion\r\n * promise. Customize `peer.webSocketFactory` first if you need a\r\n * non-default WebSocket constructor. */\r\n start(): void {\r\n void this.run();\r\n }\r\n\r\n /** Builds the `c` / `p` connect-URL parameters proving possession of the\r\n * server-issued reconnect secret, or `undefined` when there is no secret\r\n * yet or WebCrypto is unavailable. Consumes one counter value per proof\r\n * produced, so call it exactly once per connect attempt. */\r\n async computeReconnectProof(): Promise<RpcReconnectProofParameters | undefined> {\r\n const secret = this._reconnectSecret;\r\n if (secret === undefined)\r\n return undefined;\r\n\r\n // Degrading to the legacy no-proof URL keeps a client that can never prove - an insecure\r\n // context has no crypto.subtle - out of a permanent reconnect failure. It is not a way\r\n // back for a client that has already proven: the server refuses that downgrade, since it\r\n // is exactly how an attacker would strip the proof. crypto.subtle availability is a\r\n // property of the environment, not of the attempt, so a client is on one side or the other\r\n // for its whole life.\r\n if (!isReconnectProofSupported()) {\r\n warnReconnectProofUnavailable(this.ref, 'crypto.subtle is unavailable (insecure context?)');\r\n return undefined;\r\n }\r\n\r\n const counter = (this._reconnectCounter + 1).toString();\r\n try {\r\n const proof = await computeReconnectProof(secret, this.clientId, counter);\r\n this._reconnectCounter++; // Only a produced proof consumes counter space\r\n return { counter, proof };\r\n } catch (e) {\r\n // Anomalous: crypto.subtle exists but failed. The connect will be refused once this\r\n // peer has proven before, and the reconnect loop retries - which recovers if the\r\n // failure was transient.\r\n warnReconnectProofUnavailable(this.ref, `HMAC-SHA256 failed: ${String(e)}`);\r\n return undefined;\r\n }\r\n }\r\n\r\n /** The reconnect loop itself. Exposed as `protected` so subclasses (and\r\n * `start()`) can drive it; public callers should use {@link start}. */\r\n protected run(): Promise<void> {\r\n return this.whenRunning ??= this._runImpl();\r\n }\r\n\r\n private async _runImpl(): Promise<void> {\r\n if (this._disposed) return;\r\n // Mirrors RpcPeer.cs:230 — \"Started\".\r\n infoLog?.log(`'${this.ref}': Started (Client)`);\r\n try {\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- _disposed can change during await\r\n while (!this._disposed) {\r\n // Enter Connecting for this iteration — the reconnect delay\r\n // below (if any) is part of this state per the contract\r\n // \"connecting when the peer starts to reconnect\".\r\n this._setConnectionState(RpcConnectionState.Connecting);\r\n\r\n // Apply retry delay (no-op on first iteration where tryIndex === 0).\r\n // The ApiReconnectDelayer may park indefinitely here while\r\n // `Api.canConnect === false`.\r\n const retryDelay = this.hub.reconnectDelayer.getDelay(this._tryIndex, this._stopController.signal);\r\n if (retryDelay.isLimitExceeded) {\r\n warnLog?.log(`'${this.ref}': Reconnect limit exceeded - shutting down`);\r\n this._disposed = true;\r\n break;\r\n }\r\n this._setReconnectsAt(retryDelay.endsAt);\r\n try {\r\n await retryDelay.promise;\r\n } catch {\r\n // Stop signal (or an externally cancelled delay) — the\r\n // `_disposed` check below exits the loop cleanly.\r\n } finally {\r\n this._setReconnectsAt(0);\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- _disposed can change during await\r\n if (this._disposed) break;\r\n\r\n let lastCloseCode = 0;\r\n let lastCloseReason = '';\r\n // 0 until this iteration reaches `Connected`; used by the R13\r\n // premature-disconnect check below.\r\n let connectedAt = 0;\r\n try {\r\n const connUrl = await this.connectionUrlResolver(this);\r\n // Mirrors RpcClientPeer.cs:45 — \"Connecting...\".\r\n infoLog?.log(`'${this.ref}': Connecting to ${sanitizeUrl(connUrl)}`);\r\n const ws =\r\n this.webSocketFactory?.(connUrl) ??\r\n (new WebSocket(connUrl) as unknown as WebSocketLike);\r\n const conn = new RpcWebSocketConnection(\r\n ws,\r\n this.serializationFormat.isBinary,\r\n this.serializationFormat,\r\n this.hub.registry\r\n );\r\n\r\n // Eager \"connection closed\" PromiseSource — captured right\r\n // after connection creation so every subsequent await can\r\n // race against it without missing an already-fired close.\r\n // EventHandlerSet fires synchronously and does not replay\r\n // past triggers, so a late `conn.closed.add(...)` would\r\n // deadlock if the socket closed just before registration.\r\n const whenClosed = new PromiseSource<{ code: number; reason: string }>();\r\n const closedController = new AbortController();\r\n conn.closed.add(ev => {\r\n lastCloseCode = ev.code;\r\n lastCloseReason = ev.reason;\r\n whenClosed.resolve(ev);\r\n closedController.abort();\r\n });\r\n\r\n // Derived rejecting promise — used to unblock awaits that\r\n // expect a successful signal (whenConnected, handshake).\r\n const closedRejection = whenClosed.then<never>(() => {\r\n throw new Error('Connection failed');\r\n });\r\n closedRejection.catch(() => { /* noop — prevent unhandled rejection when conn closes normally */ });\r\n\r\n // Create a fresh handshake promise before setupConnection (which registers\r\n // the message handler). The handler can only fire after the WS opens,\r\n // and we send our handshake below, so timing is safe.\r\n this._pendingHandshake = new PromiseSource<RemoteHandshake>();\r\n this.setupConnection(conn);\r\n // `_connection` stays set throughout the handshake window so\r\n // `close()` can tear down the WS. Outbound calls are gated by\r\n // `_isConnected`, which only flips on when we enter `Connected`\r\n // below — before that, `call()` defers the send via the\r\n // per-call `whenConnected` listener.\r\n\r\n // Race connection open against close — if WS fails to connect,\r\n // whenConnected stays pending forever, so we must also watch for close.\r\n // The connect timeout caps how long we wait for the OPEN state: on a\r\n // hung connect (mobile after network change, half-open after sleep)\r\n // the browser may take ~2 min to emit onerror/onclose, which blocks\r\n // the reconnect loop and surfaces as \"can't reconnect\" to the user.\r\n const connectTimeoutMs = this.connectTimeoutMs;\r\n try {\r\n await withTimeout(\r\n Promise.race([conn.whenConnected, closedRejection]),\r\n connectTimeoutMs,\r\n 'Connect timeout');\r\n } catch (e) {\r\n if (e instanceof TimeoutError) {\r\n warnLog?.log(`'${this.ref}': Connect timed out after ${connectTimeoutMs}ms`);\r\n // Force-close the WS so the run loop iterates to retry-delay\r\n // instead of leaking a half-open socket. The browser onclose\r\n // may still take a moment but the loop no longer waits for it.\r\n conn.close();\r\n }\r\n throw e;\r\n }\r\n\r\n // WS open, handshake exchange about to start. Mirrors\r\n // .NET's `Handshaking` phase of `RpcPeerConnectionState`.\r\n this._setConnectionState(RpcConnectionState.Handshaking);\r\n\r\n // Send our handshake, then wait for the server's response.\r\n try {\r\n this.hub.systemCallSender.handshake(\r\n conn,\r\n this.serializationFormat,\r\n this.id,\r\n this.hub.hubId,\r\n this.nextOwnHandshakeIndex()\r\n );\r\n }\r\n catch (e) {\r\n // Mirrors RpcPeer.cs:305 — \"Failed to send handshake\".\r\n warnLog?.log(`'${this.ref}': Failed to send handshake`, e);\r\n throw e;\r\n }\r\n\r\n // Handshake timeout — mirrors .NET's `Hub.Limits.HandshakeTimeout`.\r\n // Without this, an accepted WS that never replies to $sys.Handshake\r\n // (half-open TCP, mid-restart server holding the socket briefly)\r\n // would block the run loop indefinitely, preventing reconnect.\r\n const handshakeTimeoutMs = this.handshakeTimeoutMs;\r\n let remoteHandshake: RemoteHandshake;\r\n try {\r\n remoteHandshake = await withTimeout(\r\n Promise.race([this._pendingHandshake, closedRejection]),\r\n handshakeTimeoutMs,\r\n 'Handshake timeout');\r\n } catch (e) {\r\n if (e instanceof TimeoutError) {\r\n warnLog?.log(`'${this.ref}': Handshake timed out after ${handshakeTimeoutMs}ms`);\r\n // Force-close the socket so the run loop continues to\r\n // the retry-delay branch instead of leaking the WS.\r\n conn.close();\r\n }\r\n throw e;\r\n } finally {\r\n this._pendingHandshake = undefined;\r\n }\r\n\r\n // Record the server's handshake index for $sys.Reconnect.\r\n this._remoteHandshakeIndex = remoteHandshake.Index ?? 0;\r\n // Overwrite unconditionally when a secret is present: a client\r\n // that missed one self-heals, and a client that reached a\r\n // different replica adopts that replica's secret. A legacy\r\n // server sends none, which leaves the stored value alone.\r\n if (remoteHandshake.Secret)\r\n this._reconnectSecret = remoteHandshake.Secret;\r\n\r\n // Peer change detection (like .NET's RpcHandshake.GetPeerChangeKind)\r\n const peerChangeKind = this._detectPeerChange(remoteHandshake);\r\n // Mirrors RpcPeer.cs:316 — \"Handshake succeeded\".\r\n infoLog?.log(\r\n `'${this.ref}': Handshake succeeded, peerChangeKind=${peerChangeKindNames[peerChangeKind]}`);\r\n\r\n // Activate the connection and re-send outbound calls.\r\n // All of this happens AFTER the handshake, so the server is ready to process calls.\r\n // Must be snapshotted before `Connected` — see `getSentCalls`.\r\n const sentCalls = this.outboundCalls.getSentCalls();\r\n this._setConnectionState(RpcConnectionState.Connected);\r\n connectedAt = Date.now();\r\n // No prior connection to reconcile with on the very first\r\n // handshake (RpcPeer.cs:421-424).\r\n // Pass the close signal so `_reconcileReconnect`'s inner\r\n // `$sys.Reconnect` call cancels cleanly if the socket dies\r\n // mid-reconcile — otherwise its pending result.promise would\r\n // deadlock the outer _reconnect() await.\r\n if (peerChangeKind !== RpcPeerChangeKind.ChangedToVeryFirst)\r\n await this._reconnect(\r\n sentCalls,\r\n peerChangeKind === RpcPeerChangeKind.Changed,\r\n closedController.signal);\r\n\r\n // Wait until disconnected — use the eager whenClosed promise\r\n // so we don't miss an already-fired close event.\r\n await whenClosed;\r\n } catch (e) {\r\n // connection failed or handshake failed\r\n debugLog?.log(`'${this.ref}': run() iteration failed:`, e);\r\n }\r\n\r\n // Normally redundant — the `conn.closed` handler in\r\n // `setupConnection` already fired `Disconnected`. Kept for\r\n // paths that throw before setupConnection runs.\r\n this._setConnectionState(RpcConnectionState.Disconnected);\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- _disposed can change during await\r\n if (this._disposed) break;\r\n\r\n // Server rejected our serialization format — stop reconnecting and notify listeners\r\n if (lastCloseCode === RPC_CLOSE_CODE_UNSUPPORTED_FORMAT) {\r\n // Mirrors RpcPeer.cs:518 — \"Can't (re)connect, will shut down\".\r\n warnLog?.log(`'${this.ref}': Unsupported serialization format - shutting down`);\r\n this.unsupportedFormat.trigger({ reason: lastCloseReason });\r\n this._disposed = true;\r\n break;\r\n }\r\n\r\n // R13: reset the reconnect backoff only when the connection\r\n // lived at least `prematureDisconnectTimeoutMs`. A premature\r\n // disconnect (e.g. a crash-looping server) leaves `_tryIndex`\r\n // growing so `RetryDelaySeq` backs off. Mirrors .NET's\r\n // PrematureDisconnect handling (RpcPeer.cs:402-405).\r\n if (connectedAt !== 0\r\n && Date.now() - connectedAt >= this.prematureDisconnectTimeoutMs)\r\n this._tryIndex = 0;\r\n\r\n this._tryIndex++;\r\n }\r\n } finally {\r\n this._setConnectionState(RpcConnectionState.Disconnected);\r\n }\r\n }\r\n\r\n /**\r\n * Reconcile the calls that were in flight on the connection that just died\r\n * (`sentCalls`), then resend the survivors. Calls still waiting for a\r\n * connection are deliberately NOT passed in — `AllowReconnect`/`AllowResend`\r\n * don't apply to them, and entering `Connected` already sends them.\r\n *\r\n * On peer change (different server hubId) or with binary wire format —\r\n * we blind-resend every eligible call.\r\n *\r\n * On a same-peer reconnect with JSON transport, first call\r\n * `$sys.Reconnect` to ask the server which call IDs it no longer\r\n * recognizes (e.g. inbound tracker was cleared, or the call never\r\n * reached the server). Only those get resent; the rest stay in the\r\n * tracker awaiting their $sys.Ok. This prevents duplicate server-side\r\n * invocation of in-flight streaming calls on every transient\r\n * reconnect — mirrors .NET `RpcOutboundCallTracker.Reconnect` at\r\n * src/ActualLab.Rpc/Infrastructure/RpcCallTrackers.cs:209-279.\r\n *\r\n * Stage-3 compute calls (result already resolved, awaiting invalidation)\r\n * are always self-invalidated — TS doesn't track compute-call stages\r\n * across reconnect, so the safest behavior is to force a fresh compute.\r\n */\r\n private async _reconnect(\r\n sentCalls: RpcOutboundCall[],\r\n isPeerChanged: boolean,\r\n closedSignal?: AbortSignal\r\n ): Promise<void> {\r\n if (this._connection === undefined) return;\r\n\r\n // Handle remote and shared objects on reconnect.\r\n // On peer change, the remote server is gone — both remote objects\r\n // (proxies registered here for server-owned objects) and shared objects\r\n // (e.g. RpcStreamSender instances this peer owns and is pushing to the\r\n // server) must be disposed: the new server has no corresponding entries,\r\n // so they would hang forever waiting for ACKs that never arrive.\r\n // Mirrors .NET RpcPeer.Reset (src/ActualLab.Rpc/RpcPeer.cs:430-440).\r\n if (isPeerChanged) {\r\n this.remoteObjects.disconnectAll();\r\n this.sharedObjects.disconnectAll();\r\n } else {\r\n this.remoteObjects.reconnectAll();\r\n }\r\n\r\n // Filter calls by RemoteExecutionMode (same logic as before).\r\n const eligible: RpcOutboundCall[] = [];\r\n for (const call of sentCalls) {\r\n const mode = call.remoteExecutionMode;\r\n if (!(mode & 2)) {\r\n if (!call.result.isCompleted)\r\n call.result.reject(new Error(\r\n 'Outbound call failed: disconnected and AllowReconnect is not set.'));\r\n call.onDisconnect();\r\n this.outboundCalls.remove(call.callId);\r\n continue;\r\n }\r\n if (isPeerChanged && !(mode & 4)) {\r\n if (!call.result.isCompleted)\r\n call.result.reject(new Error(\r\n 'Outbound call failed: reconnected to a different peer and AllowResend is not set.'));\r\n call.onDisconnect();\r\n this.outboundCalls.remove(call.callId);\r\n continue;\r\n }\r\n eligible.push(call);\r\n // Restart the run-timeout clock — mirrors .NET's StartedAt reset in\r\n // GetReconnectStage (RpcOutboundCall.cs:343). Without it, a call kept\r\n // (server still knows it) or resent after a long outage would be\r\n // timed out from its stale pre-disconnect send time.\r\n call.sentAt = Date.now();\r\n }\r\n\r\n // Reconcile with the server on same-peer reconnects. On peer change\r\n // we skip the round-trip (the new peer knows nothing) and blind-resend.\r\n //\r\n // Wire format: the `completedStages` dict uses plain JS-object shape\r\n // with string keys and base64-encoded `Uint8Array` values. This is\r\n // JSON-compatible with .NET's `System.Text.Json` serialization of\r\n // `Dictionary<int, byte[]>` and also works for TS-to-TS msgpack\r\n // (serializes as map<str, str>). Full .NET-msgpack interop for\r\n // $sys.Reconnect — which uses `map<int, bin>` — is a future\r\n // enhancement; the JSON path covers ActualChat's primary use case.\r\n let unknownIds: Set<number> | null = null;\r\n if (!isPeerChanged && eligible.length > 0) {\r\n unknownIds = await this._reconcileReconnect(eligible, closedSignal);\r\n }\r\n\r\n // Yield-batched resend. Eligible can be hundreds after a long\r\n // disconnect; chunking prevents a sync spike on the main thread.\r\n const YIELD_BATCH = 20;\r\n let resentSinceYield = 0;\r\n for (const call of eligible) {\r\n if (!call.removeOnOk && call.result.isCompleted) {\r\n // Stage-3 compute call: always self-invalidate to force a\r\n // fresh recompute — TS has no cross-reconnect compute-state\r\n // tracking (unlike .NET's Reliable reconnection).\r\n call.onDisconnect();\r\n this.outboundCalls.remove(call.callId);\r\n continue;\r\n }\r\n if (unknownIds !== null && !unknownIds.has(call.callId)) {\r\n // Server reports this call is still in flight on its side —\r\n // skip the resend. The pending result promise continues to\r\n // await the original call's $sys.Ok.\r\n continue;\r\n }\r\n call.sentAt = Date.now();\r\n this._sendWireData(call.serializedWireData);\r\n resentSinceYield++;\r\n if (resentSinceYield >= YIELD_BATCH) {\r\n resentSinceYield = 0;\r\n await yieldToEventLoop();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Ask the server which call IDs in `eligible` it no longer recognizes\r\n * via `$sys.Reconnect:3`. Returns the set of call IDs that need to be\r\n * resent. On any failure, falls back to \"resend everything\".\r\n *\r\n * Wire format of the `completedStages` argument is chosen per wire format:\r\n * - JSON (json5np): plain object `{\"<stage>\": \"<base64-of-bytes>\"}` —\r\n * matches .NET `System.Text.Json`'s default for `Dictionary<int, byte[]>`.\r\n * - MessagePack (msgpack6/msgpack6c): `Map<number, Uint8Array>` —\r\n * the patched encoder (see `./msgpack-map-patch.ts`) writes this as a\r\n * native msgpack `map<int, bin>`, matching .NET MessagePack-CSharp's\r\n * default for `Dictionary<int, byte[]>`.\r\n */\r\n private async _reconcileReconnect(eligible: RpcOutboundCall[], closedSignal?: AbortSignal): Promise<Set<number>> {\r\n try {\r\n // Group by completedStage, sort IDs, compress per-stage.\r\n const byStage = new Map<number, number[]>();\r\n for (const call of eligible) {\r\n const stage = call.getReconnectStage(false);\r\n if (stage === null) continue;\r\n let arr = byStage.get(stage);\r\n if (!arr) { arr = []; byStage.set(stage, arr); }\r\n arr.push(call.callId);\r\n }\r\n if (byStage.size === 0) {\r\n // Nothing to reconcile — fall back to resend-all.\r\n return new Set(eligible.map(c => c.callId));\r\n }\r\n\r\n const isBinary = this.serializationFormat.isBinary;\r\n const stagesArg: Map<number, Uint8Array> | Record<string, string> = isBinary\r\n ? new Map<number, Uint8Array>()\r\n : {};\r\n for (const [stage, ids] of byStage) {\r\n ids.sort((a, b) => a - b);\r\n const bytes = IncreasingSeqCompressor.serialize(ids);\r\n if (isBinary) {\r\n (stagesArg as Map<number, Uint8Array>).set(stage, bytes);\r\n } else {\r\n (stagesArg as Record<string, string>)[String(stage)] = base64Encode(bytes);\r\n }\r\n }\r\n\r\n // Send $sys.Reconnect via the regular call path so the server's\r\n // $sys.Ok reply resolves a PromiseSource we can await. The\r\n // first arg is the server's OWN handshake index (captured from\r\n // the remote handshake we just received), not ours — this\r\n // matches .NET's protocol, where the server validates\r\n // `ownHandshake.Index == handshakeIndex`.\r\n const reconnectCall = this.call(\r\n RpcSystemCalls.reconnect,\r\n [this._remoteHandshakeIndex, stagesArg],\r\n {\r\n remoteExecutionMode: 1, /* AwaitForConnection only; must not itself reconnect */\r\n signal: closedSignal,\r\n },\r\n );\r\n const result = await reconnectCall.result;\r\n const bytes = _toBytes(result);\r\n if (bytes === null) return new Set(eligible.map(c => c.callId));\r\n return new Set(IncreasingSeqCompressor.deserialize(bytes));\r\n } catch {\r\n // Reconciliation failed — resend everything.\r\n return new Set(eligible.map(c => c.callId));\r\n }\r\n }\r\n\r\n /** Detects a server-peer identity change by comparing the incoming\r\n * handshake's `RemotePeerId` with the last one — C#\r\n * `RpcHandshake.GetPeerChangeKind` keys off `RemotePeerId`, not\r\n * `RemoteHubId` (an idle server peer can be replaced while keeping the\r\n * same hub id). On a change it clears the inbound tracker and fires\r\n * `peerChanged`; the very first handshake is `ChangedToVeryFirst`. */\r\n private _detectPeerChange(handshake: RemoteHandshake): RpcPeerChangeKind {\r\n const lastHandshake = this._lastHandshake;\r\n this._lastHandshake = handshake;\r\n if (lastHandshake === undefined)\r\n return RpcPeerChangeKind.ChangedToVeryFirst;\r\n\r\n if (handshake.RemotePeerId === lastHandshake.RemotePeerId)\r\n return RpcPeerChangeKind.Unchanged;\r\n\r\n this.inboundCalls.clear();\r\n // Mirrors RpcPeer.cs:439 (Peer changed branch).\r\n infoLog?.log(`'${this.ref}': Peer changed`);\r\n this.peerChanged.trigger();\r\n return RpcPeerChangeKind.Changed;\r\n }\r\n\r\n protected override _onHandshakeReceived(handshake: RemoteHandshake): void {\r\n this._pendingHandshake?.resolve(handshake);\r\n }\r\n\r\n override close(): void {\r\n this._disposed = true;\r\n this._stopController.abort();\r\n super.close();\r\n }\r\n}\r\n\r\n/** Server-side RPC peer — wraps an accepted connection. ref = \"server://{uuid}\". */\r\nexport class RpcServerPeer extends RpcPeer {\r\n /** Grace period before an idle (disconnected) server peer is stopped and\r\n * removed from `hub.peers`; a re-`accept()` within it cancels removal.\r\n * Snapshotted from `hub.limits` at construction (F11). */\r\n serverPeerCloseTimeoutMs: number;\r\n private _closeTimer: ReturnType<typeof setTimeout> | undefined;\r\n private _isClosed = false;\r\n\r\n constructor(hub: RpcHub, ref: string) {\r\n super(hub, ref);\r\n this.serverPeerCloseTimeoutMs = hub.limits.serverPeerCloseTimeoutMs;\r\n }\r\n\r\n /** Accept an incoming connection — sets up message handling and handshake response. */\r\n accept(conn: RpcConnection): void {\r\n // A re-accept within the close window keeps this peer (and its trackers)\r\n // alive for a same-peer reconnect — cancel any pending auto-removal.\r\n this._clearCloseTimer();\r\n this._setConnectionState(RpcConnectionState.Connecting);\r\n this.setupConnection(conn);\r\n // Server starts in Handshaking immediately on accept — there's no\r\n // separate \"WS open\" phase since the WS is already up by the time\r\n // we accept. Stay in Handshaking until the client's handshake\r\n // arrives in `_onHandshakeReceived` below.\r\n this._setConnectionState(RpcConnectionState.Handshaking);\r\n // When the client connection drops, clean up shared objects (stream senders).\r\n // Server peers don't reconnect — once the connection is gone, senders must be\r\n // terminated so their source iterators release resources promptly.\r\n conn.closed.add(() => {\r\n // Bail if this conn has already been replaced by a subsequent accept().\r\n // Without this guard, a late-firing close from the old socket would\r\n // disconnect shared objects that belong to the live session.\r\n if (this._connection !== conn && this._connection !== undefined) return;\r\n this.sharedObjects.disconnectAll();\r\n this._armCloseTimer();\r\n });\r\n }\r\n\r\n override close(): void {\r\n this._isClosed = true;\r\n this._clearCloseTimer();\r\n super.close();\r\n }\r\n\r\n private _armCloseTimer(): void {\r\n this._clearCloseTimer();\r\n // `close()` on a connected peer triggers `conn.closed` (possibly after\r\n // `close()` returns on async transports), which lands here — never\r\n // re-arm on a closed peer, or the stale timer's `close()` would later\r\n // evict a successor peer registered under the same ref from `hub.peers`.\r\n if (this._isClosed) return;\r\n const timeout = this.serverPeerCloseTimeoutMs;\r\n if (!(timeout > 0) || !Number.isFinite(timeout)) return;\r\n this._closeTimer = setTimeout(() => {\r\n this._closeTimer = undefined;\r\n // Not re-accepted within the window — stop and remove from hub.peers.\r\n if (this._connection === undefined)\r\n this.close();\r\n }, timeout);\r\n (this._closeTimer as { unref?: () => void }).unref?.();\r\n }\r\n\r\n private _clearCloseTimer(): void {\r\n if (this._closeTimer !== undefined) {\r\n clearTimeout(this._closeTimer);\r\n this._closeTimer = undefined;\r\n }\r\n }\r\n\r\n protected override _onHandshakeReceived(_handshake: RemoteHandshake): void {\r\n // Client sent its handshake → respond with our own and transition to Connected.\r\n if (this._connection !== undefined) {\r\n // Bump our own handshake index each generation so a stale\r\n // $sys.Reconnect referencing an older one is rejected (R16).\r\n this.hub.systemCallSender.handshake(\r\n this._connection,\r\n this.serializationFormat,\r\n this.id,\r\n this.hub.hubId,\r\n this.nextOwnHandshakeIndex()\r\n );\r\n this._setConnectionState(RpcConnectionState.Connected);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Convert a UUID string to base64url — matches .NET's Guid.ToBase64Url().\r\n * .NET Guid stores the first 3 groups in little-endian byte order.\r\n */\r\nfunction guidToBase64Url(uuid: string): string {\r\n const hex = uuid.replace(/-/g, '');\r\n const bytes = new Uint8Array(16);\r\n\r\n // Group 1 (bytes 0-3): little-endian\r\n bytes[0] = parseInt(hex.slice(6, 8), 16);\r\n bytes[1] = parseInt(hex.slice(4, 6), 16);\r\n bytes[2] = parseInt(hex.slice(2, 4), 16);\r\n bytes[3] = parseInt(hex.slice(0, 2), 16);\r\n // Group 2 (bytes 4-5): little-endian\r\n bytes[4] = parseInt(hex.slice(10, 12), 16);\r\n bytes[5] = parseInt(hex.slice(8, 10), 16);\r\n // Group 3 (bytes 6-7): little-endian\r\n bytes[6] = parseInt(hex.slice(14, 16), 16);\r\n bytes[7] = parseInt(hex.slice(12, 14), 16);\r\n // Groups 4-5 (bytes 8-15): big-endian\r\n for (let i = 8; i < 16; i++)\r\n bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\r\n\r\n return base64UrlEncode(bytes);\r\n}\r\n\r\n// Warned at most once per process: the condition is a property of the host, so\r\n// it would otherwise repeat on every connect attempt, forever.\r\nlet isReconnectProofUnavailableWarned = false;\r\n\r\nfunction warnReconnectProofUnavailable(ref: string, reason: string): void {\r\n if (isReconnectProofUnavailableWarned)\r\n return;\r\n\r\n isReconnectProofUnavailableWarned = true;\r\n warnLog?.log(\r\n `'${ref}': ${reason} - connecting without a reconnect proof. `\r\n + 'This fails if the server requires one (RequireReconnectProof).');\r\n}\r\n\r\n/** Coerce a `$sys.Ok` result carrying a byte[] into a Uint8Array. */\r\nfunction _toBytes(value: unknown): Uint8Array | null {\r\n if (value instanceof Uint8Array) return value;\r\n if (typeof value === 'string') {\r\n try { return base64Decode(value); } catch { return null; }\r\n }\r\n return null;\r\n}\r\n","// RPC serialization format definitions — mirrors .NET RpcSerializationFormat.\r\n//\r\n// Each format knows how to serialize/deserialize RPC messages on the wire.\r\n// Format instances are immutable singletons — state like method registries\r\n// is passed in via parameters, not held by the format.\r\n\r\nimport { Encoder, Decoder } from '@msgpack/msgpack';\r\nimport type { RpcMessage } from './rpc-message.js';\r\nimport {\r\n serializeMessage,\r\n deserializeMessage,\r\n serializeBinaryMessage,\r\n splitBinaryFrame,\r\n serializeBinaryFrame,\r\n createBinaryEncoder,\r\n serializeCompactBinaryMessage,\r\n splitCompactBinaryFrame,\r\n} from './rpc-serialization.js';\r\nimport type { RpcMethodRegistry } from './rpc-method-registry.js';\r\n\r\n/** Parsed inbound message — result of deserialization. */\r\nexport interface RpcDeserializedMessage {\r\n message: RpcMessage;\r\n args: unknown[];\r\n}\r\n\r\n/** Wire data produced by serialization — either text or binary. */\r\nexport type RpcWireData = string | Uint8Array;\r\n\r\n/**\r\n * Base class for RPC serialization formats.\r\n * Instances are immutable singletons — one per format key.\r\n * State (like method registries) is passed via parameters.\r\n */\r\nexport abstract class RpcSerializationFormat {\r\n abstract readonly key: string;\r\n abstract readonly isBinary: boolean;\r\n /** Whether this format uses compact method hashes (needs RpcMethodRegistry). */\r\n abstract readonly isCompact: boolean;\r\n\r\n /** Serialize an outbound message. */\r\n abstract serializeMessage(\r\n message: RpcMessage,\r\n args?: unknown[],\r\n encoder?: Encoder,\r\n registry?: RpcMethodRegistry\r\n ): RpcWireData;\r\n\r\n /** Deserialize a single text message (only called for text formats). */\r\n abstract deserializeTextMessage(raw: string): RpcDeserializedMessage;\r\n\r\n /** Split + deserialize a binary frame (only called for binary formats). */\r\n abstract splitBinaryFrame(\r\n frame: Uint8Array,\r\n decoder?: Decoder,\r\n registry?: RpcMethodRegistry\r\n ): RpcDeserializedMessage[];\r\n\r\n /** Concatenate multiple serialized binary messages into one frame. */\r\n abstract serializeBinaryFrame(messages: Uint8Array[]): Uint8Array;\r\n\r\n /** Create a reusable binary encoder for this format. Returns undefined for text formats. */\r\n createEncoder(): Encoder | undefined {\r\n return undefined;\r\n }\r\n\r\n /** Create a reusable binary decoder for this format. Returns undefined for text formats. */\r\n createDecoder(): Decoder | undefined {\r\n return undefined;\r\n }\r\n\r\n // Named format instances — initialized in initFormats() below (after subclasses are defined)\r\n static SystemJsonV5: RpcSerializationFormat;\r\n static SystemJsonV5NP: RpcSerializationFormat;\r\n static NewtonsoftJsonV5: RpcSerializationFormat;\r\n static NewtonsoftJsonV5NP: RpcSerializationFormat;\r\n static MessagePackV6: RpcSerializationFormat;\r\n static MessagePackV6C: RpcSerializationFormat;\r\n static All: readonly RpcSerializationFormat[];\r\n\r\n /** Convenience shortcut for RpcSerializationFormatResolver.Default.get(key). */\r\n static get(key: string): RpcSerializationFormat {\r\n return RpcSerializationFormatResolver.Default.get(key);\r\n }\r\n\r\n /** Convenience shortcut for RpcSerializationFormatResolver.Default.tryGet(key). */\r\n static tryGet(key: string): RpcSerializationFormat | undefined {\r\n return RpcSerializationFormatResolver.Default.tryGet(key);\r\n }\r\n}\r\n\r\n// ============================================================\r\n// Concrete formats (immutable singletons)\r\n// ============================================================\r\n\r\n/**\r\n * JSON text format — \"json5np\" (System.Text.Json, no polymorphism).\r\n */\r\nexport class RpcJsonSerializationFormat extends RpcSerializationFormat {\r\n readonly key: string;\r\n readonly isBinary = false;\r\n readonly isCompact = false;\r\n\r\n constructor(key: string) {\r\n super();\r\n this.key = key;\r\n }\r\n\r\n serializeMessage(message: RpcMessage, args?: unknown[]): string {\r\n return serializeMessage(message, args);\r\n }\r\n\r\n deserializeTextMessage(raw: string): RpcDeserializedMessage {\r\n return deserializeMessage(raw);\r\n }\r\n\r\n splitBinaryFrame(): RpcDeserializedMessage[] {\r\n throw new Error('Text format does not support binary frames');\r\n }\r\n\r\n serializeBinaryFrame(): Uint8Array {\r\n throw new Error('Text format does not support binary frames');\r\n }\r\n}\r\n\r\n/**\r\n * MessagePack binary format — \"msgpack6\" (V5 wire format).\r\n */\r\nexport class RpcMessagePackSerializationFormat extends RpcSerializationFormat {\r\n readonly key: string;\r\n readonly isBinary = true;\r\n readonly isCompact = false;\r\n\r\n constructor(key: string) {\r\n super();\r\n this.key = key;\r\n }\r\n\r\n serializeMessage(\r\n message: RpcMessage,\r\n args?: unknown[],\r\n encoder?: Encoder\r\n ): Uint8Array {\r\n return serializeBinaryMessage(message, args, encoder);\r\n }\r\n\r\n deserializeTextMessage(raw: string): RpcDeserializedMessage {\r\n return deserializeMessage(raw);\r\n }\r\n\r\n splitBinaryFrame(\r\n frame: Uint8Array,\r\n decoder?: Decoder\r\n ): RpcDeserializedMessage[] {\r\n return splitBinaryFrame(frame, decoder);\r\n }\r\n\r\n serializeBinaryFrame(messages: Uint8Array[]): Uint8Array {\r\n return serializeBinaryFrame(messages);\r\n }\r\n\r\n override createEncoder(): Encoder {\r\n return createBinaryEncoder();\r\n }\r\n\r\n override createDecoder(): Decoder {\r\n return new Decoder();\r\n }\r\n}\r\n\r\n/**\r\n * MessagePack compact binary format — \"msgpack6c\" (V5Compact).\r\n * Uses 4-byte method hash instead of full method name.\r\n * Registry is passed via parameters — format instance is immutable.\r\n */\r\nexport class RpcMessagePackCompactSerializationFormat extends RpcSerializationFormat {\r\n readonly key: string;\r\n readonly isBinary = true;\r\n readonly isCompact = true;\r\n\r\n constructor(key: string) {\r\n super();\r\n this.key = key;\r\n }\r\n\r\n serializeMessage(\r\n message: RpcMessage,\r\n args?: unknown[],\r\n encoder?: Encoder,\r\n registry?: RpcMethodRegistry\r\n ): Uint8Array {\r\n return serializeCompactBinaryMessage(\r\n message,\r\n args,\r\n registry,\r\n encoder\r\n );\r\n }\r\n\r\n deserializeTextMessage(raw: string): RpcDeserializedMessage {\r\n return deserializeMessage(raw);\r\n }\r\n\r\n splitBinaryFrame(\r\n frame: Uint8Array,\r\n decoder?: Decoder,\r\n registry?: RpcMethodRegistry\r\n ): RpcDeserializedMessage[] {\r\n return splitCompactBinaryFrame(frame, registry, decoder);\r\n }\r\n\r\n serializeBinaryFrame(messages: Uint8Array[]): Uint8Array {\r\n return serializeBinaryFrame(messages);\r\n }\r\n\r\n override createEncoder(): Encoder {\r\n return createBinaryEncoder();\r\n }\r\n\r\n override createDecoder(): Decoder {\r\n return new Decoder();\r\n }\r\n}\r\n\r\n// ============================================================\r\n// Initialize named format instances (after subclasses are defined)\r\n// ============================================================\r\n\r\n// Text: System.Text.Json\r\nRpcSerializationFormat.SystemJsonV5 = new RpcJsonSerializationFormat('json5');\r\nRpcSerializationFormat.SystemJsonV5NP = new RpcJsonSerializationFormat('json5np');\r\n// Text: Newtonsoft.Json\r\nRpcSerializationFormat.NewtonsoftJsonV5 = new RpcJsonSerializationFormat('njson5');\r\nRpcSerializationFormat.NewtonsoftJsonV5NP = new RpcJsonSerializationFormat('njson5np');\r\n// Binary: MessagePack (non-compact)\r\nRpcSerializationFormat.MessagePackV6 = new RpcMessagePackSerializationFormat('msgpack6');\r\n// Binary: MessagePack (compact)\r\nRpcSerializationFormat.MessagePackV6C = new RpcMessagePackCompactSerializationFormat('msgpack6c');\r\n\r\n// The mempack6* formats are intentionally NOT registered: .NET's mempack6 uses\r\n// MemoryPack argument serialization (a different byte format), which the TS\r\n// client does not implement. Resolving one throws via isMemoryPackKey below.\r\nRpcSerializationFormat.All = [\r\n RpcSerializationFormat.SystemJsonV5,\r\n RpcSerializationFormat.SystemJsonV5NP,\r\n RpcSerializationFormat.NewtonsoftJsonV5,\r\n RpcSerializationFormat.NewtonsoftJsonV5NP,\r\n RpcSerializationFormat.MessagePackV6,\r\n RpcSerializationFormat.MessagePackV6C,\r\n];\r\n\r\n// ============================================================\r\n// RpcSerializationFormatResolver — mirrors .NET RpcSerializationFormatResolver\r\n// ============================================================\r\n\r\n/**\r\n * Resolves RpcSerializationFormat instances by key, with a configurable default.\r\n * Mirrors .NET RpcSerializationFormatResolver.\r\n */\r\nexport class RpcSerializationFormatResolver {\r\n static DefaultFormats: readonly RpcSerializationFormat[] = RpcSerializationFormat.All;\r\n static Default = new RpcSerializationFormatResolver('json5np');\r\n\r\n readonly defaultFormatKey: string;\r\n readonly formats: ReadonlyMap<string, RpcSerializationFormat>;\r\n\r\n get defaultFormat(): RpcSerializationFormat {\r\n return this.get(this.defaultFormatKey);\r\n }\r\n\r\n constructor(defaultFormatKey: string, formats?: Iterable<RpcSerializationFormat>) {\r\n const map = new Map<string, RpcSerializationFormat>();\r\n for (const f of formats ?? RpcSerializationFormatResolver.DefaultFormats)\r\n map.set(f.key, f);\r\n\r\n if (!map.has(defaultFormatKey))\r\n throw new Error(`No format with key '${defaultFormatKey}'.`);\r\n\r\n this.defaultFormatKey = defaultFormatKey;\r\n this.formats = map;\r\n }\r\n\r\n get(key: string): RpcSerializationFormat {\r\n if (isMemoryPackKey(key))\r\n throw new Error(`Serialization format '${key}': MemoryPack formats are not supported by the TS client.`);\r\n\r\n const format = this.tryGet(key);\r\n if (!format)\r\n throw new Error(`No format with key '${key}'.`);\r\n return format;\r\n }\r\n\r\n tryGet(key: string): RpcSerializationFormat | undefined {\r\n if (!key) key = this.defaultFormatKey;\r\n if (isMemoryPackKey(key))\r\n throw new Error(`Serialization format '${key}': MemoryPack formats are not supported by the TS client.`);\r\n return this.formats.get(key);\r\n }\r\n}\r\n\r\n/** True for .NET MemoryPack format keys (mempack5, mempack6, mempack6c, …). */\r\nfunction isMemoryPackKey(key: string): boolean {\r\n return key.startsWith('mempack');\r\n}\r\n","// Client half of the reconnect proof-of-possession protocol. The server mints a\r\n// per-peer secret and delivers it inside its `$sys.Handshake`; every subsequent\r\n// connect attempt carries a counter (`c`) plus an HMAC over it (`p`), so a leaked\r\n// `clientId` alone can no longer evict or hijack a live peer.\r\n//\r\n// .NET counterpart: ActualLab.Rpc.Internal.RpcReconnectProof. The two MUST agree\r\n// byte for byte, so both sides are pinned by the same test vector:\r\n// key = UTF8(secret) -- the secret is an opaque\r\n// token, NOT base64url-decoded\r\n// message = UTF8(clientId + \"\\n\" + counterText) -- \"\\n\" is a single 0x0A\r\n// proof = Base64Url_NoPad(HMAC_SHA256(key, message)) -- 32 bytes -> 43 chars\r\n\r\nimport { base64UrlEncode } from './base64.js';\r\n\r\nconst textEncoder = new TextEncoder();\r\n\r\n/** The `c` / `p` query parameter pair for one connect attempt. */\r\nexport interface RpcReconnectProofParameters {\r\n counter: string;\r\n proof: string;\r\n}\r\n\r\n// `crypto.subtle` is undefined in an insecure browser context (plain http on a\r\n// non-localhost host). A client there must connect without a proof rather than\r\n// fail outright, so callers gate on this instead of catching a TypeError.\r\nexport function isReconnectProofSupported(): boolean {\r\n return typeof crypto !== 'undefined' && (crypto.subtle as SubtleCrypto | undefined) !== undefined;\r\n}\r\n\r\n// `counterText` must be the decimal counter exactly as it will appear in the URL:\r\n// the server HMACs the `c` value string as it arrived, without reparsing it.\r\nexport async function computeReconnectProof(\r\n secret: string, clientId: string, counterText: string\r\n): Promise<string> {\r\n const key = await crypto.subtle.importKey(\r\n 'raw', textEncoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);\r\n const signature = await crypto.subtle.sign(\r\n 'HMAC', key, textEncoder.encode(`${clientId}\\n${counterText}`));\r\n return base64UrlEncode(new Uint8Array(signature));\r\n}\r\n","/**\r\n * Configurable timing limits for RPC connection lifecycle. Mirrors .NET's\r\n * `RpcLimits` (src/ActualLab.Rpc/Configuration/RpcLimits.cs).\r\n *\r\n * Override paths, from broadest to narrowest:\r\n * - **Process-wide**: mutate fields on {@link RpcLimits.Default} (affects\r\n * every hub that hasn't been given its own instance — hubs default to\r\n * using the shared `Default`, so mutations propagate live), or replace\r\n * `RpcLimits.Default = new RpcLimits({ … })` (affects hubs constructed\r\n * *after* the replacement).\r\n * - **Per-hub**: assign `hub.limits = new RpcLimits({ … })` before peers\r\n * are created from that hub.\r\n * - **Per-peer**: set the matching `*Ms` field on the peer directly\r\n * (e.g. `peer.keepAliveTimeoutMs = 60_000`) before `start()`.\r\n *\r\n * Peers snapshot the relevant values from `hub.limits` at construction —\r\n * later mutations to `hub.limits` don't retroactively affect already-built\r\n * peers; that's by design and matches the .NET semantics.\r\n */\r\nexport class RpcLimits {\r\n /** Process-wide default. Hubs read from this when they're constructed\r\n * unless caller assigns a custom instance to `hub.limits`. */\r\n static Default: RpcLimits = new RpcLimits();\r\n\r\n /** Dev preset with a relaxed keep-alive timeout (5 min), matching .NET's\r\n * debugger-attached `KeepAlivePeriod` (RpcLimits.cs:46-54). Activate\r\n * explicitly — e.g. `RpcLimits.Default = RpcLimits.Debug` or per-hub\r\n * `hub.limits = RpcLimits.Debug`. Never switched on automatically: the\r\n * TS side can't detect a debugger on the server (decision D4). */\r\n static readonly Debug: RpcLimits = new RpcLimits({ keepAliveTimeoutMs: 300_000 });\r\n\r\n /** Max time to wait for the WebSocket to enter the OPEN state. On a\r\n * hung connect (mobile after network change, half-open after sleep)\r\n * the browser can take ~2 min to emit `onerror`/`onclose`; without\r\n * this cap the reconnect loop blocks for that whole window. */\r\n connectTimeoutMs = 10_000;\r\n\r\n /** Max time to wait for the server's handshake response after WS opens. */\r\n handshakeTimeoutMs = 10_000;\r\n\r\n /** If a connection lived less than this before dropping, a graceful close\r\n * still counts as a failed attempt — the client keeps its growing\r\n * `_tryIndex` instead of resetting it, so a crash-looping server sees\r\n * increasing reconnect delays via `RetryDelaySeq`. Mirrors .NET\r\n * `RpcLimits.PrematureDisconnectTimeout` (RpcLimits.cs:18). */\r\n prematureDisconnectTimeoutMs = 15_000;\r\n\r\n /** Max completed inbound calls retained for duplicate-frame dedup\r\n * (a resent call id re-sends the computed result instead of\r\n * re-executing the handler). Deviation from .NET, which unregisters a\r\n * call once its result is sent: TS clients blind-resend on reconnect,\r\n * so completed calls are retained — bounded by this limit (oldest\r\n * evicted first) to cap memory on long-lived connections. */\r\n completedInboundCallsLimit = 1000;\r\n\r\n /** How often each peer's maintenance loop scans outbound calls for\r\n * timeouts (R12). Mirrors .NET `RpcLimits.CallTimeoutCheckPeriod`. */\r\n callTimeoutCheckPeriodMs = 1_000;\r\n\r\n /** Outbound `$sys.KeepAlive` send period. */\r\n keepAlivePeriodMs = 10_000;\r\n\r\n /** Reaper threshold — connection is force-closed (and the reconnect\r\n * loop takes over) if no inbound `$sys.KeepAlive` has been seen for\r\n * this long. Sized to tolerate a complete server stall plus most of\r\n * one keepalive cycle: the worst-case age of `_lastKeepAliveAt` is\r\n * `keepAlivePeriodMs + stall_duration`, so with these defaults the\r\n * tolerated stall is ~15 s. */\r\n keepAliveTimeoutMs = 25_000;\r\n\r\n /** How long a server peer stays in `hub.peers` after its connection closes\r\n * before it's stopped and removed. A same-peer reconnect (`accept()`)\r\n * within this window cancels the pending removal, so brief drops don't\r\n * discard the peer's trackers. Mirrors .NET's server-peer shutdown timeout\r\n * (`RpcPeerOptions.ServerPeerShutdownTimeoutProvider`, 3-15 min); the TS\r\n * port uses a fixed floor-matching default. */\r\n serverPeerCloseTimeoutMs = 180_000;\r\n\r\n constructor(overrides?: Partial<RpcLimits>) {\r\n if (overrides) Object.assign(this, overrides);\r\n }\r\n}\r\n","// .NET counterpart: ActualLab.Rpc.RpcClientPeerReconnectDelayer\r\n\r\nimport { RetryDelaySeq, RetryDelayer } from '@actuallab/core';\r\n\r\nexport class RpcClientPeerReconnectDelayer extends RetryDelayer {\r\n constructor() {\r\n super();\r\n // 0.1s base — a single flaky drop still reconnects fast (callers gate\r\n // *whether* to reconnect via app-level signals, see Api /\r\n // ApiReconnectDelayer). The exponential tail matters when `_tryIndex`\r\n // keeps climbing — repeated connect failures and premature disconnects\r\n // (RpcLimits.prematureDisconnectTimeoutMs) — so clients back off to\r\n // 10s instead of hammering a crash-looping server ~10x/s. C# uses\r\n // Exp(1s, 60s) for browser clients (RpcClientPeerReconnectDelayer.cs).\r\n this.delays = RetryDelaySeq.exp(100, 10_000);\r\n }\r\n}\r\n","// .NET counterparts:\r\n// RpcServiceBase (17 lines) — abstract base class providing DI access (Services,\r\n// Hub, Log). All RPC service implementations inherit from it.\r\n// RpcServiceRegistry — builds the full map of all services and their method defs\r\n// at startup; provides GetServerMethodResolver() for version-aware method\r\n// lookup. Inbound call dispatch is handled by RpcInboundCall.Process() which\r\n// invokes the method via a compiled delegate chain (MethodDef.InboundCallInvoker).\r\n//\r\n// Omitted from .NET:\r\n// - Compiled delegate pipeline — .NET pre-compiles per-method invoker delegates\r\n// (InboundCallServerInvokerFactory<T>, InboundCallMiddlewareInvokerFactory<T>)\r\n// using generic type instantiation. These invokers handle deserialization,\r\n// middleware chain, server method invocation, and result serialization — all\r\n// as a strongly-typed Task<TResult>. TS uses a simple async function dispatch\r\n// via fn(...args) because there's no middleware pipeline and JSON handles any\r\n// type.\r\n// - ServiceResolver (DI-based lazy service resolution) — .NET resolves the\r\n// service implementation from DI on first use. TS receives the impl object\r\n// directly in register().\r\n// - RpcServiceMode (Local/Client/Server/Hybrid) — .NET classifies each service\r\n// registration. TS has no mode; services are either registered (server) or\r\n// proxied (client).\r\n// - Version-aware method resolution (ServerMethodResolver with legacy names) —\r\n// .NET can resolve methods by legacy names for rolling-upgrade compatibility.\r\n// TS has no versioning.\r\n// - RpcInboundContext / AsyncLocal — .NET sets thread-local inbound context\r\n// so system call handlers can access the current peer/message. TS passes\r\n// context explicitly as a function argument (RpcDispatchContext).\r\n\r\nimport { getLogs } from './logging.js';\r\nimport type { RpcMethodDef } from './rpc-service-def.js';\r\nimport { wireMethodName } from './rpc-service-def.js';\r\nimport type { RpcServiceDef } from './rpc-service-def.js';\r\nimport type { RpcConnection } from './rpc-connection.js';\r\nimport type { RpcPeer } from './rpc-peer.js';\r\n\r\nconst { warnLog } = getLogs('RpcServiceHost');\r\n\r\nexport type RpcServiceImpl = Record<string, ((...args: unknown[]) => unknown) | undefined>;\r\n\r\n/** Context passed to service dispatch — carries callId, the inbound CallType,\r\n * the connection, and the peer for compute-call invalidation tracking. */\r\nexport interface RpcDispatchContext {\r\n __rpcDispatch: true;\r\n callId: number;\r\n callType: number;\r\n connection: RpcConnection;\r\n /** Aborted when the inbound call is cancelled via `$sys.Cancel` (R17). */\r\n signal?: AbortSignal;\r\n peer: RpcPeer;\r\n}\r\n\r\n/** Dispatches inbound RPC calls to registered service implementations. */\r\nexport class RpcServiceHost {\r\n private _methods = new Map<\r\n string,\r\n { def: RpcMethodDef; fn: (...args: unknown[]) => unknown; receiver: object }\r\n >();\r\n\r\n register(def: RpcServiceDef, impl: RpcServiceImpl, receiver: object = impl): void {\r\n for (const methodDef of def.methods.values()) {\r\n const fn = impl[methodDef.name];\r\n if (!fn) continue;\r\n this._methods.set(wireMethodName(methodDef), {\r\n def: methodDef,\r\n fn,\r\n receiver,\r\n });\r\n }\r\n }\r\n\r\n async dispatch(\r\n wireMethod: string,\r\n args: unknown[],\r\n context?: RpcDispatchContext\r\n ): Promise<unknown> {\r\n const entry = this._methods.get(wireMethod);\r\n if (entry === undefined) {\r\n // Mirrors .NET: missing method is sent back as $sys.Error; warn for visibility.\r\n warnLog?.log(`Method not found: ${wireMethod}`);\r\n throw new Error(`Method not found: ${wireMethod}`);\r\n }\r\n\r\n // Pass context as the last arg only for custom call types (e.g., compute) — their\r\n // wrapped functions use it; regular methods should not see it to avoid polluting ...args.\r\n if (context !== undefined && entry.def.callTypeId !== 0) {\r\n return await entry.fn.call(entry.receiver, ...args, context);\r\n }\r\n return await entry.fn.call(entry.receiver, ...args);\r\n }\r\n\r\n getMethodDef(wireMethod: string): RpcMethodDef | undefined {\r\n return this._methods.get(wireMethod)?.def;\r\n }\r\n}\r\n","// .NET counterpart:\r\n// RpcMethodAttribute — optional attribute that can override a method's wire name.\r\n// Method metadata (arg count, return type, NoWait, stream, etc.) is discovered\r\n// automatically via reflection on the service interface.\r\n// Interface-based metadata — .NET discovers all methods by reflecting on the\r\n// service interface type. NoWait is determined by return type == Task<RpcNoWait>.\r\n// Stream is determined by return type being IAsyncEnumerable<T>.\r\n//\r\n// Omitted from .NET:\r\n// - Reflection-based auto-discovery — .NET scans interface methods, parameter\r\n// types, and return types at startup. TS uses explicit decorator metadata\r\n// because TypeScript erases type information at runtime.\r\n// - RpcMethodAttribute.Name — custom wire name override. TS decorators don't\r\n// support custom wire names (always \"ServiceName.methodName\").\r\n// - RpcServiceAttribute — .NET has no class-level decorator; the service name\r\n// is configured in RpcServiceBuilder via DI. TS uses @rpcService(\"name\")\r\n// class decorator as a convenient alternative to DI configuration.\r\n// - Automatic NoWait detection from return type — .NET checks if return type\r\n// is Task<RpcNoWait>. TS requires explicit { returns: RpcType.noWait }\r\n// because return types are erased.\r\n// - Automatic stream detection from return type — .NET checks for\r\n// IAsyncEnumerable<T>. TS requires explicit { returns: RpcType.stream }.\r\n// - compute flag in method metadata — stored here for FusionHub's\r\n// addServiceFromContract() which reads decorator metadata to build a\r\n// RpcServiceDef. .NET determines this via the service interface hierarchy\r\n// (IComputeService marker interface).\r\n\r\nimport { ownMetadata, resolveArgCount } from '@actuallab/core';\r\n\r\nconst METHODS_META = Symbol.for('actuallab.methods');\r\nconst SERVICE_META = Symbol.for('actuallab.service');\r\n\r\nexport interface MethodMeta {\r\n argCount: number;\r\n returns?: symbol;\r\n /** Bitfield of RpcRemoteExecutionMode flags. */\r\n remoteExecutionMode?: number;\r\n [key: symbol]: unknown;\r\n}\r\n\r\nexport interface ServiceMeta {\r\n name: string;\r\n}\r\n\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- constructor type requires any for maximum compatibility\r\nexport type AnyConstructor = abstract new (...args: any[]) => any;\r\n\r\n/** Read method metadata from a decorated class. */\r\nexport function getMethodsMeta(\r\n cls: AnyConstructor,\r\n): Record<string, MethodMeta> | undefined {\r\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any -- Symbol.metadata is not in standard typings\r\n return (cls as any)[Symbol.metadata]?.[METHODS_META] as Record<string, MethodMeta> | undefined;\r\n}\r\n\r\n/** Read service metadata from a decorated class. */\r\nexport function getServiceMeta(\r\n cls: AnyConstructor,\r\n): ServiceMeta | undefined {\r\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any -- Symbol.metadata is not in standard typings\r\n return (cls as any)[Symbol.metadata]?.[SERVICE_META] as ServiceMeta | undefined;\r\n}\r\n\r\n/** Class decorator — stores the RPC service wire name. */\r\nexport function rpcService(serviceName: string) {\r\n return function <T extends AnyConstructor>(\r\n _target: T,\r\n context: ClassDecoratorContext<T>,\r\n ): void {\r\n const meta = ownMetadata<ServiceMeta>(context.metadata, SERVICE_META);\r\n meta.name = serviceName;\r\n };\r\n}\r\n\r\n/** Method decorator — stores RPC method metadata (argCount, returns, remoteExecutionMode). Does NOT wrap the method. */\r\nexport function rpcMethod(options?: {\r\n returns?: symbol;\r\n remoteExecutionMode?: number;\r\n argCount?: number;\r\n}) {\r\n return function <This, Args extends unknown[], Return>(\r\n target: (this: This, ...args: Args) => Return,\r\n context: ClassMethodDecoratorContext<\r\n This,\r\n (this: This, ...args: Args) => Return\r\n >,\r\n ): (this: This, ...args: Args) => Return {\r\n const methodName = String(context.name);\r\n const methods = ownMetadata<Record<string, MethodMeta>>(context.metadata, METHODS_META);\r\n methods[methodName] = {\r\n ...methods[methodName],\r\n argCount: resolveArgCount(target, options?.argCount, methodName),\r\n returns: options?.returns,\r\n remoteExecutionMode: options?.remoteExecutionMode,\r\n };\r\n return target; // no wrapping — RPC proxy handles invocation\r\n };\r\n}\r\n","// Pure TypeScript XXH3-64 implementation using BigInt.\r\n// Sync, zero dependencies. Supports inputs of any length.\r\n//\r\n// Reference: https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h\r\n// Verified against .NET System.IO.Hashing.XxHash3.HashToUInt64.\r\n\r\n \r\n\r\nconst U64_MASK = 0xffff_ffff_ffff_ffffn;\r\nconst U32_MASK = 0xffff_ffffn;\r\n\r\nconst PRIME32_1 = 0x9e3779b1;\r\nconst PRIME32_2 = 0x85ebca77;\r\nconst PRIME32_3 = 0xc2b2ae3d;\r\n\r\nconst PRIME64_1 = 0x9e3779b185ebca87n;\r\nconst PRIME64_2 = 0xc2b2ae3d27d4eb4fn;\r\nconst PRIME64_3 = 0x165667b19e3779f9n;\r\nconst PRIME64_4 = 0x85ebca77c2b2ae63n;\r\nconst PRIME64_5 = 0x27d4eb2f165667c5n;\r\n\r\nconst XXH3_SECRET_SIZE_MIN = 136;\r\nconst XXH_SECRET_DEFAULT_SIZE = 192;\r\n\r\nconst STRIPE_LEN = 64;\r\nconst XXH_SECRET_CONSUME_RATE = 8;\r\nconst ACC_NB = STRIPE_LEN / 8;\r\n\r\nconst XXH3_MIDSIZE_MAX = 240;\r\nconst XXH3_MIDSIZE_STARTOFFSET = 3;\r\nconst XXH3_MIDSIZE_LASTOFFSET = 17;\r\nconst XXH_SECRET_LASTACC_START = 7;\r\nconst XXH_SECRET_MERGEACCS_START = 11;\r\n\r\nconst RRMXMX = 0x9fb21c651e98df25n;\r\n\r\n// prettier-ignore\r\nconst DEFAULT_SECRET = new Uint8Array([\r\n 0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,\r\n 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,\r\n 0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,\r\n 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,\r\n 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,\r\n 0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,\r\n 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,\r\n 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,\r\n 0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,\r\n 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,\r\n 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,\r\n 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,\r\n]);\r\n\r\nfunction u64(x: bigint): bigint {\r\n return x & U64_MASK;\r\n}\r\n\r\nfunction read32LE(buf: Uint8Array, off: number): number {\r\n return (\r\n (buf[off] |\r\n (buf[off + 1] << 8) |\r\n (buf[off + 2] << 16) |\r\n (buf[off + 3] << 24)) >>>\r\n 0\r\n );\r\n}\r\n\r\nfunction read64LE(buf: Uint8Array, off: number): bigint {\r\n return (\r\n BigInt(buf[off]) |\r\n (BigInt(buf[off + 1]) << 8n) |\r\n (BigInt(buf[off + 2]) << 16n) |\r\n (BigInt(buf[off + 3]) << 24n) |\r\n (BigInt(buf[off + 4]) << 32n) |\r\n (BigInt(buf[off + 5]) << 40n) |\r\n (BigInt(buf[off + 6]) << 48n) |\r\n (BigInt(buf[off + 7]) << 56n)\r\n );\r\n}\r\n\r\nfunction write64LE(buf: Uint8Array, off: number, value: bigint): void {\r\n let x = u64(value);\r\n for (let i = 0; i < 8; i++) {\r\n buf[off + i] = Number(x & 0xffn);\r\n x >>= 8n;\r\n }\r\n}\r\n\r\nfunction swap32(x: number): number {\r\n return (\r\n (((x << 24) & 0xff000000) |\r\n ((x << 8) & 0x00ff0000) |\r\n ((x >>> 8) & 0x0000ff00) |\r\n ((x >>> 24) & 0x000000ff)) >>>\r\n 0\r\n );\r\n}\r\n\r\nfunction swap64(x: bigint): bigint {\r\n x = u64(x);\r\n return (\r\n ((x << 56n) & 0xff00000000000000n) |\r\n ((x << 40n) & 0x00ff000000000000n) |\r\n ((x << 24n) & 0x0000ff0000000000n) |\r\n ((x << 8n) & 0x000000ff00000000n) |\r\n ((x >> 8n) & 0x00000000ff000000n) |\r\n ((x >> 24n) & 0x0000000000ff0000n) |\r\n ((x >> 40n) & 0x000000000000ff00n) |\r\n ((x >> 56n) & 0x00000000000000ffn)\r\n );\r\n}\r\n\r\nfunction rotl64(x: bigint, amount: number): bigint {\r\n const r = BigInt(amount & 63);\r\n return u64((x << r) | (x >> (64n - r)));\r\n}\r\n\r\nfunction mul128Fold64(lhs: bigint, rhs: bigint): bigint {\r\n const product = u64(lhs) * u64(rhs);\r\n return u64(product) ^ u64(product >> 64n);\r\n}\r\n\r\n// XXH64_avalanche — used by len_0 and len_1to3\r\nfunction xxh64Avalanche(hash: bigint): bigint {\r\n let h = u64(hash);\r\n h ^= h >> 33n;\r\n h = u64(h * PRIME64_2);\r\n h ^= h >> 29n;\r\n h = u64(h * PRIME64_3);\r\n h ^= h >> 32n;\r\n return u64(h);\r\n}\r\n\r\n// XXH3_avalanche — used by len_9to16, len_17to128, len_129to240\r\nfunction xxh3Avalanche(hash: bigint): bigint {\r\n let h = u64(hash);\r\n h ^= h >> 37n;\r\n h = u64(h * 0x165667919e3779f9n);\r\n h ^= h >> 32n;\r\n return u64(h);\r\n}\r\n\r\nfunction len0(secret: Uint8Array, seed: bigint): bigint {\r\n const acc = u64(seed ^ read64LE(secret, 56) ^ read64LE(secret, 64));\r\n return xxh64Avalanche(acc);\r\n}\r\n\r\nfunction len1to3(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n seed: bigint\r\n): bigint {\r\n const byte1 = input[0];\r\n const byte2 = length > 1 ? input[1] : input[0];\r\n const byte3 = input[length - 1];\r\n const combined =\r\n ((byte1 << 16) | (byte2 << 24) | byte3 | (length << 8)) >>> 0;\r\n const bitflip = u64(\r\n BigInt((read32LE(secret, 0) ^ read32LE(secret, 4)) >>> 0) + seed\r\n );\r\n const value64 = u64(BigInt(combined) ^ bitflip);\r\n return xxh64Avalanche(value64);\r\n}\r\n\r\nfunction len4to8(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n seed: bigint\r\n): bigint {\r\n const inputHi = read32LE(input, 0);\r\n const inputLo = read32LE(input, length - 4);\r\n const input64 = BigInt(inputLo) | (BigInt(inputHi) << 32n);\r\n let acc = read64LE(secret, 8) ^ read64LE(secret, 16);\r\n seed ^= BigInt(swap32(Number(seed & U32_MASK))) << 32n;\r\n acc = u64(acc - seed);\r\n acc ^= input64;\r\n acc ^= rotl64(acc, 49) ^ rotl64(acc, 24);\r\n acc = u64(acc * RRMXMX);\r\n acc ^= (acc >> 35n) + BigInt(length);\r\n acc = u64(acc * RRMXMX);\r\n acc ^= acc >> 28n;\r\n return u64(acc);\r\n}\r\n\r\nfunction len9to16(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n seed: bigint\r\n): bigint {\r\n let inputLo = read64LE(secret, 24) ^ read64LE(secret, 32);\r\n let inputHi = read64LE(secret, 40) ^ read64LE(secret, 48);\r\n let acc = BigInt(length);\r\n inputLo = u64(inputLo + seed);\r\n inputHi = u64(inputHi - seed);\r\n inputLo ^= read64LE(input, 0);\r\n inputHi ^= read64LE(input, length - 8);\r\n acc = u64(acc + swap64(inputLo));\r\n acc = u64(acc + inputHi);\r\n acc = u64(acc + mul128Fold64(inputLo, inputHi));\r\n return xxh3Avalanche(acc);\r\n}\r\n\r\nfunction mix16B(\r\n input: Uint8Array,\r\n inputOff: number,\r\n secret: Uint8Array,\r\n secretOff: number,\r\n seed: bigint\r\n): bigint {\r\n let lhs = seed;\r\n let rhs = u64(0n - seed);\r\n lhs = u64(lhs + read64LE(secret, secretOff));\r\n rhs = u64(rhs + read64LE(secret, secretOff + 8));\r\n lhs ^= read64LE(input, inputOff);\r\n rhs ^= read64LE(input, inputOff + 8);\r\n return mul128Fold64(lhs, rhs);\r\n}\r\n\r\nfunction len17to128(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n seed: bigint\r\n): bigint {\r\n let i = Math.floor((length - 1) / 32);\r\n let acc = u64(BigInt(length) * PRIME64_1);\r\n for (; i >= 0; i--) {\r\n acc = u64(acc + mix16B(input, 16 * i, secret, 32 * i, seed));\r\n acc = u64(\r\n acc +\r\n mix16B(\r\n input,\r\n length - 16 * (i + 1),\r\n secret,\r\n 32 * i + 16,\r\n seed\r\n )\r\n );\r\n }\r\n return xxh3Avalanche(acc);\r\n}\r\n\r\nfunction len129to240(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n seed: bigint\r\n): bigint {\r\n let acc = u64(BigInt(length) * PRIME64_1);\r\n const nbRounds = Math.floor(length / 16);\r\n for (let i = 0; i < 8; i++) {\r\n acc = u64(acc + mix16B(input, 16 * i, secret, 16 * i, seed));\r\n }\r\n acc = xxh3Avalanche(acc);\r\n for (let i = 8; i < nbRounds; i++) {\r\n acc = u64(\r\n acc +\r\n mix16B(\r\n input,\r\n 16 * i,\r\n secret,\r\n 16 * (i - 8) + XXH3_MIDSIZE_STARTOFFSET,\r\n seed\r\n )\r\n );\r\n }\r\n acc = u64(\r\n acc +\r\n mix16B(\r\n input,\r\n length - 16,\r\n secret,\r\n XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET,\r\n seed\r\n )\r\n );\r\n return xxh3Avalanche(acc);\r\n}\r\n\r\nfunction hashShort(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n seed: bigint\r\n): bigint {\r\n if (length <= 16) {\r\n if (length > 8) return len9to16(input, length, secret, seed);\r\n if (length >= 4) return len4to8(input, length, secret, seed);\r\n if (length !== 0) return len1to3(input, length, secret, seed);\r\n return len0(secret, seed);\r\n }\r\n if (length <= 128) return len17to128(input, length, secret, seed);\r\n return len129to240(input, length, secret, seed);\r\n}\r\n\r\n// --- Long input (> 240 bytes) ---\r\n\r\nfunction accumulate512(\r\n acc: bigint[],\r\n input: Uint8Array,\r\n inputOff: number,\r\n secret: Uint8Array,\r\n secretOff: number\r\n): void {\r\n for (let i = 0; i < ACC_NB; i++) {\r\n const dataVal = read64LE(input, inputOff + 8 * i);\r\n const dataKey = dataVal ^ read64LE(secret, secretOff + 8 * i);\r\n acc[i ^ 1] = u64(acc[i ^ 1] + dataVal);\r\n const low32 = dataKey & U32_MASK;\r\n const high32 = dataKey >> 32n;\r\n acc[i] = u64(acc[i] + low32 * high32);\r\n }\r\n}\r\n\r\nfunction scrambleAcc(\r\n acc: bigint[],\r\n secret: Uint8Array,\r\n secretOff: number\r\n): void {\r\n for (let i = 0; i < ACC_NB; i++) {\r\n let x = acc[i];\r\n x ^= x >> 47n;\r\n x ^= read64LE(secret, secretOff + 8 * i);\r\n x = u64(x * BigInt(PRIME32_1));\r\n acc[i] = x;\r\n }\r\n}\r\n\r\nfunction accumulateLoop(\r\n acc: bigint[],\r\n input: Uint8Array,\r\n inputOff: number,\r\n secret: Uint8Array,\r\n secretOff: number,\r\n nbStripes: number\r\n): void {\r\n for (let n = 0; n < nbStripes; n++) {\r\n accumulate512(\r\n acc,\r\n input,\r\n inputOff + n * STRIPE_LEN,\r\n secret,\r\n secretOff + 8 * n\r\n );\r\n }\r\n}\r\n\r\nfunction mix2Accs(\r\n acc: bigint[],\r\n accOff: number,\r\n secret: Uint8Array,\r\n secretOff: number\r\n): bigint {\r\n return mul128Fold64(\r\n acc[accOff] ^ read64LE(secret, secretOff),\r\n acc[accOff + 1] ^ read64LE(secret, secretOff + 8)\r\n );\r\n}\r\n\r\nfunction mergeAccs(\r\n acc: bigint[],\r\n key: Uint8Array,\r\n keyOff: number,\r\n start: bigint\r\n): bigint {\r\n let result = start;\r\n for (let i = 0; i < 4; i++) {\r\n result = u64(result + mix2Accs(acc, 2 * i, key, keyOff + 16 * i));\r\n }\r\n return xxh3Avalanche(result);\r\n}\r\n\r\nfunction hashLong(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n secretSize: number\r\n): bigint {\r\n const nbRounds = Math.floor(\r\n (secretSize - STRIPE_LEN) / XXH_SECRET_CONSUME_RATE\r\n );\r\n const blockLen = STRIPE_LEN * nbRounds;\r\n const nbBlocks = Math.floor((length - 1) / blockLen);\r\n const nbStripes = Math.floor(\r\n ((length - 1) - blockLen * nbBlocks) / STRIPE_LEN\r\n );\r\n\r\n const acc: bigint[] = [\r\n BigInt(PRIME32_3),\r\n PRIME64_1,\r\n PRIME64_2,\r\n PRIME64_3,\r\n PRIME64_4,\r\n BigInt(PRIME32_2),\r\n PRIME64_5,\r\n BigInt(PRIME32_1),\r\n ];\r\n\r\n for (let n = 0; n < nbBlocks; n++) {\r\n accumulateLoop(acc, input, n * blockLen, secret, 0, nbRounds);\r\n scrambleAcc(acc, secret, secretSize - STRIPE_LEN);\r\n }\r\n\r\n accumulateLoop(acc, input, nbBlocks * blockLen, secret, 0, nbStripes);\r\n\r\n // Last stripe — always applied (C reference: unconditional)\r\n accumulate512(\r\n acc,\r\n input,\r\n length - STRIPE_LEN,\r\n secret,\r\n secretSize - STRIPE_LEN - XXH_SECRET_LASTACC_START\r\n );\r\n\r\n return mergeAccs(\r\n acc,\r\n secret,\r\n XXH_SECRET_MERGEACCS_START,\r\n u64(BigInt(length) * PRIME64_1)\r\n );\r\n}\r\n\r\nfunction hashLongWithSeed(\r\n input: Uint8Array,\r\n length: number,\r\n seed: bigint\r\n): bigint {\r\n const secret = new Uint8Array(XXH_SECRET_DEFAULT_SIZE);\r\n for (let i = 0; i < XXH_SECRET_DEFAULT_SIZE / 16; i++) {\r\n const off = 16 * i;\r\n write64LE(secret, off, u64(read64LE(DEFAULT_SECRET, off) + seed));\r\n write64LE(\r\n secret,\r\n off + 8,\r\n u64(read64LE(DEFAULT_SECRET, off + 8) - seed)\r\n );\r\n }\r\n return hashLong(input, length, secret, secret.length);\r\n}\r\n\r\n// --- Public API ---\r\n\r\nconst textEncoder = new TextEncoder();\r\n\r\n/**\r\n * Compute XXH3-64 hash of a byte array. Returns a BigInt (unsigned 64-bit).\r\n * Pure TypeScript, synchronous, supports any input length.\r\n */\r\nexport function xxh3_64(input: Uint8Array, seed = 0n): bigint {\r\n const len = input.length;\r\n if (len <= XXH3_MIDSIZE_MAX) {\r\n return hashShort(input, len, DEFAULT_SECRET, u64(seed));\r\n }\r\n return hashLongWithSeed(input, len, u64(seed));\r\n}\r\n\r\n/**\r\n * Compute XXH3-64 hash of a UTF-8 string. Returns a BigInt (unsigned 64-bit).\r\n */\r\nexport function xxh3_64str(input: string, seed = 0n): bigint {\r\n return xxh3_64(textEncoder.encode(input), seed);\r\n}\r\n\r\n/**\r\n * Compute the RPC method hash matching .NET's RpcMethodRef.ComputeHashCode.\r\n * Returns a 32-bit signed integer (lower 32 bits of XXH3-64 of [prefix + utf8Name]).\r\n *\r\n * The prefix is uint32 LE of (67211 * utf8Name.length), which ensures methods\r\n * with different name lengths but similar text produce different hashes.\r\n *\r\n * Computed on demand — caller should cache the result per method name.\r\n */\r\nexport function computeMethodHash(methodName: string): number {\r\n const utf8 = textEncoder.encode(methodName);\r\n // Prefix: uint32 LE of (67211 * utf8.length)\r\n const prefixValue = Math.imul(67211, utf8.length) >>> 0;\r\n const prefixed = new Uint8Array(4 + utf8.length);\r\n prefixed[0] = prefixValue & 0xff;\r\n prefixed[1] = (prefixValue >>> 8) & 0xff;\r\n prefixed[2] = (prefixValue >>> 16) & 0xff;\r\n prefixed[3] = (prefixValue >>> 24) & 0xff;\r\n prefixed.set(utf8, 4);\r\n const hash64 = xxh3_64(prefixed);\r\n // Truncate to signed int32 (same as C#'s unchecked((int)hash64))\r\n return Number(hash64 & 0xffffffffn) | 0;\r\n}\r\n","// Method name ↔ hash registry for compact binary serialization.\r\n//\r\n// .NET counterpart: RpcMethodResolver — maintains MethodByHashCode dictionary\r\n// for resolving compact 4-byte method hashes back to full method names.\r\n//\r\n// Hashes are computed on demand (first use) and cached. The computeMethodHash\r\n// function uses BigInt-based XXH3-64, which is pure sync TypeScript.\r\n\r\nimport { getLogs } from './logging.js';\r\nimport { computeMethodHash } from './rpc-xxhash3.js';\r\n\r\nconst { warnLog } = getLogs('RpcMethodRegistry');\r\n\r\n/**\r\n * Registry mapping RPC method wire names to/from their 4-byte XXH3 hashes.\r\n * Hashes match .NET's RpcMethodRef.ComputeHashCode.\r\n * Hashes are computed lazily on first access and cached.\r\n */\r\nexport class RpcMethodRegistry {\r\n private _nameToHash = new Map<string, number>();\r\n private _hashToName = new Map<number, string>();\r\n\r\n /** Register a method name and compute its hash. */\r\n register(methodName: string): void {\r\n if (this._nameToHash.has(methodName)) return;\r\n const hash = computeMethodHash(methodName);\r\n\r\n // Check for hash collisions\r\n const existing = this._hashToName.get(hash);\r\n if (existing !== undefined && existing !== methodName) {\r\n warnLog?.log(`hash collision: \"${methodName}\" and \"${existing}\" both hash to ${hash}`);\r\n return;\r\n }\r\n\r\n this._nameToHash.set(methodName, hash);\r\n this._hashToName.set(hash, methodName);\r\n }\r\n\r\n /** Register all wire method names from a service definition. */\r\n registerService(\r\n serviceName: string,\r\n methods: ReadonlyMap<string, { name: string; wireArgCount: number }>\r\n ): void {\r\n for (const [, def] of methods) {\r\n const wireName = `${serviceName}.${def.name}:${def.wireArgCount}`;\r\n this.register(wireName);\r\n }\r\n }\r\n\r\n /** Get the hash for a method name. Computes on demand if not registered. */\r\n getHash(methodName: string): number {\r\n let hash = this._nameToHash.get(methodName);\r\n if (hash === undefined) {\r\n this.register(methodName);\r\n hash = this._nameToHash.get(methodName)!;\r\n }\r\n return hash;\r\n }\r\n\r\n /** Get the method name for a hash. Returns undefined if not registered. */\r\n getName(hash: number): string | undefined {\r\n return this._hashToName.get(hash);\r\n }\r\n\r\n /** Get the hash for a method name, throwing if not registered. */\r\n requireHash(methodName: string): number {\r\n return this.getHash(methodName);\r\n }\r\n}\r\n","// .NET counterpart:\r\n// RpcHub (141 lines) — a sealed ProcessorBase (IAsyncDisposable) that owns:\r\n// DI-resolved configuration (RpcRegistryOptions, PeerOptions, InboundCallOptions,\r\n// OutboundCallOptions, DiagnosticsOptions), SerializationFormats, Middlewares,\r\n// ClientPeerReconnectDelayer, Limits, SystemClock.\r\n// Manages peers via ConcurrentDictionary<RpcRoute, RpcPeer> with GetPeer()\r\n// that lazily creates + starts peers. Also has GetClient<T>() / GetServer<T>()\r\n// that look up services in ServiceRegistry.\r\n//\r\n// Omitted from .NET:\r\n// - DI / IServiceProvider integration — .NET resolves all options, serializers,\r\n// middleware, delayers, etc. from DI. TS constructs everything directly.\r\n// - RpcServiceRegistry — .NET builds a full registry of all RPC services at\r\n// startup via reflection + DI, mapping Type → RpcServiceDef. TS uses explicit\r\n// addService() / addClient() calls.\r\n// - GetClient<T>() / GetServer<T>() — .NET resolves typed client/server proxies\r\n// via the ServiceRegistry + DI. TS uses addClient<T>(peer, def).\r\n// - RpcRoute-keyed peer dictionary — .NET peers are keyed by RpcRoute (one\r\n// route generation of a stable RpcRef, which encodes client/server, versions,\r\n// serialization format). TS uses string refs (URL for clients,\r\n// \"server://{uuid}\" for servers).\r\n// - Lazy peer creation + Start() — .NET's GetPeer auto-creates and starts a\r\n// peer's reconnection loop. TS requires explicit addPeer() + run().\r\n// - DisposeAsync — .NET disposes all peers on hub shutdown. TS has close().\r\n// - Route change detection (RpcRoute.WhenChanged → Dispose peer) — .NET\r\n// watches for topology changes and auto-disposes stale-route peers.\r\n// TS has no routing layer.\r\n// - Middlewares (IRpcMiddleware[]) — ordered middleware pipeline for inbound\r\n// calls. TS dispatches directly to service implementations.\r\n// - RpcConfiguration / Configuration.Freeze() — frozen config snapshot.\r\n// - HostId / SystemClock — infrastructure services.\r\n// - DefaultPeer / LoopbackPeer / LocalPeer / NonePeer — cached well-known peers.\r\n\r\nimport { getLogs } from './logging.js';\r\nimport {\r\n RpcClientPeer,\r\n RpcServerPeer,\r\n type RpcPeer,\r\n type RpcCallOptions,\r\n} from './rpc-peer.js';\r\nimport { RpcLimits } from './rpc-limits.js';\r\nimport { RpcClientPeerReconnectDelayer } from './rpc-client-peer-reconnect-delayer.js';\r\nimport { RpcServiceHost, type RpcServiceImpl } from './rpc-service-host.js';\r\nimport type { RpcServiceDef, RpcMethodDef } from './rpc-service-def.js';\r\nimport { wireMethodName, RpcType, RpcRemoteExecutionMode } from './rpc-service-def.js';\r\nimport { getServiceMeta, getMethodsMeta, type AnyConstructor } from './rpc-decorators.js';\r\nimport { RpcSystemCallSender } from './rpc-system-call-sender.js';\r\nimport { RpcSystemCallHandler } from './rpc-system-call-handler.js';\r\nimport { toRpcStream } from './rpc-stream.js';\r\nimport { RpcMethodRegistry } from './rpc-method-registry.js';\r\nimport { RpcSystemCalls } from './rpc-message.js';\r\n\r\nconst { warnLog } = getLogs('RpcHub');\r\n\r\n/** Factory signature shared by all peer lookup/creation methods. Serialization\r\n * format is encoded in the URL via `?f=...` (see {@link RpcRefBuilder}),\r\n * so the factory doesn't need an extra parameter for it. `getClientPeer` /\r\n * `getServerPeer` cast the result, so the factory returns RpcPeer. */\r\nexport type RpcPeerFactory = (hub: RpcHub, ref: string) => RpcPeer;\r\n\r\ntype RpcClientFn = (...args: unknown[]) => unknown;\r\n\r\n/** A live client proxy plus the mutable maps it reads from, so a later def can\r\n * add methods to the already-handed-out proxy. `methods` is the map the Proxy's\r\n * get-trap resolves against; `overloads` tracks each method's per-argCount defs\r\n * and functions for overload resolution and conflict detection. */\r\ninterface RpcClientProxy {\r\n readonly proxy: object;\r\n readonly methods: Map<string, RpcClientFn>;\r\n readonly overloads: Map<string, Map<number, { def: RpcMethodDef; fn: RpcClientFn }>>;\r\n}\r\n\r\n/** Central RPC coordinator — manages peers, services, and configuration. */\r\nexport class RpcHub {\r\n readonly hubId: string;\r\n readonly peers = new Map<string, RpcPeer>();\r\n readonly serviceHost: RpcServiceHost;\r\n readonly systemCallSender: RpcSystemCallSender;\r\n systemCallHandler: RpcSystemCallHandler = new RpcSystemCallHandler();\r\n\r\n /** Method registry for compact format hash ↔ name resolution.\r\n * Created lazily, populated by addService/addClient. */\r\n readonly registry = new RpcMethodRegistry();\r\n\r\n /** Shared reconnect delayer used by every client peer in this hub. Swap in\r\n * a custom subclass (e.g. app-level signal-gated) before peers start. */\r\n reconnectDelayer: RpcClientPeerReconnectDelayer = new RpcClientPeerReconnectDelayer();\r\n\r\n /** Cache of client proxies keyed by peer, then by service name — so\r\n * repeated {@link addClient} calls for the same service+peer return one\r\n * proxy. All consumers then share a single computed/call/invalidation\r\n * stream per logical value, matching .NET's singleton client proxies (F8).\r\n * A later def registered under the same name extends the existing proxy\r\n * with its extra methods (superset), so no consumer ever receives a proxy\r\n * missing methods the def declares. */\r\n private readonly _clientProxies = new WeakMap<RpcPeer, Map<string, RpcClientProxy>>();\r\n\r\n /** Connection-lifecycle timing limits. Peers constructed against this hub\r\n * read their initial `*Ms` field values from this instance; later\r\n * mutations affect only peers built afterward (existing peers can be\r\n * retuned by setting their `*Ms` fields directly). Defaults to the\r\n * process-wide {@link RpcLimits.Default} so a single global override\r\n * propagates to every hub that hasn't replaced this. */\r\n limits: RpcLimits = RpcLimits.Default;\r\n\r\n /** URL used by {@link defaultPeer} to resolve / create the default client\r\n * peer. Must be set before the first {@link defaultPeer} access. */\r\n defaultPeerUrl: string | undefined;\r\n /** Factory used by {@link defaultPeer} when the peer needs to be created.\r\n * If left undefined, `getPeer`'s built-in default (RpcClientPeer with\r\n * format derived from URL or resolver default) is used. */\r\n defaultPeerFactory: RpcPeerFactory | undefined;\r\n\r\n constructor(hubId?: string) {\r\n this.hubId = hubId ?? crypto.randomUUID();\r\n this.serviceHost = new RpcServiceHost();\r\n this.systemCallSender = this._createSystemCallSender();\r\n this.systemCallSender.registry = this.registry;\r\n // Pre-register system call method names for compact format hash resolution\r\n for (const methodName of Object.values(RpcSystemCalls)) {\r\n this.registry.register(methodName);\r\n }\r\n }\r\n\r\n /** Shortcut for the hub's default client peer. Fails if\r\n * {@link defaultPeerUrl} is not set. */\r\n get defaultPeer(): RpcClientPeer {\r\n if (this.defaultPeerUrl === undefined)\r\n throw new Error('RpcHub.defaultPeerUrl is not set.');\r\n return this.getClientPeer(this.defaultPeerUrl, this.defaultPeerFactory);\r\n }\r\n\r\n /** Register a peer under its ref. If a different peer is already registered\r\n * at the same ref, close it first — the hub guarantees at most one live\r\n * peer per ref, and no peer exists outside the hub. */\r\n addPeer(peer: RpcPeer): void {\r\n const existing = this.peers.get(peer.ref);\r\n if (existing !== undefined && existing !== peer)\r\n existing.close(); // close() removes itself from this.peers\r\n this.peers.set(peer.ref, peer);\r\n }\r\n\r\n removePeer(ref: string): void {\r\n const peer = this.peers.get(ref);\r\n if (peer !== undefined) {\r\n peer.close();\r\n this.peers.delete(ref);\r\n // Mirrors RpcHub.cs:137 — \"peer is removed from RpcHub\".\r\n warnLog?.log(`'${ref}': peer is removed from RpcHub`);\r\n }\r\n }\r\n\r\n /** Get or create a peer by ref. If the peer does not exist:\r\n * - with `factory` provided: it is constructed via `factory(this, ref)`;\r\n * - otherwise: server peers (ref starts with `server://`) get a plain\r\n * RpcServerPeer, anything else gets a default-configured RpcClientPeer.\r\n * The created peer is always registered in this hub. */\r\n getPeer(ref: string, factory?: RpcPeerFactory): RpcPeer {\r\n const existing = this.peers.get(ref);\r\n if (existing) return existing;\r\n\r\n const peer = factory\r\n ? factory(this, ref)\r\n : ref.startsWith('server://')\r\n ? new RpcServerPeer(this, ref)\r\n : new RpcClientPeer(this, ref);\r\n this.addPeer(peer);\r\n return peer;\r\n }\r\n\r\n /** Get or create a client peer for the given URL. See {@link getPeer} for\r\n * the factory semantics. */\r\n getClientPeer(ref: string, factory?: RpcPeerFactory): RpcClientPeer {\r\n return this.getPeer(ref, factory) as RpcClientPeer;\r\n }\r\n\r\n /** Get or create a server peer for the given ref. See {@link getPeer} for\r\n * the factory semantics. */\r\n getServerPeer(ref: string, factory?: RpcPeerFactory): RpcServerPeer {\r\n return this.getPeer(ref, factory) as RpcServerPeer;\r\n }\r\n\r\n /** Register a service with optional server method wrapping for custom call types. */\r\n addService(\r\n defOrContract: RpcServiceDef | (AnyConstructor),\r\n impl: RpcServiceImpl\r\n ): void {\r\n const def = this._resolveServiceDef(defOrContract);\r\n const wrappedImpl: RpcServiceImpl = {};\r\n for (const methodDef of def.methods.values()) {\r\n const fn = impl[methodDef.name];\r\n if (!fn) continue;\r\n wrappedImpl[methodDef.name] =\r\n methodDef.callTypeId !== 0\r\n ? this._wrapServerMethod(methodDef, fn, impl as object)\r\n : fn;\r\n }\r\n this.serviceHost.register(def, wrappedImpl, impl as object);\r\n this.registry.registerService(def.name, def.methods);\r\n }\r\n\r\n /** Create a typed client proxy for a service on a remote peer. Repeated calls\r\n * for the same (peer, service name) return one shared proxy; a def carrying\r\n * extra methods extends it (F8). */\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T is used for caller-specified proxy type\r\n addClient<T extends object>(\r\n peer: RpcPeer,\r\n defOrContract: RpcServiceDef | (AnyConstructor)\r\n ): T {\r\n const def = this._resolveServiceDef(defOrContract);\r\n this.registry.registerService(def.name, def.methods);\r\n\r\n let byService = this._clientProxies.get(peer);\r\n if (byService === undefined) {\r\n byService = new Map<string, RpcClientProxy>();\r\n this._clientProxies.set(peer, byService);\r\n }\r\n let entry = byService.get(def.name);\r\n if (entry === undefined) {\r\n entry = this._createClientProxy();\r\n byService.set(def.name, entry);\r\n }\r\n this._extendClientProxy(peer, entry, def);\r\n return entry.proxy as T;\r\n }\r\n\r\n close(): void {\r\n for (const peer of this.peers.values()) peer.close();\r\n this.peers.clear();\r\n }\r\n\r\n /** Create the hub's system-call sender. Override in FusionHub to add\r\n * Fusion-specific system calls (e.g. $sys-c.Invalidate). */\r\n protected _createSystemCallSender(): RpcSystemCallSender {\r\n return new RpcSystemCallSender();\r\n }\r\n\r\n /** Build an RpcServiceDef from decorator metadata on a contract class. Override in FusionHub for compute support. */\r\n protected _buildServiceDef(\r\n cls: AnyConstructor\r\n ): RpcServiceDef {\r\n const svcMeta = getServiceMeta(cls);\r\n if (svcMeta === undefined)\r\n throw new Error('Contract class missing @rpcService metadata');\r\n\r\n const methodsMeta = getMethodsMeta(cls) ?? {};\r\n const methods = new Map<string, RpcMethodDef>();\r\n\r\n for (const [name, meta] of Object.entries(methodsMeta)) {\r\n const wireArgCount = meta.argCount + 1;\r\n const mapKey = `${name}:${wireArgCount}`;\r\n const isNoWait = meta.returns === RpcType.noWait;\r\n methods.set(mapKey, {\r\n name,\r\n serviceName: svcMeta.name,\r\n argCount: meta.argCount,\r\n wireArgCount,\r\n callTypeId: 0,\r\n stream: meta.returns === RpcType.stream,\r\n noWait: isNoWait,\r\n remoteExecutionMode: isNoWait\r\n ? 0\r\n : (meta.remoteExecutionMode ?? RpcRemoteExecutionMode.Default),\r\n });\r\n }\r\n\r\n return { name: svcMeta.name, methods };\r\n }\r\n\r\n /** Resolve a service def from either a plain RpcServiceDef or a contract class. */\r\n protected _resolveServiceDef(\r\n defOrContract: RpcServiceDef | (AnyConstructor)\r\n ): RpcServiceDef {\r\n if (typeof defOrContract === 'function')\r\n return this._buildServiceDef(defOrContract);\r\n return defOrContract;\r\n }\r\n\r\n /** Override in FusionHub — default is pass-through (for methods with callTypeId !== 0). */\r\n protected _wrapServerMethod(\r\n _methodDef: RpcMethodDef,\r\n fn: (...args: unknown[]) => unknown,\r\n _impl: object\r\n ): (...args: unknown[]) => unknown {\r\n return fn;\r\n }\r\n\r\n /** Create a client method for the given methodDef. Override in FusionHub for custom call types. */\r\n protected _createClientMethod(\r\n peer: RpcPeer,\r\n methodDef: RpcMethodDef\r\n ): (...args: unknown[]) => unknown {\r\n const wireName = wireMethodName(methodDef);\r\n const mode = methodDef.remoteExecutionMode;\r\n const mustCheckConnection = !(mode & RpcRemoteExecutionMode.AwaitForConnection);\r\n\r\n // NoWait methods — remoteExecutionMode is always 0, silently drop if disconnected\r\n if (methodDef.noWait) {\r\n return (...args: unknown[]) => {\r\n const callArgs = args.slice(0, methodDef.argCount);\r\n peer.callNoWait(wireName, callArgs);\r\n };\r\n }\r\n\r\n // Stream methods — call returns an RpcStream<T> (AsyncIterable)\r\n if (methodDef.stream) {\r\n return async (...args: unknown[]) => {\r\n if (mustCheckConnection && !peer.isConnected)\r\n throw new Error('Outbound call failed: not connected and AwaitForConnection is not set.');\r\n const callArgs = args.slice(0, methodDef.argCount);\r\n const outboundCall = peer.call(wireName, callArgs);\r\n const result = await outboundCall.result;\r\n const stream = toRpcStream(result, peer);\r\n if (!stream)\r\n throw new Error(\r\n `Expected stream reference, got: ${JSON.stringify(result)}`\r\n );\r\n return stream;\r\n };\r\n }\r\n\r\n // Regular methods (including non-zero callTypeId — subclass can override for custom behavior)\r\n const callOptions: RpcCallOptions | undefined =\r\n (methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default || methodDef.timeouts !== undefined)\r\n ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode, timeouts: methodDef.timeouts }\r\n : undefined;\r\n\r\n return async (...args: unknown[]) => {\r\n if (mustCheckConnection && !peer.isConnected)\r\n throw new Error('Outbound call failed: not connected and AwaitForConnection is not set.');\r\n const callArgs = args.slice(0, methodDef.argCount);\r\n const outboundCall = peer.call(wireName, callArgs, callOptions);\r\n // The result is handed back as deserialized. A stream reference\r\n // nested inside it is converted by the caller via `toRpcStream` —\r\n // see the note on that function for why it can't be inferred here.\r\n return await outboundCall.result;\r\n };\r\n }\r\n\r\n // Private methods\r\n\r\n private _createClientProxy(): RpcClientProxy {\r\n const methods = new Map<string, RpcClientFn>();\r\n const proxy = new Proxy({}, {\r\n get: (_target, prop) =>\r\n typeof prop === 'string' ? methods.get(prop) : undefined,\r\n });\r\n return { proxy, methods, overloads: new Map() };\r\n }\r\n\r\n private _extendClientProxy(peer: RpcPeer, entry: RpcClientProxy, def: RpcServiceDef): void {\r\n // Validate the whole def before mutating anything, so a conflict\r\n // leaves the shared proxy exactly as it was.\r\n for (const methodDef of def.methods.values()) {\r\n const existing = entry.overloads.get(methodDef.name)?.get(methodDef.argCount);\r\n if (existing !== undefined && !isSameWireMethod(existing.def, methodDef))\r\n throw new Error(\r\n `RpcHub.addClient: conflicting definition of '${wireMethodName(methodDef)}' ` +\r\n `under service '${def.name}' — a different signature is already registered.`);\r\n }\r\n\r\n const changed = new Set<string>();\r\n for (const methodDef of def.methods.values()) {\r\n let byArgCount = entry.overloads.get(methodDef.name);\r\n if (byArgCount === undefined) {\r\n byArgCount = new Map();\r\n entry.overloads.set(methodDef.name, byArgCount);\r\n }\r\n if (byArgCount.has(methodDef.argCount))\r\n continue;\r\n\r\n byArgCount.set(methodDef.argCount, {\r\n def: methodDef,\r\n fn: this._createClientMethod(peer, methodDef),\r\n });\r\n changed.add(methodDef.name);\r\n }\r\n for (const name of changed) {\r\n const byArgCount = entry.overloads.get(name)!;\r\n entry.methods.set(name, buildOverloadFn(name, byArgCount));\r\n }\r\n }\r\n}\r\n\r\nfunction buildOverloadFn(\r\n name: string,\r\n byArgCount: Map<number, { def: RpcMethodDef; fn: RpcClientFn }>\r\n): RpcClientFn {\r\n if (byArgCount.size === 1) {\r\n const [[, only]] = byArgCount;\r\n return only.fn;\r\n }\r\n return (...args: unknown[]) => {\r\n const overload = byArgCount.get(args.length);\r\n if (overload === undefined)\r\n throw new Error(`No overload of ${name} accepts ${args.length} arguments`);\r\n return overload.fn(...args);\r\n };\r\n}\r\n\r\nfunction isSameWireMethod(a: RpcMethodDef, b: RpcMethodDef): boolean {\r\n return a.wireArgCount === b.wireArgCount\r\n && a.callTypeId === b.callTypeId\r\n && a.stream === b.stream\r\n && a.noWait === b.noWait\r\n && a.remoteExecutionMode === b.remoteExecutionMode;\r\n}\r\n","// Ref composition helpers for RPC peers.\n//\n// `RpcClientPeer`'s ref is its full WebSocket URL. The serialization format is\n// encoded as the `f=` query parameter (see RpcClientPeer ctor).\n// `RpcServerPeer`'s ref is `server://{id}`.\n//\n// Typical use:\n// rpcHub.defaultPeerUrl = RpcRefBuilder.forClient('wss://host/rpc/ws', 'msgpack6');\n// const serverRef = RpcRefBuilder.forServer(crypto.randomUUID());\n\nexport class RpcRefBuilder {\n /** Build a client peer ref from a WebSocket URL, optionally baking in the\n * serialization format as a `f=` query parameter.\n * @param url Base WebSocket URL, e.g. `wss://host/rpc/ws`.\n * @param serializationFormat Optional format key, e.g. `msgpack6`. When\n * provided, replaces any existing `f=` in the URL. */\n static forClient(url: string, serializationFormat?: string): string {\n if (serializationFormat === undefined) return url;\n const u = new URL(url);\n u.searchParams.set('f', serializationFormat);\n return u.toString();\n }\n\n /** Build a server peer ref from a peer id — returns `server://{id}`. */\n static forServer(id: string): string {\n return `server://${id}`;\n }\n}\n","// .NET counterpart:\r\n// RpcClient — a DI-registered service used by generated interceptor proxies.\r\n// The proxy classes (generated by Interceptor/ProxyGenerator) intercept method\r\n// calls on the service interface, create an RpcOutboundContext, resolve the\r\n// target peer via OutboundCallRouter, and invoke the outbound call pipeline.\r\n//\r\n// Omitted from .NET:\r\n// - Interceptor-based proxy generation — .NET generates proxy classes at startup\r\n// (via Castle DynamicProxy / source generators) that implement the service\r\n// interface. TS uses a runtime Proxy object instead, which achieves the same\r\n// result without code generation.\r\n// - RpcOutboundContext — .NET creates a rich context per call with CancellationToken,\r\n// Headers, CacheInfoCapture, Trace, RoutingMode. TS passes arguments directly.\r\n// - OutboundCallRouter — .NET routes each call to the appropriate peer based on\r\n// sharding/load-balancing rules. TS passes the peer explicitly.\r\n// - This standalone function is superseded by RpcHub.addClient() which adds\r\n// noWait and compute-aware dispatch. Kept for backward compatibility.\r\n\r\nimport type { RpcPeer } from './rpc-peer.js';\r\nimport type { RpcServiceDef, RpcMethodDef } from './rpc-service-def.js';\r\nimport { wireMethodName, RpcRemoteExecutionMode } from './rpc-service-def.js';\r\n\r\ntype RpcClientFn = (...args: unknown[]) => unknown;\r\n\r\n/** Creates a typed RPC client proxy — intercepts method calls and sends them over RPC. */\r\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T is used for caller-specified proxy type\r\nexport function createRpcClient<T extends object>(\r\n peer: RpcPeer,\r\n serviceDef: RpcServiceDef\r\n): T {\r\n // Group methods by clean name, indexed by argCount for overload resolution\r\n const overloads = new Map<string, Map<number, RpcMethodDef>>();\r\n for (const methodDef of serviceDef.methods.values()) {\r\n let byArgCount = overloads.get(methodDef.name);\r\n if (!byArgCount) {\r\n byArgCount = new Map<number, RpcMethodDef>();\r\n overloads.set(methodDef.name, byArgCount);\r\n }\r\n byArgCount.set(methodDef.argCount, methodDef);\r\n }\r\n\r\n // Build proxy methods\r\n const methods = new Map<string, RpcClientFn>();\r\n for (const [name, byArgCount] of overloads) {\r\n if (byArgCount.size === 1) {\r\n const [[, methodDef]] = byArgCount;\r\n methods.set(name, createMethod(peer, methodDef));\r\n } else {\r\n const fns = new Map<number, RpcClientFn>();\r\n for (const [argCount, methodDef] of byArgCount) {\r\n fns.set(argCount, createMethod(peer, methodDef));\r\n }\r\n methods.set(name, (...args: unknown[]) => {\r\n const fn = fns.get(args.length);\r\n if (!fn)\r\n throw new Error(\r\n `No overload of ${name} accepts ${args.length} arguments`\r\n );\r\n return fn(...args);\r\n });\r\n }\r\n }\r\n\r\n return new Proxy({} as T, {\r\n get(_target, prop) {\r\n if (typeof prop !== 'string') return undefined;\r\n return methods.get(prop);\r\n },\r\n });\r\n}\r\n\r\nfunction createMethod(peer: RpcPeer, methodDef: RpcMethodDef): RpcClientFn {\r\n const mode = methodDef.remoteExecutionMode;\r\n const mustCheckConnection = !(mode & RpcRemoteExecutionMode.AwaitForConnection);\r\n return async (...args: unknown[]) => {\r\n if (mustCheckConnection && !peer.isConnected)\r\n throw new Error('Outbound call failed: not connected and AwaitForConnection is not set.');\r\n const callArgs = args.slice(0, methodDef.argCount);\r\n const wireName = wireMethodName(methodDef);\r\n const options =\r\n (methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default || methodDef.timeouts !== undefined)\r\n ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode, timeouts: methodDef.timeouts }\r\n : undefined;\r\n const outboundCall = peer.call(wireName, callArgs, options);\r\n // Keep `.promise` here: this is the proxy returning a Promise to user\r\n // code. Returning the PromiseSource directly would expose `resolve()`/\r\n // `reject()` to anyone who happens to type the result as PromiseSource.\r\n return outboundCall.result.promise;\r\n };\r\n}\r\n","// .NET counterpart: ActualLab.Fusion.Extensions.RpcPeerState\r\n\r\nexport const enum RpcPeerStateKind {\r\n Connected = 0,\r\n JustDisconnected = 1,\r\n Disconnected = 2,\r\n JustConnected = 3,\r\n}\r\n\r\nexport interface RpcPeerState {\r\n readonly kind: RpcPeerStateKind;\r\n readonly lastError?: Error;\r\n readonly reconnectsIn: number; // ms, 0 = no countdown\r\n}\r\n\r\nexport function isConnected(state: RpcPeerState): boolean {\r\n return (\r\n state.kind === RpcPeerStateKind.Connected ||\r\n state.kind === RpcPeerStateKind.JustConnected\r\n );\r\n}\r\n\r\nexport function likelyConnected(state: RpcPeerState): boolean {\r\n return state.kind !== RpcPeerStateKind.Disconnected;\r\n}\r\n\r\nexport function getStateDescription(\r\n state: RpcPeerState,\r\n useLastError = false\r\n): string {\r\n switch (state.kind) {\r\n case RpcPeerStateKind.JustConnected:\r\n return 'Just connected.';\r\n case RpcPeerStateKind.Connected:\r\n return 'Connected.';\r\n case RpcPeerStateKind.JustDisconnected:\r\n return 'Just disconnected, reconnecting...';\r\n case RpcPeerStateKind.Disconnected:\r\n if (state.reconnectsIn > 0) break; // fall through to error message below\r\n return 'Reconnecting...';\r\n }\r\n if (state.lastError == null || !useLastError) return 'Disconnected.';\r\n\r\n let message = state.lastError.message.trim();\r\n if (\r\n !(\r\n message.endsWith('.') ||\r\n message.endsWith('!') ||\r\n message.endsWith('?')\r\n )\r\n )\r\n message += '.';\r\n return 'Disconnected: ' + message;\r\n}\r\n","// .NET counterpart: ActualLab.Fusion.Extensions.RpcPeerStateMonitor\n// Simplified TS version using EventHandlerSet + setTimeout (no Fusion computed state).\n\nimport { EventHandlerSet } from '@actuallab/core';\nimport { getLogs } from './logging.js';\nimport { RpcConnectionState, type RpcClientPeer } from './rpc-peer.js';\nimport { type RpcPeerState, RpcPeerStateKind } from './rpc-peer-state.js';\n\nconst { infoLog } = getLogs('RpcPeerStateMonitor');\n\nconst JustConnectedPeriodMs = 1500;\nconst JustDisconnectedPeriodMs = 3000;\nconst MinReconnectsInMs = 1000;\n\nexport class RpcPeerStateMonitor {\n readonly peer: RpcClientPeer;\n readonly stateChanged = new EventHandlerSet<RpcPeerState>();\n\n private _rawConnected: boolean;\n private _connectedAt = 0;\n private _disconnectedAt = 0;\n private _lastError: Error | undefined;\n private _state: RpcPeerState;\n private _timer: ReturnType<typeof setTimeout> | undefined;\n\n // Arrow functions to preserve `this` when used as event handlers\n private _onConnectionStateChanged = (state: RpcConnectionState): void => {\n if (state === RpcConnectionState.Connected) {\n this._rawConnected = true;\n this._connectedAt = Date.now();\n this._lastError = undefined;\n } else {\n const wasConnected = this._rawConnected;\n this._rawConnected = false;\n // We no longer receive a close code/reason — only the state.\n // Retain a generic error for UI hints.\n this._lastError = new Error('Connection closed');\n if (wasConnected) {\n // True connected→disconnected transition — start JustDisconnected period\n this._disconnectedAt = Date.now();\n }\n // When wasConnected=false, this is a failed reconnect attempt —\n // keep the original _disconnectedAt so the countdown isn't reset.\n }\n this._recompute();\n };\n\n private _onReconnectsAtChanged = (): void => {\n this._recompute();\n };\n\n private _onCancelDelaysChanged = (): void => {\n this._recompute();\n };\n\n constructor(peer: RpcClientPeer) {\n this.peer = peer;\n this._rawConnected = peer.isConnected;\n\n if (this._rawConnected) {\n this._connectedAt = Date.now();\n this._state = {\n kind: RpcPeerStateKind.JustConnected,\n reconnectsIn: 0,\n };\n } else {\n this._disconnectedAt = Date.now();\n this._state = {\n kind: RpcPeerStateKind.JustDisconnected,\n reconnectsIn: 0,\n };\n }\n\n peer.connectionStateChanged.add(this._onConnectionStateChanged);\n peer.reconnectsAtChanged.add(this._onReconnectsAtChanged);\n peer.hub.reconnectDelayer.cancelDelaysChanged.add(\n this._onCancelDelaysChanged\n );\n\n // Schedule initial transition from Just* states\n this._scheduleRecompute(\n this._rawConnected\n ? JustConnectedPeriodMs\n : JustDisconnectedPeriodMs\n );\n // Mirrors RpcPeerStateMonitor.cs:87 — \"monitor (re)started\".\n infoLog?.log(`'${peer.ref}': monitor started`);\n }\n\n get state(): RpcPeerState {\n return this._state;\n }\n\n dispose(): void {\n if (this._timer !== undefined) {\n clearTimeout(this._timer);\n this._timer = undefined;\n }\n this.peer.connectionStateChanged.remove(this._onConnectionStateChanged);\n this.peer.reconnectsAtChanged.remove(this._onReconnectsAtChanged);\n this.peer.hub.reconnectDelayer.cancelDelaysChanged.remove(\n this._onCancelDelaysChanged\n );\n // Mirrors RpcPeerStateMonitor.cs:125 — \"monitor stopped\".\n infoLog?.log(`'${this.peer.ref}': monitor stopped`);\n }\n\n private _recompute(): void {\n if (this._timer !== undefined) {\n clearTimeout(this._timer);\n this._timer = undefined;\n }\n\n const now = Date.now();\n let newState: RpcPeerState;\n\n if (this._rawConnected) {\n const connectedFor = now - this._connectedAt;\n if (connectedFor < JustConnectedPeriodMs) {\n newState = {\n kind: RpcPeerStateKind.JustConnected,\n reconnectsIn: 0,\n };\n this._scheduleRecompute(JustConnectedPeriodMs - connectedFor);\n } else {\n newState = {\n kind: RpcPeerStateKind.Connected,\n reconnectsIn: 0,\n };\n }\n } else {\n const disconnectedFor = now - this._disconnectedAt;\n if (disconnectedFor < JustDisconnectedPeriodMs) {\n newState = {\n kind: RpcPeerStateKind.JustDisconnected,\n lastError: this._lastError,\n reconnectsIn: 0,\n };\n this._scheduleRecompute(\n JustDisconnectedPeriodMs - disconnectedFor\n );\n } else {\n const reconnectsAt = this.peer.reconnectsAt;\n const reconnectsIn =\n reconnectsAt > 0 ? Math.max(0, reconnectsAt - now) : 0;\n if (reconnectsIn < MinReconnectsInMs) {\n newState = {\n kind: RpcPeerStateKind.Disconnected,\n lastError: this._lastError,\n reconnectsIn: 0,\n };\n } else {\n newState = {\n kind: RpcPeerStateKind.Disconnected,\n lastError: this._lastError,\n reconnectsIn,\n };\n // Recompute periodically to update countdown\n this._scheduleRecompute(\n Math.min(1000, reconnectsIn - MinReconnectsInMs)\n );\n }\n }\n }\n\n this._setState(newState);\n }\n\n private _setState(newState: RpcPeerState): void {\n const old = this._state;\n if (\n old.kind === newState.kind &&\n old.lastError === newState.lastError &&\n old.reconnectsIn === newState.reconnectsIn\n )\n return;\n\n this._state = newState;\n this.stateChanged.trigger(newState);\n }\n\n private _scheduleRecompute(delayMs: number): void {\n if (this._timer !== undefined) clearTimeout(this._timer);\n this._timer = setTimeout(\n () => {\n this._timer = undefined;\n this._recompute();\n },\n Math.max(0, delayMs)\n );\n }\n}\n"],"mappings":"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,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EA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","names":["RpcCallTypeId","import_msgpack","import_msgpack","import_core","warnLog","import_core","warnLog","import_core","b","warnLog","import_core","import_core","warnLog","warnLog","DEFAULT_ACK_PERIOD","DEFAULT_ACK_ADVANCE","Denque","import_core","import_msgpack","textEncoder","debugLog","warnLog","errorLog","call","RpcConnectionState","RpcPeerChangeKind","bytes","_toBytes","import_core","warnLog","import_core","textEncoder","warnLog","warnLog","RpcPeerStateKind","import_core","infoLog"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/logging.ts","../src/rpc-message.ts","../src/rpc-serialization.ts","../src/msgpack-map-patch.ts","../src/rpc-connection.ts","../src/rpc-message-channel-connection.ts","../src/rpc-service-def.ts","../src/rpc-call-tracker.ts","../src/rpc-call-stage.ts","../src/rpc-call-timeouts.ts","../src/rpc-error.ts","../src/increasing-seq-compressor.ts","../src/base64.ts","../src/rpc-system-call-handler.ts","../src/rpc-system-call-sender.ts","../src/rpc-object.ts","../src/rpc-stream.ts","../src/rpc-stream-sender.ts","../src/rpc-remote-object-tracker.ts","../src/rpc-shared-object-tracker.ts","../src/rpc-peer.ts","../src/rpc-serialization-format.ts","../src/reconnect-proof.ts","../src/rpc-limits.ts","../src/rpc-client-peer-reconnect-delayer.ts","../src/rpc-service-host.ts","../src/rpc-decorators.ts","../src/rpc-xxhash3.ts","../src/rpc-method-registry.ts","../src/rpc-hub.ts","../src/rpc-ref-builder.ts","../src/rpc-client.ts","../src/rpc-peer-state.ts","../src/rpc-peer-state-monitor.ts"],"sourcesContent":["export type { RpcLogScope } from './logging.js';\nexport { getLogs } from './logging.js';\n\nexport {\n RpcCallTypeId,\n RpcSystemCalls,\n ENVELOPE_DELIMITER,\n ARG_DELIMITER,\n FRAME_DELIMITER,\n} from './rpc-message.js';\nexport type { RpcMessage } from './rpc-message.js';\n\nexport {\n serializeMessage,\n serializeFrame,\n splitFrame,\n deserializeMessage,\n serializeBinaryMessage,\n deserializeBinaryMessage,\n splitBinaryFrame,\n serializeBinaryFrame,\n createBinaryEncoder,\n defaultBinaryEncoder,\n defaultBinaryDecoder,\n readPolymorphismMarker,\n} from './rpc-serialization.js';\n\nexport type {\n WebSocketLike,\n RpcConnection,\n RpcReceivedMessage,\n} from './rpc-connection.js';\nexport { WebSocketState, RpcWebSocketConnection } from './rpc-connection.js';\n\nexport {\n RpcMessageChannelConnection,\n createMessageChannelPair,\n} from './rpc-message-channel-connection.js';\n\nexport type {\n RpcMethodDef,\n RpcServiceDef,\n RpcMethodDefInput,\n} from './rpc-service-def.js';\nexport {\n RpcType,\n RpcRemoteExecutionMode,\n defineRpcService,\n wireMethodName,\n} from './rpc-service-def.js';\n\nexport {\n RpcOutboundCall,\n RpcOutboundCallTracker,\n RpcInboundCall,\n RpcInboundCallTracker,\n} from './rpc-call-tracker.js';\n\nexport { RpcCallStage } from './rpc-call-stage.js';\nexport { RpcCallTimeouts } from './rpc-call-timeouts.js';\nexport {\n RpcError,\n REMOTE_EXCEPTION_TYPE_REF,\n toExceptionInfo,\n genericErrorFilter,\n} from './rpc-error.js';\nexport type { RpcExceptionInfo, RpcErrorFilter } from './rpc-error.js';\nexport { IncreasingSeqCompressor } from './increasing-seq-compressor.js';\n\nexport { RpcSystemCallHandler } from './rpc-system-call-handler.js';\nexport { RpcSystemCallSender } from './rpc-system-call-sender.js';\n\nexport type { RpcObjectId, IRpcObject } from './rpc-object.js';\nexport { RpcObjectKind } from './rpc-object.js';\nexport { RpcStream, parseStreamRef, toRpcStream } from './rpc-stream.js';\nexport type { RpcStreamRef, RpcStreamOptions, RpcStreamSource } from './rpc-stream.js';\nexport { RpcStreamSender } from './rpc-stream-sender.js';\nexport { RpcRemoteObjectTracker } from './rpc-remote-object-tracker.js';\nexport { RpcSharedObjectTracker } from './rpc-shared-object-tracker.js';\n\nexport {\n RpcPeer,\n RpcConnectionState,\n RpcPeerChangeKind,\n RpcClientPeer,\n RpcServerPeer,\n RPC_CLOSE_CODE_UNSUPPORTED_FORMAT,\n defaultConnectionUrlResolver,\n} from './rpc-peer.js';\nexport { RpcLimits } from './rpc-limits.js';\nexport type {\n RemoteHandshake,\n RpcCallOptions,\n RpcConnectionUrlResolver,\n} from './rpc-peer.js';\nexport { computeReconnectProof, isReconnectProofSupported } from './reconnect-proof.js';\nexport type { RpcReconnectProofParameters } from './reconnect-proof.js';\nexport { RpcHub } from './rpc-hub.js';\nexport type { RpcPeerFactory } from './rpc-hub.js';\nexport { RpcRefBuilder } from './rpc-ref-builder.js';\nexport { RpcServiceHost } from './rpc-service-host.js';\nexport type { RpcServiceImpl, RpcDispatchContext } from './rpc-service-host.js';\nexport { createRpcClient } from './rpc-client.js';\nexport type { WebSocketServer } from './rpc-server.js';\n\nexport {\n rpcService,\n rpcMethod,\n getServiceMeta,\n getMethodsMeta,\n} from './rpc-decorators.js';\nexport type { MethodMeta, ServiceMeta } from './rpc-decorators.js';\n\nexport { RpcClientPeerReconnectDelayer } from './rpc-client-peer-reconnect-delayer.js';\nexport type { RpcPeerState } from './rpc-peer-state.js';\nexport {\n RpcPeerStateKind,\n isConnected,\n likelyConnected,\n getStateDescription,\n} from './rpc-peer-state.js';\nexport { RpcPeerStateMonitor } from './rpc-peer-state-monitor.js';\n\nexport {\n RpcSerializationFormat,\n RpcSerializationFormatResolver,\n RpcJsonSerializationFormat,\n RpcMessagePackSerializationFormat,\n RpcMessagePackCompactSerializationFormat,\n} from './rpc-serialization-format.js';\nexport type {\n RpcDeserializedMessage,\n RpcWireData,\n} from './rpc-serialization-format.js';\n\nexport { RpcMethodRegistry } from './rpc-method-registry.js';\nexport { xxh3_64, xxh3_64str, computeMethodHash } from './rpc-xxhash3.js';\n\nexport {\n serializeCompactBinaryMessage,\n deserializeCompactBinaryMessage,\n splitCompactBinaryFrame,\n} from './rpc-serialization.js';\n","import { createLogProvider, LogLevel } from '@actuallab/core';\n\n// Per-package log scopes. Use createLogProvider to get a typed helper:\n// const { debugLog, warnLog } = getLogs('RpcPeer');\n// Final scope name is 'rpc.RpcPeer' — the prefix isolates this package's\n// scopes from other packages' scopes when overriding levels at runtime.\nexport type RpcLogScope =\n | 'RpcCallTracker'\n | 'RpcClient'\n | 'RpcClientPeerReconnectDelayer'\n | 'RpcConnection'\n | 'RpcHub'\n | 'RpcInterceptor'\n | 'RpcMessageChannelConnection'\n | 'RpcMethodRegistry'\n | 'RpcOutboundCall'\n | 'RpcPeer'\n | 'RpcPeerStateMonitor'\n | 'RpcRemoteObjectTracker'\n | 'RpcSerialization'\n | 'RpcServiceHost'\n | 'RpcSharedObjectTracker'\n | 'RpcSharedStream'\n | 'RpcStream'\n | 'RpcStreamSender'\n | 'RpcSystemCallHandler'\n | 'RpcSystemCallSender'\n | 'RpcWebSocketClient';\n\n// Per-scope defaults. All Warn — output goes to the browser console, so the\n// out-of-the-box experience must be quiet. Users opt in to Info-level\n// connectivity tracing manually:\n// logLevels.override('rpc.*', LogLevel.Info)\n// or for a single scope:\n// logLevels.override('rpc.RpcPeer', LogLevel.Info)\nconst scopeDefaults: Record<RpcLogScope, LogLevel> = {\n RpcCallTracker: LogLevel.Warn,\n RpcClient: LogLevel.Warn,\n RpcClientPeerReconnectDelayer: LogLevel.Warn,\n RpcConnection: LogLevel.Warn,\n RpcHub: LogLevel.Warn,\n RpcInterceptor: LogLevel.Warn,\n RpcMessageChannelConnection: LogLevel.Warn,\n RpcMethodRegistry: LogLevel.Warn,\n RpcOutboundCall: LogLevel.Warn,\n RpcPeer: LogLevel.Info,\n RpcPeerStateMonitor: LogLevel.Warn,\n RpcRemoteObjectTracker: LogLevel.Warn,\n RpcSerialization: LogLevel.Warn,\n RpcServiceHost: LogLevel.Warn,\n RpcSharedObjectTracker: LogLevel.Warn,\n RpcSharedStream: LogLevel.Warn,\n RpcStream: LogLevel.Warn,\n RpcStreamSender: LogLevel.Warn,\n RpcSystemCallHandler: LogLevel.Warn,\n RpcSystemCallSender: LogLevel.Warn,\n RpcWebSocketClient: LogLevel.Warn,\n};\n\nexport const getLogs = createLogProvider<RpcLogScope>('rpc.', scopeDefaults);\n","// .NET counterparts:\n// RpcMessage — .NET has no single \"message\" type; the wire format is built by\n// RpcMessageSerializer / RpcOutboundMessage / RpcInboundMessage. In .NET the\n// envelope carries MethodRef, RelatedId, Headers, CallTypeId, and serialized\n// argument data as binary or text segments. In TS we use a simpler JSON\n// envelope + delimiter-separated args.\n//\n// Omitted from .NET:\n// - RpcCallTypeId Reliable (stage-based reconnection protocol) — the TS client\n// is browser-only; reconnection replays entire calls rather than resuming at\n// a stage, so Reliable call type adds no value.\n// - RpcHeader / RpcHeaders — .NET passes typed header arrays on every message\n// (Hash, ActivityContext, etc.). TS has no RPC caching, OpenTelemetry\n// propagation, or middleware pipeline, so headers aren't needed yet. The\n// Headers field on RpcMessage is reserved for future use.\n// - $sys.M (Match) / $sys.NotFound / $sys.Disconnect / Stream calls\n// (Ack, AckEnd, I, B, End) — Match is for hash-based response caching\n// (bandwidth optimisation); NotFound is a server-side \"service not registered\"\n// response; Disconnect is for RpcSharedObject lifetime management; the stream\n// calls are for RpcStream (server→client IAsyncEnumerable). None of these\n// are needed in the TS client today because:\n// * No response caching → no Match.\n// * Service-not-found is sent as $sys.Error.\n// * No shared-object tracking (server-side concept) → no Disconnect.\n// * No RpcStream yet → no Ack/AckEnd/I/B/End.\n// They can be added when streaming or caching is ported.\n// - Binary serialization variants (RpcByteMessageSerializer) — TS uses\n// text (JSON) exclusively; binary MessagePack/MemoryPack require native\n// codecs that aren't available in browsers.\n\n/** Identifies the type of an RPC call on the wire. */\nexport const enum RpcCallTypeId {\n Regular = 0,\n Reliable = 1,\n}\n\n/** Identifies system call methods — names include :argCount suffix for .NET interop. */\nexport const RpcSystemCalls = {\n handshake: '$sys.Handshake:1',\n ok: '$sys.Ok:1',\n error: '$sys.Error:1',\n cancel: '$sys.Cancel:0',\n keepAlive: '$sys.KeepAlive:1',\n reconnect: '$sys.Reconnect:3',\n ack: '$sys.Ack:2',\n ackEnd: '$sys.AckEnd:1',\n item: '$sys.I:2',\n batch: '$sys.B:2',\n end: '$sys.End:2',\n disconnect: '$sys.Disconnect:1',\n} as const;\n\n/** Wire-format RPC message envelope. */\nexport interface RpcMessage {\n CallType?: number;\n RelatedId?: number;\n Method?: string;\n Headers?: unknown[];\n}\n\n// Delimiters used in the wire format\nexport const ENVELOPE_DELIMITER = '\\n'; // \\x0A between envelope and args\nexport const ARG_DELIMITER = '\\x1F'; // Unit Separator between args\nexport const FRAME_DELIMITER = '\\n\\x1E'; // \\x0A\\x1E between messages in a frame\n","// Serialization for the RPC wire format.\r\n//\r\n// Supports two formats:\r\n// - Text (json5np): JSON envelope + delimiter-separated JSON args (V3 wire format)\r\n// - Binary (msgpack6np): V5 binary envelope + MessagePack-encoded args\r\n//\r\n// The format is selected at connection time via the `f=` query parameter.\r\n\r\nimport {\r\n Encoder,\r\n Decoder,\r\n} from '@msgpack/msgpack';\r\n// Side-effect import: patches Encoder.prototype to handle JS Map instances\r\n// as proper msgpack maps with typed keys (.NET-wire-compatible).\r\nimport './msgpack-map-patch.js';\r\nimport { getLogs } from './logging.js';\r\nimport type { RpcMessage } from './rpc-message.js';\r\nimport {\r\n ENVELOPE_DELIMITER,\r\n ARG_DELIMITER,\r\n FRAME_DELIMITER,\r\n} from './rpc-message.js';\r\n\r\nconst { warnLog } = getLogs('RpcSerialization');\r\n\r\n// ============================================================\r\n// Text format (json5np) — V3 wire format\r\n// ============================================================\r\n\r\n/** Serializes an RpcMessage + args into the json5 wire format. */\r\nexport function serializeMessage(\r\n message: RpcMessage,\r\n args?: unknown[]\r\n): string {\r\n const envelope = JSON.stringify(message);\r\n if (args === undefined || args.length === 0) {\r\n return envelope + ENVELOPE_DELIMITER;\r\n }\r\n const argsStr = args.map(a => JSON.stringify(a)).join(ARG_DELIMITER);\r\n return envelope + ENVELOPE_DELIMITER + argsStr;\r\n}\r\n\r\n/** Serializes multiple messages into a single WebSocket frame. */\r\nexport function serializeFrame(messages: string[]): string {\r\n return messages.join(FRAME_DELIMITER);\r\n}\r\n\r\n/** Splits a WebSocket text frame into individual message strings. */\r\nexport function splitFrame(frame: string): string[] {\r\n return frame.split(FRAME_DELIMITER);\r\n}\r\n\r\n/** Deserializes a single message string into envelope + args. */\r\nexport function deserializeMessage(raw: string): {\r\n message: RpcMessage;\r\n args: unknown[];\r\n} {\r\n const nlIndex = raw.indexOf(ENVELOPE_DELIMITER);\r\n if (nlIndex === -1) {\r\n return { message: JSON.parse(raw) as RpcMessage, args: [] };\r\n }\r\n\r\n const envelopeStr = raw.substring(0, nlIndex);\r\n const argsStr = raw.substring(nlIndex + 1);\r\n const message = JSON.parse(envelopeStr) as RpcMessage;\r\n\r\n if (argsStr.length === 0) {\r\n return { message, args: [] };\r\n }\r\n\r\n const args = argsStr.split(ARG_DELIMITER).map(s => {\r\n try {\r\n return JSON.parse(s) as unknown;\r\n } catch {\r\n warnLog?.log(`failed to parse arg as JSON: ${s.substring(0, 100)}`);\r\n return s; // Return raw string if not valid JSON\r\n }\r\n });\r\n\r\n return { message, args };\r\n}\r\n\r\n// ============================================================\r\n// Binary format (msgpack6np) — V5 wire format\r\n// ============================================================\r\n\r\nconst textEncoder = new TextEncoder();\r\nconst textDecoder = new TextDecoder();\r\n\r\n/**\r\n * Default module-level encoder/decoder. RpcWebSocketConnection passes its own\r\n * per-connection instances for the hot path; these defaults cover callers\r\n * that don't bother (tests, one-shot uses).\r\n *\r\n * Reusing an Encoder across calls is critical at high message rates because\r\n * - construction cost (internal resolvers, buffer allocation) is nontrivial;\r\n * - the internal write buffer stays at its largest observed size after the\r\n * first few encodes, eliminating `resizeBuffer` growth churn (which was\r\n * ~5% self time in the 300-pull profile).\r\n *\r\n * NOT thread-safe and NOT re-entrant. Safe under Node's single-threaded event\r\n * loop as long as `serializeBinaryMessage` is called synchronously (which it\r\n * always is — no awaits inside).\r\n *\r\n * Sized large enough to fit a typical video frame (~11 KB) + envelope\r\n * overhead without any `resizeBuffer` growth.\r\n *\r\n * `useBigInt64: true` makes the encoder emit JS `bigint` values as msgpack\r\n * int64/uint64 (0xd3/0xcf) instead of float64. Required for .NET `long`\r\n * fields whose 2026-era values (e.g. `Moment.EpochOffsetTicks` ~1.8×10¹⁶)\r\n * exceed `Number.MAX_SAFE_INTEGER` — a float64 payload would be rejected by\r\n * the server's `ReadInt64` path. Callers must pass a real `bigint` (not a\r\n * `number`) for such fields; `number` values follow the standard\r\n * int-or-float branching regardless of this flag.\r\n */\r\nconst INITIAL_ENCODER_BUFFER_SIZE = 32 * 1024;\r\nexport const defaultBinaryEncoder = new Encoder({ initialBufferSize: INITIAL_ENCODER_BUFFER_SIZE, useBigInt64: true });\r\nexport const defaultBinaryDecoder = new Decoder();\r\n/** Exposed so connection code can instantiate a matching Encoder\r\n * with the same initial buffer size without needing to reach into\r\n * this module's private constants. */\r\nexport function createBinaryEncoder(): Encoder {\r\n return new Encoder({ initialBufferSize: INITIAL_ENCODER_BUFFER_SIZE, useBigInt64: true });\r\n}\r\n\r\n// --- LEB128 VarUInt helpers ---\r\n\r\n/** Number of bytes a VarUint encoding of `value` will occupy. */\r\nfunction varUintByteLen(value: number): number {\r\n if (value < 0) value = 0;\r\n let n = 0;\r\n do {\r\n n++;\r\n value >>>= 7;\r\n } while (value > 0);\r\n return n;\r\n}\r\n\r\n/** Write VarUint directly into a `Uint8Array` at `pos`. Returns new pos. */\r\nfunction writeVarUintInto(out: Uint8Array, pos: number, value: number): number {\r\n if (value < 0) value = 0;\r\n do {\r\n let byte = value & 0x7f;\r\n value >>>= 7;\r\n if (value > 0) byte |= 0x80;\r\n out[pos++] = byte;\r\n } while (value > 0);\r\n return pos;\r\n}\r\n\r\nfunction readVarUint(\r\n data: Uint8Array,\r\n offset: number\r\n): { value: number; bytesRead: number } {\r\n let value = 0,\r\n shift = 0,\r\n bytesRead = 0;\r\n do {\r\n if (offset + bytesRead >= data.length) break;\r\n const byte = data[offset + bytesRead];\r\n value |= (byte & 0x7f) << shift;\r\n bytesRead++;\r\n if ((byte & 0x80) === 0) break;\r\n shift += 7;\r\n } while (shift < 35); // Max 5 bytes for uint32\r\n return { value: value >>> 0, bytesRead };\r\n}\r\n\r\n// --- Binary helpers ---\r\n\r\n// Smallest V5 envelope: flags byte + 1-byte VarUint relatedId + 1-byte VarUint\r\n// method length + 4-byte argument data length. V5Compact swaps the LVar method\r\n// name for a 4-byte hash. Both are used as the minimum per-message advance the\r\n// frame splitters accept, so a malformed envelope can never make them reparse\r\n// bytes they've already consumed.\r\nconst MIN_BINARY_ENVELOPE_SIZE = 7;\r\nconst MIN_COMPACT_BINARY_ENVELOPE_SIZE = 10;\r\n\r\n// Every wire-supplied length must pass through here before it's used to slice or\r\n// advance: an unvalidated one lets a hostile peer read past the frame or (when\r\n// read as a signed int) move the cursor backwards.\r\nfunction requireInBounds(data: Uint8Array, pos: number, length: number, what: string): number {\r\n const end = pos + length;\r\n if (!Number.isSafeInteger(length) || length < 0 || end > data.length)\r\n throw new Error(`Invalid binary RPC message: ${what} length ${length} at ${pos} is out of bounds.`);\r\n\r\n return end;\r\n}\r\n\r\n// Skips `headerCount` V5 headers (key: L1Memory, value: LVarSpan) starting at\r\n// `pos`, returning the position past them. The TS client never emits headers,\r\n// but .NET does (e.g. Activity injection), and per the V5 layout they follow\r\n// the argument data — so they must be accounted for in `bytesRead`.\r\nfunction skipHeaders(data: Uint8Array, pos: number, headerCount: number): number {\r\n for (let h = 0; h < headerCount; h++) {\r\n if (pos >= data.length)\r\n throw new Error('Invalid binary RPC message: truncated header.');\r\n\r\n const keyLen = data[pos++];\r\n pos = requireInBounds(data, pos, keyLen, 'header key');\r\n const valLen = readVarUint(data, pos);\r\n if (valLen.bytesRead === 0)\r\n throw new Error('Invalid binary RPC message: truncated header value length.');\r\n\r\n pos = requireInBounds(data, pos + valLen.bytesRead, valLen.value, 'header value');\r\n }\r\n return pos;\r\n}\r\n\r\n/**\r\n * Parses a polymorphism marker (as written by .NET `ByteTypeSerializer`) at\r\n * `offset`. The msgpack6 format prefixes every *polymorphic* argument/result\r\n * with a 2-byte little-endian length:\r\n * - 0 → expected (non-polymorphic) type — the `\\0\\0` fast path\r\n * - 1 → NullValue (the value itself is absent)\r\n * - >= 2 → a derived type (2-byte length + 2-byte hash + UTF-8 name)\r\n *\r\n * Returns how many bytes the marker occupies and whether the value is a\r\n * NullValue. Derived-type markers are unsupported by the TS client and throw a\r\n * clear error instead of letting the marker bytes corrupt the msgpack frame.\r\n */\r\nexport function readPolymorphismMarker(\r\n data: Uint8Array,\r\n offset: number\r\n): { bytesRead: number; isNull: boolean } {\r\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\r\n const length = view.getUint16(offset, true);\r\n if (length === 0)\r\n return { bytesRead: 2, isNull: false };\r\n if (length === 1)\r\n return { bytesRead: 2, isNull: true };\r\n\r\n throw new Error('polymorphic payloads are not supported by the TS client');\r\n}\r\n\r\nfunction concatUint8Arrays(arrays: Uint8Array[]): Uint8Array {\r\n let totalLength = 0;\r\n for (const arr of arrays) totalLength += arr.length;\r\n const result = new Uint8Array(totalLength);\r\n let offset = 0;\r\n for (const arr of arrays) {\r\n result.set(arr, offset);\r\n offset += arr.length;\r\n }\r\n return result;\r\n}\r\n\r\n/**\r\n * Serializes an RpcMessage + args into V5 binary format (msgpack6np).\r\n *\r\n * Layout (NO frame-level size prefix; V5 with PersistsMessageSize=false):\r\n * envelope + argData\r\n * Envelope:\r\n * [byte 0: (CallTypeId<<5) | HeaderCount] [VarUint relatedId]\r\n * [LVarSpan methodRef] [4-byte LE argLen]\r\n * ArgData: concatenated MessagePack-encoded arguments\r\n *\r\n * Single-allocation build: pre-encodes args with the supplied reusable\r\n * `Encoder`, computes the total envelope size, then writes everything\r\n * into one `Uint8Array`. Eliminates the previous build's intermediate\r\n * `headerParts` number array + four `Uint8Array` concats per call,\r\n * which showed up as ~2% self time in the 300-pull profile and\r\n * pulled the msgpack `resizeBuffer` + `Encoder` constructor along\r\n * with it for another ~6%.\r\n *\r\n * NOTE: `@msgpack/msgpack` `Encoder.encode()` already returns a fresh\r\n * copy of the internal buffer (there's a separate `encodeSharedRef()`\r\n * for zero-copy access), so we can push the results straight into\r\n * `argBufs` without an extra slice. The benefit of reusing the Encoder\r\n * is that its internal write buffer stays at its high-water mark —\r\n * once the first ~11 KB frame has grown it to the full size, no\r\n * subsequent `resizeBuffer` calls are needed.\r\n */\r\nexport function serializeBinaryMessage(\r\n message: RpcMessage,\r\n args?: unknown[],\r\n encoder: Encoder = defaultBinaryEncoder\r\n): Uint8Array {\r\n // 1. Encode each arg. encode() returns a fresh Uint8Array copy per\r\n // call, so the results are safe to retain across subsequent encodes.\r\n let argsDataLen = 0;\r\n let argBufs: Uint8Array[] | null = null;\r\n if (args && args.length > 0) {\r\n argBufs = new Array<Uint8Array>(args.length);\r\n for (let i = 0; i < args.length; i++) {\r\n const buf = encoder.encode(args[i]);\r\n argBufs[i] = buf;\r\n argsDataLen += buf.length;\r\n }\r\n }\r\n\r\n // 2. Compute header sizes without intermediate allocations.\r\n const callType = message.CallType ?? 0;\r\n const relatedId = message.RelatedId ?? 0;\r\n const methodBytes = textEncoder.encode(message.Method ?? '');\r\n const relatedIdLen = varUintByteLen(relatedId);\r\n const methodLenVarintLen = varUintByteLen(methodBytes.length);\r\n const headerSize =\r\n 1 + relatedIdLen + methodLenVarintLen + methodBytes.length;\r\n const totalSize = headerSize + 4 + argsDataLen;\r\n\r\n // 3. Allocate the final wire buffer once and fill it in place.\r\n const out = new Uint8Array(totalSize);\r\n let pos = 0;\r\n out[pos++] = (callType << 5) & 0xe0;\r\n pos = writeVarUintInto(out, pos, relatedId);\r\n pos = writeVarUintInto(out, pos, methodBytes.length);\r\n out.set(methodBytes, pos);\r\n pos += methodBytes.length;\r\n // argLen as int32 LE\r\n out[pos++] = argsDataLen & 0xff;\r\n out[pos++] = (argsDataLen >>> 8) & 0xff;\r\n out[pos++] = (argsDataLen >>> 16) & 0xff;\r\n out[pos++] = (argsDataLen >>> 24) & 0xff;\r\n if (argBufs !== null) {\r\n for (const buf of argBufs) {\r\n out.set(buf, pos);\r\n pos += buf.length;\r\n }\r\n }\r\n return out;\r\n}\r\n\r\n/**\r\n * Deserializes a single V5 binary message starting at `offset`.\r\n * Returns parsed message, args, and number of bytes consumed.\r\n *\r\n * Accepts an optional `Decoder` for reuse across calls on the same\r\n * connection — avoids constructing a new `@msgpack/msgpack.Decoder`\r\n * (and its internal resolver/table state) on every inbound message.\r\n */\r\nexport function deserializeBinaryMessage(\r\n data: Uint8Array,\r\n offset: number,\r\n decoder: Decoder = defaultBinaryDecoder\r\n): { message: RpcMessage; args: unknown[]; bytesRead: number } {\r\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\r\n if (offset + MIN_BINARY_ENVELOPE_SIZE > data.length)\r\n throw new Error(`Invalid binary RPC message: truncated envelope at ${offset}.`);\r\n\r\n // V5: no frame-level size prefix. Parse envelope directly.\r\n let pos = offset;\r\n\r\n // Byte 0: upper 3 bits = CallTypeId, lower 5 bits = HeaderCount\r\n const byte0 = data[pos++];\r\n const callTypeId = (byte0 >> 5) & 0x7;\r\n const headerCount = byte0 & 0x1f;\r\n\r\n // RelatedId as VarUint\r\n const relId = readVarUint(data, pos);\r\n pos += relId.bytesRead;\r\n\r\n // MethodRef as LVarSpan\r\n const methodLen = readVarUint(data, pos);\r\n pos += methodLen.bytesRead;\r\n const methodEnd = requireInBounds(data, pos, methodLen.value, 'method');\r\n const method = textDecoder.decode(data.subarray(pos, methodEnd));\r\n pos = methodEnd;\r\n\r\n // ArgData length as fixed 4-byte LE, immediately followed by argData (V5\r\n // writes headers AFTER the argument data — see RpcByteMessageSerializerV5).\r\n // Must be read unsigned: as a signed int it can be negative, which moves the\r\n // cursor backwards and makes the frame splitter reparse the same bytes.\r\n if (pos + 4 > data.length)\r\n throw new Error(`Invalid binary RPC message: truncated argument data length at ${pos}.`);\r\n\r\n const argDataLen = view.getUint32(pos, true);\r\n pos += 4;\r\n\r\n // Deserialize arguments from argData — multiple concatenated MessagePack values\r\n const args: unknown[] = [];\r\n const argEnd = requireInBounds(data, pos, argDataLen, 'argument data');\r\n if (argDataLen > 0) {\r\n const argSlice = data.subarray(pos, argEnd);\r\n // Reuse the provided decoder across calls; `decodeMulti` is a generator\r\n // so each yield completes before the next `decode` begins, which is\r\n // safe on a single-threaded event loop.\r\n for (const decoded of decoder.decodeMulti(argSlice)) {\r\n args.push(decoded);\r\n }\r\n }\r\n pos = argEnd;\r\n\r\n // Headers (if any) follow the argument data. The TS client ignores their\r\n // content but must skip them so `bytesRead` covers the whole message.\r\n if (headerCount > 0)\r\n pos = skipHeaders(data, pos, headerCount);\r\n\r\n const message: RpcMessage = {\r\n Method: method,\r\n RelatedId: relId.value,\r\n CallType: callTypeId,\r\n };\r\n\r\n return { message, args, bytesRead: pos - offset };\r\n}\r\n\r\n/**\r\n * Splits a binary WebSocket frame into individual deserialized messages.\r\n * V5 envelopes are self-delimiting (header + method + argLen + argData), so\r\n * multiple envelopes concatenated in a single WebSocket frame can be decoded\r\n * by iterating with the bytesRead returned from deserializeBinaryMessage.\r\n *\r\n * This must stay symmetric with serializeBinaryFrame() (which concatenates\r\n * multiple envelopes). The .NET server batches outbound RPC messages into a\r\n * single WebSocket frame via WebSocketChannel/WriteDelayer, so any assumption\r\n * that a frame is a single envelope silently drops every trailing message.\r\n *\r\n * Accepts an optional `Decoder` for reuse across calls on the same\r\n * connection — forwarded to `deserializeBinaryMessage`.\r\n */\r\nexport function splitBinaryFrame(\r\n frame: Uint8Array,\r\n decoder: Decoder = defaultBinaryDecoder\r\n): { message: RpcMessage; args: unknown[] }[] {\r\n const results: { message: RpcMessage; args: unknown[] }[] = [];\r\n let offset = 0;\r\n while (offset < frame.length) {\r\n let parsed: { message: RpcMessage; args: unknown[]; bytesRead: number };\r\n try {\r\n parsed = deserializeBinaryMessage(frame, offset, decoder);\r\n }\r\n catch (e) {\r\n // A malformed envelope leaves no resync point, but everything decoded\r\n // before it is still valid - and .NET batches many messages per frame,\r\n // so dropping them all would strand the $sys.Ok replies among them\r\n // (per-call timeouts are unbounded by default, i.e. those calls hang).\r\n warnLog?.log(`Failed to parse a binary message at offset ${offset}:`, e);\r\n break;\r\n }\r\n const { message, args, bytesRead } = parsed;\r\n // Anything below one minimal envelope means the parse went wrong; advancing\r\n // by it would reparse bytes already consumed and invent extra \"messages\".\r\n if (!Number.isFinite(bytesRead) || bytesRead < MIN_BINARY_ENVELOPE_SIZE)\r\n break;\r\n\r\n results.push({ message, args });\r\n offset += bytesRead;\r\n }\r\n return results;\r\n}\r\n\r\n/**\r\n * Serializes multiple binary messages into a single WebSocket frame.\r\n * Simply concatenates the already size-prefixed messages.\r\n */\r\nexport function serializeBinaryFrame(messages: Uint8Array[]): Uint8Array {\r\n return concatUint8Arrays(messages);\r\n}\r\n\r\n// ============================================================\r\n// Compact binary format (V5Compact) — 4-byte method hash\r\n// ============================================================\r\n\r\nimport type { RpcMethodRegistry } from './rpc-method-registry.js';\r\n\r\n/**\r\n * Serializes an RpcMessage + args into V5Compact binary format.\r\n * Same as V5 except the method is encoded as a 4-byte LE hash\r\n * instead of an LVar-prefixed UTF-8 string.\r\n *\r\n * Layout:\r\n * [byte 0: (CallTypeId<<5) | HeaderCount] [VarUint relatedId]\r\n * [4-byte LE methodHash] [4-byte LE argLen]\r\n * [argData]\r\n */\r\nexport function serializeCompactBinaryMessage(\r\n message: RpcMessage,\r\n args?: unknown[],\r\n registry?: RpcMethodRegistry,\r\n encoder: Encoder = defaultBinaryEncoder\r\n): Uint8Array {\r\n // 1. Encode args\r\n let argsDataLen = 0;\r\n let argBufs: Uint8Array[] | null = null;\r\n if (args && args.length > 0) {\r\n argBufs = new Array<Uint8Array>(args.length);\r\n for (let i = 0; i < args.length; i++) {\r\n const buf = encoder.encode(args[i]);\r\n argBufs[i] = buf;\r\n argsDataLen += buf.length;\r\n }\r\n }\r\n\r\n // 2. Get method hash\r\n const methodName = message.Method ?? '';\r\n const methodHash = registry?.requireHash(methodName) ?? 0;\r\n\r\n // 3. Compute sizes\r\n const callType = message.CallType ?? 0;\r\n const relatedId = message.RelatedId ?? 0;\r\n const relatedIdLen = varUintByteLen(relatedId);\r\n // Compact: 4 bytes for hash (instead of LVar method name)\r\n const headerSize = 1 + relatedIdLen + 4;\r\n const totalSize = headerSize + 4 + argsDataLen;\r\n\r\n // 4. Build wire buffer\r\n const out = new Uint8Array(totalSize);\r\n let pos = 0;\r\n out[pos++] = (callType << 5) & 0xe0;\r\n pos = writeVarUintInto(out, pos, relatedId);\r\n // Method hash as 4-byte LE uint32\r\n const hashU32 = methodHash >>> 0;\r\n out[pos++] = hashU32 & 0xff;\r\n out[pos++] = (hashU32 >>> 8) & 0xff;\r\n out[pos++] = (hashU32 >>> 16) & 0xff;\r\n out[pos++] = (hashU32 >>> 24) & 0xff;\r\n // argLen as int32 LE\r\n out[pos++] = argsDataLen & 0xff;\r\n out[pos++] = (argsDataLen >>> 8) & 0xff;\r\n out[pos++] = (argsDataLen >>> 16) & 0xff;\r\n out[pos++] = (argsDataLen >>> 24) & 0xff;\r\n if (argBufs !== null) {\r\n for (const buf of argBufs) {\r\n out.set(buf, pos);\r\n pos += buf.length;\r\n }\r\n }\r\n return out;\r\n}\r\n\r\n/**\r\n * Deserializes a single V5Compact binary message starting at `offset`.\r\n * Reads a 4-byte LE method hash and resolves it via the registry.\r\n */\r\nexport function deserializeCompactBinaryMessage(\r\n data: Uint8Array,\r\n offset: number,\r\n registry?: RpcMethodRegistry,\r\n decoder: Decoder = defaultBinaryDecoder\r\n): { message: RpcMessage; args: unknown[]; bytesRead: number } {\r\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\r\n if (offset + MIN_COMPACT_BINARY_ENVELOPE_SIZE > data.length)\r\n throw new Error(`Invalid compact binary RPC message: truncated envelope at ${offset}.`);\r\n\r\n let pos = offset;\r\n\r\n // Byte 0: upper 3 bits = CallTypeId, lower 5 bits = HeaderCount\r\n const byte0 = data[pos++];\r\n const callTypeId = (byte0 >> 5) & 0x7;\r\n const headerCount = byte0 & 0x1f;\r\n\r\n // RelatedId as VarUint\r\n const relId = readVarUint(data, pos);\r\n pos += relId.bytesRead;\r\n\r\n // Method hash as fixed 4-byte LE uint32\r\n if (pos + 8 > data.length)\r\n throw new Error(`Invalid compact binary RPC message: truncated envelope at ${offset}.`);\r\n\r\n const methodHash = view.getUint32(pos, true);\r\n pos += 4;\r\n const method =\r\n registry?.getName(methodHash | 0) ??\r\n `<hash:0x${methodHash.toString(16).padStart(8, '0')}>`;\r\n\r\n // ArgData length as fixed 4-byte LE, immediately followed by argData\r\n // (V5Compact writes headers AFTER the argument data, same as V5).\r\n // Unsigned for the same reason as in deserializeBinaryMessage.\r\n const argDataLen = view.getUint32(pos, true);\r\n pos += 4;\r\n\r\n // Deserialize arguments\r\n const args: unknown[] = [];\r\n const argEnd = requireInBounds(data, pos, argDataLen, 'argument data');\r\n if (argDataLen > 0) {\r\n const argSlice = data.subarray(pos, argEnd);\r\n for (const decoded of decoder.decodeMulti(argSlice)) {\r\n args.push(decoded);\r\n }\r\n }\r\n pos = argEnd;\r\n\r\n // Headers (if any) follow the argument data.\r\n if (headerCount > 0)\r\n pos = skipHeaders(data, pos, headerCount);\r\n\r\n const message: RpcMessage = {\r\n Method: method,\r\n RelatedId: relId.value,\r\n CallType: callTypeId,\r\n };\r\n\r\n return { message, args, bytesRead: pos - offset };\r\n}\r\n\r\n/**\r\n * Splits a compact binary frame into individual deserialized messages.\r\n */\r\nexport function splitCompactBinaryFrame(\r\n frame: Uint8Array,\r\n registry?: RpcMethodRegistry,\r\n decoder: Decoder = defaultBinaryDecoder\r\n): { message: RpcMessage; args: unknown[] }[] {\r\n const results: { message: RpcMessage; args: unknown[] }[] = [];\r\n let offset = 0;\r\n while (offset < frame.length) {\r\n let parsed: { message: RpcMessage; args: unknown[]; bytesRead: number };\r\n try {\r\n parsed = deserializeCompactBinaryMessage(frame, offset, registry, decoder);\r\n }\r\n catch (e) {\r\n warnLog?.log(`Failed to parse a compact binary message at offset ${offset}:`, e);\r\n break;\r\n }\r\n const { message, args, bytesRead } = parsed;\r\n if (!Number.isFinite(bytesRead) || bytesRead < MIN_COMPACT_BINARY_ENVELOPE_SIZE)\r\n break;\r\n\r\n results.push({ message, args });\r\n offset += bytesRead;\r\n }\r\n return results;\r\n}\r\n","import { Encoder } from '@msgpack/msgpack';\r\n\r\n/**\r\n * Patches `@msgpack/msgpack`'s `Encoder` so JS `Map` instances are encoded\r\n * as proper msgpack `map` types with typed keys (matching .NET's\r\n * `Dictionary<K, V>` wire format). Without this patch the encoder treats\r\n * `Map` as a plain object — since `Object.keys(new Map())` returns `[]`,\r\n * any `Map` is silently serialized as an empty `{}`.\r\n *\r\n * Idempotent: the patch is applied at most once per process.\r\n *\r\n * This is needed by the `$sys.Reconnect:3` protocol to send\r\n * `Dictionary<int, byte[]>` over msgpack in a way that a .NET server can\r\n * deserialize. The JSON side is naturally compatible (keys become strings\r\n * in JSON regardless) — this patch fixes the binary path only.\r\n */\r\n/* eslint-disable @typescript-eslint/no-explicit-any,\r\n @typescript-eslint/no-unsafe-member-access,\r\n @typescript-eslint/no-unsafe-call,\r\n @typescript-eslint/no-unsafe-assignment\r\n -- the entire patch reaches into private Encoder internals to make Map\r\n encoding work; the type annotations on `@msgpack/msgpack` don't expose\r\n these methods so all access is necessarily untyped. */\r\nexport function patchMsgpackEncoderForMaps(): void {\r\n const proto = Encoder.prototype as any;\r\n if (proto._hasMapPatch) return;\r\n proto._hasMapPatch = true;\r\n\r\n const originalDoEncode = proto.doEncode as (object: unknown, depth: number) => void;\r\n\r\n proto.doEncode = function (this: unknown, object: unknown, depth: number): void {\r\n if (object instanceof Map) {\r\n _encodeMapInstance.call(this, object, depth);\r\n return;\r\n }\r\n originalDoEncode.call(this, object, depth);\r\n };\r\n\r\n function _encodeMapInstance(this: unknown, m: Map<unknown, unknown>, depth: number): void {\r\n const self = this as any;\r\n const size = m.size;\r\n if (size < 16) {\r\n self.writeU8(0x80 + size);\r\n } else if (size < 0x10000) {\r\n self.writeU8(0xde);\r\n self.writeU16(size);\r\n } else {\r\n self.writeU8(0xdf);\r\n self.writeU32(size);\r\n }\r\n for (const [key, value] of m) {\r\n // Recurse via the (now patched) doEncode so nested Maps work too.\r\n self.doEncode(key, depth + 1);\r\n self.doEncode(value, depth + 1);\r\n }\r\n }\r\n}\r\n/* eslint-enable @typescript-eslint/no-explicit-any,\r\n @typescript-eslint/no-unsafe-member-access,\r\n @typescript-eslint/no-unsafe-call,\r\n @typescript-eslint/no-unsafe-assignment */\r\n\r\n// Apply at module load — idempotent.\r\npatchMsgpackEncoderForMaps();\r\n","// .NET counterparts:\r\n// RpcConnection — thin wrapper around RpcTransport + PropertyBag. RpcTransport\r\n// is an abstract IAsyncEnumerable<RpcInboundMessage> + Send() that can be\r\n// WebSocket-based or in-memory. The transport also handles frame batching and\r\n// back-pressure via a Channel<T> send queue.\r\n//\r\n// Omitted from .NET:\r\n// - PropertyBag on connection — used in .NET for per-connection metadata (e.g.\r\n// authentication info attached by middleware). TS has no middleware pipeline,\r\n// so no need for an extensible property bag.\r\n// - IsLocal flag — .NET distinguishes local loopback connections (same-process\r\n// server) from remote ones. TS is always a remote client.\r\n// - RpcTransport as IAsyncEnumerable — .NET reads inbound messages via\r\n// async iteration with cancellation. TS uses event-based delivery\r\n// (messageReceived handler) because browser WebSocket API is event-driven.\r\n// - Channel<RpcOutboundMessage> send queue with backpressure — .NET buffers\r\n// outbound messages in an async channel that the transport drains. TS sends\r\n// synchronously on the WebSocket; if the socket isn't ready, messages are\r\n// buffered in _sendBuffer (CONNECTING) or silently dropped (CLOSING/CLOSED).\r\n// JS is single-threaded so there's no contention, and WebSocket.send() itself\r\n// handles buffering at the OS level.\r\n// - IAsyncDisposable — .NET transports are disposable resources. TS connections\r\n// are closed via close() and garbage-collected.\r\n\r\nimport { Encoder, Decoder } from '@msgpack/msgpack';\r\nimport { PromiseSource, EventHandlerSet } from '@actuallab/core';\r\nimport { getLogs } from './logging.js';\r\nimport {\r\n splitFrame,\r\n serializeFrame,\r\n splitBinaryFrame as splitBinaryFrameFn,\r\n serializeBinaryFrame,\r\n createBinaryEncoder,\r\n} from './rpc-serialization.js';\r\nimport type { RpcMessage } from './rpc-message.js';\r\nimport type { RpcSerializationFormat } from './rpc-serialization-format.js';\r\nimport type { RpcMethodRegistry } from './rpc-method-registry.js';\r\n\r\nconst { warnLog } = getLogs('RpcConnection');\r\n\r\n/** Abstract WebSocket interface — works with both browser WebSocket and Node.js ws. */\r\nexport interface WebSocketLike {\r\n readonly readyState: number;\r\n binaryType?: string;\r\n send(data: string | ArrayBufferLike | Uint8Array | ArrayBufferView): void;\r\n close(code?: number, reason?: string): void;\r\n onopen: ((ev: unknown) => void) | null;\r\n onmessage: ((ev: { data: unknown }) => void) | null;\r\n onclose: ((ev: { code: number; reason: string }) => void) | null;\r\n onerror: ((ev: unknown) => void) | null;\r\n}\r\n\r\nexport const WebSocketState = {\r\n CONNECTING: 0,\r\n OPEN: 1,\r\n CLOSING: 2,\r\n CLOSED: 3,\r\n} as const;\r\n\r\n/** Received message — either text (string) or binary (already parsed). */\r\nexport type RpcReceivedMessage =\r\n | { kind: 'text'; raw: string }\r\n | { kind: 'binary'; message: RpcMessage; args: unknown[] };\r\n\r\n/** Abstract RPC connection — transport-agnostic interface for sending/receiving messages. */\r\nexport interface RpcConnection {\r\n readonly isOpen: boolean;\r\n readonly binaryMode: boolean;\r\n readonly whenConnected: Promise<void>;\r\n readonly messageReceived: EventHandlerSet<RpcReceivedMessage>;\r\n readonly closed: EventHandlerSet<{ code: number; reason: string }>;\r\n /** Optional reusable msgpack encoder for outbound binary messages.\r\n * Callers passing this to `serializeBinaryMessage` skip the per-call\r\n * Encoder construction + resizeBuffer growth. Implementations that\r\n * don't support binary serialization can leave it undefined. */\r\n readonly encoder?: Encoder;\r\n send(serializedMessage: string): void;\r\n sendBinary(data: Uint8Array): void;\r\n close(code?: number, reason?: string): void;\r\n}\r\n\r\n/** WebSocket-based RpcConnection — handles frame splitting, binary/text modes, and message queueing. */\r\nexport class RpcWebSocketConnection implements RpcConnection {\r\n private _ws: WebSocketLike;\r\n private _sendBuffer: (string | Uint8Array)[] = [];\r\n private _connected = new PromiseSource<void>();\r\n\r\n /** Per-connection msgpack encoder — reused across outbound messages to\r\n * amortise construction cost and to keep the internal write buffer at\r\n * its largest observed size (avoids repeated `resizeBuffer` growth on\r\n * large video frames). See serializeBinaryMessage() for details. */\r\n private _encoder: Encoder = createBinaryEncoder();\r\n\r\n /** Per-connection msgpack decoder — reused across inbound messages\r\n * rather than reconstructing one for every `decodeMulti` call. */\r\n private _decoder: Decoder = new Decoder();\r\n\r\n /** Exposed for callers who need to encode outbound binary messages\r\n * through this connection (e.g. `serializeBinaryMessage`). Safe to\r\n * pass to synchronous encode calls on the same event-loop turn. */\r\n get encoder(): Encoder {\r\n return this._encoder;\r\n }\r\n get decoder(): Decoder {\r\n return this._decoder;\r\n }\r\n\r\n private _format: RpcSerializationFormat | undefined;\r\n private _methodRegistry: RpcMethodRegistry | undefined;\r\n\r\n readonly binaryMode: boolean;\r\n readonly messageReceived = new EventHandlerSet<RpcReceivedMessage>();\r\n readonly closed = new EventHandlerSet<{ code: number; reason: string }>();\r\n readonly error = new EventHandlerSet<unknown>();\r\n\r\n constructor(\r\n ws: WebSocketLike,\r\n binaryMode = false,\r\n format?: RpcSerializationFormat,\r\n methodRegistry?: RpcMethodRegistry\r\n ) {\r\n this._ws = ws;\r\n this.binaryMode = binaryMode;\r\n this._format = format;\r\n this._methodRegistry = methodRegistry;\r\n\r\n if (binaryMode && ws.binaryType !== undefined)\r\n ws.binaryType = 'arraybuffer';\r\n\r\n if (ws.readyState === WebSocketState.OPEN) {\r\n this._connected.resolve();\r\n this._flush();\r\n }\r\n\r\n ws.onopen = () => {\r\n this._connected.resolve();\r\n this._flush();\r\n };\r\n\r\n ws.onmessage = ev => {\r\n // Binary frame — V5 self-delimiting envelopes (one or more per WS frame).\r\n //\r\n // Accept BOTH `ArrayBuffer` (browser WebSocket when `binaryType` is\r\n // 'arraybuffer') AND `Uint8Array`-shaped views (Node `ws` delivering a\r\n // `Buffer`, which is a `Uint8Array` subclass). The node-ws adapter\r\n // intentionally passes Buffer through without an ArrayBuffer copy —\r\n // at 300 concurrent video streams that copy was ~100 MB/s of pure\r\n // memcpy + allocation pressure. Matching `ArrayBuffer.isView` here\r\n // keeps the zero-copy path alive all the way to `splitBinaryFrame`.\r\n if (\r\n ev.data instanceof Uint8Array ||\r\n ev.data instanceof ArrayBuffer\r\n ) {\r\n // Node `Buffer` is a `Uint8Array` subclass, so we can hand it to\r\n // `splitBinaryFrame` directly with no view wrapping or copying.\r\n // Browsers deliver `ArrayBuffer`, which needs a single zero-copy\r\n // view wrap.\r\n const frame =\r\n ev.data instanceof Uint8Array\r\n ? ev.data\r\n : new Uint8Array(ev.data);\r\n let messages: { message: RpcMessage; args: unknown[] }[];\r\n try {\r\n messages = this._splitBinary(frame);\r\n } catch (e) {\r\n warnLog?.log('Failed to split binary frame:', e);\r\n this.error.trigger(e);\r\n return;\r\n }\r\n for (const { message, args } of messages)\r\n this._dispatchBinary(message, args);\r\n } else if (\r\n typeof Blob !== 'undefined' &&\r\n ev.data instanceof Blob &&\r\n this.binaryMode\r\n ) {\r\n // Fallback: some Chromium builds deliver the first frames as Blob even\r\n // after setting binaryType='arraybuffer'. Convert and route through\r\n // the binary path so we don't silently drop them.\r\n void ev.data.arrayBuffer().then(ab => {\r\n const frame = new Uint8Array(ab);\r\n let messages: { message: RpcMessage; args: unknown[] }[];\r\n try {\r\n messages = this._splitBinary(frame);\r\n } catch (e) {\r\n warnLog?.log('Failed to split binary blob frame:', e);\r\n this.error.trigger(e);\r\n return;\r\n }\r\n for (const { message, args } of messages)\r\n this._dispatchBinary(message, args);\r\n });\r\n } else {\r\n // Text frame — JSON delimited messages\r\n const data =\r\n typeof ev.data === 'string' ? ev.data : String(ev.data);\r\n for (const msg of splitFrame(data))\r\n if (msg.length > 0)\r\n this.messageReceived.triggerSafe(\r\n { kind: 'text', raw: msg },\r\n e => warnLog?.log('Failed to handle a text message:', e));\r\n }\r\n };\r\n\r\n ws.onclose = ev => {\r\n this.closed.trigger({ code: ev.code, reason: ev.reason });\r\n };\r\n\r\n ws.onerror = ev => {\r\n this.error.trigger(ev);\r\n };\r\n }\r\n\r\n get isOpen(): boolean {\r\n return this._ws.readyState === WebSocketState.OPEN;\r\n }\r\n\r\n get whenConnected(): Promise<void> {\r\n return this._connected;\r\n }\r\n\r\n send(serializedMessage: string): void {\r\n this._sendRaw(serializedMessage);\r\n }\r\n\r\n sendBinary(data: Uint8Array): void {\r\n this._sendRaw(data);\r\n }\r\n\r\n sendTextBatch(messages: string[]): void {\r\n this._sendRaw(serializeFrame(messages));\r\n }\r\n\r\n sendBinaryBatch(messages: Uint8Array[]): void {\r\n this._sendRaw(serializeBinaryFrame(messages));\r\n }\r\n\r\n /** Split a binary frame using the format (compact-aware) or default splitter. */\r\n private _splitBinary(\r\n frame: Uint8Array\r\n ): { message: RpcMessage; args: unknown[] }[] {\r\n if (this._format) {\r\n return this._format.splitBinaryFrame(\r\n frame,\r\n this._decoder,\r\n this._methodRegistry\r\n );\r\n }\r\n return splitBinaryFrameFn(frame, this._decoder);\r\n }\r\n\r\n // Containment matters here: .NET batches many RPC messages per WebSocket frame,\r\n // so letting one bad message escape would drop every other message in the frame -\r\n // including $sys.Ok replies, whose per-call timeouts are unbounded by default.\r\n private _dispatchBinary(message: RpcMessage, args: unknown[]): void {\r\n this.messageReceived.triggerSafe(\r\n { kind: 'binary', message, args },\r\n e => warnLog?.log('Failed to handle a binary message:', e));\r\n }\r\n\r\n close(code?: number, reason?: string): void {\r\n this._ws.close(code, reason);\r\n }\r\n\r\n private _sendRaw(data: string | Uint8Array): void {\r\n try {\r\n if (this._ws.readyState === WebSocketState.OPEN) {\r\n this._ws.send(data);\r\n } else if (this._ws.readyState === WebSocketState.CONNECTING)\r\n this._sendBuffer.push(data);\r\n // CLOSING/CLOSED: silently drop\r\n } catch {\r\n // Swallow — disconnect event handles cleanup\r\n }\r\n }\r\n\r\n private _flush(): void {\r\n if (this._sendBuffer.length === 0) return;\r\n const buffer = this._sendBuffer;\r\n this._sendBuffer = [];\r\n try {\r\n for (const item of buffer) this._ws.send(item);\r\n } catch {\r\n // Swallow — disconnect event handles cleanup\r\n }\r\n }\r\n}\r\n","// .NET counterpart: none — this is a TS-only test transport.\r\n//\r\n// .NET tests use a different mechanism: RpcTestClient creates an in-memory\r\n// Channel<RpcMessage> pair wrapped in an RpcTransport. We use the browser-\r\n// standard MessageChannel API instead, which gives us a synchronous in-process\r\n// transport without requiring WebSocket mocks.\r\n\r\nimport { EventHandlerSet } from '@actuallab/core';\r\nimport { getLogs } from './logging.js';\r\nimport { splitFrame } from './rpc-serialization.js';\r\nimport type { RpcConnection, RpcReceivedMessage } from './rpc-connection.js';\r\n\r\nconst { warnLog } = getLogs('RpcMessageChannelConnection');\r\n\r\n/** MessagePort-based RpcConnection — for in-process testing without WebSocket mocks. */\r\nexport class RpcMessageChannelConnection implements RpcConnection {\r\n private _port: MessagePort;\r\n private _open = true;\r\n\r\n readonly binaryMode = false;\r\n readonly messageReceived = new EventHandlerSet<RpcReceivedMessage>();\r\n readonly closed = new EventHandlerSet<{ code: number; reason: string }>();\r\n readonly whenConnected: Promise<void> = Promise.resolve(); // immediately connected\r\n\r\n constructor(port: MessagePort) {\r\n this._port = port;\r\n port.onmessage = (ev: MessageEvent) => {\r\n const data =\r\n typeof ev.data === 'string' ? ev.data : String(ev.data);\r\n // Isolated per message: one bad message must not drop its frame-mates,\r\n // and an escaping error would be an uncaught exception under Node.\r\n for (const msg of splitFrame(data))\r\n if (msg.length > 0)\r\n this.messageReceived.triggerSafe(\r\n { kind: 'text', raw: msg },\r\n e => warnLog?.log('Failed to handle a text message:', e));\r\n };\r\n }\r\n\r\n get isOpen(): boolean {\r\n return this._open;\r\n }\r\n\r\n send(serializedMessage: string): void {\r\n if (!this._open) return;\r\n try {\r\n this._port.postMessage(serializedMessage);\r\n } catch {\r\n // never fail\r\n }\r\n }\r\n\r\n sendBinary(data: Uint8Array): void {\r\n if (!this._open) return;\r\n try {\r\n this._port.postMessage(data, [data.buffer]);\r\n } catch {\r\n // never fail\r\n }\r\n }\r\n\r\n close(code?: number, reason?: string): void {\r\n if (!this._open) return;\r\n this._open = false;\r\n this._port.close();\r\n this.closed.trigger({ code: code ?? 1000, reason: reason ?? '' });\r\n }\r\n}\r\n\r\n/** Creates a pair of connected RpcMessageChannelConnections for testing. */\r\nexport function createMessageChannelPair(): [\r\n RpcMessageChannelConnection,\r\n RpcMessageChannelConnection,\r\n ] {\r\n const channel = new MessageChannel();\r\n return [\r\n new RpcMessageChannelConnection(channel.port1),\r\n new RpcMessageChannelConnection(channel.port2),\r\n ];\r\n}\r\n","// .NET counterparts:\r\n// RpcServiceDef (Configuration/) — 162 lines. Describes a registered service\r\n// including its .NET Type, Mode (Local/Client/Server/Hybrid), ServerResolver\r\n// (DI-based lazy resolution), ClientType, IsSystem, IsBackend flags, Scope,\r\n// LegacyNames, PropertyBag, and a method dictionary built via reflection.\r\n// RpcMethodDef (Configuration/) — 189 lines. Describes a single method:\r\n// FullName (ServiceName.MethodName:ArgCount), CallType (Regular/Reliable),\r\n// NoWait, HasPolymorphicArguments/Result, RpcMethodKind (Query/Command/System),\r\n// RpcMethodAttribute, LegacyNames, Tracer, outbound/inbound call pipeline\r\n// (factories, invokers, middleware chains, routers, timeouts).\r\n//\r\n// Omitted from .NET:\r\n// - Reflection-based method discovery (BuildMethods) — .NET scans interface\r\n// methods via reflection, filters generics, builds call pipelines. TS uses\r\n// explicit defineRpcService() or decorator metadata — no runtime reflection.\r\n// - RpcMethodKind (Query/Command/System/Other) — .NET classifies methods to\r\n// route Commands through ICommander. TS has no command bus; the distinction\r\n// is unnecessary.\r\n// - IsBackend / IBackendService — .NET separates \"backend-only\" services that\r\n// require backend auth. TS client never exposes backend services.\r\n// - RpcMethodAttribute / LegacyNames — .NET supports custom wire names and\r\n// version-specific legacy name mappings for rolling upgrades. TS has a\r\n// single version; wire names = \"ServiceName.MethodName:wireArgCount\".\r\n// - Call pipeline (OutboundCallFactory, InboundCallFactory, InboundCallInvoker,\r\n// MiddlewareFilter, OutboundCallRouter, OutboundCallTimeouts) — .NET builds a\r\n// per-method middleware chain at startup via generic factory types. TS dispatches\r\n// directly from RpcServiceHost; no middleware or routing layer.\r\n// - CallType (Regular/Reliable) — Reliable calls track stages for reconnect\r\n// resumption. TS replays entire calls on reconnect.\r\n// - HasPolymorphicArguments/Result — governs serializer behavior for abstract\r\n// argument types. TS uses JSON (inherently polymorphic); not applicable.\r\n// - Tracer / RpcCallTracer — per-method distributed tracing. Not ported.\r\n// - PropertyBag — extensible metadata on service/method def. Not needed.\r\n// - Mode (ServiceMode) / Scope / ServerResolver / ClientType — DI wiring.\r\n\r\nimport type { RpcCallTimeouts } from './rpc-call-timeouts.js';\r\n\r\n/** Controls outbound RPC call behavior regarding connection waiting, reconnection, and resending. */\r\nexport const RpcRemoteExecutionMode = {\r\n /** Wait for connection if disconnected when the call is made. */\r\n AwaitForConnection: 1,\r\n /** Resend on reconnection to the same peer. */\r\n AllowReconnect: 2,\r\n /** Resend on reconnection to a different peer (implies AllowReconnect). */\r\n AllowResend: 6, // 4 | AllowReconnect\r\n /** Default: wait + reconnect + resend. */\r\n Default: 7, // AwaitForConnection | AllowResend\r\n} as const;\r\n/** Bitfield combination of RpcRemoteExecutionMode flags. */\r\nexport type RpcRemoteExecutionMode = number;\r\n\r\n/** Describes a single method on an RPC service. */\r\nexport interface RpcMethodDef {\r\n readonly name: string;\r\n readonly serviceName: string;\r\n readonly argCount: number;\r\n readonly wireArgCount: number;\r\n readonly callTypeId: number;\r\n readonly stream: boolean;\r\n readonly noWait: boolean;\r\n readonly remoteExecutionMode: RpcRemoteExecutionMode;\r\n /** Per-method connect/run timeouts (R12). `undefined` means unbounded. */\r\n readonly timeouts?: RpcCallTimeouts;\r\n}\r\n\r\n/** Describes an RPC service — its name and method definitions. */\r\nexport interface RpcServiceDef {\r\n readonly name: string;\r\n readonly methods: ReadonlyMap<string, RpcMethodDef>;\r\n}\r\n\r\nexport interface RpcMethodDefInput {\r\n args: unknown[];\r\n returns?: symbol;\r\n callTypeId?: number;\r\n /** Override for wireArgCount. Default: args.length + 1 (assumes a CancellationToken slot). */\r\n wireArgCount?: number;\r\n /** Override for remoteExecutionMode. Default: RpcRemoteExecutionMode.Default (7). NoWait methods always use 0. */\r\n remoteExecutionMode?: RpcRemoteExecutionMode;\r\n /** Per-method connect/run timeouts (R12). Default: unbounded. */\r\n timeouts?: RpcCallTimeouts;\r\n}\r\n\r\nexport const RpcType = {\r\n object: Symbol('object'),\r\n stream: Symbol('stream'),\r\n noWait: Symbol('noWait'),\r\n void: Symbol('void'),\r\n} as const;\r\n\r\n/** Returns the full wire method name: \"ServiceName.MethodName:wireArgCount\". */\r\nexport function wireMethodName(def: RpcMethodDef): string {\r\n return `${def.serviceName}.${def.name}:${def.wireArgCount}`;\r\n}\r\n\r\nexport function defineRpcService(\r\n name: string,\r\n methods: Record<string, RpcMethodDefInput>\r\n): RpcServiceDef {\r\n const map = new Map<string, RpcMethodDef>();\r\n for (const [key, input] of Object.entries(methods)) {\r\n // If the key contains \":\", the part before \":\" is the clean name\r\n // (the suffix is for JS object key disambiguation of overloads).\r\n // Otherwise the key IS the clean name.\r\n const colonIndex = key.indexOf(':');\r\n const cleanName = colonIndex >= 0 ? key.substring(0, colonIndex) : key;\r\n const wireArgCount = input.wireArgCount ?? input.args.length + 1;\r\n const mapKey = `${cleanName}:${wireArgCount}`;\r\n const isNoWait = input.returns === RpcType.noWait;\r\n map.set(mapKey, {\r\n name: cleanName,\r\n serviceName: name,\r\n argCount: input.args.length,\r\n wireArgCount,\r\n callTypeId: input.callTypeId ?? 0,\r\n stream: input.returns === RpcType.stream,\r\n noWait: isNoWait,\r\n remoteExecutionMode: isNoWait\r\n ? 0\r\n : (input.remoteExecutionMode ?? RpcRemoteExecutionMode.Default),\r\n timeouts: input.timeouts,\r\n });\r\n }\r\n return { name, methods: map };\r\n}\r\n","// .NET counterparts:\r\n// RpcCall (29 lines) — base class: MethodDef, Id, NoWait flag, Lock.\r\n// RpcOutboundCall (437 lines) — tracks an outbound call's full lifecycle:\r\n// result via AsyncTaskMethodBuilder, CacheInfoCapture, CancellationHandler,\r\n// CompletedStage (for Reliable reconnection), StartedAt (for timeouts),\r\n// hashing for Match, tracing, routing, and retry logic.\r\n// RpcInboundCall (298 lines) — tracks an inbound call: deserialization,\r\n// middleware-chain invocation, cancellation via linked CTS, stage-based\r\n// re-processing for reconnection, sending the result back.\r\n// RpcOutboundCallTracker — thread-safe ConcurrentDictionary of outbound calls with:\r\n// HandleDisconnect() (per-outage watcher owning ConnectTimeout), Maintain() loop\r\n// (RunTimeout/DelayTimeout + logging), Reconnect() protocol (stage-based call\r\n// resumption), Abort(), TryReroute().\r\n// RpcInboundCallTracker (64 lines) — ConcurrentDictionary, GetOrRegister,\r\n// Unregister, Clear.\r\n//\r\n// Omitted from .NET:\r\n// - AsyncTaskMethodBuilder / typed RpcOutboundCall<T> — .NET creates a typed\r\n// task builder for each call's return type. TS uses PromiseSource<unknown>\r\n// (untyped) because TypeScript erases generics at runtime.\r\n// - CancellationHandler (CancellationToken.Register → Cancel) — .NET registers\r\n// a callback per outbound call. TS implements equivalent via optional\r\n// AbortSignal on RpcPeer.call(); the cancel handler rejects the promise,\r\n// removes from tracker, and sends $sys.Cancel — same behavior as .NET's\r\n// Cancel() + NotifyCancelled().\r\n// (Timeout monitoring IS ported — see RpcPeer._maintainOutboundCalls. .NET splits it\r\n// between RpcOutboundCallTracker.HandleDisconnect, a per-outage watcher that owns\r\n// ConnectTimeout, and Maintain, which owns RunTimeout while connected; TS runs both\r\n// rules from one timer.)\r\n// - CompletedStage / RpcCallStage — supports Reliable call type's stage-based\r\n// reconnection protocol where the server tells the client which stage each\r\n// call reached, avoiding re-execution of completed stages. TS replays entire\r\n// calls on reconnect (simpler, adequate for browser client).\r\n// - Reconnect() protocol — outbound tracker sends $sys.Reconnect with compressed\r\n// call IDs grouped by stage; server responds with \"unknown\" IDs that need\r\n// re-sending. Not ported; TS uses full replay.\r\n// - Abort()'s final-error latch — .NET latches the error before its (single) sweep, so a\r\n// call registered afterwards is completed by Register rather than left pending. TS does a\r\n// single-pass rejectAll() without the latch; the exposure is much smaller, since close()\r\n// drops the peer from the hub map synchronously instead of keeping it for PeerRemoveDelay.\r\n// - TryReroute() — checks if the call's peer route has changed (load-balancer\r\n// rerouting) and sets RpcRerouteException. TS has no routing layer.\r\n// - IsLongLiving / _longLivingCalls — .NET sets this on compute calls only; it governs\r\n// whether completing a call releases its cancellation handler and trace\r\n// (CompleteAndUnregister vs CompleteKeepRegistered), not whether timeouts apply -\r\n// HandleDisconnect and Maintain both iterate every call. TS has no equivalent.\r\n// - RpcInboundCall: middleware-chain invocation, deserialization with polymorphic\r\n// argument handling, stage-based TryReprocess, CancellationTokenSource per\r\n// call. TS inbound calls are dispatched directly by RpcServiceHost; no\r\n// middleware, no per-call CTS, no stage tracking.\r\n// - RpcCall.Lock (monitor-based synchronisation) — .NET uses lock(this) for\r\n// thread-safe result setting. TS is single-threaded; no locking needed.\r\n\r\nimport { PromiseSource } from '@actuallab/core';\r\nimport { RpcCallStage } from './rpc-call-stage.js';\r\nimport { RpcRemoteExecutionMode } from './rpc-service-def.js';\r\nimport type { RpcCallTimeouts } from './rpc-call-timeouts.js';\r\n\r\n/** Tracks a pending outbound RPC call. */\r\nexport class RpcOutboundCall {\r\n readonly callId: number;\r\n readonly method: string;\r\n readonly result = new PromiseSource<unknown>();\r\n /** Bitfield of RpcRemoteExecutionMode flags controlling reconnect/resend behavior. */\r\n readonly remoteExecutionMode: number;\r\n\r\n /** Whether to remove this call from the tracker on $sys.Ok. Default: true.\r\n * Subclasses (e.g. compute calls) override to false to stay in tracker for invalidation. */\r\n readonly removeOnOk: boolean = true;\r\n\r\n /** The serialized wire data — stored for re-sending on reconnect. */\r\n serializedWireData: string | Uint8Array = '';\r\n\r\n /** Per-call connect/run timeouts enforced by the peer's maintenance loop\r\n * (R12). `undefined` means unbounded (the query default). */\r\n timeouts: RpcCallTimeouts | undefined;\r\n\r\n /** `Date.now()` when this call was created (used for the connect timeout). */\r\n readonly startedAt = Date.now();\r\n\r\n /** `Date.now()` when this call was last put on the wire, or 0 if it hasn't\r\n * been sent yet (used for the run timeout). */\r\n sentAt = 0;\r\n\r\n /**\r\n * Bitfield of {@link RpcCallStage} flags indicating how far this call\r\n * has progressed. Used by the `$sys.Reconnect` protocol to tell the\r\n * remote peer which calls are still pending its attention.\r\n *\r\n * - 0: brand new, no result yet (the peer is expected to be actively processing).\r\n * - `ResultReady` (1): `$sys.Ok` received for this call.\r\n * - `Invalidated` (3): result was invalidated (compute calls only).\r\n * - `Unregistered` (0x1000): removed from the tracker.\r\n */\r\n completedStage = 0;\r\n\r\n constructor(callId: number, method: string, remoteExecutionMode = 7) {\r\n this.callId = callId;\r\n this.method = method;\r\n this.remoteExecutionMode = remoteExecutionMode;\r\n }\r\n\r\n /**\r\n * Returns the stage to report to the remote peer via `$sys.Reconnect`,\r\n * or `null` if this call should not be reconciled and must be aborted.\r\n *\r\n * Mirrors .NET `RpcOutboundCall.GetReconnectStage` at\r\n * src/ActualLab.Rpc/Infrastructure/RpcOutboundCall.cs.\r\n */\r\n getReconnectStage(isPeerChanged: boolean): number | null {\r\n const mode = this.remoteExecutionMode;\r\n if (!(mode & RpcRemoteExecutionMode.AllowReconnect)) return null;\r\n if (isPeerChanged && !(mode & RpcRemoteExecutionMode.AllowResend)) return null;\r\n return this.completedStage;\r\n }\r\n\r\n /** Called when the connection is lost. Subclasses can override to resolve invalidation promises. */\r\n onDisconnect(): void {\r\n // no-op by default\r\n }\r\n}\r\n\r\n/** Manages outbound calls by their RelatedId. */\r\nexport class RpcOutboundCallTracker {\r\n private _calls = new Map<number, RpcOutboundCall>();\r\n private _nextId = 1;\r\n\r\n get size(): number {\r\n return this._calls.size;\r\n }\r\n\r\n nextId(): number {\r\n return this._nextId++;\r\n }\r\n\r\n register(call: RpcOutboundCall): void {\r\n this._calls.set(call.callId, call);\r\n }\r\n\r\n get(callId: number): RpcOutboundCall | undefined {\r\n return this._calls.get(callId);\r\n }\r\n\r\n remove(callId: number): RpcOutboundCall | undefined {\r\n const call = this._calls.get(callId);\r\n if (call !== undefined) {\r\n this._calls.delete(callId);\r\n call.completedStage |= RpcCallStage.Unregistered;\r\n }\r\n return call;\r\n }\r\n\r\n values(): IterableIterator<RpcOutboundCall> {\r\n return this._calls.values();\r\n }\r\n\r\n activeCallIds(): number[] {\r\n return [...this._calls.keys()];\r\n }\r\n\r\n /** Calls handed to the transport at least once. Must be snapshotted BEFORE the\r\n * peer enters `Connected` — that flushes every queued call, making queued and\r\n * in-flight calls indistinguishable. Mirrors .NET's `GetSentCalls`. */\r\n getSentCalls(): RpcOutboundCall[] {\r\n const calls: RpcOutboundCall[] = [];\r\n for (const call of this._calls.values())\r\n if (call.sentAt !== 0)\r\n calls.push(call);\r\n return calls;\r\n }\r\n\r\n /** Reject all pending calls with the given error.\r\n * Stage-3 compute calls (result resolved, awaiting invalidation) are kept in the tracker. */\r\n rejectAll(error: Error): void {\r\n for (const [id, call] of this._calls) {\r\n if (!call.removeOnOk && call.result.isCompleted) {\r\n // Stage-3 compute call — keep it for later invalidation on reconnect/stop\r\n continue;\r\n }\r\n call.result.reject(error);\r\n call.onDisconnect();\r\n this._calls.delete(id);\r\n }\r\n }\r\n\r\n /** Invalidate all remaining stage-3 calls (on reconnect or peer stop). */\r\n invalidateAll(): void {\r\n for (const call of this._calls.values()) {\r\n call.onDisconnect();\r\n }\r\n this._calls.clear();\r\n }\r\n\r\n clear(): void {\r\n this._calls.clear();\r\n }\r\n}\r\n\r\n/** Tracks an incoming inbound RPC call. */\r\nexport class RpcInboundCall {\r\n readonly callId: number;\r\n readonly method: string;\r\n readonly args: unknown[];\r\n /** 0 = still processing, 1 = result computed and sent at least once. */\r\n completedStage = 0;\r\n private _resend: (() => void) | undefined;\r\n private readonly _abortController = new AbortController();\r\n\r\n constructor(callId: number, method: string, args: unknown[]) {\r\n this.callId = callId;\r\n this.method = method;\r\n this.args = args;\r\n }\r\n\r\n /** Aborted when the remote peer cancels this call via `$sys.Cancel` (R17).\r\n * Threaded into the service handler so it can abort in-flight work. */\r\n get signal(): AbortSignal {\r\n return this._abortController.signal;\r\n }\r\n\r\n /** True once the remote peer cancelled this call — the response is then\r\n * suppressed even though the call may still be registered (R9 dedup). */\r\n get isCancelled(): boolean {\r\n return this._abortController.signal.aborted;\r\n }\r\n\r\n cancel(): void {\r\n this._abortController.abort();\r\n }\r\n\r\n // Records the closure that re-sends this call's already-computed result;\r\n // marks the call as completed. Mirrors .NET's post-`ProcessStage1Plus` state.\r\n setResult(resend: () => void): void {\r\n this._resend = resend;\r\n this.completedStage = 1;\r\n }\r\n\r\n // Re-sends the result to the remote peer if it was already computed.\r\n // A resent frame for an in-flight call is a no-op — the original dispatch\r\n // will send the result when it completes. Mirrors .NET\r\n // `RpcInboundCall.TryReprocess`.\r\n resendResult(): void {\r\n if (this.completedStage >= 1)\r\n this._resend?.();\r\n }\r\n}\r\n\r\n/** Manages inbound calls by their RelatedId. */\r\nexport class RpcInboundCallTracker {\r\n private _calls = new Map<number, RpcInboundCall>();\r\n private _completedIds: number[] = [];\r\n\r\n /** Cap on retained completed calls — see `RpcLimits.completedInboundCallsLimit`. */\r\n completedCallsLimit = 1000;\r\n\r\n get size(): number {\r\n return this._calls.size;\r\n }\r\n\r\n register(call: RpcInboundCall): void {\r\n this._calls.set(call.callId, call);\r\n }\r\n\r\n // Registers `call` unless its id is already tracked, in which case the\r\n // existing call is returned. Mirrors .NET `RpcInboundCallTracker.GetOrRegister`.\r\n getOrRegister(call: RpcInboundCall): RpcInboundCall {\r\n const existing = this._calls.get(call.callId);\r\n if (existing !== undefined)\r\n return existing;\r\n\r\n this._calls.set(call.callId, call);\r\n return call;\r\n }\r\n\r\n // Keeps the completed `call` registered for duplicate-frame dedup, evicting\r\n // the oldest completed calls beyond `completedCallsLimit`. Deviation from\r\n // .NET (which unregisters a call once its result is sent): TS peers\r\n // blind-resend after reconnect, so a completed call must stay resolvable\r\n // to avoid re-executing the handler — but only within a bounded window.\r\n markCompleted(call: RpcInboundCall): void {\r\n if (this._calls.get(call.callId) !== call)\r\n return;\r\n\r\n this._completedIds.push(call.callId);\r\n while (this._completedIds.length > this.completedCallsLimit) {\r\n const id = this._completedIds.shift()!;\r\n this._calls.delete(id);\r\n }\r\n }\r\n\r\n get(callId: number): RpcInboundCall | undefined {\r\n return this._calls.get(callId);\r\n }\r\n\r\n remove(callId: number): RpcInboundCall | undefined {\r\n const call = this._calls.get(callId);\r\n if (call !== undefined) this._calls.delete(callId);\r\n return call;\r\n }\r\n\r\n clear(): void {\r\n this._calls.clear();\r\n this._completedIds.length = 0;\r\n }\r\n}\r\n","/**\n * Well-known RPC call completion stage constants, used by the\n * `$sys.Reconnect` protocol to tell the peer which calls have already\n * reached which stage and therefore don't need to be re-processed.\n *\n * Direct port of .NET `ActualLab.Rpc.Infrastructure.RpcCallStage` at\n * src/ActualLab.Rpc/Infrastructure/RpcCallStage.cs.\n */\nexport const RpcCallStage = {\n /** The call's result has been received from the peer. */\n ResultReady: 1,\n /** The compute call's result has been invalidated (compute-call specific). */\n Invalidated: 3,\n /**\n * Bit flag added on top of ResultReady/Invalidated when the outbound\n * call has been removed from the tracker (unregistered).\n */\n Unregistered: 0x1_000,\n} as const;\n\nexport type RpcCallStage = number;\n","// .NET counterpart: ActualLab.Rpc.RpcCallTimeouts (+ RpcCallTimeouts.Default).\r\n// TS omits the query/command classification .NET derives from method metadata\r\n// (the port has no RpcMethodKind), so timeouts are opt-in per call or per method\r\n// def; the default is unbounded (the .NET query default).\r\n\r\n/**\r\n * Connect/run timeouts for an outbound RPC call, enforced by the peer's\r\n * maintenance loop. `Infinity` means \"no timeout\".\r\n */\r\nexport class RpcCallTimeouts {\r\n /** Both timeouts unbounded — the query default (.NET `RpcCallTimeouts.None`). */\r\n static readonly None = new RpcCallTimeouts(Infinity, Infinity);\r\n /** Command default: 1.5 s connect / 10 s run (.NET `RpcCallTimeouts.Default.Command`). */\r\n static readonly Command = new RpcCallTimeouts(1_500, 10_000);\r\n\r\n /** Max time a call may wait for a connection - before it is sent, and again for the\r\n * duration of any outage it is still pending through. */\r\n readonly connectTimeoutMs: number;\r\n /** Max time a sent call may stay unanswered before it is failed. */\r\n readonly runTimeoutMs: number;\r\n\r\n constructor(connectTimeoutMs = Infinity, runTimeoutMs = Infinity) {\r\n this.connectTimeoutMs = connectTimeoutMs;\r\n this.runTimeoutMs = runTimeoutMs;\r\n }\r\n}\r\n","/** Error thrown for a failed remote RPC call — carries the remote exception's type name when available. */\r\nexport class RpcError extends Error {\r\n readonly typeName?: string;\r\n\r\n constructor(message: string, typeName?: string) {\r\n super(message);\r\n this.name = 'RpcError';\r\n if (typeName !== undefined)\r\n this.typeName = typeName;\r\n }\r\n}\r\n\r\n// Assembly-qualified TypeRef of ActualLab's RemoteException — the type every\r\n// ActualLab peer can reconstruct from a single (string message) ctor. TS uses\r\n// it whenever it reports a JS error to a .NET peer ($sys.Error, stream $sys.End),\r\n// folding the JS error name into the message for provenance (see decision D3).\r\nexport const REMOTE_EXCEPTION_TYPE_REF = 'ActualLab.Serialization.RemoteException, ActualLab.Core';\r\n\r\n/** Wire shape of .NET `ExceptionInfo` for a JS error sent to a .NET peer. */\r\nexport interface RpcExceptionInfo {\r\n TypeRef: string;\r\n Message: string;\r\n}\r\n\r\n/** Reshapes what a remote peer gets to see of a local error. */\r\nexport type RpcErrorFilter = (error: unknown, info: RpcExceptionInfo) => RpcExceptionInfo;\r\n\r\n/** Opt-in {@link RpcErrorFilter} that hides the original message — Node error\r\n * text routinely embeds absolute paths, connection strings and SQL. */\r\nexport const genericErrorFilter: RpcErrorFilter = () => ({\r\n TypeRef: REMOTE_EXCEPTION_TYPE_REF,\r\n Message: 'Error: An error occurred while processing the request.',\r\n});\r\n\r\nexport function toExceptionInfo(error: unknown, filter?: RpcErrorFilter): RpcExceptionInfo {\r\n const name = error instanceof Error ? error.name : 'Error';\r\n const message = error instanceof Error ? error.message : String(error);\r\n const info: RpcExceptionInfo = {\r\n TypeRef: REMOTE_EXCEPTION_TYPE_REF,\r\n Message: `${name}: ${message}`,\r\n };\r\n return filter === undefined ? info : filter(error, info);\r\n}\r\n","/**\n * Compresses and decompresses monotonically increasing sequences of\n * integers using variable-length LEB128 encoding of successive deltas.\n *\n * Wire-compatible with .NET `ActualLab.Rpc.Internal.IncreasingSeqCompressor`\n * (src/ActualLab.Rpc/Internal/IncreasingSeqCompressor.cs) — which\n * underlies the `$sys.Reconnect` protocol.\n *\n * Inputs MUST be sorted non-decreasing. Maximum supported value is\n * 2^53 - 1 (JavaScript `Number` safe-integer limit); call IDs in TS\n * never exceed this because they're generated from a 32-bit counter.\n */\nexport const IncreasingSeqCompressor = {\n /**\n * Serialize a non-decreasing sequence of non-negative integers as\n * LEB128-encoded deltas.\n *\n * Throws `RangeError` if the input is not monotonically non-decreasing,\n * or if a value exceeds `Number.MAX_SAFE_INTEGER`.\n */\n serialize(values: Iterable<number>): Uint8Array {\n // First pass: compute total byte length; second pass: write.\n // (A growable buffer would also work, but values are small here.)\n const deltas: number[] = [];\n let last = 0;\n let totalBytes = 0;\n for (const value of values) {\n if (!Number.isSafeInteger(value) || value < 0)\n throw new RangeError(\n `IncreasingSeqCompressor: value must be a non-negative safe integer (got ${value}).`);\n const delta = value - last;\n if (delta < 0)\n throw new RangeError(\n `IncreasingSeqCompressor: sequence must be non-decreasing (saw ${value} after ${last}).`);\n deltas.push(delta);\n last = value;\n totalBytes += _varUintByteLength(delta);\n }\n\n const out = new Uint8Array(totalBytes);\n let offset = 0;\n for (const delta of deltas) {\n offset = _writeVarUint(out, delta, offset);\n }\n return out;\n },\n\n /**\n * Decode bytes produced by {@link serialize} back into the original\n * sorted sequence of non-negative integers.\n */\n deserialize(data: Uint8Array): number[] {\n const result: number[] = [];\n let last = 0;\n let offset = 0;\n while (offset < data.length) {\n const [delta, next] = _readVarUint(data, offset);\n offset = next;\n last += delta;\n result.push(last);\n }\n return result;\n },\n} as const;\n\n// -- LEB128 (variable-length unsigned) encoding -----------------------------\n// Kept in sync with .NET SpanExt.WriteVarUInt64 / ReadVarUInt64 at\n// src/ActualLab.Core/Collections/SpanExt.ReadWriteVarUInt.cs:22-91.\n// We use `Number` arithmetic up to the 53-bit safe-integer ceiling; values\n// beyond that would need BigInt, which is not required for TS call IDs.\n\nconst LOW_BITS = 0x7f;\nconst HIGH_BIT = 0x80;\n\nfunction _varUintByteLength(value: number): number {\n let n = 0;\n let v = value;\n while (v >= HIGH_BIT) {\n n++;\n v = Math.floor(v / 128);\n }\n return n + 1;\n}\n\nfunction _writeVarUint(out: Uint8Array, value: number, offset: number): number {\n let v = value;\n while (v >= HIGH_BIT) {\n out[offset++] = (HIGH_BIT | (v & LOW_BITS)) & 0xff;\n v = Math.floor(v / 128);\n }\n out[offset++] = v & 0xff;\n return offset;\n}\n\nfunction _readVarUint(data: Uint8Array, offset: number): [number, number] {\n let value = 0;\n let shift = 0;\n // Up to 9 bytes contribute 7 bits each = 63 bits.\n while (shift < 63) {\n if (offset >= data.length)\n throw new RangeError('IncreasingSeqCompressor: truncated VarUInt.');\n const b = data[offset++];\n // Multiply by 2^shift using Math.pow to stay in Number arithmetic.\n // For shift up to 49 (7*7) the intermediate `(b & LOW_BITS) * 2^shift`\n // stays within Number.MAX_SAFE_INTEGER; beyond that the caller is\n // expected to keep call IDs within 2^53.\n value += (b & LOW_BITS) * Math.pow(2, shift);\n if (b <= LOW_BITS) return [value, offset];\n shift += 7;\n }\n // 10th byte — matches .NET's 1-bit allowance.\n if (offset >= data.length)\n throw new RangeError('IncreasingSeqCompressor: truncated VarUInt.');\n const b = data[offset++];\n if (b > 1)\n throw new RangeError('IncreasingSeqCompressor: VarUInt value exceeds 64 bits.');\n value += b * Math.pow(2, 63);\n return [value, offset];\n}\n","/**\r\n * Minimal base64 ↔ Uint8Array helpers used by the `$sys.Reconnect` protocol\r\n * (byte arrays are encoded as base64 strings on the JSON wire to match\r\n * .NET's default `System.Text.Json` serialization of `byte[]`).\r\n */\r\n\r\nexport function base64Encode(bytes: Uint8Array): string {\r\n // btoa works on latin1 strings; chunk to avoid \"Maximum call stack size exceeded\"\r\n // when building the intermediate string for large inputs.\r\n const chunkSize = 0x8000;\r\n let binary = '';\r\n for (let i = 0; i < bytes.length; i += chunkSize) {\r\n binary += String.fromCharCode(\r\n ...(bytes.subarray(i, Math.min(bytes.length, i + chunkSize)) as unknown as number[]),\r\n );\r\n }\r\n return btoa(binary);\r\n}\r\n\r\n/** RFC 4648 §5 base64url, unpadded — matches .NET's `Base64UrlEncoder.Encode`. */\r\nexport function base64UrlEncode(bytes: Uint8Array): string {\r\n return base64Encode(bytes)\r\n .replace(/\\+/g, '-')\r\n .replace(/\\//g, '_')\r\n .replace(/=+$/, '');\r\n}\r\n\r\nexport function base64Decode(encoded: string): Uint8Array {\r\n const binary = atob(encoded);\r\n const out = new Uint8Array(binary.length);\r\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\r\n return out;\r\n}\r\n","// .NET counterpart:\r\n// RpcSystemCalls (239 lines) — implements IRpcSystemCalls interface; handles\r\n// all inbound system messages as regular RPC method calls dispatched through\r\n// the same inbound-call pipeline as user calls.\r\n//\r\n// Omitted from .NET:\r\n// - Reconnect() handler — receives compressed call-ID sets grouped by stage,\r\n// looks up each inbound call, calls TryReprocess(), and returns the IDs of\r\n// unknown calls. TS doesn't implement the Reliable reconnection protocol.\r\n// - Cancel() handler — .NET finds the inbound call and cancels its\r\n// CancellationTokenSource, aborting the running handler. TS removes the\r\n// inbound call from the tracker but does not yet propagate cancellation\r\n// to the service handler (no AbortSignal threading through dispatch).\r\n// - M() (Match) handler — tells an outbound call to use its cached result\r\n// instead of the response payload. TS has no response caching.\r\n// - NotFound() — throws an EndpointNotFound error. TS sends this as a regular\r\n// $sys.Error; no separate system call type.\r\n// - KeepAlive() / Disconnect() — manage RpcSharedObject lifetimes. TS has no\r\n// shared-object tracker.\r\n// - IRpcPolymorphicArgumentHandler.IsValidCall — resolves the concrete\r\n// deserialization type for polymorphic Ok/Item/Batch arguments by looking up\r\n// the related outbound call's return type. TS deserializes all args as\r\n// unknown via JSON.parse (inherently polymorphic).\r\n// - DI / IServiceProvider / RpcServiceBase — .NET system calls are resolved via\r\n// DI. TS uses a class with a handle() method.\r\n\r\nimport { getLogs } from './logging.js';\r\nimport { RpcError } from './rpc-error.js';\r\nimport { RpcSystemCalls, type RpcMessage } from './rpc-message.js';\r\nimport type { RpcPeer } from './rpc-peer.js';\r\nimport type { RpcStream } from './rpc-stream.js';\r\nimport type { RpcStreamSender } from './rpc-stream-sender.js';\r\nimport { RpcCallStage } from './rpc-call-stage.js';\r\nimport { IncreasingSeqCompressor } from './increasing-seq-compressor.js';\r\nimport { base64Decode, base64Encode } from './base64.js';\r\n\r\nconst { debugLog, warnLog } = getLogs('RpcSystemCallHandler');\r\n\r\n/** Handles incoming system call messages — class-based equivalent of the former standalone function. */\r\nexport class RpcSystemCallHandler {\r\n handle(message: RpcMessage, args: unknown[], peer: RpcPeer): void {\r\n const method = message.Method;\r\n const relatedId = message.RelatedId ?? 0;\r\n debugLog?.log(`'${peer.ref}': handle ${method}#${relatedId}`);\r\n\r\n switch (method) {\r\n case RpcSystemCalls.ok: {\r\n const call = peer.outboundCalls.get(relatedId);\r\n if (call !== undefined) {\r\n if (args.length > 1) {\r\n peer.outboundCalls.remove(relatedId);\r\n call.result.reject(polymorphicPayloadError(method, args.length, 1));\r\n break;\r\n }\r\n call.completedStage |= RpcCallStage.ResultReady;\r\n if (call.removeOnOk) {\r\n peer.outboundCalls.remove(relatedId);\r\n }\r\n call.result.resolve(args[0]);\r\n }\r\n break;\r\n }\r\n case RpcSystemCalls.reconnect: {\r\n // Server-side `$sys.Reconnect` handler. Computes the set of call\r\n // IDs the inbound tracker doesn't know about, replies with a\r\n // byte[] result via $sys.Ok. Mirrors .NET `RpcSystemCalls.Reconnect`\r\n // at src/ActualLab.Rpc/Infrastructure/RpcSystemCalls.cs:51-85.\r\n //\r\n // Validates `handshakeIndex` against this peer's own handshake\r\n // (stale generation → TooLateToReconnect error). Does not perform\r\n // per-stage re-processing of compute calls (TS has no compute-\r\n // invalidation tracking on the inbound side); a known call id is\r\n // re-sent its result if already computed, else reported \"unknown\".\r\n this._handleReconnect(relatedId, args, peer);\r\n break;\r\n }\r\n case RpcSystemCalls.error: {\r\n const call = peer.outboundCalls.get(relatedId);\r\n if (call !== undefined) {\r\n // Honor removeOnOk like the Ok case: an errored compute call\r\n // stays tracked so its error computed still observes\r\n // $sys-c.Invalidate — C# RpcOutboundComputeCall.SetError\r\n // (CompleteKeepRegistered). Local invalidation or reconnect\r\n // cleans it up.\r\n call.completedStage |= RpcCallStage.ResultReady;\r\n if (call.removeOnOk) {\r\n peer.outboundCalls.remove(relatedId);\r\n }\r\n const errorInfo = args[0] as\r\n | Record<string, unknown>\r\n | undefined;\r\n const msg = (errorInfo?.Message ??\r\n errorInfo?.message ??\r\n 'RPC error') as string;\r\n const typeName = extractTypeName(errorInfo?.TypeRef ?? errorInfo?.typeRef);\r\n // Mirrors RpcSystemCalls.cs:102 — surface RpcRerouteException.\r\n if (typeName === 'ActualLab.Rpc.RpcRerouteException')\r\n warnLog?.log('Got RpcRerouteException from remote peer:', msg);\r\n call.result.reject(new RpcError(msg, typeName));\r\n }\r\n break;\r\n }\r\n case RpcSystemCalls.cancel: {\r\n // Remote peer is cancelling a call it asked us to process. Abort the\r\n // call's signal (so the running handler can bail) and unregister it;\r\n // the dispatch's result send is gated on `isCancelled`, so no\r\n // response goes out. Mirrors RpcInboundCall.Cancel + the\r\n // \"!CallCancelToken.IsCancellationRequested\" guard on SendResult\r\n // (RpcInboundCall.cs:209-210, 246-247).\r\n const call = peer.inboundCalls.remove(relatedId);\r\n call?.cancel();\r\n break;\r\n }\r\n case RpcSystemCalls.keepAlive: {\r\n // Remote keep-alive — mark the peer as \"alive\" so the peer's\r\n // watchdog doesn't force-close the connection. Mirrors .NET\r\n // `RpcObjectTrackers.KeepAlive` which sets `LastKeepAliveAt`.\r\n peer.notifyKeepAliveReceived();\r\n // The id list advertises the remote peer's remote objects, which\r\n // map to our shared objects. Any id we no longer track is stale on\r\n // its side — reply with $sys.Disconnect so it stops advertising it.\r\n // Mirrors RpcObjectTrackers.KeepAlive (RpcObjectTrackers.cs:300-317).\r\n const keepAliveIds = args[0] as number[] | undefined;\r\n if (Array.isArray(keepAliveIds) && keepAliveIds.length > 0 && peer.wireConnection !== undefined) {\r\n const unknownIds: number[] = [];\r\n for (const id of keepAliveIds)\r\n if (peer.sharedObjects.get(id) === undefined)\r\n unknownIds.push(id);\r\n\r\n if (unknownIds.length > 0)\r\n peer.hub.systemCallSender.disconnect(\r\n peer.wireConnection, peer.serializationFormat, unknownIds);\r\n }\r\n break;\r\n }\r\n case RpcSystemCalls.item: {\r\n const stream = peer.remoteObjects.get(relatedId) as\r\n | RpcStream<unknown>\r\n | undefined;\r\n // Silent no-op when no stream — mirrors RpcSystemCalls.cs:164-170.\r\n // This is normal during reconnect: the server may send items for\r\n // streams the client has already disposed.\r\n if (!stream)\r\n debugLog?.log(`$sys.I: no stream for relatedId=${relatedId}`);\r\n else if (args.length > 2)\r\n stream.onEnd(args[0] as number, polymorphicPayloadError(method, args.length, 2));\r\n else\r\n stream.onItem(args[0] as number, args[1]);\r\n break;\r\n }\r\n case RpcSystemCalls.batch: {\r\n const stream = peer.remoteObjects.get(relatedId) as\r\n | RpcStream<unknown>\r\n | undefined;\r\n if (!stream)\r\n debugLog?.log(`$sys.B: no stream for relatedId=${relatedId}`);\r\n else if (args.length > 2)\r\n stream.onEnd(args[0] as number, polymorphicPayloadError(method, args.length, 2));\r\n else if (Array.isArray(args[1]))\r\n stream.onBatch(args[0] as number, args[1] as unknown[]);\r\n break;\r\n }\r\n case RpcSystemCalls.end: {\r\n const stream = peer.remoteObjects.get(relatedId) as\r\n | RpcStream<unknown>\r\n | undefined;\r\n if (!stream) {\r\n debugLog?.log(`$sys.End: no stream for relatedId=${relatedId}`);\r\n } else {\r\n // .NET ExceptionInfo is a struct — even for normal completion it serializes\r\n // as a non-null object with empty fields. C# keys \"is this an error?\" on\r\n // TypeRef presence (ExceptionInfo.IsNone); we mirror that and fall back to a\r\n // non-empty Message. The TypeRef's type name becomes the Error's name.\r\n const errorInfo = args[1] as Record<string, unknown> | null;\r\n const typeName = extractTypeName(errorInfo?.TypeRef ?? errorInfo?.typeRef);\r\n const msg = (errorInfo?.Message ?? errorInfo?.message) as\r\n | string\r\n | undefined;\r\n let error: Error | null = null;\r\n if (typeName || msg) {\r\n error = new Error(msg ?? typeName ?? 'RPC stream error');\r\n if (typeName)\r\n error.name = typeName;\r\n }\r\n stream.onEnd(args[0] as number, error);\r\n }\r\n break;\r\n }\r\n case RpcSystemCalls.ack: {\r\n const sender = peer.sharedObjects.get(relatedId) as\r\n | RpcStreamSender<unknown>\r\n | undefined;\r\n // Unknown shared object — reply with $sys.Disconnect so the remote\r\n // stream fails fast (RpcStreamNotFoundException analog) instead of\r\n // hanging. Mirrors RpcSystemCalls.Ack (RpcSystemCalls.cs:149-159).\r\n if (sender)\r\n sender.onAck(args[0] as number, args[1] as string);\r\n else if (peer.wireConnection !== undefined)\r\n peer.hub.systemCallSender.disconnect(\r\n peer.wireConnection, peer.serializationFormat, [relatedId]);\r\n break;\r\n }\r\n case RpcSystemCalls.ackEnd: {\r\n const sender = peer.sharedObjects.get(relatedId) as\r\n | RpcStreamSender<unknown>\r\n | undefined;\r\n if (sender)\r\n sender.onAckEnd(args[0] as string);\r\n else if (peer.wireConnection !== undefined)\r\n peer.hub.systemCallSender.disconnect(\r\n peer.wireConnection, peer.serializationFormat, [relatedId]);\r\n break;\r\n }\r\n case RpcSystemCalls.disconnect: {\r\n // Server is telling us the listed remote object IDs have been torn down\r\n // on its side (e.g. shared stream closed). Propagate disconnect() to the\r\n // matching remote objects so any pending consumers (for-await loops) exit\r\n // cleanly instead of hanging.\r\n //\r\n // The ids come from the sender's shared-object namespace, which is our\r\n // *remote* one — they must never be resolved against `peer.sharedObjects`.\r\n // Both id counters start at 1, so such a lookup would routinely abort an\r\n // unrelated outgoing sender. Mirrors RpcSystemCalls.cs:139, which\r\n // likewise touches RemoteObjects only.\r\n const ids = args[0] as number[] | undefined;\r\n if (Array.isArray(ids)) {\r\n for (const id of ids) {\r\n const remoteObj = peer.remoteObjects.get(id);\r\n if (\r\n remoteObj &&\r\n typeof remoteObj.disconnect === 'function'\r\n ) {\r\n remoteObj.disconnect();\r\n }\r\n }\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Server-side handler for the `$sys.Reconnect` call. Parses the peer's\r\n * report of in-flight call IDs (grouped by stage, IncreasingSeqCompressor-\r\n * encoded, base64-wrapped for JSON transport), computes the subset this\r\n * peer does not recognize, and replies with a compressed byte[] of the\r\n * unknown IDs wrapped in `$sys.Ok`.\r\n *\r\n * @param relatedId The outbound call ID the client used for this\r\n * `$sys.Reconnect` invocation — we send `$sys.Ok` back\r\n * with the same `RelatedId`.\r\n */\r\n private _handleReconnect(relatedId: number, args: unknown[], peer: RpcPeer): void {\r\n const conn = peer.wireConnection;\r\n if (conn === undefined) return;\r\n\r\n // args[0]: handshakeIndex — the server's OWN handshake index as the\r\n // client last saw it. If it no longer matches, this $sys.Reconnect\r\n // targets a stale connection generation; reject it so the caller falls\r\n // back to blind resend (absorbed by the inbound-call dedup). Mirrors\r\n // C# RpcSystemCalls.Reconnect's ownHandshake check (RpcSystemCalls.cs:59-62).\r\n // A non-numeric or absent index is rejected rather than waved through:\r\n // C#'s parameter is a typed `int`, so there is no shape that skips the\r\n // comparison there either.\r\n const handshakeIndex = args[0];\r\n if (typeof handshakeIndex !== 'number' || handshakeIndex !== peer.ownHandshakeIndex) {\r\n const got = typeof handshakeIndex === 'number' ? handshakeIndex : `<${typeof handshakeIndex}>`;\r\n peer.hub.systemCallSender.error(\r\n conn, peer.serializationFormat, relatedId,\r\n new Error(\r\n `TooLateToReconnect: own handshake index ${peer.ownHandshakeIndex} != ${got}`));\r\n return;\r\n }\r\n\r\n // A conforming peer sends this once per connection, so a repeat is a replay -\r\n // reject it rather than paying for the reconciliation again. Mirrors C#\r\n // RpcSystemCalls.Reconnect's TryClaimReconnect gate.\r\n if (!peer.tryClaimReconnect()) {\r\n peer.hub.systemCallSender.error(\r\n conn, peer.serializationFormat, relatedId,\r\n new Error('TooLateToReconnect: this connection was already reconnected'));\r\n return;\r\n }\r\n\r\n // args[1]: completedStagesData — shape depends on wire format.\r\n // JSON: { [stage: string]: base64-string }\r\n // msgpack: Map-like with int keys and Uint8Array values (unsupported in TS).\r\n let unknownIds: number[] = [];\r\n try {\r\n const stagesRaw = args[1];\r\n if (stagesRaw && typeof stagesRaw === 'object' && !Array.isArray(stagesRaw)) {\r\n const unknownSet = new Set<number>();\r\n for (const [stageKey, rawValue] of Object.entries(stagesRaw as Record<string, unknown>)) {\r\n const bytes = _toBytes(rawValue);\r\n if (bytes === null) continue;\r\n const completedStage = parseInt(stageKey, 10) || 0;\r\n const callIds = IncreasingSeqCompressor.deserialize(bytes);\r\n for (const callId of callIds) {\r\n const call = peer.inboundCalls.get(callId);\r\n if (call === undefined)\r\n unknownSet.add(callId);\r\n else if (completedStage < 1)\r\n // Known call the client has no result for — re-send it\r\n // if already computed (an in-flight call sends on\r\n // completion). At stage >= 1 the client already has\r\n // the result, so nothing is re-sent. Mirrors C#\r\n // RpcInboundCall.TryReprocess.\r\n call.resendResult();\r\n }\r\n }\r\n unknownIds = [...unknownSet].sort((a, b) => a - b);\r\n }\r\n } catch {\r\n // If parsing fails, report all as unknown (safest behavior — client\r\n // will resend everything).\r\n // Client handles this via its own try/catch around the reconcile call.\r\n }\r\n\r\n const responseBytes = IncreasingSeqCompressor.serialize(unknownIds);\r\n const responseValue = peer.serializationFormat.isBinary ? responseBytes : base64Encode(responseBytes);\r\n peer.hub.systemCallSender.ok(conn, peer.serializationFormat, relatedId, responseValue);\r\n }\r\n}\r\n\r\n// A polymorphic .NET payload ($sys.Ok/I/B with needsPolymorphism) prefixes the\r\n// value with a type marker whose bytes decode as extra msgpack values — see\r\n// readPolymorphismMarker. Detected by arity: these calls carry a fixed value count.\r\nfunction polymorphicPayloadError(method: string, got: number, expected: number): Error {\r\n return new Error(\r\n `${method}: expected ${expected} value(s), got ${got} — ` +\r\n 'polymorphic payloads are not supported by the TS client');\r\n}\r\n\r\n// Extracts the .NET type name from a serialized TypeRef. TypeRef is serialized\r\n// as its assembly-qualified name string (e.g.\r\n// \"System.InvalidOperationException, System.Private.CoreLib\"); the type name is\r\n// the substring before the first ','. Some binary shapes send it as an object.\r\nfunction extractTypeName(typeRef: unknown): string | undefined {\r\n if (typeof typeRef === 'string') {\r\n if (typeRef.length === 0)\r\n return undefined;\r\n\r\n const commaIdx = typeRef.indexOf(',');\r\n return commaIdx >= 0 ? typeRef.slice(0, commaIdx) : typeRef;\r\n }\r\n if (typeRef !== null && typeof typeRef === 'object') {\r\n const t = typeRef as Record<string, unknown>;\r\n const aqn = (t.AssemblyQualifiedName ?? t.assemblyQualifiedName) as string | undefined;\r\n if (typeof aqn === 'string' && aqn.length > 0) {\r\n const commaIdx = aqn.indexOf(',');\r\n return commaIdx >= 0 ? aqn.slice(0, commaIdx) : aqn;\r\n }\r\n return (t.TypeName ?? t.typeName) as string | undefined;\r\n }\r\n return undefined;\r\n}\r\n\r\n/**\r\n * Coerce a wire value carrying a byte[] into a Uint8Array.\r\n * - JSON format sends byte[] as a base64 string.\r\n * - msgpack format sends byte[] as native `bin` (Uint8Array).\r\n * Returns `null` if the value is neither.\r\n */\r\nfunction _toBytes(value: unknown): Uint8Array | null {\r\n if (value instanceof Uint8Array) return value;\r\n if (typeof value === 'string') {\r\n try { return base64Decode(value); } catch { return null; }\r\n }\r\n return null;\r\n}\r\n","// .NET counterpart:\r\n// RpcSystemCallSender (202 lines) — a DI-registered service that sends system\r\n// calls by creating RpcOutboundContext + calling PrepareCallForSendNoWait +\r\n// serialising via the full outbound message pipeline.\r\n//\r\n// Omitted from .NET:\r\n// - Complete<TResult>() / typed Ok<TResult>() — .NET serialises the Ok result\r\n// using the method's unwrapped return type for proper polymorphic handling.\r\n// TS uses JSON.stringify(result) which handles any type uniformly.\r\n// - Match() — sends $sys.M when the response hash matches a cached entry,\r\n// avoiding re-sending the full payload. TS has no response caching.\r\n// - NotFound() — TS sends \"Service not found\" / \"Method not found\" as $sys.Error.\r\n// - Ack() / AckEnd() / Item<T>() / Batch<T>() / End() — stream control\r\n// messages for RpcStream. TS has no streaming support yet.\r\n// - RpcOutboundContext / PrepareCallForSendNoWait pipeline — .NET creates a full\r\n// outbound context with MethodDef, ArgumentList, etc., then serialises via the\r\n// peer's MessageSerializer. TS directly calls serializeMessage() (JSON) since\r\n// there's only one serialization format.\r\n// - Tracing / CallLogger integration — .NET logs each system call through the\r\n// peer's CallLogger. TS has no tracing infrastructure.\r\n// - StopMode-aware Error() — .NET checks peer.StopMode to decide whether to\r\n// suppress error responses when the peer is shutting down. TS has no stop\r\n// mode concept.\r\n\r\nimport { getLogs } from './logging.js';\r\nimport { toExceptionInfo, type RpcErrorFilter } from './rpc-error.js';\r\nimport { RpcSystemCalls, type RpcMessage } from './rpc-message.js';\r\nimport type { RpcConnection } from './rpc-connection.js';\r\nimport type { RpcSerializationFormat } from './rpc-serialization-format.js';\r\nimport type { RpcMethodRegistry } from './rpc-method-registry.js';\r\n\r\nconst { errorLog } = getLogs('RpcSystemCallSender');\r\n\r\n/**\r\n * Sends system RPC messages — like .NET's RpcSystemCallSender.\r\n *\r\n * Hot-path allocation notes: `item`, `batch`, `ack`, and `end` are called\r\n * once per stream frame (or once per ack window). Naively building a fresh\r\n * `{ Method, RelatedId }` envelope object plus a fresh two-element arg array\r\n * on every call showed up as ~2-4% self time + non-trivial GC pressure in\r\n * the 300-pull video-load-test profile.\r\n *\r\n * The sender itself is a per-Hub singleton and `_send`/format.serializeMessage\r\n * are fully synchronous (no awaits, no re-entrancy on a single-threaded event\r\n * loop), so we reuse a small pool of preallocated envelope + arg-array slots\r\n * per hot method. Mutate fields just before the send call; the encoder reads\r\n * them synchronously and produces a detached copy, after which the slot is\r\n * free to be mutated again on the next call.\r\n */\r\nexport class RpcSystemCallSender {\r\n // Reusable envelopes — one per hot-path method.\r\n private readonly _itemEnv: RpcMessage = {\r\n Method: RpcSystemCalls.item,\r\n RelatedId: 0,\r\n };\r\n private readonly _batchEnv: RpcMessage = {\r\n Method: RpcSystemCalls.batch,\r\n RelatedId: 0,\r\n };\r\n private readonly _endEnv: RpcMessage = {\r\n Method: RpcSystemCalls.end,\r\n RelatedId: 0,\r\n };\r\n private readonly _ackEnv: RpcMessage = {\r\n Method: RpcSystemCalls.ack,\r\n RelatedId: 0,\r\n };\r\n private readonly _ackEndEnv: RpcMessage = {\r\n Method: RpcSystemCalls.ackEnd,\r\n RelatedId: 0,\r\n };\r\n\r\n // Reusable arg arrays.\r\n private readonly _itemArgs: [number, unknown] = [0, null];\r\n private readonly _batchArgs: [number, unknown] = [0, null];\r\n private readonly _endArgs: [number, { TypeRef: string; Message: string }] =\r\n [0, { TypeRef: '', Message: '' }];\r\n private readonly _ackArgs: [number, string] = [0, ''];\r\n private readonly _ackEndArgs: [string] = [''];\r\n\r\n private static readonly NO_ERROR: { TypeRef: string; Message: string } =\r\n Object.freeze({ TypeRef: '', Message: '' }) as unknown as {\r\n TypeRef: string;\r\n Message: string;\r\n };\r\n\r\n registry?: RpcMethodRegistry;\r\n\r\n /** Applied to every error this hub sends to a remote peer. Undefined means\r\n * pass through, i.e. the peer sees the original message — set it (e.g. to\r\n * {@link genericErrorFilter}) on a TS-hosted server, where handler errors\r\n * would otherwise disclose internal details to unauthenticated callers. */\r\n errorFilter?: RpcErrorFilter;\r\n\r\n protected _send(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n envelope: RpcMessage,\r\n args?: unknown[]\r\n ): void {\r\n const wireData = format.serializeMessage(\r\n envelope,\r\n args,\r\n conn.encoder,\r\n this.registry\r\n );\r\n if (typeof wireData === 'string') {\r\n conn.send(wireData);\r\n } else {\r\n conn.sendBinary(wireData);\r\n }\r\n }\r\n\r\n handshake(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n peerId: string,\r\n hubId: string,\r\n index: number\r\n ): void {\r\n // RpcHandshake uses [MessagePackObject] array mode for binary.\r\n const handshakeArg = format.isBinary\r\n ? [peerId, null, hubId, 2, index]\r\n : {\r\n RemotePeerId: peerId,\r\n RemoteApiVersionSet: null,\r\n RemoteHubId: hubId,\r\n ProtocolVersion: 2,\r\n Index: index,\r\n };\r\n this._send(conn, format, { Method: RpcSystemCalls.handshake }, [\r\n handshakeArg,\r\n ]);\r\n }\r\n\r\n ok(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n relatedId: number,\r\n result: unknown\r\n ): void {\r\n try {\r\n this._send(\r\n conn,\r\n format,\r\n { Method: RpcSystemCalls.ok, RelatedId: relatedId },\r\n [result]\r\n );\r\n }\r\n catch (e) {\r\n // Mirrors RpcSystemCallSender.cs:104 — \"Failed to send Ok response\".\r\n errorLog?.log(`Failed to send Ok response for call #${relatedId}`, e);\r\n this.error(conn, format, relatedId, e);\r\n }\r\n }\r\n\r\n error(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n relatedId: number,\r\n error: unknown\r\n ): void {\r\n // A TypeRef-less ExceptionInfo makes .NET's ExceptionInfo.ToException()\r\n // return null → the caller's task hangs. Send RemoteException instead\r\n // (decision D3).\r\n this._send(\r\n conn,\r\n format,\r\n { Method: RpcSystemCalls.error, RelatedId: relatedId },\r\n [toExceptionInfo(error, this.errorFilter)]\r\n );\r\n }\r\n\r\n cancel(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n relatedId: number\r\n ): void {\r\n this._send(conn, format, {\r\n Method: RpcSystemCalls.cancel,\r\n RelatedId: relatedId,\r\n });\r\n }\r\n\r\n keepAlive(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n activeCallIds: number[]\r\n ): void {\r\n this._send(conn, format, { Method: RpcSystemCalls.keepAlive }, [\r\n activeCallIds,\r\n ]);\r\n }\r\n\r\n // Tells the remote peer the listed shared-object ids are gone on our side,\r\n // so its matching remote objects (e.g. RpcStream consumers) fail fast\r\n // instead of hanging. Mirrors .NET RpcSystemCallSender.Disconnect.\r\n disconnect(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n localIds: number[]\r\n ): void {\r\n this._send(conn, format, { Method: RpcSystemCalls.disconnect }, [\r\n localIds,\r\n ]);\r\n }\r\n\r\n item(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n localId: number,\r\n index: number,\r\n item: unknown\r\n ): void {\r\n this._itemEnv.RelatedId = localId;\r\n this._itemArgs[0] = index;\r\n this._itemArgs[1] = item;\r\n this._send(conn, format, this._itemEnv, this._itemArgs);\r\n this._itemArgs[1] = null;\r\n }\r\n\r\n batch(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n localId: number,\r\n index: number,\r\n items: unknown[]\r\n ): void {\r\n this._batchEnv.RelatedId = localId;\r\n this._batchArgs[0] = index;\r\n this._batchArgs[1] = items;\r\n this._send(conn, format, this._batchEnv, this._batchArgs);\r\n this._batchArgs[1] = null;\r\n }\r\n\r\n end(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n localId: number,\r\n index: number,\r\n error: {\r\n TypeRef: string;\r\n Message: string;\r\n } = RpcSystemCallSender.NO_ERROR\r\n ): void {\r\n this._endEnv.RelatedId = localId;\r\n this._endArgs[0] = index;\r\n this._endArgs[1] = error;\r\n this._send(conn, format, this._endEnv, this._endArgs);\r\n }\r\n\r\n ack(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n localId: number,\r\n nextIndex: number,\r\n hostId: string\r\n ): void {\r\n this._ackEnv.RelatedId = localId;\r\n this._ackArgs[0] = nextIndex;\r\n this._ackArgs[1] = hostId;\r\n this._send(conn, format, this._ackEnv, this._ackArgs);\r\n }\r\n\r\n ackEnd(\r\n conn: RpcConnection,\r\n format: RpcSerializationFormat,\r\n localId: number,\r\n hostId: string\r\n ): void {\r\n this._ackEndEnv.RelatedId = localId;\r\n this._ackEndArgs[0] = hostId;\r\n this._send(conn, format, this._ackEndEnv, this._ackEndArgs);\r\n }\r\n}\r\n","export interface RpcObjectId {\r\n readonly hostId: string;\r\n readonly localId: number;\r\n}\r\n\r\nexport const RpcObjectKind = { Local: 0, Remote: 1 } as const;\r\nexport type RpcObjectKind = (typeof RpcObjectKind)[keyof typeof RpcObjectKind];\r\n\r\nexport interface IRpcObject {\r\n readonly id: RpcObjectId;\r\n readonly kind: RpcObjectKind;\r\n readonly allowReconnect: boolean;\r\n reconnect(): void;\r\n disconnect(): void;\r\n}\r\n","import Denque from 'denque';\r\nimport { PromiseSource } from '@actuallab/core';\r\nimport { getLogs } from './logging.js';\r\nimport type { RpcObjectId, IRpcObject } from './rpc-object.js';\r\nimport { RpcObjectKind } from './rpc-object.js';\r\nimport type { RpcPeer } from './rpc-peer.js';\r\nimport { RpcStreamSender } from './rpc-stream-sender.js';\r\n\r\nconst { warnLog } = getLogs('RpcStream');\r\n\r\n/** Default ack period (matches .NET RpcStream defaults). */\r\nconst DEFAULT_ACK_PERIOD = 30;\r\n/** Default ack advance window (matches .NET RpcStream defaults). */\r\nconst DEFAULT_ACK_ADVANCE = 61;\r\n/** Largest $sys.B batch a conforming sender emits (matches .NET RpcStream.MaxBatchSize). */\r\nconst MAX_BATCH_SIZE = 1024;\r\n\r\n/** Parsed stream reference from the server's result payload. */\r\nexport interface RpcStreamRef {\r\n readonly hostId: string;\r\n readonly localId: number;\r\n readonly ackPeriod: number;\r\n readonly ackAdvance: number;\r\n readonly allowReconnect: boolean;\r\n readonly isRealTime: boolean;\r\n}\r\n\r\n/**\r\n * A stream source is either a plain async iterable or a factory function\r\n * that receives an AbortSignal and returns an async iterable.\r\n * The AbortSignal is aborted when the sender is disconnected, allowing\r\n * the source to release resources (e.g., camera, microphone) promptly.\r\n */\r\nexport type RpcStreamSource<T> = AsyncIterable<T> | ((abortSignal: AbortSignal) => AsyncIterable<T>);\r\n\r\n/** Configuration options for creating a local (origin-side) RpcStream. */\r\nexport interface RpcStreamOptions<T> {\r\n ackPeriod?: number;\r\n /**\r\n * Wire-level flow-control window: the maximum index the sender may advance\r\n * past the last acknowledged index. Mirrors .NET `RpcStream.AckAdvance`.\r\n */\r\n ackAdvance?: number;\r\n /**\r\n * Local-only sender ring buffer capacity. When undefined (or smaller than\r\n * `ackAdvance`), `ackAdvance` is used as the buffer size.\r\n * Set this larger than `ackAdvance` to let the sender pre-buffer items\r\n * beyond the in-flight window. Not transmitted on the wire.\r\n */\r\n bufferSize?: number;\r\n allowReconnect?: boolean;\r\n isRealTime?: boolean;\r\n canSkipTo?: (item: T) => boolean;\r\n}\r\n\r\n/**\r\n * Parses a stream reference string of the form \"{hostId},{localId},{ackPeriod},{ackAdvance}\".\r\n * Returns null if the value is not a valid stream reference.\r\n */\r\nexport function parseStreamRef(value: unknown): RpcStreamRef | null {\r\n // Text format: \"hostId,localId,ackPeriod,ackAdvance[,allowReconnect[,isRealTime]]\"\r\n if (typeof value === 'string') {\r\n const parts = value.split(',');\r\n if (parts.length < 4 || parts.length > 6) return null;\r\n const hostId = parts[0];\r\n const localId = parseInt(parts[1], 10);\r\n const ackPeriod = parseInt(parts[2], 10);\r\n const ackAdvance = parseInt(parts[3], 10);\r\n if (isNaN(localId) || isNaN(ackPeriod) || isNaN(ackAdvance))\r\n return null;\r\n const allowReconnect = parts.length < 5 || parts[4] !== '0';\r\n const isRealTime = parts.length >= 6 && parts[5] === '1';\r\n return { hostId, localId, ackPeriod, ackAdvance, allowReconnect, isRealTime };\r\n }\r\n // Binary (MessagePack) format: { SerializedId: [hostId, localId], AckPeriod, AckAdvance, AllowReconnect }\r\n if (typeof value === 'object' && value !== null) {\r\n const obj = value as Record<string, unknown>;\r\n const serializedId = obj.SerializedId as unknown[];\r\n if (!Array.isArray(serializedId) || serializedId.length < 2)\r\n return null;\r\n const rawHostId = serializedId[0];\r\n const hostId = String(rawHostId);\r\n const localId = Number(serializedId[1]);\r\n const ackPeriod = Number(obj.AckPeriod ?? 256);\r\n const ackAdvance = Number(obj.AckAdvance ?? 128);\r\n const allowReconnect = obj.AllowReconnect !== false;\r\n const isRealTime = obj.IsRealTime === true;\r\n return { hostId, localId, ackPeriod, ackAdvance, allowReconnect, isRealTime };\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Dual-mode RPC stream — works as both local (origin-side) and remote (target-side).\r\n *\r\n * **Local mode**: wraps an AsyncIterable and carries streaming configuration\r\n * (IsRealTime, CanSkipTo, etc.). Created by service methods and sent to the\r\n * remote peer via RpcStreamSender.\r\n *\r\n * **Remote mode**: consumes items sent by the server via system calls\r\n * ($sys.I, $sys.B, $sys.End) and implements AsyncIterable for for-await-of consumption.\r\n *\r\n * This mirrors the .NET RpcStream<T> design where the same type serves both roles.\r\n */\r\nexport class RpcStream<T> implements AsyncIterable<T>, IRpcObject {\r\n /**\r\n * Grace period (ms) given to a factory-form local source to honor its\r\n * AbortSignal and exit naturally before the sender force-closes the\r\n * iterator via `iterator.return()`.\r\n *\r\n * Only applies to sources provided as `(abortSignal) => AsyncIterable<T>`.\r\n * Plain `AsyncIterable<T>` sources are force-closed immediately on disconnect.\r\n */\r\n public static disconnectGracePeriodMs = 100;\r\n\r\n readonly id: RpcObjectId;\r\n readonly kind: RpcObjectKind;\r\n readonly allowReconnect: boolean;\r\n readonly isRealTime: boolean;\r\n readonly canSkipTo: (item: T) => boolean;\r\n readonly ackPeriod: number;\r\n readonly ackAdvance: number;\r\n /**\r\n * Local-only ring buffer capacity hint for the sender. Undefined means\r\n * \"use ackAdvance as the buffer size\". Not part of the wire ref.\r\n */\r\n readonly bufferSize?: number;\r\n\r\n // Local (origin) mode\r\n readonly localSource?: RpcStreamSource<T>;\r\n\r\n // Remote (target) mode\r\n readonly peer!: RpcPeer;\r\n\r\n // Local-only state\r\n private _sender?: RpcStreamSender<T>;\r\n private _toRefValue?: unknown;\r\n private _whenSent?: Promise<void>;\r\n\r\n // Remote-only state\r\n private _buffer: Denque<T> = new Denque<T>();\r\n private _nextExpectedIndex = 0;\r\n private _consumerWaiting: PromiseSource<void> | null = null;\r\n private _completed = false;\r\n private _completionError: Error | null = null;\r\n private _disposed = false;\r\n private _started = false;\r\n private _iterating = false;\r\n private _ackSentUpTo = 0;\r\n private _nextConsumedIndex = 0;\r\n\r\n /** Create a local (origin-side) stream wrapping an async iterable or source factory with optional configuration. */\r\n constructor(source: RpcStreamSource<T>, options?: RpcStreamOptions<T>);\r\n /** Create a remote (target-side) stream from a parsed stream reference and peer. */\r\n constructor(ref: RpcStreamRef, peer: RpcPeer);\r\n constructor(\r\n sourceOrRef: RpcStreamSource<T> | RpcStreamRef,\r\n optionsOrPeer?: RpcStreamOptions<T> | RpcPeer,\r\n ) {\r\n if (_isStreamSource(sourceOrRef)) {\r\n // Local (origin) mode\r\n const options = (optionsOrPeer as RpcStreamOptions<T> | undefined) ?? {};\r\n this.kind = RpcObjectKind.Local;\r\n this.localSource = sourceOrRef;\r\n this.id = { hostId: '', localId: 0 };\r\n this.allowReconnect = options.allowReconnect ?? true;\r\n this.isRealTime = options.isRealTime ?? false;\r\n this.canSkipTo = options.canSkipTo ?? (() => true);\r\n this.ackPeriod = options.ackPeriod ?? DEFAULT_ACK_PERIOD;\r\n this.ackAdvance = options.ackAdvance ?? DEFAULT_ACK_ADVANCE;\r\n this.bufferSize = options.bufferSize;\r\n } else {\r\n // Remote (target) mode\r\n const ref = sourceOrRef;\r\n const peer = optionsOrPeer as RpcPeer;\r\n this.kind = RpcObjectKind.Remote;\r\n this.id = { hostId: ref.hostId, localId: ref.localId };\r\n this.peer = peer;\r\n this.allowReconnect = ref.allowReconnect;\r\n this.isRealTime = ref.isRealTime;\r\n this.canSkipTo = () => true; // not serialized\r\n this.ackPeriod = ref.ackPeriod;\r\n this.ackAdvance = ref.ackAdvance;\r\n // bufferSize is local-only; not present on the wire ref\r\n }\r\n }\r\n\r\n /**\r\n * For local streams: resolves once the consumer acknowledges the end\r\n * ($sys.AckEnd) or the sender is disconnected — NOT when the source drains,\r\n * since the sender keeps serving acks after End so a reconnect can replay it.\r\n * A source error reaches the consumer as $sys.End(error) instead of rejecting\r\n * this. Throws if `toRef()` hasn't been called yet.\r\n */\r\n get whenSent(): Promise<void> {\r\n if (!this._whenSent)\r\n throw new Error('toRef() must be called before awaiting whenSent.');\r\n return this._whenSent;\r\n }\r\n\r\n /** Local sender backing this stream after `toRef()` has been called.\r\n * `undefined` for remote streams or before `toRef()`. */\r\n get sender(): RpcStreamSender<T> | undefined {\r\n return this._sender;\r\n }\r\n\r\n /**\r\n * For local streams: bind to a peer, create and register an `RpcStreamSender`,\r\n * start pumping items from the local source in the background, and return the\r\n * stream reference.\r\n *\r\n * For binary peers the ref is a MessagePack-compatible object\r\n * (`{ SerializedId, AckPeriod, AckAdvance, AllowReconnect }`);\r\n * for text peers it is a comma-separated string.\r\n *\r\n * Can only be called once per stream.\r\n */\r\n toRef(peer: RpcPeer): unknown {\r\n if (this.kind !== RpcObjectKind.Local)\r\n throw new Error('toRef() is only valid for local streams.');\r\n if (this._sender) {\r\n if (this._sender.peer !== peer)\r\n throw new Error('toRef() was already called with a different peer.');\r\n // Same peer — return the cached ref\r\n return this._toRefValue!;\r\n }\r\n\r\n const isFactory = typeof this.localSource === 'function';\r\n const sender = new RpcStreamSender<T>(\r\n peer, this.ackPeriod, this.ackAdvance,\r\n this.allowReconnect, this.isRealTime, this.canSkipTo,\r\n isFactory, this.bufferSize,\r\n );\r\n peer.sharedObjects.register(sender);\r\n this._sender = sender;\r\n\r\n // Resolve source: if it's a factory, call it with the sender's AbortSignal\r\n const source = isFactory\r\n ? (this.localSource as (s: AbortSignal) => AsyncIterable<T>)(sender.abortSignal)\r\n : this.localSource as AsyncIterable<T>;\r\n\r\n // Start pumping in background (writeFrom awaits the initial Ack)\r\n this._whenSent = sender.writeFrom(source);\r\n\r\n // Cache and return ref in the format appropriate for the peer's serialization\r\n this._toRefValue = peer.serializationFormat.isBinary\r\n ? {\r\n SerializedId: [sender.id.hostId, sender.id.localId],\r\n AckPeriod: sender.ackPeriod,\r\n AckAdvance: sender.ackAdvance,\r\n AllowReconnect: sender.allowReconnect,\r\n }\r\n : sender.toRef();\r\n return this._toRefValue;\r\n }\r\n\r\n /** Called by system call handler when a single item arrives ($sys.I). */\r\n onItem(index: number, item: T): void {\r\n if (this._disposed || this._completed) return;\r\n\r\n if (!_isValidIndex(index)) {\r\n this._failProtocol(`invalid $sys.I index: ${String(index)}`);\r\n return;\r\n }\r\n if (index > this._nextExpectedIndex) {\r\n warnLog?.log(`item index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);\r\n if (!this.allowReconnect) {\r\n this._complete(this._gapError(index));\r\n return;\r\n }\r\n this._sendAck(this._nextExpectedIndex, true);\r\n return;\r\n }\r\n if (index < this._nextExpectedIndex) {\r\n // Duplicate — ack and ignore\r\n this._maybeSendAck(Math.min(index + 1, this._nextConsumedIndex));\r\n return;\r\n }\r\n if (this._isBeyondAckWindow(index + 1)) {\r\n this._failProtocol(this._ackWindowError(index + 1));\r\n return;\r\n }\r\n\r\n this._buffer.push(item);\r\n this._nextExpectedIndex = index + 1;\r\n this._notifyConsumer();\r\n }\r\n\r\n /** Called by system call handler when a batch arrives ($sys.B). */\r\n onBatch(index: number, items: T[]): void {\r\n if (this._disposed || this._completed) return;\r\n\r\n if (!_isValidIndex(index) || !_isValidIndex(items.length)) {\r\n this._failProtocol(`invalid $sys.B index/length: ${String(index)}/${String(items.length)}`);\r\n return;\r\n }\r\n if (items.length > MAX_BATCH_SIZE) {\r\n this._failProtocol(`$sys.B batch is too large: ${items.length} > ${MAX_BATCH_SIZE}`);\r\n return;\r\n }\r\n if (index > this._nextExpectedIndex) {\r\n warnLog?.log(`batch index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);\r\n if (!this.allowReconnect) {\r\n this._complete(this._gapError(index));\r\n return;\r\n }\r\n this._sendAck(this._nextExpectedIndex, true);\r\n return;\r\n }\r\n if (index < this._nextExpectedIndex) {\r\n this._maybeSendAck(Math.min(index + items.length, this._nextConsumedIndex));\r\n return;\r\n }\r\n if (this._isBeyondAckWindow(index + items.length)) {\r\n this._failProtocol(this._ackWindowError(index + items.length));\r\n return;\r\n }\r\n\r\n for (const item of items) this._buffer.push(item);\r\n this._nextExpectedIndex = index + items.length;\r\n this._notifyConsumer();\r\n }\r\n\r\n /** Called by system call handler when the stream ends ($sys.End). */\r\n onEnd(index: number, error: Error | null): void {\r\n if (this._disposed || this._completed) return;\r\n\r\n if (!_isValidIndex(index)) {\r\n this._failProtocol(`invalid $sys.End index: ${String(index)}`);\r\n return;\r\n }\r\n if (index > this._nextExpectedIndex) {\r\n warnLog?.log(`end index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);\r\n if (!this.allowReconnect) {\r\n this._complete(this._gapError(index));\r\n return;\r\n }\r\n this._sendAck(this._nextExpectedIndex, true);\r\n return;\r\n }\r\n if (index < this._nextExpectedIndex) {\r\n // Stale/duplicate End — ack and ignore\r\n this._maybeSendAck(Math.min(index, this._nextConsumedIndex));\r\n return;\r\n }\r\n\r\n this._complete(error);\r\n }\r\n\r\n // -- IRpcObject --\r\n\r\n reconnect(): void {\r\n if (this.kind === RpcObjectKind.Local) return; // no-op for local streams\r\n if (this.allowReconnect) {\r\n this._sendAck(this._nextExpectedIndex, true);\r\n } else {\r\n this.disconnect();\r\n }\r\n }\r\n\r\n disconnect(): void {\r\n if (this.kind === RpcObjectKind.Local) {\r\n // Propagate to sender — aborts the AbortSignal and terminates the iterator\r\n this._sender?.disconnect();\r\n return;\r\n }\r\n if (this._completed) return;\r\n this._completed = true;\r\n this._completionError = new Error('Peer disconnected.');\r\n this._notifyConsumer();\r\n }\r\n\r\n // -- AsyncIterable --\r\n\r\n [Symbol.asyncIterator](): AsyncIterator<T> {\r\n // Local mode: delegate to the local source\r\n if (this.kind === RpcObjectKind.Local) {\r\n const source = typeof this.localSource === 'function'\r\n ? this.localSource(new AbortController().signal)\r\n : this.localSource!;\r\n return source[Symbol.asyncIterator]();\r\n }\r\n\r\n // Remote mode: buffer-based consumption\r\n if (this._iterating)\r\n throw new Error('RpcStream can only be iterated once.');\r\n this._iterating = true;\r\n\r\n return {\r\n next: async (): Promise<IteratorResult<T>> => {\r\n // Lazy start: register with the peer and send the initial ack on\r\n // the first next() call — a never-enumerated remote stream must\r\n // create no lease on either peer (RpcStream.GetAsyncEnumerator parity).\r\n if (!this._started) {\r\n this._started = true;\r\n this.peer.remoteObjects.register(this);\r\n this._sendAck(0, true);\r\n }\r\n this._maybeSendAck(this._nextConsumedIndex);\r\n\r\n // Read from buffer or wait for new data\r\n for (;;) {\r\n if (!this._buffer.isEmpty()) {\r\n // shift() is O(1) on Denque and releases the slot so the\r\n // ring buffer can be reclaimed as the consumer drains.\r\n const value = this._buffer.shift()!;\r\n this._nextConsumedIndex++;\r\n return { value, done: false };\r\n }\r\n\r\n if (this._completed) {\r\n this.dispose();\r\n if (this._completionError) throw this._completionError;\r\n return { value: undefined as T, done: true };\r\n }\r\n\r\n // Wait for more data\r\n this._consumerWaiting = new PromiseSource<void>();\r\n await this._consumerWaiting;\r\n }\r\n },\r\n\r\n return: (): Promise<IteratorResult<T>> => {\r\n // Before the lazy start nothing was sent or registered, so\r\n // disposal must stay a local no-op (C#: CloseFromLock sends\r\n // nothing when the remote channel was never created).\r\n if (this._started && !this._completed)\r\n this._sendAckEnd();\r\n\r\n this.dispose();\r\n return Promise.resolve({ value: undefined as T, done: true });\r\n },\r\n };\r\n }\r\n\r\n dispose(): void {\r\n if (this._disposed) return;\r\n this._disposed = true;\r\n this._completed = true;\r\n if (this.kind === RpcObjectKind.Remote) {\r\n this.peer.remoteObjects.unregister(this);\r\n }\r\n this._notifyConsumer();\r\n }\r\n\r\n // -- Private --\r\n\r\n // Mirrors .NET RpcStream.CloseFromLock: acknowledge the end to the sender\r\n // (so its RpcSharedStream is released) and unregister immediately.\r\n private _complete(error: Error | null): void {\r\n if (this._completed) return;\r\n this._completed = true;\r\n this._completionError = error;\r\n this._sendAckEnd();\r\n if (this.kind === RpcObjectKind.Remote)\r\n this.peer.remoteObjects.unregister(this);\r\n this._notifyConsumer();\r\n }\r\n\r\n // Terminates the stream the way the non-reconnectable gap path does: the\r\n // consumer sees the error and the sender gets $sys.AckEnd, so nothing is\r\n // left pumping into a stream we no longer read.\r\n private _failProtocol(message: string): void {\r\n warnLog?.log(`localId=${this.id.localId}: ${message}`);\r\n this._complete(new Error(message));\r\n }\r\n\r\n // The remote advertised `ackAdvance` as the furthest it may run ahead of our\r\n // last acknowledged index. The sending side is the only place that window\r\n // was ever enforced, which left a flooding remote free to grow our buffer\r\n // without bound (I6) — so we enforce our half of the contract here.\r\n //\r\n // The window is credited by consumption, never by `_ackSentUpTo`: a reset ack\r\n // re-bases to what we've *received*, so crediting it would let a peer ratchet\r\n // its own limit up by alternating \"fill the window\" with a forced gap.\r\n // Twice ackAdvance is what a conforming sender can leave buffered (ackAdvance\r\n // in steady state, plus ackAdvance more after a reset re-bases it), and\r\n // ackPeriod covers the lag of acks behind consumption.\r\n private _isBeyondAckWindow(nextExpectedIndex: number): boolean {\r\n if (this.ackAdvance <= 0)\r\n return false;\r\n\r\n return nextExpectedIndex > this._nextConsumedIndex + this._ackWindowSize();\r\n }\r\n\r\n private _ackWindowSize(): number {\r\n return 2 * this.ackAdvance + this.ackPeriod;\r\n }\r\n\r\n private _ackWindowError(nextExpectedIndex: number): string {\r\n return `Stream ran past its ack window: reached ${nextExpectedIndex}, `\r\n + `consumed ${this._nextConsumedIndex}, ackAdvance ${this.ackAdvance}`;\r\n }\r\n\r\n private _gapError(index: number): Error {\r\n return new Error(\r\n `Stream gap at index ${index} (expected ${this._nextExpectedIndex}); reconnect not allowed`\r\n );\r\n }\r\n\r\n private _notifyConsumer(): void {\r\n if (this._consumerWaiting !== null) {\r\n this._consumerWaiting.resolve();\r\n this._consumerWaiting = null;\r\n }\r\n }\r\n\r\n private _maybeSendAck(nextIndex: number): void {\r\n if (this.ackPeriod <= 0) return;\r\n const threshold = this._ackSentUpTo + this.ackPeriod;\r\n if (nextIndex >= threshold) {\r\n this._sendAck(nextIndex, false);\r\n }\r\n }\r\n\r\n private static readonly _emptyGuid = '00000000-0000-0000-0000-000000000000';\r\n\r\n private _sendAck(nextIndex: number, mustReset: boolean): void {\r\n this._ackSentUpTo = nextIndex;\r\n // A dropped ack is recovered by the reset ack `reconnect()` sends once\r\n // connected — see `RpcPeer.wireConnection` for why this is gated.\r\n const conn = this.peer.wireConnection;\r\n if (conn) {\r\n const hostId = mustReset ? this.id.hostId : RpcStream._emptyGuid;\r\n this.peer.hub.systemCallSender.ack(\r\n conn,\r\n this.peer.serializationFormat,\r\n this.id.localId,\r\n nextIndex,\r\n hostId\r\n );\r\n }\r\n }\r\n\r\n private _sendAckEnd(): void {\r\n // A dropped AckEnd just means the sender terminates via\r\n // disconnect/keep-alive instead.\r\n const conn = this.peer.wireConnection;\r\n if (conn) {\r\n this.peer.hub.systemCallSender.ackEnd(\r\n conn,\r\n this.peer.serializationFormat,\r\n this.id.localId,\r\n this.id.hostId\r\n );\r\n }\r\n }\r\n}\r\n\r\nfunction _isValidIndex(index: number): boolean {\r\n return Number.isSafeInteger(index) && index >= 0;\r\n}\r\n\r\n/** @internal Type guard for RpcStreamSource (AsyncIterable or factory function). */\r\nfunction _isStreamSource<T>(value: unknown): value is RpcStreamSource<T> {\r\n if (typeof value === 'function') return true;\r\n return (\r\n value !== null &&\r\n typeof value === 'object' &&\r\n Symbol.asyncIterator in (value)\r\n );\r\n}\r\n\r\n/**\r\n * Converts a wire value that *is* a stream reference — the comma-separated\r\n * string the text formats use, or the object shape the binary ones use — into a\r\n * live remote {@link RpcStream}. Returns `null` when the value is not a\r\n * reference.\r\n *\r\n * Call results are deliberately **not** scanned for stream references. A\r\n * TypeScript client has no typed method definitions, so \"is this string a\r\n * stream ref?\" can only be a shape heuristic, and ordinary data matches it\r\n * constantly (\"1,2,3,4\", a CSV row, a coordinate tuple). Deciding that a value\r\n * is a stream is therefore the caller's job — it is the only side that knows\r\n * which positions carry streams.\r\n */\r\nexport function toRpcStream<T>(value: unknown, peer: RpcPeer): RpcStream<T> | null {\r\n const ref = parseStreamRef(value);\r\n if (ref === null)\r\n return null;\r\n\r\n // Registration is deferred to the first iteration (see RpcStream's\r\n // lazy-start), so a stream that is never enumerated leases nothing on\r\n // either peer.\r\n return new RpcStream<T>(ref, peer);\r\n}\r\n","import { PromiseSource, RingBuffer } from '@actuallab/core';\r\nimport { getLogs } from './logging.js';\r\nimport { toExceptionInfo } from './rpc-error.js';\r\nimport type { RpcObjectId, IRpcObject } from './rpc-object.js';\r\nimport { RpcObjectKind } from './rpc-object.js';\r\nimport type { RpcPeer } from './rpc-peer.js';\r\nimport { RpcStream } from './rpc-stream.js';\r\n\r\nconst { warnLog } = getLogs('RpcSharedStream');\r\n\r\n/** Settle-only view of a promise: observes it without consuming its outcome. */\r\nfunction _ignore(): void { /* intentionally empty */ }\r\n\r\n/** Default ack period for server-side streams. */\r\nconst DEFAULT_ACK_PERIOD = 256;\r\n/** Default ack advance window for server-side streams. */\r\nconst DEFAULT_ACK_ADVANCE = 128;\r\n/** Sliding window over which minRttMs is the minimum of ack round-trip samples. */\r\nconst RTT_WINDOW_MS = 20_000;\r\n/** Hard cap on unmatched send timestamps. They are normally drained by the next\r\n * ACK, but nothing drains them while the peer is disconnected — and RTT\r\n * sampling is diagnostics, so dropping the oldest half is free. */\r\nconst MAX_PENDING_SEND_TIMES = 4096;\r\n\r\n/**\r\n * Server-side RPC stream producer — sends items to a remote consumer via\r\n * $sys.I / $sys.B / $sys.End system calls.\r\n *\r\n * Wire protocol (server perspective):\r\n * - Server returns the stream reference string as $sys.Ok\r\n * - Client sends Ack(0, hostId) to start the stream\r\n * - Server sends I (single item) / B (batch) messages\r\n * - Client acks every ackPeriod items for flow control\r\n * - Server sends End to signal completion (with optional error)\r\n * - Client sends AckEnd to acknowledge completion\r\n *\r\n * The sender uses a single ACK-driven loop:\r\n * - On each ACK, drain the source synchronously into the ring buffer up to\r\n * `effectiveBufferSize` (this lets us pre-buffer past `ackAdvance`).\r\n * - For real-time, optionally compact the unsent suffix to the latest\r\n * buffered `canSkipTo` item.\r\n * - Send items up to the `ackAdvance` flow-control window.\r\n * - Wait for the next ACK.\r\n *\r\n * Setting `bufferSize > ackAdvance` lets the sender pre-buffer items past\r\n * the in-flight window so a fresh ACK can be served from RAM instead of\r\n * waiting on the source.\r\n *\r\n * Direct port of .NET `RpcSharedStream<T>` at\r\n * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.\r\n */\r\nexport class RpcStreamSender<T> implements IRpcObject {\r\n private static readonly _emptyGuid = '00000000-0000-0000-0000-000000000000';\r\n\r\n readonly id: RpcObjectId;\r\n readonly kind = RpcObjectKind.Local;\r\n readonly allowReconnect: boolean;\r\n readonly isRealTime: boolean;\r\n readonly canSkipTo: (item: T) => boolean;\r\n readonly peer: RpcPeer;\r\n readonly ackPeriod: number;\r\n /**\r\n * Wire-level flow-control window: the maximum index the sender may\r\n * advance past the most recently acknowledged index. Mirrors .NET\r\n * `RpcStream.AckAdvance`.\r\n */\r\n readonly ackAdvance: number;\r\n /**\r\n * Effective local ring buffer capacity used by the pump. Set larger\r\n * than `ackAdvance` to pre-buffer items beyond the in-flight window;\r\n * values below `ackAdvance` are clamped up (with a warning) at\r\n * construction time. Not transmitted on the wire.\r\n */\r\n readonly bufferSize: number;\r\n /**\r\n * If true, the source was produced by a factory `(abortSignal) => AsyncIterable<T>`\r\n * and may honor the AbortSignal to exit gracefully. `disconnect()` first aborts\r\n * the signal and gives the source up to `RpcStream.disconnectGracePeriodMs` ms\r\n * before force-closing via `iterator.return()`.\r\n *\r\n * If false, the source is a plain AsyncIterable that cannot observe the signal,\r\n * so `disconnect()` force-closes immediately.\r\n */\r\n readonly sourceUsesAbortSignal: boolean;\r\n\r\n // -- Pump state --\r\n /** Next stream index that will be sent (matches .NET's `index`). */\r\n private _nextIndex = 0;\r\n /** Torn down — `disconnect()` ran. .NET's counterpart is disposal; note it\r\n * does NOT mean \"an End was sent\", which .NET does not track at all. */\r\n private _isDisposed = false;\r\n /** The pump has returned. .NET's `WhenRunning.IsCompleted`. */\r\n private _isCompleted = false;\r\n private _started = new PromiseSource<void>();\r\n /** Highest `nextIndex` observed in an ACK so far (matches .NET's `lastAckNextIndex` idiom). */\r\n private _lastAckedIndex = 0;\r\n private _abortController = new AbortController();\r\n private _iterator: AsyncIterator<T> | null = null;\r\n\r\n // -- RTT sampling (send → matching ack round trips, windowed minimum) --\r\n private _pendingSendTimes: { index: number; sentAtMs: number }[] = [];\r\n private _rttSamples: { atMs: number; rttMs: number }[] = [];\r\n\r\n // -- Recorder-controller metrics (Step 9.1) --\r\n /** Total items skipped via real-time `canSkipTo` compaction. */\r\n private _skipCount = 0;\r\n /** Fires after a $sys.Ack has been processed (post-compaction). The\r\n * recorder's quality controller uses this to bump a \"last ACK at\"\r\n * watchdog so it can distinguish \"stuck\" (no ACK > N s) from\r\n * \"throttled\" (ACKs flowing). */\r\n onAckProcessed?: () => void;\r\n /** Fires after each item is pushed onto the local ring buffer. The\r\n * argument is the buffer count *after* the push. Together with\r\n * `onAckProcessed` (which is the only place items leave the buffer),\r\n * this gives controllers a complete picture of buffer utilization.\r\n * When `bufferedCount === bufferSize`, the source pull is paused\r\n * until the next ACK frees space. Listener errors are swallowed. */\r\n onBuffered?: (bufferedCount: number) => void;\r\n\r\n get nextIndex(): number {\r\n return this._nextIndex;\r\n }\r\n get lastAckIndex(): number {\r\n return this._lastAckedIndex;\r\n }\r\n get skipCount(): number {\r\n return this._skipCount;\r\n }\r\n // Windowed-MIN ack round trip (ms, -1 until sampled): converges on propagation\r\n // RTT — unlike a mean/EMA it is not inflated by self-induced queuing.\r\n get minRttMs(): number {\r\n const cutoffMs = Date.now() - RTT_WINDOW_MS;\r\n while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)\r\n this._rttSamples.shift();\r\n if (this._rttSamples.length === 0)\r\n return -1;\r\n\r\n let min = this._rttSamples[0].rttMs;\r\n for (const s of this._rttSamples) {\r\n if (s.rttMs < min)\r\n min = s.rttMs;\r\n }\r\n return min;\r\n }\r\n\r\n // -- Coalesced ACK state (analog of .NET's Channel<(long, bool)>) --\r\n // The pump reduces any queued ACKs to the newest index plus the OR of\r\n // `mustReset` anyway, so we accumulate exactly that at receipt: an ACK flood\r\n // then costs O(1) memory and O(1) work instead of an unbounded array drained\r\n // with repeated `shift()`.\r\n /** Newest unprocessed ACK index; -1 means \"nothing pending\". */\r\n private _ackIndex = -1;\r\n private _ackMustReset = false;\r\n /** Highest ACK index accepted so far — the monotonicity baseline a\r\n * non-reset ACK may not regress below. */\r\n private _maxAckIndex = 0;\r\n private _whenAckReady: PromiseSource<void> | null = null;\r\n\r\n constructor(\r\n peer: RpcPeer,\r\n ackPeriod = DEFAULT_ACK_PERIOD,\r\n ackAdvance = DEFAULT_ACK_ADVANCE,\r\n allowReconnect = true,\r\n isRealTime = false,\r\n canSkipTo: (item: T) => boolean = () => true,\r\n sourceUsesAbortSignal = false,\r\n bufferSize?: number,\r\n ) {\r\n const localId = peer.sharedObjects.nextId();\r\n this.id = { hostId: peer.hub.hubId, localId };\r\n this.allowReconnect = allowReconnect;\r\n this.isRealTime = isRealTime;\r\n this.canSkipTo = canSkipTo;\r\n this.peer = peer;\r\n this.ackPeriod = ackPeriod;\r\n this.ackAdvance = ackAdvance;\r\n this.sourceUsesAbortSignal = sourceUsesAbortSignal;\r\n\r\n if (bufferSize !== undefined && bufferSize !== 0 && bufferSize < ackAdvance)\r\n warnLog?.log(\r\n `RpcStream bufferSize (${bufferSize}) is below ackAdvance (${ackAdvance}); using ackAdvance as buffer size.`,\r\n );\r\n this.bufferSize = bufferSize !== undefined && bufferSize >= ackAdvance ? bufferSize : ackAdvance;\r\n }\r\n\r\n /** AbortSignal that is aborted when the sender is disconnected. */\r\n get abortSignal(): AbortSignal {\r\n return this._abortController.signal;\r\n }\r\n\r\n /** Returns the stream reference string for the $sys.Ok response. */\r\n toRef(): string {\r\n return `${this.id.hostId},${this.id.localId},${this.ackPeriod},${this.ackAdvance},${this.allowReconnect ? '1' : '0'},${this.isRealTime ? '1' : '0'}`;\r\n }\r\n\r\n /**\r\n * Called by system call handler when $sys.Ack is received from the client.\r\n *\r\n * Mirrors .NET `RpcSharedStream<T>.OnAck` at\r\n * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:68-101.\r\n */\r\n onAck(nextIndex: number, hostId: string): void {\r\n const mustReset = hostId !== '' && hostId !== RpcStreamSender._emptyGuid;\r\n\r\n // Host mismatch — the consumer reconnected to a different server\r\n // instance, so our stream can never satisfy its acks (RpcSharedStream.cs:82-86).\r\n if (mustReset && hostId !== this.id.hostId) {\r\n this._sendDisconnect();\r\n return;\r\n }\r\n\r\n // A consumer can only acknowledge indexes we actually sent. A NaN,\r\n // fractional, negative or run-ahead index is a protocol violation, and\r\n // accepting one would push `_nextIndex` past the replay buffer — after\r\n // which the pump drains the source without ever sending again.\r\n if (!Number.isSafeInteger(nextIndex) || nextIndex < 0 || nextIndex > this._nextIndex) {\r\n this._sendDisconnect();\r\n return;\r\n }\r\n\r\n if (!this._started.isCompleted) {\r\n // A not-yet-started stream only accepts the initial connect ack.\r\n if (mustReset && nextIndex === 0)\r\n this._started.resolve();\r\n else {\r\n this._sendDisconnect();\r\n return;\r\n }\r\n } else if (this._isCompleted) {\r\n // The pump has returned — nothing left to serve. An End that was\r\n // merely *sent* is not terminal: the End sits in the replay buffer\r\n // and a reset ack re-sends it, exactly like any other item.\r\n this._sendDisconnect();\r\n return;\r\n } else if (mustReset && !this.allowReconnect) {\r\n // Reset (reconnect) ack on a non-reconnectable stream — reject.\r\n this._sendDisconnect();\r\n return;\r\n }\r\n\r\n // Only a reset may move the acknowledged position backwards; a plain\r\n // regression carries no window information the pump doesn't already have.\r\n if (!mustReset && nextIndex < this._maxAckIndex)\r\n return;\r\n\r\n this._maxAckIndex = nextIndex;\r\n this._sampleRtt(nextIndex, mustReset);\r\n this._ackIndex = nextIndex;\r\n this._ackMustReset ||= mustReset;\r\n if (this._whenAckReady) {\r\n this._whenAckReady.resolve();\r\n this._whenAckReady = null;\r\n }\r\n }\r\n\r\n // Tells the consumer this shared stream is gone, then disposes it —\r\n // mirrors RpcSharedStream.SendDisconnect (RpcSharedStream.cs:284-289).\r\n private _sendDisconnect(): void {\r\n const conn = this.peer.wireConnection;\r\n if (conn)\r\n this.peer.hub.systemCallSender.disconnect(conn, this.peer.serializationFormat, [this.id.localId]);\r\n\r\n this.disconnect();\r\n }\r\n\r\n /** Called by system call handler when $sys.AckEnd is received from the client. */\r\n onAckEnd(_hostId: string): void {\r\n this.disconnect();\r\n }\r\n\r\n /**\r\n * Send a single item to the client at the current `_nextIndex`.\r\n *\r\n * Advances `_nextIndex` unconditionally — even if the peer is disconnected\r\n * the item is considered \"sent\" from the pump's perspective so the replay\r\n * buffer indexing stays consistent. If the peer isn't connected, the wire\r\n * message is dropped; the item will be resent from the replay buffer\r\n * after a reconnect (client sends `Ack(N, mustReset=true)`).\r\n */\r\n sendItem(item: T): void {\r\n if (this._isDisposed) return;\r\n const conn = this.peer.wireConnection;\r\n if (conn) {\r\n this.peer.hub.systemCallSender.item(\r\n conn, this.peer.serializationFormat, this.id.localId, this._nextIndex, item,\r\n );\r\n this._pendingSendTimes.push({ index: this._nextIndex, sentAtMs: Date.now() });\r\n if (this._pendingSendTimes.length > MAX_PENDING_SEND_TIMES)\r\n this._pendingSendTimes.splice(0, MAX_PENDING_SEND_TIMES >> 1);\r\n }\r\n this._nextIndex++;\r\n }\r\n\r\n /** Send a batch of items to the client. See {@link sendItem} for disconnect semantics. */\r\n sendBatch(items: T[]): void {\r\n if (this._isDisposed || items.length === 0) return;\r\n const conn = this.peer.wireConnection;\r\n if (conn) {\r\n this.peer.hub.systemCallSender.batch(\r\n conn, this.peer.serializationFormat, this.id.localId, this._nextIndex, items,\r\n );\r\n }\r\n this._nextIndex += items.length;\r\n }\r\n\r\n /** Signal stream completion to the client. Re-sendable on purpose: if the\r\n * peer was not connected the frame is dropped, and the End stays in the\r\n * replay buffer for the next reset ack — .NET has no \"already ended\" latch\r\n * (the End is an ordinary buffer entry, RpcSharedStream.cs:185/239). */\r\n sendEnd(error?: Error | null): void {\r\n if (this._isDisposed) return;\r\n // The End occupies an index, like any item, and advances `_nextIndex`\r\n // whether or not the frame went out (.NET: `_batcher.Add(index++, item)`,\r\n // RpcSharedStream.cs:258). Leaving it un-advanced would re-send the End\r\n // on a plain ack at the End's own index, and would make `onAck` reject\r\n // an End+1 reset ack that .NET accepts.\r\n const index = this._nextIndex++;\r\n const conn = this.peer.wireConnection;\r\n if (!conn) return;\r\n // .NET ExceptionInfo is a non-nullable value type, so we must always\r\n // emit a valid map shape (empty TypeRef+Message for the \"no error\" case).\r\n const errorInfo = error\r\n ? toExceptionInfo(error)\r\n : { TypeRef: '', Message: '' };\r\n this.peer.hub.systemCallSender.end(\r\n conn, this.peer.serializationFormat, this.id.localId, index, errorInfo,\r\n );\r\n }\r\n\r\n /**\r\n * Consume an AsyncIterable and send all items to the client.\r\n *\r\n * Matches .NET `RpcSharedStream<T>.OnRun` at\r\n * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.\r\n */\r\n async writeFrom(source: AsyncIterable<T>): Promise<void> {\r\n await this._started;\r\n if (this._isDisposed) return;\r\n\r\n const iterator = source[Symbol.asyncIterator]();\r\n this._iterator = iterator;\r\n // Tracks whether the generator has reached a terminal state (done=true\r\n // or thrown). When true, its finally block has already run and the\r\n // iterator reference can be cleared. When false, disconnect()'s\r\n // scheduled iterator.return() is what drives it to its finally.\r\n const state: _PumpState = { iteratorDone: false };\r\n try {\r\n await this._run(iterator, state);\r\n } finally {\r\n this._isCompleted = true;\r\n if (state.iteratorDone && this._iterator === iterator) {\r\n this._iterator = null;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * ACK-driven pump. Non-real-time streams apply normal backpressure at\r\n * `ackAdvance`; real-time streams may compact the already-buffered unsent\r\n * suffix to the latest buffered `canSkipTo` item after the consumer ACKs.\r\n * With `bufferSize > ackAdvance` the local ring buffer holds more than the\r\n * in-flight window — real-time mode uses the extra space to pre-buffer.\r\n */\r\n /* eslint-disable @typescript-eslint/no-unnecessary-condition -- _isDisposed changes across awaits */\r\n private async _run(\r\n iterator: AsyncIterator<T>,\r\n state: _PumpState,\r\n ): Promise<void> {\r\n const buffer = new RingBuffer<_StreamItem<T>>(this.bufferSize + 1);\r\n const isRealTime = this.isRealTime;\r\n let bufferStart = 0;\r\n let isFullyBuffered = false;\r\n let whenMovedNext: Promise<IteratorResult<T>> | null = null;\r\n const pending = Symbol('pending');\r\n\r\n const readNext = async (): Promise<IteratorResult<T>> => {\r\n const next = whenMovedNext ?? iterator.next();\r\n whenMovedNext = null;\r\n return await next;\r\n };\r\n\r\n const tryReadReady = async (): Promise<IteratorResult<T> | typeof pending> => {\r\n whenMovedNext ??= iterator.next();\r\n return await Promise.race([\r\n whenMovedNext,\r\n Promise.resolve(pending),\r\n ]);\r\n };\r\n\r\n const fireBuffered = (): void => {\r\n try {\r\n this.onBuffered?.(buffer.count);\r\n } catch { /* listener errors don't break the pump */ }\r\n };\r\n\r\n const bufferNext = async (onlyIfReady: boolean, prefetchNext: boolean): Promise<boolean> => {\r\n let r: IteratorResult<T> | typeof pending;\r\n try {\r\n r = onlyIfReady ? await tryReadReady() : await readNext();\r\n if (r === pending)\r\n return false;\r\n whenMovedNext = null;\r\n if (this._isDisposed) return false;\r\n if (r.done) {\r\n buffer.pushTail(_endItem);\r\n isFullyBuffered = true;\r\n state.iteratorDone = true;\r\n } else {\r\n buffer.pushTail({ kind: 'value', value: r.value });\r\n if (prefetchNext)\r\n whenMovedNext = iterator.next();\r\n }\r\n fireBuffered();\r\n return true;\r\n } catch (e) {\r\n whenMovedNext = null;\r\n state.iteratorDone = true;\r\n isFullyBuffered = true;\r\n buffer.pushTail({\r\n kind: 'error',\r\n error: e instanceof Error ? e : new Error(String(e)),\r\n });\r\n fireBuffered();\r\n return true;\r\n }\r\n };\r\n\r\n nextAck:\r\n while (true) {\r\n // 1. Await for an acknowledgement & process accumulated ACKs.\r\n let ack = this._tryProcessAcks();\r\n if (!ack) {\r\n await this._waitAckReady();\r\n if (this._isDisposed) return;\r\n ack = this._tryProcessAcks();\r\n if (!ack) {\r\n warnLog?.log(\"Something is off: couldn't read an acknowledgement\");\r\n return;\r\n }\r\n }\r\n this._lastAckedIndex = ack.nextIndex;\r\n\r\n // 1.1. Reconnect: real-time streams can clear stale buffered data\r\n // and restart from the next source item accepted by canSkipTo.\r\n if (isRealTime && ack.mustReset && !isFullyBuffered) {\r\n buffer.clear();\r\n bufferStart = this._nextIndex;\r\n while (true) {\r\n if (this._isDisposed) return;\r\n let item: _StreamItem<T>;\r\n let accepted = false;\r\n try {\r\n const r = await readNext();\r\n if (this._isDisposed) return;\r\n if (r.done) {\r\n item = _endItem;\r\n isFullyBuffered = true;\r\n state.iteratorDone = true;\r\n } else if (this.canSkipTo(r.value)) {\r\n item = { kind: 'value', value: r.value };\r\n accepted = true;\r\n } else {\r\n continue; // discard; keep draining\r\n }\r\n } catch (e) {\r\n state.iteratorDone = true;\r\n isFullyBuffered = true;\r\n item = {\r\n kind: 'error',\r\n error: e instanceof Error ? e : new Error(String(e)),\r\n };\r\n }\r\n buffer.pushTail(item);\r\n fireBuffered();\r\n if (accepted || isFullyBuffered) break;\r\n }\r\n }\r\n\r\n // 2. Remove what's useless from the buffer.\r\n const shift = _clamp(ack.nextIndex - bufferStart, 0, buffer.count);\r\n if (shift > 0) {\r\n buffer.moveHead(shift);\r\n bufferStart += shift;\r\n }\r\n\r\n // 3. Recalculate the next range to send.\r\n if (this._nextIndex < bufferStart) {\r\n if (!this._isDisposed)\r\n this.sendEnd(new Error('Stream position unavailable.'));\r\n return;\r\n }\r\n let bufferIndex = this._nextIndex - bufferStart;\r\n\r\n // 3. Send as much as the current ACK window allows.\r\n const maxIndex = ack.nextIndex + this.ackAdvance;\r\n while (this._nextIndex < maxIndex) {\r\n if (this._isDisposed) return;\r\n\r\n if (isRealTime) {\r\n // Real-time: pre-buffer aggressively up to the ring's\r\n // capacity (which may exceed ackAdvance when bufferSize\r\n // is set), so a freshly arrived ACK is served from RAM.\r\n while (buffer.hasRemainingCapacity && !isFullyBuffered && !this._hasPendingAck) {\r\n if (!await bufferNext(true, true))\r\n break;\r\n }\r\n if (bufferIndex >= buffer.count\r\n && !isFullyBuffered\r\n && buffer.hasRemainingCapacity\r\n && !this._hasPendingAck) {\r\n // Wake on either the source or an ACK — a mustReset ACK\r\n // must not wait out a stalled source (a paused camera, a\r\n // frozen screen share). Mirrors the WhenAny in\r\n // RpcSharedStream.cs:274-279; the non-real-time branch\r\n // below deliberately just blocks and applies backpressure.\r\n whenMovedNext ??= iterator.next();\r\n const whenAckReady = (this._whenAckReady ??= new PromiseSource<void>());\r\n // Race a settle-only view, never the source promise itself:\r\n // a rejection must stay in `whenMovedNext` for `readNext`\r\n // to hit inside `bufferNext`'s try/catch, which is what\r\n // turns it into an error item and then $sys.End(error).\r\n // Awaiting it here instead would reject the whole pump.\r\n await Promise.race([whenMovedNext.then(_ignore, _ignore), whenAckReady]);\r\n if (!this._hasPendingAck)\r\n await bufferNext(false, true);\r\n }\r\n if (ack.nextIndex > 0) {\r\n const result = _compactBufferedUnsentSuffix(\r\n buffer, bufferIndex, this.canSkipTo);\r\n bufferIndex = result.newIndex;\r\n if (result.skipped > 0)\r\n this._skipCount += result.skipped;\r\n }\r\n\r\n // Bail out so step 1 can process a mustReset ACK immediately.\r\n if (this._hasPendingAck) break;\r\n }\r\n\r\n // Drain buffered items until the window is full.\r\n while (this._nextIndex < maxIndex && bufferIndex < buffer.count) {\r\n const item = buffer.get(bufferIndex++);\r\n if (item.kind === 'value') {\r\n this.sendItem(item.value);\r\n } else {\r\n // End (clean or error). Unlike a `return`, this keeps the\r\n // pump serving acks so a reset ack replays the End from\r\n // the buffer. The pump exits on AckEnd (-> disconnect) or\r\n // teardown. Mirrors RpcSharedStream.cs:258-261.\r\n this.sendEnd(item.kind === 'error' ? item.error : undefined);\r\n continue nextAck;\r\n }\r\n }\r\n if (this._nextIndex >= maxIndex)\r\n break;\r\n if (isFullyBuffered) break;\r\n if (!buffer.hasRemainingCapacity) break;\r\n\r\n if (!isRealTime)\r\n await bufferNext(false, false);\r\n }\r\n }\r\n }\r\n /* eslint-enable @typescript-eslint/no-unnecessary-condition */\r\n\r\n private get _hasPendingAck(): boolean {\r\n return this._ackIndex >= 0;\r\n }\r\n\r\n /** Consume the coalesced ACK state (or null if nothing is pending).\r\n * A reconnect's reset ACK may be coalesced with the regular consumption\r\n * ACKs the client sends right after it, so `mustReset` accumulates across\r\n * the whole batch — the newest ACK alone can't represent it. */\r\n private _tryProcessAcks(): { nextIndex: number; mustReset: boolean } | null {\r\n const nextIndex = this._ackIndex;\r\n if (nextIndex < 0)\r\n return null;\r\n\r\n const mustReset = this._ackMustReset;\r\n this._ackIndex = -1;\r\n this._ackMustReset = false;\r\n if (mustReset || this._nextIndex < nextIndex)\r\n this._nextIndex = nextIndex;\r\n\r\n try {\r\n this.onAckProcessed?.();\r\n } catch { /* listener errors don't break the pump */ }\r\n return { nextIndex, mustReset };\r\n }\r\n\r\n /** Wait until an ACK is pending (or the sender is torn down). */\r\n private async _waitAckReady(): Promise<void> {\r\n if (this._hasPendingAck || this._isDisposed)\r\n return;\r\n\r\n const whenAckReady = (this._whenAckReady ??= new PromiseSource<void>());\r\n await whenAckReady;\r\n if (this._whenAckReady === whenAckReady)\r\n this._whenAckReady = null;\r\n }\r\n\r\n // One RTT sample per ack: time from sending the newest acked item to the\r\n // ack's arrival (a slight upper bound — includes the server's ack cadence).\r\n private _sampleRtt(ackNextIndex: number, mustReset: boolean): void {\r\n if (mustReset) {\r\n this._pendingSendTimes.length = 0;\r\n return;\r\n }\r\n\r\n let sentAtMs = -1;\r\n while (this._pendingSendTimes.length > 0 && this._pendingSendTimes[0].index < ackNextIndex) {\r\n sentAtMs = this._pendingSendTimes[0].sentAtMs;\r\n this._pendingSendTimes.shift();\r\n }\r\n if (sentAtMs < 0)\r\n return;\r\n\r\n const nowMs = Date.now();\r\n const cutoffMs = nowMs - RTT_WINDOW_MS;\r\n while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)\r\n this._rttSamples.shift();\r\n this._rttSamples.push({ atMs: nowMs, rttMs: nowMs - sentAtMs });\r\n }\r\n\r\n // -- IRpcObject --\r\n\r\n reconnect(): void {\r\n // Server-side streams don't support reconnection — the client\r\n // creates a new stream on reconnect.\r\n }\r\n\r\n disconnect(): void {\r\n if (this._isDisposed) return;\r\n this._isDisposed = true;\r\n this._abortController.abort();\r\n if (!this._started.isCompleted) {\r\n this._started.resolve();\r\n }\r\n if (this._whenAckReady) {\r\n this._whenAckReady.resolve();\r\n this._whenAckReady = null;\r\n }\r\n this.peer.sharedObjects.unregister(this);\r\n\r\n // Iterator force-close policy:\r\n // - Plain AsyncIterable: abortSignal is unobserved, so force-close now.\r\n // - Factory source (abortSignal-aware): give it a grace period to exit\r\n // gracefully, then force-close if still pumping.\r\n const iterator = this._iterator;\r\n if (!iterator) return;\r\n if (!this.sourceUsesAbortSignal) {\r\n this._iterator = null;\r\n this._forceCloseIterator(iterator);\r\n return;\r\n }\r\n setTimeout(() => {\r\n if (this._iterator === iterator) {\r\n this._iterator = null;\r\n this._forceCloseIterator(iterator);\r\n }\r\n }, RpcStream.disconnectGracePeriodMs);\r\n }\r\n\r\n private _forceCloseIterator(iterator: AsyncIterator<T>): void {\r\n try {\r\n const ret = iterator.return?.(undefined);\r\n if (ret) void ret.catch(() => { /* ignore */ });\r\n } catch { /* ignore */ }\r\n }\r\n}\r\n\r\n// -- Internal helpers (file-local) --\r\n\r\n/** Wrapper matching .NET's `Result<T>` — value, end-of-stream, or error. */\r\ntype _StreamItem<T> =\r\n | { kind: 'value'; value: T }\r\n | { kind: 'end' }\r\n | { kind: 'error'; error: Error };\r\n\r\n/** Singleton \"source exhausted\" marker (matches .NET's `NoMoreItemsTag`). */\r\nconst _endItem: _StreamItem<never> = { kind: 'end' };\r\n\r\n/** Shared mutable state between writeFrom and its per-mode pump. */\r\ninterface _PumpState {\r\n iteratorDone: boolean;\r\n}\r\n\r\nfunction _clamp(value: number, min: number, max: number): number {\r\n return value < min ? min : value > max ? max : value;\r\n}\r\n\r\nfunction _compactBufferedUnsentSuffix<T>(\r\n buffer: RingBuffer<_StreamItem<T>>,\r\n firstUnsentIndex: number,\r\n canSkipTo: (item: T) => boolean,\r\n): { newIndex: number; skipped: number } {\r\n // Keep items that have already been assigned RPC stream indexes, then\r\n // collapse the unsent suffix to the latest buffered restart point. The\r\n // surviving suffix is intentionally sent under fresh RPC stream indexes;\r\n // item-level timestamps/frame indexes are the caller's responsibility.\r\n let skipToIndex = -1;\r\n for (let i = firstUnsentIndex; i < buffer.count; i++) {\r\n const item = buffer.get(i);\r\n if (item.kind === 'value' && canSkipTo(item.value))\r\n skipToIndex = i;\r\n }\r\n if (skipToIndex <= firstUnsentIndex)\r\n return { newIndex: firstUnsentIndex, skipped: 0 };\r\n\r\n const cutFrom = firstUnsentIndex;\r\n const cutTo = skipToIndex;\r\n\r\n const items = buffer.toArray();\r\n buffer.clear();\r\n for (let i = 0; i < cutFrom; i++)\r\n buffer.pushTail(items[i]);\r\n for (let i = cutTo; i < items.length; i++)\r\n buffer.pushTail(items[i]);\r\n return { newIndex: firstUnsentIndex, skipped: skipToIndex - firstUnsentIndex };\r\n}\r\n","import type { IRpcObject } from './rpc-object.js';\r\n\r\nexport class RpcRemoteObjectTracker {\r\n private _objects = new Map<number, IRpcObject>();\r\n\r\n register(obj: IRpcObject): void {\r\n const localId = obj.id.localId;\r\n const existing = this._objects.get(localId);\r\n if (existing === obj)\r\n return;\r\n\r\n // A different live object shares this localId — happens after switching\r\n // to another peer instance (e.g. via LB). Disconnect the stale one first.\r\n if (existing !== undefined)\r\n existing.disconnect();\r\n\r\n this._objects.set(localId, obj);\r\n }\r\n\r\n get(localId: number): IRpcObject | undefined {\r\n return this._objects.get(localId);\r\n }\r\n\r\n keys(): IterableIterator<number> {\r\n return this._objects.keys();\r\n }\r\n\r\n unregister(obj: IRpcObject): void {\r\n const localId = obj.id.localId;\r\n if (this._objects.get(localId) === obj)\r\n this._objects.delete(localId);\r\n }\r\n\r\n disconnectAll(): void {\r\n for (const obj of this._objects.values()) {\r\n obj.disconnect();\r\n }\r\n this._objects.clear();\r\n }\r\n\r\n reconnectAll(): void {\r\n for (const obj of this._objects.values()) {\r\n if (obj.allowReconnect) obj.reconnect();\r\n else obj.disconnect();\r\n }\r\n }\r\n}\r\n","import type { IRpcObject } from './rpc-object.js';\r\n\r\nexport class RpcSharedObjectTracker {\r\n private _nextId = 1;\r\n private _objects = new Map<number, IRpcObject>();\r\n\r\n nextId(): number {\r\n return this._nextId++;\r\n }\r\n\r\n register(obj: IRpcObject): void {\r\n const localId = obj.id.localId;\r\n if (this._objects.has(localId))\r\n throw new Error(`RPC shared object with localId ${localId} is already registered.`);\r\n\r\n this._objects.set(localId, obj);\r\n }\r\n\r\n get(localId: number): IRpcObject | undefined {\r\n return this._objects.get(localId);\r\n }\r\n\r\n keys(): IterableIterator<number> {\r\n return this._objects.keys();\r\n }\r\n\r\n unregister(obj: IRpcObject): void {\r\n const localId = obj.id.localId;\r\n if (this._objects.get(localId) === obj)\r\n this._objects.delete(localId);\r\n }\r\n\r\n disconnectAll(): void {\r\n for (const obj of this._objects.values()) {\r\n obj.disconnect();\r\n }\r\n this._objects.clear();\r\n }\r\n}\r\n","// .NET counterparts:\r\n// RpcPeer (529 lines) — abstract WorkerBase that runs a reconnection loop in\r\n// OnRun(). Manages AsyncState<RpcPeerConnectionState> (a linked list of\r\n// immutable connection snapshots), handshake exchange, peerChanged detection,\r\n// message processing loop, maintenance tasks, Reset(), and SetConnectionState()\r\n// with thread-safe locking.\r\n// RpcClientPeer (56 lines) — calls GetConnection → WebSocket transport.\r\n// RpcServerPeer (54 lines) — receives connection via RpcServerConnectionHandler.\r\n// RpcPeerConnectionKind (48 lines) — None/Remote/Loopback/Local enum.\r\n// RpcPeerConnectionState (42 lines) — immutable record: Connection, Transport,\r\n// Handshake, OwnHandshake, ReaderTokenSource, Error, TryIndex.\r\n// RpcPeerChangeKind (11 lines) — Unchanged/ChangedToVeryFirst/Changed enum.\r\n// RpcHandshake (28 lines) — PeerId, ApiVersionSet, HubId, ProtocolVersion,\r\n// Index, Secret. Has GetPeerChangeKind(lastHandshake) comparison method.\r\n// `Secret` (index 5, optional) is the server's per-peer reconnect secret;\r\n// it flows server→client only, and the client echoes possession of it via\r\n// the `c` (counter) and `p` (HMAC proof) connect-URL parameters — see\r\n// `reconnect-proof.ts`. The TS client never issues a secret, so the\r\n// handshake it sends stays a 5-element array / 5-property object.\r\n// RpcClientPeerReconnectDelayer (35 lines) — exponential backoff profiles:\r\n// test (50ms), server (250ms), client (1s) with 1.5x growth, max 10s.\r\n//\r\n// Ported from .NET:\r\n// - Handshake exchange — run() sends $sys.Handshake on connect, waits for the\r\n// server's response. RpcServerPeer._onHandshakeReceived() auto-replies.\r\n// Like .NET's OnRun() handshake sequence.\r\n// - Peer change detection — run() compares RemotePeerId from successive\r\n// handshakes to detect server restarts, triggering peerChanged + inbound.clear().\r\n// Like .NET's RpcHandshake.GetPeerChangeKind().\r\n// - Outbound call cancellation via AbortSignal — call(signal) registers an\r\n// abort listener that rejects the call, removes it from the tracker, and sends\r\n// $sys.Cancel to the server. Like .NET's CancellationHandler + NotifyCancelled().\r\n//\r\n// Omitted from .NET:\r\n// - AsyncState<RpcPeerConnectionState> — immutable linked-list of connection\r\n// states that consumers can await via WhenConnected/WhenDisconnected. TS uses\r\n// simpler EventHandlerSet events (connected/disconnected). AsyncState enables\r\n// callers to block until a specific state transition, which is essential in\r\n// .NET's multi-threaded world but unnecessary in TS's single-threaded model.\r\n// - RpcPeerConnectionState record — snapshots Connection + Handshake + OwnHandshake\r\n// + ReaderTokenSource + Error + TryIndex. TS tracks these as mutable fields\r\n// on the peer directly.\r\n// - Maintenance tasks (SharedObjects.Maintain, RemoteObjects.Maintain,\r\n// OutboundCalls.Maintain) — background loops that run while connected,\r\n// checking timeouts and managing object lifetimes. TS has simpler keep-alive\r\n// timer; no shared/remote object tracking, no call timeout monitoring.\r\n// - Reconnect protocol (OutboundCalls.Reconnect) — stage-based call resumption\r\n// after reconnection. TS uses transparent reconnect: outbound calls survive\r\n// disconnect, are re-sent on reconnect (stage-3 compute calls self-invalidate\r\n// on peer change instead of being re-sent).\r\n// - peerChangedToken / CancellationTokenSource — .NET threads a cancellation\r\n// token through all inbound calls that gets cancelled when the remote peer\r\n// changes identity. TS clears the inbound tracker on peer change;\r\n// outbound calls survive disconnect and are re-sent on reconnect.\r\n// - Reset() — aborts RemoteObjects, SharedObjects, OutboundCalls, and clears\r\n// InboundCalls. TS clears inbound tracker on peer change; outbound calls\r\n// are never rejected on disconnect (only on close/stop).\r\n// - ServerMethodResolver / GetServerMethodResolver(handshake) — resolves method\r\n// defs using the remote peer's API version set + legacy names. TS has no\r\n// versioning.\r\n// - ProcessMessage() — creates RpcInboundContext per message, dispatches via\r\n// MethodDef.InboundCallInvoker. TS dispatches via _handleMessage →\r\n// handleSystemCall or RpcServiceHost.dispatch.\r\n// - RpcRef / RpcRoute / routing — .NET peers are bound to an RpcRoute (one route\r\n// generation of a stable RpcRef, which encodes client/server, versions,\r\n// serialization format). TS peers are keyed by string ref (URL for clients,\r\n// \"server://{uuid}\" for servers).\r\n// - StopMode / ComputeAutoStopMode — controls behavior of inbound calls when\r\n// peer stops (cancel vs keep-incomplete). TS has no stop mode.\r\n// - ConnectionKind detector (Remote/Loopback/Local/None) — .NET detects if peer\r\n// is in-process. TS is always remote.\r\n// - Versions / VersionSet / API version negotiation — .NET exchanges version\r\n// sets during handshake for backward compatibility. TS has no versioning.\r\n// - Diagnostics (CallLogger, CallLogLevel, DebugLog) — per-peer logging/tracing.\r\n// Ported since: per-call timeout monitoring — see `_maintainOutboundCalls`, which\r\n// applies RpcCallTimeouts the way .NET splits them between `HandleDisconnect`\r\n// (ConnectTimeout, while the peer is away) and `Maintain` (RunTimeout, while connected).\r\n// - Inbound call cancellation propagation — when $sys.Cancel is received, .NET\r\n// cancels the inbound call's CancellationTokenSource, aborting the running\r\n// service method. TS removes the call from the inbound tracker but does not\r\n// yet propagate cancellation to the service handler (would require AbortSignal\r\n// threading through RpcServiceHost.dispatch).\r\n\r\nimport { EventHandlerSet, PromiseSource, TimeoutError, cancellationError, withTimeout } from '@actuallab/core';\r\nimport type { RpcCallTimeouts } from './rpc-call-timeouts.js';\r\nimport { getLogs } from './logging.js';\r\nimport {\r\n RpcWebSocketConnection,\r\n type RpcConnection,\r\n type RpcReceivedMessage,\r\n type WebSocketLike,\r\n} from './rpc-connection.js';\r\nimport {\r\n RpcOutboundCall,\r\n RpcOutboundCallTracker,\r\n RpcInboundCall,\r\n RpcInboundCallTracker,\r\n} from './rpc-call-tracker.js';\r\nimport { RpcSystemCalls, type RpcMessage } from './rpc-message.js';\r\nimport { deserializeMessage } from './rpc-serialization.js';\r\nimport type { RpcHub } from './rpc-hub.js';\r\nimport { RpcRemoteObjectTracker } from './rpc-remote-object-tracker.js';\r\nimport { RpcSharedObjectTracker } from './rpc-shared-object-tracker.js';\r\nimport { RpcStream } from './rpc-stream.js';\r\nimport { RpcSerializationFormat, RpcSerializationFormatResolver } from './rpc-serialization-format.js';\r\nimport { IncreasingSeqCompressor } from './increasing-seq-compressor.js';\r\nimport { base64Decode, base64Encode, base64UrlEncode } from './base64.js';\r\nimport {\r\n computeReconnectProof,\r\n isReconnectProofSupported,\r\n type RpcReconnectProofParameters,\r\n} from './reconnect-proof.js';\r\n\r\n// Yields to the event loop. Used by the reconnect-resend loop to chunk\r\n// large bursts so they don't spike main-thread time post-handshake.\r\n// setTimeout(0) is a macrotask (queueMicrotask would not yield to other\r\n// tasks); 0 ms still gets bumped to 4 ms by the browser for nested\r\n// setTimeouts but the slack is fine — we want a yield, not zero latency.\r\nfunction yieldToEventLoop(): Promise<void> {\r\n return new Promise<void>(resolve => setTimeout(resolve, 0));\r\n}\r\n\r\n/** WebSocket close code sent by the server when the client's serialization format is unsupported. */\r\n\r\nexport const RPC_CLOSE_CODE_UNSUPPORTED_FORMAT = 4001;\r\n\r\n// Connect / handshake / keepalive timing limits now live on `RpcLimits`,\r\n// resolved via `hub.limits` at peer construction. See `rpc-limits.ts` for\r\n// the override paths (process-wide / per-hub / per-peer).\r\n\r\nconst { debugLog, infoLog, warnLog, errorLog } = getLogs('RpcPeer');\r\n\r\n/** Builds the WebSocket connection URL for an RpcClientPeer. */\r\nexport type RpcConnectionUrlResolver = (peer: RpcClientPeer) => string | Promise<string>;\r\n\r\n/** Default connection URL provider — sets `clientId` and `f` query parameters,\r\n * replacing any existing values to keep the wire format consistent with the\r\n * peer's actual `serializationFormat`. Adds the reconnect proof (`c` + `p`)\r\n * once the peer holds a server-issued secret. */\r\nexport const defaultConnectionUrlResolver: RpcConnectionUrlResolver = async peer => {\r\n const formatKey = peer.serializationFormat.key;\r\n // Neither value needs percent-encoding: `c` is decimal digits and `p` is\r\n // base64url, and `URLSearchParams` encodes neither `-` nor `_`.\r\n const proof = await peer.computeReconnectProof();\r\n try {\r\n const url = new URL(peer.ref);\r\n url.searchParams.set('clientId', peer.clientId);\r\n url.searchParams.set('f', formatKey);\r\n if (proof !== undefined) {\r\n url.searchParams.set('c', proof.counter);\r\n url.searchParams.set('p', proof.proof);\r\n }\r\n return url.toString();\r\n } catch {\r\n // Fallback for non-URL refs (tests/harnesses that pass \"ws://test\" etc.)\r\n const sep = peer.ref.includes('?') ? '&' : '?';\r\n let url = peer.ref + sep + `clientId=${peer.clientId}&f=${formatKey}`;\r\n if (proof !== undefined)\r\n url += `&c=${proof.counter}&p=${proof.proof}`;\r\n return url;\r\n }\r\n};\r\n\r\n/** Query parameters replaced with `<redacted>` before a URL reaches the log.\r\n * `clientId` is here because it selects the server-side peer, and `p` because\r\n * it proves possession of that peer's secret. */\r\nconst RedactedParameterNames = ['session', 'clientId', 'p'];\r\n\r\n// eslint-disable-next-line prefer-const -- intentionally `let` so consumers can swap the implementation\r\nexport let sanitizeUrl = (url: string): string => {\r\n try {\r\n const parsed = new URL(url);\r\n let isRedacted = false;\r\n for (const name of RedactedParameterNames) {\r\n if (!parsed.searchParams.has(name))\r\n continue;\r\n\r\n parsed.searchParams.set(name, '<redacted>');\r\n isRedacted = true;\r\n }\r\n return isRedacted ? parsed.toString() : url;\r\n } catch {\r\n return url.replace(/([?&](?:session|clientId|p)=)[^&]*/gi, '$1<redacted>');\r\n }\r\n};\r\n\r\n/** Extract the serialization format key from an `f=` query parameter, if any. */\r\nfunction parseFormatFromUrl(url: string): string | undefined {\r\n try {\r\n const f = new URL(url).searchParams.get('f');\r\n return f ?? undefined;\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/** Options for RpcPeer.call() — allows custom call types and cancellation. */\r\nexport interface RpcCallOptions {\r\n /** Wire CallType field (0 = regular). */\r\n callTypeId?: number;\r\n /** Factory for creating custom outbound call instances (e.g. compute calls). */\r\n outboundCallFactory?: (id: number, method: string) => RpcOutboundCall;\r\n /** AbortSignal for caller-initiated cancellation. */\r\n signal?: AbortSignal;\r\n /** Bitfield of RpcRemoteExecutionMode flags (default: 7 = Default). */\r\n remoteExecutionMode?: number;\r\n /** Per-call connect/run timeouts (R12). Default: unbounded. */\r\n timeouts?: RpcCallTimeouts;\r\n}\r\n\r\n/** Data extracted from an inbound $sys.Handshake message. */\r\nexport interface RemoteHandshake {\r\n RemotePeerId?: string;\r\n RemoteHubId?: string;\r\n ProtocolVersion?: number;\r\n Index?: number;\r\n /** Server-issued reconnect secret. Server→client only — a client never sends one. */\r\n Secret?: string;\r\n}\r\n\r\n/** Base class for RPC peers — handles bidirectional message dispatch. */\r\nexport abstract class RpcPeer {\r\n readonly hub: RpcHub;\r\n /** Routing/addressing key — used as key in hub.peers (URL for clients, \"server://{uuid}\" for servers). */\r\n readonly ref: string;\r\n /** Auto-generated GUID — used in handshakes only. */\r\n readonly id: string = crypto.randomUUID();\r\n readonly outboundCalls = new RpcOutboundCallTracker();\r\n readonly inboundCalls = new RpcInboundCallTracker();\r\n readonly remoteObjects = new RpcRemoteObjectTracker();\r\n readonly sharedObjects = new RpcSharedObjectTracker();\r\n\r\n /** Fired on every state transition with the new value. Initial state is\r\n * `Disconnected`; `Connecting` is entered when the peer starts an\r\n * attempt (the reconnect delay is part of this phase); `Connected` is\r\n * entered only after the handshake has succeeded. */\r\n readonly connectionStateChanged = new EventHandlerSet<RpcConnectionState>();\r\n\r\n /** Index of the handshake THIS peer most recently sent. Used to reject a\r\n * stale `$sys.Reconnect` that references an older connection generation\r\n * (C# `RpcSystemCalls.Reconnect`'s `ownHandshake.Index` check). */\r\n protected _ownHandshakeIndex = 0;\r\n /** Whether the current connection generation has already used its single\r\n * `$sys.Reconnect`. Mirrors C# `RpcPeerConnectionState.TryClaimReconnect`. */\r\n protected _isReconnectClaimed = false;\r\n protected _connection: RpcConnection | undefined;\r\n /** Mirror of `_connectionState === Connected`. True only after the\r\n * handshake round-trip completes; outbound `call()` checks this to\r\n * decide between sending now and deferring until the next `Connected`\r\n * transition. Kept as its own field so it can stay false through the\r\n * Handshaking phase without exposing the run loop's `_connection`\r\n * bookkeeping. */\r\n protected _isConnected = false;\r\n protected _connectionState: RpcConnectionState = RpcConnectionState.Disconnected;\r\n private _keepAliveTimer: ReturnType<typeof setInterval> | undefined;\r\n private _keepAliveWatchdog: ReturnType<typeof setTimeout> | undefined;\r\n private _maintainTimer: ReturnType<typeof setInterval> | undefined;\r\n /** Wall-clock time of the last inbound `$sys.KeepAlive`, or 0 if none yet\r\n * for the current connection. Useful for diagnostics. */\r\n private _lastKeepAliveAt = 0;\r\n /** When the current outage began; 0 until the peer has been connected at least once. */\r\n private _disconnectedAt = 0;\r\n /** Outbound keep-alive send period. Initialized from `hub.limits` at\r\n * construction; set directly to override on a single peer. */\r\n keepAlivePeriodMs: number;\r\n /** Inbound keep-alive silence tolerance. Initialized from `hub.limits`\r\n * at construction; set directly to override on a single peer. */\r\n keepAliveTimeoutMs: number;\r\n\r\n protected constructor(hub: RpcHub, ref: string) {\r\n this.hub = hub;\r\n this.ref = ref;\r\n this.keepAlivePeriodMs = hub.limits.keepAlivePeriodMs;\r\n this.keepAliveTimeoutMs = hub.limits.keepAliveTimeoutMs;\r\n this.inboundCalls.completedCallsLimit = hub.limits.completedInboundCallsLimit;\r\n const checkPeriod = hub.limits.callTimeoutCheckPeriodMs;\r\n if (checkPeriod > 0 && Number.isFinite(checkPeriod)) {\r\n this._maintainTimer = setInterval(() => this._maintainOutboundCalls(), checkPeriod);\r\n // Don't keep a Node process alive just for the maintenance timer.\r\n (this._maintainTimer as { unref?: () => void }).unref?.();\r\n }\r\n }\r\n\r\n /** Fail outbound calls whose per-call {@link RpcCallTimeouts} elapsed —\r\n * the connect timeout while a call still awaits the wire, the run timeout\r\n * once it has been sent, and the connect timeout again for any pending call while the\r\n * peer is away. Mirrors .NET `RpcOutboundCallTracker.HandleDisconnect` (ConnectTimeout,\r\n * per outage) plus `Maintain` (RunTimeout, per connection). Calls without timeouts\r\n * (queries) never time out here. */\r\n private _maintainOutboundCalls(): void {\r\n const now = Date.now();\r\n // Run timeouts apply only while the link looks alive: .NET's `Maintain` loop is\r\n // per-connection and skips timeout checks when keep-alive is stale — otherwise calls\r\n // sent before a disconnect/stall would be failed instead of being resent on reconnect.\r\n const canRunTimeout = this._isConnected\r\n && (this._lastKeepAliveAt === 0 || now - this._lastKeepAliveAt <= this.keepAliveTimeoutMs);\r\n for (const call of [...this.outboundCalls.values()]) {\r\n const timeouts = call.timeouts;\r\n if (timeouts === undefined || call.result.isCompleted)\r\n continue;\r\n\r\n let timedOut: string | undefined;\r\n if (!this._isConnected) {\r\n // While the peer is away, ConnectTimeout caps every pending call, sent or not:\r\n // a sent call is waiting for the same reconnect an unsent one is. It runs from\r\n // the later of the disconnect and the call, so a call issued mid-outage still\r\n // gets its full timeout. Mirrors .NET `RpcOutboundCallTracker.HandleDisconnect`.\r\n const since = Math.max(this._disconnectedAt, call.startedAt);\r\n if (now - since >= timeouts.connectTimeoutMs)\r\n timedOut = `connect timeout (${timeouts.connectTimeoutMs}ms)`;\r\n } else if (call.sentAt === 0) {\r\n if (now - call.startedAt >= timeouts.connectTimeoutMs)\r\n timedOut = `connect timeout (${timeouts.connectTimeoutMs}ms)`;\r\n } else if (canRunTimeout && now - call.sentAt >= timeouts.runTimeoutMs)\r\n timedOut = `run timeout (${timeouts.runTimeoutMs}ms)`;\r\n if (timedOut === undefined)\r\n continue;\r\n\r\n this.outboundCalls.remove(call.callId);\r\n call.result.reject(new TimeoutError(`'${this.ref}': call ${call.method}#${call.callId} timed out — ${timedOut}`));\r\n call.onDisconnect();\r\n }\r\n }\r\n\r\n get connection(): RpcConnection | undefined {\r\n return this._connection;\r\n }\r\n\r\n // The connection you may send a non-handshake message on, or `undefined`.\r\n // `_connection` is set before the socket even opens and stays set through\r\n // the handshake, and `RpcConnection` buffers pre-OPEN sends and flushes them\r\n // from `onopen` — so writing through it during Connecting or Handshaking puts\r\n // the frame on the wire ahead of $sys.Handshake and corrupts the remote's\r\n // handshake. `_isConnected` is the only flag false across both phases.\r\n // Port of C#'s `RpcPeer.Transport` (RpcPeer.cs:29-52), which hides the\r\n // transport for exactly this reason. $sys.Handshake itself must use\r\n // `connection` — it is the frame this gate exists to let through first.\r\n get wireConnection(): RpcConnection | undefined {\r\n return this._isConnected ? this._connection : undefined;\r\n }\r\n\r\n get connectionState(): RpcConnectionState {\r\n return this._connectionState;\r\n }\r\n\r\n get isConnected(): boolean {\r\n return this._isConnected;\r\n }\r\n\r\n get ownHandshakeIndex(): number {\r\n return this._ownHandshakeIndex;\r\n }\r\n\r\n // A conforming peer sends `$sys.Reconnect` at most once per connection, so anything\r\n // beyond that is a replay - and the reconciliation it performs is proportional to the\r\n // call ids it carries, with nothing else capping how often it can arrive.\r\n public tryClaimReconnect(): boolean {\r\n if (this._isReconnectClaimed)\r\n return false;\r\n\r\n this._isReconnectClaimed = true;\r\n return true;\r\n }\r\n\r\n protected nextOwnHandshakeIndex(): number {\r\n this._isReconnectClaimed = false;\r\n return ++this._ownHandshakeIndex;\r\n }\r\n\r\n protected _setConnectionState(value: RpcConnectionState): void {\r\n if (this._connectionState === value) return;\r\n const previousState = this._connectionState;\r\n this._connectionState = value;\r\n // `_isConnected` flips on entering `Connected`, and off on leaving it for\r\n // ANY other state — a server peer re-`accept()`ed mid-session goes\r\n // Connected → Connecting → Handshaking, and leaving it set there would\r\n // open `wireConnection` on a connection whose handshake hasn't happened.\r\n // Failed-handshake transitions (Connecting → Disconnected) leave it alone —\r\n // it was already false in production, and tests that set it directly via\r\n // `connectWith` keep it true while the run loop spins through retry\r\n // iterations without ever reaching `Connected`.\r\n if (value === RpcConnectionState.Connected) {\r\n this._isConnected = true;\r\n this._disconnectedAt = 0;\r\n this._armKeepAliveWatchdog();\r\n } else {\r\n if (previousState === RpcConnectionState.Connected) {\r\n this._isConnected = false;\r\n this._disconnectedAt = Date.now();\r\n }\r\n // Disarmed on leaving `Connected` for any state, not just\r\n // `Disconnected`: a re-`accept()`ed server peer would otherwise carry\r\n // the previous generation's silence watchdog into the new handshake\r\n // window and force-close the new connection. `Connected` re-arms it.\r\n this._disarmKeepAliveWatchdog();\r\n }\r\n this.connectionStateChanged.trigger(value);\r\n }\r\n\r\n /** Resolves on the next transition into `Connected` — or immediately if\r\n * already in that state. */\r\n whenConnected(): Promise<void> {\r\n if (this._connectionState === RpcConnectionState.Connected)\r\n return Promise.resolve();\r\n return new Promise<void>(resolve => {\r\n const handler = (state: RpcConnectionState): void => {\r\n if (state !== RpcConnectionState.Connected) return;\r\n this.connectionStateChanged.remove(handler);\r\n resolve();\r\n };\r\n this.connectionStateChanged.add(handler);\r\n });\r\n }\r\n\r\n protected setupConnection(conn: RpcConnection): void {\r\n this._connection = conn;\r\n\r\n conn.messageReceived.add(raw => this._handleMessage(raw));\r\n conn.closed.add(ev => {\r\n // Bail if this conn has already been replaced (e.g. another\r\n // setupConnection() call ran in between). Without this guard,\r\n // a late-firing close event from the old socket would clobber\r\n // the live `_connection` field and force the state machine\r\n // back to Disconnected mid-session.\r\n if (this._connection !== conn) return;\r\n this._connection = undefined;\r\n // A live conn going away always drops us out of \"connected\",\r\n // regardless of where the state machine thought we were. The\r\n // `_setConnectionState(Disconnected)` below only flips\r\n // `_isConnected` off if we were coming from `Connected`, which\r\n // misses the case where the conn closes mid-handshake (state\r\n // still `Connecting`) or was set up via `connectWith` (state\r\n // still `Disconnected`).\r\n if (this._isConnected)\r\n this._disconnectedAt = Date.now(); // The outage starts here, not in _setConnectionState\r\n this._isConnected = false;\r\n // Mirrors RpcPeer.cs:524-526 — log disconnect with reason if present.\r\n if (ev.reason)\r\n infoLog?.log(`'${this.ref}': Disconnected: ${ev.reason} (code=${ev.code})`);\r\n else\r\n infoLog?.log(`'${this.ref}': Disconnected`);\r\n // Flipping state to Disconnected tears down the keep-alive watchdog.\r\n this._setConnectionState(RpcConnectionState.Disconnected);\r\n });\r\n\r\n void conn.whenConnected.then(() => {\r\n // The state transition to `Connected` happens after the handshake —\r\n // that's where keep-alive arming happens too.\r\n infoLog?.log(`'${this.ref}': WS open`);\r\n });\r\n }\r\n\r\n /**\r\n * The serialization format used by this peer for outgoing messages.\r\n * If not set explicitly, defaults to the resolver's default (JSON /\r\n * json5np). Subclasses can pre-assign in their constructor.\r\n */\r\n private _serializationFormat: RpcSerializationFormat | undefined;\r\n\r\n get serializationFormat(): RpcSerializationFormat {\r\n return this._serializationFormat ?? RpcSerializationFormatResolver.Default.defaultFormat;\r\n }\r\n\r\n set serializationFormat(value: RpcSerializationFormat) {\r\n this._serializationFormat = value;\r\n }\r\n\r\n call(\r\n method: string,\r\n args?: unknown[],\r\n options?: RpcCallOptions\r\n ): RpcOutboundCall {\r\n const callId = this.outboundCalls.nextId();\r\n const outboundCall = options?.outboundCallFactory\r\n ? options.outboundCallFactory(callId, method)\r\n : new RpcOutboundCall(callId, method, options?.remoteExecutionMode ?? 7);\r\n\r\n const envelope: RpcMessage = {\r\n Method: method,\r\n RelatedId: callId,\r\n CallType: options?.callTypeId ?? 0,\r\n };\r\n outboundCall.serializedWireData = this.serializationFormat.serializeMessage(\r\n envelope,\r\n args,\r\n this._connection?.encoder,\r\n this.hub.registry\r\n );\r\n outboundCall.timeouts = options?.timeouts;\r\n\r\n // Mirror .NET `RpcOutboundCall.SendAsync`: register the call up front,\r\n // then either send now (fast path) or wait for `Connected` and send.\r\n // No central \"pending\" queue — each call self-manages its wait.\r\n this.outboundCalls.register(outboundCall);\r\n if (this._isConnected) {\r\n outboundCall.sentAt = Date.now();\r\n this._sendWireData(outboundCall.serializedWireData);\r\n } else {\r\n // Defer: send when the peer transitions to `Connected`. If the\r\n // call's result is completed before that (cancel, peer close,\r\n // error), the registered handler skips the send.\r\n const stateHandler = (state: RpcConnectionState): void => {\r\n if (state !== RpcConnectionState.Connected) return;\r\n this.connectionStateChanged.remove(stateHandler);\r\n if (outboundCall.result.isCompleted) return;\r\n outboundCall.sentAt = Date.now();\r\n this._sendWireData(outboundCall.serializedWireData);\r\n };\r\n this.connectionStateChanged.add(stateHandler);\r\n // Detach the handler if the call is completed before we ever\r\n // reach `Connected` — prevents leaking the listener.\r\n outboundCall.result\r\n .then(() => this.connectionStateChanged.remove(stateHandler))\r\n .catch(() => this.connectionStateChanged.remove(stateHandler));\r\n }\r\n\r\n // Wire up caller-initiated cancellation → sends $sys.Cancel to remote peer\r\n const signal = options?.signal;\r\n if (signal !== undefined) {\r\n const onAbort = () => {\r\n if (this.outboundCalls.remove(callId) !== undefined) {\r\n outboundCall.result.reject(cancellationError('Call cancelled.'));\r\n outboundCall.onDisconnect();\r\n // A call aborted before the handshake was never put on the\r\n // wire, so there is nothing to cancel remotely either.\r\n const conn = this.wireConnection;\r\n if (conn !== undefined)\r\n this.hub.systemCallSender.cancel(conn, this.serializationFormat, callId);\r\n }\r\n };\r\n signal.addEventListener('abort', onAbort, { once: true });\r\n // Clean up listener when the call completes normally\r\n outboundCall.result\r\n .then(() => signal.removeEventListener('abort', onAbort))\r\n .catch(() => signal.removeEventListener('abort', onAbort));\r\n }\r\n\r\n return outboundCall;\r\n }\r\n\r\n callNoWait(method: string, args?: unknown[]): void {\r\n if (!this._isConnected) return; // silently drop\r\n // Invariant: `_isConnected` is only true while `_connection` is set\r\n // (they both flip together in `_setConnectionState` / `conn.closed`).\r\n const conn = this._connection!;\r\n const envelope: RpcMessage = { Method: method, RelatedId: 0 };\r\n const wireData = this.serializationFormat.serializeMessage(\r\n envelope,\r\n args,\r\n conn.encoder,\r\n this.hub.registry\r\n );\r\n this._sendWireData(wireData);\r\n }\r\n\r\n /** Close the current WS connection without disposing the peer. The\r\n * `conn.closed` handler flips state to `Disconnected`; for\r\n * `RpcClientPeer` the run loop then iterates and reopens. The peer\r\n * stays in the hub and all client proxies bound to it remain valid. */\r\n disconnect(): void {\r\n this._connection?.close();\r\n }\r\n\r\n close(): void {\r\n // Mirrors RpcPeer.cs:397 — \"Stopping\".\r\n infoLog?.log(`'${this.ref}': Stopping`);\r\n // `_setConnectionState(Disconnected)` below will tear down the keep-alive\r\n // watchdog + send timer. Calls deferred via `whenConnected` self-detach\r\n // when their result rejects (see `call()`).\r\n if (this._maintainTimer !== undefined) {\r\n clearInterval(this._maintainTimer);\r\n this._maintainTimer = undefined;\r\n }\r\n this.remoteObjects.disconnectAll();\r\n this.sharedObjects.disconnectAll();\r\n this.outboundCalls.rejectAll(cancellationError('Peer closed.'));\r\n this.outboundCalls.invalidateAll();\r\n this.inboundCalls.clear();\r\n this._connection?.close();\r\n this.hub.peers.delete(this.ref);\r\n this._setConnectionState(RpcConnectionState.Disconnected);\r\n // Mirrors RpcPeer.cs:439 — \"Stopped\".\r\n infoLog?.log(`'${this.ref}': Stopped`);\r\n }\r\n\r\n /** Send pre-serialized wire data through the current connection. Callers are\r\n * already gated on `_isConnected`; the check is repeated here so the next one\r\n * cannot reintroduce a pre-handshake write. $sys.Handshake does not use this. */\r\n protected _sendWireData(data: string | Uint8Array): void {\r\n if (!this._isConnected || this._connection === undefined) return;\r\n if (typeof data === 'string') {\r\n this._connection.send(data);\r\n } else {\r\n this._connection.sendBinary(data);\r\n }\r\n }\r\n\r\n /** Override in subclasses to handle the remote peer's handshake response. */\r\n protected _onHandshakeReceived(_handshake: RemoteHandshake): void {\r\n // Default: no-op. RpcClientPeer resolves a pending promise;\r\n // RpcServerPeer sends its own handshake back.\r\n }\r\n\r\n // The try covers dispatch too, not just deserialization: a handler that throws\r\n // here would otherwise escape into the transport's onmessage - dropping every\r\n // remaining message of the frame, or killing a Node process outright.\r\n private _handleMessage(received: RpcReceivedMessage): void {\r\n try {\r\n this._processMessage(received);\r\n }\r\n catch (e) {\r\n // Mirrors RpcPeer.cs:453 — \"Failed to process inbound message\".\r\n errorLog?.log(`'${this.ref}': Failed to process inbound message:`, received, e);\r\n }\r\n }\r\n\r\n private _processMessage(received: RpcReceivedMessage): void {\r\n let message: RpcMessage;\r\n let args: unknown[];\r\n if (received.kind === 'binary') {\r\n message = received.message;\r\n args = received.args;\r\n } else {\r\n const parsed = deserializeMessage(received.raw);\r\n message = parsed.message;\r\n args = parsed.args;\r\n }\r\n const method = message.Method ?? '';\r\n debugLog?.log(`'${this.ref}': <- ${method}#${message.RelatedId ?? 0}`);\r\n\r\n // Handshake — dispatch to subclass handler\r\n if (method === RpcSystemCalls.handshake) {\r\n const raw = args[0];\r\n if (raw !== undefined) {\r\n // .NET sends a positional array over MessagePack, but a\r\n // System.Text.Json object (Web defaults → camelCase keys) over the\r\n // text transport. Accept the array, PascalCase, and camelCase.\r\n const obj = (raw ?? {}) as Record<string, unknown>;\r\n const handshake: RemoteHandshake = Array.isArray(raw)\r\n ? {\r\n RemotePeerId: raw[0] as string | undefined,\r\n RemoteHubId: raw[2] as string | undefined,\r\n ProtocolVersion: raw[3] as number | undefined,\r\n Index: raw[4] as number | undefined,\r\n Secret: raw[5] as string | undefined,\r\n }\r\n : {\r\n RemotePeerId: (obj.RemotePeerId ?? obj.remotePeerId) as string | undefined,\r\n RemoteHubId: (obj.RemoteHubId ?? obj.remoteHubId) as string | undefined,\r\n ProtocolVersion: (obj.ProtocolVersion ?? obj.protocolVersion) as number | undefined,\r\n Index: (obj.Index ?? obj.index) as number | undefined,\r\n Secret: (obj.Secret ?? obj.secret) as string | undefined,\r\n };\r\n this._onHandshakeReceived(handshake);\r\n }\r\n return;\r\n }\r\n\r\n // Other system calls — delegate to hub's system call handler\r\n if (method.startsWith('$sys')) {\r\n this.hub.systemCallHandler.handle(message, args, this);\r\n return;\r\n }\r\n\r\n // Regular inbound call — dispatch to service host\r\n this._handleInbound(message, args);\r\n }\r\n\r\n private _handleInbound(message: RpcMessage, args: unknown[]): void {\r\n const method = message.Method ?? '';\r\n const relatedId = message.RelatedId ?? 0;\r\n const methodDef = this.hub.serviceHost.getMethodDef(method);\r\n const isNoWait = methodDef?.noWait === true;\r\n const serviceHost = this.hub.serviceHost;\r\n\r\n const dispatch = async (call: RpcInboundCall | undefined): Promise<void> => {\r\n try {\r\n const context =\r\n this._connection !== undefined\r\n ? {\r\n __rpcDispatch: true as const,\r\n callId: relatedId,\r\n callType: message.CallType ?? 0,\r\n connection: this._connection,\r\n signal: call?.signal,\r\n peer: this,\r\n }\r\n : undefined;\r\n const result = await serviceHost.dispatch(method, args, context);\r\n if (methodDef?.stream === true) {\r\n if (this._connection !== undefined) {\r\n // Stream method — wrap result in RpcStream if needed, then let\r\n // toRef() create the sender, register it, and start pumping.\r\n const stream = result instanceof RpcStream\r\n ? result as RpcStream<unknown>\r\n : new RpcStream<unknown>(result as AsyncIterable<unknown>);\r\n const ref = stream.toRef(this);\r\n const send = (): void => {\r\n // R17: a cancelled call sends no response, even though\r\n // it may still be registered for R9 dedup.\r\n if (call?.isCancelled === true) return;\r\n // Read at completion time, so it may land mid-reconnect —\r\n // `resendResult` re-sends what this drops.\r\n const conn = this.wireConnection;\r\n if (conn !== undefined)\r\n this.hub.systemCallSender.ok(\r\n conn, this.serializationFormat, relatedId, ref);\r\n };\r\n if (call !== undefined) {\r\n call.setResult(send);\r\n this.inboundCalls.markCompleted(call);\r\n }\r\n send();\r\n }\r\n } else if (call !== undefined) {\r\n const send = (): void => {\r\n if (call.isCancelled) return;\r\n const conn = this.wireConnection;\r\n if (conn !== undefined)\r\n this.hub.systemCallSender.ok(\r\n conn, this.serializationFormat, relatedId, result);\r\n };\r\n call.setResult(send);\r\n this.inboundCalls.markCompleted(call);\r\n send();\r\n }\r\n } catch (e) {\r\n if (call !== undefined) {\r\n const send = (): void => {\r\n if (call.isCancelled) return;\r\n const conn = this.wireConnection;\r\n if (conn !== undefined)\r\n this.hub.systemCallSender.error(\r\n conn, this.serializationFormat, relatedId, e);\r\n };\r\n call.setResult(send);\r\n this.inboundCalls.markCompleted(call);\r\n send();\r\n }\r\n debugLog?.log(`'${this.ref}': Inbound call failed: ${method}#${relatedId}`, e);\r\n }\r\n };\r\n\r\n // For noWait calls: don't track, don't respond.\r\n if (isNoWait) {\r\n void dispatch(undefined);\r\n return;\r\n }\r\n\r\n // Dedup resent frames: a known call id attaches to the existing dispatch\r\n // and re-sends its result once computed, instead of re-invoking the\r\n // handler. Mirrors .NET GetOrRegister + RpcInboundCall.TryReprocess.\r\n const call = new RpcInboundCall(relatedId, method, args);\r\n const existing = this.inboundCalls.getOrRegister(call);\r\n if (existing !== call) {\r\n existing.resendResult();\r\n return;\r\n }\r\n void dispatch(call);\r\n }\r\n\r\n /** Start sending `$sys.KeepAlive` pings and arm the silence watchdog.\r\n * Called when the peer transitions into `Connected` (after handshake).\r\n * Each inbound `$sys.KeepAlive` re-arms the watchdog; if it fires, the\r\n * WebSocket is force-closed so the normal disconnect path runs\r\n * (`conn.closed` → `connectionState = Disconnected` → UI).\r\n * Mirrors .NET `RpcObjectTrackers.Maintain`'s `KeepAliveTimeout` branch. */\r\n private _armKeepAliveWatchdog(): void {\r\n // Start the send timer once per connected session.\r\n this._keepAliveTimer ??= setInterval(() => {\r\n // A re-`accept()`ed server peer can still hold a live timer from the\r\n // previous connection while the new one is handshaking.\r\n const conn = this.wireConnection;\r\n if (conn !== undefined) {\r\n // Send remote object IDs so the server's SharedObjectTracker keeps them alive.\r\n // Must NOT send outbound call IDs — those are a different ID namespace and would\r\n // cause the server to send $sys.Disconnect for IDs it doesn't recognize, which\r\n // the client may misinterpret as a disconnect of its own shared objects (e.g.\r\n // RpcStreamSender) when the IDs collide numerically.\r\n this.hub.systemCallSender.keepAlive(conn, this.serializationFormat, [\r\n ...this.remoteObjects.keys(),\r\n ]);\r\n }\r\n }, this.keepAlivePeriodMs);\r\n // (Re)arm the silence watchdog.\r\n if (this._keepAliveWatchdog !== undefined)\r\n clearTimeout(this._keepAliveWatchdog);\r\n this._keepAliveWatchdog = setTimeout(() => {\r\n this._keepAliveWatchdog = undefined;\r\n if (this._connection === undefined) return;\r\n warnLog?.log(\r\n `'${this.ref}': no keep-alive for ${this.keepAliveTimeoutMs}ms — force-closing connection`);\r\n this._connection.close();\r\n }, this.keepAliveTimeoutMs);\r\n }\r\n\r\n /** Stop sending keep-alives and clear the silence watchdog. Called when\r\n * the peer transitions into `Disconnected`. */\r\n private _disarmKeepAliveWatchdog(): void {\r\n if (this._keepAliveTimer !== undefined) {\r\n clearInterval(this._keepAliveTimer);\r\n this._keepAliveTimer = undefined;\r\n }\r\n if (this._keepAliveWatchdog !== undefined) {\r\n clearTimeout(this._keepAliveWatchdog);\r\n this._keepAliveWatchdog = undefined;\r\n }\r\n }\r\n\r\n /** Called by `RpcSystemCallHandler` when a `$sys.KeepAlive` message\r\n * arrives — records the timestamp and re-arms the silence watchdog. */\r\n notifyKeepAliveReceived(): void {\r\n this._lastKeepAliveAt = Date.now();\r\n if (this._keepAliveWatchdog !== undefined)\r\n this._armKeepAliveWatchdog();\r\n }\r\n}\r\n\r\n/** Connection state for RpcPeer. Mirrors .NET `RpcPeerConnectionState`'s\r\n * observable phases: the transport is `Connecting` until the WebSocket\r\n * opens, `Handshaking` from WS-open until the server's handshake reply\r\n * arrives, and `Connected` once the handshake completes. Outbound calls\r\n * are gated on `Connected`. */\r\nexport const enum RpcConnectionState {\r\n Disconnected = 0,\r\n Connecting = 1,\r\n Handshaking = 2,\r\n Connected = 3,\r\n}\r\n\r\n/** Outcome of comparing an incoming handshake with the previous one. Mirrors\r\n * .NET `RpcPeerChangeKind` — `ChangedToVeryFirst` lets the first handshake skip\r\n * reconnect processing, since there's no prior connection to reconcile with. */\r\nexport const enum RpcPeerChangeKind {\r\n Unchanged = 0,\r\n ChangedToVeryFirst = 1,\r\n Changed = 2,\r\n}\r\n\r\nconst peerChangeKindNames = ['Unchanged', 'ChangedToVeryFirst', 'Changed'];\r\n\r\n/** Client-side RPC peer — initiates WebSocket connection. ref = URL. */\r\nexport class RpcClientPeer extends RpcPeer {\r\n private _disposed = false;\r\n /** Aborted when the peer is stopped/disposed — threaded into the reconnect\r\n * delay so a pending delay rejects promptly instead of parking. */\r\n private readonly _stopController = new AbortController();\r\n private _tryIndex = 0;\r\n /** Index from the MOST RECENTLY RECEIVED server handshake — sent back\r\n * to the server in `$sys.Reconnect:3` so the server can verify we're\r\n * talking about the current connection. Mirrors .NET's\r\n * `connectionState.Handshake.Index` path in\r\n * `RpcOutboundCallTracker.TryReconnect`. */\r\n private _remoteHandshakeIndex = 0;\r\n /** Mirrors .NET's `lastHandshake` local in `RpcPeer.OnRun`: first-ness is keyed\r\n * off \"was there a previous handshake\", not off `RemotePeerId` — a legacy server\r\n * sends none, so every one of its handshakes would look like the first. */\r\n private _lastHandshake: RemoteHandshake | undefined;\r\n private _pendingHandshake: PromiseSource<RemoteHandshake> | undefined;\r\n private _reconnectsAt = 0;\r\n /** Server-issued reconnect secret, from the most recent handshake that\r\n * carried one. In-memory only — never localStorage/sessionStorage/cookie.\r\n * A tab reload mints a new `clientId`, so a persisted secret would be\r\n * useless anyway. */\r\n private _reconnectSecret: string | undefined;\r\n /** Incremented once per *connect attempt*, in `computeReconnectProof()`. A\r\n * failed attempt still burns its value: the server may have advanced its\r\n * own counter at the gate before the connection died, and reusing the\r\n * value would then be rejected forever. */\r\n private _reconnectCounter = 0;\r\n\r\n /** Base64url-encoded peer ID — matches .NET's RpcClientPeer.ClientId (Guid.ToBase64Url). */\r\n readonly clientId: string;\r\n /** Builds the WebSocket URL for this peer. Replace to customize URL construction. */\r\n connectionUrlResolver: RpcConnectionUrlResolver =\r\n defaultConnectionUrlResolver;\r\n /** Optional WebSocket factory. When set, `run()` uses this instead of the\r\n * global `new WebSocket(url)` constructor. Used by tests (to inject mock\r\n * sockets) and by the Node load-test harness (to attach `Session` /\r\n * `Cookie` headers to the WS upgrade, which the browser `WebSocket`\r\n * constructor can't do). Must be set before `start()`. */\r\n webSocketFactory: ((url: string) => WebSocketLike) | undefined;\r\n /** Max time to wait for the WebSocket to reach OPEN before aborting the\r\n * connection attempt. Initialized from `hub.limits` at construction;\r\n * set directly to override on a single peer. */\r\n connectTimeoutMs: number;\r\n /** Max time to wait for the server's handshake response before aborting\r\n * the connection attempt. Initialized from `hub.limits` at construction;\r\n * set directly to override on a single peer. */\r\n handshakeTimeoutMs: number;\r\n /** A connection that lived less than this before dropping does NOT reset\r\n * `_tryIndex`, so a crash-looping server sees growing reconnect delays.\r\n * Initialized from `hub.limits` at construction; override per peer. */\r\n prematureDisconnectTimeoutMs: number;\r\n readonly peerChanged = new EventHandlerSet<void>();\r\n /** Fired when the server rejects the connection due to an unsupported serialization format (close code 4010). */\r\n readonly unsupportedFormat = new EventHandlerSet<{ reason: string }>();\r\n readonly reconnectsAtChanged = new EventHandlerSet<void>();\r\n\r\n get reconnectsAt(): number {\r\n return this._reconnectsAt;\r\n }\r\n\r\n private _setReconnectsAt(value: number): void {\r\n if (this._reconnectsAt === value) return;\r\n this._reconnectsAt = value;\r\n this.reconnectsAtChanged.trigger();\r\n }\r\n\r\n /** The promise returned by the reconnect loop. `undefined` until\r\n * `start()` is invoked (which happens automatically when\r\n * `mustStart = true` is passed to the ctor). */\r\n whenRunning: Promise<void> | undefined;\r\n\r\n /**\r\n * @param hub RpcHub instance this peer will be tracked by\r\n * @param url Full WebSocket URL — the serialization format is taken from\r\n * the `f=` query parameter (e.g. `wss://host/rpc/ws?f=msgpack6`);\r\n * falls back to the resolver's default when absent. Use\r\n * {@link RpcRefBuilder.forClient} to compose URLs with a format.\r\n * @param mustStart Kick off the reconnect loop automatically at the end of\r\n * construction (default `true`). Pass `false` if you need to\r\n * tweak `reconnectDelayer`, `handshakeTimeoutMs`, or similar\r\n * options before the loop starts — then call `peer.start()`\r\n * manually.\r\n */\r\n constructor(hub: RpcHub, url: string, mustStart = true) {\r\n super(hub, url);\r\n this.connectTimeoutMs = hub.limits.connectTimeoutMs;\r\n this.handshakeTimeoutMs = hub.limits.handshakeTimeoutMs;\r\n this.prematureDisconnectTimeoutMs = hub.limits.prematureDisconnectTimeoutMs;\r\n this.clientId = guidToBase64Url(this.id);\r\n const resolver = RpcSerializationFormatResolver.Default;\r\n this.serializationFormat = resolver.get(parseFormatFromUrl(url) ?? resolver.defaultFormatKey);\r\n if (mustStart)\r\n this.start();\r\n }\r\n\r\n /** One-shot connection for tests — no reconnection loop, no handshake.\r\n * @param conn Rpc connection\r\n * @param isPeerChanged Whether to treat this as a peer-identity change\r\n * (default `true` — no handshake was exchanged, so stage-3 compute calls\r\n * self-invalidate and remote/shared objects are disposed). Pass `false`\r\n * to simulate a same-peer reconnect in tests. */\r\n connectWith(conn: RpcConnection, isPeerChanged = true): void {\r\n const sentCalls = this.outboundCalls.getSentCalls();\r\n this.setupConnection(conn);\r\n // Test-only shortcut through the run loop's sequence: it exchanges a real\r\n // $sys.Handshake (a server peer on the other end stays in `Handshaking`\r\n // until one arrives, and everything it sends is gated on `Connected` —\r\n // see `wireConnection`), but synthesizes this side's `Connected` instead\r\n // of driving the state machine, because `_setConnectionState(Connected)`\r\n // would arm the keep-alive watchdog against a mock transport that never\r\n // answers keep-alives. Calls deferred via `whenConnected` are released by\r\n // triggering the listener set directly.\r\n const whenHandshake = isPeerChanged\r\n ? undefined\r\n : (this._pendingHandshake = new PromiseSource<RemoteHandshake>());\r\n // The handshake goes out before `Connected` releases deferred calls —\r\n // the run loop's order.\r\n this.hub.systemCallSender.handshake(\r\n conn, this.serializationFormat, this.id, this.hub.hubId,\r\n this.nextOwnHandshakeIndex());\r\n this._isConnected = true;\r\n this.connectionStateChanged.trigger(RpcConnectionState.Connected);\r\n if (whenHandshake === undefined) {\r\n // Peer change: the resend is blind, so it needs no remote index.\r\n void this._reconnect(sentCalls, true);\r\n return;\r\n }\r\n\r\n // Same-peer: `_reconcileReconnect` sends `_remoteHandshakeIndex`, which is\r\n // only known once the remote's handshake lands, so wait for it exactly as\r\n // the run loop does — racing the reply against the socket closing, no\r\n // timer. Reconciling synchronously would send a stale index and get\r\n // TooLateToReconnect, silently downgrading every same-peer test to blind\r\n // resend.\r\n const whenClosed = new PromiseSource<void>();\r\n conn.closed.add(() => whenClosed.resolve());\r\n void Promise.race([whenHandshake, whenClosed]).then(() => {\r\n this._pendingHandshake = undefined;\r\n return this._reconnect(sentCalls, false);\r\n });\r\n }\r\n\r\n /** Kick off the reconnect loop. Idempotent — subsequent calls are\r\n * no-ops; await `peer.whenRunning` if you need the loop's completion\r\n * promise. Customize `peer.webSocketFactory` first if you need a\r\n * non-default WebSocket constructor. */\r\n start(): void {\r\n void this.run();\r\n }\r\n\r\n /** Builds the `c` / `p` connect-URL parameters proving possession of the\r\n * server-issued reconnect secret, or `undefined` when there is no secret\r\n * yet or WebCrypto is unavailable. Consumes one counter value per proof\r\n * produced, so call it exactly once per connect attempt. */\r\n async computeReconnectProof(): Promise<RpcReconnectProofParameters | undefined> {\r\n const secret = this._reconnectSecret;\r\n if (secret === undefined)\r\n return undefined;\r\n\r\n // Degrading to the legacy no-proof URL keeps a client that can never prove - an insecure\r\n // context has no crypto.subtle - out of a permanent reconnect failure. It is not a way\r\n // back for a client that has already proven: the server refuses that downgrade, since it\r\n // is exactly how an attacker would strip the proof. crypto.subtle availability is a\r\n // property of the environment, not of the attempt, so a client is on one side or the other\r\n // for its whole life.\r\n if (!isReconnectProofSupported()) {\r\n warnReconnectProofUnavailable(this.ref, 'crypto.subtle is unavailable (insecure context?)');\r\n return undefined;\r\n }\r\n\r\n const counter = (this._reconnectCounter + 1).toString();\r\n try {\r\n const proof = await computeReconnectProof(secret, this.clientId, counter);\r\n this._reconnectCounter++; // Only a produced proof consumes counter space\r\n return { counter, proof };\r\n } catch (e) {\r\n // Anomalous: crypto.subtle exists but failed. The connect will be refused once this\r\n // peer has proven before, and the reconnect loop retries - which recovers if the\r\n // failure was transient.\r\n warnReconnectProofUnavailable(this.ref, `HMAC-SHA256 failed: ${String(e)}`);\r\n return undefined;\r\n }\r\n }\r\n\r\n /** The reconnect loop itself. Exposed as `protected` so subclasses (and\r\n * `start()`) can drive it; public callers should use {@link start}. */\r\n protected run(): Promise<void> {\r\n return this.whenRunning ??= this._runImpl();\r\n }\r\n\r\n private async _runImpl(): Promise<void> {\r\n if (this._disposed) return;\r\n // Mirrors RpcPeer.cs:230 — \"Started\".\r\n infoLog?.log(`'${this.ref}': Started (Client)`);\r\n try {\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- _disposed can change during await\r\n while (!this._disposed) {\r\n // Enter Connecting for this iteration — the reconnect delay\r\n // below (if any) is part of this state per the contract\r\n // \"connecting when the peer starts to reconnect\".\r\n this._setConnectionState(RpcConnectionState.Connecting);\r\n\r\n // Apply retry delay (no-op on first iteration where tryIndex === 0).\r\n // The ApiReconnectDelayer may park indefinitely here while\r\n // `Api.canConnect === false`.\r\n const retryDelay = this.hub.reconnectDelayer.getDelay(this._tryIndex, this._stopController.signal);\r\n if (retryDelay.isLimitExceeded) {\r\n warnLog?.log(`'${this.ref}': Reconnect limit exceeded - shutting down`);\r\n this._disposed = true;\r\n break;\r\n }\r\n this._setReconnectsAt(retryDelay.endsAt);\r\n try {\r\n await retryDelay.promise;\r\n } catch {\r\n // Stop signal (or an externally cancelled delay) — the\r\n // `_disposed` check below exits the loop cleanly.\r\n } finally {\r\n this._setReconnectsAt(0);\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- _disposed can change during await\r\n if (this._disposed) break;\r\n\r\n let lastCloseCode = 0;\r\n let lastCloseReason = '';\r\n // 0 until this iteration reaches `Connected`; used by the R13\r\n // premature-disconnect check below.\r\n let connectedAt = 0;\r\n try {\r\n const connUrl = await this.connectionUrlResolver(this);\r\n // Mirrors RpcClientPeer.cs:45 — \"Connecting...\".\r\n infoLog?.log(`'${this.ref}': Connecting to ${sanitizeUrl(connUrl)}`);\r\n const ws =\r\n this.webSocketFactory?.(connUrl) ??\r\n (new WebSocket(connUrl) as unknown as WebSocketLike);\r\n const conn = new RpcWebSocketConnection(\r\n ws,\r\n this.serializationFormat.isBinary,\r\n this.serializationFormat,\r\n this.hub.registry\r\n );\r\n\r\n // Eager \"connection closed\" PromiseSource — captured right\r\n // after connection creation so every subsequent await can\r\n // race against it without missing an already-fired close.\r\n // EventHandlerSet fires synchronously and does not replay\r\n // past triggers, so a late `conn.closed.add(...)` would\r\n // deadlock if the socket closed just before registration.\r\n const whenClosed = new PromiseSource<{ code: number; reason: string }>();\r\n const closedController = new AbortController();\r\n conn.closed.add(ev => {\r\n lastCloseCode = ev.code;\r\n lastCloseReason = ev.reason;\r\n whenClosed.resolve(ev);\r\n closedController.abort();\r\n });\r\n\r\n // Derived rejecting promise — used to unblock awaits that\r\n // expect a successful signal (whenConnected, handshake).\r\n const closedRejection = whenClosed.then<never>(() => {\r\n throw new Error('Connection failed');\r\n });\r\n closedRejection.catch(() => { /* noop — prevent unhandled rejection when conn closes normally */ });\r\n\r\n // Create a fresh handshake promise before setupConnection (which registers\r\n // the message handler). The handler can only fire after the WS opens,\r\n // and we send our handshake below, so timing is safe.\r\n this._pendingHandshake = new PromiseSource<RemoteHandshake>();\r\n this.setupConnection(conn);\r\n // `_connection` stays set throughout the handshake window so\r\n // `close()` can tear down the WS. Outbound calls are gated by\r\n // `_isConnected`, which only flips on when we enter `Connected`\r\n // below — before that, `call()` defers the send via the\r\n // per-call `whenConnected` listener.\r\n\r\n // Race connection open against close — if WS fails to connect,\r\n // whenConnected stays pending forever, so we must also watch for close.\r\n // The connect timeout caps how long we wait for the OPEN state: on a\r\n // hung connect (mobile after network change, half-open after sleep)\r\n // the browser may take ~2 min to emit onerror/onclose, which blocks\r\n // the reconnect loop and surfaces as \"can't reconnect\" to the user.\r\n const connectTimeoutMs = this.connectTimeoutMs;\r\n try {\r\n await withTimeout(\r\n Promise.race([conn.whenConnected, closedRejection]),\r\n connectTimeoutMs,\r\n 'Connect timeout');\r\n } catch (e) {\r\n if (e instanceof TimeoutError) {\r\n warnLog?.log(`'${this.ref}': Connect timed out after ${connectTimeoutMs}ms`);\r\n // Force-close the WS so the run loop iterates to retry-delay\r\n // instead of leaking a half-open socket. The browser onclose\r\n // may still take a moment but the loop no longer waits for it.\r\n conn.close();\r\n }\r\n throw e;\r\n }\r\n\r\n // WS open, handshake exchange about to start. Mirrors\r\n // .NET's `Handshaking` phase of `RpcPeerConnectionState`.\r\n this._setConnectionState(RpcConnectionState.Handshaking);\r\n\r\n // Send our handshake, then wait for the server's response.\r\n try {\r\n this.hub.systemCallSender.handshake(\r\n conn,\r\n this.serializationFormat,\r\n this.id,\r\n this.hub.hubId,\r\n this.nextOwnHandshakeIndex()\r\n );\r\n }\r\n catch (e) {\r\n // Mirrors RpcPeer.cs:305 — \"Failed to send handshake\".\r\n warnLog?.log(`'${this.ref}': Failed to send handshake`, e);\r\n throw e;\r\n }\r\n\r\n // Handshake timeout — mirrors .NET's `Hub.Limits.HandshakeTimeout`.\r\n // Without this, an accepted WS that never replies to $sys.Handshake\r\n // (half-open TCP, mid-restart server holding the socket briefly)\r\n // would block the run loop indefinitely, preventing reconnect.\r\n const handshakeTimeoutMs = this.handshakeTimeoutMs;\r\n let remoteHandshake: RemoteHandshake;\r\n try {\r\n remoteHandshake = await withTimeout(\r\n Promise.race([this._pendingHandshake, closedRejection]),\r\n handshakeTimeoutMs,\r\n 'Handshake timeout');\r\n } catch (e) {\r\n if (e instanceof TimeoutError) {\r\n warnLog?.log(`'${this.ref}': Handshake timed out after ${handshakeTimeoutMs}ms`);\r\n // Force-close the socket so the run loop continues to\r\n // the retry-delay branch instead of leaking the WS.\r\n conn.close();\r\n }\r\n throw e;\r\n } finally {\r\n this._pendingHandshake = undefined;\r\n }\r\n\r\n // Record the server's handshake index for $sys.Reconnect.\r\n this._remoteHandshakeIndex = remoteHandshake.Index ?? 0;\r\n // Overwrite unconditionally when a secret is present: a client\r\n // that missed one self-heals, and a client that reached a\r\n // different replica adopts that replica's secret. A legacy\r\n // server sends none, which leaves the stored value alone.\r\n if (remoteHandshake.Secret)\r\n this._reconnectSecret = remoteHandshake.Secret;\r\n\r\n // Peer change detection (like .NET's RpcHandshake.GetPeerChangeKind)\r\n const peerChangeKind = this._detectPeerChange(remoteHandshake);\r\n // Mirrors RpcPeer.cs:316 — \"Handshake succeeded\".\r\n infoLog?.log(\r\n `'${this.ref}': Handshake succeeded, peerChangeKind=${peerChangeKindNames[peerChangeKind]}`);\r\n\r\n // Activate the connection and re-send outbound calls.\r\n // All of this happens AFTER the handshake, so the server is ready to process calls.\r\n // Must be snapshotted before `Connected` — see `getSentCalls`.\r\n const sentCalls = this.outboundCalls.getSentCalls();\r\n this._setConnectionState(RpcConnectionState.Connected);\r\n connectedAt = Date.now();\r\n // No prior connection to reconcile with on the very first\r\n // handshake (RpcPeer.cs:421-424).\r\n // Pass the close signal so `_reconcileReconnect`'s inner\r\n // `$sys.Reconnect` call cancels cleanly if the socket dies\r\n // mid-reconcile — otherwise its pending result.promise would\r\n // deadlock the outer _reconnect() await.\r\n if (peerChangeKind !== RpcPeerChangeKind.ChangedToVeryFirst)\r\n await this._reconnect(\r\n sentCalls,\r\n peerChangeKind === RpcPeerChangeKind.Changed,\r\n closedController.signal);\r\n\r\n // Wait until disconnected — use the eager whenClosed promise\r\n // so we don't miss an already-fired close event.\r\n await whenClosed;\r\n } catch (e) {\r\n // connection failed or handshake failed\r\n debugLog?.log(`'${this.ref}': run() iteration failed:`, e);\r\n }\r\n\r\n // Normally redundant — the `conn.closed` handler in\r\n // `setupConnection` already fired `Disconnected`. Kept for\r\n // paths that throw before setupConnection runs.\r\n this._setConnectionState(RpcConnectionState.Disconnected);\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- _disposed can change during await\r\n if (this._disposed) break;\r\n\r\n // Server rejected our serialization format — stop reconnecting and notify listeners\r\n if (lastCloseCode === RPC_CLOSE_CODE_UNSUPPORTED_FORMAT) {\r\n // Mirrors RpcPeer.cs:518 — \"Can't (re)connect, will shut down\".\r\n warnLog?.log(`'${this.ref}': Unsupported serialization format - shutting down`);\r\n this.unsupportedFormat.trigger({ reason: lastCloseReason });\r\n this._disposed = true;\r\n break;\r\n }\r\n\r\n // R13: reset the reconnect backoff only when the connection\r\n // lived at least `prematureDisconnectTimeoutMs`. A premature\r\n // disconnect (e.g. a crash-looping server) leaves `_tryIndex`\r\n // growing so `RetryDelaySeq` backs off. Mirrors .NET's\r\n // PrematureDisconnect handling (RpcPeer.cs:402-405).\r\n if (connectedAt !== 0\r\n && Date.now() - connectedAt >= this.prematureDisconnectTimeoutMs)\r\n this._tryIndex = 0;\r\n\r\n this._tryIndex++;\r\n }\r\n } finally {\r\n this._setConnectionState(RpcConnectionState.Disconnected);\r\n }\r\n }\r\n\r\n /**\r\n * Reconcile the calls that were in flight on the connection that just died\r\n * (`sentCalls`), then resend the survivors. Calls still waiting for a\r\n * connection are deliberately NOT passed in — `AllowReconnect`/`AllowResend`\r\n * don't apply to them, and entering `Connected` already sends them.\r\n *\r\n * On peer change (different server hubId) or with binary wire format —\r\n * we blind-resend every eligible call.\r\n *\r\n * On a same-peer reconnect with JSON transport, first call\r\n * `$sys.Reconnect` to ask the server which call IDs it no longer\r\n * recognizes (e.g. inbound tracker was cleared, or the call never\r\n * reached the server). Only those get resent; the rest stay in the\r\n * tracker awaiting their $sys.Ok. This prevents duplicate server-side\r\n * invocation of in-flight streaming calls on every transient\r\n * reconnect — mirrors .NET `RpcOutboundCallTracker.Reconnect` at\r\n * src/ActualLab.Rpc/Infrastructure/RpcCallTrackers.cs:209-279.\r\n *\r\n * Stage-3 compute calls (result already resolved, awaiting invalidation)\r\n * are always self-invalidated — TS doesn't track compute-call stages\r\n * across reconnect, so the safest behavior is to force a fresh compute.\r\n */\r\n private async _reconnect(\r\n sentCalls: RpcOutboundCall[],\r\n isPeerChanged: boolean,\r\n closedSignal?: AbortSignal\r\n ): Promise<void> {\r\n if (this._connection === undefined) return;\r\n\r\n // Handle remote and shared objects on reconnect.\r\n // On peer change, the remote server is gone — both remote objects\r\n // (proxies registered here for server-owned objects) and shared objects\r\n // (e.g. RpcStreamSender instances this peer owns and is pushing to the\r\n // server) must be disposed: the new server has no corresponding entries,\r\n // so they would hang forever waiting for ACKs that never arrive.\r\n // Mirrors .NET RpcPeer.Reset (src/ActualLab.Rpc/RpcPeer.cs:430-440).\r\n if (isPeerChanged) {\r\n this.remoteObjects.disconnectAll();\r\n this.sharedObjects.disconnectAll();\r\n } else {\r\n this.remoteObjects.reconnectAll();\r\n }\r\n\r\n // Filter calls by RemoteExecutionMode (same logic as before).\r\n const eligible: RpcOutboundCall[] = [];\r\n for (const call of sentCalls) {\r\n const mode = call.remoteExecutionMode;\r\n if (!(mode & 2)) {\r\n if (!call.result.isCompleted)\r\n call.result.reject(new Error(\r\n 'Outbound call failed: disconnected and AllowReconnect is not set.'));\r\n call.onDisconnect();\r\n this.outboundCalls.remove(call.callId);\r\n continue;\r\n }\r\n if (isPeerChanged && !(mode & 4)) {\r\n if (!call.result.isCompleted)\r\n call.result.reject(new Error(\r\n 'Outbound call failed: reconnected to a different peer and AllowResend is not set.'));\r\n call.onDisconnect();\r\n this.outboundCalls.remove(call.callId);\r\n continue;\r\n }\r\n eligible.push(call);\r\n // Restart the run-timeout clock — mirrors .NET's StartedAt reset in\r\n // GetReconnectStage (RpcOutboundCall.cs:343). Without it, a call kept\r\n // (server still knows it) or resent after a long outage would be\r\n // timed out from its stale pre-disconnect send time.\r\n call.sentAt = Date.now();\r\n }\r\n\r\n // Reconcile with the server on same-peer reconnects. On peer change\r\n // we skip the round-trip (the new peer knows nothing) and blind-resend.\r\n //\r\n // Wire format: the `completedStages` dict uses plain JS-object shape\r\n // with string keys and base64-encoded `Uint8Array` values. This is\r\n // JSON-compatible with .NET's `System.Text.Json` serialization of\r\n // `Dictionary<int, byte[]>` and also works for TS-to-TS msgpack\r\n // (serializes as map<str, str>). Full .NET-msgpack interop for\r\n // $sys.Reconnect — which uses `map<int, bin>` — is a future\r\n // enhancement; the JSON path covers ActualChat's primary use case.\r\n let unknownIds: Set<number> | null = null;\r\n if (!isPeerChanged && eligible.length > 0) {\r\n unknownIds = await this._reconcileReconnect(eligible, closedSignal);\r\n }\r\n\r\n // Yield-batched resend. Eligible can be hundreds after a long\r\n // disconnect; chunking prevents a sync spike on the main thread.\r\n const YIELD_BATCH = 20;\r\n let resentSinceYield = 0;\r\n for (const call of eligible) {\r\n if (!call.removeOnOk && call.result.isCompleted) {\r\n // Stage-3 compute call: always self-invalidate to force a\r\n // fresh recompute — TS has no cross-reconnect compute-state\r\n // tracking (unlike .NET's Reliable reconnection).\r\n call.onDisconnect();\r\n this.outboundCalls.remove(call.callId);\r\n continue;\r\n }\r\n if (unknownIds !== null && !unknownIds.has(call.callId)) {\r\n // Server reports this call is still in flight on its side —\r\n // skip the resend. The pending result promise continues to\r\n // await the original call's $sys.Ok.\r\n continue;\r\n }\r\n call.sentAt = Date.now();\r\n this._sendWireData(call.serializedWireData);\r\n resentSinceYield++;\r\n if (resentSinceYield >= YIELD_BATCH) {\r\n resentSinceYield = 0;\r\n await yieldToEventLoop();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Ask the server which call IDs in `eligible` it no longer recognizes\r\n * via `$sys.Reconnect:3`. Returns the set of call IDs that need to be\r\n * resent. On any failure, falls back to \"resend everything\".\r\n *\r\n * Wire format of the `completedStages` argument is chosen per wire format:\r\n * - JSON (json5np): plain object `{\"<stage>\": \"<base64-of-bytes>\"}` —\r\n * matches .NET `System.Text.Json`'s default for `Dictionary<int, byte[]>`.\r\n * - MessagePack (msgpack6/msgpack6c): `Map<number, Uint8Array>` —\r\n * the patched encoder (see `./msgpack-map-patch.ts`) writes this as a\r\n * native msgpack `map<int, bin>`, matching .NET MessagePack-CSharp's\r\n * default for `Dictionary<int, byte[]>`.\r\n */\r\n private async _reconcileReconnect(eligible: RpcOutboundCall[], closedSignal?: AbortSignal): Promise<Set<number>> {\r\n try {\r\n // Group by completedStage, sort IDs, compress per-stage.\r\n const byStage = new Map<number, number[]>();\r\n for (const call of eligible) {\r\n const stage = call.getReconnectStage(false);\r\n if (stage === null) continue;\r\n let arr = byStage.get(stage);\r\n if (!arr) { arr = []; byStage.set(stage, arr); }\r\n arr.push(call.callId);\r\n }\r\n if (byStage.size === 0) {\r\n // Nothing to reconcile — fall back to resend-all.\r\n return new Set(eligible.map(c => c.callId));\r\n }\r\n\r\n const isBinary = this.serializationFormat.isBinary;\r\n const stagesArg: Map<number, Uint8Array> | Record<string, string> = isBinary\r\n ? new Map<number, Uint8Array>()\r\n : {};\r\n for (const [stage, ids] of byStage) {\r\n ids.sort((a, b) => a - b);\r\n const bytes = IncreasingSeqCompressor.serialize(ids);\r\n if (isBinary) {\r\n (stagesArg as Map<number, Uint8Array>).set(stage, bytes);\r\n } else {\r\n (stagesArg as Record<string, string>)[String(stage)] = base64Encode(bytes);\r\n }\r\n }\r\n\r\n // Send $sys.Reconnect via the regular call path so the server's\r\n // $sys.Ok reply resolves a PromiseSource we can await. The\r\n // first arg is the server's OWN handshake index (captured from\r\n // the remote handshake we just received), not ours — this\r\n // matches .NET's protocol, where the server validates\r\n // `ownHandshake.Index == handshakeIndex`.\r\n const reconnectCall = this.call(\r\n RpcSystemCalls.reconnect,\r\n [this._remoteHandshakeIndex, stagesArg],\r\n {\r\n remoteExecutionMode: 1, /* AwaitForConnection only; must not itself reconnect */\r\n signal: closedSignal,\r\n },\r\n );\r\n const result = await reconnectCall.result;\r\n const bytes = _toBytes(result);\r\n if (bytes === null) return new Set(eligible.map(c => c.callId));\r\n return new Set(IncreasingSeqCompressor.deserialize(bytes));\r\n } catch {\r\n // Reconciliation failed — resend everything.\r\n return new Set(eligible.map(c => c.callId));\r\n }\r\n }\r\n\r\n /** Detects a server-peer identity change by comparing the incoming\r\n * handshake's `RemotePeerId` with the last one — C#\r\n * `RpcHandshake.GetPeerChangeKind` keys off `RemotePeerId`, not\r\n * `RemoteHubId` (an idle server peer can be replaced while keeping the\r\n * same hub id). On a change it clears the inbound tracker and fires\r\n * `peerChanged`; the very first handshake is `ChangedToVeryFirst`. */\r\n private _detectPeerChange(handshake: RemoteHandshake): RpcPeerChangeKind {\r\n const lastHandshake = this._lastHandshake;\r\n this._lastHandshake = handshake;\r\n if (lastHandshake === undefined)\r\n return RpcPeerChangeKind.ChangedToVeryFirst;\r\n\r\n if (handshake.RemotePeerId === lastHandshake.RemotePeerId)\r\n return RpcPeerChangeKind.Unchanged;\r\n\r\n this.inboundCalls.clear();\r\n // Mirrors RpcPeer.cs:439 (Peer changed branch).\r\n infoLog?.log(`'${this.ref}': Peer changed`);\r\n this.peerChanged.trigger();\r\n return RpcPeerChangeKind.Changed;\r\n }\r\n\r\n protected override _onHandshakeReceived(handshake: RemoteHandshake): void {\r\n // Also recorded here, not just on the run loop's path: `connectWith` has\r\n // no pending handshake, and leaving this at 0 makes every reconcile it\r\n // drives fail with TooLateToReconnect against a real remote index.\r\n this._remoteHandshakeIndex = handshake.Index ?? 0;\r\n this._pendingHandshake?.resolve(handshake);\r\n }\r\n\r\n override close(): void {\r\n this._disposed = true;\r\n this._stopController.abort();\r\n super.close();\r\n }\r\n}\r\n\r\n/** Server-side RPC peer — wraps an accepted connection. ref = \"server://{uuid}\". */\r\nexport class RpcServerPeer extends RpcPeer {\r\n /** Grace period before an idle (disconnected) server peer is stopped and\r\n * removed from `hub.peers`; a re-`accept()` within it cancels removal.\r\n * Snapshotted from `hub.limits` at construction (F11). */\r\n serverPeerCloseTimeoutMs: number;\r\n private _closeTimer: ReturnType<typeof setTimeout> | undefined;\r\n private _isClosed = false;\r\n\r\n constructor(hub: RpcHub, ref: string) {\r\n super(hub, ref);\r\n this.serverPeerCloseTimeoutMs = hub.limits.serverPeerCloseTimeoutMs;\r\n }\r\n\r\n /** Accept an incoming connection — sets up message handling and handshake response. */\r\n accept(conn: RpcConnection): void {\r\n // A re-accept within the close window keeps this peer (and its trackers)\r\n // alive for a same-peer reconnect — cancel any pending auto-removal.\r\n this._clearCloseTimer();\r\n this._setConnectionState(RpcConnectionState.Connecting);\r\n this.setupConnection(conn);\r\n // Server starts in Handshaking immediately on accept — there's no\r\n // separate \"WS open\" phase since the WS is already up by the time\r\n // we accept. Stay in Handshaking until the client's handshake\r\n // arrives in `_onHandshakeReceived` below.\r\n this._setConnectionState(RpcConnectionState.Handshaking);\r\n // When the client connection drops, clean up shared objects (stream senders).\r\n // Server peers don't reconnect — once the connection is gone, senders must be\r\n // terminated so their source iterators release resources promptly.\r\n conn.closed.add(() => {\r\n // Bail if this conn has already been replaced by a subsequent accept().\r\n // Without this guard, a late-firing close from the old socket would\r\n // disconnect shared objects that belong to the live session.\r\n if (this._connection !== conn && this._connection !== undefined) return;\r\n this.sharedObjects.disconnectAll();\r\n this._armCloseTimer();\r\n });\r\n }\r\n\r\n override close(): void {\r\n this._isClosed = true;\r\n this._clearCloseTimer();\r\n super.close();\r\n }\r\n\r\n private _armCloseTimer(): void {\r\n this._clearCloseTimer();\r\n // `close()` on a connected peer triggers `conn.closed` (possibly after\r\n // `close()` returns on async transports), which lands here — never\r\n // re-arm on a closed peer, or the stale timer's `close()` would later\r\n // evict a successor peer registered under the same ref from `hub.peers`.\r\n if (this._isClosed) return;\r\n const timeout = this.serverPeerCloseTimeoutMs;\r\n if (!(timeout > 0) || !Number.isFinite(timeout)) return;\r\n this._closeTimer = setTimeout(() => {\r\n this._closeTimer = undefined;\r\n // Not re-accepted within the window — stop and remove from hub.peers.\r\n if (this._connection === undefined)\r\n this.close();\r\n }, timeout);\r\n (this._closeTimer as { unref?: () => void }).unref?.();\r\n }\r\n\r\n private _clearCloseTimer(): void {\r\n if (this._closeTimer !== undefined) {\r\n clearTimeout(this._closeTimer);\r\n this._closeTimer = undefined;\r\n }\r\n }\r\n\r\n protected override _onHandshakeReceived(_handshake: RemoteHandshake): void {\r\n // Client sent its handshake → respond with our own and transition to Connected.\r\n if (this._connection !== undefined) {\r\n // Bump our own handshake index each generation so a stale\r\n // $sys.Reconnect referencing an older one is rejected (R16).\r\n this.hub.systemCallSender.handshake(\r\n this._connection,\r\n this.serializationFormat,\r\n this.id,\r\n this.hub.hubId,\r\n this.nextOwnHandshakeIndex()\r\n );\r\n this._setConnectionState(RpcConnectionState.Connected);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Convert a UUID string to base64url — matches .NET's Guid.ToBase64Url().\r\n * .NET Guid stores the first 3 groups in little-endian byte order.\r\n */\r\nfunction guidToBase64Url(uuid: string): string {\r\n const hex = uuid.replace(/-/g, '');\r\n const bytes = new Uint8Array(16);\r\n\r\n // Group 1 (bytes 0-3): little-endian\r\n bytes[0] = parseInt(hex.slice(6, 8), 16);\r\n bytes[1] = parseInt(hex.slice(4, 6), 16);\r\n bytes[2] = parseInt(hex.slice(2, 4), 16);\r\n bytes[3] = parseInt(hex.slice(0, 2), 16);\r\n // Group 2 (bytes 4-5): little-endian\r\n bytes[4] = parseInt(hex.slice(10, 12), 16);\r\n bytes[5] = parseInt(hex.slice(8, 10), 16);\r\n // Group 3 (bytes 6-7): little-endian\r\n bytes[6] = parseInt(hex.slice(14, 16), 16);\r\n bytes[7] = parseInt(hex.slice(12, 14), 16);\r\n // Groups 4-5 (bytes 8-15): big-endian\r\n for (let i = 8; i < 16; i++)\r\n bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\r\n\r\n return base64UrlEncode(bytes);\r\n}\r\n\r\n// Warned at most once per process: the condition is a property of the host, so\r\n// it would otherwise repeat on every connect attempt, forever.\r\nlet isReconnectProofUnavailableWarned = false;\r\n\r\nfunction warnReconnectProofUnavailable(ref: string, reason: string): void {\r\n if (isReconnectProofUnavailableWarned)\r\n return;\r\n\r\n isReconnectProofUnavailableWarned = true;\r\n warnLog?.log(\r\n `'${ref}': ${reason} - connecting without a reconnect proof. `\r\n + 'This fails if the server requires one (RequireReconnectProof).');\r\n}\r\n\r\n/** Coerce a `$sys.Ok` result carrying a byte[] into a Uint8Array. */\r\nfunction _toBytes(value: unknown): Uint8Array | null {\r\n if (value instanceof Uint8Array) return value;\r\n if (typeof value === 'string') {\r\n try { return base64Decode(value); } catch { return null; }\r\n }\r\n return null;\r\n}\r\n","// RPC serialization format definitions — mirrors .NET RpcSerializationFormat.\r\n//\r\n// Each format knows how to serialize/deserialize RPC messages on the wire.\r\n// Format instances are immutable singletons — state like method registries\r\n// is passed in via parameters, not held by the format.\r\n\r\nimport { Encoder, Decoder } from '@msgpack/msgpack';\r\nimport type { RpcMessage } from './rpc-message.js';\r\nimport {\r\n serializeMessage,\r\n deserializeMessage,\r\n serializeBinaryMessage,\r\n splitBinaryFrame,\r\n serializeBinaryFrame,\r\n createBinaryEncoder,\r\n serializeCompactBinaryMessage,\r\n splitCompactBinaryFrame,\r\n} from './rpc-serialization.js';\r\nimport type { RpcMethodRegistry } from './rpc-method-registry.js';\r\n\r\n/** Parsed inbound message — result of deserialization. */\r\nexport interface RpcDeserializedMessage {\r\n message: RpcMessage;\r\n args: unknown[];\r\n}\r\n\r\n/** Wire data produced by serialization — either text or binary. */\r\nexport type RpcWireData = string | Uint8Array;\r\n\r\n/**\r\n * Base class for RPC serialization formats.\r\n * Instances are immutable singletons — one per format key.\r\n * State (like method registries) is passed via parameters.\r\n */\r\nexport abstract class RpcSerializationFormat {\r\n abstract readonly key: string;\r\n abstract readonly isBinary: boolean;\r\n /** Whether this format uses compact method hashes (needs RpcMethodRegistry). */\r\n abstract readonly isCompact: boolean;\r\n\r\n /** Serialize an outbound message. */\r\n abstract serializeMessage(\r\n message: RpcMessage,\r\n args?: unknown[],\r\n encoder?: Encoder,\r\n registry?: RpcMethodRegistry\r\n ): RpcWireData;\r\n\r\n /** Deserialize a single text message (only called for text formats). */\r\n abstract deserializeTextMessage(raw: string): RpcDeserializedMessage;\r\n\r\n /** Split + deserialize a binary frame (only called for binary formats). */\r\n abstract splitBinaryFrame(\r\n frame: Uint8Array,\r\n decoder?: Decoder,\r\n registry?: RpcMethodRegistry\r\n ): RpcDeserializedMessage[];\r\n\r\n /** Concatenate multiple serialized binary messages into one frame. */\r\n abstract serializeBinaryFrame(messages: Uint8Array[]): Uint8Array;\r\n\r\n /** Create a reusable binary encoder for this format. Returns undefined for text formats. */\r\n createEncoder(): Encoder | undefined {\r\n return undefined;\r\n }\r\n\r\n /** Create a reusable binary decoder for this format. Returns undefined for text formats. */\r\n createDecoder(): Decoder | undefined {\r\n return undefined;\r\n }\r\n\r\n // Named format instances — initialized in initFormats() below (after subclasses are defined)\r\n static SystemJsonV5: RpcSerializationFormat;\r\n static SystemJsonV5NP: RpcSerializationFormat;\r\n static NewtonsoftJsonV5: RpcSerializationFormat;\r\n static NewtonsoftJsonV5NP: RpcSerializationFormat;\r\n static MessagePackV6: RpcSerializationFormat;\r\n static MessagePackV6C: RpcSerializationFormat;\r\n static All: readonly RpcSerializationFormat[];\r\n\r\n /** Convenience shortcut for RpcSerializationFormatResolver.Default.get(key). */\r\n static get(key: string): RpcSerializationFormat {\r\n return RpcSerializationFormatResolver.Default.get(key);\r\n }\r\n\r\n /** Convenience shortcut for RpcSerializationFormatResolver.Default.tryGet(key). */\r\n static tryGet(key: string): RpcSerializationFormat | undefined {\r\n return RpcSerializationFormatResolver.Default.tryGet(key);\r\n }\r\n}\r\n\r\n// ============================================================\r\n// Concrete formats (immutable singletons)\r\n// ============================================================\r\n\r\n/**\r\n * JSON text format — \"json5np\" (System.Text.Json, no polymorphism).\r\n */\r\nexport class RpcJsonSerializationFormat extends RpcSerializationFormat {\r\n readonly key: string;\r\n readonly isBinary = false;\r\n readonly isCompact = false;\r\n\r\n constructor(key: string) {\r\n super();\r\n this.key = key;\r\n }\r\n\r\n serializeMessage(message: RpcMessage, args?: unknown[]): string {\r\n return serializeMessage(message, args);\r\n }\r\n\r\n deserializeTextMessage(raw: string): RpcDeserializedMessage {\r\n return deserializeMessage(raw);\r\n }\r\n\r\n splitBinaryFrame(): RpcDeserializedMessage[] {\r\n throw new Error('Text format does not support binary frames');\r\n }\r\n\r\n serializeBinaryFrame(): Uint8Array {\r\n throw new Error('Text format does not support binary frames');\r\n }\r\n}\r\n\r\n/**\r\n * MessagePack binary format — \"msgpack6\" (V5 wire format).\r\n */\r\nexport class RpcMessagePackSerializationFormat extends RpcSerializationFormat {\r\n readonly key: string;\r\n readonly isBinary = true;\r\n readonly isCompact = false;\r\n\r\n constructor(key: string) {\r\n super();\r\n this.key = key;\r\n }\r\n\r\n serializeMessage(\r\n message: RpcMessage,\r\n args?: unknown[],\r\n encoder?: Encoder\r\n ): Uint8Array {\r\n return serializeBinaryMessage(message, args, encoder);\r\n }\r\n\r\n deserializeTextMessage(raw: string): RpcDeserializedMessage {\r\n return deserializeMessage(raw);\r\n }\r\n\r\n splitBinaryFrame(\r\n frame: Uint8Array,\r\n decoder?: Decoder\r\n ): RpcDeserializedMessage[] {\r\n return splitBinaryFrame(frame, decoder);\r\n }\r\n\r\n serializeBinaryFrame(messages: Uint8Array[]): Uint8Array {\r\n return serializeBinaryFrame(messages);\r\n }\r\n\r\n override createEncoder(): Encoder {\r\n return createBinaryEncoder();\r\n }\r\n\r\n override createDecoder(): Decoder {\r\n return new Decoder();\r\n }\r\n}\r\n\r\n/**\r\n * MessagePack compact binary format — \"msgpack6c\" (V5Compact).\r\n * Uses 4-byte method hash instead of full method name.\r\n * Registry is passed via parameters — format instance is immutable.\r\n */\r\nexport class RpcMessagePackCompactSerializationFormat extends RpcSerializationFormat {\r\n readonly key: string;\r\n readonly isBinary = true;\r\n readonly isCompact = true;\r\n\r\n constructor(key: string) {\r\n super();\r\n this.key = key;\r\n }\r\n\r\n serializeMessage(\r\n message: RpcMessage,\r\n args?: unknown[],\r\n encoder?: Encoder,\r\n registry?: RpcMethodRegistry\r\n ): Uint8Array {\r\n return serializeCompactBinaryMessage(\r\n message,\r\n args,\r\n registry,\r\n encoder\r\n );\r\n }\r\n\r\n deserializeTextMessage(raw: string): RpcDeserializedMessage {\r\n return deserializeMessage(raw);\r\n }\r\n\r\n splitBinaryFrame(\r\n frame: Uint8Array,\r\n decoder?: Decoder,\r\n registry?: RpcMethodRegistry\r\n ): RpcDeserializedMessage[] {\r\n return splitCompactBinaryFrame(frame, registry, decoder);\r\n }\r\n\r\n serializeBinaryFrame(messages: Uint8Array[]): Uint8Array {\r\n return serializeBinaryFrame(messages);\r\n }\r\n\r\n override createEncoder(): Encoder {\r\n return createBinaryEncoder();\r\n }\r\n\r\n override createDecoder(): Decoder {\r\n return new Decoder();\r\n }\r\n}\r\n\r\n// ============================================================\r\n// Initialize named format instances (after subclasses are defined)\r\n// ============================================================\r\n\r\n// Text: System.Text.Json\r\nRpcSerializationFormat.SystemJsonV5 = new RpcJsonSerializationFormat('json5');\r\nRpcSerializationFormat.SystemJsonV5NP = new RpcJsonSerializationFormat('json5np');\r\n// Text: Newtonsoft.Json\r\nRpcSerializationFormat.NewtonsoftJsonV5 = new RpcJsonSerializationFormat('njson5');\r\nRpcSerializationFormat.NewtonsoftJsonV5NP = new RpcJsonSerializationFormat('njson5np');\r\n// Binary: MessagePack (non-compact)\r\nRpcSerializationFormat.MessagePackV6 = new RpcMessagePackSerializationFormat('msgpack6');\r\n// Binary: MessagePack (compact)\r\nRpcSerializationFormat.MessagePackV6C = new RpcMessagePackCompactSerializationFormat('msgpack6c');\r\n\r\n// The mempack6* formats are intentionally NOT registered: .NET's mempack6 uses\r\n// MemoryPack argument serialization (a different byte format), which the TS\r\n// client does not implement. Resolving one throws via isMemoryPackKey below.\r\nRpcSerializationFormat.All = [\r\n RpcSerializationFormat.SystemJsonV5,\r\n RpcSerializationFormat.SystemJsonV5NP,\r\n RpcSerializationFormat.NewtonsoftJsonV5,\r\n RpcSerializationFormat.NewtonsoftJsonV5NP,\r\n RpcSerializationFormat.MessagePackV6,\r\n RpcSerializationFormat.MessagePackV6C,\r\n];\r\n\r\n// ============================================================\r\n// RpcSerializationFormatResolver — mirrors .NET RpcSerializationFormatResolver\r\n// ============================================================\r\n\r\n/**\r\n * Resolves RpcSerializationFormat instances by key, with a configurable default.\r\n * Mirrors .NET RpcSerializationFormatResolver.\r\n */\r\nexport class RpcSerializationFormatResolver {\r\n static DefaultFormats: readonly RpcSerializationFormat[] = RpcSerializationFormat.All;\r\n static Default = new RpcSerializationFormatResolver('json5np');\r\n\r\n readonly defaultFormatKey: string;\r\n readonly formats: ReadonlyMap<string, RpcSerializationFormat>;\r\n\r\n get defaultFormat(): RpcSerializationFormat {\r\n return this.get(this.defaultFormatKey);\r\n }\r\n\r\n constructor(defaultFormatKey: string, formats?: Iterable<RpcSerializationFormat>) {\r\n const map = new Map<string, RpcSerializationFormat>();\r\n for (const f of formats ?? RpcSerializationFormatResolver.DefaultFormats)\r\n map.set(f.key, f);\r\n\r\n if (!map.has(defaultFormatKey))\r\n throw new Error(`No format with key '${defaultFormatKey}'.`);\r\n\r\n this.defaultFormatKey = defaultFormatKey;\r\n this.formats = map;\r\n }\r\n\r\n get(key: string): RpcSerializationFormat {\r\n if (isMemoryPackKey(key))\r\n throw new Error(`Serialization format '${key}': MemoryPack formats are not supported by the TS client.`);\r\n\r\n const format = this.tryGet(key);\r\n if (!format)\r\n throw new Error(`No format with key '${key}'.`);\r\n return format;\r\n }\r\n\r\n tryGet(key: string): RpcSerializationFormat | undefined {\r\n if (!key) key = this.defaultFormatKey;\r\n if (isMemoryPackKey(key))\r\n throw new Error(`Serialization format '${key}': MemoryPack formats are not supported by the TS client.`);\r\n return this.formats.get(key);\r\n }\r\n}\r\n\r\n/** True for .NET MemoryPack format keys (mempack5, mempack6, mempack6c, …). */\r\nfunction isMemoryPackKey(key: string): boolean {\r\n return key.startsWith('mempack');\r\n}\r\n","// Client half of the reconnect proof-of-possession protocol. The server mints a\r\n// per-peer secret and delivers it inside its `$sys.Handshake`; every subsequent\r\n// connect attempt carries a counter (`c`) plus an HMAC over it (`p`), so a leaked\r\n// `clientId` alone can no longer evict or hijack a live peer.\r\n//\r\n// .NET counterpart: ActualLab.Rpc.Internal.RpcReconnectProof. The two MUST agree\r\n// byte for byte, so both sides are pinned by the same test vector:\r\n// key = UTF8(secret) -- the secret is an opaque\r\n// token, NOT base64url-decoded\r\n// message = UTF8(clientId + \"\\n\" + counterText) -- \"\\n\" is a single 0x0A\r\n// proof = Base64Url_NoPad(HMAC_SHA256(key, message)) -- 32 bytes -> 43 chars\r\n\r\nimport { base64UrlEncode } from './base64.js';\r\n\r\nconst textEncoder = new TextEncoder();\r\n\r\n/** The `c` / `p` query parameter pair for one connect attempt. */\r\nexport interface RpcReconnectProofParameters {\r\n counter: string;\r\n proof: string;\r\n}\r\n\r\n// `crypto.subtle` is undefined in an insecure browser context (plain http on a\r\n// non-localhost host). A client there must connect without a proof rather than\r\n// fail outright, so callers gate on this instead of catching a TypeError.\r\nexport function isReconnectProofSupported(): boolean {\r\n return typeof crypto !== 'undefined' && (crypto.subtle as SubtleCrypto | undefined) !== undefined;\r\n}\r\n\r\n// `counterText` must be the decimal counter exactly as it will appear in the URL:\r\n// the server HMACs the `c` value string as it arrived, without reparsing it.\r\nexport async function computeReconnectProof(\r\n secret: string, clientId: string, counterText: string\r\n): Promise<string> {\r\n const key = await crypto.subtle.importKey(\r\n 'raw', textEncoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);\r\n const signature = await crypto.subtle.sign(\r\n 'HMAC', key, textEncoder.encode(`${clientId}\\n${counterText}`));\r\n return base64UrlEncode(new Uint8Array(signature));\r\n}\r\n","/**\r\n * Configurable timing limits for RPC connection lifecycle. Mirrors .NET's\r\n * `RpcLimits` (src/ActualLab.Rpc/Configuration/RpcLimits.cs).\r\n *\r\n * Override paths, from broadest to narrowest:\r\n * - **Process-wide**: mutate fields on {@link RpcLimits.Default} (affects\r\n * every hub that hasn't been given its own instance — hubs default to\r\n * using the shared `Default`, so mutations propagate live), or replace\r\n * `RpcLimits.Default = new RpcLimits({ … })` (affects hubs constructed\r\n * *after* the replacement).\r\n * - **Per-hub**: assign `hub.limits = new RpcLimits({ … })` before peers\r\n * are created from that hub.\r\n * - **Per-peer**: set the matching `*Ms` field on the peer directly\r\n * (e.g. `peer.keepAliveTimeoutMs = 60_000`) before `start()`.\r\n *\r\n * Peers snapshot the relevant values from `hub.limits` at construction —\r\n * later mutations to `hub.limits` don't retroactively affect already-built\r\n * peers; that's by design and matches the .NET semantics.\r\n */\r\nexport class RpcLimits {\r\n /** Process-wide default. Hubs read from this when they're constructed\r\n * unless caller assigns a custom instance to `hub.limits`. */\r\n static Default: RpcLimits = new RpcLimits();\r\n\r\n /** Dev preset with a relaxed keep-alive timeout (5 min), matching .NET's\r\n * debugger-attached `KeepAlivePeriod` (RpcLimits.cs:46-54). Activate\r\n * explicitly — e.g. `RpcLimits.Default = RpcLimits.Debug` or per-hub\r\n * `hub.limits = RpcLimits.Debug`. Never switched on automatically: the\r\n * TS side can't detect a debugger on the server (decision D4). */\r\n static readonly Debug: RpcLimits = new RpcLimits({ keepAliveTimeoutMs: 300_000 });\r\n\r\n /** Max time to wait for the WebSocket to enter the OPEN state. On a\r\n * hung connect (mobile after network change, half-open after sleep)\r\n * the browser can take ~2 min to emit `onerror`/`onclose`; without\r\n * this cap the reconnect loop blocks for that whole window. */\r\n connectTimeoutMs = 10_000;\r\n\r\n /** Max time to wait for the server's handshake response after WS opens. */\r\n handshakeTimeoutMs = 10_000;\r\n\r\n /** If a connection lived less than this before dropping, a graceful close\r\n * still counts as a failed attempt — the client keeps its growing\r\n * `_tryIndex` instead of resetting it, so a crash-looping server sees\r\n * increasing reconnect delays via `RetryDelaySeq`. Mirrors .NET\r\n * `RpcLimits.PrematureDisconnectTimeout` (RpcLimits.cs:18). */\r\n prematureDisconnectTimeoutMs = 15_000;\r\n\r\n /** Max completed inbound calls retained for duplicate-frame dedup\r\n * (a resent call id re-sends the computed result instead of\r\n * re-executing the handler). Deviation from .NET, which unregisters a\r\n * call once its result is sent: TS clients blind-resend on reconnect,\r\n * so completed calls are retained — bounded by this limit (oldest\r\n * evicted first) to cap memory on long-lived connections. */\r\n completedInboundCallsLimit = 1000;\r\n\r\n /** How often each peer's maintenance loop scans outbound calls for\r\n * timeouts (R12). Mirrors .NET `RpcLimits.CallTimeoutCheckPeriod`. */\r\n callTimeoutCheckPeriodMs = 1_000;\r\n\r\n /** Outbound `$sys.KeepAlive` send period. */\r\n keepAlivePeriodMs = 10_000;\r\n\r\n /** Reaper threshold — connection is force-closed (and the reconnect\r\n * loop takes over) if no inbound `$sys.KeepAlive` has been seen for\r\n * this long. Sized to tolerate a complete server stall plus most of\r\n * one keepalive cycle: the worst-case age of `_lastKeepAliveAt` is\r\n * `keepAlivePeriodMs + stall_duration`, so with these defaults the\r\n * tolerated stall is ~15 s. */\r\n keepAliveTimeoutMs = 25_000;\r\n\r\n /** How long a server peer stays in `hub.peers` after its connection closes\r\n * before it's stopped and removed. A same-peer reconnect (`accept()`)\r\n * within this window cancels the pending removal, so brief drops don't\r\n * discard the peer's trackers. Mirrors .NET's server-peer shutdown timeout\r\n * (`RpcPeerOptions.ServerPeerShutdownTimeoutProvider`, 3-15 min); the TS\r\n * port uses a fixed floor-matching default. */\r\n serverPeerCloseTimeoutMs = 180_000;\r\n\r\n constructor(overrides?: Partial<RpcLimits>) {\r\n if (overrides) Object.assign(this, overrides);\r\n }\r\n}\r\n","// .NET counterpart: ActualLab.Rpc.RpcClientPeerReconnectDelayer\r\n\r\nimport { RetryDelaySeq, RetryDelayer } from '@actuallab/core';\r\n\r\nexport class RpcClientPeerReconnectDelayer extends RetryDelayer {\r\n constructor() {\r\n super();\r\n // 0.1s base — a single flaky drop still reconnects fast (callers gate\r\n // *whether* to reconnect via app-level signals, see Api /\r\n // ApiReconnectDelayer). The exponential tail matters when `_tryIndex`\r\n // keeps climbing — repeated connect failures and premature disconnects\r\n // (RpcLimits.prematureDisconnectTimeoutMs) — so clients back off to\r\n // 10s instead of hammering a crash-looping server ~10x/s. C# uses\r\n // Exp(1s, 60s) for browser clients (RpcClientPeerReconnectDelayer.cs).\r\n this.delays = RetryDelaySeq.exp(100, 10_000);\r\n }\r\n}\r\n","// .NET counterparts:\r\n// RpcServiceBase (17 lines) — abstract base class providing DI access (Services,\r\n// Hub, Log). All RPC service implementations inherit from it.\r\n// RpcServiceRegistry — builds the full map of all services and their method defs\r\n// at startup; provides GetServerMethodResolver() for version-aware method\r\n// lookup. Inbound call dispatch is handled by RpcInboundCall.Process() which\r\n// invokes the method via a compiled delegate chain (MethodDef.InboundCallInvoker).\r\n//\r\n// Omitted from .NET:\r\n// - Compiled delegate pipeline — .NET pre-compiles per-method invoker delegates\r\n// (InboundCallServerInvokerFactory<T>, InboundCallMiddlewareInvokerFactory<T>)\r\n// using generic type instantiation. These invokers handle deserialization,\r\n// middleware chain, server method invocation, and result serialization — all\r\n// as a strongly-typed Task<TResult>. TS uses a simple async function dispatch\r\n// via fn(...args) because there's no middleware pipeline and JSON handles any\r\n// type.\r\n// - ServiceResolver (DI-based lazy service resolution) — .NET resolves the\r\n// service implementation from DI on first use. TS receives the impl object\r\n// directly in register().\r\n// - RpcServiceMode (Local/Client/Server/Hybrid) — .NET classifies each service\r\n// registration. TS has no mode; services are either registered (server) or\r\n// proxied (client).\r\n// - Version-aware method resolution (ServerMethodResolver with legacy names) —\r\n// .NET can resolve methods by legacy names for rolling-upgrade compatibility.\r\n// TS has no versioning.\r\n// - RpcInboundContext / AsyncLocal — .NET sets thread-local inbound context\r\n// so system call handlers can access the current peer/message. TS passes\r\n// context explicitly as a function argument (RpcDispatchContext).\r\n\r\nimport { getLogs } from './logging.js';\r\nimport type { RpcMethodDef } from './rpc-service-def.js';\r\nimport { wireMethodName } from './rpc-service-def.js';\r\nimport type { RpcServiceDef } from './rpc-service-def.js';\r\nimport type { RpcConnection } from './rpc-connection.js';\r\nimport type { RpcPeer } from './rpc-peer.js';\r\n\r\nconst { warnLog } = getLogs('RpcServiceHost');\r\n\r\nexport type RpcServiceImpl = Record<string, ((...args: unknown[]) => unknown) | undefined>;\r\n\r\n/** Context passed to service dispatch — carries callId, the inbound CallType,\r\n * the connection, and the peer for compute-call invalidation tracking. */\r\nexport interface RpcDispatchContext {\r\n __rpcDispatch: true;\r\n callId: number;\r\n callType: number;\r\n connection: RpcConnection;\r\n /** Aborted when the inbound call is cancelled via `$sys.Cancel` (R17). */\r\n signal?: AbortSignal;\r\n peer: RpcPeer;\r\n}\r\n\r\n/** Dispatches inbound RPC calls to registered service implementations. */\r\nexport class RpcServiceHost {\r\n private _methods = new Map<\r\n string,\r\n { def: RpcMethodDef; fn: (...args: unknown[]) => unknown; receiver: object }\r\n >();\r\n\r\n register(def: RpcServiceDef, impl: RpcServiceImpl, receiver: object = impl): void {\r\n for (const methodDef of def.methods.values()) {\r\n const fn = impl[methodDef.name];\r\n if (!fn) continue;\r\n this._methods.set(wireMethodName(methodDef), {\r\n def: methodDef,\r\n fn,\r\n receiver,\r\n });\r\n }\r\n }\r\n\r\n async dispatch(\r\n wireMethod: string,\r\n args: unknown[],\r\n context?: RpcDispatchContext\r\n ): Promise<unknown> {\r\n const entry = this._methods.get(wireMethod);\r\n if (entry === undefined) {\r\n // Mirrors .NET: missing method is sent back as $sys.Error; warn for visibility.\r\n warnLog?.log(`Method not found: ${wireMethod}`);\r\n throw new Error(`Method not found: ${wireMethod}`);\r\n }\r\n\r\n // Pass context as the last arg only for custom call types (e.g., compute) — their\r\n // wrapped functions use it; regular methods should not see it to avoid polluting ...args.\r\n if (context !== undefined && entry.def.callTypeId !== 0) {\r\n return await entry.fn.call(entry.receiver, ...args, context);\r\n }\r\n return await entry.fn.call(entry.receiver, ...args);\r\n }\r\n\r\n getMethodDef(wireMethod: string): RpcMethodDef | undefined {\r\n return this._methods.get(wireMethod)?.def;\r\n }\r\n}\r\n","// .NET counterpart:\r\n// RpcMethodAttribute — optional attribute that can override a method's wire name.\r\n// Method metadata (arg count, return type, NoWait, stream, etc.) is discovered\r\n// automatically via reflection on the service interface.\r\n// Interface-based metadata — .NET discovers all methods by reflecting on the\r\n// service interface type. NoWait is determined by return type == Task<RpcNoWait>.\r\n// Stream is determined by return type being IAsyncEnumerable<T>.\r\n//\r\n// Omitted from .NET:\r\n// - Reflection-based auto-discovery — .NET scans interface methods, parameter\r\n// types, and return types at startup. TS uses explicit decorator metadata\r\n// because TypeScript erases type information at runtime.\r\n// - RpcMethodAttribute.Name — custom wire name override. TS decorators don't\r\n// support custom wire names (always \"ServiceName.methodName\").\r\n// - RpcServiceAttribute — .NET has no class-level decorator; the service name\r\n// is configured in RpcServiceBuilder via DI. TS uses @rpcService(\"name\")\r\n// class decorator as a convenient alternative to DI configuration.\r\n// - Automatic NoWait detection from return type — .NET checks if return type\r\n// is Task<RpcNoWait>. TS requires explicit { returns: RpcType.noWait }\r\n// because return types are erased.\r\n// - Automatic stream detection from return type — .NET checks for\r\n// IAsyncEnumerable<T>. TS requires explicit { returns: RpcType.stream }.\r\n// - compute flag in method metadata — stored here for FusionHub's\r\n// addServiceFromContract() which reads decorator metadata to build a\r\n// RpcServiceDef. .NET determines this via the service interface hierarchy\r\n// (IComputeService marker interface).\r\n\r\nimport { ownMetadata, resolveArgCount } from '@actuallab/core';\r\n\r\nconst METHODS_META = Symbol.for('actuallab.methods');\r\nconst SERVICE_META = Symbol.for('actuallab.service');\r\n\r\nexport interface MethodMeta {\r\n argCount: number;\r\n returns?: symbol;\r\n /** Bitfield of RpcRemoteExecutionMode flags. */\r\n remoteExecutionMode?: number;\r\n [key: symbol]: unknown;\r\n}\r\n\r\nexport interface ServiceMeta {\r\n name: string;\r\n}\r\n\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- constructor type requires any for maximum compatibility\r\nexport type AnyConstructor = abstract new (...args: any[]) => any;\r\n\r\n/** Read method metadata from a decorated class. */\r\nexport function getMethodsMeta(\r\n cls: AnyConstructor,\r\n): Record<string, MethodMeta> | undefined {\r\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any -- Symbol.metadata is not in standard typings\r\n return (cls as any)[Symbol.metadata]?.[METHODS_META] as Record<string, MethodMeta> | undefined;\r\n}\r\n\r\n/** Read service metadata from a decorated class. */\r\nexport function getServiceMeta(\r\n cls: AnyConstructor,\r\n): ServiceMeta | undefined {\r\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any -- Symbol.metadata is not in standard typings\r\n return (cls as any)[Symbol.metadata]?.[SERVICE_META] as ServiceMeta | undefined;\r\n}\r\n\r\n/** Class decorator — stores the RPC service wire name. */\r\nexport function rpcService(serviceName: string) {\r\n return function <T extends AnyConstructor>(\r\n _target: T,\r\n context: ClassDecoratorContext<T>,\r\n ): void {\r\n const meta = ownMetadata<ServiceMeta>(context.metadata, SERVICE_META);\r\n meta.name = serviceName;\r\n };\r\n}\r\n\r\n/** Method decorator — stores RPC method metadata (argCount, returns, remoteExecutionMode). Does NOT wrap the method. */\r\nexport function rpcMethod(options?: {\r\n returns?: symbol;\r\n remoteExecutionMode?: number;\r\n argCount?: number;\r\n}) {\r\n return function <This, Args extends unknown[], Return>(\r\n target: (this: This, ...args: Args) => Return,\r\n context: ClassMethodDecoratorContext<\r\n This,\r\n (this: This, ...args: Args) => Return\r\n >,\r\n ): (this: This, ...args: Args) => Return {\r\n const methodName = String(context.name);\r\n const methods = ownMetadata<Record<string, MethodMeta>>(context.metadata, METHODS_META);\r\n methods[methodName] = {\r\n ...methods[methodName],\r\n argCount: resolveArgCount(target, options?.argCount, methodName),\r\n returns: options?.returns,\r\n remoteExecutionMode: options?.remoteExecutionMode,\r\n };\r\n return target; // no wrapping — RPC proxy handles invocation\r\n };\r\n}\r\n","// Pure TypeScript XXH3-64 implementation using BigInt.\r\n// Sync, zero dependencies. Supports inputs of any length.\r\n//\r\n// Reference: https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h\r\n// Verified against .NET System.IO.Hashing.XxHash3.HashToUInt64.\r\n\r\n \r\n\r\nconst U64_MASK = 0xffff_ffff_ffff_ffffn;\r\nconst U32_MASK = 0xffff_ffffn;\r\n\r\nconst PRIME32_1 = 0x9e3779b1;\r\nconst PRIME32_2 = 0x85ebca77;\r\nconst PRIME32_3 = 0xc2b2ae3d;\r\n\r\nconst PRIME64_1 = 0x9e3779b185ebca87n;\r\nconst PRIME64_2 = 0xc2b2ae3d27d4eb4fn;\r\nconst PRIME64_3 = 0x165667b19e3779f9n;\r\nconst PRIME64_4 = 0x85ebca77c2b2ae63n;\r\nconst PRIME64_5 = 0x27d4eb2f165667c5n;\r\n\r\nconst XXH3_SECRET_SIZE_MIN = 136;\r\nconst XXH_SECRET_DEFAULT_SIZE = 192;\r\n\r\nconst STRIPE_LEN = 64;\r\nconst XXH_SECRET_CONSUME_RATE = 8;\r\nconst ACC_NB = STRIPE_LEN / 8;\r\n\r\nconst XXH3_MIDSIZE_MAX = 240;\r\nconst XXH3_MIDSIZE_STARTOFFSET = 3;\r\nconst XXH3_MIDSIZE_LASTOFFSET = 17;\r\nconst XXH_SECRET_LASTACC_START = 7;\r\nconst XXH_SECRET_MERGEACCS_START = 11;\r\n\r\nconst RRMXMX = 0x9fb21c651e98df25n;\r\n\r\n// prettier-ignore\r\nconst DEFAULT_SECRET = new Uint8Array([\r\n 0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,\r\n 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,\r\n 0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,\r\n 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,\r\n 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,\r\n 0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,\r\n 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,\r\n 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,\r\n 0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,\r\n 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,\r\n 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,\r\n 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,\r\n]);\r\n\r\nfunction u64(x: bigint): bigint {\r\n return x & U64_MASK;\r\n}\r\n\r\nfunction read32LE(buf: Uint8Array, off: number): number {\r\n return (\r\n (buf[off] |\r\n (buf[off + 1] << 8) |\r\n (buf[off + 2] << 16) |\r\n (buf[off + 3] << 24)) >>>\r\n 0\r\n );\r\n}\r\n\r\nfunction read64LE(buf: Uint8Array, off: number): bigint {\r\n return (\r\n BigInt(buf[off]) |\r\n (BigInt(buf[off + 1]) << 8n) |\r\n (BigInt(buf[off + 2]) << 16n) |\r\n (BigInt(buf[off + 3]) << 24n) |\r\n (BigInt(buf[off + 4]) << 32n) |\r\n (BigInt(buf[off + 5]) << 40n) |\r\n (BigInt(buf[off + 6]) << 48n) |\r\n (BigInt(buf[off + 7]) << 56n)\r\n );\r\n}\r\n\r\nfunction write64LE(buf: Uint8Array, off: number, value: bigint): void {\r\n let x = u64(value);\r\n for (let i = 0; i < 8; i++) {\r\n buf[off + i] = Number(x & 0xffn);\r\n x >>= 8n;\r\n }\r\n}\r\n\r\nfunction swap32(x: number): number {\r\n return (\r\n (((x << 24) & 0xff000000) |\r\n ((x << 8) & 0x00ff0000) |\r\n ((x >>> 8) & 0x0000ff00) |\r\n ((x >>> 24) & 0x000000ff)) >>>\r\n 0\r\n );\r\n}\r\n\r\nfunction swap64(x: bigint): bigint {\r\n x = u64(x);\r\n return (\r\n ((x << 56n) & 0xff00000000000000n) |\r\n ((x << 40n) & 0x00ff000000000000n) |\r\n ((x << 24n) & 0x0000ff0000000000n) |\r\n ((x << 8n) & 0x000000ff00000000n) |\r\n ((x >> 8n) & 0x00000000ff000000n) |\r\n ((x >> 24n) & 0x0000000000ff0000n) |\r\n ((x >> 40n) & 0x000000000000ff00n) |\r\n ((x >> 56n) & 0x00000000000000ffn)\r\n );\r\n}\r\n\r\nfunction rotl64(x: bigint, amount: number): bigint {\r\n const r = BigInt(amount & 63);\r\n return u64((x << r) | (x >> (64n - r)));\r\n}\r\n\r\nfunction mul128Fold64(lhs: bigint, rhs: bigint): bigint {\r\n const product = u64(lhs) * u64(rhs);\r\n return u64(product) ^ u64(product >> 64n);\r\n}\r\n\r\n// XXH64_avalanche — used by len_0 and len_1to3\r\nfunction xxh64Avalanche(hash: bigint): bigint {\r\n let h = u64(hash);\r\n h ^= h >> 33n;\r\n h = u64(h * PRIME64_2);\r\n h ^= h >> 29n;\r\n h = u64(h * PRIME64_3);\r\n h ^= h >> 32n;\r\n return u64(h);\r\n}\r\n\r\n// XXH3_avalanche — used by len_9to16, len_17to128, len_129to240\r\nfunction xxh3Avalanche(hash: bigint): bigint {\r\n let h = u64(hash);\r\n h ^= h >> 37n;\r\n h = u64(h * 0x165667919e3779f9n);\r\n h ^= h >> 32n;\r\n return u64(h);\r\n}\r\n\r\nfunction len0(secret: Uint8Array, seed: bigint): bigint {\r\n const acc = u64(seed ^ read64LE(secret, 56) ^ read64LE(secret, 64));\r\n return xxh64Avalanche(acc);\r\n}\r\n\r\nfunction len1to3(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n seed: bigint\r\n): bigint {\r\n const byte1 = input[0];\r\n const byte2 = length > 1 ? input[1] : input[0];\r\n const byte3 = input[length - 1];\r\n const combined =\r\n ((byte1 << 16) | (byte2 << 24) | byte3 | (length << 8)) >>> 0;\r\n const bitflip = u64(\r\n BigInt((read32LE(secret, 0) ^ read32LE(secret, 4)) >>> 0) + seed\r\n );\r\n const value64 = u64(BigInt(combined) ^ bitflip);\r\n return xxh64Avalanche(value64);\r\n}\r\n\r\nfunction len4to8(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n seed: bigint\r\n): bigint {\r\n const inputHi = read32LE(input, 0);\r\n const inputLo = read32LE(input, length - 4);\r\n const input64 = BigInt(inputLo) | (BigInt(inputHi) << 32n);\r\n let acc = read64LE(secret, 8) ^ read64LE(secret, 16);\r\n seed ^= BigInt(swap32(Number(seed & U32_MASK))) << 32n;\r\n acc = u64(acc - seed);\r\n acc ^= input64;\r\n acc ^= rotl64(acc, 49) ^ rotl64(acc, 24);\r\n acc = u64(acc * RRMXMX);\r\n acc ^= (acc >> 35n) + BigInt(length);\r\n acc = u64(acc * RRMXMX);\r\n acc ^= acc >> 28n;\r\n return u64(acc);\r\n}\r\n\r\nfunction len9to16(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n seed: bigint\r\n): bigint {\r\n let inputLo = read64LE(secret, 24) ^ read64LE(secret, 32);\r\n let inputHi = read64LE(secret, 40) ^ read64LE(secret, 48);\r\n let acc = BigInt(length);\r\n inputLo = u64(inputLo + seed);\r\n inputHi = u64(inputHi - seed);\r\n inputLo ^= read64LE(input, 0);\r\n inputHi ^= read64LE(input, length - 8);\r\n acc = u64(acc + swap64(inputLo));\r\n acc = u64(acc + inputHi);\r\n acc = u64(acc + mul128Fold64(inputLo, inputHi));\r\n return xxh3Avalanche(acc);\r\n}\r\n\r\nfunction mix16B(\r\n input: Uint8Array,\r\n inputOff: number,\r\n secret: Uint8Array,\r\n secretOff: number,\r\n seed: bigint\r\n): bigint {\r\n let lhs = seed;\r\n let rhs = u64(0n - seed);\r\n lhs = u64(lhs + read64LE(secret, secretOff));\r\n rhs = u64(rhs + read64LE(secret, secretOff + 8));\r\n lhs ^= read64LE(input, inputOff);\r\n rhs ^= read64LE(input, inputOff + 8);\r\n return mul128Fold64(lhs, rhs);\r\n}\r\n\r\nfunction len17to128(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n seed: bigint\r\n): bigint {\r\n let i = Math.floor((length - 1) / 32);\r\n let acc = u64(BigInt(length) * PRIME64_1);\r\n for (; i >= 0; i--) {\r\n acc = u64(acc + mix16B(input, 16 * i, secret, 32 * i, seed));\r\n acc = u64(\r\n acc +\r\n mix16B(\r\n input,\r\n length - 16 * (i + 1),\r\n secret,\r\n 32 * i + 16,\r\n seed\r\n )\r\n );\r\n }\r\n return xxh3Avalanche(acc);\r\n}\r\n\r\nfunction len129to240(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n seed: bigint\r\n): bigint {\r\n let acc = u64(BigInt(length) * PRIME64_1);\r\n const nbRounds = Math.floor(length / 16);\r\n for (let i = 0; i < 8; i++) {\r\n acc = u64(acc + mix16B(input, 16 * i, secret, 16 * i, seed));\r\n }\r\n acc = xxh3Avalanche(acc);\r\n for (let i = 8; i < nbRounds; i++) {\r\n acc = u64(\r\n acc +\r\n mix16B(\r\n input,\r\n 16 * i,\r\n secret,\r\n 16 * (i - 8) + XXH3_MIDSIZE_STARTOFFSET,\r\n seed\r\n )\r\n );\r\n }\r\n acc = u64(\r\n acc +\r\n mix16B(\r\n input,\r\n length - 16,\r\n secret,\r\n XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET,\r\n seed\r\n )\r\n );\r\n return xxh3Avalanche(acc);\r\n}\r\n\r\nfunction hashShort(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n seed: bigint\r\n): bigint {\r\n if (length <= 16) {\r\n if (length > 8) return len9to16(input, length, secret, seed);\r\n if (length >= 4) return len4to8(input, length, secret, seed);\r\n if (length !== 0) return len1to3(input, length, secret, seed);\r\n return len0(secret, seed);\r\n }\r\n if (length <= 128) return len17to128(input, length, secret, seed);\r\n return len129to240(input, length, secret, seed);\r\n}\r\n\r\n// --- Long input (> 240 bytes) ---\r\n\r\nfunction accumulate512(\r\n acc: bigint[],\r\n input: Uint8Array,\r\n inputOff: number,\r\n secret: Uint8Array,\r\n secretOff: number\r\n): void {\r\n for (let i = 0; i < ACC_NB; i++) {\r\n const dataVal = read64LE(input, inputOff + 8 * i);\r\n const dataKey = dataVal ^ read64LE(secret, secretOff + 8 * i);\r\n acc[i ^ 1] = u64(acc[i ^ 1] + dataVal);\r\n const low32 = dataKey & U32_MASK;\r\n const high32 = dataKey >> 32n;\r\n acc[i] = u64(acc[i] + low32 * high32);\r\n }\r\n}\r\n\r\nfunction scrambleAcc(\r\n acc: bigint[],\r\n secret: Uint8Array,\r\n secretOff: number\r\n): void {\r\n for (let i = 0; i < ACC_NB; i++) {\r\n let x = acc[i];\r\n x ^= x >> 47n;\r\n x ^= read64LE(secret, secretOff + 8 * i);\r\n x = u64(x * BigInt(PRIME32_1));\r\n acc[i] = x;\r\n }\r\n}\r\n\r\nfunction accumulateLoop(\r\n acc: bigint[],\r\n input: Uint8Array,\r\n inputOff: number,\r\n secret: Uint8Array,\r\n secretOff: number,\r\n nbStripes: number\r\n): void {\r\n for (let n = 0; n < nbStripes; n++) {\r\n accumulate512(\r\n acc,\r\n input,\r\n inputOff + n * STRIPE_LEN,\r\n secret,\r\n secretOff + 8 * n\r\n );\r\n }\r\n}\r\n\r\nfunction mix2Accs(\r\n acc: bigint[],\r\n accOff: number,\r\n secret: Uint8Array,\r\n secretOff: number\r\n): bigint {\r\n return mul128Fold64(\r\n acc[accOff] ^ read64LE(secret, secretOff),\r\n acc[accOff + 1] ^ read64LE(secret, secretOff + 8)\r\n );\r\n}\r\n\r\nfunction mergeAccs(\r\n acc: bigint[],\r\n key: Uint8Array,\r\n keyOff: number,\r\n start: bigint\r\n): bigint {\r\n let result = start;\r\n for (let i = 0; i < 4; i++) {\r\n result = u64(result + mix2Accs(acc, 2 * i, key, keyOff + 16 * i));\r\n }\r\n return xxh3Avalanche(result);\r\n}\r\n\r\nfunction hashLong(\r\n input: Uint8Array,\r\n length: number,\r\n secret: Uint8Array,\r\n secretSize: number\r\n): bigint {\r\n const nbRounds = Math.floor(\r\n (secretSize - STRIPE_LEN) / XXH_SECRET_CONSUME_RATE\r\n );\r\n const blockLen = STRIPE_LEN * nbRounds;\r\n const nbBlocks = Math.floor((length - 1) / blockLen);\r\n const nbStripes = Math.floor(\r\n ((length - 1) - blockLen * nbBlocks) / STRIPE_LEN\r\n );\r\n\r\n const acc: bigint[] = [\r\n BigInt(PRIME32_3),\r\n PRIME64_1,\r\n PRIME64_2,\r\n PRIME64_3,\r\n PRIME64_4,\r\n BigInt(PRIME32_2),\r\n PRIME64_5,\r\n BigInt(PRIME32_1),\r\n ];\r\n\r\n for (let n = 0; n < nbBlocks; n++) {\r\n accumulateLoop(acc, input, n * blockLen, secret, 0, nbRounds);\r\n scrambleAcc(acc, secret, secretSize - STRIPE_LEN);\r\n }\r\n\r\n accumulateLoop(acc, input, nbBlocks * blockLen, secret, 0, nbStripes);\r\n\r\n // Last stripe — always applied (C reference: unconditional)\r\n accumulate512(\r\n acc,\r\n input,\r\n length - STRIPE_LEN,\r\n secret,\r\n secretSize - STRIPE_LEN - XXH_SECRET_LASTACC_START\r\n );\r\n\r\n return mergeAccs(\r\n acc,\r\n secret,\r\n XXH_SECRET_MERGEACCS_START,\r\n u64(BigInt(length) * PRIME64_1)\r\n );\r\n}\r\n\r\nfunction hashLongWithSeed(\r\n input: Uint8Array,\r\n length: number,\r\n seed: bigint\r\n): bigint {\r\n const secret = new Uint8Array(XXH_SECRET_DEFAULT_SIZE);\r\n for (let i = 0; i < XXH_SECRET_DEFAULT_SIZE / 16; i++) {\r\n const off = 16 * i;\r\n write64LE(secret, off, u64(read64LE(DEFAULT_SECRET, off) + seed));\r\n write64LE(\r\n secret,\r\n off + 8,\r\n u64(read64LE(DEFAULT_SECRET, off + 8) - seed)\r\n );\r\n }\r\n return hashLong(input, length, secret, secret.length);\r\n}\r\n\r\n// --- Public API ---\r\n\r\nconst textEncoder = new TextEncoder();\r\n\r\n/**\r\n * Compute XXH3-64 hash of a byte array. Returns a BigInt (unsigned 64-bit).\r\n * Pure TypeScript, synchronous, supports any input length.\r\n */\r\nexport function xxh3_64(input: Uint8Array, seed = 0n): bigint {\r\n const len = input.length;\r\n if (len <= XXH3_MIDSIZE_MAX) {\r\n return hashShort(input, len, DEFAULT_SECRET, u64(seed));\r\n }\r\n return hashLongWithSeed(input, len, u64(seed));\r\n}\r\n\r\n/**\r\n * Compute XXH3-64 hash of a UTF-8 string. Returns a BigInt (unsigned 64-bit).\r\n */\r\nexport function xxh3_64str(input: string, seed = 0n): bigint {\r\n return xxh3_64(textEncoder.encode(input), seed);\r\n}\r\n\r\n/**\r\n * Compute the RPC method hash matching .NET's RpcMethodRef.ComputeHashCode.\r\n * Returns a 32-bit signed integer (lower 32 bits of XXH3-64 of [prefix + utf8Name]).\r\n *\r\n * The prefix is uint32 LE of (67211 * utf8Name.length), which ensures methods\r\n * with different name lengths but similar text produce different hashes.\r\n *\r\n * Computed on demand — caller should cache the result per method name.\r\n */\r\nexport function computeMethodHash(methodName: string): number {\r\n const utf8 = textEncoder.encode(methodName);\r\n // Prefix: uint32 LE of (67211 * utf8.length)\r\n const prefixValue = Math.imul(67211, utf8.length) >>> 0;\r\n const prefixed = new Uint8Array(4 + utf8.length);\r\n prefixed[0] = prefixValue & 0xff;\r\n prefixed[1] = (prefixValue >>> 8) & 0xff;\r\n prefixed[2] = (prefixValue >>> 16) & 0xff;\r\n prefixed[3] = (prefixValue >>> 24) & 0xff;\r\n prefixed.set(utf8, 4);\r\n const hash64 = xxh3_64(prefixed);\r\n // Truncate to signed int32 (same as C#'s unchecked((int)hash64))\r\n return Number(hash64 & 0xffffffffn) | 0;\r\n}\r\n","// Method name ↔ hash registry for compact binary serialization.\r\n//\r\n// .NET counterpart: RpcMethodResolver — maintains MethodByHashCode dictionary\r\n// for resolving compact 4-byte method hashes back to full method names.\r\n//\r\n// Hashes are computed on demand (first use) and cached. The computeMethodHash\r\n// function uses BigInt-based XXH3-64, which is pure sync TypeScript.\r\n\r\nimport { getLogs } from './logging.js';\r\nimport { computeMethodHash } from './rpc-xxhash3.js';\r\n\r\nconst { warnLog } = getLogs('RpcMethodRegistry');\r\n\r\n/**\r\n * Registry mapping RPC method wire names to/from their 4-byte XXH3 hashes.\r\n * Hashes match .NET's RpcMethodRef.ComputeHashCode.\r\n * Hashes are computed lazily on first access and cached.\r\n */\r\nexport class RpcMethodRegistry {\r\n private _nameToHash = new Map<string, number>();\r\n private _hashToName = new Map<number, string>();\r\n\r\n /** Register a method name and compute its hash. */\r\n register(methodName: string): void {\r\n if (this._nameToHash.has(methodName)) return;\r\n const hash = computeMethodHash(methodName);\r\n\r\n // Check for hash collisions\r\n const existing = this._hashToName.get(hash);\r\n if (existing !== undefined && existing !== methodName) {\r\n warnLog?.log(`hash collision: \"${methodName}\" and \"${existing}\" both hash to ${hash}`);\r\n return;\r\n }\r\n\r\n this._nameToHash.set(methodName, hash);\r\n this._hashToName.set(hash, methodName);\r\n }\r\n\r\n /** Register all wire method names from a service definition. */\r\n registerService(\r\n serviceName: string,\r\n methods: ReadonlyMap<string, { name: string; wireArgCount: number }>\r\n ): void {\r\n for (const [, def] of methods) {\r\n const wireName = `${serviceName}.${def.name}:${def.wireArgCount}`;\r\n this.register(wireName);\r\n }\r\n }\r\n\r\n /** Get the hash for a method name. Computes on demand if not registered. */\r\n getHash(methodName: string): number {\r\n let hash = this._nameToHash.get(methodName);\r\n if (hash === undefined) {\r\n this.register(methodName);\r\n hash = this._nameToHash.get(methodName)!;\r\n }\r\n return hash;\r\n }\r\n\r\n /** Get the method name for a hash. Returns undefined if not registered. */\r\n getName(hash: number): string | undefined {\r\n return this._hashToName.get(hash);\r\n }\r\n\r\n /** Get the hash for a method name, throwing if not registered. */\r\n requireHash(methodName: string): number {\r\n return this.getHash(methodName);\r\n }\r\n}\r\n","// .NET counterpart:\r\n// RpcHub (141 lines) — a sealed ProcessorBase (IAsyncDisposable) that owns:\r\n// DI-resolved configuration (RpcRegistryOptions, PeerOptions, InboundCallOptions,\r\n// OutboundCallOptions, DiagnosticsOptions), SerializationFormats, Middlewares,\r\n// ClientPeerReconnectDelayer, Limits, SystemClock.\r\n// Manages peers via ConcurrentDictionary<RpcRoute, RpcPeer> with GetPeer()\r\n// that lazily creates + starts peers. Also has GetClient<T>() / GetServer<T>()\r\n// that look up services in ServiceRegistry.\r\n//\r\n// Omitted from .NET:\r\n// - DI / IServiceProvider integration — .NET resolves all options, serializers,\r\n// middleware, delayers, etc. from DI. TS constructs everything directly.\r\n// - RpcServiceRegistry — .NET builds a full registry of all RPC services at\r\n// startup via reflection + DI, mapping Type → RpcServiceDef. TS uses explicit\r\n// addService() / addClient() calls.\r\n// - GetClient<T>() / GetServer<T>() — .NET resolves typed client/server proxies\r\n// via the ServiceRegistry + DI. TS uses addClient<T>(peer, def).\r\n// - RpcRoute-keyed peer dictionary — .NET peers are keyed by RpcRoute (one\r\n// route generation of a stable RpcRef, which encodes client/server, versions,\r\n// serialization format). TS uses string refs (URL for clients,\r\n// \"server://{uuid}\" for servers).\r\n// - Lazy peer creation + Start() — .NET's GetPeer auto-creates and starts a\r\n// peer's reconnection loop. TS requires explicit addPeer() + run().\r\n// - DisposeAsync — .NET disposes all peers on hub shutdown. TS has close().\r\n// - Route change detection (RpcRoute.WhenChanged → Dispose peer) — .NET\r\n// watches for topology changes and auto-disposes stale-route peers.\r\n// TS has no routing layer.\r\n// - Middlewares (IRpcMiddleware[]) — ordered middleware pipeline for inbound\r\n// calls. TS dispatches directly to service implementations.\r\n// - RpcConfiguration / Configuration.Freeze() — frozen config snapshot.\r\n// - HostId / SystemClock — infrastructure services.\r\n// - DefaultPeer / LoopbackPeer / LocalPeer / NonePeer — cached well-known peers.\r\n\r\nimport { getLogs } from './logging.js';\r\nimport {\r\n RpcClientPeer,\r\n RpcServerPeer,\r\n type RpcPeer,\r\n type RpcCallOptions,\r\n} from './rpc-peer.js';\r\nimport { RpcLimits } from './rpc-limits.js';\r\nimport { RpcClientPeerReconnectDelayer } from './rpc-client-peer-reconnect-delayer.js';\r\nimport { RpcServiceHost, type RpcServiceImpl } from './rpc-service-host.js';\r\nimport type { RpcServiceDef, RpcMethodDef } from './rpc-service-def.js';\r\nimport { wireMethodName, RpcType, RpcRemoteExecutionMode } from './rpc-service-def.js';\r\nimport { getServiceMeta, getMethodsMeta, type AnyConstructor } from './rpc-decorators.js';\r\nimport { RpcSystemCallSender } from './rpc-system-call-sender.js';\r\nimport { RpcSystemCallHandler } from './rpc-system-call-handler.js';\r\nimport { toRpcStream } from './rpc-stream.js';\r\nimport { RpcMethodRegistry } from './rpc-method-registry.js';\r\nimport { RpcSystemCalls } from './rpc-message.js';\r\n\r\nconst { warnLog } = getLogs('RpcHub');\r\n\r\n/** Factory signature shared by all peer lookup/creation methods. Serialization\r\n * format is encoded in the URL via `?f=...` (see {@link RpcRefBuilder}),\r\n * so the factory doesn't need an extra parameter for it. `getClientPeer` /\r\n * `getServerPeer` cast the result, so the factory returns RpcPeer. */\r\nexport type RpcPeerFactory = (hub: RpcHub, ref: string) => RpcPeer;\r\n\r\ntype RpcClientFn = (...args: unknown[]) => unknown;\r\n\r\n/** A live client proxy plus the mutable maps it reads from, so a later def can\r\n * add methods to the already-handed-out proxy. `methods` is the map the Proxy's\r\n * get-trap resolves against; `overloads` tracks each method's per-argCount defs\r\n * and functions for overload resolution and conflict detection. */\r\ninterface RpcClientProxy {\r\n readonly proxy: object;\r\n readonly methods: Map<string, RpcClientFn>;\r\n readonly overloads: Map<string, Map<number, { def: RpcMethodDef; fn: RpcClientFn }>>;\r\n}\r\n\r\n/** Central RPC coordinator — manages peers, services, and configuration. */\r\nexport class RpcHub {\r\n readonly hubId: string;\r\n readonly peers = new Map<string, RpcPeer>();\r\n readonly serviceHost: RpcServiceHost;\r\n readonly systemCallSender: RpcSystemCallSender;\r\n systemCallHandler: RpcSystemCallHandler = new RpcSystemCallHandler();\r\n\r\n /** Method registry for compact format hash ↔ name resolution.\r\n * Created lazily, populated by addService/addClient. */\r\n readonly registry = new RpcMethodRegistry();\r\n\r\n /** Shared reconnect delayer used by every client peer in this hub. Swap in\r\n * a custom subclass (e.g. app-level signal-gated) before peers start. */\r\n reconnectDelayer: RpcClientPeerReconnectDelayer = new RpcClientPeerReconnectDelayer();\r\n\r\n /** Cache of client proxies keyed by peer, then by service name — so\r\n * repeated {@link addClient} calls for the same service+peer return one\r\n * proxy. All consumers then share a single computed/call/invalidation\r\n * stream per logical value, matching .NET's singleton client proxies (F8).\r\n * A later def registered under the same name extends the existing proxy\r\n * with its extra methods (superset), so no consumer ever receives a proxy\r\n * missing methods the def declares. */\r\n private readonly _clientProxies = new WeakMap<RpcPeer, Map<string, RpcClientProxy>>();\r\n\r\n /** Connection-lifecycle timing limits. Peers constructed against this hub\r\n * read their initial `*Ms` field values from this instance; later\r\n * mutations affect only peers built afterward (existing peers can be\r\n * retuned by setting their `*Ms` fields directly). Defaults to the\r\n * process-wide {@link RpcLimits.Default} so a single global override\r\n * propagates to every hub that hasn't replaced this. */\r\n limits: RpcLimits = RpcLimits.Default;\r\n\r\n /** URL used by {@link defaultPeer} to resolve / create the default client\r\n * peer. Must be set before the first {@link defaultPeer} access. */\r\n defaultPeerUrl: string | undefined;\r\n /** Factory used by {@link defaultPeer} when the peer needs to be created.\r\n * If left undefined, `getPeer`'s built-in default (RpcClientPeer with\r\n * format derived from URL or resolver default) is used. */\r\n defaultPeerFactory: RpcPeerFactory | undefined;\r\n\r\n constructor(hubId?: string) {\r\n this.hubId = hubId ?? crypto.randomUUID();\r\n this.serviceHost = new RpcServiceHost();\r\n this.systemCallSender = this._createSystemCallSender();\r\n this.systemCallSender.registry = this.registry;\r\n // Pre-register system call method names for compact format hash resolution\r\n for (const methodName of Object.values(RpcSystemCalls)) {\r\n this.registry.register(methodName);\r\n }\r\n }\r\n\r\n /** Shortcut for the hub's default client peer. Fails if\r\n * {@link defaultPeerUrl} is not set. */\r\n get defaultPeer(): RpcClientPeer {\r\n if (this.defaultPeerUrl === undefined)\r\n throw new Error('RpcHub.defaultPeerUrl is not set.');\r\n return this.getClientPeer(this.defaultPeerUrl, this.defaultPeerFactory);\r\n }\r\n\r\n /** Register a peer under its ref. If a different peer is already registered\r\n * at the same ref, close it first — the hub guarantees at most one live\r\n * peer per ref, and no peer exists outside the hub. */\r\n addPeer(peer: RpcPeer): void {\r\n const existing = this.peers.get(peer.ref);\r\n if (existing !== undefined && existing !== peer)\r\n existing.close(); // close() removes itself from this.peers\r\n this.peers.set(peer.ref, peer);\r\n }\r\n\r\n removePeer(ref: string): void {\r\n const peer = this.peers.get(ref);\r\n if (peer !== undefined) {\r\n peer.close();\r\n this.peers.delete(ref);\r\n // Mirrors RpcHub.cs:137 — \"peer is removed from RpcHub\".\r\n warnLog?.log(`'${ref}': peer is removed from RpcHub`);\r\n }\r\n }\r\n\r\n /** Get or create a peer by ref. If the peer does not exist:\r\n * - with `factory` provided: it is constructed via `factory(this, ref)`;\r\n * - otherwise: server peers (ref starts with `server://`) get a plain\r\n * RpcServerPeer, anything else gets a default-configured RpcClientPeer.\r\n * The created peer is always registered in this hub. */\r\n getPeer(ref: string, factory?: RpcPeerFactory): RpcPeer {\r\n const existing = this.peers.get(ref);\r\n if (existing) return existing;\r\n\r\n const peer = factory\r\n ? factory(this, ref)\r\n : ref.startsWith('server://')\r\n ? new RpcServerPeer(this, ref)\r\n : new RpcClientPeer(this, ref);\r\n this.addPeer(peer);\r\n return peer;\r\n }\r\n\r\n /** Get or create a client peer for the given URL. See {@link getPeer} for\r\n * the factory semantics. */\r\n getClientPeer(ref: string, factory?: RpcPeerFactory): RpcClientPeer {\r\n return this.getPeer(ref, factory) as RpcClientPeer;\r\n }\r\n\r\n /** Get or create a server peer for the given ref. See {@link getPeer} for\r\n * the factory semantics. */\r\n getServerPeer(ref: string, factory?: RpcPeerFactory): RpcServerPeer {\r\n return this.getPeer(ref, factory) as RpcServerPeer;\r\n }\r\n\r\n /** Register a service with optional server method wrapping for custom call types. */\r\n addService(\r\n defOrContract: RpcServiceDef | (AnyConstructor),\r\n impl: RpcServiceImpl\r\n ): void {\r\n const def = this._resolveServiceDef(defOrContract);\r\n const wrappedImpl: RpcServiceImpl = {};\r\n for (const methodDef of def.methods.values()) {\r\n const fn = impl[methodDef.name];\r\n if (!fn) continue;\r\n wrappedImpl[methodDef.name] =\r\n methodDef.callTypeId !== 0\r\n ? this._wrapServerMethod(methodDef, fn, impl as object)\r\n : fn;\r\n }\r\n this.serviceHost.register(def, wrappedImpl, impl as object);\r\n this.registry.registerService(def.name, def.methods);\r\n }\r\n\r\n /** Create a typed client proxy for a service on a remote peer. Repeated calls\r\n * for the same (peer, service name) return one shared proxy; a def carrying\r\n * extra methods extends it (F8). */\r\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T is used for caller-specified proxy type\r\n addClient<T extends object>(\r\n peer: RpcPeer,\r\n defOrContract: RpcServiceDef | (AnyConstructor)\r\n ): T {\r\n const def = this._resolveServiceDef(defOrContract);\r\n this.registry.registerService(def.name, def.methods);\r\n\r\n let byService = this._clientProxies.get(peer);\r\n if (byService === undefined) {\r\n byService = new Map<string, RpcClientProxy>();\r\n this._clientProxies.set(peer, byService);\r\n }\r\n let entry = byService.get(def.name);\r\n if (entry === undefined) {\r\n entry = this._createClientProxy();\r\n byService.set(def.name, entry);\r\n }\r\n this._extendClientProxy(peer, entry, def);\r\n return entry.proxy as T;\r\n }\r\n\r\n close(): void {\r\n for (const peer of this.peers.values()) peer.close();\r\n this.peers.clear();\r\n }\r\n\r\n /** Create the hub's system-call sender. Override in FusionHub to add\r\n * Fusion-specific system calls (e.g. $sys-c.Invalidate). */\r\n protected _createSystemCallSender(): RpcSystemCallSender {\r\n return new RpcSystemCallSender();\r\n }\r\n\r\n /** Build an RpcServiceDef from decorator metadata on a contract class. Override in FusionHub for compute support. */\r\n protected _buildServiceDef(\r\n cls: AnyConstructor\r\n ): RpcServiceDef {\r\n const svcMeta = getServiceMeta(cls);\r\n if (svcMeta === undefined)\r\n throw new Error('Contract class missing @rpcService metadata');\r\n\r\n const methodsMeta = getMethodsMeta(cls) ?? {};\r\n const methods = new Map<string, RpcMethodDef>();\r\n\r\n for (const [name, meta] of Object.entries(methodsMeta)) {\r\n const wireArgCount = meta.argCount + 1;\r\n const mapKey = `${name}:${wireArgCount}`;\r\n const isNoWait = meta.returns === RpcType.noWait;\r\n methods.set(mapKey, {\r\n name,\r\n serviceName: svcMeta.name,\r\n argCount: meta.argCount,\r\n wireArgCount,\r\n callTypeId: 0,\r\n stream: meta.returns === RpcType.stream,\r\n noWait: isNoWait,\r\n remoteExecutionMode: isNoWait\r\n ? 0\r\n : (meta.remoteExecutionMode ?? RpcRemoteExecutionMode.Default),\r\n });\r\n }\r\n\r\n return { name: svcMeta.name, methods };\r\n }\r\n\r\n /** Resolve a service def from either a plain RpcServiceDef or a contract class. */\r\n protected _resolveServiceDef(\r\n defOrContract: RpcServiceDef | (AnyConstructor)\r\n ): RpcServiceDef {\r\n if (typeof defOrContract === 'function')\r\n return this._buildServiceDef(defOrContract);\r\n return defOrContract;\r\n }\r\n\r\n /** Override in FusionHub — default is pass-through (for methods with callTypeId !== 0). */\r\n protected _wrapServerMethod(\r\n _methodDef: RpcMethodDef,\r\n fn: (...args: unknown[]) => unknown,\r\n _impl: object\r\n ): (...args: unknown[]) => unknown {\r\n return fn;\r\n }\r\n\r\n /** Create a client method for the given methodDef. Override in FusionHub for custom call types. */\r\n protected _createClientMethod(\r\n peer: RpcPeer,\r\n methodDef: RpcMethodDef\r\n ): (...args: unknown[]) => unknown {\r\n const wireName = wireMethodName(methodDef);\r\n const mode = methodDef.remoteExecutionMode;\r\n const mustCheckConnection = !(mode & RpcRemoteExecutionMode.AwaitForConnection);\r\n\r\n // NoWait methods — remoteExecutionMode is always 0, silently drop if disconnected\r\n if (methodDef.noWait) {\r\n return (...args: unknown[]) => {\r\n const callArgs = args.slice(0, methodDef.argCount);\r\n peer.callNoWait(wireName, callArgs);\r\n };\r\n }\r\n\r\n // Stream methods — call returns an RpcStream<T> (AsyncIterable)\r\n if (methodDef.stream) {\r\n return async (...args: unknown[]) => {\r\n if (mustCheckConnection && !peer.isConnected)\r\n throw new Error('Outbound call failed: not connected and AwaitForConnection is not set.');\r\n const callArgs = args.slice(0, methodDef.argCount);\r\n const outboundCall = peer.call(wireName, callArgs);\r\n const result = await outboundCall.result;\r\n const stream = toRpcStream(result, peer);\r\n if (!stream)\r\n throw new Error(\r\n `Expected stream reference, got: ${JSON.stringify(result)}`\r\n );\r\n return stream;\r\n };\r\n }\r\n\r\n // Regular methods (including non-zero callTypeId — subclass can override for custom behavior)\r\n const callOptions: RpcCallOptions | undefined =\r\n (methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default || methodDef.timeouts !== undefined)\r\n ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode, timeouts: methodDef.timeouts }\r\n : undefined;\r\n\r\n return async (...args: unknown[]) => {\r\n if (mustCheckConnection && !peer.isConnected)\r\n throw new Error('Outbound call failed: not connected and AwaitForConnection is not set.');\r\n const callArgs = args.slice(0, methodDef.argCount);\r\n const outboundCall = peer.call(wireName, callArgs, callOptions);\r\n // The result is handed back as deserialized. A stream reference\r\n // nested inside it is converted by the caller via `toRpcStream` —\r\n // see the note on that function for why it can't be inferred here.\r\n return await outboundCall.result;\r\n };\r\n }\r\n\r\n // Private methods\r\n\r\n private _createClientProxy(): RpcClientProxy {\r\n const methods = new Map<string, RpcClientFn>();\r\n const proxy = new Proxy({}, {\r\n get: (_target, prop) =>\r\n typeof prop === 'string' ? methods.get(prop) : undefined,\r\n });\r\n return { proxy, methods, overloads: new Map() };\r\n }\r\n\r\n private _extendClientProxy(peer: RpcPeer, entry: RpcClientProxy, def: RpcServiceDef): void {\r\n // Validate the whole def before mutating anything, so a conflict\r\n // leaves the shared proxy exactly as it was.\r\n for (const methodDef of def.methods.values()) {\r\n const existing = entry.overloads.get(methodDef.name)?.get(methodDef.argCount);\r\n if (existing !== undefined && !isSameWireMethod(existing.def, methodDef))\r\n throw new Error(\r\n `RpcHub.addClient: conflicting definition of '${wireMethodName(methodDef)}' ` +\r\n `under service '${def.name}' — a different signature is already registered.`);\r\n }\r\n\r\n const changed = new Set<string>();\r\n for (const methodDef of def.methods.values()) {\r\n let byArgCount = entry.overloads.get(methodDef.name);\r\n if (byArgCount === undefined) {\r\n byArgCount = new Map();\r\n entry.overloads.set(methodDef.name, byArgCount);\r\n }\r\n if (byArgCount.has(methodDef.argCount))\r\n continue;\r\n\r\n byArgCount.set(methodDef.argCount, {\r\n def: methodDef,\r\n fn: this._createClientMethod(peer, methodDef),\r\n });\r\n changed.add(methodDef.name);\r\n }\r\n for (const name of changed) {\r\n const byArgCount = entry.overloads.get(name)!;\r\n entry.methods.set(name, buildOverloadFn(name, byArgCount));\r\n }\r\n }\r\n}\r\n\r\nfunction buildOverloadFn(\r\n name: string,\r\n byArgCount: Map<number, { def: RpcMethodDef; fn: RpcClientFn }>\r\n): RpcClientFn {\r\n if (byArgCount.size === 1) {\r\n const [[, only]] = byArgCount;\r\n return only.fn;\r\n }\r\n return (...args: unknown[]) => {\r\n const overload = byArgCount.get(args.length);\r\n if (overload === undefined)\r\n throw new Error(`No overload of ${name} accepts ${args.length} arguments`);\r\n return overload.fn(...args);\r\n };\r\n}\r\n\r\nfunction isSameWireMethod(a: RpcMethodDef, b: RpcMethodDef): boolean {\r\n return a.wireArgCount === b.wireArgCount\r\n && a.callTypeId === b.callTypeId\r\n && a.stream === b.stream\r\n && a.noWait === b.noWait\r\n && a.remoteExecutionMode === b.remoteExecutionMode;\r\n}\r\n","// Ref composition helpers for RPC peers.\n//\n// `RpcClientPeer`'s ref is its full WebSocket URL. The serialization format is\n// encoded as the `f=` query parameter (see RpcClientPeer ctor).\n// `RpcServerPeer`'s ref is `server://{id}`.\n//\n// Typical use:\n// rpcHub.defaultPeerUrl = RpcRefBuilder.forClient('wss://host/rpc/ws', 'msgpack6');\n// const serverRef = RpcRefBuilder.forServer(crypto.randomUUID());\n\nexport class RpcRefBuilder {\n /** Build a client peer ref from a WebSocket URL, optionally baking in the\n * serialization format as a `f=` query parameter.\n * @param url Base WebSocket URL, e.g. `wss://host/rpc/ws`.\n * @param serializationFormat Optional format key, e.g. `msgpack6`. When\n * provided, replaces any existing `f=` in the URL. */\n static forClient(url: string, serializationFormat?: string): string {\n if (serializationFormat === undefined) return url;\n const u = new URL(url);\n u.searchParams.set('f', serializationFormat);\n return u.toString();\n }\n\n /** Build a server peer ref from a peer id — returns `server://{id}`. */\n static forServer(id: string): string {\n return `server://${id}`;\n }\n}\n","// .NET counterpart:\r\n// RpcClient — a DI-registered service used by generated interceptor proxies.\r\n// The proxy classes (generated by Interceptor/ProxyGenerator) intercept method\r\n// calls on the service interface, create an RpcOutboundContext, resolve the\r\n// target peer via OutboundCallRouter, and invoke the outbound call pipeline.\r\n//\r\n// Omitted from .NET:\r\n// - Interceptor-based proxy generation — .NET generates proxy classes at startup\r\n// (via Castle DynamicProxy / source generators) that implement the service\r\n// interface. TS uses a runtime Proxy object instead, which achieves the same\r\n// result without code generation.\r\n// - RpcOutboundContext — .NET creates a rich context per call with CancellationToken,\r\n// Headers, CacheInfoCapture, Trace, RoutingMode. TS passes arguments directly.\r\n// - OutboundCallRouter — .NET routes each call to the appropriate peer based on\r\n// sharding/load-balancing rules. TS passes the peer explicitly.\r\n// - This standalone function is superseded by RpcHub.addClient() which adds\r\n// noWait and compute-aware dispatch. Kept for backward compatibility.\r\n\r\nimport type { RpcPeer } from './rpc-peer.js';\r\nimport type { RpcServiceDef, RpcMethodDef } from './rpc-service-def.js';\r\nimport { wireMethodName, RpcRemoteExecutionMode } from './rpc-service-def.js';\r\n\r\ntype RpcClientFn = (...args: unknown[]) => unknown;\r\n\r\n/** Creates a typed RPC client proxy — intercepts method calls and sends them over RPC. */\r\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T is used for caller-specified proxy type\r\nexport function createRpcClient<T extends object>(\r\n peer: RpcPeer,\r\n serviceDef: RpcServiceDef\r\n): T {\r\n // Group methods by clean name, indexed by argCount for overload resolution\r\n const overloads = new Map<string, Map<number, RpcMethodDef>>();\r\n for (const methodDef of serviceDef.methods.values()) {\r\n let byArgCount = overloads.get(methodDef.name);\r\n if (!byArgCount) {\r\n byArgCount = new Map<number, RpcMethodDef>();\r\n overloads.set(methodDef.name, byArgCount);\r\n }\r\n byArgCount.set(methodDef.argCount, methodDef);\r\n }\r\n\r\n // Build proxy methods\r\n const methods = new Map<string, RpcClientFn>();\r\n for (const [name, byArgCount] of overloads) {\r\n if (byArgCount.size === 1) {\r\n const [[, methodDef]] = byArgCount;\r\n methods.set(name, createMethod(peer, methodDef));\r\n } else {\r\n const fns = new Map<number, RpcClientFn>();\r\n for (const [argCount, methodDef] of byArgCount) {\r\n fns.set(argCount, createMethod(peer, methodDef));\r\n }\r\n methods.set(name, (...args: unknown[]) => {\r\n const fn = fns.get(args.length);\r\n if (!fn)\r\n throw new Error(\r\n `No overload of ${name} accepts ${args.length} arguments`\r\n );\r\n return fn(...args);\r\n });\r\n }\r\n }\r\n\r\n return new Proxy({} as T, {\r\n get(_target, prop) {\r\n if (typeof prop !== 'string') return undefined;\r\n return methods.get(prop);\r\n },\r\n });\r\n}\r\n\r\nfunction createMethod(peer: RpcPeer, methodDef: RpcMethodDef): RpcClientFn {\r\n const mode = methodDef.remoteExecutionMode;\r\n const mustCheckConnection = !(mode & RpcRemoteExecutionMode.AwaitForConnection);\r\n return async (...args: unknown[]) => {\r\n if (mustCheckConnection && !peer.isConnected)\r\n throw new Error('Outbound call failed: not connected and AwaitForConnection is not set.');\r\n const callArgs = args.slice(0, methodDef.argCount);\r\n const wireName = wireMethodName(methodDef);\r\n const options =\r\n (methodDef.callTypeId !== 0 || mode !== RpcRemoteExecutionMode.Default || methodDef.timeouts !== undefined)\r\n ? { callTypeId: methodDef.callTypeId, remoteExecutionMode: mode, timeouts: methodDef.timeouts }\r\n : undefined;\r\n const outboundCall = peer.call(wireName, callArgs, options);\r\n // Keep `.promise` here: this is the proxy returning a Promise to user\r\n // code. Returning the PromiseSource directly would expose `resolve()`/\r\n // `reject()` to anyone who happens to type the result as PromiseSource.\r\n return outboundCall.result.promise;\r\n };\r\n}\r\n","// .NET counterpart: ActualLab.Fusion.Extensions.RpcPeerState\r\n\r\nexport const enum RpcPeerStateKind {\r\n Connected = 0,\r\n JustDisconnected = 1,\r\n Disconnected = 2,\r\n JustConnected = 3,\r\n}\r\n\r\nexport interface RpcPeerState {\r\n readonly kind: RpcPeerStateKind;\r\n readonly lastError?: Error;\r\n readonly reconnectsIn: number; // ms, 0 = no countdown\r\n}\r\n\r\nexport function isConnected(state: RpcPeerState): boolean {\r\n return (\r\n state.kind === RpcPeerStateKind.Connected ||\r\n state.kind === RpcPeerStateKind.JustConnected\r\n );\r\n}\r\n\r\nexport function likelyConnected(state: RpcPeerState): boolean {\r\n return state.kind !== RpcPeerStateKind.Disconnected;\r\n}\r\n\r\nexport function getStateDescription(\r\n state: RpcPeerState,\r\n useLastError = false\r\n): string {\r\n switch (state.kind) {\r\n case RpcPeerStateKind.JustConnected:\r\n return 'Just connected.';\r\n case RpcPeerStateKind.Connected:\r\n return 'Connected.';\r\n case RpcPeerStateKind.JustDisconnected:\r\n return 'Just disconnected, reconnecting...';\r\n case RpcPeerStateKind.Disconnected:\r\n if (state.reconnectsIn > 0) break; // fall through to error message below\r\n return 'Reconnecting...';\r\n }\r\n if (state.lastError == null || !useLastError) return 'Disconnected.';\r\n\r\n let message = state.lastError.message.trim();\r\n if (\r\n !(\r\n message.endsWith('.') ||\r\n message.endsWith('!') ||\r\n message.endsWith('?')\r\n )\r\n )\r\n message += '.';\r\n return 'Disconnected: ' + message;\r\n}\r\n","// .NET counterpart: ActualLab.Fusion.Extensions.RpcPeerStateMonitor\n// Simplified TS version using EventHandlerSet + setTimeout (no Fusion computed state).\n\nimport { EventHandlerSet } from '@actuallab/core';\nimport { getLogs } from './logging.js';\nimport { RpcConnectionState, type RpcClientPeer } from './rpc-peer.js';\nimport { type RpcPeerState, RpcPeerStateKind } from './rpc-peer-state.js';\n\nconst { infoLog } = getLogs('RpcPeerStateMonitor');\n\nconst JustConnectedPeriodMs = 1500;\nconst JustDisconnectedPeriodMs = 3000;\nconst MinReconnectsInMs = 1000;\n\nexport class RpcPeerStateMonitor {\n readonly peer: RpcClientPeer;\n readonly stateChanged = new EventHandlerSet<RpcPeerState>();\n\n private _rawConnected: boolean;\n private _connectedAt = 0;\n private _disconnectedAt = 0;\n private _lastError: Error | undefined;\n private _state: RpcPeerState;\n private _timer: ReturnType<typeof setTimeout> | undefined;\n\n // Arrow functions to preserve `this` when used as event handlers\n private _onConnectionStateChanged = (state: RpcConnectionState): void => {\n if (state === RpcConnectionState.Connected) {\n this._rawConnected = true;\n this._connectedAt = Date.now();\n this._lastError = undefined;\n } else {\n const wasConnected = this._rawConnected;\n this._rawConnected = false;\n // We no longer receive a close code/reason — only the state.\n // Retain a generic error for UI hints.\n this._lastError = new Error('Connection closed');\n if (wasConnected) {\n // True connected→disconnected transition — start JustDisconnected period\n this._disconnectedAt = Date.now();\n }\n // When wasConnected=false, this is a failed reconnect attempt —\n // keep the original _disconnectedAt so the countdown isn't reset.\n }\n this._recompute();\n };\n\n private _onReconnectsAtChanged = (): void => {\n this._recompute();\n };\n\n private _onCancelDelaysChanged = (): void => {\n this._recompute();\n };\n\n constructor(peer: RpcClientPeer) {\n this.peer = peer;\n this._rawConnected = peer.isConnected;\n\n if (this._rawConnected) {\n this._connectedAt = Date.now();\n this._state = {\n kind: RpcPeerStateKind.JustConnected,\n reconnectsIn: 0,\n };\n } else {\n this._disconnectedAt = Date.now();\n this._state = {\n kind: RpcPeerStateKind.JustDisconnected,\n reconnectsIn: 0,\n };\n }\n\n peer.connectionStateChanged.add(this._onConnectionStateChanged);\n peer.reconnectsAtChanged.add(this._onReconnectsAtChanged);\n peer.hub.reconnectDelayer.cancelDelaysChanged.add(\n this._onCancelDelaysChanged\n );\n\n // Schedule initial transition from Just* states\n this._scheduleRecompute(\n this._rawConnected\n ? JustConnectedPeriodMs\n : JustDisconnectedPeriodMs\n );\n // Mirrors RpcPeerStateMonitor.cs:87 — \"monitor (re)started\".\n infoLog?.log(`'${peer.ref}': monitor started`);\n }\n\n get state(): RpcPeerState {\n return this._state;\n }\n\n dispose(): void {\n if (this._timer !== undefined) {\n clearTimeout(this._timer);\n this._timer = undefined;\n }\n this.peer.connectionStateChanged.remove(this._onConnectionStateChanged);\n this.peer.reconnectsAtChanged.remove(this._onReconnectsAtChanged);\n this.peer.hub.reconnectDelayer.cancelDelaysChanged.remove(\n this._onCancelDelaysChanged\n );\n // Mirrors RpcPeerStateMonitor.cs:125 — \"monitor stopped\".\n infoLog?.log(`'${this.peer.ref}': monitor stopped`);\n }\n\n private _recompute(): void {\n if (this._timer !== undefined) {\n clearTimeout(this._timer);\n this._timer = undefined;\n }\n\n const now = Date.now();\n let newState: RpcPeerState;\n\n if (this._rawConnected) {\n const connectedFor = now - this._connectedAt;\n if (connectedFor < JustConnectedPeriodMs) {\n newState = {\n kind: RpcPeerStateKind.JustConnected,\n reconnectsIn: 0,\n };\n this._scheduleRecompute(JustConnectedPeriodMs - connectedFor);\n } else {\n newState = {\n kind: RpcPeerStateKind.Connected,\n reconnectsIn: 0,\n };\n }\n } else {\n const disconnectedFor = now - this._disconnectedAt;\n if (disconnectedFor < JustDisconnectedPeriodMs) {\n newState = {\n kind: RpcPeerStateKind.JustDisconnected,\n lastError: this._lastError,\n reconnectsIn: 0,\n };\n this._scheduleRecompute(\n JustDisconnectedPeriodMs - disconnectedFor\n );\n } else {\n const reconnectsAt = this.peer.reconnectsAt;\n const reconnectsIn =\n reconnectsAt > 0 ? Math.max(0, reconnectsAt - now) : 0;\n if (reconnectsIn < MinReconnectsInMs) {\n newState = {\n kind: RpcPeerStateKind.Disconnected,\n lastError: this._lastError,\n reconnectsIn: 0,\n };\n } else {\n newState = {\n kind: RpcPeerStateKind.Disconnected,\n lastError: this._lastError,\n reconnectsIn,\n };\n // Recompute periodically to update countdown\n this._scheduleRecompute(\n Math.min(1000, reconnectsIn - MinReconnectsInMs)\n );\n }\n }\n }\n\n this._setState(newState);\n }\n\n private _setState(newState: RpcPeerState): void {\n const old = this._state;\n if (\n old.kind === newState.kind &&\n old.lastError === newState.lastError &&\n old.reconnectsIn === newState.reconnectsIn\n )\n return;\n\n this._state = newState;\n this.stateChanged.trigger(newState);\n }\n\n private _scheduleRecompute(delayMs: number): void {\n if (this._timer !== undefined) clearTimeout(this._timer);\n this._timer = setTimeout(\n () => {\n this._timer = undefined;\n this._recompute();\n },\n Math.max(0, delayMs)\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAA4C;AAmC5C,IAAM,gBAA+C;AAAA,EACjD,gBAAgB,qBAAS;AAAA,EACzB,WAAW,qBAAS;AAAA,EACpB,+BAA+B,qBAAS;AAAA,EACxC,eAAe,qBAAS;AAAA,EACxB,QAAQ,qBAAS;AAAA,EACjB,gBAAgB,qBAAS;AAAA,EACzB,6BAA6B,qBAAS;AAAA,EACtC,mBAAmB,qBAAS;AAAA,EAC5B,iBAAiB,qBAAS;AAAA,EAC1B,SAAS,qBAAS;AAAA,EAClB,qBAAqB,qBAAS;AAAA,EAC9B,wBAAwB,qBAAS;AAAA,EACjC,kBAAkB,qBAAS;AAAA,EAC3B,gBAAgB,qBAAS;AAAA,EACzB,wBAAwB,qBAAS;AAAA,EACjC,iBAAiB,qBAAS;AAAA,EAC1B,WAAW,qBAAS;AAAA,EACpB,iBAAiB,qBAAS;AAAA,EAC1B,sBAAsB,qBAAS;AAAA,EAC/B,qBAAqB,qBAAS;AAAA,EAC9B,oBAAoB,qBAAS;AACjC;AAEO,IAAM,cAAU,+BAA+B,QAAQ,aAAa;;;AC5BpE,IAAW,gBAAX,kBAAWA,mBAAX;AACH,EAAAA,8BAAA,aAAU,KAAV;AACA,EAAAA,8BAAA,cAAW,KAAX;AAFc,SAAAA;AAAA,GAAA;AAMX,IAAM,iBAAiB;AAAA,EAC1B,WAAW;AAAA,EACX,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,WAAW;AAAA,EACX,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AAAA,EACP,KAAK;AAAA,EACL,YAAY;AAChB;AAWO,IAAM,qBAAqB;AAC3B,IAAM,gBAAgB;AACtB,IAAM,kBAAkB;;;ACvD/B,IAAAC,kBAGO;;;ACXP,qBAAwB;AAuBjB,SAAS,6BAAmC;AAC/C,QAAM,QAAQ,uBAAQ;AACtB,MAAI,MAAM,aAAc;AACxB,QAAM,eAAe;AAErB,QAAM,mBAAmB,MAAM;AAE/B,QAAM,WAAW,SAAyB,QAAiB,OAAqB;AAC5E,QAAI,kBAAkB,KAAK;AACvB,yBAAmB,KAAK,MAAM,QAAQ,KAAK;AAC3C;AAAA,IACJ;AACA,qBAAiB,KAAK,MAAM,QAAQ,KAAK;AAAA,EAC7C;AAEA,WAAS,mBAAkC,GAA0B,OAAqB;AACtF,UAAM,OAAO;AACb,UAAM,OAAO,EAAE;AACf,QAAI,OAAO,IAAI;AACX,WAAK,QAAQ,MAAO,IAAI;AAAA,IAC5B,WAAW,OAAO,OAAS;AACvB,WAAK,QAAQ,GAAI;AACjB,WAAK,SAAS,IAAI;AAAA,IACtB,OAAO;AACH,WAAK,QAAQ,GAAI;AACjB,WAAK,SAAS,IAAI;AAAA,IACtB;AACA,eAAW,CAAC,KAAK,KAAK,KAAK,GAAG;AAE1B,WAAK,SAAS,KAAK,QAAQ,CAAC;AAC5B,WAAK,SAAS,OAAO,QAAQ,CAAC;AAAA,IAClC;AAAA,EACJ;AACJ;AAOA,2BAA2B;;;ADxC3B,IAAM,EAAE,QAAQ,IAAI,QAAQ,kBAAkB;AAOvC,SAAS,iBACZ,SACA,MACM;AACN,QAAM,WAAW,KAAK,UAAU,OAAO;AACvC,MAAI,SAAS,UAAa,KAAK,WAAW,GAAG;AACzC,WAAO,WAAW;AAAA,EACtB;AACA,QAAM,UAAU,KAAK,IAAI,OAAK,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,aAAa;AACnE,SAAO,WAAW,qBAAqB;AAC3C;AAGO,SAAS,eAAe,UAA4B;AACvD,SAAO,SAAS,KAAK,eAAe;AACxC;AAGO,SAAS,WAAW,OAAyB;AAChD,SAAO,MAAM,MAAM,eAAe;AACtC;AAGO,SAAS,mBAAmB,KAGjC;AACE,QAAM,UAAU,IAAI,QAAQ,kBAAkB;AAC9C,MAAI,YAAY,IAAI;AAChB,WAAO,EAAE,SAAS,KAAK,MAAM,GAAG,GAAiB,MAAM,CAAC,EAAE;AAAA,EAC9D;AAEA,QAAM,cAAc,IAAI,UAAU,GAAG,OAAO;AAC5C,QAAM,UAAU,IAAI,UAAU,UAAU,CAAC;AACzC,QAAM,UAAU,KAAK,MAAM,WAAW;AAEtC,MAAI,QAAQ,WAAW,GAAG;AACtB,WAAO,EAAE,SAAS,MAAM,CAAC,EAAE;AAAA,EAC/B;AAEA,QAAM,OAAO,QAAQ,MAAM,aAAa,EAAE,IAAI,OAAK;AAC/C,QAAI;AACA,aAAO,KAAK,MAAM,CAAC;AAAA,IACvB,QAAQ;AACJ,eAAS,IAAI,gCAAgC,EAAE,UAAU,GAAG,GAAG,CAAC,EAAE;AAClE,aAAO;AAAA,IACX;AAAA,EACJ,CAAC;AAED,SAAO,EAAE,SAAS,KAAK;AAC3B;AAMA,IAAM,cAAc,IAAI,YAAY;AACpC,IAAM,cAAc,IAAI,YAAY;AA4BpC,IAAM,8BAA8B,KAAK;AAClC,IAAM,uBAAuB,IAAI,wBAAQ,EAAE,mBAAmB,6BAA6B,aAAa,KAAK,CAAC;AAC9G,IAAM,uBAAuB,IAAI,wBAAQ;AAIzC,SAAS,sBAA+B;AAC3C,SAAO,IAAI,wBAAQ,EAAE,mBAAmB,6BAA6B,aAAa,KAAK,CAAC;AAC5F;AAKA,SAAS,eAAe,OAAuB;AAC3C,MAAI,QAAQ,EAAG,SAAQ;AACvB,MAAI,IAAI;AACR,KAAG;AACC;AACA,eAAW;AAAA,EACf,SAAS,QAAQ;AACjB,SAAO;AACX;AAGA,SAAS,iBAAiB,KAAiB,KAAa,OAAuB;AAC3E,MAAI,QAAQ,EAAG,SAAQ;AACvB,KAAG;AACC,QAAI,OAAO,QAAQ;AACnB,eAAW;AACX,QAAI,QAAQ,EAAG,SAAQ;AACvB,QAAI,KAAK,IAAI;AAAA,EACjB,SAAS,QAAQ;AACjB,SAAO;AACX;AAEA,SAAS,YACL,MACA,QACoC;AACpC,MAAI,QAAQ,GACR,QAAQ,GACR,YAAY;AAChB,KAAG;AACC,QAAI,SAAS,aAAa,KAAK,OAAQ;AACvC,UAAM,OAAO,KAAK,SAAS,SAAS;AACpC,cAAU,OAAO,QAAS;AAC1B;AACA,SAAK,OAAO,SAAU,EAAG;AACzB,aAAS;AAAA,EACb,SAAS,QAAQ;AACjB,SAAO,EAAE,OAAO,UAAU,GAAG,UAAU;AAC3C;AASA,IAAM,2BAA2B;AACjC,IAAM,mCAAmC;AAKzC,SAAS,gBAAgB,MAAkB,KAAa,QAAgB,MAAsB;AAC1F,QAAM,MAAM,MAAM;AAClB,MAAI,CAAC,OAAO,cAAc,MAAM,KAAK,SAAS,KAAK,MAAM,KAAK;AAC1D,UAAM,IAAI,MAAM,+BAA+B,IAAI,WAAW,MAAM,OAAO,GAAG,oBAAoB;AAEtG,SAAO;AACX;AAMA,SAAS,YAAY,MAAkB,KAAa,aAA6B;AAC7E,WAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AAClC,QAAI,OAAO,KAAK;AACZ,YAAM,IAAI,MAAM,+CAA+C;AAEnE,UAAM,SAAS,KAAK,KAAK;AACzB,UAAM,gBAAgB,MAAM,KAAK,QAAQ,YAAY;AACrD,UAAM,SAAS,YAAY,MAAM,GAAG;AACpC,QAAI,OAAO,cAAc;AACrB,YAAM,IAAI,MAAM,4DAA4D;AAEhF,UAAM,gBAAgB,MAAM,MAAM,OAAO,WAAW,OAAO,OAAO,cAAc;AAAA,EACpF;AACA,SAAO;AACX;AAcO,SAAS,uBACZ,MACA,QACsC;AACtC,QAAM,OAAO,IAAI,SAAS,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;AACvE,QAAM,SAAS,KAAK,UAAU,QAAQ,IAAI;AAC1C,MAAI,WAAW;AACX,WAAO,EAAE,WAAW,GAAG,QAAQ,MAAM;AACzC,MAAI,WAAW;AACX,WAAO,EAAE,WAAW,GAAG,QAAQ,KAAK;AAExC,QAAM,IAAI,MAAM,yDAAyD;AAC7E;AAEA,SAAS,kBAAkB,QAAkC;AACzD,MAAI,cAAc;AAClB,aAAW,OAAO,OAAQ,gBAAe,IAAI;AAC7C,QAAM,SAAS,IAAI,WAAW,WAAW;AACzC,MAAI,SAAS;AACb,aAAW,OAAO,QAAQ;AACtB,WAAO,IAAI,KAAK,MAAM;AACtB,cAAU,IAAI;AAAA,EAClB;AACA,SAAO;AACX;AA4BO,SAAS,uBACZ,SACA,MACA,UAAmB,sBACT;AAGV,MAAI,cAAc;AAClB,MAAI,UAA+B;AACnC,MAAI,QAAQ,KAAK,SAAS,GAAG;AACzB,cAAU,IAAI,MAAkB,KAAK,MAAM;AAC3C,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AAClC,YAAM,MAAM,QAAQ,OAAO,KAAK,CAAC,CAAC;AAClC,cAAQ,CAAC,IAAI;AACb,qBAAe,IAAI;AAAA,IACvB;AAAA,EACJ;AAGA,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,cAAc,YAAY,OAAO,QAAQ,UAAU,EAAE;AAC3D,QAAM,eAAe,eAAe,SAAS;AAC7C,QAAM,qBAAqB,eAAe,YAAY,MAAM;AAC5D,QAAM,aACF,IAAI,eAAe,qBAAqB,YAAY;AACxD,QAAM,YAAY,aAAa,IAAI;AAGnC,QAAM,MAAM,IAAI,WAAW,SAAS;AACpC,MAAI,MAAM;AACV,MAAI,KAAK,IAAK,YAAY,IAAK;AAC/B,QAAM,iBAAiB,KAAK,KAAK,SAAS;AAC1C,QAAM,iBAAiB,KAAK,KAAK,YAAY,MAAM;AACnD,MAAI,IAAI,aAAa,GAAG;AACxB,SAAO,YAAY;AAEnB,MAAI,KAAK,IAAI,cAAc;AAC3B,MAAI,KAAK,IAAK,gBAAgB,IAAK;AACnC,MAAI,KAAK,IAAK,gBAAgB,KAAM;AACpC,MAAI,KAAK,IAAK,gBAAgB,KAAM;AACpC,MAAI,YAAY,MAAM;AAClB,eAAW,OAAO,SAAS;AACvB,UAAI,IAAI,KAAK,GAAG;AAChB,aAAO,IAAI;AAAA,IACf;AAAA,EACJ;AACA,SAAO;AACX;AAUO,SAAS,yBACZ,MACA,QACA,UAAmB,sBACwC;AAC3D,QAAM,OAAO,IAAI,SAAS,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;AACvE,MAAI,SAAS,2BAA2B,KAAK;AACzC,UAAM,IAAI,MAAM,qDAAqD,MAAM,GAAG;AAGlF,MAAI,MAAM;AAGV,QAAM,QAAQ,KAAK,KAAK;AACxB,QAAM,aAAc,SAAS,IAAK;AAClC,QAAM,cAAc,QAAQ;AAG5B,QAAM,QAAQ,YAAY,MAAM,GAAG;AACnC,SAAO,MAAM;AAGb,QAAM,YAAY,YAAY,MAAM,GAAG;AACvC,SAAO,UAAU;AACjB,QAAM,YAAY,gBAAgB,MAAM,KAAK,UAAU,OAAO,QAAQ;AACtE,QAAM,SAAS,YAAY,OAAO,KAAK,SAAS,KAAK,SAAS,CAAC;AAC/D,QAAM;AAMN,MAAI,MAAM,IAAI,KAAK;AACf,UAAM,IAAI,MAAM,iEAAiE,GAAG,GAAG;AAE3F,QAAM,aAAa,KAAK,UAAU,KAAK,IAAI;AAC3C,SAAO;AAGP,QAAM,OAAkB,CAAC;AACzB,QAAM,SAAS,gBAAgB,MAAM,KAAK,YAAY,eAAe;AACrE,MAAI,aAAa,GAAG;AAChB,UAAM,WAAW,KAAK,SAAS,KAAK,MAAM;AAI1C,eAAW,WAAW,QAAQ,YAAY,QAAQ,GAAG;AACjD,WAAK,KAAK,OAAO;AAAA,IACrB;AAAA,EACJ;AACA,QAAM;AAIN,MAAI,cAAc;AACd,UAAM,YAAY,MAAM,KAAK,WAAW;AAE5C,QAAM,UAAsB;AAAA,IACxB,QAAQ;AAAA,IACR,WAAW,MAAM;AAAA,IACjB,UAAU;AAAA,EACd;AAEA,SAAO,EAAE,SAAS,MAAM,WAAW,MAAM,OAAO;AACpD;AAgBO,SAAS,iBACZ,OACA,UAAmB,sBACuB;AAC1C,QAAM,UAAsD,CAAC;AAC7D,MAAI,SAAS;AACb,SAAO,SAAS,MAAM,QAAQ;AAC1B,QAAI;AACJ,QAAI;AACA,eAAS,yBAAyB,OAAO,QAAQ,OAAO;AAAA,IAC5D,SACO,GAAG;AAKN,eAAS,IAAI,8CAA8C,MAAM,KAAK,CAAC;AACvE;AAAA,IACJ;AACA,UAAM,EAAE,SAAS,MAAM,UAAU,IAAI;AAGrC,QAAI,CAAC,OAAO,SAAS,SAAS,KAAK,YAAY;AAC3C;AAEJ,YAAQ,KAAK,EAAE,SAAS,KAAK,CAAC;AAC9B,cAAU;AAAA,EACd;AACA,SAAO;AACX;AAMO,SAAS,qBAAqB,UAAoC;AACrE,SAAO,kBAAkB,QAAQ;AACrC;AAkBO,SAAS,8BACZ,SACA,MACA,UACA,UAAmB,sBACT;AAEV,MAAI,cAAc;AAClB,MAAI,UAA+B;AACnC,MAAI,QAAQ,KAAK,SAAS,GAAG;AACzB,cAAU,IAAI,MAAkB,KAAK,MAAM;AAC3C,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AAClC,YAAM,MAAM,QAAQ,OAAO,KAAK,CAAC,CAAC;AAClC,cAAQ,CAAC,IAAI;AACb,qBAAe,IAAI;AAAA,IACvB;AAAA,EACJ;AAGA,QAAM,aAAa,QAAQ,UAAU;AACrC,QAAM,aAAa,UAAU,YAAY,UAAU,KAAK;AAGxD,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,eAAe,eAAe,SAAS;AAE7C,QAAM,aAAa,IAAI,eAAe;AACtC,QAAM,YAAY,aAAa,IAAI;AAGnC,QAAM,MAAM,IAAI,WAAW,SAAS;AACpC,MAAI,MAAM;AACV,MAAI,KAAK,IAAK,YAAY,IAAK;AAC/B,QAAM,iBAAiB,KAAK,KAAK,SAAS;AAE1C,QAAM,UAAU,eAAe;AAC/B,MAAI,KAAK,IAAI,UAAU;AACvB,MAAI,KAAK,IAAK,YAAY,IAAK;AAC/B,MAAI,KAAK,IAAK,YAAY,KAAM;AAChC,MAAI,KAAK,IAAK,YAAY,KAAM;AAEhC,MAAI,KAAK,IAAI,cAAc;AAC3B,MAAI,KAAK,IAAK,gBAAgB,IAAK;AACnC,MAAI,KAAK,IAAK,gBAAgB,KAAM;AACpC,MAAI,KAAK,IAAK,gBAAgB,KAAM;AACpC,MAAI,YAAY,MAAM;AAClB,eAAW,OAAO,SAAS;AACvB,UAAI,IAAI,KAAK,GAAG;AAChB,aAAO,IAAI;AAAA,IACf;AAAA,EACJ;AACA,SAAO;AACX;AAMO,SAAS,gCACZ,MACA,QACA,UACA,UAAmB,sBACwC;AAC3D,QAAM,OAAO,IAAI,SAAS,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;AACvE,MAAI,SAAS,mCAAmC,KAAK;AACjD,UAAM,IAAI,MAAM,6DAA6D,MAAM,GAAG;AAE1F,MAAI,MAAM;AAGV,QAAM,QAAQ,KAAK,KAAK;AACxB,QAAM,aAAc,SAAS,IAAK;AAClC,QAAM,cAAc,QAAQ;AAG5B,QAAM,QAAQ,YAAY,MAAM,GAAG;AACnC,SAAO,MAAM;AAGb,MAAI,MAAM,IAAI,KAAK;AACf,UAAM,IAAI,MAAM,6DAA6D,MAAM,GAAG;AAE1F,QAAM,aAAa,KAAK,UAAU,KAAK,IAAI;AAC3C,SAAO;AACP,QAAM,SACF,UAAU,QAAQ,aAAa,CAAC,KAChC,WAAW,WAAW,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC;AAKvD,QAAM,aAAa,KAAK,UAAU,KAAK,IAAI;AAC3C,SAAO;AAGP,QAAM,OAAkB,CAAC;AACzB,QAAM,SAAS,gBAAgB,MAAM,KAAK,YAAY,eAAe;AACrE,MAAI,aAAa,GAAG;AAChB,UAAM,WAAW,KAAK,SAAS,KAAK,MAAM;AAC1C,eAAW,WAAW,QAAQ,YAAY,QAAQ,GAAG;AACjD,WAAK,KAAK,OAAO;AAAA,IACrB;AAAA,EACJ;AACA,QAAM;AAGN,MAAI,cAAc;AACd,UAAM,YAAY,MAAM,KAAK,WAAW;AAE5C,QAAM,UAAsB;AAAA,IACxB,QAAQ;AAAA,IACR,WAAW,MAAM;AAAA,IACjB,UAAU;AAAA,EACd;AAEA,SAAO,EAAE,SAAS,MAAM,WAAW,MAAM,OAAO;AACpD;AAKO,SAAS,wBACZ,OACA,UACA,UAAmB,sBACuB;AAC1C,QAAM,UAAsD,CAAC;AAC7D,MAAI,SAAS;AACb,SAAO,SAAS,MAAM,QAAQ;AAC1B,QAAI;AACJ,QAAI;AACA,eAAS,gCAAgC,OAAO,QAAQ,UAAU,OAAO;AAAA,IAC7E,SACO,GAAG;AACN,eAAS,IAAI,sDAAsD,MAAM,KAAK,CAAC;AAC/E;AAAA,IACJ;AACA,UAAM,EAAE,SAAS,MAAM,UAAU,IAAI;AACrC,QAAI,CAAC,OAAO,SAAS,SAAS,KAAK,YAAY;AAC3C;AAEJ,YAAQ,KAAK,EAAE,SAAS,KAAK,CAAC;AAC9B,cAAU;AAAA,EACd;AACA,SAAO;AACX;;;AE7kBA,IAAAC,kBAAiC;AACjC,IAAAC,eAA+C;AAa/C,IAAM,EAAE,SAAAC,SAAQ,IAAI,QAAQ,eAAe;AAcpC,IAAM,iBAAiB;AAAA,EAC1B,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,SAAS;AAAA,EACT,QAAQ;AACZ;AAyBO,IAAM,yBAAN,MAAsD;AAAA,EACjD;AAAA,EACA,cAAuC,CAAC;AAAA,EACxC,aAAa,IAAI,2BAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMrC,WAAoB,oBAAoB;AAAA;AAAA;AAAA,EAIxC,WAAoB,IAAI,wBAAQ;AAAA;AAAA;AAAA;AAAA,EAKxC,IAAI,UAAmB;AACnB,WAAO,KAAK;AAAA,EAChB;AAAA,EACA,IAAI,UAAmB;AACnB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEQ;AAAA,EACA;AAAA,EAEC;AAAA,EACA,kBAAkB,IAAI,6BAAoC;AAAA,EAC1D,SAAS,IAAI,6BAAkD;AAAA,EAC/D,QAAQ,IAAI,6BAAyB;AAAA,EAE9C,YACI,IACA,aAAa,OACb,QACA,gBACF;AACE,SAAK,MAAM;AACX,SAAK,aAAa;AAClB,SAAK,UAAU;AACf,SAAK,kBAAkB;AAEvB,QAAI,cAAc,GAAG,eAAe;AAChC,SAAG,aAAa;AAEpB,QAAI,GAAG,eAAe,eAAe,MAAM;AACvC,WAAK,WAAW,QAAQ;AACxB,WAAK,OAAO;AAAA,IAChB;AAEA,OAAG,SAAS,MAAM;AACd,WAAK,WAAW,QAAQ;AACxB,WAAK,OAAO;AAAA,IAChB;AAEA,OAAG,YAAY,QAAM;AAUjB,UACI,GAAG,gBAAgB,cACnB,GAAG,gBAAgB,aACrB;AAKE,cAAM,QACF,GAAG,gBAAgB,aACb,GAAG,OACH,IAAI,WAAW,GAAG,IAAI;AAChC,YAAI;AACJ,YAAI;AACA,qBAAW,KAAK,aAAa,KAAK;AAAA,QACtC,SAAS,GAAG;AACR,UAAAA,UAAS,IAAI,iCAAiC,CAAC;AAC/C,eAAK,MAAM,QAAQ,CAAC;AACpB;AAAA,QACJ;AACA,mBAAW,EAAE,SAAS,KAAK,KAAK;AAC5B,eAAK,gBAAgB,SAAS,IAAI;AAAA,MAC1C,WACI,OAAO,SAAS,eAChB,GAAG,gBAAgB,QACnB,KAAK,YACP;AAIE,aAAK,GAAG,KAAK,YAAY,EAAE,KAAK,QAAM;AAClC,gBAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,cAAI;AACJ,cAAI;AACA,uBAAW,KAAK,aAAa,KAAK;AAAA,UACtC,SAAS,GAAG;AACR,YAAAA,UAAS,IAAI,sCAAsC,CAAC;AACpD,iBAAK,MAAM,QAAQ,CAAC;AACpB;AAAA,UACJ;AACA,qBAAW,EAAE,SAAS,KAAK,KAAK;AAC5B,iBAAK,gBAAgB,SAAS,IAAI;AAAA,QAC1C,CAAC;AAAA,MACL,OAAO;AAEH,cAAM,OACF,OAAO,GAAG,SAAS,WAAW,GAAG,OAAO,OAAO,GAAG,IAAI;AAC1D,mBAAW,OAAO,WAAW,IAAI;AAC7B,cAAI,IAAI,SAAS;AACb,iBAAK,gBAAgB;AAAA,cACjB,EAAE,MAAM,QAAQ,KAAK,IAAI;AAAA,cACzB,OAAKA,UAAS,IAAI,oCAAoC,CAAC;AAAA,YAAC;AAAA,MACxE;AAAA,IACJ;AAEA,OAAG,UAAU,QAAM;AACf,WAAK,OAAO,QAAQ,EAAE,MAAM,GAAG,MAAM,QAAQ,GAAG,OAAO,CAAC;AAAA,IAC5D;AAEA,OAAG,UAAU,QAAM;AACf,WAAK,MAAM,QAAQ,EAAE;AAAA,IACzB;AAAA,EACJ;AAAA,EAEA,IAAI,SAAkB;AAClB,WAAO,KAAK,IAAI,eAAe,eAAe;AAAA,EAClD;AAAA,EAEA,IAAI,gBAA+B;AAC/B,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,KAAK,mBAAiC;AAClC,SAAK,SAAS,iBAAiB;AAAA,EACnC;AAAA,EAEA,WAAW,MAAwB;AAC/B,SAAK,SAAS,IAAI;AAAA,EACtB;AAAA,EAEA,cAAc,UAA0B;AACpC,SAAK,SAAS,eAAe,QAAQ,CAAC;AAAA,EAC1C;AAAA,EAEA,gBAAgB,UAA8B;AAC1C,SAAK,SAAS,qBAAqB,QAAQ,CAAC;AAAA,EAChD;AAAA;AAAA,EAGQ,aACJ,OAC0C;AAC1C,QAAI,KAAK,SAAS;AACd,aAAO,KAAK,QAAQ;AAAA,QAChB;AAAA,QACA,KAAK;AAAA,QACL,KAAK;AAAA,MACT;AAAA,IACJ;AACA,WAAO,iBAAmB,OAAO,KAAK,QAAQ;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,SAAqB,MAAuB;AAChE,SAAK,gBAAgB;AAAA,MACjB,EAAE,MAAM,UAAU,SAAS,KAAK;AAAA,MAChC,OAAKA,UAAS,IAAI,sCAAsC,CAAC;AAAA,IAAC;AAAA,EAClE;AAAA,EAEA,MAAM,MAAe,QAAuB;AACxC,SAAK,IAAI,MAAM,MAAM,MAAM;AAAA,EAC/B;AAAA,EAEQ,SAAS,MAAiC;AAC9C,QAAI;AACA,UAAI,KAAK,IAAI,eAAe,eAAe,MAAM;AAC7C,aAAK,IAAI,KAAK,IAAI;AAAA,MACtB,WAAW,KAAK,IAAI,eAAe,eAAe;AAC9C,aAAK,YAAY,KAAK,IAAI;AAAA,IAElC,QAAQ;AAAA,IAER;AAAA,EACJ;AAAA,EAEQ,SAAe;AACnB,QAAI,KAAK,YAAY,WAAW,EAAG;AACnC,UAAM,SAAS,KAAK;AACpB,SAAK,cAAc,CAAC;AACpB,QAAI;AACA,iBAAW,QAAQ,OAAQ,MAAK,IAAI,KAAK,IAAI;AAAA,IACjD,QAAQ;AAAA,IAER;AAAA,EACJ;AACJ;;;ACvRA,IAAAC,eAAgC;AAKhC,IAAM,EAAE,SAAAC,SAAQ,IAAI,QAAQ,6BAA6B;AAGlD,IAAM,8BAAN,MAA2D;AAAA,EACtD;AAAA,EACA,QAAQ;AAAA,EAEP,aAAa;AAAA,EACb,kBAAkB,IAAI,6BAAoC;AAAA,EAC1D,SAAS,IAAI,6BAAkD;AAAA,EAC/D,gBAA+B,QAAQ,QAAQ;AAAA;AAAA,EAExD,YAAY,MAAmB;AAC3B,SAAK,QAAQ;AACb,SAAK,YAAY,CAAC,OAAqB;AACnC,YAAM,OACF,OAAO,GAAG,SAAS,WAAW,GAAG,OAAO,OAAO,GAAG,IAAI;AAG1D,iBAAW,OAAO,WAAW,IAAI;AAC7B,YAAI,IAAI,SAAS;AACb,eAAK,gBAAgB;AAAA,YACjB,EAAE,MAAM,QAAQ,KAAK,IAAI;AAAA,YACzB,OAAKA,UAAS,IAAI,oCAAoC,CAAC;AAAA,UAAC;AAAA,IACxE;AAAA,EACJ;AAAA,EAEA,IAAI,SAAkB;AAClB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,KAAK,mBAAiC;AAClC,QAAI,CAAC,KAAK,MAAO;AACjB,QAAI;AACA,WAAK,MAAM,YAAY,iBAAiB;AAAA,IAC5C,QAAQ;AAAA,IAER;AAAA,EACJ;AAAA,EAEA,WAAW,MAAwB;AAC/B,QAAI,CAAC,KAAK,MAAO;AACjB,QAAI;AACA,WAAK,MAAM,YAAY,MAAM,CAAC,KAAK,MAAM,CAAC;AAAA,IAC9C,QAAQ;AAAA,IAER;AAAA,EACJ;AAAA,EAEA,MAAM,MAAe,QAAuB;AACxC,QAAI,CAAC,KAAK,MAAO;AACjB,SAAK,QAAQ;AACb,SAAK,MAAM,MAAM;AACjB,SAAK,OAAO,QAAQ,EAAE,MAAM,QAAQ,KAAM,QAAQ,UAAU,GAAG,CAAC;AAAA,EACpE;AACJ;AAGO,SAAS,2BAGV;AACF,QAAM,UAAU,IAAI,eAAe;AACnC,SAAO;AAAA,IACH,IAAI,4BAA4B,QAAQ,KAAK;AAAA,IAC7C,IAAI,4BAA4B,QAAQ,KAAK;AAAA,EACjD;AACJ;;;ACzCO,IAAM,yBAAyB;AAAA;AAAA,EAElC,oBAAoB;AAAA;AAAA,EAEpB,gBAAgB;AAAA;AAAA,EAEhB,aAAa;AAAA;AAAA;AAAA,EAEb,SAAS;AAAA;AACb;AAoCO,IAAM,UAAU;AAAA,EACnB,QAAQ,uBAAO,QAAQ;AAAA,EACvB,QAAQ,uBAAO,QAAQ;AAAA,EACvB,QAAQ,uBAAO,QAAQ;AAAA,EACvB,MAAM,uBAAO,MAAM;AACvB;AAGO,SAAS,eAAe,KAA2B;AACtD,SAAO,GAAG,IAAI,WAAW,IAAI,IAAI,IAAI,IAAI,IAAI,YAAY;AAC7D;AAEO,SAAS,iBACZ,MACA,SACa;AACb,QAAM,MAAM,oBAAI,IAA0B;AAC1C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAIhD,UAAM,aAAa,IAAI,QAAQ,GAAG;AAClC,UAAM,YAAY,cAAc,IAAI,IAAI,UAAU,GAAG,UAAU,IAAI;AACnE,UAAM,eAAe,MAAM,gBAAgB,MAAM,KAAK,SAAS;AAC/D,UAAM,SAAS,GAAG,SAAS,IAAI,YAAY;AAC3C,UAAM,WAAW,MAAM,YAAY,QAAQ;AAC3C,QAAI,IAAI,QAAQ;AAAA,MACZ,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU,MAAM,KAAK;AAAA,MACrB;AAAA,MACA,YAAY,MAAM,cAAc;AAAA,MAChC,QAAQ,MAAM,YAAY,QAAQ;AAAA,MAClC,QAAQ;AAAA,MACR,qBAAqB,WACf,IACC,MAAM,uBAAuB,uBAAuB;AAAA,MAC3D,UAAU,MAAM;AAAA,IACpB,CAAC;AAAA,EACL;AACA,SAAO,EAAE,MAAM,SAAS,IAAI;AAChC;;;ACvEA,IAAAC,eAA8B;;;AC7CvB,IAAM,eAAe;AAAA;AAAA,EAExB,aAAa;AAAA;AAAA,EAEb,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA,EAKb,cAAc;AAClB;;;ADyCO,IAAM,kBAAN,MAAsB;AAAA,EAChB;AAAA,EACA;AAAA,EACA,SAAS,IAAI,2BAAuB;AAAA;AAAA,EAEpC;AAAA;AAAA;AAAA,EAIA,aAAsB;AAAA;AAAA,EAG/B,qBAA0C;AAAA;AAAA;AAAA,EAI1C;AAAA;AAAA,EAGS,YAAY,KAAK,IAAI;AAAA;AAAA;AAAA,EAI9B,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYT,iBAAiB;AAAA,EAEjB,YAAY,QAAgB,QAAgB,sBAAsB,GAAG;AACjE,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,sBAAsB;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,kBAAkB,eAAuC;AACrD,UAAM,OAAO,KAAK;AAClB,QAAI,EAAE,OAAO,uBAAuB,gBAAiB,QAAO;AAC5D,QAAI,iBAAiB,EAAE,OAAO,uBAAuB,aAAc,QAAO;AAC1E,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA,EAGA,eAAqB;AAAA,EAErB;AACJ;AAGO,IAAM,yBAAN,MAA6B;AAAA,EACxB,SAAS,oBAAI,IAA6B;AAAA,EAC1C,UAAU;AAAA,EAElB,IAAI,OAAe;AACf,WAAO,KAAK,OAAO;AAAA,EACvB;AAAA,EAEA,SAAiB;AACb,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,SAAS,MAA6B;AAClC,SAAK,OAAO,IAAI,KAAK,QAAQ,IAAI;AAAA,EACrC;AAAA,EAEA,IAAI,QAA6C;AAC7C,WAAO,KAAK,OAAO,IAAI,MAAM;AAAA,EACjC;AAAA,EAEA,OAAO,QAA6C;AAChD,UAAM,OAAO,KAAK,OAAO,IAAI,MAAM;AACnC,QAAI,SAAS,QAAW;AACpB,WAAK,OAAO,OAAO,MAAM;AACzB,WAAK,kBAAkB,aAAa;AAAA,IACxC;AACA,WAAO;AAAA,EACX;AAAA,EAEA,SAA4C;AACxC,WAAO,KAAK,OAAO,OAAO;AAAA,EAC9B;AAAA,EAEA,gBAA0B;AACtB,WAAO,CAAC,GAAG,KAAK,OAAO,KAAK,CAAC;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAkC;AAC9B,UAAM,QAA2B,CAAC;AAClC,eAAW,QAAQ,KAAK,OAAO,OAAO;AAClC,UAAI,KAAK,WAAW;AAChB,cAAM,KAAK,IAAI;AACvB,WAAO;AAAA,EACX;AAAA;AAAA;AAAA,EAIA,UAAU,OAAoB;AAC1B,eAAW,CAAC,IAAI,IAAI,KAAK,KAAK,QAAQ;AAClC,UAAI,CAAC,KAAK,cAAc,KAAK,OAAO,aAAa;AAE7C;AAAA,MACJ;AACA,WAAK,OAAO,OAAO,KAAK;AACxB,WAAK,aAAa;AAClB,WAAK,OAAO,OAAO,EAAE;AAAA,IACzB;AAAA,EACJ;AAAA;AAAA,EAGA,gBAAsB;AAClB,eAAW,QAAQ,KAAK,OAAO,OAAO,GAAG;AACrC,WAAK,aAAa;AAAA,IACtB;AACA,SAAK,OAAO,MAAM;AAAA,EACtB;AAAA,EAEA,QAAc;AACV,SAAK,OAAO,MAAM;AAAA,EACtB;AACJ;AAGO,IAAM,iBAAN,MAAqB;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAET,iBAAiB;AAAA,EACT;AAAA,EACS,mBAAmB,IAAI,gBAAgB;AAAA,EAExD,YAAY,QAAgB,QAAgB,MAAiB;AACzD,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EAChB;AAAA;AAAA;AAAA,EAIA,IAAI,SAAsB;AACtB,WAAO,KAAK,iBAAiB;AAAA,EACjC;AAAA;AAAA;AAAA,EAIA,IAAI,cAAuB;AACvB,WAAO,KAAK,iBAAiB,OAAO;AAAA,EACxC;AAAA,EAEA,SAAe;AACX,SAAK,iBAAiB,MAAM;AAAA,EAChC;AAAA;AAAA;AAAA,EAIA,UAAU,QAA0B;AAChC,SAAK,UAAU;AACf,SAAK,iBAAiB;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAqB;AACjB,QAAI,KAAK,kBAAkB;AACvB,WAAK,UAAU;AAAA,EACvB;AACJ;AAGO,IAAM,wBAAN,MAA4B;AAAA,EACvB,SAAS,oBAAI,IAA4B;AAAA,EACzC,gBAA0B,CAAC;AAAA;AAAA,EAGnC,sBAAsB;AAAA,EAEtB,IAAI,OAAe;AACf,WAAO,KAAK,OAAO;AAAA,EACvB;AAAA,EAEA,SAAS,MAA4B;AACjC,SAAK,OAAO,IAAI,KAAK,QAAQ,IAAI;AAAA,EACrC;AAAA;AAAA;AAAA,EAIA,cAAc,MAAsC;AAChD,UAAM,WAAW,KAAK,OAAO,IAAI,KAAK,MAAM;AAC5C,QAAI,aAAa;AACb,aAAO;AAEX,SAAK,OAAO,IAAI,KAAK,QAAQ,IAAI;AACjC,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc,MAA4B;AACtC,QAAI,KAAK,OAAO,IAAI,KAAK,MAAM,MAAM;AACjC;AAEJ,SAAK,cAAc,KAAK,KAAK,MAAM;AACnC,WAAO,KAAK,cAAc,SAAS,KAAK,qBAAqB;AACzD,YAAM,KAAK,KAAK,cAAc,MAAM;AACpC,WAAK,OAAO,OAAO,EAAE;AAAA,IACzB;AAAA,EACJ;AAAA,EAEA,IAAI,QAA4C;AAC5C,WAAO,KAAK,OAAO,IAAI,MAAM;AAAA,EACjC;AAAA,EAEA,OAAO,QAA4C;AAC/C,UAAM,OAAO,KAAK,OAAO,IAAI,MAAM;AACnC,QAAI,SAAS,OAAW,MAAK,OAAO,OAAO,MAAM;AACjD,WAAO;AAAA,EACX;AAAA,EAEA,QAAc;AACV,SAAK,OAAO,MAAM;AAClB,SAAK,cAAc,SAAS;AAAA,EAChC;AACJ;;;AEvSO,IAAM,kBAAN,MAAM,iBAAgB;AAAA;AAAA,EAEzB,OAAgB,OAAO,IAAI,iBAAgB,UAAU,QAAQ;AAAA;AAAA,EAE7D,OAAgB,UAAU,IAAI,iBAAgB,MAAO,GAAM;AAAA;AAAA;AAAA,EAIlD;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,mBAAmB,UAAU,eAAe,UAAU;AAC9D,SAAK,mBAAmB;AACxB,SAAK,eAAe;AAAA,EACxB;AACJ;;;ACxBO,IAAM,WAAN,cAAuB,MAAM;AAAA,EACvB;AAAA,EAET,YAAY,SAAiB,UAAmB;AAC5C,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,QAAI,aAAa;AACb,WAAK,WAAW;AAAA,EACxB;AACJ;AAMO,IAAM,4BAA4B;AAalC,IAAM,qBAAqC,OAAO;AAAA,EACrD,SAAS;AAAA,EACT,SAAS;AACb;AAEO,SAAS,gBAAgB,OAAgB,QAA2C;AACvF,QAAM,OAAO,iBAAiB,QAAQ,MAAM,OAAO;AACnD,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,QAAM,OAAyB;AAAA,IAC3B,SAAS;AAAA,IACT,SAAS,GAAG,IAAI,KAAK,OAAO;AAAA,EAChC;AACA,SAAO,WAAW,SAAY,OAAO,OAAO,OAAO,IAAI;AAC3D;;;AC9BO,IAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQnC,UAAU,QAAsC;AAG5C,UAAM,SAAmB,CAAC;AAC1B,QAAI,OAAO;AACX,QAAI,aAAa;AACjB,eAAW,SAAS,QAAQ;AACxB,UAAI,CAAC,OAAO,cAAc,KAAK,KAAK,QAAQ;AACxC,cAAM,IAAI;AAAA,UACN,2EAA2E,KAAK;AAAA,QAAI;AAC5F,YAAM,QAAQ,QAAQ;AACtB,UAAI,QAAQ;AACR,cAAM,IAAI;AAAA,UACN,iEAAiE,KAAK,UAAU,IAAI;AAAA,QAAI;AAChG,aAAO,KAAK,KAAK;AACjB,aAAO;AACP,oBAAc,mBAAmB,KAAK;AAAA,IAC1C;AAEA,UAAM,MAAM,IAAI,WAAW,UAAU;AACrC,QAAI,SAAS;AACb,eAAW,SAAS,QAAQ;AACxB,eAAS,cAAc,KAAK,OAAO,MAAM;AAAA,IAC7C;AACA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,MAA4B;AACpC,UAAM,SAAmB,CAAC;AAC1B,QAAI,OAAO;AACX,QAAI,SAAS;AACb,WAAO,SAAS,KAAK,QAAQ;AACzB,YAAM,CAAC,OAAO,IAAI,IAAI,aAAa,MAAM,MAAM;AAC/C,eAAS;AACT,cAAQ;AACR,aAAO,KAAK,IAAI;AAAA,IACpB;AACA,WAAO;AAAA,EACX;AACJ;AAQA,IAAM,WAAW;AACjB,IAAM,WAAW;AAEjB,SAAS,mBAAmB,OAAuB;AAC/C,MAAI,IAAI;AACR,MAAI,IAAI;AACR,SAAO,KAAK,UAAU;AAClB;AACA,QAAI,KAAK,MAAM,IAAI,GAAG;AAAA,EAC1B;AACA,SAAO,IAAI;AACf;AAEA,SAAS,cAAc,KAAiB,OAAe,QAAwB;AAC3E,MAAI,IAAI;AACR,SAAO,KAAK,UAAU;AAClB,QAAI,QAAQ,KAAK,WAAY,IAAI,YAAa;AAC9C,QAAI,KAAK,MAAM,IAAI,GAAG;AAAA,EAC1B;AACA,MAAI,QAAQ,IAAI,IAAI;AACpB,SAAO;AACX;AAEA,SAAS,aAAa,MAAkB,QAAkC;AACtE,MAAI,QAAQ;AACZ,MAAI,QAAQ;AAEZ,SAAO,QAAQ,IAAI;AACf,QAAI,UAAU,KAAK;AACf,YAAM,IAAI,WAAW,6CAA6C;AACtE,UAAMC,KAAI,KAAK,QAAQ;AAKvB,cAAUA,KAAI,YAAY,KAAK,IAAI,GAAG,KAAK;AAC3C,QAAIA,MAAK,SAAU,QAAO,CAAC,OAAO,MAAM;AACxC,aAAS;AAAA,EACb;AAEA,MAAI,UAAU,KAAK;AACf,UAAM,IAAI,WAAW,6CAA6C;AACtE,QAAM,IAAI,KAAK,QAAQ;AACvB,MAAI,IAAI;AACJ,UAAM,IAAI,WAAW,yDAAyD;AAClF,WAAS,IAAI,KAAK,IAAI,GAAG,EAAE;AAC3B,SAAO,CAAC,OAAO,MAAM;AACzB;;;AChHO,SAAS,aAAa,OAA2B;AAGpD,QAAM,YAAY;AAClB,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,WAAW;AAC9C,cAAU,OAAO;AAAA,MACb,GAAI,MAAM,SAAS,GAAG,KAAK,IAAI,MAAM,QAAQ,IAAI,SAAS,CAAC;AAAA,IAC/D;AAAA,EACJ;AACA,SAAO,KAAK,MAAM;AACtB;AAGO,SAAS,gBAAgB,OAA2B;AACvD,SAAO,aAAa,KAAK,EACpB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AAC1B;AAEO,SAAS,aAAa,SAA6B;AACtD,QAAM,SAAS,KAAK,OAAO;AAC3B,QAAM,MAAM,IAAI,WAAW,OAAO,MAAM;AACxC,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,IAAK,KAAI,CAAC,IAAI,OAAO,WAAW,CAAC;AACpE,SAAO;AACX;;;ACIA,IAAM,EAAE,UAAU,SAAAC,SAAQ,IAAI,QAAQ,sBAAsB;AAGrD,IAAM,uBAAN,MAA2B;AAAA,EAC9B,OAAO,SAAqB,MAAiB,MAAqB;AAC9D,UAAM,SAAS,QAAQ;AACvB,UAAM,YAAY,QAAQ,aAAa;AACvC,cAAU,IAAI,IAAI,KAAK,GAAG,aAAa,MAAM,IAAI,SAAS,EAAE;AAE5D,YAAQ,QAAQ;AAAA,MAChB,KAAK,eAAe,IAAI;AACpB,cAAM,OAAO,KAAK,cAAc,IAAI,SAAS;AAC7C,YAAI,SAAS,QAAW;AACpB,cAAI,KAAK,SAAS,GAAG;AACjB,iBAAK,cAAc,OAAO,SAAS;AACnC,iBAAK,OAAO,OAAO,wBAAwB,QAAQ,KAAK,QAAQ,CAAC,CAAC;AAClE;AAAA,UACJ;AACA,eAAK,kBAAkB,aAAa;AACpC,cAAI,KAAK,YAAY;AACjB,iBAAK,cAAc,OAAO,SAAS;AAAA,UACvC;AACA,eAAK,OAAO,QAAQ,KAAK,CAAC,CAAC;AAAA,QAC/B;AACA;AAAA,MACJ;AAAA,MACA,KAAK,eAAe,WAAW;AAW3B,aAAK,iBAAiB,WAAW,MAAM,IAAI;AAC3C;AAAA,MACJ;AAAA,MACA,KAAK,eAAe,OAAO;AACvB,cAAM,OAAO,KAAK,cAAc,IAAI,SAAS;AAC7C,YAAI,SAAS,QAAW;AAMpB,eAAK,kBAAkB,aAAa;AACpC,cAAI,KAAK,YAAY;AACjB,iBAAK,cAAc,OAAO,SAAS;AAAA,UACvC;AACA,gBAAM,YAAY,KAAK,CAAC;AAGxB,gBAAM,MAAO,WAAW,WAChB,WAAW,WACX;AACR,gBAAM,WAAW,gBAAgB,WAAW,WAAW,WAAW,OAAO;AAEzE,cAAI,aAAa;AACb,YAAAA,UAAS,IAAI,6CAA6C,GAAG;AACjE,eAAK,OAAO,OAAO,IAAI,SAAS,KAAK,QAAQ,CAAC;AAAA,QAClD;AACA;AAAA,MACJ;AAAA,MACA,KAAK,eAAe,QAAQ;AAOxB,cAAM,OAAO,KAAK,aAAa,OAAO,SAAS;AAC/C,cAAM,OAAO;AACb;AAAA,MACJ;AAAA,MACA,KAAK,eAAe,WAAW;AAI3B,aAAK,wBAAwB;AAK7B,cAAM,eAAe,KAAK,CAAC;AAC3B,YAAI,MAAM,QAAQ,YAAY,KAAK,aAAa,SAAS,KAAK,KAAK,mBAAmB,QAAW;AAC7F,gBAAM,aAAuB,CAAC;AAC9B,qBAAW,MAAM;AACb,gBAAI,KAAK,cAAc,IAAI,EAAE,MAAM;AAC/B,yBAAW,KAAK,EAAE;AAE1B,cAAI,WAAW,SAAS;AACpB,iBAAK,IAAI,iBAAiB;AAAA,cACtB,KAAK;AAAA,cAAgB,KAAK;AAAA,cAAqB;AAAA,YAAU;AAAA,QACrE;AACA;AAAA,MACJ;AAAA,MACA,KAAK,eAAe,MAAM;AACtB,cAAM,SAAS,KAAK,cAAc,IAAI,SAAS;AAM/C,YAAI,CAAC;AACD,oBAAU,IAAI,mCAAmC,SAAS,EAAE;AAAA,iBACvD,KAAK,SAAS;AACnB,iBAAO,MAAM,KAAK,CAAC,GAAa,wBAAwB,QAAQ,KAAK,QAAQ,CAAC,CAAC;AAAA;AAE/E,iBAAO,OAAO,KAAK,CAAC,GAAa,KAAK,CAAC,CAAC;AAC5C;AAAA,MACJ;AAAA,MACA,KAAK,eAAe,OAAO;AACvB,cAAM,SAAS,KAAK,cAAc,IAAI,SAAS;AAG/C,YAAI,CAAC;AACD,oBAAU,IAAI,mCAAmC,SAAS,EAAE;AAAA,iBACvD,KAAK,SAAS;AACnB,iBAAO,MAAM,KAAK,CAAC,GAAa,wBAAwB,QAAQ,KAAK,QAAQ,CAAC,CAAC;AAAA,iBAC1E,MAAM,QAAQ,KAAK,CAAC,CAAC;AAC1B,iBAAO,QAAQ,KAAK,CAAC,GAAa,KAAK,CAAC,CAAc;AAC1D;AAAA,MACJ;AAAA,MACA,KAAK,eAAe,KAAK;AACrB,cAAM,SAAS,KAAK,cAAc,IAAI,SAAS;AAG/C,YAAI,CAAC,QAAQ;AACT,oBAAU,IAAI,qCAAqC,SAAS,EAAE;AAAA,QAClE,OAAO;AAKH,gBAAM,YAAY,KAAK,CAAC;AACxB,gBAAM,WAAW,gBAAgB,WAAW,WAAW,WAAW,OAAO;AACzE,gBAAM,MAAO,WAAW,WAAW,WAAW;AAG9C,cAAI,QAAsB;AAC1B,cAAI,YAAY,KAAK;AACjB,oBAAQ,IAAI,MAAM,OAAO,YAAY,kBAAkB;AACvD,gBAAI;AACA,oBAAM,OAAO;AAAA,UACrB;AACA,iBAAO,MAAM,KAAK,CAAC,GAAa,KAAK;AAAA,QACzC;AACA;AAAA,MACJ;AAAA,MACA,KAAK,eAAe,KAAK;AACrB,cAAM,SAAS,KAAK,cAAc,IAAI,SAAS;AAM/C,YAAI;AACA,iBAAO,MAAM,KAAK,CAAC,GAAa,KAAK,CAAC,CAAW;AAAA,iBAC5C,KAAK,mBAAmB;AAC7B,eAAK,IAAI,iBAAiB;AAAA,YACtB,KAAK;AAAA,YAAgB,KAAK;AAAA,YAAqB,CAAC,SAAS;AAAA,UAAC;AAClE;AAAA,MACJ;AAAA,MACA,KAAK,eAAe,QAAQ;AACxB,cAAM,SAAS,KAAK,cAAc,IAAI,SAAS;AAG/C,YAAI;AACA,iBAAO,SAAS,KAAK,CAAC,CAAW;AAAA,iBAC5B,KAAK,mBAAmB;AAC7B,eAAK,IAAI,iBAAiB;AAAA,YACtB,KAAK;AAAA,YAAgB,KAAK;AAAA,YAAqB,CAAC,SAAS;AAAA,UAAC;AAClE;AAAA,MACJ;AAAA,MACA,KAAK,eAAe,YAAY;AAW5B,cAAM,MAAM,KAAK,CAAC;AAClB,YAAI,MAAM,QAAQ,GAAG,GAAG;AACpB,qBAAW,MAAM,KAAK;AAClB,kBAAM,YAAY,KAAK,cAAc,IAAI,EAAE;AAC3C,gBACI,aACI,OAAO,UAAU,eAAe,YACtC;AACE,wBAAU,WAAW;AAAA,YACzB;AAAA,UACJ;AAAA,QACJ;AACA;AAAA,MACJ;AAAA,IACA;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,iBAAiB,WAAmB,MAAiB,MAAqB;AAC9E,UAAM,OAAO,KAAK;AAClB,QAAI,SAAS,OAAW;AAUxB,UAAM,iBAAiB,KAAK,CAAC;AAC7B,QAAI,OAAO,mBAAmB,YAAY,mBAAmB,KAAK,mBAAmB;AACjF,YAAM,MAAM,OAAO,mBAAmB,WAAW,iBAAiB,IAAI,OAAO,cAAc;AAC3F,WAAK,IAAI,iBAAiB;AAAA,QACtB;AAAA,QAAM,KAAK;AAAA,QAAqB;AAAA,QAChC,IAAI;AAAA,UACA,2CAA2C,KAAK,iBAAiB,OAAO,GAAG;AAAA,QAAE;AAAA,MAAC;AACtF;AAAA,IACJ;AAKA,QAAI,CAAC,KAAK,kBAAkB,GAAG;AAC3B,WAAK,IAAI,iBAAiB;AAAA,QACtB;AAAA,QAAM,KAAK;AAAA,QAAqB;AAAA,QAChC,IAAI,MAAM,6DAA6D;AAAA,MAAC;AAC5E;AAAA,IACJ;AAKA,QAAI,aAAuB,CAAC;AAC5B,QAAI;AACA,YAAM,YAAY,KAAK,CAAC;AACxB,UAAI,aAAa,OAAO,cAAc,YAAY,CAAC,MAAM,QAAQ,SAAS,GAAG;AACzE,cAAM,aAAa,oBAAI,IAAY;AACnC,mBAAW,CAAC,UAAU,QAAQ,KAAK,OAAO,QAAQ,SAAoC,GAAG;AACrF,gBAAM,QAAQ,SAAS,QAAQ;AAC/B,cAAI,UAAU,KAAM;AACpB,gBAAM,iBAAiB,SAAS,UAAU,EAAE,KAAK;AACjD,gBAAM,UAAU,wBAAwB,YAAY,KAAK;AACzD,qBAAW,UAAU,SAAS;AAC1B,kBAAM,OAAO,KAAK,aAAa,IAAI,MAAM;AACzC,gBAAI,SAAS;AACT,yBAAW,IAAI,MAAM;AAAA,qBAChB,iBAAiB;AAMtB,mBAAK,aAAa;AAAA,UAC1B;AAAA,QACJ;AACA,qBAAa,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAAA,MACrD;AAAA,IACJ,QAAQ;AAAA,IAIR;AAEA,UAAM,gBAAgB,wBAAwB,UAAU,UAAU;AAClE,UAAM,gBAAgB,KAAK,oBAAoB,WAAW,gBAAgB,aAAa,aAAa;AACpG,SAAK,IAAI,iBAAiB,GAAG,MAAM,KAAK,qBAAqB,WAAW,aAAa;AAAA,EACzF;AACJ;AAKA,SAAS,wBAAwB,QAAgB,KAAa,UAAyB;AACnF,SAAO,IAAI;AAAA,IACP,GAAG,MAAM,cAAc,QAAQ,kBAAkB,GAAG;AAAA,EACK;AACjE;AAMA,SAAS,gBAAgB,SAAsC;AAC3D,MAAI,OAAO,YAAY,UAAU;AAC7B,QAAI,QAAQ,WAAW;AACnB,aAAO;AAEX,UAAM,WAAW,QAAQ,QAAQ,GAAG;AACpC,WAAO,YAAY,IAAI,QAAQ,MAAM,GAAG,QAAQ,IAAI;AAAA,EACxD;AACA,MAAI,YAAY,QAAQ,OAAO,YAAY,UAAU;AACjD,UAAM,IAAI;AACV,UAAM,MAAO,EAAE,yBAAyB,EAAE;AAC1C,QAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,GAAG;AAC3C,YAAM,WAAW,IAAI,QAAQ,GAAG;AAChC,aAAO,YAAY,IAAI,IAAI,MAAM,GAAG,QAAQ,IAAI;AAAA,IACpD;AACA,WAAQ,EAAE,YAAY,EAAE;AAAA,EAC5B;AACA,SAAO;AACX;AAQA,SAAS,SAAS,OAAmC;AACjD,MAAI,iBAAiB,WAAY,QAAO;AACxC,MAAI,OAAO,UAAU,UAAU;AAC3B,QAAI;AAAE,aAAO,aAAa,KAAK;AAAA,IAAG,QAAQ;AAAE,aAAO;AAAA,IAAM;AAAA,EAC7D;AACA,SAAO;AACX;;;AClVA,IAAM,EAAE,SAAS,IAAI,QAAQ,qBAAqB;AAkB3C,IAAM,sBAAN,MAAM,qBAAoB;AAAA;AAAA,EAEZ,WAAuB;AAAA,IACpC,QAAQ,eAAe;AAAA,IACvB,WAAW;AAAA,EACf;AAAA,EACiB,YAAwB;AAAA,IACrC,QAAQ,eAAe;AAAA,IACvB,WAAW;AAAA,EACf;AAAA,EACiB,UAAsB;AAAA,IACnC,QAAQ,eAAe;AAAA,IACvB,WAAW;AAAA,EACf;AAAA,EACiB,UAAsB;AAAA,IACnC,QAAQ,eAAe;AAAA,IACvB,WAAW;AAAA,EACf;AAAA,EACiB,aAAyB;AAAA,IACtC,QAAQ,eAAe;AAAA,IACvB,WAAW;AAAA,EACf;AAAA;AAAA,EAGiB,YAA+B,CAAC,GAAG,IAAI;AAAA,EACvC,aAAgC,CAAC,GAAG,IAAI;AAAA,EACxC,WACb,CAAC,GAAG,EAAE,SAAS,IAAI,SAAS,GAAG,CAAC;AAAA,EACnB,WAA6B,CAAC,GAAG,EAAE;AAAA,EACnC,cAAwB,CAAC,EAAE;AAAA,EAE5C,OAAwB,WACpB,OAAO,OAAO,EAAE,SAAS,IAAI,SAAS,GAAG,CAAC;AAAA,EAK9C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA,EAEU,MACN,MACA,QACA,UACA,MACI;AACJ,UAAM,WAAW,OAAO;AAAA,MACpB;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,IACT;AACA,QAAI,OAAO,aAAa,UAAU;AAC9B,WAAK,KAAK,QAAQ;AAAA,IACtB,OAAO;AACH,WAAK,WAAW,QAAQ;AAAA,IAC5B;AAAA,EACJ;AAAA,EAEA,UACI,MACA,QACA,QACA,OACA,OACI;AAEJ,UAAM,eAAe,OAAO,WACtB,CAAC,QAAQ,MAAM,OAAO,GAAG,KAAK,IAC9B;AAAA,MACE,cAAc;AAAA,MACd,qBAAqB;AAAA,MACrB,aAAa;AAAA,MACb,iBAAiB;AAAA,MACjB,OAAO;AAAA,IACX;AACJ,SAAK,MAAM,MAAM,QAAQ,EAAE,QAAQ,eAAe,UAAU,GAAG;AAAA,MAC3D;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EAEA,GACI,MACA,QACA,WACA,QACI;AACJ,QAAI;AACA,WAAK;AAAA,QACD;AAAA,QACA;AAAA,QACA,EAAE,QAAQ,eAAe,IAAI,WAAW,UAAU;AAAA,QAClD,CAAC,MAAM;AAAA,MACX;AAAA,IACJ,SACO,GAAG;AAEN,gBAAU,IAAI,wCAAwC,SAAS,IAAI,CAAC;AACpE,WAAK,MAAM,MAAM,QAAQ,WAAW,CAAC;AAAA,IACzC;AAAA,EACJ;AAAA,EAEA,MACI,MACA,QACA,WACA,OACI;AAIJ,SAAK;AAAA,MACD;AAAA,MACA;AAAA,MACA,EAAE,QAAQ,eAAe,OAAO,WAAW,UAAU;AAAA,MACrD,CAAC,gBAAgB,OAAO,KAAK,WAAW,CAAC;AAAA,IAC7C;AAAA,EACJ;AAAA,EAEA,OACI,MACA,QACA,WACI;AACJ,SAAK,MAAM,MAAM,QAAQ;AAAA,MACrB,QAAQ,eAAe;AAAA,MACvB,WAAW;AAAA,IACf,CAAC;AAAA,EACL;AAAA,EAEA,UACI,MACA,QACA,eACI;AACJ,SAAK,MAAM,MAAM,QAAQ,EAAE,QAAQ,eAAe,UAAU,GAAG;AAAA,MAC3D;AAAA,IACJ,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAKA,WACI,MACA,QACA,UACI;AACJ,SAAK,MAAM,MAAM,QAAQ,EAAE,QAAQ,eAAe,WAAW,GAAG;AAAA,MAC5D;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EAEA,KACI,MACA,QACA,SACA,OACA,MACI;AACJ,SAAK,SAAS,YAAY;AAC1B,SAAK,UAAU,CAAC,IAAI;AACpB,SAAK,UAAU,CAAC,IAAI;AACpB,SAAK,MAAM,MAAM,QAAQ,KAAK,UAAU,KAAK,SAAS;AACtD,SAAK,UAAU,CAAC,IAAI;AAAA,EACxB;AAAA,EAEA,MACI,MACA,QACA,SACA,OACA,OACI;AACJ,SAAK,UAAU,YAAY;AAC3B,SAAK,WAAW,CAAC,IAAI;AACrB,SAAK,WAAW,CAAC,IAAI;AACrB,SAAK,MAAM,MAAM,QAAQ,KAAK,WAAW,KAAK,UAAU;AACxD,SAAK,WAAW,CAAC,IAAI;AAAA,EACzB;AAAA,EAEA,IACI,MACA,QACA,SACA,OACA,QAGI,qBAAoB,UACpB;AACJ,SAAK,QAAQ,YAAY;AACzB,SAAK,SAAS,CAAC,IAAI;AACnB,SAAK,SAAS,CAAC,IAAI;AACnB,SAAK,MAAM,MAAM,QAAQ,KAAK,SAAS,KAAK,QAAQ;AAAA,EACxD;AAAA,EAEA,IACI,MACA,QACA,SACA,WACA,QACI;AACJ,SAAK,QAAQ,YAAY;AACzB,SAAK,SAAS,CAAC,IAAI;AACnB,SAAK,SAAS,CAAC,IAAI;AACnB,SAAK,MAAM,MAAM,QAAQ,KAAK,SAAS,KAAK,QAAQ;AAAA,EACxD;AAAA,EAEA,OACI,MACA,QACA,SACA,QACI;AACJ,SAAK,WAAW,YAAY;AAC5B,SAAK,YAAY,CAAC,IAAI;AACtB,SAAK,MAAM,MAAM,QAAQ,KAAK,YAAY,KAAK,WAAW;AAAA,EAC9D;AACJ;;;AC7QO,IAAM,gBAAgB,EAAE,OAAO,GAAG,QAAQ,EAAE;;;ACLnD,oBAAmB;AACnB,IAAAC,eAA8B;;;ACD9B,IAAAC,eAA0C;AAQ1C,IAAM,EAAE,SAAAC,SAAQ,IAAI,QAAQ,iBAAiB;AAG7C,SAAS,UAAgB;AAA4B;AAGrD,IAAM,qBAAqB;AAE3B,IAAM,sBAAsB;AAE5B,IAAM,gBAAgB;AAItB,IAAM,yBAAyB;AA6BxB,IAAM,kBAAN,MAAM,iBAAyC;AAAA,EAClD,OAAwB,aAAa;AAAA,EAE5B;AAAA,EACA,OAAO,cAAc;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA;AAAA;AAAA;AAAA,EAID,aAAa;AAAA;AAAA;AAAA,EAGb,cAAc;AAAA;AAAA,EAEd,eAAe;AAAA,EACf,WAAW,IAAI,2BAAoB;AAAA;AAAA,EAEnC,kBAAkB;AAAA,EAClB,mBAAmB,IAAI,gBAAgB;AAAA,EACvC,YAAqC;AAAA;AAAA,EAGrC,oBAA2D,CAAC;AAAA,EAC5D,cAAiD,CAAC;AAAA;AAAA;AAAA,EAIlD,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA,EAKrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA;AAAA,EAEA,IAAI,YAAoB;AACpB,WAAO,KAAK;AAAA,EAChB;AAAA,EACA,IAAI,eAAuB;AACvB,WAAO,KAAK;AAAA,EAChB;AAAA,EACA,IAAI,YAAoB;AACpB,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA,EAGA,IAAI,WAAmB;AACnB,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,WAAO,KAAK,YAAY,SAAS,KAAK,KAAK,YAAY,CAAC,EAAE,OAAO;AAC7D,WAAK,YAAY,MAAM;AAC3B,QAAI,KAAK,YAAY,WAAW;AAC5B,aAAO;AAEX,QAAI,MAAM,KAAK,YAAY,CAAC,EAAE;AAC9B,eAAW,KAAK,KAAK,aAAa;AAC9B,UAAI,EAAE,QAAQ;AACV,cAAM,EAAE;AAAA,IAChB;AACA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,YAAY;AAAA,EACZ,gBAAgB;AAAA;AAAA;AAAA,EAGhB,eAAe;AAAA,EACf,gBAA4C;AAAA,EAEpD,YACI,MACA,YAAY,oBACZ,aAAa,qBACb,iBAAiB,MACjB,aAAa,OACb,YAAkC,MAAM,MACxC,wBAAwB,OACxB,YACF;AACE,UAAM,UAAU,KAAK,cAAc,OAAO;AAC1C,SAAK,KAAK,EAAE,QAAQ,KAAK,IAAI,OAAO,QAAQ;AAC5C,SAAK,iBAAiB;AACtB,SAAK,aAAa;AAClB,SAAK,YAAY;AACjB,SAAK,OAAO;AACZ,SAAK,YAAY;AACjB,SAAK,aAAa;AAClB,SAAK,wBAAwB;AAE7B,QAAI,eAAe,UAAa,eAAe,KAAK,aAAa;AAC7D,MAAAA,UAAS;AAAA,QACL,yBAAyB,UAAU,0BAA0B,UAAU;AAAA,MAC3E;AACJ,SAAK,aAAa,eAAe,UAAa,cAAc,aAAa,aAAa;AAAA,EAC1F;AAAA;AAAA,EAGA,IAAI,cAA2B;AAC3B,WAAO,KAAK,iBAAiB;AAAA,EACjC;AAAA;AAAA,EAGA,QAAgB;AACZ,WAAO,GAAG,KAAK,GAAG,MAAM,IAAI,KAAK,GAAG,OAAO,IAAI,KAAK,SAAS,IAAI,KAAK,UAAU,IAAI,KAAK,iBAAiB,MAAM,GAAG,IAAI,KAAK,aAAa,MAAM,GAAG;AAAA,EACtJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,WAAmB,QAAsB;AAC3C,UAAM,YAAY,WAAW,MAAM,WAAW,iBAAgB;AAI9D,QAAI,aAAa,WAAW,KAAK,GAAG,QAAQ;AACxC,WAAK,gBAAgB;AACrB;AAAA,IACJ;AAMA,QAAI,CAAC,OAAO,cAAc,SAAS,KAAK,YAAY,KAAK,YAAY,KAAK,YAAY;AAClF,WAAK,gBAAgB;AACrB;AAAA,IACJ;AAEA,QAAI,CAAC,KAAK,SAAS,aAAa;AAE5B,UAAI,aAAa,cAAc;AAC3B,aAAK,SAAS,QAAQ;AAAA,WACrB;AACD,aAAK,gBAAgB;AACrB;AAAA,MACJ;AAAA,IACJ,WAAW,KAAK,cAAc;AAI1B,WAAK,gBAAgB;AACrB;AAAA,IACJ,WAAW,aAAa,CAAC,KAAK,gBAAgB;AAE1C,WAAK,gBAAgB;AACrB;AAAA,IACJ;AAIA,QAAI,CAAC,aAAa,YAAY,KAAK;AAC/B;AAEJ,SAAK,eAAe;AACpB,SAAK,WAAW,WAAW,SAAS;AACpC,SAAK,YAAY;AACjB,SAAK,kBAAkB;AACvB,QAAI,KAAK,eAAe;AACpB,WAAK,cAAc,QAAQ;AAC3B,WAAK,gBAAgB;AAAA,IACzB;AAAA,EACJ;AAAA;AAAA;AAAA,EAIQ,kBAAwB;AAC5B,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI;AACA,WAAK,KAAK,IAAI,iBAAiB,WAAW,MAAM,KAAK,KAAK,qBAAqB,CAAC,KAAK,GAAG,OAAO,CAAC;AAEpG,SAAK,WAAW;AAAA,EACpB;AAAA;AAAA,EAGA,SAAS,SAAuB;AAC5B,SAAK,WAAW;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,SAAS,MAAe;AACpB,QAAI,KAAK,YAAa;AACtB,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI,MAAM;AACN,WAAK,KAAK,IAAI,iBAAiB;AAAA,QAC3B;AAAA,QAAM,KAAK,KAAK;AAAA,QAAqB,KAAK,GAAG;AAAA,QAAS,KAAK;AAAA,QAAY;AAAA,MAC3E;AACA,WAAK,kBAAkB,KAAK,EAAE,OAAO,KAAK,YAAY,UAAU,KAAK,IAAI,EAAE,CAAC;AAC5E,UAAI,KAAK,kBAAkB,SAAS;AAChC,aAAK,kBAAkB,OAAO,GAAG,0BAA0B,CAAC;AAAA,IACpE;AACA,SAAK;AAAA,EACT;AAAA;AAAA,EAGA,UAAU,OAAkB;AACxB,QAAI,KAAK,eAAe,MAAM,WAAW,EAAG;AAC5C,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI,MAAM;AACN,WAAK,KAAK,IAAI,iBAAiB;AAAA,QAC3B;AAAA,QAAM,KAAK,KAAK;AAAA,QAAqB,KAAK,GAAG;AAAA,QAAS,KAAK;AAAA,QAAY;AAAA,MAC3E;AAAA,IACJ;AACA,SAAK,cAAc,MAAM;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAQ,OAA4B;AAChC,QAAI,KAAK,YAAa;AAMtB,UAAM,QAAQ,KAAK;AACnB,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI,CAAC,KAAM;AAGX,UAAM,YAAY,QACZ,gBAAgB,KAAK,IACrB,EAAE,SAAS,IAAI,SAAS,GAAG;AACjC,SAAK,KAAK,IAAI,iBAAiB;AAAA,MAC3B;AAAA,MAAM,KAAK,KAAK;AAAA,MAAqB,KAAK,GAAG;AAAA,MAAS;AAAA,MAAO;AAAA,IACjE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAAU,QAAyC;AACrD,UAAM,KAAK;AACX,QAAI,KAAK,YAAa;AAEtB,UAAM,WAAW,OAAO,OAAO,aAAa,EAAE;AAC9C,SAAK,YAAY;AAKjB,UAAM,QAAoB,EAAE,cAAc,MAAM;AAChD,QAAI;AACA,YAAM,KAAK,KAAK,UAAU,KAAK;AAAA,IACnC,UAAE;AACE,WAAK,eAAe;AACpB,UAAI,MAAM,gBAAgB,KAAK,cAAc,UAAU;AACnD,aAAK,YAAY;AAAA,MACrB;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,KACV,UACA,OACa;AACb,UAAM,SAAS,IAAI,wBAA2B,KAAK,aAAa,CAAC;AACjE,UAAM,aAAa,KAAK;AACxB,QAAI,cAAc;AAClB,QAAI,kBAAkB;AACtB,QAAI,gBAAmD;AACvD,UAAM,UAAU,uBAAO,SAAS;AAEhC,UAAM,WAAW,YAAwC;AACrD,YAAM,OAAO,iBAAiB,SAAS,KAAK;AAC5C,sBAAgB;AAChB,aAAO,MAAM;AAAA,IACjB;AAEA,UAAM,eAAe,YAAyD;AAC1E,wBAAkB,SAAS,KAAK;AAChC,aAAO,MAAM,QAAQ,KAAK;AAAA,QACtB;AAAA,QACA,QAAQ,QAAQ,OAAO;AAAA,MAC3B,CAAC;AAAA,IACL;AAEA,UAAM,eAAe,MAAY;AAC7B,UAAI;AACA,aAAK,aAAa,OAAO,KAAK;AAAA,MAClC,QAAQ;AAAA,MAA6C;AAAA,IACzD;AAEA,UAAM,aAAa,OAAO,aAAsB,iBAA4C;AACxF,UAAI;AACJ,UAAI;AACA,YAAI,cAAc,MAAM,aAAa,IAAI,MAAM,SAAS;AACxD,YAAI,MAAM;AACN,iBAAO;AACX,wBAAgB;AAChB,YAAI,KAAK,YAAa,QAAO;AAC7B,YAAI,EAAE,MAAM;AACR,iBAAO,SAAS,QAAQ;AACxB,4BAAkB;AAClB,gBAAM,eAAe;AAAA,QACzB,OAAO;AACH,iBAAO,SAAS,EAAE,MAAM,SAAS,OAAO,EAAE,MAAM,CAAC;AACjD,cAAI;AACA,4BAAgB,SAAS,KAAK;AAAA,QACtC;AACA,qBAAa;AACb,eAAO;AAAA,MACX,SAAS,GAAG;AACR,wBAAgB;AAChB,cAAM,eAAe;AACrB,0BAAkB;AAClB,eAAO,SAAS;AAAA,UACZ,MAAM;AAAA,UACN,OAAO,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC;AAAA,QACvD,CAAC;AACD,qBAAa;AACb,eAAO;AAAA,MACX;AAAA,IACJ;AAEA;AACA,aAAO,MAAM;AAET,YAAI,MAAM,KAAK,gBAAgB;AAC/B,YAAI,CAAC,KAAK;AACN,gBAAM,KAAK,cAAc;AACzB,cAAI,KAAK,YAAa;AACtB,gBAAM,KAAK,gBAAgB;AAC3B,cAAI,CAAC,KAAK;AACN,YAAAA,UAAS,IAAI,oDAAoD;AACjE;AAAA,UACJ;AAAA,QACJ;AACA,aAAK,kBAAkB,IAAI;AAI3B,YAAI,cAAc,IAAI,aAAa,CAAC,iBAAiB;AACjD,iBAAO,MAAM;AACb,wBAAc,KAAK;AACnB,iBAAO,MAAM;AACT,gBAAI,KAAK,YAAa;AACtB,gBAAI;AACJ,gBAAI,WAAW;AACf,gBAAI;AACA,oBAAM,IAAI,MAAM,SAAS;AACzB,kBAAI,KAAK,YAAa;AACtB,kBAAI,EAAE,MAAM;AACR,uBAAO;AACP,kCAAkB;AAClB,sBAAM,eAAe;AAAA,cACzB,WAAW,KAAK,UAAU,EAAE,KAAK,GAAG;AAChC,uBAAO,EAAE,MAAM,SAAS,OAAO,EAAE,MAAM;AACvC,2BAAW;AAAA,cACf,OAAO;AACH;AAAA,cACJ;AAAA,YACJ,SAAS,GAAG;AACR,oBAAM,eAAe;AACrB,gCAAkB;AAClB,qBAAO;AAAA,gBACH,MAAM;AAAA,gBACN,OAAO,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC;AAAA,cACvD;AAAA,YACJ;AACA,mBAAO,SAAS,IAAI;AACpB,yBAAa;AACb,gBAAI,YAAY,gBAAiB;AAAA,UACrC;AAAA,QACJ;AAGA,cAAM,QAAQ,OAAO,IAAI,YAAY,aAAa,GAAG,OAAO,KAAK;AACjE,YAAI,QAAQ,GAAG;AACX,iBAAO,SAAS,KAAK;AACrB,yBAAe;AAAA,QACnB;AAGA,YAAI,KAAK,aAAa,aAAa;AAC/B,cAAI,CAAC,KAAK;AACN,iBAAK,QAAQ,IAAI,MAAM,8BAA8B,CAAC;AAC1D;AAAA,QACJ;AACA,YAAI,cAAc,KAAK,aAAa;AAGpC,cAAM,WAAW,IAAI,YAAY,KAAK;AACtC,eAAO,KAAK,aAAa,UAAU;AAC/B,cAAI,KAAK,YAAa;AAEtB,cAAI,YAAY;AAIZ,mBAAO,OAAO,wBAAwB,CAAC,mBAAmB,CAAC,KAAK,gBAAgB;AAC5E,kBAAI,CAAC,MAAM,WAAW,MAAM,IAAI;AAC5B;AAAA,YACR;AACA,gBAAI,eAAe,OAAO,SACnB,CAAC,mBACD,OAAO,wBACP,CAAC,KAAK,gBAAgB;AAMzB,gCAAkB,SAAS,KAAK;AAChC,oBAAM,eAAgB,KAAK,kBAAkB,IAAI,2BAAoB;AAMrE,oBAAM,QAAQ,KAAK,CAAC,cAAc,KAAK,SAAS,OAAO,GAAG,YAAY,CAAC;AACvE,kBAAI,CAAC,KAAK;AACN,sBAAM,WAAW,OAAO,IAAI;AAAA,YACpC;AACA,gBAAI,IAAI,YAAY,GAAG;AACnB,oBAAM,SAAS;AAAA,gBACX;AAAA,gBAAQ;AAAA,gBAAa,KAAK;AAAA,cAAS;AACvC,4BAAc,OAAO;AACrB,kBAAI,OAAO,UAAU;AACjB,qBAAK,cAAc,OAAO;AAAA,YAClC;AAGA,gBAAI,KAAK,eAAgB;AAAA,UAC7B;AAGA,iBAAO,KAAK,aAAa,YAAY,cAAc,OAAO,OAAO;AAC7D,kBAAM,OAAO,OAAO,IAAI,aAAa;AACrC,gBAAI,KAAK,SAAS,SAAS;AACvB,mBAAK,SAAS,KAAK,KAAK;AAAA,YAC5B,OAAO;AAKH,mBAAK,QAAQ,KAAK,SAAS,UAAU,KAAK,QAAQ,MAAS;AAC3D,uBAAS;AAAA,YACb;AAAA,UACJ;AACA,cAAI,KAAK,cAAc;AACnB;AACJ,cAAI,gBAAiB;AACrB,cAAI,CAAC,OAAO,qBAAsB;AAElC,cAAI,CAAC;AACD,kBAAM,WAAW,OAAO,KAAK;AAAA,QACrC;AAAA,MACJ;AAAA,EACJ;AAAA;AAAA,EAGA,IAAY,iBAA0B;AAClC,WAAO,KAAK,aAAa;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAoE;AACxE,UAAM,YAAY,KAAK;AACvB,QAAI,YAAY;AACZ,aAAO;AAEX,UAAM,YAAY,KAAK;AACvB,SAAK,YAAY;AACjB,SAAK,gBAAgB;AACrB,QAAI,aAAa,KAAK,aAAa;AAC/B,WAAK,aAAa;AAEtB,QAAI;AACA,WAAK,iBAAiB;AAAA,IAC1B,QAAQ;AAAA,IAA6C;AACrD,WAAO,EAAE,WAAW,UAAU;AAAA,EAClC;AAAA;AAAA,EAGA,MAAc,gBAA+B;AACzC,QAAI,KAAK,kBAAkB,KAAK;AAC5B;AAEJ,UAAM,eAAgB,KAAK,kBAAkB,IAAI,2BAAoB;AACrE,UAAM;AACN,QAAI,KAAK,kBAAkB;AACvB,WAAK,gBAAgB;AAAA,EAC7B;AAAA;AAAA;AAAA,EAIQ,WAAW,cAAsB,WAA0B;AAC/D,QAAI,WAAW;AACX,WAAK,kBAAkB,SAAS;AAChC;AAAA,IACJ;AAEA,QAAI,WAAW;AACf,WAAO,KAAK,kBAAkB,SAAS,KAAK,KAAK,kBAAkB,CAAC,EAAE,QAAQ,cAAc;AACxF,iBAAW,KAAK,kBAAkB,CAAC,EAAE;AACrC,WAAK,kBAAkB,MAAM;AAAA,IACjC;AACA,QAAI,WAAW;AACX;AAEJ,UAAM,QAAQ,KAAK,IAAI;AACvB,UAAM,WAAW,QAAQ;AACzB,WAAO,KAAK,YAAY,SAAS,KAAK,KAAK,YAAY,CAAC,EAAE,OAAO;AAC7D,WAAK,YAAY,MAAM;AAC3B,SAAK,YAAY,KAAK,EAAE,MAAM,OAAO,OAAO,QAAQ,SAAS,CAAC;AAAA,EAClE;AAAA;AAAA,EAIA,YAAkB;AAAA,EAGlB;AAAA,EAEA,aAAmB;AACf,QAAI,KAAK,YAAa;AACtB,SAAK,cAAc;AACnB,SAAK,iBAAiB,MAAM;AAC5B,QAAI,CAAC,KAAK,SAAS,aAAa;AAC5B,WAAK,SAAS,QAAQ;AAAA,IAC1B;AACA,QAAI,KAAK,eAAe;AACpB,WAAK,cAAc,QAAQ;AAC3B,WAAK,gBAAgB;AAAA,IACzB;AACA,SAAK,KAAK,cAAc,WAAW,IAAI;AAMvC,UAAM,WAAW,KAAK;AACtB,QAAI,CAAC,SAAU;AACf,QAAI,CAAC,KAAK,uBAAuB;AAC7B,WAAK,YAAY;AACjB,WAAK,oBAAoB,QAAQ;AACjC;AAAA,IACJ;AACA,eAAW,MAAM;AACb,UAAI,KAAK,cAAc,UAAU;AAC7B,aAAK,YAAY;AACjB,aAAK,oBAAoB,QAAQ;AAAA,MACrC;AAAA,IACJ,GAAG,UAAU,uBAAuB;AAAA,EACxC;AAAA,EAEQ,oBAAoB,UAAkC;AAC1D,QAAI;AACA,YAAM,MAAM,SAAS,SAAS,MAAS;AACvC,UAAI,IAAK,MAAK,IAAI,MAAM,MAAM;AAAA,MAAe,CAAC;AAAA,IAClD,QAAQ;AAAA,IAAe;AAAA,EAC3B;AACJ;AAWA,IAAM,WAA+B,EAAE,MAAM,MAAM;AAOnD,SAAS,OAAO,OAAe,KAAa,KAAqB;AAC7D,SAAO,QAAQ,MAAM,MAAM,QAAQ,MAAM,MAAM;AACnD;AAEA,SAAS,6BACL,QACA,kBACA,WACqC;AAKrC,MAAI,cAAc;AAClB,WAAS,IAAI,kBAAkB,IAAI,OAAO,OAAO,KAAK;AAClD,UAAM,OAAO,OAAO,IAAI,CAAC;AACzB,QAAI,KAAK,SAAS,WAAW,UAAU,KAAK,KAAK;AAC7C,oBAAc;AAAA,EACtB;AACA,MAAI,eAAe;AACf,WAAO,EAAE,UAAU,kBAAkB,SAAS,EAAE;AAEpD,QAAM,UAAU;AAChB,QAAM,QAAQ;AAEd,QAAM,QAAQ,OAAO,QAAQ;AAC7B,SAAO,MAAM;AACb,WAAS,IAAI,GAAG,IAAI,SAAS;AACzB,WAAO,SAAS,MAAM,CAAC,CAAC;AAC5B,WAAS,IAAI,OAAO,IAAI,MAAM,QAAQ;AAClC,WAAO,SAAS,MAAM,CAAC,CAAC;AAC5B,SAAO,EAAE,UAAU,kBAAkB,SAAS,cAAc,iBAAiB;AACjF;;;ADtsBA,IAAM,EAAE,SAAAC,SAAQ,IAAI,QAAQ,WAAW;AAGvC,IAAMC,sBAAqB;AAE3B,IAAMC,uBAAsB;AAE5B,IAAM,iBAAiB;AA4ChB,SAAS,eAAe,OAAqC;AAEhE,MAAI,OAAO,UAAU,UAAU;AAC3B,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,SAAS,KAAK,MAAM,SAAS,EAAG,QAAO;AACjD,UAAM,SAAS,MAAM,CAAC;AACtB,UAAM,UAAU,SAAS,MAAM,CAAC,GAAG,EAAE;AACrC,UAAM,YAAY,SAAS,MAAM,CAAC,GAAG,EAAE;AACvC,UAAM,aAAa,SAAS,MAAM,CAAC,GAAG,EAAE;AACxC,QAAI,MAAM,OAAO,KAAK,MAAM,SAAS,KAAK,MAAM,UAAU;AACtD,aAAO;AACX,UAAM,iBAAiB,MAAM,SAAS,KAAK,MAAM,CAAC,MAAM;AACxD,UAAM,aAAa,MAAM,UAAU,KAAK,MAAM,CAAC,MAAM;AACrD,WAAO,EAAE,QAAQ,SAAS,WAAW,YAAY,gBAAgB,WAAW;AAAA,EAChF;AAEA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC7C,UAAM,MAAM;AACZ,UAAM,eAAe,IAAI;AACzB,QAAI,CAAC,MAAM,QAAQ,YAAY,KAAK,aAAa,SAAS;AACtD,aAAO;AACX,UAAM,YAAY,aAAa,CAAC;AAChC,UAAM,SAAS,OAAO,SAAS;AAC/B,UAAM,UAAU,OAAO,aAAa,CAAC,CAAC;AACtC,UAAM,YAAY,OAAO,IAAI,aAAa,GAAG;AAC7C,UAAM,aAAa,OAAO,IAAI,cAAc,GAAG;AAC/C,UAAM,iBAAiB,IAAI,mBAAmB;AAC9C,UAAM,aAAa,IAAI,eAAe;AACtC,WAAO,EAAE,QAAQ,SAAS,WAAW,YAAY,gBAAgB,WAAW;AAAA,EAChF;AACA,SAAO;AACX;AAcO,IAAM,YAAN,MAAM,WAAqD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS9D,OAAc,0BAA0B;AAAA,EAE/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGD;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA,UAAqB,IAAI,cAAAC,QAAU;AAAA,EACnC,qBAAqB;AAAA,EACrB,mBAA+C;AAAA,EAC/C,aAAa;AAAA,EACb,mBAAiC;AAAA,EACjC,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,aAAa;AAAA,EACb,eAAe;AAAA,EACf,qBAAqB;AAAA,EAM7B,YACI,aACA,eACF;AACE,QAAI,gBAAgB,WAAW,GAAG;AAE9B,YAAM,UAAW,iBAAqD,CAAC;AACvE,WAAK,OAAO,cAAc;AAC1B,WAAK,cAAc;AACnB,WAAK,KAAK,EAAE,QAAQ,IAAI,SAAS,EAAE;AACnC,WAAK,iBAAiB,QAAQ,kBAAkB;AAChD,WAAK,aAAa,QAAQ,cAAc;AACxC,WAAK,YAAY,QAAQ,cAAc,MAAM;AAC7C,WAAK,YAAY,QAAQ,aAAaF;AACtC,WAAK,aAAa,QAAQ,cAAcC;AACxC,WAAK,aAAa,QAAQ;AAAA,IAC9B,OAAO;AAEH,YAAM,MAAM;AACZ,YAAM,OAAO;AACb,WAAK,OAAO,cAAc;AAC1B,WAAK,KAAK,EAAE,QAAQ,IAAI,QAAQ,SAAS,IAAI,QAAQ;AACrD,WAAK,OAAO;AACZ,WAAK,iBAAiB,IAAI;AAC1B,WAAK,aAAa,IAAI;AACtB,WAAK,YAAY,MAAM;AACvB,WAAK,YAAY,IAAI;AACrB,WAAK,aAAa,IAAI;AAAA,IAE1B;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,IAAI,WAA0B;AAC1B,QAAI,CAAC,KAAK;AACN,YAAM,IAAI,MAAM,kDAAkD;AACtE,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA,EAIA,IAAI,SAAyC;AACzC,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,MAAwB;AAC1B,QAAI,KAAK,SAAS,cAAc;AAC5B,YAAM,IAAI,MAAM,0CAA0C;AAC9D,QAAI,KAAK,SAAS;AACd,UAAI,KAAK,QAAQ,SAAS;AACtB,cAAM,IAAI,MAAM,mDAAmD;AAEvE,aAAO,KAAK;AAAA,IAChB;AAEA,UAAM,YAAY,OAAO,KAAK,gBAAgB;AAC9C,UAAM,SAAS,IAAI;AAAA,MACf;AAAA,MAAM,KAAK;AAAA,MAAW,KAAK;AAAA,MAC3B,KAAK;AAAA,MAAgB,KAAK;AAAA,MAAY,KAAK;AAAA,MAC3C;AAAA,MAAW,KAAK;AAAA,IACpB;AACA,SAAK,cAAc,SAAS,MAAM;AAClC,SAAK,UAAU;AAGf,UAAM,SAAS,YACR,KAAK,YAAqD,OAAO,WAAW,IAC7E,KAAK;AAGX,SAAK,YAAY,OAAO,UAAU,MAAM;AAGxC,SAAK,cAAc,KAAK,oBAAoB,WACtC;AAAA,MACE,cAAc,CAAC,OAAO,GAAG,QAAQ,OAAO,GAAG,OAAO;AAAA,MAClD,WAAW,OAAO;AAAA,MAClB,YAAY,OAAO;AAAA,MACnB,gBAAgB,OAAO;AAAA,IAC3B,IACE,OAAO,MAAM;AACnB,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA,EAGA,OAAO,OAAe,MAAe;AACjC,QAAI,KAAK,aAAa,KAAK,WAAY;AAEvC,QAAI,CAAC,cAAc,KAAK,GAAG;AACvB,WAAK,cAAc,yBAAyB,OAAO,KAAK,CAAC,EAAE;AAC3D;AAAA,IACJ;AACA,QAAI,QAAQ,KAAK,oBAAoB;AACjC,MAAAF,UAAS,IAAI,2BAA2B,KAAK,GAAG,OAAO,cAAc,KAAK,kBAAkB,cAAc,KAAK,EAAE;AACjH,UAAI,CAAC,KAAK,gBAAgB;AACtB,aAAK,UAAU,KAAK,UAAU,KAAK,CAAC;AACpC;AAAA,MACJ;AACA,WAAK,SAAS,KAAK,oBAAoB,IAAI;AAC3C;AAAA,IACJ;AACA,QAAI,QAAQ,KAAK,oBAAoB;AAEjC,WAAK,cAAc,KAAK,IAAI,QAAQ,GAAG,KAAK,kBAAkB,CAAC;AAC/D;AAAA,IACJ;AACA,QAAI,KAAK,mBAAmB,QAAQ,CAAC,GAAG;AACpC,WAAK,cAAc,KAAK,gBAAgB,QAAQ,CAAC,CAAC;AAClD;AAAA,IACJ;AAEA,SAAK,QAAQ,KAAK,IAAI;AACtB,SAAK,qBAAqB,QAAQ;AAClC,SAAK,gBAAgB;AAAA,EACzB;AAAA;AAAA,EAGA,QAAQ,OAAe,OAAkB;AACrC,QAAI,KAAK,aAAa,KAAK,WAAY;AAEvC,QAAI,CAAC,cAAc,KAAK,KAAK,CAAC,cAAc,MAAM,MAAM,GAAG;AACvD,WAAK,cAAc,gCAAgC,OAAO,KAAK,CAAC,IAAI,OAAO,MAAM,MAAM,CAAC,EAAE;AAC1F;AAAA,IACJ;AACA,QAAI,MAAM,SAAS,gBAAgB;AAC/B,WAAK,cAAc,8BAA8B,MAAM,MAAM,MAAM,cAAc,EAAE;AACnF;AAAA,IACJ;AACA,QAAI,QAAQ,KAAK,oBAAoB;AACjC,MAAAA,UAAS,IAAI,4BAA4B,KAAK,GAAG,OAAO,cAAc,KAAK,kBAAkB,cAAc,KAAK,EAAE;AAClH,UAAI,CAAC,KAAK,gBAAgB;AACtB,aAAK,UAAU,KAAK,UAAU,KAAK,CAAC;AACpC;AAAA,MACJ;AACA,WAAK,SAAS,KAAK,oBAAoB,IAAI;AAC3C;AAAA,IACJ;AACA,QAAI,QAAQ,KAAK,oBAAoB;AACjC,WAAK,cAAc,KAAK,IAAI,QAAQ,MAAM,QAAQ,KAAK,kBAAkB,CAAC;AAC1E;AAAA,IACJ;AACA,QAAI,KAAK,mBAAmB,QAAQ,MAAM,MAAM,GAAG;AAC/C,WAAK,cAAc,KAAK,gBAAgB,QAAQ,MAAM,MAAM,CAAC;AAC7D;AAAA,IACJ;AAEA,eAAW,QAAQ,MAAO,MAAK,QAAQ,KAAK,IAAI;AAChD,SAAK,qBAAqB,QAAQ,MAAM;AACxC,SAAK,gBAAgB;AAAA,EACzB;AAAA;AAAA,EAGA,MAAM,OAAe,OAA2B;AAC5C,QAAI,KAAK,aAAa,KAAK,WAAY;AAEvC,QAAI,CAAC,cAAc,KAAK,GAAG;AACvB,WAAK,cAAc,2BAA2B,OAAO,KAAK,CAAC,EAAE;AAC7D;AAAA,IACJ;AACA,QAAI,QAAQ,KAAK,oBAAoB;AACjC,MAAAA,UAAS,IAAI,0BAA0B,KAAK,GAAG,OAAO,cAAc,KAAK,kBAAkB,cAAc,KAAK,EAAE;AAChH,UAAI,CAAC,KAAK,gBAAgB;AACtB,aAAK,UAAU,KAAK,UAAU,KAAK,CAAC;AACpC;AAAA,MACJ;AACA,WAAK,SAAS,KAAK,oBAAoB,IAAI;AAC3C;AAAA,IACJ;AACA,QAAI,QAAQ,KAAK,oBAAoB;AAEjC,WAAK,cAAc,KAAK,IAAI,OAAO,KAAK,kBAAkB,CAAC;AAC3D;AAAA,IACJ;AAEA,SAAK,UAAU,KAAK;AAAA,EACxB;AAAA;AAAA,EAIA,YAAkB;AACd,QAAI,KAAK,SAAS,cAAc,MAAO;AACvC,QAAI,KAAK,gBAAgB;AACrB,WAAK,SAAS,KAAK,oBAAoB,IAAI;AAAA,IAC/C,OAAO;AACH,WAAK,WAAW;AAAA,IACpB;AAAA,EACJ;AAAA,EAEA,aAAmB;AACf,QAAI,KAAK,SAAS,cAAc,OAAO;AAEnC,WAAK,SAAS,WAAW;AACzB;AAAA,IACJ;AACA,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAClB,SAAK,mBAAmB,IAAI,MAAM,oBAAoB;AACtD,SAAK,gBAAgB;AAAA,EACzB;AAAA;AAAA,EAIA,CAAC,OAAO,aAAa,IAAsB;AAEvC,QAAI,KAAK,SAAS,cAAc,OAAO;AACnC,YAAM,SAAS,OAAO,KAAK,gBAAgB,aACrC,KAAK,YAAY,IAAI,gBAAgB,EAAE,MAAM,IAC7C,KAAK;AACX,aAAO,OAAO,OAAO,aAAa,EAAE;AAAA,IACxC;AAGA,QAAI,KAAK;AACL,YAAM,IAAI,MAAM,sCAAsC;AAC1D,SAAK,aAAa;AAElB,WAAO;AAAA,MACH,MAAM,YAAwC;AAI1C,YAAI,CAAC,KAAK,UAAU;AAChB,eAAK,WAAW;AAChB,eAAK,KAAK,cAAc,SAAS,IAAI;AACrC,eAAK,SAAS,GAAG,IAAI;AAAA,QACzB;AACA,aAAK,cAAc,KAAK,kBAAkB;AAG1C,mBAAS;AACL,cAAI,CAAC,KAAK,QAAQ,QAAQ,GAAG;AAGzB,kBAAM,QAAQ,KAAK,QAAQ,MAAM;AACjC,iBAAK;AACL,mBAAO,EAAE,OAAO,MAAM,MAAM;AAAA,UAChC;AAEA,cAAI,KAAK,YAAY;AACjB,iBAAK,QAAQ;AACb,gBAAI,KAAK,iBAAkB,OAAM,KAAK;AACtC,mBAAO,EAAE,OAAO,QAAgB,MAAM,KAAK;AAAA,UAC/C;AAGA,eAAK,mBAAmB,IAAI,2BAAoB;AAChD,gBAAM,KAAK;AAAA,QACf;AAAA,MACJ;AAAA,MAEA,QAAQ,MAAkC;AAItC,YAAI,KAAK,YAAY,CAAC,KAAK;AACvB,eAAK,YAAY;AAErB,aAAK,QAAQ;AACb,eAAO,QAAQ,QAAQ,EAAE,OAAO,QAAgB,MAAM,KAAK,CAAC;AAAA,MAChE;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,UAAgB;AACZ,QAAI,KAAK,UAAW;AACpB,SAAK,YAAY;AACjB,SAAK,aAAa;AAClB,QAAI,KAAK,SAAS,cAAc,QAAQ;AACpC,WAAK,KAAK,cAAc,WAAW,IAAI;AAAA,IAC3C;AACA,SAAK,gBAAgB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAMQ,UAAU,OAA2B;AACzC,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAClB,SAAK,mBAAmB;AACxB,SAAK,YAAY;AACjB,QAAI,KAAK,SAAS,cAAc;AAC5B,WAAK,KAAK,cAAc,WAAW,IAAI;AAC3C,SAAK,gBAAgB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,SAAuB;AACzC,IAAAA,UAAS,IAAI,WAAW,KAAK,GAAG,OAAO,KAAK,OAAO,EAAE;AACrD,SAAK,UAAU,IAAI,MAAM,OAAO,CAAC;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,mBAAmB,mBAAoC;AAC3D,QAAI,KAAK,cAAc;AACnB,aAAO;AAEX,WAAO,oBAAoB,KAAK,qBAAqB,KAAK,eAAe;AAAA,EAC7E;AAAA,EAEQ,iBAAyB;AAC7B,WAAO,IAAI,KAAK,aAAa,KAAK;AAAA,EACtC;AAAA,EAEQ,gBAAgB,mBAAmC;AACvD,WAAO,2CAA2C,iBAAiB,cACjD,KAAK,kBAAkB,gBAAgB,KAAK,UAAU;AAAA,EAC5E;AAAA,EAEQ,UAAU,OAAsB;AACpC,WAAO,IAAI;AAAA,MACP,uBAAuB,KAAK,cAAc,KAAK,kBAAkB;AAAA,IACrE;AAAA,EACJ;AAAA,EAEQ,kBAAwB;AAC5B,QAAI,KAAK,qBAAqB,MAAM;AAChC,WAAK,iBAAiB,QAAQ;AAC9B,WAAK,mBAAmB;AAAA,IAC5B;AAAA,EACJ;AAAA,EAEQ,cAAc,WAAyB;AAC3C,QAAI,KAAK,aAAa,EAAG;AACzB,UAAM,YAAY,KAAK,eAAe,KAAK;AAC3C,QAAI,aAAa,WAAW;AACxB,WAAK,SAAS,WAAW,KAAK;AAAA,IAClC;AAAA,EACJ;AAAA,EAEA,OAAwB,aAAa;AAAA,EAE7B,SAAS,WAAmB,WAA0B;AAC1D,SAAK,eAAe;AAGpB,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI,MAAM;AACN,YAAM,SAAS,YAAY,KAAK,GAAG,SAAS,WAAU;AACtD,WAAK,KAAK,IAAI,iBAAiB;AAAA,QAC3B;AAAA,QACA,KAAK,KAAK;AAAA,QACV,KAAK,GAAG;AAAA,QACR;AAAA,QACA;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAAA,EAEQ,cAAoB;AAGxB,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI,MAAM;AACN,WAAK,KAAK,IAAI,iBAAiB;AAAA,QAC3B;AAAA,QACA,KAAK,KAAK;AAAA,QACV,KAAK,GAAG;AAAA,QACR,KAAK,GAAG;AAAA,MACZ;AAAA,IACJ;AAAA,EACJ;AACJ;AAEA,SAAS,cAAc,OAAwB;AAC3C,SAAO,OAAO,cAAc,KAAK,KAAK,SAAS;AACnD;AAGA,SAAS,gBAAmB,OAA6C;AACrE,MAAI,OAAO,UAAU,WAAY,QAAO;AACxC,SACI,UAAU,QACV,OAAO,UAAU,YACjB,OAAO,iBAAkB;AAEjC;AAeO,SAAS,YAAe,OAAgB,MAAoC;AAC/E,QAAM,MAAM,eAAe,KAAK;AAChC,MAAI,QAAQ;AACR,WAAO;AAKX,SAAO,IAAI,UAAa,KAAK,IAAI;AACrC;;;AEvkBO,IAAM,yBAAN,MAA6B;AAAA,EACxB,WAAW,oBAAI,IAAwB;AAAA,EAE/C,SAAS,KAAuB;AAC5B,UAAM,UAAU,IAAI,GAAG;AACvB,UAAM,WAAW,KAAK,SAAS,IAAI,OAAO;AAC1C,QAAI,aAAa;AACb;AAIJ,QAAI,aAAa;AACb,eAAS,WAAW;AAExB,SAAK,SAAS,IAAI,SAAS,GAAG;AAAA,EAClC;AAAA,EAEA,IAAI,SAAyC;AACzC,WAAO,KAAK,SAAS,IAAI,OAAO;AAAA,EACpC;AAAA,EAEA,OAAiC;AAC7B,WAAO,KAAK,SAAS,KAAK;AAAA,EAC9B;AAAA,EAEA,WAAW,KAAuB;AAC9B,UAAM,UAAU,IAAI,GAAG;AACvB,QAAI,KAAK,SAAS,IAAI,OAAO,MAAM;AAC/B,WAAK,SAAS,OAAO,OAAO;AAAA,EACpC;AAAA,EAEA,gBAAsB;AAClB,eAAW,OAAO,KAAK,SAAS,OAAO,GAAG;AACtC,UAAI,WAAW;AAAA,IACnB;AACA,SAAK,SAAS,MAAM;AAAA,EACxB;AAAA,EAEA,eAAqB;AACjB,eAAW,OAAO,KAAK,SAAS,OAAO,GAAG;AACtC,UAAI,IAAI,eAAgB,KAAI,UAAU;AAAA,UACjC,KAAI,WAAW;AAAA,IACxB;AAAA,EACJ;AACJ;;;AC5CO,IAAM,yBAAN,MAA6B;AAAA,EACxB,UAAU;AAAA,EACV,WAAW,oBAAI,IAAwB;AAAA,EAE/C,SAAiB;AACb,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,SAAS,KAAuB;AAC5B,UAAM,UAAU,IAAI,GAAG;AACvB,QAAI,KAAK,SAAS,IAAI,OAAO;AACzB,YAAM,IAAI,MAAM,kCAAkC,OAAO,yBAAyB;AAEtF,SAAK,SAAS,IAAI,SAAS,GAAG;AAAA,EAClC;AAAA,EAEA,IAAI,SAAyC;AACzC,WAAO,KAAK,SAAS,IAAI,OAAO;AAAA,EACpC;AAAA,EAEA,OAAiC;AAC7B,WAAO,KAAK,SAAS,KAAK;AAAA,EAC9B;AAAA,EAEA,WAAW,KAAuB;AAC9B,UAAM,UAAU,IAAI,GAAG;AACvB,QAAI,KAAK,SAAS,IAAI,OAAO,MAAM;AAC/B,WAAK,SAAS,OAAO,OAAO;AAAA,EACpC;AAAA,EAEA,gBAAsB;AAClB,eAAW,OAAO,KAAK,SAAS,OAAO,GAAG;AACtC,UAAI,WAAW;AAAA,IACnB;AACA,SAAK,SAAS,MAAM;AAAA,EACxB;AACJ;;;AC6CA,IAAAI,eAA6F;;;AC7E7F,IAAAC,kBAAiC;AA4B1B,IAAe,yBAAf,MAAsC;AAAA;AAAA,EA4BzC,gBAAqC;AACjC,WAAO;AAAA,EACX;AAAA;AAAA,EAGA,gBAAqC;AACjC,WAAO;AAAA,EACX;AAAA;AAAA,EAGA,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA;AAAA,EAGP,OAAO,IAAI,KAAqC;AAC5C,WAAO,+BAA+B,QAAQ,IAAI,GAAG;AAAA,EACzD;AAAA;AAAA,EAGA,OAAO,OAAO,KAAiD;AAC3D,WAAO,+BAA+B,QAAQ,OAAO,GAAG;AAAA,EAC5D;AACJ;AASO,IAAM,6BAAN,cAAyC,uBAAuB;AAAA,EAC1D;AAAA,EACA,WAAW;AAAA,EACX,YAAY;AAAA,EAErB,YAAY,KAAa;AACrB,UAAM;AACN,SAAK,MAAM;AAAA,EACf;AAAA,EAEA,iBAAiB,SAAqB,MAA0B;AAC5D,WAAO,iBAAiB,SAAS,IAAI;AAAA,EACzC;AAAA,EAEA,uBAAuB,KAAqC;AACxD,WAAO,mBAAmB,GAAG;AAAA,EACjC;AAAA,EAEA,mBAA6C;AACzC,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAChE;AAAA,EAEA,uBAAmC;AAC/B,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAChE;AACJ;AAKO,IAAM,oCAAN,cAAgD,uBAAuB;AAAA,EACjE;AAAA,EACA,WAAW;AAAA,EACX,YAAY;AAAA,EAErB,YAAY,KAAa;AACrB,UAAM;AACN,SAAK,MAAM;AAAA,EACf;AAAA,EAEA,iBACI,SACA,MACA,SACU;AACV,WAAO,uBAAuB,SAAS,MAAM,OAAO;AAAA,EACxD;AAAA,EAEA,uBAAuB,KAAqC;AACxD,WAAO,mBAAmB,GAAG;AAAA,EACjC;AAAA,EAEA,iBACI,OACA,SACwB;AACxB,WAAO,iBAAiB,OAAO,OAAO;AAAA,EAC1C;AAAA,EAEA,qBAAqB,UAAoC;AACrD,WAAO,qBAAqB,QAAQ;AAAA,EACxC;AAAA,EAES,gBAAyB;AAC9B,WAAO,oBAAoB;AAAA,EAC/B;AAAA,EAES,gBAAyB;AAC9B,WAAO,IAAI,wBAAQ;AAAA,EACvB;AACJ;AAOO,IAAM,2CAAN,cAAuD,uBAAuB;AAAA,EACxE;AAAA,EACA,WAAW;AAAA,EACX,YAAY;AAAA,EAErB,YAAY,KAAa;AACrB,UAAM;AACN,SAAK,MAAM;AAAA,EACf;AAAA,EAEA,iBACI,SACA,MACA,SACA,UACU;AACV,WAAO;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,uBAAuB,KAAqC;AACxD,WAAO,mBAAmB,GAAG;AAAA,EACjC;AAAA,EAEA,iBACI,OACA,SACA,UACwB;AACxB,WAAO,wBAAwB,OAAO,UAAU,OAAO;AAAA,EAC3D;AAAA,EAEA,qBAAqB,UAAoC;AACrD,WAAO,qBAAqB,QAAQ;AAAA,EACxC;AAAA,EAES,gBAAyB;AAC9B,WAAO,oBAAoB;AAAA,EAC/B;AAAA,EAES,gBAAyB;AAC9B,WAAO,IAAI,wBAAQ;AAAA,EACvB;AACJ;AAOA,uBAAuB,eAAe,IAAI,2BAA2B,OAAO;AAC5E,uBAAuB,iBAAiB,IAAI,2BAA2B,SAAS;AAEhF,uBAAuB,mBAAmB,IAAI,2BAA2B,QAAQ;AACjF,uBAAuB,qBAAqB,IAAI,2BAA2B,UAAU;AAErF,uBAAuB,gBAAgB,IAAI,kCAAkC,UAAU;AAEvF,uBAAuB,iBAAiB,IAAI,yCAAyC,WAAW;AAKhG,uBAAuB,MAAM;AAAA,EACzB,uBAAuB;AAAA,EACvB,uBAAuB;AAAA,EACvB,uBAAuB;AAAA,EACvB,uBAAuB;AAAA,EACvB,uBAAuB;AAAA,EACvB,uBAAuB;AAC3B;AAUO,IAAM,iCAAN,MAAM,gCAA+B;AAAA,EACxC,OAAO,iBAAoD,uBAAuB;AAAA,EAClF,OAAO,UAAU,IAAI,gCAA+B,SAAS;AAAA,EAEpD;AAAA,EACA;AAAA,EAET,IAAI,gBAAwC;AACxC,WAAO,KAAK,IAAI,KAAK,gBAAgB;AAAA,EACzC;AAAA,EAEA,YAAY,kBAA0B,SAA4C;AAC9E,UAAM,MAAM,oBAAI,IAAoC;AACpD,eAAW,KAAK,WAAW,gCAA+B;AACtD,UAAI,IAAI,EAAE,KAAK,CAAC;AAEpB,QAAI,CAAC,IAAI,IAAI,gBAAgB;AACzB,YAAM,IAAI,MAAM,uBAAuB,gBAAgB,IAAI;AAE/D,SAAK,mBAAmB;AACxB,SAAK,UAAU;AAAA,EACnB;AAAA,EAEA,IAAI,KAAqC;AACrC,QAAI,gBAAgB,GAAG;AACnB,YAAM,IAAI,MAAM,yBAAyB,GAAG,2DAA2D;AAE3G,UAAM,SAAS,KAAK,OAAO,GAAG;AAC9B,QAAI,CAAC;AACD,YAAM,IAAI,MAAM,uBAAuB,GAAG,IAAI;AAClD,WAAO;AAAA,EACX;AAAA,EAEA,OAAO,KAAiD;AACpD,QAAI,CAAC,IAAK,OAAM,KAAK;AACrB,QAAI,gBAAgB,GAAG;AACnB,YAAM,IAAI,MAAM,yBAAyB,GAAG,2DAA2D;AAC3G,WAAO,KAAK,QAAQ,IAAI,GAAG;AAAA,EAC/B;AACJ;AAGA,SAAS,gBAAgB,KAAsB;AAC3C,SAAO,IAAI,WAAW,SAAS;AACnC;;;ACjSA,IAAMC,eAAc,IAAI,YAAY;AAW7B,SAAS,4BAAqC;AACjD,SAAO,OAAO,WAAW,eAAgB,OAAO,WAAwC;AAC5F;AAIA,eAAsB,sBAClB,QAAgB,UAAkB,aACnB;AACf,QAAM,MAAM,MAAM,OAAO,OAAO;AAAA,IAC5B;AAAA,IAAOA,aAAY,OAAO,MAAM;AAAA,IAAG,EAAE,MAAM,QAAQ,MAAM,UAAU;AAAA,IAAG;AAAA,IAAO,CAAC,MAAM;AAAA,EAAC;AACzF,QAAM,YAAY,MAAM,OAAO,OAAO;AAAA,IAClC;AAAA,IAAQ;AAAA,IAAKA,aAAY,OAAO,GAAG,QAAQ;AAAA,EAAK,WAAW,EAAE;AAAA,EAAC;AAClE,SAAO,gBAAgB,IAAI,WAAW,SAAS,CAAC;AACpD;;;AF+EA,SAAS,mBAAkC;AACvC,SAAO,IAAI,QAAc,aAAW,WAAW,SAAS,CAAC,CAAC;AAC9D;AAIO,IAAM,oCAAoC;AAMjD,IAAM,EAAE,UAAAC,WAAU,SAAS,SAAAC,UAAS,UAAAC,UAAS,IAAI,QAAQ,SAAS;AAS3D,IAAM,+BAAyD,OAAM,SAAQ;AAChF,QAAM,YAAY,KAAK,oBAAoB;AAG3C,QAAM,QAAQ,MAAM,KAAK,sBAAsB;AAC/C,MAAI;AACA,UAAM,MAAM,IAAI,IAAI,KAAK,GAAG;AAC5B,QAAI,aAAa,IAAI,YAAY,KAAK,QAAQ;AAC9C,QAAI,aAAa,IAAI,KAAK,SAAS;AACnC,QAAI,UAAU,QAAW;AACrB,UAAI,aAAa,IAAI,KAAK,MAAM,OAAO;AACvC,UAAI,aAAa,IAAI,KAAK,MAAM,KAAK;AAAA,IACzC;AACA,WAAO,IAAI,SAAS;AAAA,EACxB,QAAQ;AAEJ,UAAM,MAAM,KAAK,IAAI,SAAS,GAAG,IAAI,MAAM;AAC3C,QAAI,MAAM,KAAK,MAAM,MAAM,YAAY,KAAK,QAAQ,MAAM,SAAS;AACnE,QAAI,UAAU;AACV,aAAO,MAAM,MAAM,OAAO,MAAM,MAAM,KAAK;AAC/C,WAAO;AAAA,EACX;AACJ;AAKA,IAAM,yBAAyB,CAAC,WAAW,YAAY,GAAG;AAGnD,IAAI,cAAc,CAAC,QAAwB;AAC9C,MAAI;AACA,UAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,QAAI,aAAa;AACjB,eAAW,QAAQ,wBAAwB;AACvC,UAAI,CAAC,OAAO,aAAa,IAAI,IAAI;AAC7B;AAEJ,aAAO,aAAa,IAAI,MAAM,YAAY;AAC1C,mBAAa;AAAA,IACjB;AACA,WAAO,aAAa,OAAO,SAAS,IAAI;AAAA,EAC5C,QAAQ;AACJ,WAAO,IAAI,QAAQ,wCAAwC,cAAc;AAAA,EAC7E;AACJ;AAGA,SAAS,mBAAmB,KAAiC;AACzD,MAAI;AACA,UAAM,IAAI,IAAI,IAAI,GAAG,EAAE,aAAa,IAAI,GAAG;AAC3C,WAAO,KAAK;AAAA,EAChB,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AA2BO,IAAe,UAAf,MAAuB;AAAA,EACjB;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA,KAAa,OAAO,WAAW;AAAA,EAC/B,gBAAgB,IAAI,uBAAuB;AAAA,EAC3C,eAAe,IAAI,sBAAsB;AAAA,EACzC,gBAAgB,IAAI,uBAAuB;AAAA,EAC3C,gBAAgB,IAAI,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA,EAM3C,yBAAyB,IAAI,6BAAoC;AAAA;AAAA;AAAA;AAAA,EAKhE,qBAAqB;AAAA;AAAA;AAAA,EAGrB,sBAAsB;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe;AAAA,EACf,mBAAuC;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA,mBAAmB;AAAA;AAAA,EAEnB,kBAAkB;AAAA;AAAA;AAAA,EAG1B;AAAA;AAAA;AAAA,EAGA;AAAA,EAEU,YAAY,KAAa,KAAa;AAC5C,SAAK,MAAM;AACX,SAAK,MAAM;AACX,SAAK,oBAAoB,IAAI,OAAO;AACpC,SAAK,qBAAqB,IAAI,OAAO;AACrC,SAAK,aAAa,sBAAsB,IAAI,OAAO;AACnD,UAAM,cAAc,IAAI,OAAO;AAC/B,QAAI,cAAc,KAAK,OAAO,SAAS,WAAW,GAAG;AACjD,WAAK,iBAAiB,YAAY,MAAM,KAAK,uBAAuB,GAAG,WAAW;AAElF,MAAC,KAAK,eAA0C,QAAQ;AAAA,IAC5D;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,yBAA+B;AACnC,UAAM,MAAM,KAAK,IAAI;AAIrB,UAAM,gBAAgB,KAAK,iBACnB,KAAK,qBAAqB,KAAK,MAAM,KAAK,oBAAoB,KAAK;AAC3E,eAAW,QAAQ,CAAC,GAAG,KAAK,cAAc,OAAO,CAAC,GAAG;AACjD,YAAM,WAAW,KAAK;AACtB,UAAI,aAAa,UAAa,KAAK,OAAO;AACtC;AAEJ,UAAI;AACJ,UAAI,CAAC,KAAK,cAAc;AAKpB,cAAM,QAAQ,KAAK,IAAI,KAAK,iBAAiB,KAAK,SAAS;AAC3D,YAAI,MAAM,SAAS,SAAS;AACxB,qBAAW,oBAAoB,SAAS,gBAAgB;AAAA,MAChE,WAAW,KAAK,WAAW,GAAG;AAC1B,YAAI,MAAM,KAAK,aAAa,SAAS;AACjC,qBAAW,oBAAoB,SAAS,gBAAgB;AAAA,MAChE,WAAW,iBAAiB,MAAM,KAAK,UAAU,SAAS;AACtD,mBAAW,gBAAgB,SAAS,YAAY;AACpD,UAAI,aAAa;AACb;AAEJ,WAAK,cAAc,OAAO,KAAK,MAAM;AACrC,WAAK,OAAO,OAAO,IAAI,0BAAa,IAAI,KAAK,GAAG,WAAW,KAAK,MAAM,IAAI,KAAK,MAAM,qBAAgB,QAAQ,EAAE,CAAC;AAChH,WAAK,aAAa;AAAA,IACtB;AAAA,EACJ;AAAA,EAEA,IAAI,aAAwC;AACxC,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,IAAI,iBAA4C;AAC5C,WAAO,KAAK,eAAe,KAAK,cAAc;AAAA,EAClD;AAAA,EAEA,IAAI,kBAAsC;AACtC,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,IAAI,cAAuB;AACvB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,IAAI,oBAA4B;AAC5B,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKO,oBAA6B;AAChC,QAAI,KAAK;AACL,aAAO;AAEX,SAAK,sBAAsB;AAC3B,WAAO;AAAA,EACX;AAAA,EAEU,wBAAgC;AACtC,SAAK,sBAAsB;AAC3B,WAAO,EAAE,KAAK;AAAA,EAClB;AAAA,EAEU,oBAAoB,OAAiC;AAC3D,QAAI,KAAK,qBAAqB,MAAO;AACrC,UAAM,gBAAgB,KAAK;AAC3B,SAAK,mBAAmB;AASxB,QAAI,UAAU,mBAA8B;AACxC,WAAK,eAAe;AACpB,WAAK,kBAAkB;AACvB,WAAK,sBAAsB;AAAA,IAC/B,OAAO;AACH,UAAI,kBAAkB,mBAA8B;AAChD,aAAK,eAAe;AACpB,aAAK,kBAAkB,KAAK,IAAI;AAAA,MACpC;AAKA,WAAK,yBAAyB;AAAA,IAClC;AACA,SAAK,uBAAuB,QAAQ,KAAK;AAAA,EAC7C;AAAA;AAAA;AAAA,EAIA,gBAA+B;AAC3B,QAAI,KAAK,qBAAqB;AAC1B,aAAO,QAAQ,QAAQ;AAC3B,WAAO,IAAI,QAAc,aAAW;AAChC,YAAM,UAAU,CAAC,UAAoC;AACjD,YAAI,UAAU,kBAA8B;AAC5C,aAAK,uBAAuB,OAAO,OAAO;AAC1C,gBAAQ;AAAA,MACZ;AACA,WAAK,uBAAuB,IAAI,OAAO;AAAA,IAC3C,CAAC;AAAA,EACL;AAAA,EAEU,gBAAgB,MAA2B;AACjD,SAAK,cAAc;AAEnB,SAAK,gBAAgB,IAAI,SAAO,KAAK,eAAe,GAAG,CAAC;AACxD,SAAK,OAAO,IAAI,QAAM;AAMlB,UAAI,KAAK,gBAAgB,KAAM;AAC/B,WAAK,cAAc;AAQnB,UAAI,KAAK;AACL,aAAK,kBAAkB,KAAK,IAAI;AACpC,WAAK,eAAe;AAEpB,UAAI,GAAG;AACH,iBAAS,IAAI,IAAI,KAAK,GAAG,oBAAoB,GAAG,MAAM,UAAU,GAAG,IAAI,GAAG;AAAA;AAE1E,iBAAS,IAAI,IAAI,KAAK,GAAG,iBAAiB;AAE9C,WAAK,oBAAoB,oBAA+B;AAAA,IAC5D,CAAC;AAED,SAAK,KAAK,cAAc,KAAK,MAAM;AAG/B,eAAS,IAAI,IAAI,KAAK,GAAG,YAAY;AAAA,IACzC,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ;AAAA,EAER,IAAI,sBAA8C;AAC9C,WAAO,KAAK,wBAAwB,+BAA+B,QAAQ;AAAA,EAC/E;AAAA,EAEA,IAAI,oBAAoB,OAA+B;AACnD,SAAK,uBAAuB;AAAA,EAChC;AAAA,EAEA,KACI,QACA,MACA,SACe;AACf,UAAM,SAAS,KAAK,cAAc,OAAO;AACzC,UAAM,eAAe,SAAS,sBACxB,QAAQ,oBAAoB,QAAQ,MAAM,IAC1C,IAAI,gBAAgB,QAAQ,QAAQ,SAAS,uBAAuB,CAAC;AAE3E,UAAM,WAAuB;AAAA,MACzB,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,UAAU,SAAS,cAAc;AAAA,IACrC;AACA,iBAAa,qBAAqB,KAAK,oBAAoB;AAAA,MACvD;AAAA,MACA;AAAA,MACA,KAAK,aAAa;AAAA,MAClB,KAAK,IAAI;AAAA,IACb;AACA,iBAAa,WAAW,SAAS;AAKjC,SAAK,cAAc,SAAS,YAAY;AACxC,QAAI,KAAK,cAAc;AACnB,mBAAa,SAAS,KAAK,IAAI;AAC/B,WAAK,cAAc,aAAa,kBAAkB;AAAA,IACtD,OAAO;AAIH,YAAM,eAAe,CAAC,UAAoC;AACtD,YAAI,UAAU,kBAA8B;AAC5C,aAAK,uBAAuB,OAAO,YAAY;AAC/C,YAAI,aAAa,OAAO,YAAa;AACrC,qBAAa,SAAS,KAAK,IAAI;AAC/B,aAAK,cAAc,aAAa,kBAAkB;AAAA,MACtD;AACA,WAAK,uBAAuB,IAAI,YAAY;AAG5C,mBAAa,OACR,KAAK,MAAM,KAAK,uBAAuB,OAAO,YAAY,CAAC,EAC3D,MAAM,MAAM,KAAK,uBAAuB,OAAO,YAAY,CAAC;AAAA,IACrE;AAGA,UAAM,SAAS,SAAS;AACxB,QAAI,WAAW,QAAW;AACtB,YAAM,UAAU,MAAM;AAClB,YAAI,KAAK,cAAc,OAAO,MAAM,MAAM,QAAW;AACjD,uBAAa,OAAO,WAAO,gCAAkB,iBAAiB,CAAC;AAC/D,uBAAa,aAAa;AAG1B,gBAAM,OAAO,KAAK;AAClB,cAAI,SAAS;AACT,iBAAK,IAAI,iBAAiB,OAAO,MAAM,KAAK,qBAAqB,MAAM;AAAA,QAC/E;AAAA,MACJ;AACA,aAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAExD,mBAAa,OACR,KAAK,MAAM,OAAO,oBAAoB,SAAS,OAAO,CAAC,EACvD,MAAM,MAAM,OAAO,oBAAoB,SAAS,OAAO,CAAC;AAAA,IACjE;AAEA,WAAO;AAAA,EACX;AAAA,EAEA,WAAW,QAAgB,MAAwB;AAC/C,QAAI,CAAC,KAAK,aAAc;AAGxB,UAAM,OAAO,KAAK;AAClB,UAAM,WAAuB,EAAE,QAAQ,QAAQ,WAAW,EAAE;AAC5D,UAAM,WAAW,KAAK,oBAAoB;AAAA,MACtC;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,KAAK,IAAI;AAAA,IACb;AACA,SAAK,cAAc,QAAQ;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAmB;AACf,SAAK,aAAa,MAAM;AAAA,EAC5B;AAAA,EAEA,QAAc;AAEV,aAAS,IAAI,IAAI,KAAK,GAAG,aAAa;AAItC,QAAI,KAAK,mBAAmB,QAAW;AACnC,oBAAc,KAAK,cAAc;AACjC,WAAK,iBAAiB;AAAA,IAC1B;AACA,SAAK,cAAc,cAAc;AACjC,SAAK,cAAc,cAAc;AACjC,SAAK,cAAc,cAAU,gCAAkB,cAAc,CAAC;AAC9D,SAAK,cAAc,cAAc;AACjC,SAAK,aAAa,MAAM;AACxB,SAAK,aAAa,MAAM;AACxB,SAAK,IAAI,MAAM,OAAO,KAAK,GAAG;AAC9B,SAAK,oBAAoB,oBAA+B;AAExD,aAAS,IAAI,IAAI,KAAK,GAAG,YAAY;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKU,cAAc,MAAiC;AACrD,QAAI,CAAC,KAAK,gBAAgB,KAAK,gBAAgB,OAAW;AAC1D,QAAI,OAAO,SAAS,UAAU;AAC1B,WAAK,YAAY,KAAK,IAAI;AAAA,IAC9B,OAAO;AACH,WAAK,YAAY,WAAW,IAAI;AAAA,IACpC;AAAA,EACJ;AAAA;AAAA,EAGU,qBAAqB,YAAmC;AAAA,EAGlE;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,UAAoC;AACvD,QAAI;AACA,WAAK,gBAAgB,QAAQ;AAAA,IACjC,SACO,GAAG;AAEN,MAAAA,WAAU,IAAI,IAAI,KAAK,GAAG,yCAAyC,UAAU,CAAC;AAAA,IAClF;AAAA,EACJ;AAAA,EAEQ,gBAAgB,UAAoC;AACxD,QAAI;AACJ,QAAI;AACJ,QAAI,SAAS,SAAS,UAAU;AAC5B,gBAAU,SAAS;AACnB,aAAO,SAAS;AAAA,IACpB,OAAO;AACH,YAAM,SAAS,mBAAmB,SAAS,GAAG;AAC9C,gBAAU,OAAO;AACjB,aAAO,OAAO;AAAA,IAClB;AACA,UAAM,SAAS,QAAQ,UAAU;AACjC,IAAAF,WAAU,IAAI,IAAI,KAAK,GAAG,SAAS,MAAM,IAAI,QAAQ,aAAa,CAAC,EAAE;AAGrE,QAAI,WAAW,eAAe,WAAW;AACrC,YAAM,MAAM,KAAK,CAAC;AAClB,UAAI,QAAQ,QAAW;AAInB,cAAM,MAAO,OAAO,CAAC;AACrB,cAAM,YAA6B,MAAM,QAAQ,GAAG,IAC9C;AAAA,UACE,cAAc,IAAI,CAAC;AAAA,UACnB,aAAa,IAAI,CAAC;AAAA,UAClB,iBAAiB,IAAI,CAAC;AAAA,UACtB,OAAO,IAAI,CAAC;AAAA,UACZ,QAAQ,IAAI,CAAC;AAAA,QACjB,IACE;AAAA,UACE,cAAe,IAAI,gBAAgB,IAAI;AAAA,UACvC,aAAc,IAAI,eAAe,IAAI;AAAA,UACrC,iBAAkB,IAAI,mBAAmB,IAAI;AAAA,UAC7C,OAAQ,IAAI,SAAS,IAAI;AAAA,UACzB,QAAS,IAAI,UAAU,IAAI;AAAA,QAC/B;AACJ,aAAK,qBAAqB,SAAS;AAAA,MACvC;AACA;AAAA,IACJ;AAGA,QAAI,OAAO,WAAW,MAAM,GAAG;AAC3B,WAAK,IAAI,kBAAkB,OAAO,SAAS,MAAM,IAAI;AACrD;AAAA,IACJ;AAGA,SAAK,eAAe,SAAS,IAAI;AAAA,EACrC;AAAA,EAEQ,eAAe,SAAqB,MAAuB;AAC/D,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,YAAY,QAAQ,aAAa;AACvC,UAAM,YAAY,KAAK,IAAI,YAAY,aAAa,MAAM;AAC1D,UAAM,WAAW,WAAW,WAAW;AACvC,UAAM,cAAc,KAAK,IAAI;AAE7B,UAAM,WAAW,OAAOG,UAAoD;AACxE,UAAI;AACA,cAAM,UACF,KAAK,gBAAgB,SACf;AAAA,UACE,eAAe;AAAA,UACf,QAAQ;AAAA,UACR,UAAU,QAAQ,YAAY;AAAA,UAC9B,YAAY,KAAK;AAAA,UACjB,QAAQA,OAAM;AAAA,UACd,MAAM;AAAA,QACV,IACE;AACV,cAAM,SAAS,MAAM,YAAY,SAAS,QAAQ,MAAM,OAAO;AAC/D,YAAI,WAAW,WAAW,MAAM;AAC5B,cAAI,KAAK,gBAAgB,QAAW;AAGhC,kBAAM,SAAS,kBAAkB,YAC3B,SACA,IAAI,UAAmB,MAAgC;AAC7D,kBAAM,MAAM,OAAO,MAAM,IAAI;AAC7B,kBAAM,OAAO,MAAY;AAGrB,kBAAIA,OAAM,gBAAgB,KAAM;AAGhC,oBAAM,OAAO,KAAK;AAClB,kBAAI,SAAS;AACT,qBAAK,IAAI,iBAAiB;AAAA,kBACtB;AAAA,kBAAM,KAAK;AAAA,kBAAqB;AAAA,kBAAW;AAAA,gBAAG;AAAA,YAC1D;AACA,gBAAIA,UAAS,QAAW;AACpB,cAAAA,MAAK,UAAU,IAAI;AACnB,mBAAK,aAAa,cAAcA,KAAI;AAAA,YACxC;AACA,iBAAK;AAAA,UACT;AAAA,QACJ,WAAWA,UAAS,QAAW;AAC3B,gBAAM,OAAO,MAAY;AACrB,gBAAIA,MAAK,YAAa;AACtB,kBAAM,OAAO,KAAK;AAClB,gBAAI,SAAS;AACT,mBAAK,IAAI,iBAAiB;AAAA,gBACtB;AAAA,gBAAM,KAAK;AAAA,gBAAqB;AAAA,gBAAW;AAAA,cAAM;AAAA,UAC7D;AACA,UAAAA,MAAK,UAAU,IAAI;AACnB,eAAK,aAAa,cAAcA,KAAI;AACpC,eAAK;AAAA,QACT;AAAA,MACJ,SAAS,GAAG;AACR,YAAIA,UAAS,QAAW;AACpB,gBAAM,OAAO,MAAY;AACrB,gBAAIA,MAAK,YAAa;AACtB,kBAAM,OAAO,KAAK;AAClB,gBAAI,SAAS;AACT,mBAAK,IAAI,iBAAiB;AAAA,gBACtB;AAAA,gBAAM,KAAK;AAAA,gBAAqB;AAAA,gBAAW;AAAA,cAAC;AAAA,UACxD;AACA,UAAAA,MAAK,UAAU,IAAI;AACnB,eAAK,aAAa,cAAcA,KAAI;AACpC,eAAK;AAAA,QACT;AACA,QAAAH,WAAU,IAAI,IAAI,KAAK,GAAG,2BAA2B,MAAM,IAAI,SAAS,IAAI,CAAC;AAAA,MACjF;AAAA,IACJ;AAGA,QAAI,UAAU;AACV,WAAK,SAAS,MAAS;AACvB;AAAA,IACJ;AAKA,UAAM,OAAO,IAAI,eAAe,WAAW,QAAQ,IAAI;AACvD,UAAM,WAAW,KAAK,aAAa,cAAc,IAAI;AACrD,QAAI,aAAa,MAAM;AACnB,eAAS,aAAa;AACtB;AAAA,IACJ;AACA,SAAK,SAAS,IAAI;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,wBAA8B;AAElC,SAAK,oBAAoB,YAAY,MAAM;AAGvC,YAAM,OAAO,KAAK;AAClB,UAAI,SAAS,QAAW;AAMpB,aAAK,IAAI,iBAAiB,UAAU,MAAM,KAAK,qBAAqB;AAAA,UAChE,GAAG,KAAK,cAAc,KAAK;AAAA,QAC/B,CAAC;AAAA,MACL;AAAA,IACJ,GAAG,KAAK,iBAAiB;AAEzB,QAAI,KAAK,uBAAuB;AAC5B,mBAAa,KAAK,kBAAkB;AACxC,SAAK,qBAAqB,WAAW,MAAM;AACvC,WAAK,qBAAqB;AAC1B,UAAI,KAAK,gBAAgB,OAAW;AACpC,MAAAC,UAAS;AAAA,QACL,IAAI,KAAK,GAAG,wBAAwB,KAAK,kBAAkB;AAAA,MAA+B;AAC9F,WAAK,YAAY,MAAM;AAAA,IAC3B,GAAG,KAAK,kBAAkB;AAAA,EAC9B;AAAA;AAAA;AAAA,EAIQ,2BAAiC;AACrC,QAAI,KAAK,oBAAoB,QAAW;AACpC,oBAAc,KAAK,eAAe;AAClC,WAAK,kBAAkB;AAAA,IAC3B;AACA,QAAI,KAAK,uBAAuB,QAAW;AACvC,mBAAa,KAAK,kBAAkB;AACpC,WAAK,qBAAqB;AAAA,IAC9B;AAAA,EACJ;AAAA;AAAA;AAAA,EAIA,0BAAgC;AAC5B,SAAK,mBAAmB,KAAK,IAAI;AACjC,QAAI,KAAK,uBAAuB;AAC5B,WAAK,sBAAsB;AAAA,EACnC;AACJ;AAOO,IAAW,qBAAX,kBAAWG,wBAAX;AACH,EAAAA,wCAAA,kBAAe,KAAf;AACA,EAAAA,wCAAA,gBAAa,KAAb;AACA,EAAAA,wCAAA,iBAAc,KAAd;AACA,EAAAA,wCAAA,eAAY,KAAZ;AAJc,SAAAA;AAAA,GAAA;AAUX,IAAW,oBAAX,kBAAWC,uBAAX;AACH,EAAAA,sCAAA,eAAY,KAAZ;AACA,EAAAA,sCAAA,wBAAqB,KAArB;AACA,EAAAA,sCAAA,aAAU,KAAV;AAHc,SAAAA;AAAA,GAAA;AAMlB,IAAM,sBAAsB,CAAC,aAAa,sBAAsB,SAAS;AAGlE,IAAM,gBAAN,cAA4B,QAAQ;AAAA,EAC/B,YAAY;AAAA;AAAA;AAAA,EAGH,kBAAkB,IAAI,gBAAgB;AAAA,EAC/C,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMZ,wBAAwB;AAAA;AAAA;AAAA;AAAA,EAIxB;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB;AAAA;AAAA,EAGnB;AAAA;AAAA,EAET,wBACI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMJ;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA,EACS,cAAc,IAAI,6BAAsB;AAAA;AAAA,EAExC,oBAAoB,IAAI,6BAAoC;AAAA,EAC5D,sBAAsB,IAAI,6BAAsB;AAAA,EAEzD,IAAI,eAAuB;AACvB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEQ,iBAAiB,OAAqB;AAC1C,QAAI,KAAK,kBAAkB,MAAO;AAClC,SAAK,gBAAgB;AACrB,SAAK,oBAAoB,QAAQ;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,YAAY,KAAa,KAAa,YAAY,MAAM;AACpD,UAAM,KAAK,GAAG;AACd,SAAK,mBAAmB,IAAI,OAAO;AACnC,SAAK,qBAAqB,IAAI,OAAO;AACrC,SAAK,+BAA+B,IAAI,OAAO;AAC/C,SAAK,WAAW,gBAAgB,KAAK,EAAE;AACvC,UAAM,WAAW,+BAA+B;AAChD,SAAK,sBAAsB,SAAS,IAAI,mBAAmB,GAAG,KAAK,SAAS,gBAAgB;AAC5F,QAAI;AACA,WAAK,MAAM;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,MAAqB,gBAAgB,MAAY;AACzD,UAAM,YAAY,KAAK,cAAc,aAAa;AAClD,SAAK,gBAAgB,IAAI;AASzB,UAAM,gBAAgB,gBAChB,SACC,KAAK,oBAAoB,IAAI,2BAA+B;AAGnE,SAAK,IAAI,iBAAiB;AAAA,MACtB;AAAA,MAAM,KAAK;AAAA,MAAqB,KAAK;AAAA,MAAI,KAAK,IAAI;AAAA,MAClD,KAAK,sBAAsB;AAAA,IAAC;AAChC,SAAK,eAAe;AACpB,SAAK,uBAAuB,QAAQ,iBAA4B;AAChE,QAAI,kBAAkB,QAAW;AAE7B,WAAK,KAAK,WAAW,WAAW,IAAI;AACpC;AAAA,IACJ;AAQA,UAAM,aAAa,IAAI,2BAAoB;AAC3C,SAAK,OAAO,IAAI,MAAM,WAAW,QAAQ,CAAC;AAC1C,SAAK,QAAQ,KAAK,CAAC,eAAe,UAAU,CAAC,EAAE,KAAK,MAAM;AACtD,WAAK,oBAAoB;AACzB,aAAO,KAAK,WAAW,WAAW,KAAK;AAAA,IAC3C,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAc;AACV,SAAK,KAAK,IAAI;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,wBAA0E;AAC5E,UAAM,SAAS,KAAK;AACpB,QAAI,WAAW;AACX,aAAO;AAQX,QAAI,CAAC,0BAA0B,GAAG;AAC9B,oCAA8B,KAAK,KAAK,kDAAkD;AAC1F,aAAO;AAAA,IACX;AAEA,UAAM,WAAW,KAAK,oBAAoB,GAAG,SAAS;AACtD,QAAI;AACA,YAAM,QAAQ,MAAM,sBAAsB,QAAQ,KAAK,UAAU,OAAO;AACxE,WAAK;AACL,aAAO,EAAE,SAAS,MAAM;AAAA,IAC5B,SAAS,GAAG;AAIR,oCAA8B,KAAK,KAAK,uBAAuB,OAAO,CAAC,CAAC,EAAE;AAC1E,aAAO;AAAA,IACX;AAAA,EACJ;AAAA;AAAA;AAAA,EAIU,MAAqB;AAC3B,WAAO,KAAK,gBAAgB,KAAK,SAAS;AAAA,EAC9C;AAAA,EAEA,MAAc,WAA0B;AACpC,QAAI,KAAK,UAAW;AAEpB,aAAS,IAAI,IAAI,KAAK,GAAG,qBAAqB;AAC9C,QAAI;AAEA,aAAO,CAAC,KAAK,WAAW;AAIpB,aAAK,oBAAoB,kBAA6B;AAKtD,cAAM,aAAa,KAAK,IAAI,iBAAiB,SAAS,KAAK,WAAW,KAAK,gBAAgB,MAAM;AACjG,YAAI,WAAW,iBAAiB;AAC5B,UAAAJ,UAAS,IAAI,IAAI,KAAK,GAAG,6CAA6C;AACtE,eAAK,YAAY;AACjB;AAAA,QACJ;AACA,aAAK,iBAAiB,WAAW,MAAM;AACvC,YAAI;AACA,gBAAM,WAAW;AAAA,QACrB,QAAQ;AAAA,QAGR,UAAE;AACE,eAAK,iBAAiB,CAAC;AAAA,QAC3B;AAEA,YAAI,KAAK,UAAW;AAEpB,YAAI,gBAAgB;AACpB,YAAI,kBAAkB;AAGtB,YAAI,cAAc;AAClB,YAAI;AACA,gBAAM,UAAU,MAAM,KAAK,sBAAsB,IAAI;AAErD,mBAAS,IAAI,IAAI,KAAK,GAAG,oBAAoB,YAAY,OAAO,CAAC,EAAE;AACnE,gBAAM,KACF,KAAK,mBAAmB,OAAO,KAC9B,IAAI,UAAU,OAAO;AAC1B,gBAAM,OAAO,IAAI;AAAA,YACb;AAAA,YACA,KAAK,oBAAoB;AAAA,YACzB,KAAK;AAAA,YACL,KAAK,IAAI;AAAA,UACb;AAQA,gBAAM,aAAa,IAAI,2BAAgD;AACvE,gBAAM,mBAAmB,IAAI,gBAAgB;AAC7C,eAAK,OAAO,IAAI,QAAM;AAClB,4BAAgB,GAAG;AACnB,8BAAkB,GAAG;AACrB,uBAAW,QAAQ,EAAE;AACrB,6BAAiB,MAAM;AAAA,UAC3B,CAAC;AAID,gBAAM,kBAAkB,WAAW,KAAY,MAAM;AACjD,kBAAM,IAAI,MAAM,mBAAmB;AAAA,UACvC,CAAC;AACD,0BAAgB,MAAM,MAAM;AAAA,UAAqE,CAAC;AAKlG,eAAK,oBAAoB,IAAI,2BAA+B;AAC5D,eAAK,gBAAgB,IAAI;AAazB,gBAAM,mBAAmB,KAAK;AAC9B,cAAI;AACA,sBAAM;AAAA,cACF,QAAQ,KAAK,CAAC,KAAK,eAAe,eAAe,CAAC;AAAA,cAClD;AAAA,cACA;AAAA,YAAiB;AAAA,UACzB,SAAS,GAAG;AACR,gBAAI,aAAa,2BAAc;AAC3B,cAAAA,UAAS,IAAI,IAAI,KAAK,GAAG,8BAA8B,gBAAgB,IAAI;AAI3E,mBAAK,MAAM;AAAA,YACf;AACA,kBAAM;AAAA,UACV;AAIA,eAAK,oBAAoB,mBAA8B;AAGvD,cAAI;AACA,iBAAK,IAAI,iBAAiB;AAAA,cACtB;AAAA,cACA,KAAK;AAAA,cACL,KAAK;AAAA,cACL,KAAK,IAAI;AAAA,cACT,KAAK,sBAAsB;AAAA,YAC/B;AAAA,UACJ,SACO,GAAG;AAEN,YAAAA,UAAS,IAAI,IAAI,KAAK,GAAG,+BAA+B,CAAC;AACzD,kBAAM;AAAA,UACV;AAMA,gBAAM,qBAAqB,KAAK;AAChC,cAAI;AACJ,cAAI;AACA,8BAAkB,UAAM;AAAA,cACpB,QAAQ,KAAK,CAAC,KAAK,mBAAmB,eAAe,CAAC;AAAA,cACtD;AAAA,cACA;AAAA,YAAmB;AAAA,UAC3B,SAAS,GAAG;AACR,gBAAI,aAAa,2BAAc;AAC3B,cAAAA,UAAS,IAAI,IAAI,KAAK,GAAG,gCAAgC,kBAAkB,IAAI;AAG/E,mBAAK,MAAM;AAAA,YACf;AACA,kBAAM;AAAA,UACV,UAAE;AACE,iBAAK,oBAAoB;AAAA,UAC7B;AAGA,eAAK,wBAAwB,gBAAgB,SAAS;AAKtD,cAAI,gBAAgB;AAChB,iBAAK,mBAAmB,gBAAgB;AAG5C,gBAAM,iBAAiB,KAAK,kBAAkB,eAAe;AAE7D,mBAAS;AAAA,YACL,IAAI,KAAK,GAAG,0CAA0C,oBAAoB,cAAc,CAAC;AAAA,UAAE;AAK/F,gBAAM,YAAY,KAAK,cAAc,aAAa;AAClD,eAAK,oBAAoB,iBAA4B;AACrD,wBAAc,KAAK,IAAI;AAOvB,cAAI,mBAAmB;AACnB,kBAAM,KAAK;AAAA,cACP;AAAA,cACA,mBAAmB;AAAA,cACnB,iBAAiB;AAAA,YAAM;AAI/B,gBAAM;AAAA,QACV,SAAS,GAAG;AAER,UAAAD,WAAU,IAAI,IAAI,KAAK,GAAG,8BAA8B,CAAC;AAAA,QAC7D;AAKA,aAAK,oBAAoB,oBAA+B;AAExD,YAAI,KAAK,UAAW;AAGpB,YAAI,kBAAkB,mCAAmC;AAErD,UAAAC,UAAS,IAAI,IAAI,KAAK,GAAG,qDAAqD;AAC9E,eAAK,kBAAkB,QAAQ,EAAE,QAAQ,gBAAgB,CAAC;AAC1D,eAAK,YAAY;AACjB;AAAA,QACJ;AAOA,YAAI,gBAAgB,KACb,KAAK,IAAI,IAAI,eAAe,KAAK;AACpC,eAAK,YAAY;AAErB,aAAK;AAAA,MACT;AAAA,IACJ,UAAE;AACE,WAAK,oBAAoB,oBAA+B;AAAA,IAC5D;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBA,MAAc,WACV,WACA,eACA,cACa;AACb,QAAI,KAAK,gBAAgB,OAAW;AASpC,QAAI,eAAe;AACf,WAAK,cAAc,cAAc;AACjC,WAAK,cAAc,cAAc;AAAA,IACrC,OAAO;AACH,WAAK,cAAc,aAAa;AAAA,IACpC;AAGA,UAAM,WAA8B,CAAC;AACrC,eAAW,QAAQ,WAAW;AAC1B,YAAM,OAAO,KAAK;AAClB,UAAI,EAAE,OAAO,IAAI;AACb,YAAI,CAAC,KAAK,OAAO;AACb,eAAK,OAAO,OAAO,IAAI;AAAA,YACnB;AAAA,UAAmE,CAAC;AAC5E,aAAK,aAAa;AAClB,aAAK,cAAc,OAAO,KAAK,MAAM;AACrC;AAAA,MACJ;AACA,UAAI,iBAAiB,EAAE,OAAO,IAAI;AAC9B,YAAI,CAAC,KAAK,OAAO;AACb,eAAK,OAAO,OAAO,IAAI;AAAA,YACnB;AAAA,UAAmF,CAAC;AAC5F,aAAK,aAAa;AAClB,aAAK,cAAc,OAAO,KAAK,MAAM;AACrC;AAAA,MACJ;AACA,eAAS,KAAK,IAAI;AAKlB,WAAK,SAAS,KAAK,IAAI;AAAA,IAC3B;AAYA,QAAI,aAAiC;AACrC,QAAI,CAAC,iBAAiB,SAAS,SAAS,GAAG;AACvC,mBAAa,MAAM,KAAK,oBAAoB,UAAU,YAAY;AAAA,IACtE;AAIA,UAAM,cAAc;AACpB,QAAI,mBAAmB;AACvB,eAAW,QAAQ,UAAU;AACzB,UAAI,CAAC,KAAK,cAAc,KAAK,OAAO,aAAa;AAI7C,aAAK,aAAa;AAClB,aAAK,cAAc,OAAO,KAAK,MAAM;AACrC;AAAA,MACJ;AACA,UAAI,eAAe,QAAQ,CAAC,WAAW,IAAI,KAAK,MAAM,GAAG;AAIrD;AAAA,MACJ;AACA,WAAK,SAAS,KAAK,IAAI;AACvB,WAAK,cAAc,KAAK,kBAAkB;AAC1C;AACA,UAAI,oBAAoB,aAAa;AACjC,2BAAmB;AACnB,cAAM,iBAAiB;AAAA,MAC3B;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAc,oBAAoB,UAA6B,cAAkD;AAC7G,QAAI;AAEA,YAAM,UAAU,oBAAI,IAAsB;AAC1C,iBAAW,QAAQ,UAAU;AACzB,cAAM,QAAQ,KAAK,kBAAkB,KAAK;AAC1C,YAAI,UAAU,KAAM;AACpB,YAAI,MAAM,QAAQ,IAAI,KAAK;AAC3B,YAAI,CAAC,KAAK;AAAE,gBAAM,CAAC;AAAG,kBAAQ,IAAI,OAAO,GAAG;AAAA,QAAG;AAC/C,YAAI,KAAK,KAAK,MAAM;AAAA,MACxB;AACA,UAAI,QAAQ,SAAS,GAAG;AAEpB,eAAO,IAAI,IAAI,SAAS,IAAI,OAAK,EAAE,MAAM,CAAC;AAAA,MAC9C;AAEA,YAAM,WAAW,KAAK,oBAAoB;AAC1C,YAAM,YAA8D,WAC9D,oBAAI,IAAwB,IAC5B,CAAC;AACP,iBAAW,CAAC,OAAO,GAAG,KAAK,SAAS;AAChC,YAAI,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACxB,cAAMK,SAAQ,wBAAwB,UAAU,GAAG;AACnD,YAAI,UAAU;AACV,UAAC,UAAsC,IAAI,OAAOA,MAAK;AAAA,QAC3D,OAAO;AACH,UAAC,UAAqC,OAAO,KAAK,CAAC,IAAI,aAAaA,MAAK;AAAA,QAC7E;AAAA,MACJ;AAQA,YAAM,gBAAgB,KAAK;AAAA,QACvB,eAAe;AAAA,QACf,CAAC,KAAK,uBAAuB,SAAS;AAAA,QACtC;AAAA,UACI,qBAAqB;AAAA;AAAA,UACrB,QAAQ;AAAA,QACZ;AAAA,MACJ;AACA,YAAM,SAAS,MAAM,cAAc;AACnC,YAAM,QAAQC,UAAS,MAAM;AAC7B,UAAI,UAAU,KAAM,QAAO,IAAI,IAAI,SAAS,IAAI,OAAK,EAAE,MAAM,CAAC;AAC9D,aAAO,IAAI,IAAI,wBAAwB,YAAY,KAAK,CAAC;AAAA,IAC7D,QAAQ;AAEJ,aAAO,IAAI,IAAI,SAAS,IAAI,OAAK,EAAE,MAAM,CAAC;AAAA,IAC9C;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,kBAAkB,WAA+C;AACrE,UAAM,gBAAgB,KAAK;AAC3B,SAAK,iBAAiB;AACtB,QAAI,kBAAkB;AAClB,aAAO;AAEX,QAAI,UAAU,iBAAiB,cAAc;AACzC,aAAO;AAEX,SAAK,aAAa,MAAM;AAExB,aAAS,IAAI,IAAI,KAAK,GAAG,iBAAiB;AAC1C,SAAK,YAAY,QAAQ;AACzB,WAAO;AAAA,EACX;AAAA,EAEmB,qBAAqB,WAAkC;AAItE,SAAK,wBAAwB,UAAU,SAAS;AAChD,SAAK,mBAAmB,QAAQ,SAAS;AAAA,EAC7C;AAAA,EAES,QAAc;AACnB,SAAK,YAAY;AACjB,SAAK,gBAAgB,MAAM;AAC3B,UAAM,MAAM;AAAA,EAChB;AACJ;AAGO,IAAM,gBAAN,cAA4B,QAAQ;AAAA;AAAA;AAAA;AAAA,EAIvC;AAAA,EACQ;AAAA,EACA,YAAY;AAAA,EAEpB,YAAY,KAAa,KAAa;AAClC,UAAM,KAAK,GAAG;AACd,SAAK,2BAA2B,IAAI,OAAO;AAAA,EAC/C;AAAA;AAAA,EAGA,OAAO,MAA2B;AAG9B,SAAK,iBAAiB;AACtB,SAAK,oBAAoB,kBAA6B;AACtD,SAAK,gBAAgB,IAAI;AAKzB,SAAK,oBAAoB,mBAA8B;AAIvD,SAAK,OAAO,IAAI,MAAM;AAIlB,UAAI,KAAK,gBAAgB,QAAQ,KAAK,gBAAgB,OAAW;AACjE,WAAK,cAAc,cAAc;AACjC,WAAK,eAAe;AAAA,IACxB,CAAC;AAAA,EACL;AAAA,EAES,QAAc;AACnB,SAAK,YAAY;AACjB,SAAK,iBAAiB;AACtB,UAAM,MAAM;AAAA,EAChB;AAAA,EAEQ,iBAAuB;AAC3B,SAAK,iBAAiB;AAKtB,QAAI,KAAK,UAAW;AACpB,UAAM,UAAU,KAAK;AACrB,QAAI,EAAE,UAAU,MAAM,CAAC,OAAO,SAAS,OAAO,EAAG;AACjD,SAAK,cAAc,WAAW,MAAM;AAChC,WAAK,cAAc;AAEnB,UAAI,KAAK,gBAAgB;AACrB,aAAK,MAAM;AAAA,IACnB,GAAG,OAAO;AACV,IAAC,KAAK,YAAuC,QAAQ;AAAA,EACzD;AAAA,EAEQ,mBAAyB;AAC7B,QAAI,KAAK,gBAAgB,QAAW;AAChC,mBAAa,KAAK,WAAW;AAC7B,WAAK,cAAc;AAAA,IACvB;AAAA,EACJ;AAAA,EAEmB,qBAAqB,YAAmC;AAEvE,QAAI,KAAK,gBAAgB,QAAW;AAGhC,WAAK,IAAI,iBAAiB;AAAA,QACtB,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK,IAAI;AAAA,QACT,KAAK,sBAAsB;AAAA,MAC/B;AACA,WAAK,oBAAoB,iBAA4B;AAAA,IACzD;AAAA,EACJ;AACJ;AAMA,SAAS,gBAAgB,MAAsB;AAC3C,QAAM,MAAM,KAAK,QAAQ,MAAM,EAAE;AACjC,QAAM,QAAQ,IAAI,WAAW,EAAE;AAG/B,QAAM,CAAC,IAAI,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;AACvC,QAAM,CAAC,IAAI,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;AACvC,QAAM,CAAC,IAAI,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;AACvC,QAAM,CAAC,IAAI,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;AAEvC,QAAM,CAAC,IAAI,SAAS,IAAI,MAAM,IAAI,EAAE,GAAG,EAAE;AACzC,QAAM,CAAC,IAAI,SAAS,IAAI,MAAM,GAAG,EAAE,GAAG,EAAE;AAExC,QAAM,CAAC,IAAI,SAAS,IAAI,MAAM,IAAI,EAAE,GAAG,EAAE;AACzC,QAAM,CAAC,IAAI,SAAS,IAAI,MAAM,IAAI,EAAE,GAAG,EAAE;AAEzC,WAAS,IAAI,GAAG,IAAI,IAAI;AACpB,UAAM,CAAC,IAAI,SAAS,IAAI,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE;AAEvD,SAAO,gBAAgB,KAAK;AAChC;AAIA,IAAI,oCAAoC;AAExC,SAAS,8BAA8B,KAAa,QAAsB;AACtE,MAAI;AACA;AAEJ,sCAAoC;AACpC,EAAAN,UAAS;AAAA,IACL,IAAI,GAAG,MAAM,MAAM;AAAA,EAC+C;AAC1E;AAGA,SAASM,UAAS,OAAmC;AACjD,MAAI,iBAAiB,WAAY,QAAO;AACxC,MAAI,OAAO,UAAU,UAAU;AAC3B,QAAI;AAAE,aAAO,aAAa,KAAK;AAAA,IAAG,QAAQ;AAAE,aAAO;AAAA,IAAM;AAAA,EAC7D;AACA,SAAO;AACX;;;AGvjDO,IAAM,YAAN,MAAM,WAAU;AAAA;AAAA;AAAA,EAGnB,OAAO,UAAqB,IAAI,WAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO1C,OAAgB,QAAmB,IAAI,WAAU,EAAE,oBAAoB,IAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhF,mBAAmB;AAAA;AAAA,EAGnB,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOrB,+BAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ/B,6BAA6B;AAAA;AAAA;AAAA,EAI7B,2BAA2B;AAAA;AAAA,EAG3B,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQpB,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQrB,2BAA2B;AAAA,EAE3B,YAAY,WAAgC;AACxC,QAAI,UAAW,QAAO,OAAO,MAAM,SAAS;AAAA,EAChD;AACJ;;;AC/EA,IAAAC,eAA4C;AAErC,IAAM,gCAAN,cAA4C,0BAAa;AAAA,EAC5D,cAAc;AACV,UAAM;AAQN,SAAK,SAAS,2BAAc,IAAI,KAAK,GAAM;AAAA,EAC/C;AACJ;;;ACoBA,IAAM,EAAE,SAAAC,SAAQ,IAAI,QAAQ,gBAAgB;AAiBrC,IAAM,iBAAN,MAAqB;AAAA,EAChB,WAAW,oBAAI,IAGrB;AAAA,EAEF,SAAS,KAAoB,MAAsB,WAAmB,MAAY;AAC9E,eAAW,aAAa,IAAI,QAAQ,OAAO,GAAG;AAC1C,YAAM,KAAK,KAAK,UAAU,IAAI;AAC9B,UAAI,CAAC,GAAI;AACT,WAAK,SAAS,IAAI,eAAe,SAAS,GAAG;AAAA,QACzC,KAAK;AAAA,QACL;AAAA,QACA;AAAA,MACJ,CAAC;AAAA,IACL;AAAA,EACJ;AAAA,EAEA,MAAM,SACF,YACA,MACA,SACgB;AAChB,UAAM,QAAQ,KAAK,SAAS,IAAI,UAAU;AAC1C,QAAI,UAAU,QAAW;AAErB,MAAAA,UAAS,IAAI,qBAAqB,UAAU,EAAE;AAC9C,YAAM,IAAI,MAAM,qBAAqB,UAAU,EAAE;AAAA,IACrD;AAIA,QAAI,YAAY,UAAa,MAAM,IAAI,eAAe,GAAG;AACrD,aAAO,MAAM,MAAM,GAAG,KAAK,MAAM,UAAU,GAAG,MAAM,OAAO;AAAA,IAC/D;AACA,WAAO,MAAM,MAAM,GAAG,KAAK,MAAM,UAAU,GAAG,IAAI;AAAA,EACtD;AAAA,EAEA,aAAa,YAA8C;AACvD,WAAO,KAAK,SAAS,IAAI,UAAU,GAAG;AAAA,EAC1C;AACJ;;;ACnEA,IAAAC,eAA6C;AAE7C,IAAM,eAAe,uBAAO,IAAI,mBAAmB;AACnD,IAAM,eAAe,uBAAO,IAAI,mBAAmB;AAkB5C,SAAS,eACZ,KACsC;AAEtC,SAAQ,IAAY,OAAO,QAAQ,IAAI,YAAY;AACvD;AAGO,SAAS,eACZ,KACuB;AAEvB,SAAQ,IAAY,OAAO,QAAQ,IAAI,YAAY;AACvD;AAGO,SAAS,WAAW,aAAqB;AAC5C,SAAO,SACH,SACA,SACI;AACJ,UAAM,WAAO,0BAAyB,QAAQ,UAAU,YAAY;AACpE,SAAK,OAAO;AAAA,EAChB;AACJ;AAGO,SAAS,UAAU,SAIvB;AACC,SAAO,SACH,QACA,SAIqC;AACrC,UAAM,aAAa,OAAO,QAAQ,IAAI;AACtC,UAAM,cAAU,0BAAwC,QAAQ,UAAU,YAAY;AACtF,YAAQ,UAAU,IAAI;AAAA,MAClB,GAAG,QAAQ,UAAU;AAAA,MACrB,cAAU,8BAAgB,QAAQ,SAAS,UAAU,UAAU;AAAA,MAC/D,SAAS,SAAS;AAAA,MAClB,qBAAqB,SAAS;AAAA,IAClC;AACA,WAAO;AAAA,EACX;AACJ;;;ACzFA,IAAM,WAAW;AACjB,IAAM,WAAW;AAEjB,IAAM,YAAY;AAClB,IAAM,YAAY;AAClB,IAAM,YAAY;AAElB,IAAM,YAAY;AAClB,IAAM,YAAY;AAClB,IAAM,YAAY;AAClB,IAAM,YAAY;AAClB,IAAM,YAAY;AAElB,IAAM,uBAAuB;AAC7B,IAAM,0BAA0B;AAEhC,IAAM,aAAa;AACnB,IAAM,0BAA0B;AAChC,IAAM,SAAS,aAAa;AAE5B,IAAM,mBAAmB;AACzB,IAAM,2BAA2B;AACjC,IAAM,0BAA0B;AAChC,IAAM,2BAA2B;AACjC,IAAM,6BAA6B;AAEnC,IAAM,SAAS;AAGf,IAAM,iBAAiB,IAAI,WAAW;AAAA,EAClC;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAC1F;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAC9F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","names":["RpcCallTypeId","import_msgpack","import_msgpack","import_core","warnLog","import_core","warnLog","import_core","b","warnLog","import_core","import_core","warnLog","warnLog","DEFAULT_ACK_PERIOD","DEFAULT_ACK_ADVANCE","Denque","import_core","import_msgpack","textEncoder","debugLog","warnLog","errorLog","call","RpcConnectionState","RpcPeerChangeKind","bytes","_toBytes","import_core","warnLog","import_core","textEncoder","warnLog","warnLog","RpcPeerStateKind","import_core","infoLog"]}