@trpc/client
Advanced tools
| const require_chunk = require('./chunk-DWy1uDak.cjs'); | ||
| const require_objectSpread2$1 = require('./objectSpread2-Bsvh_OqM.cjs'); | ||
| const require_TRPCClientError = require('./TRPCClientError-Dey88Uiy.cjs'); | ||
| const require_unstable_internals = require('./unstable-internals-M84gUQCV.cjs'); | ||
| const __trpc_server_observable = require_chunk.__toESM(require("@trpc/server/observable")); | ||
| const __trpc_server_unstable_core_do_not_import = require_chunk.__toESM(require("@trpc/server/unstable-core-do-not-import")); | ||
| //#region src/links/wsLink/wsClient/encoder.ts | ||
| const jsonEncoder = { | ||
| encode: (data) => JSON.stringify(data), | ||
| decode: (data) => { | ||
| if (typeof data !== "string") throw new Error("jsonEncoder received binary data. JSON uses text frames. Use a binary encoder for binary data."); | ||
| return JSON.parse(data); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/options.ts | ||
| const lazyDefaults = { | ||
| enabled: false, | ||
| closeMs: 0 | ||
| }; | ||
| const keepAliveDefaults = { | ||
| enabled: false, | ||
| pongTimeoutMs: 1e3, | ||
| intervalMs: 5e3 | ||
| }; | ||
| /** | ||
| * Calculates a delay for exponential backoff based on the retry attempt index. | ||
| * The delay starts at 0 for the first attempt and doubles for each subsequent attempt, | ||
| * capped at 30 seconds. | ||
| */ | ||
| const exponentialBackoff = (attemptIndex) => { | ||
| return attemptIndex === 0 ? 0 : Math.min(1e3 * 2 ** attemptIndex, 3e4); | ||
| }; | ||
| //#endregion | ||
| //#region src/links/internals/urlWithConnectionParams.ts | ||
| /** | ||
| * Get the result of a value or function that returns a value | ||
| * It also optionally accepts typesafe arguments for the function | ||
| */ | ||
| const resultOf = (value, ...args) => { | ||
| return typeof value === "function" ? value(...args) : value; | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/utils.ts | ||
| var import_defineProperty$3 = require_chunk.__toESM(require_objectSpread2$1.require_defineProperty(), 1); | ||
| var TRPCWebSocketClosedError = class TRPCWebSocketClosedError extends Error { | ||
| constructor(opts) { | ||
| super(opts.message, { cause: opts.cause }); | ||
| this.name = "TRPCWebSocketClosedError"; | ||
| Object.setPrototypeOf(this, TRPCWebSocketClosedError.prototype); | ||
| } | ||
| }; | ||
| /** | ||
| * Utility class for managing a timeout that can be started, stopped, and reset. | ||
| * Useful for scenarios where the timeout duration is reset dynamically based on events. | ||
| */ | ||
| var ResettableTimeout = class { | ||
| constructor(onTimeout, timeoutMs) { | ||
| this.onTimeout = onTimeout; | ||
| this.timeoutMs = timeoutMs; | ||
| (0, import_defineProperty$3.default)(this, "timeout", void 0); | ||
| } | ||
| /** | ||
| * Resets the current timeout, restarting it with the same duration. | ||
| * Does nothing if no timeout is active. | ||
| */ | ||
| reset() { | ||
| if (!this.timeout) return; | ||
| clearTimeout(this.timeout); | ||
| this.timeout = setTimeout(this.onTimeout, this.timeoutMs); | ||
| } | ||
| start() { | ||
| clearTimeout(this.timeout); | ||
| this.timeout = setTimeout(this.onTimeout, this.timeoutMs); | ||
| } | ||
| stop() { | ||
| clearTimeout(this.timeout); | ||
| this.timeout = void 0; | ||
| } | ||
| }; | ||
| function withResolvers() { | ||
| let resolve; | ||
| let reject; | ||
| const promise = new Promise((res, rej) => { | ||
| resolve = res; | ||
| reject = rej; | ||
| }); | ||
| return { | ||
| promise, | ||
| resolve, | ||
| reject | ||
| }; | ||
| } | ||
| /** | ||
| * Resolves a WebSocket URL and optionally appends connection parameters. | ||
| * | ||
| * If connectionParams are provided, appends 'connectionParams=1' query parameter. | ||
| */ | ||
| async function prepareUrl(urlOptions) { | ||
| const url = await resultOf(urlOptions.url); | ||
| if (!urlOptions.connectionParams) return url; | ||
| const prefix = url.includes("?") ? "&" : "?"; | ||
| const connectionParams = `${prefix}connectionParams=1`; | ||
| return url + connectionParams; | ||
| } | ||
| async function buildConnectionMessage(connectionParams) { | ||
| const message = { | ||
| method: "connectionParams", | ||
| data: await resultOf(connectionParams) | ||
| }; | ||
| return JSON.stringify(message); | ||
| } | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/requestManager.ts | ||
| var import_defineProperty$2 = require_chunk.__toESM(require_objectSpread2$1.require_defineProperty(), 1); | ||
| /** | ||
| * Manages WebSocket requests, tracking their lifecycle and providing utility methods | ||
| * for handling outgoing and pending requests. | ||
| * | ||
| * - **Outgoing requests**: Requests that are queued and waiting to be sent. | ||
| * - **Pending requests**: Requests that have been sent and are in flight awaiting a response. | ||
| * For subscriptions, multiple responses may be received until the subscription is closed. | ||
| */ | ||
| var RequestManager = class { | ||
| constructor() { | ||
| (0, import_defineProperty$2.default)(this, "outgoingRequests", new Array()); | ||
| (0, import_defineProperty$2.default)(this, "pendingRequests", {}); | ||
| } | ||
| /** | ||
| * Registers a new request by adding it to the outgoing queue and setting up | ||
| * callbacks for lifecycle events such as completion or error. | ||
| * | ||
| * @param message - The outgoing message to be sent. | ||
| * @param callbacks - Callback functions to observe the request's state. | ||
| * @returns A cleanup function to manually remove the request. | ||
| */ | ||
| register(message, callbacks) { | ||
| const { promise: end, resolve } = withResolvers(); | ||
| this.outgoingRequests.push({ | ||
| id: String(message.id), | ||
| message, | ||
| end, | ||
| callbacks: { | ||
| next: callbacks.next, | ||
| complete: () => { | ||
| callbacks.complete(); | ||
| resolve(); | ||
| }, | ||
| error: (e) => { | ||
| callbacks.error(e); | ||
| resolve(); | ||
| } | ||
| } | ||
| }); | ||
| return () => { | ||
| this.delete(message.id); | ||
| callbacks.complete(); | ||
| resolve(); | ||
| }; | ||
| } | ||
| /** | ||
| * Deletes a request from both the outgoing and pending collections, if it exists. | ||
| */ | ||
| delete(messageId) { | ||
| if (messageId === null) return; | ||
| this.outgoingRequests = this.outgoingRequests.filter(({ id }) => id !== String(messageId)); | ||
| delete this.pendingRequests[String(messageId)]; | ||
| } | ||
| /** | ||
| * Moves all outgoing requests to the pending state and clears the outgoing queue. | ||
| * | ||
| * The caller is expected to handle the actual sending of the requests | ||
| * (e.g., sending them over the network) after this method is called. | ||
| * | ||
| * @returns The list of requests that were transitioned to the pending state. | ||
| */ | ||
| flush() { | ||
| const requests = this.outgoingRequests; | ||
| this.outgoingRequests = []; | ||
| for (const request of requests) this.pendingRequests[request.id] = request; | ||
| return requests; | ||
| } | ||
| /** | ||
| * Retrieves all currently pending requests, which are in flight awaiting responses | ||
| * or handling ongoing subscriptions. | ||
| */ | ||
| getPendingRequests() { | ||
| return Object.values(this.pendingRequests); | ||
| } | ||
| /** | ||
| * Retrieves a specific pending request by its message ID. | ||
| */ | ||
| getPendingRequest(messageId) { | ||
| if (messageId === null) return null; | ||
| return this.pendingRequests[String(messageId)]; | ||
| } | ||
| /** | ||
| * Retrieves all outgoing requests, which are waiting to be sent. | ||
| */ | ||
| getOutgoingRequests() { | ||
| return this.outgoingRequests; | ||
| } | ||
| /** | ||
| * Retrieves all requests, both outgoing and pending, with their respective states. | ||
| * | ||
| * @returns An array of all requests with their state ("outgoing" or "pending"). | ||
| */ | ||
| getRequests() { | ||
| return [...this.getOutgoingRequests().map((request) => ({ | ||
| state: "outgoing", | ||
| message: request.message, | ||
| end: request.end, | ||
| callbacks: request.callbacks | ||
| })), ...this.getPendingRequests().map((request) => ({ | ||
| state: "pending", | ||
| message: request.message, | ||
| end: request.end, | ||
| callbacks: request.callbacks | ||
| }))]; | ||
| } | ||
| /** | ||
| * Checks if there are any pending requests, including ongoing subscriptions. | ||
| */ | ||
| hasPendingRequests() { | ||
| return this.getPendingRequests().length > 0; | ||
| } | ||
| /** | ||
| * Checks if there are any pending subscriptions | ||
| */ | ||
| hasPendingSubscriptions() { | ||
| return this.getPendingRequests().some((request) => request.message.method === "subscription"); | ||
| } | ||
| /** | ||
| * Checks if there are any outgoing requests waiting to be sent. | ||
| */ | ||
| hasOutgoingRequests() { | ||
| return this.outgoingRequests.length > 0; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/wsConnection.ts | ||
| var import_defineProperty$1 = require_chunk.__toESM(require_objectSpread2$1.require_defineProperty(), 1); | ||
| /** | ||
| * Opens a WebSocket connection asynchronously and returns a promise | ||
| * that resolves when the connection is successfully established. | ||
| * The promise rejects if an error occurs during the connection attempt. | ||
| */ | ||
| function asyncWsOpen(ws) { | ||
| const { promise, resolve, reject } = withResolvers(); | ||
| ws.addEventListener("open", () => { | ||
| ws.removeEventListener("error", reject); | ||
| resolve(); | ||
| }); | ||
| ws.addEventListener("error", reject); | ||
| return promise; | ||
| } | ||
| /** | ||
| * Sets up a periodic ping-pong mechanism to keep the WebSocket connection alive. | ||
| * | ||
| * - Sends "PING" messages at regular intervals defined by `intervalMs`. | ||
| * - If a "PONG" response is not received within the `pongTimeoutMs`, the WebSocket is closed. | ||
| * - The ping timer resets upon receiving any message to maintain activity. | ||
| * - Automatically starts the ping process when the WebSocket connection is opened. | ||
| * - Cleans up timers when the WebSocket is closed. | ||
| * | ||
| * @param ws - The WebSocket instance to manage. | ||
| * @param options - Configuration options for ping-pong intervals and timeouts. | ||
| */ | ||
| function setupPingInterval(ws, { intervalMs, pongTimeoutMs }) { | ||
| let pingTimeout; | ||
| let pongTimeout; | ||
| function start() { | ||
| pingTimeout = setTimeout(() => { | ||
| ws.send("PING"); | ||
| pongTimeout = setTimeout(() => { | ||
| ws.close(); | ||
| }, pongTimeoutMs); | ||
| }, intervalMs); | ||
| } | ||
| function reset() { | ||
| clearTimeout(pingTimeout); | ||
| start(); | ||
| } | ||
| function pong() { | ||
| clearTimeout(pongTimeout); | ||
| reset(); | ||
| } | ||
| ws.addEventListener("open", start); | ||
| ws.addEventListener("message", ({ data }) => { | ||
| clearTimeout(pingTimeout); | ||
| start(); | ||
| if (data === "PONG") pong(); | ||
| }); | ||
| ws.addEventListener("close", () => { | ||
| clearTimeout(pingTimeout); | ||
| clearTimeout(pongTimeout); | ||
| }); | ||
| } | ||
| /** | ||
| * Manages a WebSocket connection with support for reconnection, keep-alive mechanisms, | ||
| * and observable state tracking. | ||
| */ | ||
| var WsConnection = class WsConnection { | ||
| constructor(opts) { | ||
| var _opts$WebSocketPonyfi; | ||
| (0, import_defineProperty$1.default)(this, "id", ++WsConnection.connectCount); | ||
| (0, import_defineProperty$1.default)(this, "WebSocketPonyfill", void 0); | ||
| (0, import_defineProperty$1.default)(this, "urlOptions", void 0); | ||
| (0, import_defineProperty$1.default)(this, "keepAliveOpts", void 0); | ||
| (0, import_defineProperty$1.default)(this, "wsObservable", (0, __trpc_server_observable.behaviorSubject)(null)); | ||
| (0, import_defineProperty$1.default)(this, "openPromise", null); | ||
| this.WebSocketPonyfill = (_opts$WebSocketPonyfi = opts.WebSocketPonyfill) !== null && _opts$WebSocketPonyfi !== void 0 ? _opts$WebSocketPonyfi : WebSocket; | ||
| if (!this.WebSocketPonyfill) throw new Error("No WebSocket implementation found - you probably don't want to use this on the server, but if you do you need to pass a `WebSocket`-ponyfill"); | ||
| this.urlOptions = opts.urlOptions; | ||
| this.keepAliveOpts = opts.keepAlive; | ||
| } | ||
| get ws() { | ||
| return this.wsObservable.get(); | ||
| } | ||
| set ws(ws) { | ||
| this.wsObservable.next(ws); | ||
| } | ||
| /** | ||
| * Checks if the WebSocket connection is open and ready to communicate. | ||
| */ | ||
| isOpen() { | ||
| return !!this.ws && this.ws.readyState === this.WebSocketPonyfill.OPEN && !this.openPromise; | ||
| } | ||
| /** | ||
| * Checks if the WebSocket connection is closed or in the process of closing. | ||
| */ | ||
| isClosed() { | ||
| return !!this.ws && (this.ws.readyState === this.WebSocketPonyfill.CLOSING || this.ws.readyState === this.WebSocketPonyfill.CLOSED); | ||
| } | ||
| async open() { | ||
| var _this = this; | ||
| if (_this.openPromise) return _this.openPromise; | ||
| _this.id = ++WsConnection.connectCount; | ||
| const wsPromise = prepareUrl(_this.urlOptions).then((url) => new _this.WebSocketPonyfill(url)); | ||
| _this.openPromise = wsPromise.then(async (ws) => { | ||
| _this.ws = ws; | ||
| ws.binaryType = "arraybuffer"; | ||
| ws.addEventListener("message", function({ data }) { | ||
| if (data === "PING") this.send("PONG"); | ||
| }); | ||
| if (_this.keepAliveOpts.enabled) setupPingInterval(ws, _this.keepAliveOpts); | ||
| ws.addEventListener("close", () => { | ||
| if (_this.ws === ws) _this.ws = null; | ||
| }); | ||
| await asyncWsOpen(ws); | ||
| if (_this.urlOptions.connectionParams) ws.send(await buildConnectionMessage(_this.urlOptions.connectionParams)); | ||
| }); | ||
| try { | ||
| await _this.openPromise; | ||
| } finally { | ||
| _this.openPromise = null; | ||
| } | ||
| } | ||
| /** | ||
| * Closes the WebSocket connection gracefully. | ||
| * Waits for any ongoing open operation to complete before closing. | ||
| */ | ||
| async close() { | ||
| var _this2 = this; | ||
| try { | ||
| await _this2.openPromise; | ||
| } finally { | ||
| var _this$ws; | ||
| (_this$ws = _this2.ws) === null || _this$ws === void 0 || _this$ws.close(); | ||
| } | ||
| } | ||
| }; | ||
| (0, import_defineProperty$1.default)(WsConnection, "connectCount", 0); | ||
| /** | ||
| * Provides a backward-compatible representation of the connection state. | ||
| */ | ||
| function backwardCompatibility(connection) { | ||
| if (connection.isOpen()) return { | ||
| id: connection.id, | ||
| state: "open", | ||
| ws: connection.ws | ||
| }; | ||
| if (connection.isClosed()) return { | ||
| id: connection.id, | ||
| state: "closed", | ||
| ws: connection.ws | ||
| }; | ||
| if (!connection.ws) return null; | ||
| return { | ||
| id: connection.id, | ||
| state: "connecting", | ||
| ws: connection.ws | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/wsClient.ts | ||
| var import_defineProperty = require_chunk.__toESM(require_objectSpread2$1.require_defineProperty(), 1); | ||
| var import_objectSpread2 = require_chunk.__toESM(require_objectSpread2$1.require_objectSpread2(), 1); | ||
| /** | ||
| * A WebSocket client for managing TRPC operations, supporting lazy initialization, | ||
| * reconnection, keep-alive, and request management. | ||
| */ | ||
| var WsClient = class { | ||
| constructor(opts) { | ||
| var _opts$experimental_en, _opts$retryDelayMs; | ||
| (0, import_defineProperty.default)(this, "connectionState", void 0); | ||
| (0, import_defineProperty.default)(this, "allowReconnect", false); | ||
| (0, import_defineProperty.default)(this, "requestManager", new RequestManager()); | ||
| (0, import_defineProperty.default)(this, "activeConnection", void 0); | ||
| (0, import_defineProperty.default)(this, "reconnectRetryDelay", void 0); | ||
| (0, import_defineProperty.default)(this, "inactivityTimeout", void 0); | ||
| (0, import_defineProperty.default)(this, "callbacks", void 0); | ||
| (0, import_defineProperty.default)(this, "lazyMode", void 0); | ||
| (0, import_defineProperty.default)(this, "encoder", void 0); | ||
| (0, import_defineProperty.default)(this, "reconnecting", null); | ||
| this.encoder = (_opts$experimental_en = opts.experimental_encoder) !== null && _opts$experimental_en !== void 0 ? _opts$experimental_en : jsonEncoder; | ||
| this.callbacks = { | ||
| onOpen: opts.onOpen, | ||
| onClose: opts.onClose, | ||
| onError: opts.onError | ||
| }; | ||
| const lazyOptions = (0, import_objectSpread2.default)((0, import_objectSpread2.default)({}, lazyDefaults), opts.lazy); | ||
| this.inactivityTimeout = new ResettableTimeout(() => { | ||
| if (this.requestManager.hasOutgoingRequests() || this.requestManager.hasPendingRequests()) { | ||
| this.inactivityTimeout.reset(); | ||
| return; | ||
| } | ||
| this.close().catch(() => null); | ||
| }, lazyOptions.closeMs); | ||
| this.activeConnection = new WsConnection({ | ||
| WebSocketPonyfill: opts.WebSocket, | ||
| urlOptions: opts, | ||
| keepAlive: (0, import_objectSpread2.default)((0, import_objectSpread2.default)({}, keepAliveDefaults), opts.keepAlive) | ||
| }); | ||
| this.activeConnection.wsObservable.subscribe({ next: (ws) => { | ||
| if (!ws) return; | ||
| this.setupWebSocketListeners(ws); | ||
| } }); | ||
| this.reconnectRetryDelay = (_opts$retryDelayMs = opts.retryDelayMs) !== null && _opts$retryDelayMs !== void 0 ? _opts$retryDelayMs : exponentialBackoff; | ||
| this.lazyMode = lazyOptions.enabled; | ||
| this.connectionState = (0, __trpc_server_observable.behaviorSubject)({ | ||
| type: "state", | ||
| state: lazyOptions.enabled ? "idle" : "connecting", | ||
| error: null | ||
| }); | ||
| if (!this.lazyMode) this.open().catch(() => null); | ||
| } | ||
| /** | ||
| * Opens the WebSocket connection. Handles reconnection attempts and updates | ||
| * the connection state accordingly. | ||
| */ | ||
| async open() { | ||
| var _this = this; | ||
| _this.allowReconnect = true; | ||
| if (_this.connectionState.get().state === "idle") _this.connectionState.next({ | ||
| type: "state", | ||
| state: "connecting", | ||
| error: null | ||
| }); | ||
| try { | ||
| await _this.activeConnection.open(); | ||
| } catch (error) { | ||
| _this.reconnect(new TRPCWebSocketClosedError({ | ||
| message: "Initialization error", | ||
| cause: error | ||
| })); | ||
| return _this.reconnecting; | ||
| } | ||
| } | ||
| /** | ||
| * Closes the WebSocket connection and stops managing requests. | ||
| * Ensures all outgoing and pending requests are properly finalized. | ||
| */ | ||
| async close() { | ||
| var _this2 = this; | ||
| _this2.allowReconnect = false; | ||
| _this2.inactivityTimeout.stop(); | ||
| const requestsToAwait = []; | ||
| for (const request of _this2.requestManager.getRequests()) if (request.message.method === "subscription") request.callbacks.complete(); | ||
| else if (request.state === "outgoing") request.callbacks.error(require_TRPCClientError.TRPCClientError.from(new TRPCWebSocketClosedError({ message: "Closed before connection was established" }))); | ||
| else requestsToAwait.push(request.end); | ||
| await Promise.all(requestsToAwait).catch(() => null); | ||
| await _this2.activeConnection.close().catch(() => null); | ||
| _this2.connectionState.next({ | ||
| type: "state", | ||
| state: "idle", | ||
| error: null | ||
| }); | ||
| } | ||
| /** | ||
| * Method to request the server. | ||
| * Handles data transformation, batching of requests, and subscription lifecycle. | ||
| * | ||
| * @param op - The operation details including id, type, path, input and signal | ||
| * @param transformer - Data transformer for serializing requests and deserializing responses | ||
| * @param lastEventId - Optional ID of the last received event for subscriptions | ||
| * | ||
| * @returns An observable that emits operation results and handles cleanup | ||
| */ | ||
| request({ op: { id, type, path, input, signal }, transformer, lastEventId }) { | ||
| return (0, __trpc_server_observable.observable)((observer) => { | ||
| const abort = this.batchSend({ | ||
| id, | ||
| method: type, | ||
| params: { | ||
| input: transformer.input.serialize(input), | ||
| path, | ||
| lastEventId | ||
| } | ||
| }, (0, import_objectSpread2.default)((0, import_objectSpread2.default)({}, observer), {}, { next(event) { | ||
| const transformed = (0, __trpc_server_unstable_core_do_not_import.transformResult)(event, transformer.output); | ||
| if (!transformed.ok) { | ||
| observer.error(require_TRPCClientError.TRPCClientError.from(transformed.error)); | ||
| return; | ||
| } | ||
| observer.next({ result: transformed.result }); | ||
| } })); | ||
| return () => { | ||
| abort(); | ||
| if (type === "subscription" && this.activeConnection.isOpen()) this.send({ | ||
| id, | ||
| method: "subscription.stop" | ||
| }); | ||
| signal === null || signal === void 0 || signal.removeEventListener("abort", abort); | ||
| }; | ||
| }); | ||
| } | ||
| get connection() { | ||
| return backwardCompatibility(this.activeConnection); | ||
| } | ||
| reconnect(closedError) { | ||
| var _this3 = this; | ||
| this.connectionState.next({ | ||
| type: "state", | ||
| state: "connecting", | ||
| error: require_TRPCClientError.TRPCClientError.from(closedError) | ||
| }); | ||
| if (this.reconnecting) return; | ||
| const tryReconnect = async (attemptIndex) => { | ||
| try { | ||
| await (0, __trpc_server_unstable_core_do_not_import.sleep)(_this3.reconnectRetryDelay(attemptIndex)); | ||
| if (_this3.allowReconnect) { | ||
| await _this3.activeConnection.close(); | ||
| await _this3.activeConnection.open(); | ||
| if (_this3.requestManager.hasPendingRequests()) _this3.send(_this3.requestManager.getPendingRequests().map(({ message }) => message)); | ||
| } | ||
| _this3.reconnecting = null; | ||
| } catch (_unused) { | ||
| await tryReconnect(attemptIndex + 1); | ||
| } | ||
| }; | ||
| this.reconnecting = tryReconnect(0); | ||
| } | ||
| setupWebSocketListeners(ws) { | ||
| var _this4 = this; | ||
| const handleCloseOrError = (cause) => { | ||
| const reqs = this.requestManager.getPendingRequests(); | ||
| for (const { message, callbacks } of reqs) { | ||
| if (message.method === "subscription") continue; | ||
| callbacks.error(require_TRPCClientError.TRPCClientError.from(cause !== null && cause !== void 0 ? cause : new TRPCWebSocketClosedError({ | ||
| message: "WebSocket closed", | ||
| cause | ||
| }))); | ||
| this.requestManager.delete(message.id); | ||
| } | ||
| }; | ||
| ws.addEventListener("open", () => { | ||
| (0, __trpc_server_unstable_core_do_not_import.run)(async () => { | ||
| var _this$callbacks$onOpe, _this$callbacks; | ||
| if (_this4.lazyMode) _this4.inactivityTimeout.start(); | ||
| (_this$callbacks$onOpe = (_this$callbacks = _this4.callbacks).onOpen) === null || _this$callbacks$onOpe === void 0 || _this$callbacks$onOpe.call(_this$callbacks); | ||
| _this4.connectionState.next({ | ||
| type: "state", | ||
| state: "pending", | ||
| error: null | ||
| }); | ||
| }).catch((error) => { | ||
| ws.close(3e3); | ||
| handleCloseOrError(error); | ||
| }); | ||
| }); | ||
| ws.addEventListener("message", ({ data }) => { | ||
| this.inactivityTimeout.reset(); | ||
| if (["PING", "PONG"].includes(data)) return; | ||
| const incomingMessage = this.encoder.decode(data); | ||
| if ("method" in incomingMessage) { | ||
| this.handleIncomingRequest(incomingMessage); | ||
| return; | ||
| } | ||
| this.handleResponseMessage(incomingMessage); | ||
| }); | ||
| ws.addEventListener("close", (event) => { | ||
| var _this$callbacks$onClo, _this$callbacks2; | ||
| handleCloseOrError(event); | ||
| (_this$callbacks$onClo = (_this$callbacks2 = this.callbacks).onClose) === null || _this$callbacks$onClo === void 0 || _this$callbacks$onClo.call(_this$callbacks2, event); | ||
| if (!this.lazyMode || this.requestManager.hasPendingSubscriptions()) this.reconnect(new TRPCWebSocketClosedError({ | ||
| message: "WebSocket closed", | ||
| cause: event | ||
| })); | ||
| }); | ||
| ws.addEventListener("error", (event) => { | ||
| var _this$callbacks$onErr, _this$callbacks3; | ||
| handleCloseOrError(event); | ||
| (_this$callbacks$onErr = (_this$callbacks3 = this.callbacks).onError) === null || _this$callbacks$onErr === void 0 || _this$callbacks$onErr.call(_this$callbacks3, event); | ||
| this.reconnect(new TRPCWebSocketClosedError({ | ||
| message: "WebSocket closed", | ||
| cause: event | ||
| })); | ||
| }); | ||
| } | ||
| handleResponseMessage(message) { | ||
| const request = this.requestManager.getPendingRequest(message.id); | ||
| if (!request) return; | ||
| request.callbacks.next(message); | ||
| let completed = true; | ||
| if ("result" in message && request.message.method === "subscription") { | ||
| if (message.result.type === "data") request.message.params.lastEventId = message.result.id; | ||
| if (message.result.type !== "stopped") completed = false; | ||
| } | ||
| if (completed) { | ||
| request.callbacks.complete(); | ||
| this.requestManager.delete(message.id); | ||
| } | ||
| } | ||
| handleIncomingRequest(message) { | ||
| if (message.method === "reconnect") this.reconnect(new TRPCWebSocketClosedError({ message: "Server requested reconnect" })); | ||
| } | ||
| /** | ||
| * Sends a message or batch of messages directly to the server. | ||
| */ | ||
| send(messageOrMessages) { | ||
| if (!this.activeConnection.isOpen()) throw new Error("Active connection is not open"); | ||
| const messages = messageOrMessages instanceof Array ? messageOrMessages : [messageOrMessages]; | ||
| this.activeConnection.ws.send(this.encoder.encode(messages.length === 1 ? messages[0] : messages)); | ||
| } | ||
| /** | ||
| * Groups requests for batch sending. | ||
| * | ||
| * @returns A function to abort the batched request. | ||
| */ | ||
| batchSend(message, callbacks) { | ||
| var _this5 = this; | ||
| this.inactivityTimeout.reset(); | ||
| (0, __trpc_server_unstable_core_do_not_import.run)(async () => { | ||
| if (!_this5.activeConnection.isOpen()) await _this5.open(); | ||
| await (0, __trpc_server_unstable_core_do_not_import.sleep)(0); | ||
| if (!_this5.requestManager.hasOutgoingRequests()) return; | ||
| _this5.send(_this5.requestManager.flush().map(({ message: message$1 }) => message$1)); | ||
| }).catch((err) => { | ||
| this.requestManager.delete(message.id); | ||
| callbacks.error(require_TRPCClientError.TRPCClientError.from(err)); | ||
| }); | ||
| return this.requestManager.register(message, callbacks); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/createWsClient.ts | ||
| function createWSClient(opts) { | ||
| return new WsClient(opts); | ||
| } | ||
| //#endregion | ||
| //#region src/links/wsLink/wsLink.ts | ||
| function wsLink(opts) { | ||
| const { client } = opts; | ||
| const transformer = require_unstable_internals.getTransformer(opts.transformer); | ||
| return () => { | ||
| return ({ op }) => { | ||
| return (0, __trpc_server_observable.observable)((observer) => { | ||
| const connStateSubscription = op.type === "subscription" ? client.connectionState.subscribe({ next(result) { | ||
| observer.next({ | ||
| result, | ||
| context: op.context | ||
| }); | ||
| } }) : null; | ||
| const requestSubscription = client.request({ | ||
| op, | ||
| transformer | ||
| }).subscribe(observer); | ||
| return () => { | ||
| requestSubscription.unsubscribe(); | ||
| connStateSubscription === null || connStateSubscription === void 0 || connStateSubscription.unsubscribe(); | ||
| }; | ||
| }); | ||
| }; | ||
| }; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'createWSClient', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createWSClient; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'jsonEncoder', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return jsonEncoder; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'resultOf', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return resultOf; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'wsLink', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return wsLink; | ||
| } | ||
| }); |
| import { __toESM, require_defineProperty, require_objectSpread2 } from "./objectSpread2-BvkFp-_Y.mjs"; | ||
| import { TRPCClientError } from "./TRPCClientError-CjKyS10w.mjs"; | ||
| import { getTransformer } from "./unstable-internals-Bg7n9BBj.mjs"; | ||
| import { behaviorSubject, observable } from "@trpc/server/observable"; | ||
| import { run, sleep, transformResult } from "@trpc/server/unstable-core-do-not-import"; | ||
| //#region src/links/wsLink/wsClient/encoder.ts | ||
| const jsonEncoder = { | ||
| encode: (data) => JSON.stringify(data), | ||
| decode: (data) => { | ||
| if (typeof data !== "string") throw new Error("jsonEncoder received binary data. JSON uses text frames. Use a binary encoder for binary data."); | ||
| return JSON.parse(data); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/options.ts | ||
| const lazyDefaults = { | ||
| enabled: false, | ||
| closeMs: 0 | ||
| }; | ||
| const keepAliveDefaults = { | ||
| enabled: false, | ||
| pongTimeoutMs: 1e3, | ||
| intervalMs: 5e3 | ||
| }; | ||
| /** | ||
| * Calculates a delay for exponential backoff based on the retry attempt index. | ||
| * The delay starts at 0 for the first attempt and doubles for each subsequent attempt, | ||
| * capped at 30 seconds. | ||
| */ | ||
| const exponentialBackoff = (attemptIndex) => { | ||
| return attemptIndex === 0 ? 0 : Math.min(1e3 * 2 ** attemptIndex, 3e4); | ||
| }; | ||
| //#endregion | ||
| //#region src/links/internals/urlWithConnectionParams.ts | ||
| /** | ||
| * Get the result of a value or function that returns a value | ||
| * It also optionally accepts typesafe arguments for the function | ||
| */ | ||
| const resultOf = (value, ...args) => { | ||
| return typeof value === "function" ? value(...args) : value; | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/utils.ts | ||
| var import_defineProperty$3 = __toESM(require_defineProperty(), 1); | ||
| var TRPCWebSocketClosedError = class TRPCWebSocketClosedError extends Error { | ||
| constructor(opts) { | ||
| super(opts.message, { cause: opts.cause }); | ||
| this.name = "TRPCWebSocketClosedError"; | ||
| Object.setPrototypeOf(this, TRPCWebSocketClosedError.prototype); | ||
| } | ||
| }; | ||
| /** | ||
| * Utility class for managing a timeout that can be started, stopped, and reset. | ||
| * Useful for scenarios where the timeout duration is reset dynamically based on events. | ||
| */ | ||
| var ResettableTimeout = class { | ||
| constructor(onTimeout, timeoutMs) { | ||
| this.onTimeout = onTimeout; | ||
| this.timeoutMs = timeoutMs; | ||
| (0, import_defineProperty$3.default)(this, "timeout", void 0); | ||
| } | ||
| /** | ||
| * Resets the current timeout, restarting it with the same duration. | ||
| * Does nothing if no timeout is active. | ||
| */ | ||
| reset() { | ||
| if (!this.timeout) return; | ||
| clearTimeout(this.timeout); | ||
| this.timeout = setTimeout(this.onTimeout, this.timeoutMs); | ||
| } | ||
| start() { | ||
| clearTimeout(this.timeout); | ||
| this.timeout = setTimeout(this.onTimeout, this.timeoutMs); | ||
| } | ||
| stop() { | ||
| clearTimeout(this.timeout); | ||
| this.timeout = void 0; | ||
| } | ||
| }; | ||
| function withResolvers() { | ||
| let resolve; | ||
| let reject; | ||
| const promise = new Promise((res, rej) => { | ||
| resolve = res; | ||
| reject = rej; | ||
| }); | ||
| return { | ||
| promise, | ||
| resolve, | ||
| reject | ||
| }; | ||
| } | ||
| /** | ||
| * Resolves a WebSocket URL and optionally appends connection parameters. | ||
| * | ||
| * If connectionParams are provided, appends 'connectionParams=1' query parameter. | ||
| */ | ||
| async function prepareUrl(urlOptions) { | ||
| const url = await resultOf(urlOptions.url); | ||
| if (!urlOptions.connectionParams) return url; | ||
| const prefix = url.includes("?") ? "&" : "?"; | ||
| const connectionParams = `${prefix}connectionParams=1`; | ||
| return url + connectionParams; | ||
| } | ||
| async function buildConnectionMessage(connectionParams) { | ||
| const message = { | ||
| method: "connectionParams", | ||
| data: await resultOf(connectionParams) | ||
| }; | ||
| return JSON.stringify(message); | ||
| } | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/requestManager.ts | ||
| var import_defineProperty$2 = __toESM(require_defineProperty(), 1); | ||
| /** | ||
| * Manages WebSocket requests, tracking their lifecycle and providing utility methods | ||
| * for handling outgoing and pending requests. | ||
| * | ||
| * - **Outgoing requests**: Requests that are queued and waiting to be sent. | ||
| * - **Pending requests**: Requests that have been sent and are in flight awaiting a response. | ||
| * For subscriptions, multiple responses may be received until the subscription is closed. | ||
| */ | ||
| var RequestManager = class { | ||
| constructor() { | ||
| (0, import_defineProperty$2.default)(this, "outgoingRequests", new Array()); | ||
| (0, import_defineProperty$2.default)(this, "pendingRequests", {}); | ||
| } | ||
| /** | ||
| * Registers a new request by adding it to the outgoing queue and setting up | ||
| * callbacks for lifecycle events such as completion or error. | ||
| * | ||
| * @param message - The outgoing message to be sent. | ||
| * @param callbacks - Callback functions to observe the request's state. | ||
| * @returns A cleanup function to manually remove the request. | ||
| */ | ||
| register(message, callbacks) { | ||
| const { promise: end, resolve } = withResolvers(); | ||
| this.outgoingRequests.push({ | ||
| id: String(message.id), | ||
| message, | ||
| end, | ||
| callbacks: { | ||
| next: callbacks.next, | ||
| complete: () => { | ||
| callbacks.complete(); | ||
| resolve(); | ||
| }, | ||
| error: (e) => { | ||
| callbacks.error(e); | ||
| resolve(); | ||
| } | ||
| } | ||
| }); | ||
| return () => { | ||
| this.delete(message.id); | ||
| callbacks.complete(); | ||
| resolve(); | ||
| }; | ||
| } | ||
| /** | ||
| * Deletes a request from both the outgoing and pending collections, if it exists. | ||
| */ | ||
| delete(messageId) { | ||
| if (messageId === null) return; | ||
| this.outgoingRequests = this.outgoingRequests.filter(({ id }) => id !== String(messageId)); | ||
| delete this.pendingRequests[String(messageId)]; | ||
| } | ||
| /** | ||
| * Moves all outgoing requests to the pending state and clears the outgoing queue. | ||
| * | ||
| * The caller is expected to handle the actual sending of the requests | ||
| * (e.g., sending them over the network) after this method is called. | ||
| * | ||
| * @returns The list of requests that were transitioned to the pending state. | ||
| */ | ||
| flush() { | ||
| const requests = this.outgoingRequests; | ||
| this.outgoingRequests = []; | ||
| for (const request of requests) this.pendingRequests[request.id] = request; | ||
| return requests; | ||
| } | ||
| /** | ||
| * Retrieves all currently pending requests, which are in flight awaiting responses | ||
| * or handling ongoing subscriptions. | ||
| */ | ||
| getPendingRequests() { | ||
| return Object.values(this.pendingRequests); | ||
| } | ||
| /** | ||
| * Retrieves a specific pending request by its message ID. | ||
| */ | ||
| getPendingRequest(messageId) { | ||
| if (messageId === null) return null; | ||
| return this.pendingRequests[String(messageId)]; | ||
| } | ||
| /** | ||
| * Retrieves all outgoing requests, which are waiting to be sent. | ||
| */ | ||
| getOutgoingRequests() { | ||
| return this.outgoingRequests; | ||
| } | ||
| /** | ||
| * Retrieves all requests, both outgoing and pending, with their respective states. | ||
| * | ||
| * @returns An array of all requests with their state ("outgoing" or "pending"). | ||
| */ | ||
| getRequests() { | ||
| return [...this.getOutgoingRequests().map((request) => ({ | ||
| state: "outgoing", | ||
| message: request.message, | ||
| end: request.end, | ||
| callbacks: request.callbacks | ||
| })), ...this.getPendingRequests().map((request) => ({ | ||
| state: "pending", | ||
| message: request.message, | ||
| end: request.end, | ||
| callbacks: request.callbacks | ||
| }))]; | ||
| } | ||
| /** | ||
| * Checks if there are any pending requests, including ongoing subscriptions. | ||
| */ | ||
| hasPendingRequests() { | ||
| return this.getPendingRequests().length > 0; | ||
| } | ||
| /** | ||
| * Checks if there are any pending subscriptions | ||
| */ | ||
| hasPendingSubscriptions() { | ||
| return this.getPendingRequests().some((request) => request.message.method === "subscription"); | ||
| } | ||
| /** | ||
| * Checks if there are any outgoing requests waiting to be sent. | ||
| */ | ||
| hasOutgoingRequests() { | ||
| return this.outgoingRequests.length > 0; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/wsConnection.ts | ||
| var import_defineProperty$1 = __toESM(require_defineProperty(), 1); | ||
| /** | ||
| * Opens a WebSocket connection asynchronously and returns a promise | ||
| * that resolves when the connection is successfully established. | ||
| * The promise rejects if an error occurs during the connection attempt. | ||
| */ | ||
| function asyncWsOpen(ws) { | ||
| const { promise, resolve, reject } = withResolvers(); | ||
| ws.addEventListener("open", () => { | ||
| ws.removeEventListener("error", reject); | ||
| resolve(); | ||
| }); | ||
| ws.addEventListener("error", reject); | ||
| return promise; | ||
| } | ||
| /** | ||
| * Sets up a periodic ping-pong mechanism to keep the WebSocket connection alive. | ||
| * | ||
| * - Sends "PING" messages at regular intervals defined by `intervalMs`. | ||
| * - If a "PONG" response is not received within the `pongTimeoutMs`, the WebSocket is closed. | ||
| * - The ping timer resets upon receiving any message to maintain activity. | ||
| * - Automatically starts the ping process when the WebSocket connection is opened. | ||
| * - Cleans up timers when the WebSocket is closed. | ||
| * | ||
| * @param ws - The WebSocket instance to manage. | ||
| * @param options - Configuration options for ping-pong intervals and timeouts. | ||
| */ | ||
| function setupPingInterval(ws, { intervalMs, pongTimeoutMs }) { | ||
| let pingTimeout; | ||
| let pongTimeout; | ||
| function start() { | ||
| pingTimeout = setTimeout(() => { | ||
| ws.send("PING"); | ||
| pongTimeout = setTimeout(() => { | ||
| ws.close(); | ||
| }, pongTimeoutMs); | ||
| }, intervalMs); | ||
| } | ||
| function reset() { | ||
| clearTimeout(pingTimeout); | ||
| start(); | ||
| } | ||
| function pong() { | ||
| clearTimeout(pongTimeout); | ||
| reset(); | ||
| } | ||
| ws.addEventListener("open", start); | ||
| ws.addEventListener("message", ({ data }) => { | ||
| clearTimeout(pingTimeout); | ||
| start(); | ||
| if (data === "PONG") pong(); | ||
| }); | ||
| ws.addEventListener("close", () => { | ||
| clearTimeout(pingTimeout); | ||
| clearTimeout(pongTimeout); | ||
| }); | ||
| } | ||
| /** | ||
| * Manages a WebSocket connection with support for reconnection, keep-alive mechanisms, | ||
| * and observable state tracking. | ||
| */ | ||
| var WsConnection = class WsConnection { | ||
| constructor(opts) { | ||
| var _opts$WebSocketPonyfi; | ||
| (0, import_defineProperty$1.default)(this, "id", ++WsConnection.connectCount); | ||
| (0, import_defineProperty$1.default)(this, "WebSocketPonyfill", void 0); | ||
| (0, import_defineProperty$1.default)(this, "urlOptions", void 0); | ||
| (0, import_defineProperty$1.default)(this, "keepAliveOpts", void 0); | ||
| (0, import_defineProperty$1.default)(this, "wsObservable", behaviorSubject(null)); | ||
| (0, import_defineProperty$1.default)(this, "openPromise", null); | ||
| this.WebSocketPonyfill = (_opts$WebSocketPonyfi = opts.WebSocketPonyfill) !== null && _opts$WebSocketPonyfi !== void 0 ? _opts$WebSocketPonyfi : WebSocket; | ||
| if (!this.WebSocketPonyfill) throw new Error("No WebSocket implementation found - you probably don't want to use this on the server, but if you do you need to pass a `WebSocket`-ponyfill"); | ||
| this.urlOptions = opts.urlOptions; | ||
| this.keepAliveOpts = opts.keepAlive; | ||
| } | ||
| get ws() { | ||
| return this.wsObservable.get(); | ||
| } | ||
| set ws(ws) { | ||
| this.wsObservable.next(ws); | ||
| } | ||
| /** | ||
| * Checks if the WebSocket connection is open and ready to communicate. | ||
| */ | ||
| isOpen() { | ||
| return !!this.ws && this.ws.readyState === this.WebSocketPonyfill.OPEN && !this.openPromise; | ||
| } | ||
| /** | ||
| * Checks if the WebSocket connection is closed or in the process of closing. | ||
| */ | ||
| isClosed() { | ||
| return !!this.ws && (this.ws.readyState === this.WebSocketPonyfill.CLOSING || this.ws.readyState === this.WebSocketPonyfill.CLOSED); | ||
| } | ||
| async open() { | ||
| var _this = this; | ||
| if (_this.openPromise) return _this.openPromise; | ||
| _this.id = ++WsConnection.connectCount; | ||
| const wsPromise = prepareUrl(_this.urlOptions).then((url) => new _this.WebSocketPonyfill(url)); | ||
| _this.openPromise = wsPromise.then(async (ws) => { | ||
| _this.ws = ws; | ||
| ws.binaryType = "arraybuffer"; | ||
| ws.addEventListener("message", function({ data }) { | ||
| if (data === "PING") this.send("PONG"); | ||
| }); | ||
| if (_this.keepAliveOpts.enabled) setupPingInterval(ws, _this.keepAliveOpts); | ||
| ws.addEventListener("close", () => { | ||
| if (_this.ws === ws) _this.ws = null; | ||
| }); | ||
| await asyncWsOpen(ws); | ||
| if (_this.urlOptions.connectionParams) ws.send(await buildConnectionMessage(_this.urlOptions.connectionParams)); | ||
| }); | ||
| try { | ||
| await _this.openPromise; | ||
| } finally { | ||
| _this.openPromise = null; | ||
| } | ||
| } | ||
| /** | ||
| * Closes the WebSocket connection gracefully. | ||
| * Waits for any ongoing open operation to complete before closing. | ||
| */ | ||
| async close() { | ||
| var _this2 = this; | ||
| try { | ||
| await _this2.openPromise; | ||
| } finally { | ||
| var _this$ws; | ||
| (_this$ws = _this2.ws) === null || _this$ws === void 0 || _this$ws.close(); | ||
| } | ||
| } | ||
| }; | ||
| (0, import_defineProperty$1.default)(WsConnection, "connectCount", 0); | ||
| /** | ||
| * Provides a backward-compatible representation of the connection state. | ||
| */ | ||
| function backwardCompatibility(connection) { | ||
| if (connection.isOpen()) return { | ||
| id: connection.id, | ||
| state: "open", | ||
| ws: connection.ws | ||
| }; | ||
| if (connection.isClosed()) return { | ||
| id: connection.id, | ||
| state: "closed", | ||
| ws: connection.ws | ||
| }; | ||
| if (!connection.ws) return null; | ||
| return { | ||
| id: connection.id, | ||
| state: "connecting", | ||
| ws: connection.ws | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/wsClient.ts | ||
| var import_defineProperty = __toESM(require_defineProperty(), 1); | ||
| var import_objectSpread2 = __toESM(require_objectSpread2(), 1); | ||
| /** | ||
| * A WebSocket client for managing TRPC operations, supporting lazy initialization, | ||
| * reconnection, keep-alive, and request management. | ||
| */ | ||
| var WsClient = class { | ||
| constructor(opts) { | ||
| var _opts$experimental_en, _opts$retryDelayMs; | ||
| (0, import_defineProperty.default)(this, "connectionState", void 0); | ||
| (0, import_defineProperty.default)(this, "allowReconnect", false); | ||
| (0, import_defineProperty.default)(this, "requestManager", new RequestManager()); | ||
| (0, import_defineProperty.default)(this, "activeConnection", void 0); | ||
| (0, import_defineProperty.default)(this, "reconnectRetryDelay", void 0); | ||
| (0, import_defineProperty.default)(this, "inactivityTimeout", void 0); | ||
| (0, import_defineProperty.default)(this, "callbacks", void 0); | ||
| (0, import_defineProperty.default)(this, "lazyMode", void 0); | ||
| (0, import_defineProperty.default)(this, "encoder", void 0); | ||
| (0, import_defineProperty.default)(this, "reconnecting", null); | ||
| this.encoder = (_opts$experimental_en = opts.experimental_encoder) !== null && _opts$experimental_en !== void 0 ? _opts$experimental_en : jsonEncoder; | ||
| this.callbacks = { | ||
| onOpen: opts.onOpen, | ||
| onClose: opts.onClose, | ||
| onError: opts.onError | ||
| }; | ||
| const lazyOptions = (0, import_objectSpread2.default)((0, import_objectSpread2.default)({}, lazyDefaults), opts.lazy); | ||
| this.inactivityTimeout = new ResettableTimeout(() => { | ||
| if (this.requestManager.hasOutgoingRequests() || this.requestManager.hasPendingRequests()) { | ||
| this.inactivityTimeout.reset(); | ||
| return; | ||
| } | ||
| this.close().catch(() => null); | ||
| }, lazyOptions.closeMs); | ||
| this.activeConnection = new WsConnection({ | ||
| WebSocketPonyfill: opts.WebSocket, | ||
| urlOptions: opts, | ||
| keepAlive: (0, import_objectSpread2.default)((0, import_objectSpread2.default)({}, keepAliveDefaults), opts.keepAlive) | ||
| }); | ||
| this.activeConnection.wsObservable.subscribe({ next: (ws) => { | ||
| if (!ws) return; | ||
| this.setupWebSocketListeners(ws); | ||
| } }); | ||
| this.reconnectRetryDelay = (_opts$retryDelayMs = opts.retryDelayMs) !== null && _opts$retryDelayMs !== void 0 ? _opts$retryDelayMs : exponentialBackoff; | ||
| this.lazyMode = lazyOptions.enabled; | ||
| this.connectionState = behaviorSubject({ | ||
| type: "state", | ||
| state: lazyOptions.enabled ? "idle" : "connecting", | ||
| error: null | ||
| }); | ||
| if (!this.lazyMode) this.open().catch(() => null); | ||
| } | ||
| /** | ||
| * Opens the WebSocket connection. Handles reconnection attempts and updates | ||
| * the connection state accordingly. | ||
| */ | ||
| async open() { | ||
| var _this = this; | ||
| _this.allowReconnect = true; | ||
| if (_this.connectionState.get().state === "idle") _this.connectionState.next({ | ||
| type: "state", | ||
| state: "connecting", | ||
| error: null | ||
| }); | ||
| try { | ||
| await _this.activeConnection.open(); | ||
| } catch (error) { | ||
| _this.reconnect(new TRPCWebSocketClosedError({ | ||
| message: "Initialization error", | ||
| cause: error | ||
| })); | ||
| return _this.reconnecting; | ||
| } | ||
| } | ||
| /** | ||
| * Closes the WebSocket connection and stops managing requests. | ||
| * Ensures all outgoing and pending requests are properly finalized. | ||
| */ | ||
| async close() { | ||
| var _this2 = this; | ||
| _this2.allowReconnect = false; | ||
| _this2.inactivityTimeout.stop(); | ||
| const requestsToAwait = []; | ||
| for (const request of _this2.requestManager.getRequests()) if (request.message.method === "subscription") request.callbacks.complete(); | ||
| else if (request.state === "outgoing") request.callbacks.error(TRPCClientError.from(new TRPCWebSocketClosedError({ message: "Closed before connection was established" }))); | ||
| else requestsToAwait.push(request.end); | ||
| await Promise.all(requestsToAwait).catch(() => null); | ||
| await _this2.activeConnection.close().catch(() => null); | ||
| _this2.connectionState.next({ | ||
| type: "state", | ||
| state: "idle", | ||
| error: null | ||
| }); | ||
| } | ||
| /** | ||
| * Method to request the server. | ||
| * Handles data transformation, batching of requests, and subscription lifecycle. | ||
| * | ||
| * @param op - The operation details including id, type, path, input and signal | ||
| * @param transformer - Data transformer for serializing requests and deserializing responses | ||
| * @param lastEventId - Optional ID of the last received event for subscriptions | ||
| * | ||
| * @returns An observable that emits operation results and handles cleanup | ||
| */ | ||
| request({ op: { id, type, path, input, signal }, transformer, lastEventId }) { | ||
| return observable((observer) => { | ||
| const abort = this.batchSend({ | ||
| id, | ||
| method: type, | ||
| params: { | ||
| input: transformer.input.serialize(input), | ||
| path, | ||
| lastEventId | ||
| } | ||
| }, (0, import_objectSpread2.default)((0, import_objectSpread2.default)({}, observer), {}, { next(event) { | ||
| const transformed = transformResult(event, transformer.output); | ||
| if (!transformed.ok) { | ||
| observer.error(TRPCClientError.from(transformed.error)); | ||
| return; | ||
| } | ||
| observer.next({ result: transformed.result }); | ||
| } })); | ||
| return () => { | ||
| abort(); | ||
| if (type === "subscription" && this.activeConnection.isOpen()) this.send({ | ||
| id, | ||
| method: "subscription.stop" | ||
| }); | ||
| signal === null || signal === void 0 || signal.removeEventListener("abort", abort); | ||
| }; | ||
| }); | ||
| } | ||
| get connection() { | ||
| return backwardCompatibility(this.activeConnection); | ||
| } | ||
| reconnect(closedError) { | ||
| var _this3 = this; | ||
| this.connectionState.next({ | ||
| type: "state", | ||
| state: "connecting", | ||
| error: TRPCClientError.from(closedError) | ||
| }); | ||
| if (this.reconnecting) return; | ||
| const tryReconnect = async (attemptIndex) => { | ||
| try { | ||
| await sleep(_this3.reconnectRetryDelay(attemptIndex)); | ||
| if (_this3.allowReconnect) { | ||
| await _this3.activeConnection.close(); | ||
| await _this3.activeConnection.open(); | ||
| if (_this3.requestManager.hasPendingRequests()) _this3.send(_this3.requestManager.getPendingRequests().map(({ message }) => message)); | ||
| } | ||
| _this3.reconnecting = null; | ||
| } catch (_unused) { | ||
| await tryReconnect(attemptIndex + 1); | ||
| } | ||
| }; | ||
| this.reconnecting = tryReconnect(0); | ||
| } | ||
| setupWebSocketListeners(ws) { | ||
| var _this4 = this; | ||
| const handleCloseOrError = (cause) => { | ||
| const reqs = this.requestManager.getPendingRequests(); | ||
| for (const { message, callbacks } of reqs) { | ||
| if (message.method === "subscription") continue; | ||
| callbacks.error(TRPCClientError.from(cause !== null && cause !== void 0 ? cause : new TRPCWebSocketClosedError({ | ||
| message: "WebSocket closed", | ||
| cause | ||
| }))); | ||
| this.requestManager.delete(message.id); | ||
| } | ||
| }; | ||
| ws.addEventListener("open", () => { | ||
| run(async () => { | ||
| var _this$callbacks$onOpe, _this$callbacks; | ||
| if (_this4.lazyMode) _this4.inactivityTimeout.start(); | ||
| (_this$callbacks$onOpe = (_this$callbacks = _this4.callbacks).onOpen) === null || _this$callbacks$onOpe === void 0 || _this$callbacks$onOpe.call(_this$callbacks); | ||
| _this4.connectionState.next({ | ||
| type: "state", | ||
| state: "pending", | ||
| error: null | ||
| }); | ||
| }).catch((error) => { | ||
| ws.close(3e3); | ||
| handleCloseOrError(error); | ||
| }); | ||
| }); | ||
| ws.addEventListener("message", ({ data }) => { | ||
| this.inactivityTimeout.reset(); | ||
| if (["PING", "PONG"].includes(data)) return; | ||
| const incomingMessage = this.encoder.decode(data); | ||
| if ("method" in incomingMessage) { | ||
| this.handleIncomingRequest(incomingMessage); | ||
| return; | ||
| } | ||
| this.handleResponseMessage(incomingMessage); | ||
| }); | ||
| ws.addEventListener("close", (event) => { | ||
| var _this$callbacks$onClo, _this$callbacks2; | ||
| handleCloseOrError(event); | ||
| (_this$callbacks$onClo = (_this$callbacks2 = this.callbacks).onClose) === null || _this$callbacks$onClo === void 0 || _this$callbacks$onClo.call(_this$callbacks2, event); | ||
| if (!this.lazyMode || this.requestManager.hasPendingSubscriptions()) this.reconnect(new TRPCWebSocketClosedError({ | ||
| message: "WebSocket closed", | ||
| cause: event | ||
| })); | ||
| }); | ||
| ws.addEventListener("error", (event) => { | ||
| var _this$callbacks$onErr, _this$callbacks3; | ||
| handleCloseOrError(event); | ||
| (_this$callbacks$onErr = (_this$callbacks3 = this.callbacks).onError) === null || _this$callbacks$onErr === void 0 || _this$callbacks$onErr.call(_this$callbacks3, event); | ||
| this.reconnect(new TRPCWebSocketClosedError({ | ||
| message: "WebSocket closed", | ||
| cause: event | ||
| })); | ||
| }); | ||
| } | ||
| handleResponseMessage(message) { | ||
| const request = this.requestManager.getPendingRequest(message.id); | ||
| if (!request) return; | ||
| request.callbacks.next(message); | ||
| let completed = true; | ||
| if ("result" in message && request.message.method === "subscription") { | ||
| if (message.result.type === "data") request.message.params.lastEventId = message.result.id; | ||
| if (message.result.type !== "stopped") completed = false; | ||
| } | ||
| if (completed) { | ||
| request.callbacks.complete(); | ||
| this.requestManager.delete(message.id); | ||
| } | ||
| } | ||
| handleIncomingRequest(message) { | ||
| if (message.method === "reconnect") this.reconnect(new TRPCWebSocketClosedError({ message: "Server requested reconnect" })); | ||
| } | ||
| /** | ||
| * Sends a message or batch of messages directly to the server. | ||
| */ | ||
| send(messageOrMessages) { | ||
| if (!this.activeConnection.isOpen()) throw new Error("Active connection is not open"); | ||
| const messages = messageOrMessages instanceof Array ? messageOrMessages : [messageOrMessages]; | ||
| this.activeConnection.ws.send(this.encoder.encode(messages.length === 1 ? messages[0] : messages)); | ||
| } | ||
| /** | ||
| * Groups requests for batch sending. | ||
| * | ||
| * @returns A function to abort the batched request. | ||
| */ | ||
| batchSend(message, callbacks) { | ||
| var _this5 = this; | ||
| this.inactivityTimeout.reset(); | ||
| run(async () => { | ||
| if (!_this5.activeConnection.isOpen()) await _this5.open(); | ||
| await sleep(0); | ||
| if (!_this5.requestManager.hasOutgoingRequests()) return; | ||
| _this5.send(_this5.requestManager.flush().map(({ message: message$1 }) => message$1)); | ||
| }).catch((err) => { | ||
| this.requestManager.delete(message.id); | ||
| callbacks.error(TRPCClientError.from(err)); | ||
| }); | ||
| return this.requestManager.register(message, callbacks); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/createWsClient.ts | ||
| function createWSClient(opts) { | ||
| return new WsClient(opts); | ||
| } | ||
| //#endregion | ||
| //#region src/links/wsLink/wsLink.ts | ||
| function wsLink(opts) { | ||
| const { client } = opts; | ||
| const transformer = getTransformer(opts.transformer); | ||
| return () => { | ||
| return ({ op }) => { | ||
| return observable((observer) => { | ||
| const connStateSubscription = op.type === "subscription" ? client.connectionState.subscribe({ next(result) { | ||
| observer.next({ | ||
| result, | ||
| context: op.context | ||
| }); | ||
| } }) : null; | ||
| const requestSubscription = client.request({ | ||
| op, | ||
| transformer | ||
| }).subscribe(observer); | ||
| return () => { | ||
| requestSubscription.unsubscribe(); | ||
| connStateSubscription === null || connStateSubscription === void 0 || connStateSubscription.unsubscribe(); | ||
| }; | ||
| }); | ||
| }; | ||
| }; | ||
| } | ||
| //#endregion | ||
| export { createWSClient, jsonEncoder, resultOf, wsLink }; | ||
| //# sourceMappingURL=wsLink-DVm7B-YP.mjs.map |
| {"version":3,"file":"wsLink-DVm7B-YP.mjs","names":["jsonEncoder: Encoder","lazyDefaults: LazyOptions","keepAliveDefaults: KeepAliveOptions","attemptIndex: number","value: T | ((...args: TArgs) => T)","opts: { message: string; cause?: unknown }","onTimeout: () => void","timeoutMs: number","resolve: (value: T | PromiseLike<T>) => void","reject: (reason?: any) => void","urlOptions: UrlOptionsWithConnectionParams","connectionParams: CallbackOrValue<TRPCRequestInfo['connectionParams']>","message: TRPCConnectionParamsMessage","message: TRPCClientOutgoingMessage","callbacks: TCallbacks","messageId: MessageIdLike","ws: WebSocket","pingTimeout: ReturnType<typeof setTimeout> | undefined","pongTimeout: ReturnType<typeof setTimeout> | undefined","opts: WebSocketConnectionOptions","this","connection: WsConnection","opts: WebSocketClientOptions","this","requestsToAwait: Promise<void>[]","closedError: TRPCWebSocketClosedError","attemptIndex: number","ws: WebSocket","cause: unknown","message: TRPCResponseMessage","message: TRPCClientIncomingRequest","messageOrMessages: TRPCClientOutgoingMessage | TRPCClientOutgoingMessage[]","message: TRPCClientOutgoingMessage","callbacks: TCallbacks","message","opts: WebSocketClientOptions","opts: WebSocketLinkOptions<TRouter>"],"sources":["../src/links/wsLink/wsClient/encoder.ts","../src/links/wsLink/wsClient/options.ts","../src/links/internals/urlWithConnectionParams.ts","../src/links/wsLink/wsClient/utils.ts","../src/links/wsLink/wsClient/requestManager.ts","../src/links/wsLink/wsClient/wsConnection.ts","../src/links/wsLink/wsClient/wsClient.ts","../src/links/wsLink/createWsClient.ts","../src/links/wsLink/wsLink.ts"],"sourcesContent":["import type { Encoder } from '@trpc/server/adapters/ws';\n\nexport type { Encoder };\n\nexport const jsonEncoder: Encoder = {\n encode: (data) => JSON.stringify(data),\n decode: (data) => {\n if (typeof data !== 'string') {\n throw new Error(\n 'jsonEncoder received binary data. JSON uses text frames. ' +\n 'Use a binary encoder for binary data.',\n );\n }\n return JSON.parse(data);\n },\n};\n","import type { UrlOptionsWithConnectionParams } from '../../internals/urlWithConnectionParams';\nimport type { Encoder } from './encoder';\n\nexport interface WebSocketClientOptions extends UrlOptionsWithConnectionParams {\n /**\n * Ponyfill which WebSocket implementation to use\n */\n WebSocket?: typeof WebSocket;\n /**\n * The number of milliseconds before a reconnect is attempted.\n * @default {@link exponentialBackoff}\n */\n retryDelayMs?: (attemptIndex: number) => number;\n /**\n * Triggered when a WebSocket connection is established\n */\n onOpen?: () => void;\n /**\n * Triggered when a WebSocket connection encounters an error\n */\n onError?: (evt?: Event) => void;\n /**\n * Triggered when a WebSocket connection is closed\n */\n onClose?: (cause?: { code?: number }) => void;\n /**\n * Lazy mode will close the WebSocket automatically after a period of inactivity (no messages sent or received and no pending requests)\n */\n lazy?: {\n /**\n * Enable lazy mode\n * @default false\n */\n enabled: boolean;\n /**\n * Close the WebSocket after this many milliseconds\n * @default 0\n */\n closeMs: number;\n };\n /**\n * Send ping messages to the server and kill the connection if no pong message is returned\n */\n keepAlive?: {\n /**\n * @default false\n */\n enabled: boolean;\n /**\n * Send a ping message every this many milliseconds\n * @default 5_000\n */\n intervalMs?: number;\n /**\n * Close the WebSocket after this many milliseconds if the server does not respond\n * @default 1_000\n */\n pongTimeoutMs?: number;\n };\n /**\n * Custom encoder for wire encoding (e.g. custom binary formats)\n * @default jsonEncoder\n */\n experimental_encoder?: Encoder;\n}\n\n/**\n * Default options for lazy WebSocket connections.\n * Determines whether the connection should be established lazily and defines the delay before closure.\n */\nexport type LazyOptions = Required<NonNullable<WebSocketClientOptions['lazy']>>;\nexport const lazyDefaults: LazyOptions = {\n enabled: false,\n closeMs: 0,\n};\n\n/**\n * Default options for the WebSocket keep-alive mechanism.\n * Configures whether keep-alive is enabled and specifies the timeout and interval for ping-pong messages.\n */\nexport type KeepAliveOptions = Required<\n NonNullable<WebSocketClientOptions['keepAlive']>\n>;\nexport const keepAliveDefaults: KeepAliveOptions = {\n enabled: false,\n pongTimeoutMs: 1_000,\n intervalMs: 5_000,\n};\n\n/**\n * Calculates a delay for exponential backoff based on the retry attempt index.\n * The delay starts at 0 for the first attempt and doubles for each subsequent attempt,\n * capped at 30 seconds.\n */\nexport const exponentialBackoff = (attemptIndex: number) => {\n return attemptIndex === 0 ? 0 : Math.min(1000 * 2 ** attemptIndex, 30000);\n};\n","import { type TRPCRequestInfo } from '@trpc/server/http';\n\n/**\n * Get the result of a value or function that returns a value\n * It also optionally accepts typesafe arguments for the function\n */\nexport const resultOf = <T, TArgs extends any[]>(\n value: T | ((...args: TArgs) => T),\n ...args: TArgs\n): T => {\n return typeof value === 'function'\n ? (value as (...args: TArgs) => T)(...args)\n : value;\n};\n\n/**\n * A value that can be wrapped in callback\n */\nexport type CallbackOrValue<T> = T | (() => T | Promise<T>);\n\nexport interface UrlOptionsWithConnectionParams {\n /**\n * The URL to connect to (can be a function that returns a URL)\n */\n url: CallbackOrValue<string>;\n\n /**\n * Connection params that are available in `createContext()`\n * - For `wsLink`/`wsClient`, these are sent as the first message\n * - For `httpSubscriptionLink`, these are serialized as part of the URL under the `connectionParams` query\n */\n connectionParams?: CallbackOrValue<TRPCRequestInfo['connectionParams']>;\n}\n","import type {\n TRPCConnectionParamsMessage,\n TRPCRequestInfo,\n} from '@trpc/server/unstable-core-do-not-import';\nimport type {\n CallbackOrValue,\n UrlOptionsWithConnectionParams,\n} from '../../internals/urlWithConnectionParams';\nimport { resultOf } from '../../internals/urlWithConnectionParams';\n\nexport class TRPCWebSocketClosedError extends Error {\n constructor(opts: { message: string; cause?: unknown }) {\n super(opts.message, {\n cause: opts.cause,\n });\n this.name = 'TRPCWebSocketClosedError';\n Object.setPrototypeOf(this, TRPCWebSocketClosedError.prototype);\n }\n}\n\n/**\n * Utility class for managing a timeout that can be started, stopped, and reset.\n * Useful for scenarios where the timeout duration is reset dynamically based on events.\n */\nexport class ResettableTimeout {\n private timeout: ReturnType<typeof setTimeout> | undefined;\n\n constructor(\n private readonly onTimeout: () => void,\n private readonly timeoutMs: number,\n ) {}\n\n /**\n * Resets the current timeout, restarting it with the same duration.\n * Does nothing if no timeout is active.\n */\n public reset() {\n if (!this.timeout) return;\n\n clearTimeout(this.timeout);\n this.timeout = setTimeout(this.onTimeout, this.timeoutMs);\n }\n\n public start() {\n clearTimeout(this.timeout);\n this.timeout = setTimeout(this.onTimeout, this.timeoutMs);\n }\n\n public stop() {\n clearTimeout(this.timeout);\n this.timeout = undefined;\n }\n}\n\n// Ponyfill for Promise.withResolvers https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers\nexport function withResolvers<T>() {\n let resolve: (value: T | PromiseLike<T>) => void;\n let reject: (reason?: any) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return { promise, resolve: resolve!, reject: reject! };\n}\n\n/**\n * Resolves a WebSocket URL and optionally appends connection parameters.\n *\n * If connectionParams are provided, appends 'connectionParams=1' query parameter.\n */\nexport async function prepareUrl(urlOptions: UrlOptionsWithConnectionParams) {\n const url = await resultOf(urlOptions.url);\n\n if (!urlOptions.connectionParams) return url;\n\n // append `?connectionParams=1` when connection params are used\n const prefix = url.includes('?') ? '&' : '?';\n const connectionParams = `${prefix}connectionParams=1`;\n\n return url + connectionParams;\n}\n\nexport async function buildConnectionMessage(\n connectionParams: CallbackOrValue<TRPCRequestInfo['connectionParams']>,\n) {\n const message: TRPCConnectionParamsMessage = {\n method: 'connectionParams',\n data: await resultOf(connectionParams),\n };\n\n return JSON.stringify(message);\n}\n","import type { AnyTRPCRouter, inferRouterError } from '@trpc/server';\nimport type { Observer } from '@trpc/server/observable';\nimport type {\n TRPCClientOutgoingMessage,\n TRPCResponseMessage,\n} from '@trpc/server/unstable-core-do-not-import';\nimport type { TRPCClientError } from '../../../TRPCClientError';\nimport { withResolvers } from './utils';\n\nexport type TCallbacks = Observer<\n TRPCResponseMessage<unknown, inferRouterError<AnyTRPCRouter>>,\n TRPCClientError<AnyTRPCRouter>\n>;\n\ntype MessageId = string;\ntype MessageIdLike = string | number | null;\n\n/**\n * Represents a WebSocket request managed by the RequestManager.\n * Combines the network message, a utility promise (`end`) that mirrors the lifecycle\n * handled by `callbacks`, and a set of state monitoring callbacks.\n */\ninterface Request {\n message: TRPCClientOutgoingMessage;\n end: Promise<void>;\n callbacks: TCallbacks;\n}\n\n/**\n * Manages WebSocket requests, tracking their lifecycle and providing utility methods\n * for handling outgoing and pending requests.\n *\n * - **Outgoing requests**: Requests that are queued and waiting to be sent.\n * - **Pending requests**: Requests that have been sent and are in flight awaiting a response.\n * For subscriptions, multiple responses may be received until the subscription is closed.\n */\nexport class RequestManager {\n /**\n * Stores requests that are outgoing, meaning they are registered but not yet sent over the WebSocket.\n */\n private outgoingRequests = new Array<Request & { id: MessageId }>();\n\n /**\n * Stores requests that are pending (in flight), meaning they have been sent over the WebSocket\n * and are awaiting responses. For subscriptions, this includes requests\n * that may receive multiple responses.\n */\n private pendingRequests: Record<MessageId, Request> = {};\n\n /**\n * Registers a new request by adding it to the outgoing queue and setting up\n * callbacks for lifecycle events such as completion or error.\n *\n * @param message - The outgoing message to be sent.\n * @param callbacks - Callback functions to observe the request's state.\n * @returns A cleanup function to manually remove the request.\n */\n public register(message: TRPCClientOutgoingMessage, callbacks: TCallbacks) {\n const { promise: end, resolve } = withResolvers<void>();\n\n this.outgoingRequests.push({\n id: String(message.id),\n message,\n end,\n callbacks: {\n next: callbacks.next,\n complete: () => {\n callbacks.complete();\n resolve();\n },\n error: (e) => {\n callbacks.error(e);\n resolve();\n },\n },\n });\n\n return () => {\n this.delete(message.id);\n callbacks.complete();\n resolve();\n };\n }\n\n /**\n * Deletes a request from both the outgoing and pending collections, if it exists.\n */\n public delete(messageId: MessageIdLike) {\n if (messageId === null) return;\n\n this.outgoingRequests = this.outgoingRequests.filter(\n ({ id }) => id !== String(messageId),\n );\n delete this.pendingRequests[String(messageId)];\n }\n\n /**\n * Moves all outgoing requests to the pending state and clears the outgoing queue.\n *\n * The caller is expected to handle the actual sending of the requests\n * (e.g., sending them over the network) after this method is called.\n *\n * @returns The list of requests that were transitioned to the pending state.\n */\n public flush() {\n const requests = this.outgoingRequests;\n this.outgoingRequests = [];\n\n for (const request of requests) {\n this.pendingRequests[request.id] = request;\n }\n return requests;\n }\n\n /**\n * Retrieves all currently pending requests, which are in flight awaiting responses\n * or handling ongoing subscriptions.\n */\n public getPendingRequests() {\n return Object.values(this.pendingRequests);\n }\n\n /**\n * Retrieves a specific pending request by its message ID.\n */\n public getPendingRequest(messageId: MessageIdLike) {\n if (messageId === null) return null;\n\n return this.pendingRequests[String(messageId)];\n }\n\n /**\n * Retrieves all outgoing requests, which are waiting to be sent.\n */\n public getOutgoingRequests() {\n return this.outgoingRequests;\n }\n\n /**\n * Retrieves all requests, both outgoing and pending, with their respective states.\n *\n * @returns An array of all requests with their state (\"outgoing\" or \"pending\").\n */\n public getRequests() {\n return [\n ...this.getOutgoingRequests().map((request) => ({\n state: 'outgoing' as const,\n message: request.message,\n end: request.end,\n callbacks: request.callbacks,\n })),\n ...this.getPendingRequests().map((request) => ({\n state: 'pending' as const,\n message: request.message,\n end: request.end,\n callbacks: request.callbacks,\n })),\n ];\n }\n\n /**\n * Checks if there are any pending requests, including ongoing subscriptions.\n */\n public hasPendingRequests() {\n return this.getPendingRequests().length > 0;\n }\n\n /**\n * Checks if there are any pending subscriptions\n */\n public hasPendingSubscriptions() {\n return this.getPendingRequests().some(\n (request) => request.message.method === 'subscription',\n );\n }\n\n /**\n * Checks if there are any outgoing requests waiting to be sent.\n */\n public hasOutgoingRequests() {\n return this.outgoingRequests.length > 0;\n }\n}\n","import { behaviorSubject } from '@trpc/server/observable';\nimport type { UrlOptionsWithConnectionParams } from '../../internals/urlWithConnectionParams';\nimport { buildConnectionMessage, prepareUrl, withResolvers } from './utils';\n\n/**\n * Opens a WebSocket connection asynchronously and returns a promise\n * that resolves when the connection is successfully established.\n * The promise rejects if an error occurs during the connection attempt.\n */\nfunction asyncWsOpen(ws: WebSocket) {\n const { promise, resolve, reject } = withResolvers<void>();\n\n ws.addEventListener('open', () => {\n ws.removeEventListener('error', reject);\n resolve();\n });\n ws.addEventListener('error', reject);\n\n return promise;\n}\n\ninterface PingPongOptions {\n /**\n * The interval (in milliseconds) between \"PING\" messages.\n */\n intervalMs: number;\n\n /**\n * The timeout (in milliseconds) to wait for a \"PONG\" response before closing the connection.\n */\n pongTimeoutMs: number;\n}\n\n/**\n * Sets up a periodic ping-pong mechanism to keep the WebSocket connection alive.\n *\n * - Sends \"PING\" messages at regular intervals defined by `intervalMs`.\n * - If a \"PONG\" response is not received within the `pongTimeoutMs`, the WebSocket is closed.\n * - The ping timer resets upon receiving any message to maintain activity.\n * - Automatically starts the ping process when the WebSocket connection is opened.\n * - Cleans up timers when the WebSocket is closed.\n *\n * @param ws - The WebSocket instance to manage.\n * @param options - Configuration options for ping-pong intervals and timeouts.\n */\nfunction setupPingInterval(\n ws: WebSocket,\n { intervalMs, pongTimeoutMs }: PingPongOptions,\n) {\n let pingTimeout: ReturnType<typeof setTimeout> | undefined;\n let pongTimeout: ReturnType<typeof setTimeout> | undefined;\n\n function start() {\n pingTimeout = setTimeout(() => {\n ws.send('PING');\n pongTimeout = setTimeout(() => {\n ws.close();\n }, pongTimeoutMs);\n }, intervalMs);\n }\n\n function reset() {\n clearTimeout(pingTimeout);\n start();\n }\n\n function pong() {\n clearTimeout(pongTimeout);\n reset();\n }\n\n ws.addEventListener('open', start);\n ws.addEventListener('message', ({ data }) => {\n clearTimeout(pingTimeout);\n start();\n\n if (data === 'PONG') {\n pong();\n }\n });\n ws.addEventListener('close', () => {\n clearTimeout(pingTimeout);\n clearTimeout(pongTimeout);\n });\n}\n\nexport interface WebSocketConnectionOptions {\n WebSocketPonyfill?: typeof WebSocket;\n urlOptions: UrlOptionsWithConnectionParams;\n keepAlive: PingPongOptions & {\n enabled: boolean;\n };\n}\n\n/**\n * Manages a WebSocket connection with support for reconnection, keep-alive mechanisms,\n * and observable state tracking.\n */\nexport class WsConnection {\n static connectCount = 0;\n public id = ++WsConnection.connectCount;\n\n private readonly WebSocketPonyfill: typeof WebSocket;\n private readonly urlOptions: UrlOptionsWithConnectionParams;\n private readonly keepAliveOpts: WebSocketConnectionOptions['keepAlive'];\n public readonly wsObservable = behaviorSubject<WebSocket | null>(null);\n\n constructor(opts: WebSocketConnectionOptions) {\n this.WebSocketPonyfill = opts.WebSocketPonyfill ?? WebSocket;\n if (!this.WebSocketPonyfill) {\n throw new Error(\n \"No WebSocket implementation found - you probably don't want to use this on the server, but if you do you need to pass a `WebSocket`-ponyfill\",\n );\n }\n\n this.urlOptions = opts.urlOptions;\n this.keepAliveOpts = opts.keepAlive;\n }\n\n public get ws() {\n return this.wsObservable.get();\n }\n\n private set ws(ws) {\n this.wsObservable.next(ws);\n }\n\n /**\n * Checks if the WebSocket connection is open and ready to communicate.\n */\n public isOpen(): this is { ws: WebSocket } {\n return (\n !!this.ws &&\n this.ws.readyState === this.WebSocketPonyfill.OPEN &&\n !this.openPromise\n );\n }\n\n /**\n * Checks if the WebSocket connection is closed or in the process of closing.\n */\n public isClosed(): this is { ws: WebSocket } {\n return (\n !!this.ws &&\n (this.ws.readyState === this.WebSocketPonyfill.CLOSING ||\n this.ws.readyState === this.WebSocketPonyfill.CLOSED)\n );\n }\n\n /**\n * Manages the WebSocket opening process, ensuring that only one open operation\n * occurs at a time. Tracks the ongoing operation with `openPromise` to avoid\n * redundant calls and ensure proper synchronization.\n *\n * Sets up the keep-alive mechanism and necessary event listeners for the connection.\n *\n * @returns A promise that resolves once the WebSocket connection is successfully opened.\n */\n private openPromise: Promise<void> | null = null;\n public async open() {\n if (this.openPromise) return this.openPromise;\n\n this.id = ++WsConnection.connectCount;\n const wsPromise = prepareUrl(this.urlOptions).then(\n (url) => new this.WebSocketPonyfill(url),\n );\n this.openPromise = wsPromise.then(async (ws) => {\n this.ws = ws;\n\n // Set binaryType to handle both text and binary messages consistently\n ws.binaryType = 'arraybuffer';\n\n // Setup ping listener\n ws.addEventListener('message', function ({ data }) {\n if (data === 'PING') {\n this.send('PONG');\n }\n });\n\n if (this.keepAliveOpts.enabled) {\n setupPingInterval(ws, this.keepAliveOpts);\n }\n\n ws.addEventListener('close', () => {\n if (this.ws === ws) {\n this.ws = null;\n }\n });\n\n await asyncWsOpen(ws);\n\n if (this.urlOptions.connectionParams) {\n ws.send(await buildConnectionMessage(this.urlOptions.connectionParams));\n }\n });\n\n try {\n await this.openPromise;\n } finally {\n this.openPromise = null;\n }\n }\n\n /**\n * Closes the WebSocket connection gracefully.\n * Waits for any ongoing open operation to complete before closing.\n */\n public async close() {\n try {\n await this.openPromise;\n } finally {\n this.ws?.close();\n }\n }\n}\n\n/**\n * Provides a backward-compatible representation of the connection state.\n */\nexport function backwardCompatibility(connection: WsConnection) {\n if (connection.isOpen()) {\n return {\n id: connection.id,\n state: 'open',\n ws: connection.ws,\n } as const;\n }\n\n if (connection.isClosed()) {\n return {\n id: connection.id,\n state: 'closed',\n ws: connection.ws,\n } as const;\n }\n\n if (!connection.ws) {\n return null;\n }\n\n return {\n id: connection.id,\n state: 'connecting',\n ws: connection.ws,\n } as const;\n}\n","import type { AnyTRPCRouter } from '@trpc/server';\nimport type { BehaviorSubject } from '@trpc/server/observable';\nimport { behaviorSubject, observable } from '@trpc/server/observable';\nimport type {\n CombinedDataTransformer,\n TRPCClientIncomingMessage,\n TRPCClientIncomingRequest,\n TRPCClientOutgoingMessage,\n TRPCResponseMessage,\n} from '@trpc/server/unstable-core-do-not-import';\nimport {\n run,\n sleep,\n transformResult,\n} from '@trpc/server/unstable-core-do-not-import';\nimport { TRPCClientError } from '../../../TRPCClientError';\nimport type { TRPCConnectionState } from '../../internals/subscriptions';\nimport type { Operation, OperationResultEnvelope } from '../../types';\nimport type { Encoder } from './encoder';\nimport { jsonEncoder } from './encoder';\nimport type { WebSocketClientOptions } from './options';\nimport { exponentialBackoff, keepAliveDefaults, lazyDefaults } from './options';\nimport type { TCallbacks } from './requestManager';\nimport { RequestManager } from './requestManager';\nimport { ResettableTimeout, TRPCWebSocketClosedError } from './utils';\nimport { backwardCompatibility, WsConnection } from './wsConnection';\n\n/**\n * A WebSocket client for managing TRPC operations, supporting lazy initialization,\n * reconnection, keep-alive, and request management.\n */\nexport class WsClient {\n /**\n * Observable tracking the current connection state, including errors.\n */\n public readonly connectionState: BehaviorSubject<\n TRPCConnectionState<TRPCClientError<AnyTRPCRouter>>\n >;\n\n private allowReconnect = false;\n private requestManager = new RequestManager();\n private readonly activeConnection: WsConnection;\n private readonly reconnectRetryDelay: (attemptIndex: number) => number;\n private inactivityTimeout: ResettableTimeout;\n private readonly callbacks: Pick<\n WebSocketClientOptions,\n 'onOpen' | 'onClose' | 'onError'\n >;\n private readonly lazyMode: boolean;\n private readonly encoder: Encoder;\n\n constructor(opts: WebSocketClientOptions) {\n this.encoder = opts.experimental_encoder ?? jsonEncoder;\n // Initialize callbacks, connection parameters, and options.\n this.callbacks = {\n onOpen: opts.onOpen,\n onClose: opts.onClose,\n onError: opts.onError,\n };\n\n const lazyOptions = {\n ...lazyDefaults,\n ...opts.lazy,\n };\n\n // Set up inactivity timeout for lazy connections.\n this.inactivityTimeout = new ResettableTimeout(() => {\n if (\n this.requestManager.hasOutgoingRequests() ||\n this.requestManager.hasPendingRequests()\n ) {\n this.inactivityTimeout.reset();\n return;\n }\n\n this.close().catch(() => null);\n }, lazyOptions.closeMs);\n\n // Initialize the WebSocket connection.\n this.activeConnection = new WsConnection({\n WebSocketPonyfill: opts.WebSocket,\n urlOptions: opts,\n keepAlive: {\n ...keepAliveDefaults,\n ...opts.keepAlive,\n },\n });\n this.activeConnection.wsObservable.subscribe({\n next: (ws) => {\n if (!ws) return;\n this.setupWebSocketListeners(ws);\n },\n });\n this.reconnectRetryDelay = opts.retryDelayMs ?? exponentialBackoff;\n\n this.lazyMode = lazyOptions.enabled;\n\n this.connectionState = behaviorSubject<\n TRPCConnectionState<TRPCClientError<AnyTRPCRouter>>\n >({\n type: 'state',\n state: lazyOptions.enabled ? 'idle' : 'connecting',\n error: null,\n });\n\n // Automatically open the connection if lazy mode is disabled.\n if (!this.lazyMode) {\n this.open().catch(() => null);\n }\n }\n\n /**\n * Opens the WebSocket connection. Handles reconnection attempts and updates\n * the connection state accordingly.\n */\n private async open() {\n this.allowReconnect = true;\n if (this.connectionState.get().state === 'idle') {\n this.connectionState.next({\n type: 'state',\n state: 'connecting',\n error: null,\n });\n }\n\n try {\n await this.activeConnection.open();\n } catch (error) {\n this.reconnect(\n new TRPCWebSocketClosedError({\n message: 'Initialization error',\n cause: error,\n }),\n );\n return this.reconnecting;\n }\n }\n\n /**\n * Closes the WebSocket connection and stops managing requests.\n * Ensures all outgoing and pending requests are properly finalized.\n */\n public async close() {\n this.allowReconnect = false;\n this.inactivityTimeout.stop();\n\n const requestsToAwait: Promise<void>[] = [];\n for (const request of this.requestManager.getRequests()) {\n if (request.message.method === 'subscription') {\n request.callbacks.complete();\n } else if (request.state === 'outgoing') {\n request.callbacks.error(\n TRPCClientError.from(\n new TRPCWebSocketClosedError({\n message: 'Closed before connection was established',\n }),\n ),\n );\n } else {\n requestsToAwait.push(request.end);\n }\n }\n\n await Promise.all(requestsToAwait).catch(() => null);\n await this.activeConnection.close().catch(() => null);\n\n this.connectionState.next({\n type: 'state',\n state: 'idle',\n error: null,\n });\n }\n\n /**\n * Method to request the server.\n * Handles data transformation, batching of requests, and subscription lifecycle.\n *\n * @param op - The operation details including id, type, path, input and signal\n * @param transformer - Data transformer for serializing requests and deserializing responses\n * @param lastEventId - Optional ID of the last received event for subscriptions\n *\n * @returns An observable that emits operation results and handles cleanup\n */\n public request({\n op: { id, type, path, input, signal },\n transformer,\n lastEventId,\n }: {\n op: Pick<Operation, 'id' | 'type' | 'path' | 'input' | 'signal'>;\n transformer: CombinedDataTransformer;\n lastEventId?: string;\n }) {\n return observable<\n OperationResultEnvelope<unknown, TRPCClientError<AnyTRPCRouter>>,\n TRPCClientError<AnyTRPCRouter>\n >((observer) => {\n const abort = this.batchSend(\n {\n id,\n method: type,\n params: {\n input: transformer.input.serialize(input),\n path,\n lastEventId,\n },\n },\n {\n ...observer,\n next(event) {\n const transformed = transformResult(event, transformer.output);\n\n if (!transformed.ok) {\n observer.error(TRPCClientError.from(transformed.error));\n return;\n }\n\n observer.next({\n result: transformed.result,\n });\n },\n },\n );\n\n return () => {\n abort();\n\n if (type === 'subscription' && this.activeConnection.isOpen()) {\n this.send({\n id,\n method: 'subscription.stop',\n });\n }\n\n signal?.removeEventListener('abort', abort);\n };\n });\n }\n\n public get connection() {\n return backwardCompatibility(this.activeConnection);\n }\n\n /**\n * Manages the reconnection process for the WebSocket using retry logic.\n * Ensures that only one reconnection attempt is active at a time by tracking the current\n * reconnection state in the `reconnecting` promise.\n */\n private reconnecting: Promise<void> | null = null;\n private reconnect(closedError: TRPCWebSocketClosedError) {\n this.connectionState.next({\n type: 'state',\n state: 'connecting',\n error: TRPCClientError.from(closedError),\n });\n if (this.reconnecting) return;\n\n const tryReconnect = async (attemptIndex: number) => {\n try {\n await sleep(this.reconnectRetryDelay(attemptIndex));\n if (this.allowReconnect) {\n await this.activeConnection.close();\n await this.activeConnection.open();\n\n if (this.requestManager.hasPendingRequests()) {\n this.send(\n this.requestManager\n .getPendingRequests()\n .map(({ message }) => message),\n );\n }\n }\n this.reconnecting = null;\n } catch {\n await tryReconnect(attemptIndex + 1);\n }\n };\n\n this.reconnecting = tryReconnect(0);\n }\n\n private setupWebSocketListeners(ws: WebSocket) {\n const handleCloseOrError = (cause: unknown) => {\n const reqs = this.requestManager.getPendingRequests();\n for (const { message, callbacks } of reqs) {\n if (message.method === 'subscription') continue;\n\n callbacks.error(\n TRPCClientError.from(\n cause ??\n new TRPCWebSocketClosedError({\n message: 'WebSocket closed',\n cause,\n }),\n ),\n );\n this.requestManager.delete(message.id);\n }\n };\n\n ws.addEventListener('open', () => {\n run(async () => {\n if (this.lazyMode) {\n this.inactivityTimeout.start();\n }\n\n this.callbacks.onOpen?.();\n\n this.connectionState.next({\n type: 'state',\n state: 'pending',\n error: null,\n });\n }).catch((error) => {\n ws.close(3000);\n handleCloseOrError(error);\n });\n });\n\n ws.addEventListener('message', ({ data }) => {\n this.inactivityTimeout.reset();\n\n // Handle PING/PONG as text regardless of encoder\n if (['PING', 'PONG'].includes(data)) return;\n\n const incomingMessage = this.encoder.decode(\n data,\n ) as TRPCClientIncomingMessage;\n if ('method' in incomingMessage) {\n this.handleIncomingRequest(incomingMessage);\n return;\n }\n\n this.handleResponseMessage(incomingMessage);\n });\n\n ws.addEventListener('close', (event) => {\n handleCloseOrError(event);\n this.callbacks.onClose?.(event);\n\n if (!this.lazyMode || this.requestManager.hasPendingSubscriptions()) {\n this.reconnect(\n new TRPCWebSocketClosedError({\n message: 'WebSocket closed',\n cause: event,\n }),\n );\n }\n });\n\n ws.addEventListener('error', (event) => {\n handleCloseOrError(event);\n this.callbacks.onError?.(event);\n\n this.reconnect(\n new TRPCWebSocketClosedError({\n message: 'WebSocket closed',\n cause: event,\n }),\n );\n });\n }\n\n private handleResponseMessage(message: TRPCResponseMessage) {\n const request = this.requestManager.getPendingRequest(message.id);\n if (!request) return;\n\n request.callbacks.next(message);\n\n let completed = true;\n if ('result' in message && request.message.method === 'subscription') {\n if (message.result.type === 'data') {\n request.message.params.lastEventId = message.result.id;\n }\n\n if (message.result.type !== 'stopped') {\n completed = false;\n }\n }\n\n if (completed) {\n request.callbacks.complete();\n this.requestManager.delete(message.id);\n }\n }\n\n private handleIncomingRequest(message: TRPCClientIncomingRequest) {\n if (message.method === 'reconnect') {\n this.reconnect(\n new TRPCWebSocketClosedError({\n message: 'Server requested reconnect',\n }),\n );\n }\n }\n\n /**\n * Sends a message or batch of messages directly to the server.\n */\n private send(\n messageOrMessages: TRPCClientOutgoingMessage | TRPCClientOutgoingMessage[],\n ) {\n if (!this.activeConnection.isOpen()) {\n throw new Error('Active connection is not open');\n }\n\n const messages =\n messageOrMessages instanceof Array\n ? messageOrMessages\n : [messageOrMessages];\n this.activeConnection.ws.send(\n this.encoder.encode(messages.length === 1 ? messages[0] : messages),\n );\n }\n\n /**\n * Groups requests for batch sending.\n *\n * @returns A function to abort the batched request.\n */\n private batchSend(message: TRPCClientOutgoingMessage, callbacks: TCallbacks) {\n this.inactivityTimeout.reset();\n\n run(async () => {\n if (!this.activeConnection.isOpen()) {\n await this.open();\n }\n await sleep(0);\n\n if (!this.requestManager.hasOutgoingRequests()) return;\n\n this.send(this.requestManager.flush().map(({ message }) => message));\n }).catch((err) => {\n this.requestManager.delete(message.id);\n callbacks.error(TRPCClientError.from(err));\n });\n\n return this.requestManager.register(message, callbacks);\n }\n}\n","import type { Encoder } from './wsClient/encoder';\nimport { jsonEncoder } from './wsClient/encoder';\nimport type { WebSocketClientOptions } from './wsClient/options';\nimport { WsClient } from './wsClient/wsClient';\n\nexport function createWSClient(opts: WebSocketClientOptions) {\n return new WsClient(opts);\n}\n\nexport type TRPCWebSocketClient = ReturnType<typeof createWSClient>;\n\nexport { jsonEncoder, type Encoder, type WebSocketClientOptions };\n","import { observable } from '@trpc/server/observable';\nimport type {\n AnyRouter,\n inferClientTypes,\n} from '@trpc/server/unstable-core-do-not-import';\nimport type { TransformerOptions } from '../../unstable-internals';\nimport { getTransformer } from '../../unstable-internals';\nimport type { TRPCLink } from '../types';\nimport type {\n Encoder,\n TRPCWebSocketClient,\n WebSocketClientOptions,\n} from './createWsClient';\nimport { createWSClient, jsonEncoder } from './createWsClient';\n\nexport type WebSocketLinkOptions<TRouter extends AnyRouter> = {\n client: TRPCWebSocketClient;\n} & TransformerOptions<inferClientTypes<TRouter>>;\n\nexport function wsLink<TRouter extends AnyRouter>(\n opts: WebSocketLinkOptions<TRouter>,\n): TRPCLink<TRouter> {\n const { client } = opts;\n const transformer = getTransformer(opts.transformer);\n return () => {\n return ({ op }) => {\n return observable((observer) => {\n const connStateSubscription =\n op.type === 'subscription'\n ? client.connectionState.subscribe({\n next(result) {\n observer.next({\n result,\n context: op.context,\n });\n },\n })\n : null;\n\n const requestSubscription = client\n .request({\n op,\n transformer,\n })\n .subscribe(observer);\n\n return () => {\n requestSubscription.unsubscribe();\n connStateSubscription?.unsubscribe();\n };\n });\n };\n };\n}\n\nexport {\n createWSClient,\n jsonEncoder,\n type Encoder,\n type TRPCWebSocketClient,\n type WebSocketClientOptions,\n};\n"],"mappings":"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"} |
| import { TRPCConnectionState } from "./subscriptions.d-Dlr1nWGD.mjs"; | ||
| import { Operation, OperationResultEnvelope, TRPCClientError, TRPCLink } from "./types.d-CAt1zKAY.mjs"; | ||
| import { TransformerOptions } from "./unstable-internals.d-BOmV7EK1.mjs"; | ||
| import * as _trpc_server_observable0 from "@trpc/server/observable"; | ||
| import { BehaviorSubject } from "@trpc/server/observable"; | ||
| import { AnyRouter, CombinedDataTransformer, inferClientTypes } from "@trpc/server/unstable-core-do-not-import"; | ||
| import { AnyTRPCRouter } from "@trpc/server"; | ||
| import { Encoder } from "@trpc/server/adapters/ws"; | ||
| import { TRPCRequestInfo } from "@trpc/server/http"; | ||
| //#region src/links/wsLink/wsClient/encoder.d.ts | ||
| declare const jsonEncoder: Encoder; | ||
| //# sourceMappingURL=encoder.d.ts.map | ||
| //#endregion | ||
| //#region src/links/internals/urlWithConnectionParams.d.ts | ||
| /** | ||
| * A value that can be wrapped in callback | ||
| */ | ||
| type CallbackOrValue<T> = T | (() => T | Promise<T>); | ||
| interface UrlOptionsWithConnectionParams { | ||
| /** | ||
| * The URL to connect to (can be a function that returns a URL) | ||
| */ | ||
| url: CallbackOrValue<string>; | ||
| /** | ||
| * Connection params that are available in `createContext()` | ||
| * - For `wsLink`/`wsClient`, these are sent as the first message | ||
| * - For `httpSubscriptionLink`, these are serialized as part of the URL under the `connectionParams` query | ||
| */ | ||
| connectionParams?: CallbackOrValue<TRPCRequestInfo['connectionParams']>; | ||
| } | ||
| //# sourceMappingURL=urlWithConnectionParams.d.ts.map | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/options.d.ts | ||
| interface WebSocketClientOptions extends UrlOptionsWithConnectionParams { | ||
| /** | ||
| * Ponyfill which WebSocket implementation to use | ||
| */ | ||
| WebSocket?: typeof WebSocket; | ||
| /** | ||
| * The number of milliseconds before a reconnect is attempted. | ||
| * @default {@link exponentialBackoff} | ||
| */ | ||
| retryDelayMs?: (attemptIndex: number) => number; | ||
| /** | ||
| * Triggered when a WebSocket connection is established | ||
| */ | ||
| onOpen?: () => void; | ||
| /** | ||
| * Triggered when a WebSocket connection encounters an error | ||
| */ | ||
| onError?: (evt?: Event) => void; | ||
| /** | ||
| * Triggered when a WebSocket connection is closed | ||
| */ | ||
| onClose?: (cause?: { | ||
| code?: number; | ||
| }) => void; | ||
| /** | ||
| * Lazy mode will close the WebSocket automatically after a period of inactivity (no messages sent or received and no pending requests) | ||
| */ | ||
| lazy?: { | ||
| /** | ||
| * Enable lazy mode | ||
| * @default false | ||
| */ | ||
| enabled: boolean; | ||
| /** | ||
| * Close the WebSocket after this many milliseconds | ||
| * @default 0 | ||
| */ | ||
| closeMs: number; | ||
| }; | ||
| /** | ||
| * Send ping messages to the server and kill the connection if no pong message is returned | ||
| */ | ||
| keepAlive?: { | ||
| /** | ||
| * @default false | ||
| */ | ||
| enabled: boolean; | ||
| /** | ||
| * Send a ping message every this many milliseconds | ||
| * @default 5_000 | ||
| */ | ||
| intervalMs?: number; | ||
| /** | ||
| * Close the WebSocket after this many milliseconds if the server does not respond | ||
| * @default 1_000 | ||
| */ | ||
| pongTimeoutMs?: number; | ||
| }; | ||
| /** | ||
| * Custom encoder for wire encoding (e.g. custom binary formats) | ||
| * @default jsonEncoder | ||
| */ | ||
| experimental_encoder?: Encoder; | ||
| } | ||
| /** | ||
| * Default options for lazy WebSocket connections. | ||
| * Determines whether the connection should be established lazily and defines the delay before closure. | ||
| */ | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/wsClient.d.ts | ||
| /** | ||
| * A WebSocket client for managing TRPC operations, supporting lazy initialization, | ||
| * reconnection, keep-alive, and request management. | ||
| */ | ||
| declare class WsClient { | ||
| /** | ||
| * Observable tracking the current connection state, including errors. | ||
| */ | ||
| readonly connectionState: BehaviorSubject<TRPCConnectionState<TRPCClientError<AnyTRPCRouter>>>; | ||
| private allowReconnect; | ||
| private requestManager; | ||
| private readonly activeConnection; | ||
| private readonly reconnectRetryDelay; | ||
| private inactivityTimeout; | ||
| private readonly callbacks; | ||
| private readonly lazyMode; | ||
| private readonly encoder; | ||
| constructor(opts: WebSocketClientOptions); | ||
| /** | ||
| * Opens the WebSocket connection. Handles reconnection attempts and updates | ||
| * the connection state accordingly. | ||
| */ | ||
| private open; | ||
| /** | ||
| * Closes the WebSocket connection and stops managing requests. | ||
| * Ensures all outgoing and pending requests are properly finalized. | ||
| */ | ||
| close(): Promise<void>; | ||
| /** | ||
| * Method to request the server. | ||
| * Handles data transformation, batching of requests, and subscription lifecycle. | ||
| * | ||
| * @param op - The operation details including id, type, path, input and signal | ||
| * @param transformer - Data transformer for serializing requests and deserializing responses | ||
| * @param lastEventId - Optional ID of the last received event for subscriptions | ||
| * | ||
| * @returns An observable that emits operation results and handles cleanup | ||
| */ | ||
| request({ | ||
| op: { | ||
| id, | ||
| type, | ||
| path, | ||
| input, | ||
| signal | ||
| }, | ||
| transformer, | ||
| lastEventId | ||
| }: { | ||
| op: Pick<Operation, 'id' | 'type' | 'path' | 'input' | 'signal'>; | ||
| transformer: CombinedDataTransformer; | ||
| lastEventId?: string; | ||
| }): _trpc_server_observable0.Observable<OperationResultEnvelope<unknown, TRPCClientError<AnyTRPCRouter>>, TRPCClientError<AnyTRPCRouter>>; | ||
| get connection(): { | ||
| readonly id: number; | ||
| readonly state: "open"; | ||
| readonly ws: WebSocket; | ||
| } | { | ||
| readonly id: number; | ||
| readonly state: "closed"; | ||
| readonly ws: WebSocket; | ||
| } | { | ||
| readonly id: number; | ||
| readonly state: "connecting"; | ||
| readonly ws: WebSocket; | ||
| } | null; | ||
| /** | ||
| * Manages the reconnection process for the WebSocket using retry logic. | ||
| * Ensures that only one reconnection attempt is active at a time by tracking the current | ||
| * reconnection state in the `reconnecting` promise. | ||
| */ | ||
| private reconnecting; | ||
| private reconnect; | ||
| private setupWebSocketListeners; | ||
| private handleResponseMessage; | ||
| private handleIncomingRequest; | ||
| /** | ||
| * Sends a message or batch of messages directly to the server. | ||
| */ | ||
| private send; | ||
| /** | ||
| * Groups requests for batch sending. | ||
| * | ||
| * @returns A function to abort the batched request. | ||
| */ | ||
| private batchSend; | ||
| } | ||
| //# sourceMappingURL=wsClient.d.ts.map | ||
| //#endregion | ||
| //#region src/links/wsLink/createWsClient.d.ts | ||
| declare function createWSClient(opts: WebSocketClientOptions): WsClient; | ||
| type TRPCWebSocketClient = ReturnType<typeof createWSClient>; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsLink.d.ts | ||
| type WebSocketLinkOptions<TRouter extends AnyRouter> = { | ||
| client: TRPCWebSocketClient; | ||
| } & TransformerOptions<inferClientTypes<TRouter>>; | ||
| declare function wsLink<TRouter extends AnyRouter>(opts: WebSocketLinkOptions<TRouter>): TRPCLink<TRouter>; | ||
| //#endregion | ||
| export { Encoder, TRPCWebSocketClient, UrlOptionsWithConnectionParams, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, jsonEncoder, wsLink }; | ||
| //# sourceMappingURL=wsLink.d-CDi0tYE2.d.mts.map |
| {"version":3,"file":"wsLink.d-CDi0tYE2.d.mts","names":[],"sources":["../src/links/wsLink/wsClient/encoder.ts","../src/links/internals/urlWithConnectionParams.ts","../src/links/wsLink/wsClient/options.ts","../src/links/wsLink/wsClient/wsClient.ts","../src/links/wsLink/createWsClient.ts","../src/links/wsLink/wsLink.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAIa,aAAa;;;;;;;;KCcd,qBAAqB,WAAW,IAAI,QAAQ;UAEvC,8BAAA;EDhBJ;;;OCoBN;EANK;;;;;EAA6C,gBAAT,CAAA,EAa3B,eAb2B,CAaX,eAbW,CAAA,kBAAA,CAAA,CAAA;AAAO;AAEvD;;;UCjBiB,sBAAA,SAA+B;;;;qBAI3B;;;;;EFHR,YAAA,CAAA,EAWZ,CAAA,YAXyB,EAAA,MAWzB,EAAA,GAAA,MAAA;;;;ECGW,MAAA,CAAA,EAAA,GAAA,GAAA,IAAe;EAAA;;;EAAkB,OAAW,CAAA,EAAA,CAAA,GAAA,CAAA,ECErC,KDFqC,EAAA,GAAA,IAAA;EAAC;AAAF;AAEvD;EAA+C,OAAA,CAAA,EAAA,CAAA,KAIzB,CAJyB,EAAA;IAIxC,IAAA,CAAA,EAAA,MAAA;EAAe,CAAA,EAOe,GAAA,IAAA;EAAe;AAAhB;;;;AC5BpC;;;IAiBmB,OAAA,EAAA,OAAA;IA2CM;;AA5DqD;;;;EC4BjE;;;EAKwC,SAA7B,CAAA,EAAA;IAApB;;;IA0GgB,OAAA,EAAA,OAAA;IA0CV;;;;IAAuB,UAAA,CAAA,EAAA,MAAA;IAC7B;;;;IAIa,aAAA,CAAA,EAAA,MAAA;EAAuB,CAAA;EAErC;;;;EAAA,oBAAA,CAAA,EDhIsB,OCgItB;;;;;;;;;;;;AH3LU,cG2BA,QAAA,CH3Ba;;;;ECcd,SAAA,eAAe,EEiBQ,eFjBR,CEkBvB,mBFlBuB,CEkBH,eFlBG,CEkBa,aFlBb,CAAA,CAAA,CAAA;EAAA,QAAA,cAAA;EAAA,QAAM,cAAA;EAAC,iBAAU,gBAAA;EAAC,iBAAW,mBAAA;EAAC,QAAT,iBAAA;EAAO,iBAAA,SAAA;EAEtC,iBAAA,QAAA;EAA8B,iBAAA,OAAA;EAAA,WAIxC,CAAA,IAAA,EE2Ba,sBF3Bb;EAAe;;AAOc;;;;AC5BpC;;;EAI8B,KAaX,CAAA,CAAA,EC0HC,OD1HD,CAAA,IAAA,CAAA;EAAK;;AAjBsD;;;;AC4B9E;;;;EAKuC,OAAnC,CAAA;IAAA,EAAA,EAAA;MAAA,EAAA;MAAA,IAAA;MAAA,IAAA;MAAA,KAAA;MAAA;IAAA,CAAA;IAAA,WAAA;IAAA;EA0GgB,CA1GhB,EAAA;IAD+B,EAAA,EAyJ3B,IAzJ2B,CAyJtB,SAzJsB,EAAA,IAAA,GAAA,MAAA,GAAA,MAAA,GAAA,OAAA,GAAA,QAAA,CAAA;IAgBf,WAAA,EA0IH,uBA1IG;IA2FA,WAAA,CAAA,EAAA,MAAA;EAAA,CAAA,CAAA,EAiDjB,wBAAA,CAAA,UAPO,CAOP,uBAPO,CAAA,OAAA,EAOP,eAPO,CAOP,aAPO,CAAA,CAAA,EAOP,eAPO,CAOP,aAPO,CAAA,CAAA;EAAE,IAAE,UAAA,CAAA,CAAA,EAAA;IAAM,SAAA,EAAA,EAAA,MAAA;IAAM,SAAA,KAAA,EAAA,MAAA;IAAO,SAAA,EAAA,EAO9B,SAP8B;EAAM,CAAA,GACnC;IACA,SAAA,EAAA,EAAA,MAAA;IAES,SAAA,KAAA,EAAA,QAAA;IAAL,SAAA,EAAA,WAAA;EAAI,CAAA,GACK;IAEd,SAAA,EAAA,EAAA,MAAA;IAAA,SAAA,KAAA,EAAA,YAAA;IAAA,SAAA,EAAA,WAAA;EAAA,CAAA,GAAA,IAAA;EAAA;;;;;;;;;EC1La,QAAA,qBAAc;EAAA;;;EAA6B,QAAA,IAAA;EAI/C;;;;AAAgC;;;;;;iBAJ5B,cAAA,OAAqB,yBAAsB;KAI/C,mBAAA,GAAsB,kBAAkB;;;KCMxC,qCAAqC;UACvC;IACN,mBAAmB,iBAAiB;iBAExB,uBAAuB,iBAC/B,qBAAqB,WAC1B,SAAS"} |
| import { TRPCConnectionState } from "./subscriptions.d-Ciljg_dH.cjs"; | ||
| import { Operation, OperationResultEnvelope, TRPCClientError, TRPCLink } from "./types.d-B2PuQAdV.cjs"; | ||
| import { TransformerOptions } from "./unstable-internals.d-kWsZTlQq.cjs"; | ||
| import { AnyRouter, CombinedDataTransformer, inferClientTypes } from "@trpc/server/unstable-core-do-not-import"; | ||
| import * as _trpc_server_observable0 from "@trpc/server/observable"; | ||
| import { BehaviorSubject } from "@trpc/server/observable"; | ||
| import { AnyTRPCRouter } from "@trpc/server"; | ||
| import { Encoder } from "@trpc/server/adapters/ws"; | ||
| import { TRPCRequestInfo } from "@trpc/server/http"; | ||
| //#region src/links/wsLink/wsClient/encoder.d.ts | ||
| declare const jsonEncoder: Encoder; | ||
| //# sourceMappingURL=encoder.d.ts.map | ||
| //#endregion | ||
| //#region src/links/internals/urlWithConnectionParams.d.ts | ||
| /** | ||
| * A value that can be wrapped in callback | ||
| */ | ||
| type CallbackOrValue<T> = T | (() => T | Promise<T>); | ||
| interface UrlOptionsWithConnectionParams { | ||
| /** | ||
| * The URL to connect to (can be a function that returns a URL) | ||
| */ | ||
| url: CallbackOrValue<string>; | ||
| /** | ||
| * Connection params that are available in `createContext()` | ||
| * - For `wsLink`/`wsClient`, these are sent as the first message | ||
| * - For `httpSubscriptionLink`, these are serialized as part of the URL under the `connectionParams` query | ||
| */ | ||
| connectionParams?: CallbackOrValue<TRPCRequestInfo['connectionParams']>; | ||
| } | ||
| //# sourceMappingURL=urlWithConnectionParams.d.ts.map | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/options.d.ts | ||
| interface WebSocketClientOptions extends UrlOptionsWithConnectionParams { | ||
| /** | ||
| * Ponyfill which WebSocket implementation to use | ||
| */ | ||
| WebSocket?: typeof WebSocket; | ||
| /** | ||
| * The number of milliseconds before a reconnect is attempted. | ||
| * @default {@link exponentialBackoff} | ||
| */ | ||
| retryDelayMs?: (attemptIndex: number) => number; | ||
| /** | ||
| * Triggered when a WebSocket connection is established | ||
| */ | ||
| onOpen?: () => void; | ||
| /** | ||
| * Triggered when a WebSocket connection encounters an error | ||
| */ | ||
| onError?: (evt?: Event) => void; | ||
| /** | ||
| * Triggered when a WebSocket connection is closed | ||
| */ | ||
| onClose?: (cause?: { | ||
| code?: number; | ||
| }) => void; | ||
| /** | ||
| * Lazy mode will close the WebSocket automatically after a period of inactivity (no messages sent or received and no pending requests) | ||
| */ | ||
| lazy?: { | ||
| /** | ||
| * Enable lazy mode | ||
| * @default false | ||
| */ | ||
| enabled: boolean; | ||
| /** | ||
| * Close the WebSocket after this many milliseconds | ||
| * @default 0 | ||
| */ | ||
| closeMs: number; | ||
| }; | ||
| /** | ||
| * Send ping messages to the server and kill the connection if no pong message is returned | ||
| */ | ||
| keepAlive?: { | ||
| /** | ||
| * @default false | ||
| */ | ||
| enabled: boolean; | ||
| /** | ||
| * Send a ping message every this many milliseconds | ||
| * @default 5_000 | ||
| */ | ||
| intervalMs?: number; | ||
| /** | ||
| * Close the WebSocket after this many milliseconds if the server does not respond | ||
| * @default 1_000 | ||
| */ | ||
| pongTimeoutMs?: number; | ||
| }; | ||
| /** | ||
| * Custom encoder for wire encoding (e.g. custom binary formats) | ||
| * @default jsonEncoder | ||
| */ | ||
| experimental_encoder?: Encoder; | ||
| } | ||
| /** | ||
| * Default options for lazy WebSocket connections. | ||
| * Determines whether the connection should be established lazily and defines the delay before closure. | ||
| */ | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/wsClient.d.ts | ||
| /** | ||
| * A WebSocket client for managing TRPC operations, supporting lazy initialization, | ||
| * reconnection, keep-alive, and request management. | ||
| */ | ||
| declare class WsClient { | ||
| /** | ||
| * Observable tracking the current connection state, including errors. | ||
| */ | ||
| readonly connectionState: BehaviorSubject<TRPCConnectionState<TRPCClientError<AnyTRPCRouter>>>; | ||
| private allowReconnect; | ||
| private requestManager; | ||
| private readonly activeConnection; | ||
| private readonly reconnectRetryDelay; | ||
| private inactivityTimeout; | ||
| private readonly callbacks; | ||
| private readonly lazyMode; | ||
| private readonly encoder; | ||
| constructor(opts: WebSocketClientOptions); | ||
| /** | ||
| * Opens the WebSocket connection. Handles reconnection attempts and updates | ||
| * the connection state accordingly. | ||
| */ | ||
| private open; | ||
| /** | ||
| * Closes the WebSocket connection and stops managing requests. | ||
| * Ensures all outgoing and pending requests are properly finalized. | ||
| */ | ||
| close(): Promise<void>; | ||
| /** | ||
| * Method to request the server. | ||
| * Handles data transformation, batching of requests, and subscription lifecycle. | ||
| * | ||
| * @param op - The operation details including id, type, path, input and signal | ||
| * @param transformer - Data transformer for serializing requests and deserializing responses | ||
| * @param lastEventId - Optional ID of the last received event for subscriptions | ||
| * | ||
| * @returns An observable that emits operation results and handles cleanup | ||
| */ | ||
| request({ | ||
| op: { | ||
| id, | ||
| type, | ||
| path, | ||
| input, | ||
| signal | ||
| }, | ||
| transformer, | ||
| lastEventId | ||
| }: { | ||
| op: Pick<Operation, 'id' | 'type' | 'path' | 'input' | 'signal'>; | ||
| transformer: CombinedDataTransformer; | ||
| lastEventId?: string; | ||
| }): _trpc_server_observable0.Observable<OperationResultEnvelope<unknown, TRPCClientError<AnyTRPCRouter>>, TRPCClientError<AnyTRPCRouter>>; | ||
| get connection(): { | ||
| readonly id: number; | ||
| readonly state: "open"; | ||
| readonly ws: WebSocket; | ||
| } | { | ||
| readonly id: number; | ||
| readonly state: "closed"; | ||
| readonly ws: WebSocket; | ||
| } | { | ||
| readonly id: number; | ||
| readonly state: "connecting"; | ||
| readonly ws: WebSocket; | ||
| } | null; | ||
| /** | ||
| * Manages the reconnection process for the WebSocket using retry logic. | ||
| * Ensures that only one reconnection attempt is active at a time by tracking the current | ||
| * reconnection state in the `reconnecting` promise. | ||
| */ | ||
| private reconnecting; | ||
| private reconnect; | ||
| private setupWebSocketListeners; | ||
| private handleResponseMessage; | ||
| private handleIncomingRequest; | ||
| /** | ||
| * Sends a message or batch of messages directly to the server. | ||
| */ | ||
| private send; | ||
| /** | ||
| * Groups requests for batch sending. | ||
| * | ||
| * @returns A function to abort the batched request. | ||
| */ | ||
| private batchSend; | ||
| } | ||
| //# sourceMappingURL=wsClient.d.ts.map | ||
| //#endregion | ||
| //#region src/links/wsLink/createWsClient.d.ts | ||
| declare function createWSClient(opts: WebSocketClientOptions): WsClient; | ||
| type TRPCWebSocketClient = ReturnType<typeof createWSClient>; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsLink.d.ts | ||
| type WebSocketLinkOptions<TRouter extends AnyRouter> = { | ||
| client: TRPCWebSocketClient; | ||
| } & TransformerOptions<inferClientTypes<TRouter>>; | ||
| declare function wsLink<TRouter extends AnyRouter>(opts: WebSocketLinkOptions<TRouter>): TRPCLink<TRouter>; | ||
| //#endregion | ||
| export { Encoder, TRPCWebSocketClient, UrlOptionsWithConnectionParams, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, jsonEncoder, wsLink }; | ||
| //# sourceMappingURL=wsLink.d-DSgoRR08.d.cts.map |
| {"version":3,"file":"wsLink.d-DSgoRR08.d.cts","names":[],"sources":["../src/links/wsLink/wsClient/encoder.ts","../src/links/internals/urlWithConnectionParams.ts","../src/links/wsLink/wsClient/options.ts","../src/links/wsLink/wsClient/wsClient.ts","../src/links/wsLink/createWsClient.ts","../src/links/wsLink/wsLink.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;cAIa,aAAa;;;;;;;;KCcd,qBAAqB,WAAW,IAAI,QAAQ;UAEvC,8BAAA;EDhBJ;;;OCoBN;EANK;;;;;EAA6C,gBAAT,CAAA,EAa3B,eAb2B,CAaX,eAbW,CAAA,kBAAA,CAAA,CAAA;AAAO;AAEvD;;;UCjBiB,sBAAA,SAA+B;;;;qBAI3B;;;;;EFHR,YAAA,CAAA,EAWZ,CAAA,YAXyB,EAAA,MAWzB,EAAA,GAAA,MAAA;;;;ECGW,MAAA,CAAA,EAAA,GAAA,GAAA,IAAe;EAAA;;;EAAkB,OAAW,CAAA,EAAA,CAAA,GAAA,CAAA,ECErC,KDFqC,EAAA,GAAA,IAAA;EAAC;AAAF;AAEvD;EAA+C,OAAA,CAAA,EAAA,CAAA,KAIzB,CAJyB,EAAA;IAIxC,IAAA,CAAA,EAAA,MAAA;EAAe,CAAA,EAOe,GAAA,IAAA;EAAe;AAAhB;;;;AC5BpC;;;IAiBmB,OAAA,EAAA,OAAA;IA2CM;;AA5DqD;;;;EC4BjE;;;EAKwC,SAA7B,CAAA,EAAA;IAApB;;;IA0GgB,OAAA,EAAA,OAAA;IA0CV;;;;IAAuB,UAAA,CAAA,EAAA,MAAA;IAC7B;;;;IAIa,aAAA,CAAA,EAAA,MAAA;EAAuB,CAAA;EAErC;;;;EAAA,oBAAA,CAAA,EDhIsB,OCgItB;;;;;;;;;;;;AH3LU,cG2BA,QAAA,CH3Ba;;;;ECcd,SAAA,eAAe,EEiBQ,eFjBR,CEkBvB,mBFlBuB,CEkBH,eFlBG,CEkBa,aFlBb,CAAA,CAAA,CAAA;EAAA,QAAA,cAAA;EAAA,QAAM,cAAA;EAAC,iBAAU,gBAAA;EAAC,iBAAW,mBAAA;EAAC,QAAT,iBAAA;EAAO,iBAAA,SAAA;EAEtC,iBAAA,QAAA;EAA8B,iBAAA,OAAA;EAAA,WAIxC,CAAA,IAAA,EE2Ba,sBF3Bb;EAAe;;AAOc;;;;AC5BpC;;;EAI8B,KAaX,CAAA,CAAA,EC0HC,OD1HD,CAAA,IAAA,CAAA;EAAK;;AAjBsD;;;;AC4B9E;;;;EAKuC,OAAnC,CAAA;IAAA,EAAA,EAAA;MAAA,EAAA;MAAA,IAAA;MAAA,IAAA;MAAA,KAAA;MAAA;IAAA,CAAA;IAAA,WAAA;IAAA;EA0GgB,CA1GhB,EAAA;IAD+B,EAAA,EAyJ3B,IAzJ2B,CAyJtB,SAzJsB,EAAA,IAAA,GAAA,MAAA,GAAA,MAAA,GAAA,OAAA,GAAA,QAAA,CAAA;IAgBf,WAAA,EA0IH,uBA1IG;IA2FA,WAAA,CAAA,EAAA,MAAA;EAAA,CAAA,CAAA,EAiDjB,wBAAA,CAAA,UAPO,CAOP,uBAPO,CAAA,OAAA,EAOP,eAPO,CAOP,aAPO,CAAA,CAAA,EAOP,eAPO,CAOP,aAPO,CAAA,CAAA;EAAE,IAAE,UAAA,CAAA,CAAA,EAAA;IAAM,SAAA,EAAA,EAAA,MAAA;IAAM,SAAA,KAAA,EAAA,MAAA;IAAO,SAAA,EAAA,EAO9B,SAP8B;EAAM,CAAA,GACnC;IACA,SAAA,EAAA,EAAA,MAAA;IAES,SAAA,KAAA,EAAA,QAAA;IAAL,SAAA,EAAA,WAAA;EAAI,CAAA,GACK;IAEd,SAAA,EAAA,EAAA,MAAA;IAAA,SAAA,KAAA,EAAA,YAAA;IAAA,SAAA,EAAA,WAAA;EAAA,CAAA,GAAA,IAAA;EAAA;;;;;;;;;EC1La,QAAA,qBAAc;EAAA;;;EAA6B,QAAA,IAAA;EAI/C;;;;AAAgC;;;;;;iBAJ5B,cAAA,OAAqB,yBAAsB;KAI/C,mBAAA,GAAsB,kBAAkB;;;KCMxC,qCAAqC;UACvC;IACN,mBAAmB,iBAAiB;iBAExB,uBAAuB,iBAC/B,qBAAqB,WAC1B,SAAS"} |
| import type { Encoder } from '@trpc/server/adapters/ws'; | ||
| export type { Encoder }; | ||
| export const jsonEncoder: Encoder = { | ||
| encode: (data) => JSON.stringify(data), | ||
| decode: (data) => { | ||
| if (typeof data !== 'string') { | ||
| throw new Error( | ||
| 'jsonEncoder received binary data. JSON uses text frames. ' + | ||
| 'Use a binary encoder for binary data.', | ||
| ); | ||
| } | ||
| return JSON.parse(data); | ||
| }, | ||
| }; |
+2
-1
@@ -10,3 +10,3 @@ const require_chunk = require('./chunk-DWy1uDak.cjs'); | ||
| const require_loggerLink = require('./loggerLink-CuYvRzyH.cjs'); | ||
| const require_wsLink = require('./wsLink-C_xbifLm.cjs'); | ||
| const require_wsLink = require('./wsLink-CFFDLi4T.cjs'); | ||
| const __trpc_server_observable = require_chunk.__toESM(require("@trpc/server/observable")); | ||
@@ -895,2 +895,3 @@ const __trpc_server_unstable_core_do_not_import = require_chunk.__toESM(require("@trpc/server/unstable-core-do-not-import")); | ||
| exports.isTRPCClientError = require_TRPCClientError.isTRPCClientError; | ||
| exports.jsonEncoder = require_wsLink.jsonEncoder; | ||
| exports.loggerLink = require_loggerLink.loggerLink; | ||
@@ -897,0 +898,0 @@ exports.retryLink = retryLink; |
+2
-2
@@ -9,3 +9,3 @@ import { TRPCConnectionState } from "./subscriptions.d-Ciljg_dH.cjs"; | ||
| import { splitLink } from "./splitLink.d-3ZZnmg7h.cjs"; | ||
| import { TRPCWebSocketClient, UrlOptionsWithConnectionParams, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, wsLink } from "./wsLink.d-Dtb4zdet.cjs"; | ||
| import { Encoder, TRPCWebSocketClient, UrlOptionsWithConnectionParams, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, jsonEncoder, wsLink } from "./wsLink.d-DSgoRR08.cjs"; | ||
| import { AnyClientTypes, AnyProcedure, AnyRouter, ErrorHandlerOptions, EventSourceLike, InferrableClientTypes, ProcedureType, RouterRecord, TypeError, inferAsyncIterableYield, inferClientTypes, inferProcedureInput, inferRouterContext, inferTransformedProcedureOutput } from "@trpc/server/unstable-core-do-not-import"; | ||
@@ -201,3 +201,3 @@ import { Unsubscribable } from "@trpc/server/observable"; | ||
| //#endregion | ||
| export { CreateTRPCClient, CreateTRPCClientOptions, HTTPBatchLinkOptions, HTTPHeaders, HTTPLinkOptions, LocalLinkOptions, LoggerLinkOptions, Operation, OperationContext, OperationLink, OperationResultEnvelope, OperationResultObservable, OperationResultObserver, Resolver, TRPCClient, TRPCClientError, TRPCClientErrorBase, TRPCClientErrorLike, TRPCClientRuntime, TRPCFetch, TRPCLink, TRPCProcedureOptions, TRPCRequestOptions, TRPCUntypedClient, TRPCWebSocketClient, WebSocketClientOptions, WebSocketLinkOptions, clientCallTypeToProcedureType, createTRPCClient, createTRPCClientProxy, createTRPCClient as createTRPCProxyClient, createTRPCUntypedClient, createWSClient, experimental_localLink, getFetch, getUntypedClient, httpBatchLink, httpBatchStreamLink, httpLink, httpSubscriptionLink, inferRouterClient, inferRouterClient as inferRouterProxyClient, isFormData, isNonJsonSerializable, isOctetType, isTRPCClientError, loggerLink, retryLink, splitLink, unstable_httpBatchStreamLink, unstable_httpSubscriptionLink, unstable_localLink, wsLink }; | ||
| export { CreateTRPCClient, CreateTRPCClientOptions, Encoder, HTTPBatchLinkOptions, HTTPHeaders, HTTPLinkOptions, LocalLinkOptions, LoggerLinkOptions, Operation, OperationContext, OperationLink, OperationResultEnvelope, OperationResultObservable, OperationResultObserver, Resolver, TRPCClient, TRPCClientError, TRPCClientErrorBase, TRPCClientErrorLike, TRPCClientRuntime, TRPCFetch, TRPCLink, TRPCProcedureOptions, TRPCRequestOptions, TRPCUntypedClient, TRPCWebSocketClient, WebSocketClientOptions, WebSocketLinkOptions, clientCallTypeToProcedureType, createTRPCClient, createTRPCClientProxy, createTRPCClient as createTRPCProxyClient, createTRPCUntypedClient, createWSClient, experimental_localLink, getFetch, getUntypedClient, httpBatchLink, httpBatchStreamLink, httpLink, httpSubscriptionLink, inferRouterClient, inferRouterClient as inferRouterProxyClient, isFormData, isNonJsonSerializable, isOctetType, isTRPCClientError, jsonEncoder, loggerLink, retryLink, splitLink, unstable_httpBatchStreamLink, unstable_httpSubscriptionLink, unstable_localLink, wsLink }; | ||
| //# sourceMappingURL=index.d.cts.map |
+2
-2
@@ -9,3 +9,3 @@ import { TRPCConnectionState } from "./subscriptions.d-Dlr1nWGD.mjs"; | ||
| import { splitLink } from "./splitLink.d-Df2gT0RV.mjs"; | ||
| import { TRPCWebSocketClient, UrlOptionsWithConnectionParams, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, wsLink } from "./wsLink.d-Bssh2HIQ.mjs"; | ||
| import { Encoder, TRPCWebSocketClient, UrlOptionsWithConnectionParams, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, jsonEncoder, wsLink } from "./wsLink.d-CDi0tYE2.mjs"; | ||
| import { Unsubscribable } from "@trpc/server/observable"; | ||
@@ -201,3 +201,3 @@ import { AnyClientTypes, AnyProcedure, AnyRouter, ErrorHandlerOptions, EventSourceLike, InferrableClientTypes, ProcedureType, RouterRecord, TypeError, inferAsyncIterableYield, inferClientTypes, inferProcedureInput, inferRouterContext, inferTransformedProcedureOutput } from "@trpc/server/unstable-core-do-not-import"; | ||
| //#endregion | ||
| export { CreateTRPCClient, CreateTRPCClientOptions, HTTPBatchLinkOptions, HTTPHeaders, HTTPLinkOptions, LocalLinkOptions, LoggerLinkOptions, Operation, OperationContext, OperationLink, OperationResultEnvelope, OperationResultObservable, OperationResultObserver, Resolver, TRPCClient, TRPCClientError, TRPCClientErrorBase, TRPCClientErrorLike, TRPCClientRuntime, TRPCFetch, TRPCLink, TRPCProcedureOptions, TRPCRequestOptions, TRPCUntypedClient, TRPCWebSocketClient, WebSocketClientOptions, WebSocketLinkOptions, clientCallTypeToProcedureType, createTRPCClient, createTRPCClientProxy, createTRPCClient as createTRPCProxyClient, createTRPCUntypedClient, createWSClient, experimental_localLink, getFetch, getUntypedClient, httpBatchLink, httpBatchStreamLink, httpLink, httpSubscriptionLink, inferRouterClient, inferRouterClient as inferRouterProxyClient, isFormData, isNonJsonSerializable, isOctetType, isTRPCClientError, loggerLink, retryLink, splitLink, unstable_httpBatchStreamLink, unstable_httpSubscriptionLink, unstable_localLink, wsLink }; | ||
| export { CreateTRPCClient, CreateTRPCClientOptions, Encoder, HTTPBatchLinkOptions, HTTPHeaders, HTTPLinkOptions, LocalLinkOptions, LoggerLinkOptions, Operation, OperationContext, OperationLink, OperationResultEnvelope, OperationResultObservable, OperationResultObserver, Resolver, TRPCClient, TRPCClientError, TRPCClientErrorBase, TRPCClientErrorLike, TRPCClientRuntime, TRPCFetch, TRPCLink, TRPCProcedureOptions, TRPCRequestOptions, TRPCUntypedClient, TRPCWebSocketClient, WebSocketClientOptions, WebSocketLinkOptions, clientCallTypeToProcedureType, createTRPCClient, createTRPCClientProxy, createTRPCClient as createTRPCProxyClient, createTRPCUntypedClient, createWSClient, experimental_localLink, getFetch, getUntypedClient, httpBatchLink, httpBatchStreamLink, httpLink, httpSubscriptionLink, inferRouterClient, inferRouterClient as inferRouterProxyClient, isFormData, isNonJsonSerializable, isOctetType, isTRPCClientError, jsonEncoder, loggerLink, retryLink, splitLink, unstable_httpBatchStreamLink, unstable_httpSubscriptionLink, unstable_localLink, wsLink }; | ||
| //# sourceMappingURL=index.d.mts.map |
+2
-2
@@ -9,3 +9,3 @@ import { __commonJS, __toESM, require_defineProperty, require_objectSpread2 } from "./objectSpread2-BvkFp-_Y.mjs"; | ||
| import { loggerLink } from "./loggerLink-ineCN1PO.mjs"; | ||
| import { createWSClient, resultOf, wsLink } from "./wsLink-CatceK3c.mjs"; | ||
| import { createWSClient, jsonEncoder, resultOf, wsLink } from "./wsLink-DVm7B-YP.mjs"; | ||
| import { behaviorSubject, observable, observableToPromise, share } from "@trpc/server/observable"; | ||
@@ -875,3 +875,3 @@ import { callProcedure, createFlatProxy, createRecursiveProxy, isAbortError, isAsyncIterable, iteratorResource, jsonlStreamConsumer, makeResource, retryableRpcCodes, run, sseStreamConsumer } from "@trpc/server/unstable-core-do-not-import"; | ||
| //#endregion | ||
| export { TRPCClientError, TRPCUntypedClient, clientCallTypeToProcedureType, createTRPCClient, createTRPCClientProxy, createTRPCClient as createTRPCProxyClient, createTRPCUntypedClient, createWSClient, experimental_localLink, getFetch, getUntypedClient, httpBatchLink, httpBatchStreamLink, httpLink, httpSubscriptionLink, isFormData, isNonJsonSerializable, isOctetType, isTRPCClientError, loggerLink, retryLink, splitLink, unstable_httpBatchStreamLink, unstable_httpSubscriptionLink, unstable_localLink, wsLink }; | ||
| export { TRPCClientError, TRPCUntypedClient, clientCallTypeToProcedureType, createTRPCClient, createTRPCClientProxy, createTRPCClient as createTRPCProxyClient, createTRPCUntypedClient, createWSClient, experimental_localLink, getFetch, getUntypedClient, httpBatchLink, httpBatchStreamLink, httpLink, httpSubscriptionLink, isFormData, isNonJsonSerializable, isOctetType, isTRPCClientError, jsonEncoder, loggerLink, retryLink, splitLink, unstable_httpBatchStreamLink, unstable_httpSubscriptionLink, unstable_localLink, wsLink }; | ||
| //# sourceMappingURL=index.mjs.map |
| require('../../objectSpread2-Bsvh_OqM.cjs'); | ||
| require('../../TRPCClientError-Dey88Uiy.cjs'); | ||
| require('../../unstable-internals-M84gUQCV.cjs'); | ||
| const require_wsLink = require('../../wsLink-C_xbifLm.cjs'); | ||
| const require_wsLink = require('../../wsLink-CFFDLi4T.cjs'); | ||
| exports.createWSClient = require_wsLink.createWSClient; | ||
| exports.jsonEncoder = require_wsLink.jsonEncoder; | ||
| exports.wsLink = require_wsLink.wsLink; |
| import "../../subscriptions.d-Ciljg_dH.cjs"; | ||
| import "../../types.d-B2PuQAdV.cjs"; | ||
| import "../../unstable-internals.d-kWsZTlQq.cjs"; | ||
| import { TRPCWebSocketClient, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, wsLink } from "../../wsLink.d-Dtb4zdet.cjs"; | ||
| export { TRPCWebSocketClient, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, wsLink }; | ||
| import { Encoder, TRPCWebSocketClient, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, jsonEncoder, wsLink } from "../../wsLink.d-DSgoRR08.cjs"; | ||
| export { Encoder, TRPCWebSocketClient, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, jsonEncoder, wsLink }; |
| import "../../subscriptions.d-Dlr1nWGD.mjs"; | ||
| import "../../types.d-CAt1zKAY.mjs"; | ||
| import "../../unstable-internals.d-BOmV7EK1.mjs"; | ||
| import { TRPCWebSocketClient, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, wsLink } from "../../wsLink.d-Bssh2HIQ.mjs"; | ||
| export { TRPCWebSocketClient, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, wsLink }; | ||
| import { Encoder, TRPCWebSocketClient, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, jsonEncoder, wsLink } from "../../wsLink.d-CDi0tYE2.mjs"; | ||
| export { Encoder, TRPCWebSocketClient, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, jsonEncoder, wsLink }; |
| import "../../objectSpread2-BvkFp-_Y.mjs"; | ||
| import "../../TRPCClientError-CjKyS10w.mjs"; | ||
| import "../../unstable-internals-Bg7n9BBj.mjs"; | ||
| import { createWSClient, wsLink } from "../../wsLink-CatceK3c.mjs"; | ||
| import { createWSClient, jsonEncoder, wsLink } from "../../wsLink-DVm7B-YP.mjs"; | ||
| export { createWSClient, wsLink }; | ||
| export { createWSClient, jsonEncoder, wsLink }; |
+4
-4
| { | ||
| "name": "@trpc/client", | ||
| "type": "module", | ||
| "version": "11.8.2-canary.3+f48ed5c18", | ||
| "version": "11.8.2-canary.19+7b6e62472", | ||
| "description": "The tRPC client library", | ||
@@ -115,7 +115,7 @@ "author": "KATT", | ||
| "peerDependencies": { | ||
| "@trpc/server": "11.8.2-canary.3+f48ed5c18", | ||
| "@trpc/server": "11.8.2-canary.19+7b6e62472", | ||
| "typescript": ">=5.7.2" | ||
| }, | ||
| "devDependencies": { | ||
| "@trpc/server": "11.8.2-canary.3+f48ed5c18", | ||
| "@trpc/server": "11.8.2-canary.19+7b6e62472", | ||
| "@types/isomorphic-fetch": "^0.0.39", | ||
@@ -139,3 +139,3 @@ "@types/node": "^22.13.5", | ||
| ], | ||
| "gitHead": "f48ed5c18121bca0808f59d8f6c6d6d141c5e1f0" | ||
| "gitHead": "7b6e62472221ff21dd4c8509a4aca71b34eec78b" | ||
| } |
@@ -0,1 +1,3 @@ | ||
| import type { Encoder } from './wsClient/encoder'; | ||
| import { jsonEncoder } from './wsClient/encoder'; | ||
| import type { WebSocketClientOptions } from './wsClient/options'; | ||
@@ -10,2 +12,2 @@ import { WsClient } from './wsClient/wsClient'; | ||
| export { WebSocketClientOptions }; | ||
| export { jsonEncoder, type Encoder, type WebSocketClientOptions }; |
| import type { UrlOptionsWithConnectionParams } from '../../internals/urlWithConnectionParams'; | ||
| import type { Encoder } from './encoder'; | ||
@@ -59,2 +60,7 @@ export interface WebSocketClientOptions extends UrlOptionsWithConnectionParams { | ||
| }; | ||
| /** | ||
| * Custom encoder for wire encoding (e.g. custom binary formats) | ||
| * @default jsonEncoder | ||
| */ | ||
| experimental_encoder?: Encoder; | ||
| } | ||
@@ -61,0 +67,0 @@ |
@@ -19,2 +19,4 @@ import type { AnyTRPCRouter } from '@trpc/server'; | ||
| import type { Operation, OperationResultEnvelope } from '../../types'; | ||
| import type { Encoder } from './encoder'; | ||
| import { jsonEncoder } from './encoder'; | ||
| import type { WebSocketClientOptions } from './options'; | ||
@@ -49,4 +51,6 @@ import { exponentialBackoff, keepAliveDefaults, lazyDefaults } from './options'; | ||
| private readonly lazyMode: boolean; | ||
| private readonly encoder: Encoder; | ||
| constructor(opts: WebSocketClientOptions) { | ||
| this.encoder = opts.experimental_encoder ?? jsonEncoder; | ||
| // Initialize callbacks, connection parameters, and options. | ||
@@ -320,5 +324,8 @@ this.callbacks = { | ||
| if (typeof data !== 'string' || ['PING', 'PONG'].includes(data)) return; | ||
| // Handle PING/PONG as text regardless of encoder | ||
| if (['PING', 'PONG'].includes(data)) return; | ||
| const incomingMessage = JSON.parse(data) as TRPCClientIncomingMessage; | ||
| const incomingMessage = this.encoder.decode( | ||
| data, | ||
| ) as TRPCClientIncomingMessage; | ||
| if ('method' in incomingMessage) { | ||
@@ -407,3 +414,3 @@ this.handleIncomingRequest(incomingMessage); | ||
| this.activeConnection.ws.send( | ||
| JSON.stringify(messages.length === 1 ? messages[0] : messages), | ||
| this.encoder.encode(messages.length === 1 ? messages[0] : messages), | ||
| ); | ||
@@ -410,0 +417,0 @@ } |
@@ -170,2 +170,5 @@ import { behaviorSubject } from '@trpc/server/observable'; | ||
| // Set binaryType to handle both text and binary messages consistently | ||
| ws.binaryType = 'arraybuffer'; | ||
| // Setup ping listener | ||
@@ -172,0 +175,0 @@ ws.addEventListener('message', function ({ data }) { |
@@ -10,6 +10,7 @@ import { observable } from '@trpc/server/observable'; | ||
| import type { | ||
| Encoder, | ||
| TRPCWebSocketClient, | ||
| WebSocketClientOptions, | ||
| } from './createWsClient'; | ||
| import { createWSClient } from './createWsClient'; | ||
| import { createWSClient, jsonEncoder } from './createWsClient'; | ||
@@ -56,2 +57,8 @@ export type WebSocketLinkOptions<TRouter extends AnyRouter> = { | ||
| export { TRPCWebSocketClient, WebSocketClientOptions, createWSClient }; | ||
| export { | ||
| createWSClient, | ||
| jsonEncoder, | ||
| type Encoder, | ||
| type TRPCWebSocketClient, | ||
| type WebSocketClientOptions, | ||
| }; |
| const require_chunk = require('./chunk-DWy1uDak.cjs'); | ||
| const require_objectSpread2$1 = require('./objectSpread2-Bsvh_OqM.cjs'); | ||
| const require_TRPCClientError = require('./TRPCClientError-Dey88Uiy.cjs'); | ||
| const require_unstable_internals = require('./unstable-internals-M84gUQCV.cjs'); | ||
| const __trpc_server_observable = require_chunk.__toESM(require("@trpc/server/observable")); | ||
| const __trpc_server_unstable_core_do_not_import = require_chunk.__toESM(require("@trpc/server/unstable-core-do-not-import")); | ||
| //#region src/links/wsLink/wsClient/options.ts | ||
| const lazyDefaults = { | ||
| enabled: false, | ||
| closeMs: 0 | ||
| }; | ||
| const keepAliveDefaults = { | ||
| enabled: false, | ||
| pongTimeoutMs: 1e3, | ||
| intervalMs: 5e3 | ||
| }; | ||
| /** | ||
| * Calculates a delay for exponential backoff based on the retry attempt index. | ||
| * The delay starts at 0 for the first attempt and doubles for each subsequent attempt, | ||
| * capped at 30 seconds. | ||
| */ | ||
| const exponentialBackoff = (attemptIndex) => { | ||
| return attemptIndex === 0 ? 0 : Math.min(1e3 * 2 ** attemptIndex, 3e4); | ||
| }; | ||
| //#endregion | ||
| //#region src/links/internals/urlWithConnectionParams.ts | ||
| /** | ||
| * Get the result of a value or function that returns a value | ||
| * It also optionally accepts typesafe arguments for the function | ||
| */ | ||
| const resultOf = (value, ...args) => { | ||
| return typeof value === "function" ? value(...args) : value; | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/utils.ts | ||
| var import_defineProperty$3 = require_chunk.__toESM(require_objectSpread2$1.require_defineProperty(), 1); | ||
| var TRPCWebSocketClosedError = class TRPCWebSocketClosedError extends Error { | ||
| constructor(opts) { | ||
| super(opts.message, { cause: opts.cause }); | ||
| this.name = "TRPCWebSocketClosedError"; | ||
| Object.setPrototypeOf(this, TRPCWebSocketClosedError.prototype); | ||
| } | ||
| }; | ||
| /** | ||
| * Utility class for managing a timeout that can be started, stopped, and reset. | ||
| * Useful for scenarios where the timeout duration is reset dynamically based on events. | ||
| */ | ||
| var ResettableTimeout = class { | ||
| constructor(onTimeout, timeoutMs) { | ||
| this.onTimeout = onTimeout; | ||
| this.timeoutMs = timeoutMs; | ||
| (0, import_defineProperty$3.default)(this, "timeout", void 0); | ||
| } | ||
| /** | ||
| * Resets the current timeout, restarting it with the same duration. | ||
| * Does nothing if no timeout is active. | ||
| */ | ||
| reset() { | ||
| if (!this.timeout) return; | ||
| clearTimeout(this.timeout); | ||
| this.timeout = setTimeout(this.onTimeout, this.timeoutMs); | ||
| } | ||
| start() { | ||
| clearTimeout(this.timeout); | ||
| this.timeout = setTimeout(this.onTimeout, this.timeoutMs); | ||
| } | ||
| stop() { | ||
| clearTimeout(this.timeout); | ||
| this.timeout = void 0; | ||
| } | ||
| }; | ||
| function withResolvers() { | ||
| let resolve; | ||
| let reject; | ||
| const promise = new Promise((res, rej) => { | ||
| resolve = res; | ||
| reject = rej; | ||
| }); | ||
| return { | ||
| promise, | ||
| resolve, | ||
| reject | ||
| }; | ||
| } | ||
| /** | ||
| * Resolves a WebSocket URL and optionally appends connection parameters. | ||
| * | ||
| * If connectionParams are provided, appends 'connectionParams=1' query parameter. | ||
| */ | ||
| async function prepareUrl(urlOptions) { | ||
| const url = await resultOf(urlOptions.url); | ||
| if (!urlOptions.connectionParams) return url; | ||
| const prefix = url.includes("?") ? "&" : "?"; | ||
| const connectionParams = `${prefix}connectionParams=1`; | ||
| return url + connectionParams; | ||
| } | ||
| async function buildConnectionMessage(connectionParams) { | ||
| const message = { | ||
| method: "connectionParams", | ||
| data: await resultOf(connectionParams) | ||
| }; | ||
| return JSON.stringify(message); | ||
| } | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/requestManager.ts | ||
| var import_defineProperty$2 = require_chunk.__toESM(require_objectSpread2$1.require_defineProperty(), 1); | ||
| /** | ||
| * Manages WebSocket requests, tracking their lifecycle and providing utility methods | ||
| * for handling outgoing and pending requests. | ||
| * | ||
| * - **Outgoing requests**: Requests that are queued and waiting to be sent. | ||
| * - **Pending requests**: Requests that have been sent and are in flight awaiting a response. | ||
| * For subscriptions, multiple responses may be received until the subscription is closed. | ||
| */ | ||
| var RequestManager = class { | ||
| constructor() { | ||
| (0, import_defineProperty$2.default)(this, "outgoingRequests", new Array()); | ||
| (0, import_defineProperty$2.default)(this, "pendingRequests", {}); | ||
| } | ||
| /** | ||
| * Registers a new request by adding it to the outgoing queue and setting up | ||
| * callbacks for lifecycle events such as completion or error. | ||
| * | ||
| * @param message - The outgoing message to be sent. | ||
| * @param callbacks - Callback functions to observe the request's state. | ||
| * @returns A cleanup function to manually remove the request. | ||
| */ | ||
| register(message, callbacks) { | ||
| const { promise: end, resolve } = withResolvers(); | ||
| this.outgoingRequests.push({ | ||
| id: String(message.id), | ||
| message, | ||
| end, | ||
| callbacks: { | ||
| next: callbacks.next, | ||
| complete: () => { | ||
| callbacks.complete(); | ||
| resolve(); | ||
| }, | ||
| error: (e) => { | ||
| callbacks.error(e); | ||
| resolve(); | ||
| } | ||
| } | ||
| }); | ||
| return () => { | ||
| this.delete(message.id); | ||
| callbacks.complete(); | ||
| resolve(); | ||
| }; | ||
| } | ||
| /** | ||
| * Deletes a request from both the outgoing and pending collections, if it exists. | ||
| */ | ||
| delete(messageId) { | ||
| if (messageId === null) return; | ||
| this.outgoingRequests = this.outgoingRequests.filter(({ id }) => id !== String(messageId)); | ||
| delete this.pendingRequests[String(messageId)]; | ||
| } | ||
| /** | ||
| * Moves all outgoing requests to the pending state and clears the outgoing queue. | ||
| * | ||
| * The caller is expected to handle the actual sending of the requests | ||
| * (e.g., sending them over the network) after this method is called. | ||
| * | ||
| * @returns The list of requests that were transitioned to the pending state. | ||
| */ | ||
| flush() { | ||
| const requests = this.outgoingRequests; | ||
| this.outgoingRequests = []; | ||
| for (const request of requests) this.pendingRequests[request.id] = request; | ||
| return requests; | ||
| } | ||
| /** | ||
| * Retrieves all currently pending requests, which are in flight awaiting responses | ||
| * or handling ongoing subscriptions. | ||
| */ | ||
| getPendingRequests() { | ||
| return Object.values(this.pendingRequests); | ||
| } | ||
| /** | ||
| * Retrieves a specific pending request by its message ID. | ||
| */ | ||
| getPendingRequest(messageId) { | ||
| if (messageId === null) return null; | ||
| return this.pendingRequests[String(messageId)]; | ||
| } | ||
| /** | ||
| * Retrieves all outgoing requests, which are waiting to be sent. | ||
| */ | ||
| getOutgoingRequests() { | ||
| return this.outgoingRequests; | ||
| } | ||
| /** | ||
| * Retrieves all requests, both outgoing and pending, with their respective states. | ||
| * | ||
| * @returns An array of all requests with their state ("outgoing" or "pending"). | ||
| */ | ||
| getRequests() { | ||
| return [...this.getOutgoingRequests().map((request) => ({ | ||
| state: "outgoing", | ||
| message: request.message, | ||
| end: request.end, | ||
| callbacks: request.callbacks | ||
| })), ...this.getPendingRequests().map((request) => ({ | ||
| state: "pending", | ||
| message: request.message, | ||
| end: request.end, | ||
| callbacks: request.callbacks | ||
| }))]; | ||
| } | ||
| /** | ||
| * Checks if there are any pending requests, including ongoing subscriptions. | ||
| */ | ||
| hasPendingRequests() { | ||
| return this.getPendingRequests().length > 0; | ||
| } | ||
| /** | ||
| * Checks if there are any pending subscriptions | ||
| */ | ||
| hasPendingSubscriptions() { | ||
| return this.getPendingRequests().some((request) => request.message.method === "subscription"); | ||
| } | ||
| /** | ||
| * Checks if there are any outgoing requests waiting to be sent. | ||
| */ | ||
| hasOutgoingRequests() { | ||
| return this.outgoingRequests.length > 0; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/wsConnection.ts | ||
| var import_defineProperty$1 = require_chunk.__toESM(require_objectSpread2$1.require_defineProperty(), 1); | ||
| /** | ||
| * Opens a WebSocket connection asynchronously and returns a promise | ||
| * that resolves when the connection is successfully established. | ||
| * The promise rejects if an error occurs during the connection attempt. | ||
| */ | ||
| function asyncWsOpen(ws) { | ||
| const { promise, resolve, reject } = withResolvers(); | ||
| ws.addEventListener("open", () => { | ||
| ws.removeEventListener("error", reject); | ||
| resolve(); | ||
| }); | ||
| ws.addEventListener("error", reject); | ||
| return promise; | ||
| } | ||
| /** | ||
| * Sets up a periodic ping-pong mechanism to keep the WebSocket connection alive. | ||
| * | ||
| * - Sends "PING" messages at regular intervals defined by `intervalMs`. | ||
| * - If a "PONG" response is not received within the `pongTimeoutMs`, the WebSocket is closed. | ||
| * - The ping timer resets upon receiving any message to maintain activity. | ||
| * - Automatically starts the ping process when the WebSocket connection is opened. | ||
| * - Cleans up timers when the WebSocket is closed. | ||
| * | ||
| * @param ws - The WebSocket instance to manage. | ||
| * @param options - Configuration options for ping-pong intervals and timeouts. | ||
| */ | ||
| function setupPingInterval(ws, { intervalMs, pongTimeoutMs }) { | ||
| let pingTimeout; | ||
| let pongTimeout; | ||
| function start() { | ||
| pingTimeout = setTimeout(() => { | ||
| ws.send("PING"); | ||
| pongTimeout = setTimeout(() => { | ||
| ws.close(); | ||
| }, pongTimeoutMs); | ||
| }, intervalMs); | ||
| } | ||
| function reset() { | ||
| clearTimeout(pingTimeout); | ||
| start(); | ||
| } | ||
| function pong() { | ||
| clearTimeout(pongTimeout); | ||
| reset(); | ||
| } | ||
| ws.addEventListener("open", start); | ||
| ws.addEventListener("message", ({ data }) => { | ||
| clearTimeout(pingTimeout); | ||
| start(); | ||
| if (data === "PONG") pong(); | ||
| }); | ||
| ws.addEventListener("close", () => { | ||
| clearTimeout(pingTimeout); | ||
| clearTimeout(pongTimeout); | ||
| }); | ||
| } | ||
| /** | ||
| * Manages a WebSocket connection with support for reconnection, keep-alive mechanisms, | ||
| * and observable state tracking. | ||
| */ | ||
| var WsConnection = class WsConnection { | ||
| constructor(opts) { | ||
| var _opts$WebSocketPonyfi; | ||
| (0, import_defineProperty$1.default)(this, "id", ++WsConnection.connectCount); | ||
| (0, import_defineProperty$1.default)(this, "WebSocketPonyfill", void 0); | ||
| (0, import_defineProperty$1.default)(this, "urlOptions", void 0); | ||
| (0, import_defineProperty$1.default)(this, "keepAliveOpts", void 0); | ||
| (0, import_defineProperty$1.default)(this, "wsObservable", (0, __trpc_server_observable.behaviorSubject)(null)); | ||
| (0, import_defineProperty$1.default)(this, "openPromise", null); | ||
| this.WebSocketPonyfill = (_opts$WebSocketPonyfi = opts.WebSocketPonyfill) !== null && _opts$WebSocketPonyfi !== void 0 ? _opts$WebSocketPonyfi : WebSocket; | ||
| if (!this.WebSocketPonyfill) throw new Error("No WebSocket implementation found - you probably don't want to use this on the server, but if you do you need to pass a `WebSocket`-ponyfill"); | ||
| this.urlOptions = opts.urlOptions; | ||
| this.keepAliveOpts = opts.keepAlive; | ||
| } | ||
| get ws() { | ||
| return this.wsObservable.get(); | ||
| } | ||
| set ws(ws) { | ||
| this.wsObservable.next(ws); | ||
| } | ||
| /** | ||
| * Checks if the WebSocket connection is open and ready to communicate. | ||
| */ | ||
| isOpen() { | ||
| return !!this.ws && this.ws.readyState === this.WebSocketPonyfill.OPEN && !this.openPromise; | ||
| } | ||
| /** | ||
| * Checks if the WebSocket connection is closed or in the process of closing. | ||
| */ | ||
| isClosed() { | ||
| return !!this.ws && (this.ws.readyState === this.WebSocketPonyfill.CLOSING || this.ws.readyState === this.WebSocketPonyfill.CLOSED); | ||
| } | ||
| async open() { | ||
| var _this = this; | ||
| if (_this.openPromise) return _this.openPromise; | ||
| _this.id = ++WsConnection.connectCount; | ||
| const wsPromise = prepareUrl(_this.urlOptions).then((url) => new _this.WebSocketPonyfill(url)); | ||
| _this.openPromise = wsPromise.then(async (ws) => { | ||
| _this.ws = ws; | ||
| ws.addEventListener("message", function({ data }) { | ||
| if (data === "PING") this.send("PONG"); | ||
| }); | ||
| if (_this.keepAliveOpts.enabled) setupPingInterval(ws, _this.keepAliveOpts); | ||
| ws.addEventListener("close", () => { | ||
| if (_this.ws === ws) _this.ws = null; | ||
| }); | ||
| await asyncWsOpen(ws); | ||
| if (_this.urlOptions.connectionParams) ws.send(await buildConnectionMessage(_this.urlOptions.connectionParams)); | ||
| }); | ||
| try { | ||
| await _this.openPromise; | ||
| } finally { | ||
| _this.openPromise = null; | ||
| } | ||
| } | ||
| /** | ||
| * Closes the WebSocket connection gracefully. | ||
| * Waits for any ongoing open operation to complete before closing. | ||
| */ | ||
| async close() { | ||
| var _this2 = this; | ||
| try { | ||
| await _this2.openPromise; | ||
| } finally { | ||
| var _this$ws; | ||
| (_this$ws = _this2.ws) === null || _this$ws === void 0 || _this$ws.close(); | ||
| } | ||
| } | ||
| }; | ||
| (0, import_defineProperty$1.default)(WsConnection, "connectCount", 0); | ||
| /** | ||
| * Provides a backward-compatible representation of the connection state. | ||
| */ | ||
| function backwardCompatibility(connection) { | ||
| if (connection.isOpen()) return { | ||
| id: connection.id, | ||
| state: "open", | ||
| ws: connection.ws | ||
| }; | ||
| if (connection.isClosed()) return { | ||
| id: connection.id, | ||
| state: "closed", | ||
| ws: connection.ws | ||
| }; | ||
| if (!connection.ws) return null; | ||
| return { | ||
| id: connection.id, | ||
| state: "connecting", | ||
| ws: connection.ws | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/wsClient.ts | ||
| var import_defineProperty = require_chunk.__toESM(require_objectSpread2$1.require_defineProperty(), 1); | ||
| var import_objectSpread2 = require_chunk.__toESM(require_objectSpread2$1.require_objectSpread2(), 1); | ||
| /** | ||
| * A WebSocket client for managing TRPC operations, supporting lazy initialization, | ||
| * reconnection, keep-alive, and request management. | ||
| */ | ||
| var WsClient = class { | ||
| constructor(opts) { | ||
| var _opts$retryDelayMs; | ||
| (0, import_defineProperty.default)(this, "connectionState", void 0); | ||
| (0, import_defineProperty.default)(this, "allowReconnect", false); | ||
| (0, import_defineProperty.default)(this, "requestManager", new RequestManager()); | ||
| (0, import_defineProperty.default)(this, "activeConnection", void 0); | ||
| (0, import_defineProperty.default)(this, "reconnectRetryDelay", void 0); | ||
| (0, import_defineProperty.default)(this, "inactivityTimeout", void 0); | ||
| (0, import_defineProperty.default)(this, "callbacks", void 0); | ||
| (0, import_defineProperty.default)(this, "lazyMode", void 0); | ||
| (0, import_defineProperty.default)(this, "reconnecting", null); | ||
| this.callbacks = { | ||
| onOpen: opts.onOpen, | ||
| onClose: opts.onClose, | ||
| onError: opts.onError | ||
| }; | ||
| const lazyOptions = (0, import_objectSpread2.default)((0, import_objectSpread2.default)({}, lazyDefaults), opts.lazy); | ||
| this.inactivityTimeout = new ResettableTimeout(() => { | ||
| if (this.requestManager.hasOutgoingRequests() || this.requestManager.hasPendingRequests()) { | ||
| this.inactivityTimeout.reset(); | ||
| return; | ||
| } | ||
| this.close().catch(() => null); | ||
| }, lazyOptions.closeMs); | ||
| this.activeConnection = new WsConnection({ | ||
| WebSocketPonyfill: opts.WebSocket, | ||
| urlOptions: opts, | ||
| keepAlive: (0, import_objectSpread2.default)((0, import_objectSpread2.default)({}, keepAliveDefaults), opts.keepAlive) | ||
| }); | ||
| this.activeConnection.wsObservable.subscribe({ next: (ws) => { | ||
| if (!ws) return; | ||
| this.setupWebSocketListeners(ws); | ||
| } }); | ||
| this.reconnectRetryDelay = (_opts$retryDelayMs = opts.retryDelayMs) !== null && _opts$retryDelayMs !== void 0 ? _opts$retryDelayMs : exponentialBackoff; | ||
| this.lazyMode = lazyOptions.enabled; | ||
| this.connectionState = (0, __trpc_server_observable.behaviorSubject)({ | ||
| type: "state", | ||
| state: lazyOptions.enabled ? "idle" : "connecting", | ||
| error: null | ||
| }); | ||
| if (!this.lazyMode) this.open().catch(() => null); | ||
| } | ||
| /** | ||
| * Opens the WebSocket connection. Handles reconnection attempts and updates | ||
| * the connection state accordingly. | ||
| */ | ||
| async open() { | ||
| var _this = this; | ||
| _this.allowReconnect = true; | ||
| if (_this.connectionState.get().state === "idle") _this.connectionState.next({ | ||
| type: "state", | ||
| state: "connecting", | ||
| error: null | ||
| }); | ||
| try { | ||
| await _this.activeConnection.open(); | ||
| } catch (error) { | ||
| _this.reconnect(new TRPCWebSocketClosedError({ | ||
| message: "Initialization error", | ||
| cause: error | ||
| })); | ||
| return _this.reconnecting; | ||
| } | ||
| } | ||
| /** | ||
| * Closes the WebSocket connection and stops managing requests. | ||
| * Ensures all outgoing and pending requests are properly finalized. | ||
| */ | ||
| async close() { | ||
| var _this2 = this; | ||
| _this2.allowReconnect = false; | ||
| _this2.inactivityTimeout.stop(); | ||
| const requestsToAwait = []; | ||
| for (const request of _this2.requestManager.getRequests()) if (request.message.method === "subscription") request.callbacks.complete(); | ||
| else if (request.state === "outgoing") request.callbacks.error(require_TRPCClientError.TRPCClientError.from(new TRPCWebSocketClosedError({ message: "Closed before connection was established" }))); | ||
| else requestsToAwait.push(request.end); | ||
| await Promise.all(requestsToAwait).catch(() => null); | ||
| await _this2.activeConnection.close().catch(() => null); | ||
| _this2.connectionState.next({ | ||
| type: "state", | ||
| state: "idle", | ||
| error: null | ||
| }); | ||
| } | ||
| /** | ||
| * Method to request the server. | ||
| * Handles data transformation, batching of requests, and subscription lifecycle. | ||
| * | ||
| * @param op - The operation details including id, type, path, input and signal | ||
| * @param transformer - Data transformer for serializing requests and deserializing responses | ||
| * @param lastEventId - Optional ID of the last received event for subscriptions | ||
| * | ||
| * @returns An observable that emits operation results and handles cleanup | ||
| */ | ||
| request({ op: { id, type, path, input, signal }, transformer, lastEventId }) { | ||
| return (0, __trpc_server_observable.observable)((observer) => { | ||
| const abort = this.batchSend({ | ||
| id, | ||
| method: type, | ||
| params: { | ||
| input: transformer.input.serialize(input), | ||
| path, | ||
| lastEventId | ||
| } | ||
| }, (0, import_objectSpread2.default)((0, import_objectSpread2.default)({}, observer), {}, { next(event) { | ||
| const transformed = (0, __trpc_server_unstable_core_do_not_import.transformResult)(event, transformer.output); | ||
| if (!transformed.ok) { | ||
| observer.error(require_TRPCClientError.TRPCClientError.from(transformed.error)); | ||
| return; | ||
| } | ||
| observer.next({ result: transformed.result }); | ||
| } })); | ||
| return () => { | ||
| abort(); | ||
| if (type === "subscription" && this.activeConnection.isOpen()) this.send({ | ||
| id, | ||
| method: "subscription.stop" | ||
| }); | ||
| signal === null || signal === void 0 || signal.removeEventListener("abort", abort); | ||
| }; | ||
| }); | ||
| } | ||
| get connection() { | ||
| return backwardCompatibility(this.activeConnection); | ||
| } | ||
| reconnect(closedError) { | ||
| var _this3 = this; | ||
| this.connectionState.next({ | ||
| type: "state", | ||
| state: "connecting", | ||
| error: require_TRPCClientError.TRPCClientError.from(closedError) | ||
| }); | ||
| if (this.reconnecting) return; | ||
| const tryReconnect = async (attemptIndex) => { | ||
| try { | ||
| await (0, __trpc_server_unstable_core_do_not_import.sleep)(_this3.reconnectRetryDelay(attemptIndex)); | ||
| if (_this3.allowReconnect) { | ||
| await _this3.activeConnection.close(); | ||
| await _this3.activeConnection.open(); | ||
| if (_this3.requestManager.hasPendingRequests()) _this3.send(_this3.requestManager.getPendingRequests().map(({ message }) => message)); | ||
| } | ||
| _this3.reconnecting = null; | ||
| } catch (_unused) { | ||
| await tryReconnect(attemptIndex + 1); | ||
| } | ||
| }; | ||
| this.reconnecting = tryReconnect(0); | ||
| } | ||
| setupWebSocketListeners(ws) { | ||
| var _this4 = this; | ||
| const handleCloseOrError = (cause) => { | ||
| const reqs = this.requestManager.getPendingRequests(); | ||
| for (const { message, callbacks } of reqs) { | ||
| if (message.method === "subscription") continue; | ||
| callbacks.error(require_TRPCClientError.TRPCClientError.from(cause !== null && cause !== void 0 ? cause : new TRPCWebSocketClosedError({ | ||
| message: "WebSocket closed", | ||
| cause | ||
| }))); | ||
| this.requestManager.delete(message.id); | ||
| } | ||
| }; | ||
| ws.addEventListener("open", () => { | ||
| (0, __trpc_server_unstable_core_do_not_import.run)(async () => { | ||
| var _this$callbacks$onOpe, _this$callbacks; | ||
| if (_this4.lazyMode) _this4.inactivityTimeout.start(); | ||
| (_this$callbacks$onOpe = (_this$callbacks = _this4.callbacks).onOpen) === null || _this$callbacks$onOpe === void 0 || _this$callbacks$onOpe.call(_this$callbacks); | ||
| _this4.connectionState.next({ | ||
| type: "state", | ||
| state: "pending", | ||
| error: null | ||
| }); | ||
| }).catch((error) => { | ||
| ws.close(3e3); | ||
| handleCloseOrError(error); | ||
| }); | ||
| }); | ||
| ws.addEventListener("message", ({ data }) => { | ||
| this.inactivityTimeout.reset(); | ||
| if (typeof data !== "string" || ["PING", "PONG"].includes(data)) return; | ||
| const incomingMessage = JSON.parse(data); | ||
| if ("method" in incomingMessage) { | ||
| this.handleIncomingRequest(incomingMessage); | ||
| return; | ||
| } | ||
| this.handleResponseMessage(incomingMessage); | ||
| }); | ||
| ws.addEventListener("close", (event) => { | ||
| var _this$callbacks$onClo, _this$callbacks2; | ||
| handleCloseOrError(event); | ||
| (_this$callbacks$onClo = (_this$callbacks2 = this.callbacks).onClose) === null || _this$callbacks$onClo === void 0 || _this$callbacks$onClo.call(_this$callbacks2, event); | ||
| if (!this.lazyMode || this.requestManager.hasPendingSubscriptions()) this.reconnect(new TRPCWebSocketClosedError({ | ||
| message: "WebSocket closed", | ||
| cause: event | ||
| })); | ||
| }); | ||
| ws.addEventListener("error", (event) => { | ||
| var _this$callbacks$onErr, _this$callbacks3; | ||
| handleCloseOrError(event); | ||
| (_this$callbacks$onErr = (_this$callbacks3 = this.callbacks).onError) === null || _this$callbacks$onErr === void 0 || _this$callbacks$onErr.call(_this$callbacks3, event); | ||
| this.reconnect(new TRPCWebSocketClosedError({ | ||
| message: "WebSocket closed", | ||
| cause: event | ||
| })); | ||
| }); | ||
| } | ||
| handleResponseMessage(message) { | ||
| const request = this.requestManager.getPendingRequest(message.id); | ||
| if (!request) return; | ||
| request.callbacks.next(message); | ||
| let completed = true; | ||
| if ("result" in message && request.message.method === "subscription") { | ||
| if (message.result.type === "data") request.message.params.lastEventId = message.result.id; | ||
| if (message.result.type !== "stopped") completed = false; | ||
| } | ||
| if (completed) { | ||
| request.callbacks.complete(); | ||
| this.requestManager.delete(message.id); | ||
| } | ||
| } | ||
| handleIncomingRequest(message) { | ||
| if (message.method === "reconnect") this.reconnect(new TRPCWebSocketClosedError({ message: "Server requested reconnect" })); | ||
| } | ||
| /** | ||
| * Sends a message or batch of messages directly to the server. | ||
| */ | ||
| send(messageOrMessages) { | ||
| if (!this.activeConnection.isOpen()) throw new Error("Active connection is not open"); | ||
| const messages = messageOrMessages instanceof Array ? messageOrMessages : [messageOrMessages]; | ||
| this.activeConnection.ws.send(JSON.stringify(messages.length === 1 ? messages[0] : messages)); | ||
| } | ||
| /** | ||
| * Groups requests for batch sending. | ||
| * | ||
| * @returns A function to abort the batched request. | ||
| */ | ||
| batchSend(message, callbacks) { | ||
| var _this5 = this; | ||
| this.inactivityTimeout.reset(); | ||
| (0, __trpc_server_unstable_core_do_not_import.run)(async () => { | ||
| if (!_this5.activeConnection.isOpen()) await _this5.open(); | ||
| await (0, __trpc_server_unstable_core_do_not_import.sleep)(0); | ||
| if (!_this5.requestManager.hasOutgoingRequests()) return; | ||
| _this5.send(_this5.requestManager.flush().map(({ message: message$1 }) => message$1)); | ||
| }).catch((err) => { | ||
| this.requestManager.delete(message.id); | ||
| callbacks.error(require_TRPCClientError.TRPCClientError.from(err)); | ||
| }); | ||
| return this.requestManager.register(message, callbacks); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/createWsClient.ts | ||
| function createWSClient(opts) { | ||
| return new WsClient(opts); | ||
| } | ||
| //#endregion | ||
| //#region src/links/wsLink/wsLink.ts | ||
| function wsLink(opts) { | ||
| const { client } = opts; | ||
| const transformer = require_unstable_internals.getTransformer(opts.transformer); | ||
| return () => { | ||
| return ({ op }) => { | ||
| return (0, __trpc_server_observable.observable)((observer) => { | ||
| const connStateSubscription = op.type === "subscription" ? client.connectionState.subscribe({ next(result) { | ||
| observer.next({ | ||
| result, | ||
| context: op.context | ||
| }); | ||
| } }) : null; | ||
| const requestSubscription = client.request({ | ||
| op, | ||
| transformer | ||
| }).subscribe(observer); | ||
| return () => { | ||
| requestSubscription.unsubscribe(); | ||
| connStateSubscription === null || connStateSubscription === void 0 || connStateSubscription.unsubscribe(); | ||
| }; | ||
| }); | ||
| }; | ||
| }; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'createWSClient', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createWSClient; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'resultOf', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return resultOf; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'wsLink', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return wsLink; | ||
| } | ||
| }); |
| import { __toESM, require_defineProperty, require_objectSpread2 } from "./objectSpread2-BvkFp-_Y.mjs"; | ||
| import { TRPCClientError } from "./TRPCClientError-CjKyS10w.mjs"; | ||
| import { getTransformer } from "./unstable-internals-Bg7n9BBj.mjs"; | ||
| import { behaviorSubject, observable } from "@trpc/server/observable"; | ||
| import { run, sleep, transformResult } from "@trpc/server/unstable-core-do-not-import"; | ||
| //#region src/links/wsLink/wsClient/options.ts | ||
| const lazyDefaults = { | ||
| enabled: false, | ||
| closeMs: 0 | ||
| }; | ||
| const keepAliveDefaults = { | ||
| enabled: false, | ||
| pongTimeoutMs: 1e3, | ||
| intervalMs: 5e3 | ||
| }; | ||
| /** | ||
| * Calculates a delay for exponential backoff based on the retry attempt index. | ||
| * The delay starts at 0 for the first attempt and doubles for each subsequent attempt, | ||
| * capped at 30 seconds. | ||
| */ | ||
| const exponentialBackoff = (attemptIndex) => { | ||
| return attemptIndex === 0 ? 0 : Math.min(1e3 * 2 ** attemptIndex, 3e4); | ||
| }; | ||
| //#endregion | ||
| //#region src/links/internals/urlWithConnectionParams.ts | ||
| /** | ||
| * Get the result of a value or function that returns a value | ||
| * It also optionally accepts typesafe arguments for the function | ||
| */ | ||
| const resultOf = (value, ...args) => { | ||
| return typeof value === "function" ? value(...args) : value; | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/utils.ts | ||
| var import_defineProperty$3 = __toESM(require_defineProperty(), 1); | ||
| var TRPCWebSocketClosedError = class TRPCWebSocketClosedError extends Error { | ||
| constructor(opts) { | ||
| super(opts.message, { cause: opts.cause }); | ||
| this.name = "TRPCWebSocketClosedError"; | ||
| Object.setPrototypeOf(this, TRPCWebSocketClosedError.prototype); | ||
| } | ||
| }; | ||
| /** | ||
| * Utility class for managing a timeout that can be started, stopped, and reset. | ||
| * Useful for scenarios where the timeout duration is reset dynamically based on events. | ||
| */ | ||
| var ResettableTimeout = class { | ||
| constructor(onTimeout, timeoutMs) { | ||
| this.onTimeout = onTimeout; | ||
| this.timeoutMs = timeoutMs; | ||
| (0, import_defineProperty$3.default)(this, "timeout", void 0); | ||
| } | ||
| /** | ||
| * Resets the current timeout, restarting it with the same duration. | ||
| * Does nothing if no timeout is active. | ||
| */ | ||
| reset() { | ||
| if (!this.timeout) return; | ||
| clearTimeout(this.timeout); | ||
| this.timeout = setTimeout(this.onTimeout, this.timeoutMs); | ||
| } | ||
| start() { | ||
| clearTimeout(this.timeout); | ||
| this.timeout = setTimeout(this.onTimeout, this.timeoutMs); | ||
| } | ||
| stop() { | ||
| clearTimeout(this.timeout); | ||
| this.timeout = void 0; | ||
| } | ||
| }; | ||
| function withResolvers() { | ||
| let resolve; | ||
| let reject; | ||
| const promise = new Promise((res, rej) => { | ||
| resolve = res; | ||
| reject = rej; | ||
| }); | ||
| return { | ||
| promise, | ||
| resolve, | ||
| reject | ||
| }; | ||
| } | ||
| /** | ||
| * Resolves a WebSocket URL and optionally appends connection parameters. | ||
| * | ||
| * If connectionParams are provided, appends 'connectionParams=1' query parameter. | ||
| */ | ||
| async function prepareUrl(urlOptions) { | ||
| const url = await resultOf(urlOptions.url); | ||
| if (!urlOptions.connectionParams) return url; | ||
| const prefix = url.includes("?") ? "&" : "?"; | ||
| const connectionParams = `${prefix}connectionParams=1`; | ||
| return url + connectionParams; | ||
| } | ||
| async function buildConnectionMessage(connectionParams) { | ||
| const message = { | ||
| method: "connectionParams", | ||
| data: await resultOf(connectionParams) | ||
| }; | ||
| return JSON.stringify(message); | ||
| } | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/requestManager.ts | ||
| var import_defineProperty$2 = __toESM(require_defineProperty(), 1); | ||
| /** | ||
| * Manages WebSocket requests, tracking their lifecycle and providing utility methods | ||
| * for handling outgoing and pending requests. | ||
| * | ||
| * - **Outgoing requests**: Requests that are queued and waiting to be sent. | ||
| * - **Pending requests**: Requests that have been sent and are in flight awaiting a response. | ||
| * For subscriptions, multiple responses may be received until the subscription is closed. | ||
| */ | ||
| var RequestManager = class { | ||
| constructor() { | ||
| (0, import_defineProperty$2.default)(this, "outgoingRequests", new Array()); | ||
| (0, import_defineProperty$2.default)(this, "pendingRequests", {}); | ||
| } | ||
| /** | ||
| * Registers a new request by adding it to the outgoing queue and setting up | ||
| * callbacks for lifecycle events such as completion or error. | ||
| * | ||
| * @param message - The outgoing message to be sent. | ||
| * @param callbacks - Callback functions to observe the request's state. | ||
| * @returns A cleanup function to manually remove the request. | ||
| */ | ||
| register(message, callbacks) { | ||
| const { promise: end, resolve } = withResolvers(); | ||
| this.outgoingRequests.push({ | ||
| id: String(message.id), | ||
| message, | ||
| end, | ||
| callbacks: { | ||
| next: callbacks.next, | ||
| complete: () => { | ||
| callbacks.complete(); | ||
| resolve(); | ||
| }, | ||
| error: (e) => { | ||
| callbacks.error(e); | ||
| resolve(); | ||
| } | ||
| } | ||
| }); | ||
| return () => { | ||
| this.delete(message.id); | ||
| callbacks.complete(); | ||
| resolve(); | ||
| }; | ||
| } | ||
| /** | ||
| * Deletes a request from both the outgoing and pending collections, if it exists. | ||
| */ | ||
| delete(messageId) { | ||
| if (messageId === null) return; | ||
| this.outgoingRequests = this.outgoingRequests.filter(({ id }) => id !== String(messageId)); | ||
| delete this.pendingRequests[String(messageId)]; | ||
| } | ||
| /** | ||
| * Moves all outgoing requests to the pending state and clears the outgoing queue. | ||
| * | ||
| * The caller is expected to handle the actual sending of the requests | ||
| * (e.g., sending them over the network) after this method is called. | ||
| * | ||
| * @returns The list of requests that were transitioned to the pending state. | ||
| */ | ||
| flush() { | ||
| const requests = this.outgoingRequests; | ||
| this.outgoingRequests = []; | ||
| for (const request of requests) this.pendingRequests[request.id] = request; | ||
| return requests; | ||
| } | ||
| /** | ||
| * Retrieves all currently pending requests, which are in flight awaiting responses | ||
| * or handling ongoing subscriptions. | ||
| */ | ||
| getPendingRequests() { | ||
| return Object.values(this.pendingRequests); | ||
| } | ||
| /** | ||
| * Retrieves a specific pending request by its message ID. | ||
| */ | ||
| getPendingRequest(messageId) { | ||
| if (messageId === null) return null; | ||
| return this.pendingRequests[String(messageId)]; | ||
| } | ||
| /** | ||
| * Retrieves all outgoing requests, which are waiting to be sent. | ||
| */ | ||
| getOutgoingRequests() { | ||
| return this.outgoingRequests; | ||
| } | ||
| /** | ||
| * Retrieves all requests, both outgoing and pending, with their respective states. | ||
| * | ||
| * @returns An array of all requests with their state ("outgoing" or "pending"). | ||
| */ | ||
| getRequests() { | ||
| return [...this.getOutgoingRequests().map((request) => ({ | ||
| state: "outgoing", | ||
| message: request.message, | ||
| end: request.end, | ||
| callbacks: request.callbacks | ||
| })), ...this.getPendingRequests().map((request) => ({ | ||
| state: "pending", | ||
| message: request.message, | ||
| end: request.end, | ||
| callbacks: request.callbacks | ||
| }))]; | ||
| } | ||
| /** | ||
| * Checks if there are any pending requests, including ongoing subscriptions. | ||
| */ | ||
| hasPendingRequests() { | ||
| return this.getPendingRequests().length > 0; | ||
| } | ||
| /** | ||
| * Checks if there are any pending subscriptions | ||
| */ | ||
| hasPendingSubscriptions() { | ||
| return this.getPendingRequests().some((request) => request.message.method === "subscription"); | ||
| } | ||
| /** | ||
| * Checks if there are any outgoing requests waiting to be sent. | ||
| */ | ||
| hasOutgoingRequests() { | ||
| return this.outgoingRequests.length > 0; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/wsConnection.ts | ||
| var import_defineProperty$1 = __toESM(require_defineProperty(), 1); | ||
| /** | ||
| * Opens a WebSocket connection asynchronously and returns a promise | ||
| * that resolves when the connection is successfully established. | ||
| * The promise rejects if an error occurs during the connection attempt. | ||
| */ | ||
| function asyncWsOpen(ws) { | ||
| const { promise, resolve, reject } = withResolvers(); | ||
| ws.addEventListener("open", () => { | ||
| ws.removeEventListener("error", reject); | ||
| resolve(); | ||
| }); | ||
| ws.addEventListener("error", reject); | ||
| return promise; | ||
| } | ||
| /** | ||
| * Sets up a periodic ping-pong mechanism to keep the WebSocket connection alive. | ||
| * | ||
| * - Sends "PING" messages at regular intervals defined by `intervalMs`. | ||
| * - If a "PONG" response is not received within the `pongTimeoutMs`, the WebSocket is closed. | ||
| * - The ping timer resets upon receiving any message to maintain activity. | ||
| * - Automatically starts the ping process when the WebSocket connection is opened. | ||
| * - Cleans up timers when the WebSocket is closed. | ||
| * | ||
| * @param ws - The WebSocket instance to manage. | ||
| * @param options - Configuration options for ping-pong intervals and timeouts. | ||
| */ | ||
| function setupPingInterval(ws, { intervalMs, pongTimeoutMs }) { | ||
| let pingTimeout; | ||
| let pongTimeout; | ||
| function start() { | ||
| pingTimeout = setTimeout(() => { | ||
| ws.send("PING"); | ||
| pongTimeout = setTimeout(() => { | ||
| ws.close(); | ||
| }, pongTimeoutMs); | ||
| }, intervalMs); | ||
| } | ||
| function reset() { | ||
| clearTimeout(pingTimeout); | ||
| start(); | ||
| } | ||
| function pong() { | ||
| clearTimeout(pongTimeout); | ||
| reset(); | ||
| } | ||
| ws.addEventListener("open", start); | ||
| ws.addEventListener("message", ({ data }) => { | ||
| clearTimeout(pingTimeout); | ||
| start(); | ||
| if (data === "PONG") pong(); | ||
| }); | ||
| ws.addEventListener("close", () => { | ||
| clearTimeout(pingTimeout); | ||
| clearTimeout(pongTimeout); | ||
| }); | ||
| } | ||
| /** | ||
| * Manages a WebSocket connection with support for reconnection, keep-alive mechanisms, | ||
| * and observable state tracking. | ||
| */ | ||
| var WsConnection = class WsConnection { | ||
| constructor(opts) { | ||
| var _opts$WebSocketPonyfi; | ||
| (0, import_defineProperty$1.default)(this, "id", ++WsConnection.connectCount); | ||
| (0, import_defineProperty$1.default)(this, "WebSocketPonyfill", void 0); | ||
| (0, import_defineProperty$1.default)(this, "urlOptions", void 0); | ||
| (0, import_defineProperty$1.default)(this, "keepAliveOpts", void 0); | ||
| (0, import_defineProperty$1.default)(this, "wsObservable", behaviorSubject(null)); | ||
| (0, import_defineProperty$1.default)(this, "openPromise", null); | ||
| this.WebSocketPonyfill = (_opts$WebSocketPonyfi = opts.WebSocketPonyfill) !== null && _opts$WebSocketPonyfi !== void 0 ? _opts$WebSocketPonyfi : WebSocket; | ||
| if (!this.WebSocketPonyfill) throw new Error("No WebSocket implementation found - you probably don't want to use this on the server, but if you do you need to pass a `WebSocket`-ponyfill"); | ||
| this.urlOptions = opts.urlOptions; | ||
| this.keepAliveOpts = opts.keepAlive; | ||
| } | ||
| get ws() { | ||
| return this.wsObservable.get(); | ||
| } | ||
| set ws(ws) { | ||
| this.wsObservable.next(ws); | ||
| } | ||
| /** | ||
| * Checks if the WebSocket connection is open and ready to communicate. | ||
| */ | ||
| isOpen() { | ||
| return !!this.ws && this.ws.readyState === this.WebSocketPonyfill.OPEN && !this.openPromise; | ||
| } | ||
| /** | ||
| * Checks if the WebSocket connection is closed or in the process of closing. | ||
| */ | ||
| isClosed() { | ||
| return !!this.ws && (this.ws.readyState === this.WebSocketPonyfill.CLOSING || this.ws.readyState === this.WebSocketPonyfill.CLOSED); | ||
| } | ||
| async open() { | ||
| var _this = this; | ||
| if (_this.openPromise) return _this.openPromise; | ||
| _this.id = ++WsConnection.connectCount; | ||
| const wsPromise = prepareUrl(_this.urlOptions).then((url) => new _this.WebSocketPonyfill(url)); | ||
| _this.openPromise = wsPromise.then(async (ws) => { | ||
| _this.ws = ws; | ||
| ws.addEventListener("message", function({ data }) { | ||
| if (data === "PING") this.send("PONG"); | ||
| }); | ||
| if (_this.keepAliveOpts.enabled) setupPingInterval(ws, _this.keepAliveOpts); | ||
| ws.addEventListener("close", () => { | ||
| if (_this.ws === ws) _this.ws = null; | ||
| }); | ||
| await asyncWsOpen(ws); | ||
| if (_this.urlOptions.connectionParams) ws.send(await buildConnectionMessage(_this.urlOptions.connectionParams)); | ||
| }); | ||
| try { | ||
| await _this.openPromise; | ||
| } finally { | ||
| _this.openPromise = null; | ||
| } | ||
| } | ||
| /** | ||
| * Closes the WebSocket connection gracefully. | ||
| * Waits for any ongoing open operation to complete before closing. | ||
| */ | ||
| async close() { | ||
| var _this2 = this; | ||
| try { | ||
| await _this2.openPromise; | ||
| } finally { | ||
| var _this$ws; | ||
| (_this$ws = _this2.ws) === null || _this$ws === void 0 || _this$ws.close(); | ||
| } | ||
| } | ||
| }; | ||
| (0, import_defineProperty$1.default)(WsConnection, "connectCount", 0); | ||
| /** | ||
| * Provides a backward-compatible representation of the connection state. | ||
| */ | ||
| function backwardCompatibility(connection) { | ||
| if (connection.isOpen()) return { | ||
| id: connection.id, | ||
| state: "open", | ||
| ws: connection.ws | ||
| }; | ||
| if (connection.isClosed()) return { | ||
| id: connection.id, | ||
| state: "closed", | ||
| ws: connection.ws | ||
| }; | ||
| if (!connection.ws) return null; | ||
| return { | ||
| id: connection.id, | ||
| state: "connecting", | ||
| ws: connection.ws | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/wsClient.ts | ||
| var import_defineProperty = __toESM(require_defineProperty(), 1); | ||
| var import_objectSpread2 = __toESM(require_objectSpread2(), 1); | ||
| /** | ||
| * A WebSocket client for managing TRPC operations, supporting lazy initialization, | ||
| * reconnection, keep-alive, and request management. | ||
| */ | ||
| var WsClient = class { | ||
| constructor(opts) { | ||
| var _opts$retryDelayMs; | ||
| (0, import_defineProperty.default)(this, "connectionState", void 0); | ||
| (0, import_defineProperty.default)(this, "allowReconnect", false); | ||
| (0, import_defineProperty.default)(this, "requestManager", new RequestManager()); | ||
| (0, import_defineProperty.default)(this, "activeConnection", void 0); | ||
| (0, import_defineProperty.default)(this, "reconnectRetryDelay", void 0); | ||
| (0, import_defineProperty.default)(this, "inactivityTimeout", void 0); | ||
| (0, import_defineProperty.default)(this, "callbacks", void 0); | ||
| (0, import_defineProperty.default)(this, "lazyMode", void 0); | ||
| (0, import_defineProperty.default)(this, "reconnecting", null); | ||
| this.callbacks = { | ||
| onOpen: opts.onOpen, | ||
| onClose: opts.onClose, | ||
| onError: opts.onError | ||
| }; | ||
| const lazyOptions = (0, import_objectSpread2.default)((0, import_objectSpread2.default)({}, lazyDefaults), opts.lazy); | ||
| this.inactivityTimeout = new ResettableTimeout(() => { | ||
| if (this.requestManager.hasOutgoingRequests() || this.requestManager.hasPendingRequests()) { | ||
| this.inactivityTimeout.reset(); | ||
| return; | ||
| } | ||
| this.close().catch(() => null); | ||
| }, lazyOptions.closeMs); | ||
| this.activeConnection = new WsConnection({ | ||
| WebSocketPonyfill: opts.WebSocket, | ||
| urlOptions: opts, | ||
| keepAlive: (0, import_objectSpread2.default)((0, import_objectSpread2.default)({}, keepAliveDefaults), opts.keepAlive) | ||
| }); | ||
| this.activeConnection.wsObservable.subscribe({ next: (ws) => { | ||
| if (!ws) return; | ||
| this.setupWebSocketListeners(ws); | ||
| } }); | ||
| this.reconnectRetryDelay = (_opts$retryDelayMs = opts.retryDelayMs) !== null && _opts$retryDelayMs !== void 0 ? _opts$retryDelayMs : exponentialBackoff; | ||
| this.lazyMode = lazyOptions.enabled; | ||
| this.connectionState = behaviorSubject({ | ||
| type: "state", | ||
| state: lazyOptions.enabled ? "idle" : "connecting", | ||
| error: null | ||
| }); | ||
| if (!this.lazyMode) this.open().catch(() => null); | ||
| } | ||
| /** | ||
| * Opens the WebSocket connection. Handles reconnection attempts and updates | ||
| * the connection state accordingly. | ||
| */ | ||
| async open() { | ||
| var _this = this; | ||
| _this.allowReconnect = true; | ||
| if (_this.connectionState.get().state === "idle") _this.connectionState.next({ | ||
| type: "state", | ||
| state: "connecting", | ||
| error: null | ||
| }); | ||
| try { | ||
| await _this.activeConnection.open(); | ||
| } catch (error) { | ||
| _this.reconnect(new TRPCWebSocketClosedError({ | ||
| message: "Initialization error", | ||
| cause: error | ||
| })); | ||
| return _this.reconnecting; | ||
| } | ||
| } | ||
| /** | ||
| * Closes the WebSocket connection and stops managing requests. | ||
| * Ensures all outgoing and pending requests are properly finalized. | ||
| */ | ||
| async close() { | ||
| var _this2 = this; | ||
| _this2.allowReconnect = false; | ||
| _this2.inactivityTimeout.stop(); | ||
| const requestsToAwait = []; | ||
| for (const request of _this2.requestManager.getRequests()) if (request.message.method === "subscription") request.callbacks.complete(); | ||
| else if (request.state === "outgoing") request.callbacks.error(TRPCClientError.from(new TRPCWebSocketClosedError({ message: "Closed before connection was established" }))); | ||
| else requestsToAwait.push(request.end); | ||
| await Promise.all(requestsToAwait).catch(() => null); | ||
| await _this2.activeConnection.close().catch(() => null); | ||
| _this2.connectionState.next({ | ||
| type: "state", | ||
| state: "idle", | ||
| error: null | ||
| }); | ||
| } | ||
| /** | ||
| * Method to request the server. | ||
| * Handles data transformation, batching of requests, and subscription lifecycle. | ||
| * | ||
| * @param op - The operation details including id, type, path, input and signal | ||
| * @param transformer - Data transformer for serializing requests and deserializing responses | ||
| * @param lastEventId - Optional ID of the last received event for subscriptions | ||
| * | ||
| * @returns An observable that emits operation results and handles cleanup | ||
| */ | ||
| request({ op: { id, type, path, input, signal }, transformer, lastEventId }) { | ||
| return observable((observer) => { | ||
| const abort = this.batchSend({ | ||
| id, | ||
| method: type, | ||
| params: { | ||
| input: transformer.input.serialize(input), | ||
| path, | ||
| lastEventId | ||
| } | ||
| }, (0, import_objectSpread2.default)((0, import_objectSpread2.default)({}, observer), {}, { next(event) { | ||
| const transformed = transformResult(event, transformer.output); | ||
| if (!transformed.ok) { | ||
| observer.error(TRPCClientError.from(transformed.error)); | ||
| return; | ||
| } | ||
| observer.next({ result: transformed.result }); | ||
| } })); | ||
| return () => { | ||
| abort(); | ||
| if (type === "subscription" && this.activeConnection.isOpen()) this.send({ | ||
| id, | ||
| method: "subscription.stop" | ||
| }); | ||
| signal === null || signal === void 0 || signal.removeEventListener("abort", abort); | ||
| }; | ||
| }); | ||
| } | ||
| get connection() { | ||
| return backwardCompatibility(this.activeConnection); | ||
| } | ||
| reconnect(closedError) { | ||
| var _this3 = this; | ||
| this.connectionState.next({ | ||
| type: "state", | ||
| state: "connecting", | ||
| error: TRPCClientError.from(closedError) | ||
| }); | ||
| if (this.reconnecting) return; | ||
| const tryReconnect = async (attemptIndex) => { | ||
| try { | ||
| await sleep(_this3.reconnectRetryDelay(attemptIndex)); | ||
| if (_this3.allowReconnect) { | ||
| await _this3.activeConnection.close(); | ||
| await _this3.activeConnection.open(); | ||
| if (_this3.requestManager.hasPendingRequests()) _this3.send(_this3.requestManager.getPendingRequests().map(({ message }) => message)); | ||
| } | ||
| _this3.reconnecting = null; | ||
| } catch (_unused) { | ||
| await tryReconnect(attemptIndex + 1); | ||
| } | ||
| }; | ||
| this.reconnecting = tryReconnect(0); | ||
| } | ||
| setupWebSocketListeners(ws) { | ||
| var _this4 = this; | ||
| const handleCloseOrError = (cause) => { | ||
| const reqs = this.requestManager.getPendingRequests(); | ||
| for (const { message, callbacks } of reqs) { | ||
| if (message.method === "subscription") continue; | ||
| callbacks.error(TRPCClientError.from(cause !== null && cause !== void 0 ? cause : new TRPCWebSocketClosedError({ | ||
| message: "WebSocket closed", | ||
| cause | ||
| }))); | ||
| this.requestManager.delete(message.id); | ||
| } | ||
| }; | ||
| ws.addEventListener("open", () => { | ||
| run(async () => { | ||
| var _this$callbacks$onOpe, _this$callbacks; | ||
| if (_this4.lazyMode) _this4.inactivityTimeout.start(); | ||
| (_this$callbacks$onOpe = (_this$callbacks = _this4.callbacks).onOpen) === null || _this$callbacks$onOpe === void 0 || _this$callbacks$onOpe.call(_this$callbacks); | ||
| _this4.connectionState.next({ | ||
| type: "state", | ||
| state: "pending", | ||
| error: null | ||
| }); | ||
| }).catch((error) => { | ||
| ws.close(3e3); | ||
| handleCloseOrError(error); | ||
| }); | ||
| }); | ||
| ws.addEventListener("message", ({ data }) => { | ||
| this.inactivityTimeout.reset(); | ||
| if (typeof data !== "string" || ["PING", "PONG"].includes(data)) return; | ||
| const incomingMessage = JSON.parse(data); | ||
| if ("method" in incomingMessage) { | ||
| this.handleIncomingRequest(incomingMessage); | ||
| return; | ||
| } | ||
| this.handleResponseMessage(incomingMessage); | ||
| }); | ||
| ws.addEventListener("close", (event) => { | ||
| var _this$callbacks$onClo, _this$callbacks2; | ||
| handleCloseOrError(event); | ||
| (_this$callbacks$onClo = (_this$callbacks2 = this.callbacks).onClose) === null || _this$callbacks$onClo === void 0 || _this$callbacks$onClo.call(_this$callbacks2, event); | ||
| if (!this.lazyMode || this.requestManager.hasPendingSubscriptions()) this.reconnect(new TRPCWebSocketClosedError({ | ||
| message: "WebSocket closed", | ||
| cause: event | ||
| })); | ||
| }); | ||
| ws.addEventListener("error", (event) => { | ||
| var _this$callbacks$onErr, _this$callbacks3; | ||
| handleCloseOrError(event); | ||
| (_this$callbacks$onErr = (_this$callbacks3 = this.callbacks).onError) === null || _this$callbacks$onErr === void 0 || _this$callbacks$onErr.call(_this$callbacks3, event); | ||
| this.reconnect(new TRPCWebSocketClosedError({ | ||
| message: "WebSocket closed", | ||
| cause: event | ||
| })); | ||
| }); | ||
| } | ||
| handleResponseMessage(message) { | ||
| const request = this.requestManager.getPendingRequest(message.id); | ||
| if (!request) return; | ||
| request.callbacks.next(message); | ||
| let completed = true; | ||
| if ("result" in message && request.message.method === "subscription") { | ||
| if (message.result.type === "data") request.message.params.lastEventId = message.result.id; | ||
| if (message.result.type !== "stopped") completed = false; | ||
| } | ||
| if (completed) { | ||
| request.callbacks.complete(); | ||
| this.requestManager.delete(message.id); | ||
| } | ||
| } | ||
| handleIncomingRequest(message) { | ||
| if (message.method === "reconnect") this.reconnect(new TRPCWebSocketClosedError({ message: "Server requested reconnect" })); | ||
| } | ||
| /** | ||
| * Sends a message or batch of messages directly to the server. | ||
| */ | ||
| send(messageOrMessages) { | ||
| if (!this.activeConnection.isOpen()) throw new Error("Active connection is not open"); | ||
| const messages = messageOrMessages instanceof Array ? messageOrMessages : [messageOrMessages]; | ||
| this.activeConnection.ws.send(JSON.stringify(messages.length === 1 ? messages[0] : messages)); | ||
| } | ||
| /** | ||
| * Groups requests for batch sending. | ||
| * | ||
| * @returns A function to abort the batched request. | ||
| */ | ||
| batchSend(message, callbacks) { | ||
| var _this5 = this; | ||
| this.inactivityTimeout.reset(); | ||
| run(async () => { | ||
| if (!_this5.activeConnection.isOpen()) await _this5.open(); | ||
| await sleep(0); | ||
| if (!_this5.requestManager.hasOutgoingRequests()) return; | ||
| _this5.send(_this5.requestManager.flush().map(({ message: message$1 }) => message$1)); | ||
| }).catch((err) => { | ||
| this.requestManager.delete(message.id); | ||
| callbacks.error(TRPCClientError.from(err)); | ||
| }); | ||
| return this.requestManager.register(message, callbacks); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/links/wsLink/createWsClient.ts | ||
| function createWSClient(opts) { | ||
| return new WsClient(opts); | ||
| } | ||
| //#endregion | ||
| //#region src/links/wsLink/wsLink.ts | ||
| function wsLink(opts) { | ||
| const { client } = opts; | ||
| const transformer = getTransformer(opts.transformer); | ||
| return () => { | ||
| return ({ op }) => { | ||
| return observable((observer) => { | ||
| const connStateSubscription = op.type === "subscription" ? client.connectionState.subscribe({ next(result) { | ||
| observer.next({ | ||
| result, | ||
| context: op.context | ||
| }); | ||
| } }) : null; | ||
| const requestSubscription = client.request({ | ||
| op, | ||
| transformer | ||
| }).subscribe(observer); | ||
| return () => { | ||
| requestSubscription.unsubscribe(); | ||
| connStateSubscription === null || connStateSubscription === void 0 || connStateSubscription.unsubscribe(); | ||
| }; | ||
| }); | ||
| }; | ||
| }; | ||
| } | ||
| //#endregion | ||
| export { createWSClient, resultOf, wsLink }; | ||
| //# sourceMappingURL=wsLink-CatceK3c.mjs.map |
| {"version":3,"file":"wsLink-CatceK3c.mjs","names":["lazyDefaults: LazyOptions","keepAliveDefaults: KeepAliveOptions","attemptIndex: number","value: T | ((...args: TArgs) => T)","opts: { message: string; cause?: unknown }","onTimeout: () => void","timeoutMs: number","resolve: (value: T | PromiseLike<T>) => void","reject: (reason?: any) => void","urlOptions: UrlOptionsWithConnectionParams","connectionParams: CallbackOrValue<TRPCRequestInfo['connectionParams']>","message: TRPCConnectionParamsMessage","message: TRPCClientOutgoingMessage","callbacks: TCallbacks","messageId: MessageIdLike","ws: WebSocket","pingTimeout: ReturnType<typeof setTimeout> | undefined","pongTimeout: ReturnType<typeof setTimeout> | undefined","opts: WebSocketConnectionOptions","this","connection: WsConnection","opts: WebSocketClientOptions","this","requestsToAwait: Promise<void>[]","closedError: TRPCWebSocketClosedError","attemptIndex: number","ws: WebSocket","cause: unknown","message: TRPCResponseMessage","message: TRPCClientIncomingRequest","messageOrMessages: TRPCClientOutgoingMessage | TRPCClientOutgoingMessage[]","message: TRPCClientOutgoingMessage","callbacks: TCallbacks","message","opts: WebSocketClientOptions","opts: WebSocketLinkOptions<TRouter>"],"sources":["../src/links/wsLink/wsClient/options.ts","../src/links/internals/urlWithConnectionParams.ts","../src/links/wsLink/wsClient/utils.ts","../src/links/wsLink/wsClient/requestManager.ts","../src/links/wsLink/wsClient/wsConnection.ts","../src/links/wsLink/wsClient/wsClient.ts","../src/links/wsLink/createWsClient.ts","../src/links/wsLink/wsLink.ts"],"sourcesContent":["import type { UrlOptionsWithConnectionParams } from '../../internals/urlWithConnectionParams';\n\nexport interface WebSocketClientOptions extends UrlOptionsWithConnectionParams {\n /**\n * Ponyfill which WebSocket implementation to use\n */\n WebSocket?: typeof WebSocket;\n /**\n * The number of milliseconds before a reconnect is attempted.\n * @default {@link exponentialBackoff}\n */\n retryDelayMs?: (attemptIndex: number) => number;\n /**\n * Triggered when a WebSocket connection is established\n */\n onOpen?: () => void;\n /**\n * Triggered when a WebSocket connection encounters an error\n */\n onError?: (evt?: Event) => void;\n /**\n * Triggered when a WebSocket connection is closed\n */\n onClose?: (cause?: { code?: number }) => void;\n /**\n * Lazy mode will close the WebSocket automatically after a period of inactivity (no messages sent or received and no pending requests)\n */\n lazy?: {\n /**\n * Enable lazy mode\n * @default false\n */\n enabled: boolean;\n /**\n * Close the WebSocket after this many milliseconds\n * @default 0\n */\n closeMs: number;\n };\n /**\n * Send ping messages to the server and kill the connection if no pong message is returned\n */\n keepAlive?: {\n /**\n * @default false\n */\n enabled: boolean;\n /**\n * Send a ping message every this many milliseconds\n * @default 5_000\n */\n intervalMs?: number;\n /**\n * Close the WebSocket after this many milliseconds if the server does not respond\n * @default 1_000\n */\n pongTimeoutMs?: number;\n };\n}\n\n/**\n * Default options for lazy WebSocket connections.\n * Determines whether the connection should be established lazily and defines the delay before closure.\n */\nexport type LazyOptions = Required<NonNullable<WebSocketClientOptions['lazy']>>;\nexport const lazyDefaults: LazyOptions = {\n enabled: false,\n closeMs: 0,\n};\n\n/**\n * Default options for the WebSocket keep-alive mechanism.\n * Configures whether keep-alive is enabled and specifies the timeout and interval for ping-pong messages.\n */\nexport type KeepAliveOptions = Required<\n NonNullable<WebSocketClientOptions['keepAlive']>\n>;\nexport const keepAliveDefaults: KeepAliveOptions = {\n enabled: false,\n pongTimeoutMs: 1_000,\n intervalMs: 5_000,\n};\n\n/**\n * Calculates a delay for exponential backoff based on the retry attempt index.\n * The delay starts at 0 for the first attempt and doubles for each subsequent attempt,\n * capped at 30 seconds.\n */\nexport const exponentialBackoff = (attemptIndex: number) => {\n return attemptIndex === 0 ? 0 : Math.min(1000 * 2 ** attemptIndex, 30000);\n};\n","import { type TRPCRequestInfo } from '@trpc/server/http';\n\n/**\n * Get the result of a value or function that returns a value\n * It also optionally accepts typesafe arguments for the function\n */\nexport const resultOf = <T, TArgs extends any[]>(\n value: T | ((...args: TArgs) => T),\n ...args: TArgs\n): T => {\n return typeof value === 'function'\n ? (value as (...args: TArgs) => T)(...args)\n : value;\n};\n\n/**\n * A value that can be wrapped in callback\n */\nexport type CallbackOrValue<T> = T | (() => T | Promise<T>);\n\nexport interface UrlOptionsWithConnectionParams {\n /**\n * The URL to connect to (can be a function that returns a URL)\n */\n url: CallbackOrValue<string>;\n\n /**\n * Connection params that are available in `createContext()`\n * - For `wsLink`/`wsClient`, these are sent as the first message\n * - For `httpSubscriptionLink`, these are serialized as part of the URL under the `connectionParams` query\n */\n connectionParams?: CallbackOrValue<TRPCRequestInfo['connectionParams']>;\n}\n","import type {\n TRPCConnectionParamsMessage,\n TRPCRequestInfo,\n} from '@trpc/server/unstable-core-do-not-import';\nimport type {\n CallbackOrValue,\n UrlOptionsWithConnectionParams,\n} from '../../internals/urlWithConnectionParams';\nimport { resultOf } from '../../internals/urlWithConnectionParams';\n\nexport class TRPCWebSocketClosedError extends Error {\n constructor(opts: { message: string; cause?: unknown }) {\n super(opts.message, {\n cause: opts.cause,\n });\n this.name = 'TRPCWebSocketClosedError';\n Object.setPrototypeOf(this, TRPCWebSocketClosedError.prototype);\n }\n}\n\n/**\n * Utility class for managing a timeout that can be started, stopped, and reset.\n * Useful for scenarios where the timeout duration is reset dynamically based on events.\n */\nexport class ResettableTimeout {\n private timeout: ReturnType<typeof setTimeout> | undefined;\n\n constructor(\n private readonly onTimeout: () => void,\n private readonly timeoutMs: number,\n ) {}\n\n /**\n * Resets the current timeout, restarting it with the same duration.\n * Does nothing if no timeout is active.\n */\n public reset() {\n if (!this.timeout) return;\n\n clearTimeout(this.timeout);\n this.timeout = setTimeout(this.onTimeout, this.timeoutMs);\n }\n\n public start() {\n clearTimeout(this.timeout);\n this.timeout = setTimeout(this.onTimeout, this.timeoutMs);\n }\n\n public stop() {\n clearTimeout(this.timeout);\n this.timeout = undefined;\n }\n}\n\n// Ponyfill for Promise.withResolvers https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers\nexport function withResolvers<T>() {\n let resolve: (value: T | PromiseLike<T>) => void;\n let reject: (reason?: any) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return { promise, resolve: resolve!, reject: reject! };\n}\n\n/**\n * Resolves a WebSocket URL and optionally appends connection parameters.\n *\n * If connectionParams are provided, appends 'connectionParams=1' query parameter.\n */\nexport async function prepareUrl(urlOptions: UrlOptionsWithConnectionParams) {\n const url = await resultOf(urlOptions.url);\n\n if (!urlOptions.connectionParams) return url;\n\n // append `?connectionParams=1` when connection params are used\n const prefix = url.includes('?') ? '&' : '?';\n const connectionParams = `${prefix}connectionParams=1`;\n\n return url + connectionParams;\n}\n\nexport async function buildConnectionMessage(\n connectionParams: CallbackOrValue<TRPCRequestInfo['connectionParams']>,\n) {\n const message: TRPCConnectionParamsMessage = {\n method: 'connectionParams',\n data: await resultOf(connectionParams),\n };\n\n return JSON.stringify(message);\n}\n","import type { AnyTRPCRouter, inferRouterError } from '@trpc/server';\nimport type { Observer } from '@trpc/server/observable';\nimport type {\n TRPCClientOutgoingMessage,\n TRPCResponseMessage,\n} from '@trpc/server/unstable-core-do-not-import';\nimport type { TRPCClientError } from '../../../TRPCClientError';\nimport { withResolvers } from './utils';\n\nexport type TCallbacks = Observer<\n TRPCResponseMessage<unknown, inferRouterError<AnyTRPCRouter>>,\n TRPCClientError<AnyTRPCRouter>\n>;\n\ntype MessageId = string;\ntype MessageIdLike = string | number | null;\n\n/**\n * Represents a WebSocket request managed by the RequestManager.\n * Combines the network message, a utility promise (`end`) that mirrors the lifecycle\n * handled by `callbacks`, and a set of state monitoring callbacks.\n */\ninterface Request {\n message: TRPCClientOutgoingMessage;\n end: Promise<void>;\n callbacks: TCallbacks;\n}\n\n/**\n * Manages WebSocket requests, tracking their lifecycle and providing utility methods\n * for handling outgoing and pending requests.\n *\n * - **Outgoing requests**: Requests that are queued and waiting to be sent.\n * - **Pending requests**: Requests that have been sent and are in flight awaiting a response.\n * For subscriptions, multiple responses may be received until the subscription is closed.\n */\nexport class RequestManager {\n /**\n * Stores requests that are outgoing, meaning they are registered but not yet sent over the WebSocket.\n */\n private outgoingRequests = new Array<Request & { id: MessageId }>();\n\n /**\n * Stores requests that are pending (in flight), meaning they have been sent over the WebSocket\n * and are awaiting responses. For subscriptions, this includes requests\n * that may receive multiple responses.\n */\n private pendingRequests: Record<MessageId, Request> = {};\n\n /**\n * Registers a new request by adding it to the outgoing queue and setting up\n * callbacks for lifecycle events such as completion or error.\n *\n * @param message - The outgoing message to be sent.\n * @param callbacks - Callback functions to observe the request's state.\n * @returns A cleanup function to manually remove the request.\n */\n public register(message: TRPCClientOutgoingMessage, callbacks: TCallbacks) {\n const { promise: end, resolve } = withResolvers<void>();\n\n this.outgoingRequests.push({\n id: String(message.id),\n message,\n end,\n callbacks: {\n next: callbacks.next,\n complete: () => {\n callbacks.complete();\n resolve();\n },\n error: (e) => {\n callbacks.error(e);\n resolve();\n },\n },\n });\n\n return () => {\n this.delete(message.id);\n callbacks.complete();\n resolve();\n };\n }\n\n /**\n * Deletes a request from both the outgoing and pending collections, if it exists.\n */\n public delete(messageId: MessageIdLike) {\n if (messageId === null) return;\n\n this.outgoingRequests = this.outgoingRequests.filter(\n ({ id }) => id !== String(messageId),\n );\n delete this.pendingRequests[String(messageId)];\n }\n\n /**\n * Moves all outgoing requests to the pending state and clears the outgoing queue.\n *\n * The caller is expected to handle the actual sending of the requests\n * (e.g., sending them over the network) after this method is called.\n *\n * @returns The list of requests that were transitioned to the pending state.\n */\n public flush() {\n const requests = this.outgoingRequests;\n this.outgoingRequests = [];\n\n for (const request of requests) {\n this.pendingRequests[request.id] = request;\n }\n return requests;\n }\n\n /**\n * Retrieves all currently pending requests, which are in flight awaiting responses\n * or handling ongoing subscriptions.\n */\n public getPendingRequests() {\n return Object.values(this.pendingRequests);\n }\n\n /**\n * Retrieves a specific pending request by its message ID.\n */\n public getPendingRequest(messageId: MessageIdLike) {\n if (messageId === null) return null;\n\n return this.pendingRequests[String(messageId)];\n }\n\n /**\n * Retrieves all outgoing requests, which are waiting to be sent.\n */\n public getOutgoingRequests() {\n return this.outgoingRequests;\n }\n\n /**\n * Retrieves all requests, both outgoing and pending, with their respective states.\n *\n * @returns An array of all requests with their state (\"outgoing\" or \"pending\").\n */\n public getRequests() {\n return [\n ...this.getOutgoingRequests().map((request) => ({\n state: 'outgoing' as const,\n message: request.message,\n end: request.end,\n callbacks: request.callbacks,\n })),\n ...this.getPendingRequests().map((request) => ({\n state: 'pending' as const,\n message: request.message,\n end: request.end,\n callbacks: request.callbacks,\n })),\n ];\n }\n\n /**\n * Checks if there are any pending requests, including ongoing subscriptions.\n */\n public hasPendingRequests() {\n return this.getPendingRequests().length > 0;\n }\n\n /**\n * Checks if there are any pending subscriptions\n */\n public hasPendingSubscriptions() {\n return this.getPendingRequests().some(\n (request) => request.message.method === 'subscription',\n );\n }\n\n /**\n * Checks if there are any outgoing requests waiting to be sent.\n */\n public hasOutgoingRequests() {\n return this.outgoingRequests.length > 0;\n }\n}\n","import { behaviorSubject } from '@trpc/server/observable';\nimport type { UrlOptionsWithConnectionParams } from '../../internals/urlWithConnectionParams';\nimport { buildConnectionMessage, prepareUrl, withResolvers } from './utils';\n\n/**\n * Opens a WebSocket connection asynchronously and returns a promise\n * that resolves when the connection is successfully established.\n * The promise rejects if an error occurs during the connection attempt.\n */\nfunction asyncWsOpen(ws: WebSocket) {\n const { promise, resolve, reject } = withResolvers<void>();\n\n ws.addEventListener('open', () => {\n ws.removeEventListener('error', reject);\n resolve();\n });\n ws.addEventListener('error', reject);\n\n return promise;\n}\n\ninterface PingPongOptions {\n /**\n * The interval (in milliseconds) between \"PING\" messages.\n */\n intervalMs: number;\n\n /**\n * The timeout (in milliseconds) to wait for a \"PONG\" response before closing the connection.\n */\n pongTimeoutMs: number;\n}\n\n/**\n * Sets up a periodic ping-pong mechanism to keep the WebSocket connection alive.\n *\n * - Sends \"PING\" messages at regular intervals defined by `intervalMs`.\n * - If a \"PONG\" response is not received within the `pongTimeoutMs`, the WebSocket is closed.\n * - The ping timer resets upon receiving any message to maintain activity.\n * - Automatically starts the ping process when the WebSocket connection is opened.\n * - Cleans up timers when the WebSocket is closed.\n *\n * @param ws - The WebSocket instance to manage.\n * @param options - Configuration options for ping-pong intervals and timeouts.\n */\nfunction setupPingInterval(\n ws: WebSocket,\n { intervalMs, pongTimeoutMs }: PingPongOptions,\n) {\n let pingTimeout: ReturnType<typeof setTimeout> | undefined;\n let pongTimeout: ReturnType<typeof setTimeout> | undefined;\n\n function start() {\n pingTimeout = setTimeout(() => {\n ws.send('PING');\n pongTimeout = setTimeout(() => {\n ws.close();\n }, pongTimeoutMs);\n }, intervalMs);\n }\n\n function reset() {\n clearTimeout(pingTimeout);\n start();\n }\n\n function pong() {\n clearTimeout(pongTimeout);\n reset();\n }\n\n ws.addEventListener('open', start);\n ws.addEventListener('message', ({ data }) => {\n clearTimeout(pingTimeout);\n start();\n\n if (data === 'PONG') {\n pong();\n }\n });\n ws.addEventListener('close', () => {\n clearTimeout(pingTimeout);\n clearTimeout(pongTimeout);\n });\n}\n\nexport interface WebSocketConnectionOptions {\n WebSocketPonyfill?: typeof WebSocket;\n urlOptions: UrlOptionsWithConnectionParams;\n keepAlive: PingPongOptions & {\n enabled: boolean;\n };\n}\n\n/**\n * Manages a WebSocket connection with support for reconnection, keep-alive mechanisms,\n * and observable state tracking.\n */\nexport class WsConnection {\n static connectCount = 0;\n public id = ++WsConnection.connectCount;\n\n private readonly WebSocketPonyfill: typeof WebSocket;\n private readonly urlOptions: UrlOptionsWithConnectionParams;\n private readonly keepAliveOpts: WebSocketConnectionOptions['keepAlive'];\n public readonly wsObservable = behaviorSubject<WebSocket | null>(null);\n\n constructor(opts: WebSocketConnectionOptions) {\n this.WebSocketPonyfill = opts.WebSocketPonyfill ?? WebSocket;\n if (!this.WebSocketPonyfill) {\n throw new Error(\n \"No WebSocket implementation found - you probably don't want to use this on the server, but if you do you need to pass a `WebSocket`-ponyfill\",\n );\n }\n\n this.urlOptions = opts.urlOptions;\n this.keepAliveOpts = opts.keepAlive;\n }\n\n public get ws() {\n return this.wsObservable.get();\n }\n\n private set ws(ws) {\n this.wsObservable.next(ws);\n }\n\n /**\n * Checks if the WebSocket connection is open and ready to communicate.\n */\n public isOpen(): this is { ws: WebSocket } {\n return (\n !!this.ws &&\n this.ws.readyState === this.WebSocketPonyfill.OPEN &&\n !this.openPromise\n );\n }\n\n /**\n * Checks if the WebSocket connection is closed or in the process of closing.\n */\n public isClosed(): this is { ws: WebSocket } {\n return (\n !!this.ws &&\n (this.ws.readyState === this.WebSocketPonyfill.CLOSING ||\n this.ws.readyState === this.WebSocketPonyfill.CLOSED)\n );\n }\n\n /**\n * Manages the WebSocket opening process, ensuring that only one open operation\n * occurs at a time. Tracks the ongoing operation with `openPromise` to avoid\n * redundant calls and ensure proper synchronization.\n *\n * Sets up the keep-alive mechanism and necessary event listeners for the connection.\n *\n * @returns A promise that resolves once the WebSocket connection is successfully opened.\n */\n private openPromise: Promise<void> | null = null;\n public async open() {\n if (this.openPromise) return this.openPromise;\n\n this.id = ++WsConnection.connectCount;\n const wsPromise = prepareUrl(this.urlOptions).then(\n (url) => new this.WebSocketPonyfill(url),\n );\n this.openPromise = wsPromise.then(async (ws) => {\n this.ws = ws;\n\n // Setup ping listener\n ws.addEventListener('message', function ({ data }) {\n if (data === 'PING') {\n this.send('PONG');\n }\n });\n\n if (this.keepAliveOpts.enabled) {\n setupPingInterval(ws, this.keepAliveOpts);\n }\n\n ws.addEventListener('close', () => {\n if (this.ws === ws) {\n this.ws = null;\n }\n });\n\n await asyncWsOpen(ws);\n\n if (this.urlOptions.connectionParams) {\n ws.send(await buildConnectionMessage(this.urlOptions.connectionParams));\n }\n });\n\n try {\n await this.openPromise;\n } finally {\n this.openPromise = null;\n }\n }\n\n /**\n * Closes the WebSocket connection gracefully.\n * Waits for any ongoing open operation to complete before closing.\n */\n public async close() {\n try {\n await this.openPromise;\n } finally {\n this.ws?.close();\n }\n }\n}\n\n/**\n * Provides a backward-compatible representation of the connection state.\n */\nexport function backwardCompatibility(connection: WsConnection) {\n if (connection.isOpen()) {\n return {\n id: connection.id,\n state: 'open',\n ws: connection.ws,\n } as const;\n }\n\n if (connection.isClosed()) {\n return {\n id: connection.id,\n state: 'closed',\n ws: connection.ws,\n } as const;\n }\n\n if (!connection.ws) {\n return null;\n }\n\n return {\n id: connection.id,\n state: 'connecting',\n ws: connection.ws,\n } as const;\n}\n","import type { AnyTRPCRouter } from '@trpc/server';\nimport type { BehaviorSubject } from '@trpc/server/observable';\nimport { behaviorSubject, observable } from '@trpc/server/observable';\nimport type {\n CombinedDataTransformer,\n TRPCClientIncomingMessage,\n TRPCClientIncomingRequest,\n TRPCClientOutgoingMessage,\n TRPCResponseMessage,\n} from '@trpc/server/unstable-core-do-not-import';\nimport {\n run,\n sleep,\n transformResult,\n} from '@trpc/server/unstable-core-do-not-import';\nimport { TRPCClientError } from '../../../TRPCClientError';\nimport type { TRPCConnectionState } from '../../internals/subscriptions';\nimport type { Operation, OperationResultEnvelope } from '../../types';\nimport type { WebSocketClientOptions } from './options';\nimport { exponentialBackoff, keepAliveDefaults, lazyDefaults } from './options';\nimport type { TCallbacks } from './requestManager';\nimport { RequestManager } from './requestManager';\nimport { ResettableTimeout, TRPCWebSocketClosedError } from './utils';\nimport { backwardCompatibility, WsConnection } from './wsConnection';\n\n/**\n * A WebSocket client for managing TRPC operations, supporting lazy initialization,\n * reconnection, keep-alive, and request management.\n */\nexport class WsClient {\n /**\n * Observable tracking the current connection state, including errors.\n */\n public readonly connectionState: BehaviorSubject<\n TRPCConnectionState<TRPCClientError<AnyTRPCRouter>>\n >;\n\n private allowReconnect = false;\n private requestManager = new RequestManager();\n private readonly activeConnection: WsConnection;\n private readonly reconnectRetryDelay: (attemptIndex: number) => number;\n private inactivityTimeout: ResettableTimeout;\n private readonly callbacks: Pick<\n WebSocketClientOptions,\n 'onOpen' | 'onClose' | 'onError'\n >;\n private readonly lazyMode: boolean;\n\n constructor(opts: WebSocketClientOptions) {\n // Initialize callbacks, connection parameters, and options.\n this.callbacks = {\n onOpen: opts.onOpen,\n onClose: opts.onClose,\n onError: opts.onError,\n };\n\n const lazyOptions = {\n ...lazyDefaults,\n ...opts.lazy,\n };\n\n // Set up inactivity timeout for lazy connections.\n this.inactivityTimeout = new ResettableTimeout(() => {\n if (\n this.requestManager.hasOutgoingRequests() ||\n this.requestManager.hasPendingRequests()\n ) {\n this.inactivityTimeout.reset();\n return;\n }\n\n this.close().catch(() => null);\n }, lazyOptions.closeMs);\n\n // Initialize the WebSocket connection.\n this.activeConnection = new WsConnection({\n WebSocketPonyfill: opts.WebSocket,\n urlOptions: opts,\n keepAlive: {\n ...keepAliveDefaults,\n ...opts.keepAlive,\n },\n });\n this.activeConnection.wsObservable.subscribe({\n next: (ws) => {\n if (!ws) return;\n this.setupWebSocketListeners(ws);\n },\n });\n this.reconnectRetryDelay = opts.retryDelayMs ?? exponentialBackoff;\n\n this.lazyMode = lazyOptions.enabled;\n\n this.connectionState = behaviorSubject<\n TRPCConnectionState<TRPCClientError<AnyTRPCRouter>>\n >({\n type: 'state',\n state: lazyOptions.enabled ? 'idle' : 'connecting',\n error: null,\n });\n\n // Automatically open the connection if lazy mode is disabled.\n if (!this.lazyMode) {\n this.open().catch(() => null);\n }\n }\n\n /**\n * Opens the WebSocket connection. Handles reconnection attempts and updates\n * the connection state accordingly.\n */\n private async open() {\n this.allowReconnect = true;\n if (this.connectionState.get().state === 'idle') {\n this.connectionState.next({\n type: 'state',\n state: 'connecting',\n error: null,\n });\n }\n\n try {\n await this.activeConnection.open();\n } catch (error) {\n this.reconnect(\n new TRPCWebSocketClosedError({\n message: 'Initialization error',\n cause: error,\n }),\n );\n return this.reconnecting;\n }\n }\n\n /**\n * Closes the WebSocket connection and stops managing requests.\n * Ensures all outgoing and pending requests are properly finalized.\n */\n public async close() {\n this.allowReconnect = false;\n this.inactivityTimeout.stop();\n\n const requestsToAwait: Promise<void>[] = [];\n for (const request of this.requestManager.getRequests()) {\n if (request.message.method === 'subscription') {\n request.callbacks.complete();\n } else if (request.state === 'outgoing') {\n request.callbacks.error(\n TRPCClientError.from(\n new TRPCWebSocketClosedError({\n message: 'Closed before connection was established',\n }),\n ),\n );\n } else {\n requestsToAwait.push(request.end);\n }\n }\n\n await Promise.all(requestsToAwait).catch(() => null);\n await this.activeConnection.close().catch(() => null);\n\n this.connectionState.next({\n type: 'state',\n state: 'idle',\n error: null,\n });\n }\n\n /**\n * Method to request the server.\n * Handles data transformation, batching of requests, and subscription lifecycle.\n *\n * @param op - The operation details including id, type, path, input and signal\n * @param transformer - Data transformer for serializing requests and deserializing responses\n * @param lastEventId - Optional ID of the last received event for subscriptions\n *\n * @returns An observable that emits operation results and handles cleanup\n */\n public request({\n op: { id, type, path, input, signal },\n transformer,\n lastEventId,\n }: {\n op: Pick<Operation, 'id' | 'type' | 'path' | 'input' | 'signal'>;\n transformer: CombinedDataTransformer;\n lastEventId?: string;\n }) {\n return observable<\n OperationResultEnvelope<unknown, TRPCClientError<AnyTRPCRouter>>,\n TRPCClientError<AnyTRPCRouter>\n >((observer) => {\n const abort = this.batchSend(\n {\n id,\n method: type,\n params: {\n input: transformer.input.serialize(input),\n path,\n lastEventId,\n },\n },\n {\n ...observer,\n next(event) {\n const transformed = transformResult(event, transformer.output);\n\n if (!transformed.ok) {\n observer.error(TRPCClientError.from(transformed.error));\n return;\n }\n\n observer.next({\n result: transformed.result,\n });\n },\n },\n );\n\n return () => {\n abort();\n\n if (type === 'subscription' && this.activeConnection.isOpen()) {\n this.send({\n id,\n method: 'subscription.stop',\n });\n }\n\n signal?.removeEventListener('abort', abort);\n };\n });\n }\n\n public get connection() {\n return backwardCompatibility(this.activeConnection);\n }\n\n /**\n * Manages the reconnection process for the WebSocket using retry logic.\n * Ensures that only one reconnection attempt is active at a time by tracking the current\n * reconnection state in the `reconnecting` promise.\n */\n private reconnecting: Promise<void> | null = null;\n private reconnect(closedError: TRPCWebSocketClosedError) {\n this.connectionState.next({\n type: 'state',\n state: 'connecting',\n error: TRPCClientError.from(closedError),\n });\n if (this.reconnecting) return;\n\n const tryReconnect = async (attemptIndex: number) => {\n try {\n await sleep(this.reconnectRetryDelay(attemptIndex));\n if (this.allowReconnect) {\n await this.activeConnection.close();\n await this.activeConnection.open();\n\n if (this.requestManager.hasPendingRequests()) {\n this.send(\n this.requestManager\n .getPendingRequests()\n .map(({ message }) => message),\n );\n }\n }\n this.reconnecting = null;\n } catch {\n await tryReconnect(attemptIndex + 1);\n }\n };\n\n this.reconnecting = tryReconnect(0);\n }\n\n private setupWebSocketListeners(ws: WebSocket) {\n const handleCloseOrError = (cause: unknown) => {\n const reqs = this.requestManager.getPendingRequests();\n for (const { message, callbacks } of reqs) {\n if (message.method === 'subscription') continue;\n\n callbacks.error(\n TRPCClientError.from(\n cause ??\n new TRPCWebSocketClosedError({\n message: 'WebSocket closed',\n cause,\n }),\n ),\n );\n this.requestManager.delete(message.id);\n }\n };\n\n ws.addEventListener('open', () => {\n run(async () => {\n if (this.lazyMode) {\n this.inactivityTimeout.start();\n }\n\n this.callbacks.onOpen?.();\n\n this.connectionState.next({\n type: 'state',\n state: 'pending',\n error: null,\n });\n }).catch((error) => {\n ws.close(3000);\n handleCloseOrError(error);\n });\n });\n\n ws.addEventListener('message', ({ data }) => {\n this.inactivityTimeout.reset();\n\n if (typeof data !== 'string' || ['PING', 'PONG'].includes(data)) return;\n\n const incomingMessage = JSON.parse(data) as TRPCClientIncomingMessage;\n if ('method' in incomingMessage) {\n this.handleIncomingRequest(incomingMessage);\n return;\n }\n\n this.handleResponseMessage(incomingMessage);\n });\n\n ws.addEventListener('close', (event) => {\n handleCloseOrError(event);\n this.callbacks.onClose?.(event);\n\n if (!this.lazyMode || this.requestManager.hasPendingSubscriptions()) {\n this.reconnect(\n new TRPCWebSocketClosedError({\n message: 'WebSocket closed',\n cause: event,\n }),\n );\n }\n });\n\n ws.addEventListener('error', (event) => {\n handleCloseOrError(event);\n this.callbacks.onError?.(event);\n\n this.reconnect(\n new TRPCWebSocketClosedError({\n message: 'WebSocket closed',\n cause: event,\n }),\n );\n });\n }\n\n private handleResponseMessage(message: TRPCResponseMessage) {\n const request = this.requestManager.getPendingRequest(message.id);\n if (!request) return;\n\n request.callbacks.next(message);\n\n let completed = true;\n if ('result' in message && request.message.method === 'subscription') {\n if (message.result.type === 'data') {\n request.message.params.lastEventId = message.result.id;\n }\n\n if (message.result.type !== 'stopped') {\n completed = false;\n }\n }\n\n if (completed) {\n request.callbacks.complete();\n this.requestManager.delete(message.id);\n }\n }\n\n private handleIncomingRequest(message: TRPCClientIncomingRequest) {\n if (message.method === 'reconnect') {\n this.reconnect(\n new TRPCWebSocketClosedError({\n message: 'Server requested reconnect',\n }),\n );\n }\n }\n\n /**\n * Sends a message or batch of messages directly to the server.\n */\n private send(\n messageOrMessages: TRPCClientOutgoingMessage | TRPCClientOutgoingMessage[],\n ) {\n if (!this.activeConnection.isOpen()) {\n throw new Error('Active connection is not open');\n }\n\n const messages =\n messageOrMessages instanceof Array\n ? messageOrMessages\n : [messageOrMessages];\n this.activeConnection.ws.send(\n JSON.stringify(messages.length === 1 ? messages[0] : messages),\n );\n }\n\n /**\n * Groups requests for batch sending.\n *\n * @returns A function to abort the batched request.\n */\n private batchSend(message: TRPCClientOutgoingMessage, callbacks: TCallbacks) {\n this.inactivityTimeout.reset();\n\n run(async () => {\n if (!this.activeConnection.isOpen()) {\n await this.open();\n }\n await sleep(0);\n\n if (!this.requestManager.hasOutgoingRequests()) return;\n\n this.send(this.requestManager.flush().map(({ message }) => message));\n }).catch((err) => {\n this.requestManager.delete(message.id);\n callbacks.error(TRPCClientError.from(err));\n });\n\n return this.requestManager.register(message, callbacks);\n }\n}\n","import type { WebSocketClientOptions } from './wsClient/options';\nimport { WsClient } from './wsClient/wsClient';\n\nexport function createWSClient(opts: WebSocketClientOptions) {\n return new WsClient(opts);\n}\n\nexport type TRPCWebSocketClient = ReturnType<typeof createWSClient>;\n\nexport { WebSocketClientOptions };\n","import { observable } from '@trpc/server/observable';\nimport type {\n AnyRouter,\n inferClientTypes,\n} from '@trpc/server/unstable-core-do-not-import';\nimport type { TransformerOptions } from '../../unstable-internals';\nimport { getTransformer } from '../../unstable-internals';\nimport type { TRPCLink } from '../types';\nimport type {\n TRPCWebSocketClient,\n WebSocketClientOptions,\n} from './createWsClient';\nimport { createWSClient } from './createWsClient';\n\nexport type WebSocketLinkOptions<TRouter extends AnyRouter> = {\n client: TRPCWebSocketClient;\n} & TransformerOptions<inferClientTypes<TRouter>>;\n\nexport function wsLink<TRouter extends AnyRouter>(\n opts: WebSocketLinkOptions<TRouter>,\n): TRPCLink<TRouter> {\n const { client } = opts;\n const transformer = getTransformer(opts.transformer);\n return () => {\n return ({ op }) => {\n return observable((observer) => {\n const connStateSubscription =\n op.type === 'subscription'\n ? client.connectionState.subscribe({\n next(result) {\n observer.next({\n result,\n context: op.context,\n });\n },\n })\n : null;\n\n const requestSubscription = client\n .request({\n op,\n transformer,\n })\n .subscribe(observer);\n\n return () => {\n requestSubscription.unsubscribe();\n connStateSubscription?.unsubscribe();\n };\n });\n };\n };\n}\n\nexport { TRPCWebSocketClient, WebSocketClientOptions, createWSClient };\n"],"mappings":"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"} |
| import { TRPCConnectionState } from "./subscriptions.d-Dlr1nWGD.mjs"; | ||
| import { Operation, OperationResultEnvelope, TRPCClientError, TRPCLink } from "./types.d-CAt1zKAY.mjs"; | ||
| import { TransformerOptions } from "./unstable-internals.d-BOmV7EK1.mjs"; | ||
| import * as _trpc_server_observable0 from "@trpc/server/observable"; | ||
| import { BehaviorSubject } from "@trpc/server/observable"; | ||
| import { AnyRouter, CombinedDataTransformer, inferClientTypes } from "@trpc/server/unstable-core-do-not-import"; | ||
| import { AnyTRPCRouter } from "@trpc/server"; | ||
| import { TRPCRequestInfo } from "@trpc/server/http"; | ||
| //#region src/links/internals/urlWithConnectionParams.d.ts | ||
| /** | ||
| * A value that can be wrapped in callback | ||
| */ | ||
| type CallbackOrValue<T> = T | (() => T | Promise<T>); | ||
| interface UrlOptionsWithConnectionParams { | ||
| /** | ||
| * The URL to connect to (can be a function that returns a URL) | ||
| */ | ||
| url: CallbackOrValue<string>; | ||
| /** | ||
| * Connection params that are available in `createContext()` | ||
| * - For `wsLink`/`wsClient`, these are sent as the first message | ||
| * - For `httpSubscriptionLink`, these are serialized as part of the URL under the `connectionParams` query | ||
| */ | ||
| connectionParams?: CallbackOrValue<TRPCRequestInfo['connectionParams']>; | ||
| } | ||
| //# sourceMappingURL=urlWithConnectionParams.d.ts.map | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/options.d.ts | ||
| interface WebSocketClientOptions extends UrlOptionsWithConnectionParams { | ||
| /** | ||
| * Ponyfill which WebSocket implementation to use | ||
| */ | ||
| WebSocket?: typeof WebSocket; | ||
| /** | ||
| * The number of milliseconds before a reconnect is attempted. | ||
| * @default {@link exponentialBackoff} | ||
| */ | ||
| retryDelayMs?: (attemptIndex: number) => number; | ||
| /** | ||
| * Triggered when a WebSocket connection is established | ||
| */ | ||
| onOpen?: () => void; | ||
| /** | ||
| * Triggered when a WebSocket connection encounters an error | ||
| */ | ||
| onError?: (evt?: Event) => void; | ||
| /** | ||
| * Triggered when a WebSocket connection is closed | ||
| */ | ||
| onClose?: (cause?: { | ||
| code?: number; | ||
| }) => void; | ||
| /** | ||
| * Lazy mode will close the WebSocket automatically after a period of inactivity (no messages sent or received and no pending requests) | ||
| */ | ||
| lazy?: { | ||
| /** | ||
| * Enable lazy mode | ||
| * @default false | ||
| */ | ||
| enabled: boolean; | ||
| /** | ||
| * Close the WebSocket after this many milliseconds | ||
| * @default 0 | ||
| */ | ||
| closeMs: number; | ||
| }; | ||
| /** | ||
| * Send ping messages to the server and kill the connection if no pong message is returned | ||
| */ | ||
| keepAlive?: { | ||
| /** | ||
| * @default false | ||
| */ | ||
| enabled: boolean; | ||
| /** | ||
| * Send a ping message every this many milliseconds | ||
| * @default 5_000 | ||
| */ | ||
| intervalMs?: number; | ||
| /** | ||
| * Close the WebSocket after this many milliseconds if the server does not respond | ||
| * @default 1_000 | ||
| */ | ||
| pongTimeoutMs?: number; | ||
| }; | ||
| } | ||
| /** | ||
| * Default options for lazy WebSocket connections. | ||
| * Determines whether the connection should be established lazily and defines the delay before closure. | ||
| */ | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/wsClient.d.ts | ||
| /** | ||
| * A WebSocket client for managing TRPC operations, supporting lazy initialization, | ||
| * reconnection, keep-alive, and request management. | ||
| */ | ||
| declare class WsClient { | ||
| /** | ||
| * Observable tracking the current connection state, including errors. | ||
| */ | ||
| readonly connectionState: BehaviorSubject<TRPCConnectionState<TRPCClientError<AnyTRPCRouter>>>; | ||
| private allowReconnect; | ||
| private requestManager; | ||
| private readonly activeConnection; | ||
| private readonly reconnectRetryDelay; | ||
| private inactivityTimeout; | ||
| private readonly callbacks; | ||
| private readonly lazyMode; | ||
| constructor(opts: WebSocketClientOptions); | ||
| /** | ||
| * Opens the WebSocket connection. Handles reconnection attempts and updates | ||
| * the connection state accordingly. | ||
| */ | ||
| private open; | ||
| /** | ||
| * Closes the WebSocket connection and stops managing requests. | ||
| * Ensures all outgoing and pending requests are properly finalized. | ||
| */ | ||
| close(): Promise<void>; | ||
| /** | ||
| * Method to request the server. | ||
| * Handles data transformation, batching of requests, and subscription lifecycle. | ||
| * | ||
| * @param op - The operation details including id, type, path, input and signal | ||
| * @param transformer - Data transformer for serializing requests and deserializing responses | ||
| * @param lastEventId - Optional ID of the last received event for subscriptions | ||
| * | ||
| * @returns An observable that emits operation results and handles cleanup | ||
| */ | ||
| request({ | ||
| op: { | ||
| id, | ||
| type, | ||
| path, | ||
| input, | ||
| signal | ||
| }, | ||
| transformer, | ||
| lastEventId | ||
| }: { | ||
| op: Pick<Operation, 'id' | 'type' | 'path' | 'input' | 'signal'>; | ||
| transformer: CombinedDataTransformer; | ||
| lastEventId?: string; | ||
| }): _trpc_server_observable0.Observable<OperationResultEnvelope<unknown, TRPCClientError<AnyTRPCRouter>>, TRPCClientError<AnyTRPCRouter>>; | ||
| get connection(): { | ||
| readonly id: number; | ||
| readonly state: "open"; | ||
| readonly ws: WebSocket; | ||
| } | { | ||
| readonly id: number; | ||
| readonly state: "closed"; | ||
| readonly ws: WebSocket; | ||
| } | { | ||
| readonly id: number; | ||
| readonly state: "connecting"; | ||
| readonly ws: WebSocket; | ||
| } | null; | ||
| /** | ||
| * Manages the reconnection process for the WebSocket using retry logic. | ||
| * Ensures that only one reconnection attempt is active at a time by tracking the current | ||
| * reconnection state in the `reconnecting` promise. | ||
| */ | ||
| private reconnecting; | ||
| private reconnect; | ||
| private setupWebSocketListeners; | ||
| private handleResponseMessage; | ||
| private handleIncomingRequest; | ||
| /** | ||
| * Sends a message or batch of messages directly to the server. | ||
| */ | ||
| private send; | ||
| /** | ||
| * Groups requests for batch sending. | ||
| * | ||
| * @returns A function to abort the batched request. | ||
| */ | ||
| private batchSend; | ||
| } | ||
| //# sourceMappingURL=wsClient.d.ts.map | ||
| //#endregion | ||
| //#region src/links/wsLink/createWsClient.d.ts | ||
| declare function createWSClient(opts: WebSocketClientOptions): WsClient; | ||
| type TRPCWebSocketClient = ReturnType<typeof createWSClient>; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsLink.d.ts | ||
| type WebSocketLinkOptions<TRouter extends AnyRouter> = { | ||
| client: TRPCWebSocketClient; | ||
| } & TransformerOptions<inferClientTypes<TRouter>>; | ||
| declare function wsLink<TRouter extends AnyRouter>(opts: WebSocketLinkOptions<TRouter>): TRPCLink<TRouter>; | ||
| //#endregion | ||
| export { TRPCWebSocketClient, UrlOptionsWithConnectionParams, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, wsLink }; | ||
| //# sourceMappingURL=wsLink.d-Bssh2HIQ.d.mts.map |
| {"version":3,"file":"wsLink.d-Bssh2HIQ.d.mts","names":[],"sources":["../src/links/internals/urlWithConnectionParams.ts","../src/links/wsLink/wsClient/options.ts","../src/links/wsLink/wsClient/wsClient.ts","../src/links/wsLink/createWsClient.ts","../src/links/wsLink/wsLink.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;KAkBY,qBAAqB,WAAW,IAAI,QAAQ;AAA5C,UAEK,8BAAA,CAFU;EAAA;;;EAAkB,GAAW,EAMjD,eANiD,CAAA,MAAA,CAAA;EAAC;AAAF;AAEvD;;;EAIsB,gBAOe,CAAA,EAAhB,eAAgB,CAAA,eAAA,CAAA,kBAAA,CAAA,CAAA;;AAAD;;;UC7BnB,sBAAA,SAA+B;;;;qBAI3B;;;;;EDYT,YAAA,CAAA,EAAA,CAAA,YAAe,EAAA,MAAA,EAAA,GAAA,MAAA;EAAA;;;EAAkB,MAAW,CAAA,EAAA,GAAA,GAAA,IAAA;EAAC;AAAF;AAEvD;EAA+C,OAAA,CAAA,EAAA,CAAA,GAAA,CAAA,ECD5B,KDC4B,EAAA,GAAA,IAAA;EAAA;;;EAWX,OAAA,CAAA,EAAA,CAAA,MAAA,EAAA;;;;AC7BpC;;EAAwC,IAInB,CAAA,EAAA;IAaF;;AAjB2D;;;;AC2B9E;;;IAKwB,OAAA,EAAA,MAAA;EAAe,CAAA;EAAhB;;;EAwGH,SA0CV,CAAA,EAAA;IAAI;;;IAAmB,OAAA,EAAA,OAAA;IAC7B;;;;IAIa,UAAA,CAAA,EAAA,MAAA;IAEd;;;;IAAA,aAAA,CAAA,EAAA,MAAA;EAAA,CAAA;;;;;;;;;;;AFzKH;AAA2B,cEWd,QAAA,CFXc;EAAA;;;EAA8B,SAAT,eAAA,EEeb,eFfa,CEgB5C,mBFhB4C,CEgBxB,eFhBwB,CEgBR,aFhBQ,CAAA,CAAA,CAAA;EAAO,QAAA,cAAA;EAEtC,QAAA,cAAA;EAA8B,iBAAA,gBAAA;EAAA,iBAIxC,mBAAA;EAAe,QAOe,iBAAA;EAAe,iBAA/B,SAAA;EAAe,iBAAA,QAAA;oBEiBhB;;;AD9CpB;;EAAwC,QAInB,IAAA;EAAS;;AAJgD;;WCwI1D;;AA7GpB;;;;;;;;;EAuJY,OAAE,CAAA;IAAA,EAAA,EAAA;MAAA,EAAA;MAAA,IAAA;MAAA,IAAA;MAAA,KAAA;MAAA;IAAA,CAAA;IAAA,WAAA;IAAA;EAAyB,CAAzB,EAAA;IAAM,EAAA,EAIZ,IAJY,CAIP,SAJO,EAAA,IAAA,GAAA,MAAA,GAAA,MAAA,GAAA,OAAA,GAAA,QAAA,CAAA;IAAM,WAAA,EAKT,uBALS;IAAO,WAAA,CAAA,EAAA,MAAA;EAAM,CAAA,CAAA,EAOpC,wBAAA,CAAA,UANC,CAMD,uBANC,CAAA,OAAA,EAMD,eANC,CAMD,aANC,CAAA,CAAA,EAMD,eANC,CAMD,aANC,CAAA,CAAA;EAAW,IACX,UAAA,CAAA,CAAA,EAAA;IAES,SAAA,EAAA,EAAA,MAAA;IAAL,SAAA,KAAA,EAAA,MAAA;IACS,SAAA,EAAA,EAEd,SAFc;EAAuB,CAAA,GAErC;IAAA,SAAA,EAAA,EAAA,MAAA;IAAA,SAAA,KAAA,EAAA,QAAA;IAAA,SAAA,EAAA,WAAA;EAAA,CAAA,GAAA;IAAA,SAAA,EAAA,EAAA,MAAA;IAAA,SAAA,KAAA,EAAA,YAAA;;;;;;;ACxLH;EAA8B,QAAA,YAAA;EAAA,QAAO,SAAA;EAAsB,QAAA,uBAAA;EAAA,QAAA,qBAAA;EAI/C,QAAA,qBAAmB;EAAA;;;EAAa,QAAA,IAAA;;;;ACO5C;;EAAgC,QAAiB,SAAA;;;;;iBDXjC,cAAA,OAAqB,yBAAsB;KAI/C,mBAAA,GAAsB,kBAAkB;;;KCOxC,qCAAqC;UACvC;IACN,mBAAmB,iBAAiB;iBAExB,uBAAuB,iBAC/B,qBAAqB,WAC1B,SAAS"} |
| import { TRPCConnectionState } from "./subscriptions.d-Ciljg_dH.cjs"; | ||
| import { Operation, OperationResultEnvelope, TRPCClientError, TRPCLink } from "./types.d-B2PuQAdV.cjs"; | ||
| import { TransformerOptions } from "./unstable-internals.d-kWsZTlQq.cjs"; | ||
| import { AnyRouter, CombinedDataTransformer, inferClientTypes } from "@trpc/server/unstable-core-do-not-import"; | ||
| import * as _trpc_server_observable0 from "@trpc/server/observable"; | ||
| import { BehaviorSubject } from "@trpc/server/observable"; | ||
| import { AnyTRPCRouter } from "@trpc/server"; | ||
| import { TRPCRequestInfo } from "@trpc/server/http"; | ||
| //#region src/links/internals/urlWithConnectionParams.d.ts | ||
| /** | ||
| * A value that can be wrapped in callback | ||
| */ | ||
| type CallbackOrValue<T> = T | (() => T | Promise<T>); | ||
| interface UrlOptionsWithConnectionParams { | ||
| /** | ||
| * The URL to connect to (can be a function that returns a URL) | ||
| */ | ||
| url: CallbackOrValue<string>; | ||
| /** | ||
| * Connection params that are available in `createContext()` | ||
| * - For `wsLink`/`wsClient`, these are sent as the first message | ||
| * - For `httpSubscriptionLink`, these are serialized as part of the URL under the `connectionParams` query | ||
| */ | ||
| connectionParams?: CallbackOrValue<TRPCRequestInfo['connectionParams']>; | ||
| } | ||
| //# sourceMappingURL=urlWithConnectionParams.d.ts.map | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/options.d.ts | ||
| interface WebSocketClientOptions extends UrlOptionsWithConnectionParams { | ||
| /** | ||
| * Ponyfill which WebSocket implementation to use | ||
| */ | ||
| WebSocket?: typeof WebSocket; | ||
| /** | ||
| * The number of milliseconds before a reconnect is attempted. | ||
| * @default {@link exponentialBackoff} | ||
| */ | ||
| retryDelayMs?: (attemptIndex: number) => number; | ||
| /** | ||
| * Triggered when a WebSocket connection is established | ||
| */ | ||
| onOpen?: () => void; | ||
| /** | ||
| * Triggered when a WebSocket connection encounters an error | ||
| */ | ||
| onError?: (evt?: Event) => void; | ||
| /** | ||
| * Triggered when a WebSocket connection is closed | ||
| */ | ||
| onClose?: (cause?: { | ||
| code?: number; | ||
| }) => void; | ||
| /** | ||
| * Lazy mode will close the WebSocket automatically after a period of inactivity (no messages sent or received and no pending requests) | ||
| */ | ||
| lazy?: { | ||
| /** | ||
| * Enable lazy mode | ||
| * @default false | ||
| */ | ||
| enabled: boolean; | ||
| /** | ||
| * Close the WebSocket after this many milliseconds | ||
| * @default 0 | ||
| */ | ||
| closeMs: number; | ||
| }; | ||
| /** | ||
| * Send ping messages to the server and kill the connection if no pong message is returned | ||
| */ | ||
| keepAlive?: { | ||
| /** | ||
| * @default false | ||
| */ | ||
| enabled: boolean; | ||
| /** | ||
| * Send a ping message every this many milliseconds | ||
| * @default 5_000 | ||
| */ | ||
| intervalMs?: number; | ||
| /** | ||
| * Close the WebSocket after this many milliseconds if the server does not respond | ||
| * @default 1_000 | ||
| */ | ||
| pongTimeoutMs?: number; | ||
| }; | ||
| } | ||
| /** | ||
| * Default options for lazy WebSocket connections. | ||
| * Determines whether the connection should be established lazily and defines the delay before closure. | ||
| */ | ||
| //#endregion | ||
| //#region src/links/wsLink/wsClient/wsClient.d.ts | ||
| /** | ||
| * A WebSocket client for managing TRPC operations, supporting lazy initialization, | ||
| * reconnection, keep-alive, and request management. | ||
| */ | ||
| declare class WsClient { | ||
| /** | ||
| * Observable tracking the current connection state, including errors. | ||
| */ | ||
| readonly connectionState: BehaviorSubject<TRPCConnectionState<TRPCClientError<AnyTRPCRouter>>>; | ||
| private allowReconnect; | ||
| private requestManager; | ||
| private readonly activeConnection; | ||
| private readonly reconnectRetryDelay; | ||
| private inactivityTimeout; | ||
| private readonly callbacks; | ||
| private readonly lazyMode; | ||
| constructor(opts: WebSocketClientOptions); | ||
| /** | ||
| * Opens the WebSocket connection. Handles reconnection attempts and updates | ||
| * the connection state accordingly. | ||
| */ | ||
| private open; | ||
| /** | ||
| * Closes the WebSocket connection and stops managing requests. | ||
| * Ensures all outgoing and pending requests are properly finalized. | ||
| */ | ||
| close(): Promise<void>; | ||
| /** | ||
| * Method to request the server. | ||
| * Handles data transformation, batching of requests, and subscription lifecycle. | ||
| * | ||
| * @param op - The operation details including id, type, path, input and signal | ||
| * @param transformer - Data transformer for serializing requests and deserializing responses | ||
| * @param lastEventId - Optional ID of the last received event for subscriptions | ||
| * | ||
| * @returns An observable that emits operation results and handles cleanup | ||
| */ | ||
| request({ | ||
| op: { | ||
| id, | ||
| type, | ||
| path, | ||
| input, | ||
| signal | ||
| }, | ||
| transformer, | ||
| lastEventId | ||
| }: { | ||
| op: Pick<Operation, 'id' | 'type' | 'path' | 'input' | 'signal'>; | ||
| transformer: CombinedDataTransformer; | ||
| lastEventId?: string; | ||
| }): _trpc_server_observable0.Observable<OperationResultEnvelope<unknown, TRPCClientError<AnyTRPCRouter>>, TRPCClientError<AnyTRPCRouter>>; | ||
| get connection(): { | ||
| readonly id: number; | ||
| readonly state: "open"; | ||
| readonly ws: WebSocket; | ||
| } | { | ||
| readonly id: number; | ||
| readonly state: "closed"; | ||
| readonly ws: WebSocket; | ||
| } | { | ||
| readonly id: number; | ||
| readonly state: "connecting"; | ||
| readonly ws: WebSocket; | ||
| } | null; | ||
| /** | ||
| * Manages the reconnection process for the WebSocket using retry logic. | ||
| * Ensures that only one reconnection attempt is active at a time by tracking the current | ||
| * reconnection state in the `reconnecting` promise. | ||
| */ | ||
| private reconnecting; | ||
| private reconnect; | ||
| private setupWebSocketListeners; | ||
| private handleResponseMessage; | ||
| private handleIncomingRequest; | ||
| /** | ||
| * Sends a message or batch of messages directly to the server. | ||
| */ | ||
| private send; | ||
| /** | ||
| * Groups requests for batch sending. | ||
| * | ||
| * @returns A function to abort the batched request. | ||
| */ | ||
| private batchSend; | ||
| } | ||
| //# sourceMappingURL=wsClient.d.ts.map | ||
| //#endregion | ||
| //#region src/links/wsLink/createWsClient.d.ts | ||
| declare function createWSClient(opts: WebSocketClientOptions): WsClient; | ||
| type TRPCWebSocketClient = ReturnType<typeof createWSClient>; | ||
| //#endregion | ||
| //#region src/links/wsLink/wsLink.d.ts | ||
| type WebSocketLinkOptions<TRouter extends AnyRouter> = { | ||
| client: TRPCWebSocketClient; | ||
| } & TransformerOptions<inferClientTypes<TRouter>>; | ||
| declare function wsLink<TRouter extends AnyRouter>(opts: WebSocketLinkOptions<TRouter>): TRPCLink<TRouter>; | ||
| //#endregion | ||
| export { TRPCWebSocketClient, UrlOptionsWithConnectionParams, WebSocketClientOptions, WebSocketLinkOptions, createWSClient, wsLink }; | ||
| //# sourceMappingURL=wsLink.d-Dtb4zdet.d.cts.map |
| {"version":3,"file":"wsLink.d-Dtb4zdet.d.cts","names":[],"sources":["../src/links/internals/urlWithConnectionParams.ts","../src/links/wsLink/wsClient/options.ts","../src/links/wsLink/wsClient/wsClient.ts","../src/links/wsLink/createWsClient.ts","../src/links/wsLink/wsLink.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;KAkBY,qBAAqB,WAAW,IAAI,QAAQ;AAA5C,UAEK,8BAAA,CAFU;EAAA;;;EAAkB,GAAW,EAMjD,eANiD,CAAA,MAAA,CAAA;EAAC;AAAF;AAEvD;;;EAIsB,gBAOe,CAAA,EAAhB,eAAgB,CAAA,eAAA,CAAA,kBAAA,CAAA,CAAA;;AAAD;;;UC7BnB,sBAAA,SAA+B;;;;qBAI3B;;;;;EDYT,YAAA,CAAA,EAAA,CAAA,YAAe,EAAA,MAAA,EAAA,GAAA,MAAA;EAAA;;;EAAkB,MAAW,CAAA,EAAA,GAAA,GAAA,IAAA;EAAC;AAAF;AAEvD;EAA+C,OAAA,CAAA,EAAA,CAAA,GAAA,CAAA,ECD5B,KDC4B,EAAA,GAAA,IAAA;EAAA;;;EAWX,OAAA,CAAA,EAAA,CAAA,MAAA,EAAA;;;;AC7BpC;;EAAwC,IAInB,CAAA,EAAA;IAaF;;AAjB2D;;;;AC2B9E;;;IAKwB,OAAA,EAAA,MAAA;EAAe,CAAA;EAAhB;;;EAwGH,SA0CV,CAAA,EAAA;IAAI;;;IAAmB,OAAA,EAAA,OAAA;IAC7B;;;;IAIa,UAAA,CAAA,EAAA,MAAA;IAEd;;;;IAAA,aAAA,CAAA,EAAA,MAAA;EAAA,CAAA;;;;;;;;;;;AFzKH;AAA2B,cEWd,QAAA,CFXc;EAAA;;;EAA8B,SAAT,eAAA,EEeb,eFfa,CEgB5C,mBFhB4C,CEgBxB,eFhBwB,CEgBR,aFhBQ,CAAA,CAAA,CAAA;EAAO,QAAA,cAAA;EAEtC,QAAA,cAAA;EAA8B,iBAAA,gBAAA;EAAA,iBAIxC,mBAAA;EAAe,QAOe,iBAAA;EAAe,iBAA/B,SAAA;EAAe,iBAAA,QAAA;oBEiBhB;;;AD9CpB;;EAAwC,QAInB,IAAA;EAAS;;AAJgD;;WCwI1D;;AA7GpB;;;;;;;;;EAuJY,OAAE,CAAA;IAAA,EAAA,EAAA;MAAA,EAAA;MAAA,IAAA;MAAA,IAAA;MAAA,KAAA;MAAA;IAAA,CAAA;IAAA,WAAA;IAAA;EAAyB,CAAzB,EAAA;IAAM,EAAA,EAIZ,IAJY,CAIP,SAJO,EAAA,IAAA,GAAA,MAAA,GAAA,MAAA,GAAA,OAAA,GAAA,QAAA,CAAA;IAAM,WAAA,EAKT,uBALS;IAAO,WAAA,CAAA,EAAA,MAAA;EAAM,CAAA,CAAA,EAOpC,wBAAA,CAAA,UANC,CAMD,uBANC,CAAA,OAAA,EAMD,eANC,CAMD,aANC,CAAA,CAAA,EAMD,eANC,CAMD,aANC,CAAA,CAAA;EAAW,IACX,UAAA,CAAA,CAAA,EAAA;IAES,SAAA,EAAA,EAAA,MAAA;IAAL,SAAA,KAAA,EAAA,MAAA;IACS,SAAA,EAAA,EAEd,SAFc;EAAuB,CAAA,GAErC;IAAA,SAAA,EAAA,EAAA,MAAA;IAAA,SAAA,KAAA,EAAA,QAAA;IAAA,SAAA,EAAA,WAAA;EAAA,CAAA,GAAA;IAAA,SAAA,EAAA,EAAA,MAAA;IAAA,SAAA,KAAA,EAAA,YAAA;;;;;;;ACxLH;EAA8B,QAAA,YAAA;EAAA,QAAO,SAAA;EAAsB,QAAA,uBAAA;EAAA,QAAA,qBAAA;EAI/C,QAAA,qBAAmB;EAAA;;;EAAa,QAAA,IAAA;;;;ACO5C;;EAAgC,QAAiB,SAAA;;;;;iBDXjC,cAAA,OAAqB,yBAAsB;KAI/C,mBAAA,GAAsB,kBAAkB;;;KCOxC,qCAAqC;UACvC;IACN,mBAAmB,iBAAiB;iBAExB,uBAAuB,iBAC/B,qBAAqB,WAC1B,SAAS"} |
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
559544
0.96%141
0.71%8544
0.83%