@trpc/client 11.0.0-rc.772 → 11.0.0-rc.781

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/bundle-analysis.json +132 -39
  2. package/dist/index.js +3 -2
  3. package/dist/index.mjs +2 -1
  4. package/dist/links/wsLink/createWsClient.d.ts +6 -0
  5. package/dist/links/wsLink/createWsClient.d.ts.map +1 -0
  6. package/dist/links/wsLink/createWsClient.js +9 -0
  7. package/dist/links/wsLink/createWsClient.mjs +7 -0
  8. package/dist/links/wsLink/wsClient/options.d.ts +79 -0
  9. package/dist/links/wsLink/wsClient/options.d.ts.map +1 -0
  10. package/dist/links/wsLink/wsClient/options.js +22 -0
  11. package/dist/links/wsLink/wsClient/options.mjs +18 -0
  12. package/dist/links/wsLink/wsClient/requestManager.d.ts +102 -0
  13. package/dist/links/wsLink/wsClient/requestManager.d.ts.map +1 -0
  14. package/dist/links/wsLink/wsClient/requestManager.js +138 -0
  15. package/dist/links/wsLink/wsClient/requestManager.mjs +136 -0
  16. package/dist/links/wsLink/wsClient/utils.d.ts +38 -0
  17. package/dist/links/wsLink/wsClient/utils.d.ts.map +1 -0
  18. package/dist/links/wsLink/wsClient/utils.js +94 -0
  19. package/dist/links/wsLink/wsClient/utils.mjs +88 -0
  20. package/dist/links/wsLink/wsClient/wsClient.d.ts +85 -0
  21. package/dist/links/wsLink/wsClient/wsClient.d.ts.map +1 -0
  22. package/dist/links/wsLink/wsClient/wsClient.js +331 -0
  23. package/dist/links/wsLink/wsClient/wsClient.mjs +329 -0
  24. package/dist/links/wsLink/wsClient/wsConnection.d.ts +79 -0
  25. package/dist/links/wsLink/wsClient/wsConnection.d.ts.map +1 -0
  26. package/dist/links/wsLink/wsClient/wsConnection.js +181 -0
  27. package/dist/links/wsLink/wsClient/wsConnection.mjs +178 -0
  28. package/dist/links/wsLink/wsLink.d.ts +11 -0
  29. package/dist/links/wsLink/wsLink.d.ts.map +1 -0
  30. package/dist/links/wsLink/wsLink.js +35 -0
  31. package/dist/links/wsLink/wsLink.mjs +32 -0
  32. package/dist/links.d.ts +1 -1
  33. package/dist/links.d.ts.map +1 -1
  34. package/links/wsLink/wsLink/index.d.ts +1 -0
  35. package/links/wsLink/wsLink/index.js +1 -0
  36. package/package.json +8 -8
  37. package/src/links/wsLink/createWsClient.ts +10 -0
  38. package/src/links/wsLink/wsClient/options.ts +91 -0
  39. package/src/links/wsLink/wsClient/requestManager.ts +174 -0
  40. package/src/links/wsLink/wsClient/utils.ts +94 -0
  41. package/src/links/wsLink/wsClient/wsClient.ts +441 -0
  42. package/src/links/wsLink/wsClient/wsConnection.ts +230 -0
  43. package/src/links/wsLink/wsLink.ts +55 -0
  44. package/src/links.ts +1 -1
  45. package/dist/links/wsLink.d.ts +0 -125
  46. package/dist/links/wsLink.d.ts.map +0 -1
  47. package/dist/links/wsLink.js +0 -498
  48. package/dist/links/wsLink.mjs +0 -495
  49. package/links/wsLink/index.d.ts +0 -1
  50. package/links/wsLink/index.js +0 -1
  51. package/src/links/wsLink.ts +0 -737
@@ -0,0 +1,331 @@
1
+ 'use strict';
2
+
3
+ var observable = require('@trpc/server/observable');
4
+ var unstableCoreDoNotImport = require('@trpc/server/unstable-core-do-not-import');
5
+ var TRPCClientError = require('../../../TRPCClientError.js');
6
+ var options = require('./options.js');
7
+ var requestManager = require('./requestManager.js');
8
+ var utils = require('./utils.js');
9
+ var wsConnection = require('./wsConnection.js');
10
+
11
+ function _define_property(obj, key, value) {
12
+ if (key in obj) {
13
+ Object.defineProperty(obj, key, {
14
+ value: value,
15
+ enumerable: true,
16
+ configurable: true,
17
+ writable: true
18
+ });
19
+ } else {
20
+ obj[key] = value;
21
+ }
22
+ return obj;
23
+ }
24
+ /**
25
+ * A WebSocket client for managing TRPC operations, supporting lazy initialization,
26
+ * reconnection, keep-alive, and request management.
27
+ */ class WsClient {
28
+ /**
29
+ * Opens the WebSocket connection. Handles reconnection attempts and updates
30
+ * the connection state accordingly.
31
+ */ async open() {
32
+ this.allowReconnect = true;
33
+ if (this.connectionState.get().state !== 'connecting') {
34
+ this.connectionState.next({
35
+ type: 'state',
36
+ state: 'connecting',
37
+ error: null
38
+ });
39
+ }
40
+ try {
41
+ await this.activeConnection.open();
42
+ } catch (error) {
43
+ this.reconnect(new utils.TRPCWebSocketClosedError({
44
+ message: 'Initialization error',
45
+ cause: error
46
+ }));
47
+ return this.reconnecting;
48
+ }
49
+ }
50
+ /**
51
+ * Closes the WebSocket connection and stops managing requests.
52
+ * Ensures all outgoing and pending requests are properly finalized.
53
+ */ async close() {
54
+ this.allowReconnect = false;
55
+ this.inactivityTimeout.stop();
56
+ const requestsToAwait = [];
57
+ for (const request of this.requestManager.getRequests()){
58
+ if (request.message.method === 'subscription') {
59
+ request.callbacks.complete();
60
+ } else if (request.state === 'outgoing') {
61
+ request.callbacks.error(TRPCClientError.TRPCClientError.from(new utils.TRPCWebSocketClosedError({
62
+ message: 'Closed before connection was established'
63
+ })));
64
+ } else {
65
+ requestsToAwait.push(request.end);
66
+ }
67
+ }
68
+ await Promise.all(requestsToAwait).catch(()=>null);
69
+ await this.activeConnection.close().catch(()=>null);
70
+ this.connectionState.next({
71
+ type: 'state',
72
+ state: 'idle',
73
+ error: null
74
+ });
75
+ }
76
+ /**
77
+ * Method to request the server.
78
+ * Handles data transformation, batching of requests, and subscription lifecycle.
79
+ *
80
+ * @param op - The operation details including id, type, path, input and signal
81
+ * @param transformer - Data transformer for serializing requests and deserializing responses
82
+ * @param lastEventId - Optional ID of the last received event for subscriptions
83
+ *
84
+ * @returns An observable that emits operation results and handles cleanup
85
+ */ request({ op: { id, type, path, input, signal }, transformer, lastEventId }) {
86
+ return observable.observable((observer)=>{
87
+ const abort = this.batchSend({
88
+ id,
89
+ method: type,
90
+ params: {
91
+ input: transformer.input.serialize(input),
92
+ path,
93
+ lastEventId
94
+ }
95
+ }, {
96
+ ...observer,
97
+ next (event) {
98
+ const transformed = unstableCoreDoNotImport.transformResult(event, transformer.output);
99
+ if (!transformed.ok) {
100
+ observer.error(TRPCClientError.TRPCClientError.from(transformed.error));
101
+ return;
102
+ }
103
+ observer.next({
104
+ result: transformed.result
105
+ });
106
+ }
107
+ });
108
+ return ()=>{
109
+ abort();
110
+ if (type === 'subscription' && this.activeConnection.isOpen()) {
111
+ this.send({
112
+ id,
113
+ method: 'subscription.stop'
114
+ });
115
+ }
116
+ signal?.removeEventListener('abort', abort);
117
+ };
118
+ });
119
+ }
120
+ get connection() {
121
+ return wsConnection.backwardCompatibility(this.activeConnection);
122
+ }
123
+ reconnect(closedError) {
124
+ this.connectionState.next({
125
+ type: 'state',
126
+ state: 'connecting',
127
+ error: TRPCClientError.TRPCClientError.from(closedError)
128
+ });
129
+ if (this.reconnecting) return;
130
+ const tryReconnect = async (attemptIndex)=>{
131
+ try {
132
+ await unstableCoreDoNotImport.sleep(this.reconnectRetryDelay(attemptIndex));
133
+ if (this.allowReconnect) {
134
+ await this.activeConnection.close();
135
+ await this.activeConnection.open();
136
+ }
137
+ this.reconnecting = null;
138
+ } catch {
139
+ await tryReconnect(attemptIndex + 1);
140
+ }
141
+ };
142
+ this.reconnecting = tryReconnect(0);
143
+ }
144
+ setupWebSocketListeners(ws) {
145
+ const handleCloseOrError = (cause)=>{
146
+ const reqs = this.requestManager.getPendingRequests();
147
+ for (const { message, callbacks } of reqs){
148
+ if (message.method === 'subscription') continue;
149
+ callbacks.error(TRPCClientError.TRPCClientError.from(cause ?? new utils.TRPCWebSocketClosedError({
150
+ message: 'WebSocket closed',
151
+ cause
152
+ })));
153
+ this.requestManager.delete(message.id);
154
+ }
155
+ };
156
+ ws.addEventListener('open', ()=>{
157
+ unstableCoreDoNotImport.run(async ()=>{
158
+ if (this.lazyMode) {
159
+ this.inactivityTimeout.start();
160
+ }
161
+ if (this.connectionParams) {
162
+ ws.send(await utils.buildConnectionMessage(this.connectionParams));
163
+ }
164
+ this.callbacks.onOpen?.();
165
+ this.connectionState.next({
166
+ type: 'state',
167
+ state: 'pending',
168
+ error: null
169
+ });
170
+ const messages = this.requestManager.getPendingRequests().map(({ message })=>message);
171
+ if (messages.length) {
172
+ ws.send(JSON.stringify(messages));
173
+ }
174
+ }).catch((error)=>{
175
+ ws.close(3000);
176
+ handleCloseOrError(error);
177
+ });
178
+ });
179
+ ws.addEventListener('message', ({ data })=>{
180
+ this.inactivityTimeout.reset();
181
+ if (typeof data !== 'string' || [
182
+ 'PING',
183
+ 'PONG'
184
+ ].includes(data)) return;
185
+ const incomingMessage = JSON.parse(data);
186
+ if ('method' in incomingMessage) {
187
+ this.handleIncomingRequest(incomingMessage);
188
+ return;
189
+ }
190
+ this.handleResponseMessage(incomingMessage);
191
+ });
192
+ ws.addEventListener('close', (event)=>{
193
+ handleCloseOrError(event);
194
+ this.callbacks.onClose?.(event);
195
+ if (!this.lazyMode) {
196
+ this.reconnect(new utils.TRPCWebSocketClosedError({
197
+ message: 'WebSocket closed',
198
+ cause: event
199
+ }));
200
+ }
201
+ });
202
+ ws.addEventListener('error', (event)=>{
203
+ handleCloseOrError(event);
204
+ this.callbacks.onError?.(event);
205
+ this.reconnect(new utils.TRPCWebSocketClosedError({
206
+ message: 'WebSocket closed',
207
+ cause: event
208
+ }));
209
+ });
210
+ }
211
+ handleResponseMessage(message) {
212
+ const request = this.requestManager.getPendingRequest(message.id);
213
+ if (!request) return;
214
+ request.callbacks.next(message);
215
+ let completed = true;
216
+ if ('result' in message && request.message.method === 'subscription') {
217
+ if (message.result.type === 'data') {
218
+ request.message.params.lastEventId = message.result.id;
219
+ }
220
+ if (message.result.type !== 'stopped') {
221
+ completed = false;
222
+ }
223
+ }
224
+ if (completed) {
225
+ request.callbacks.complete();
226
+ this.requestManager.delete(message.id);
227
+ }
228
+ }
229
+ handleIncomingRequest(message) {
230
+ if (message.method === 'reconnect') {
231
+ this.reconnect(new utils.TRPCWebSocketClosedError({
232
+ message: 'Server requested reconnect'
233
+ }));
234
+ }
235
+ }
236
+ /**
237
+ * Sends a message or batch of messages directly to the server.
238
+ */ send(messageOrMessages) {
239
+ if (!this.activeConnection.isOpen()) {
240
+ throw new Error('Active connection is not open');
241
+ }
242
+ const messages = messageOrMessages instanceof Array ? messageOrMessages : [
243
+ messageOrMessages
244
+ ];
245
+ this.activeConnection.ws.send(JSON.stringify(messages.length === 1 ? messages[0] : messages));
246
+ }
247
+ /**
248
+ * Groups requests for batch sending.
249
+ *
250
+ * @returns A function to abort the batched request.
251
+ */ batchSend(message, callbacks) {
252
+ this.inactivityTimeout.reset();
253
+ unstableCoreDoNotImport.run(async ()=>{
254
+ if (!this.activeConnection.isOpen()) {
255
+ await this.open();
256
+ }
257
+ await unstableCoreDoNotImport.sleep(0);
258
+ if (!this.requestManager.hasOutgoingRequests()) return;
259
+ this.send(this.requestManager.flush().map(({ message })=>message));
260
+ }).catch((err)=>{
261
+ this.requestManager.delete(message.id);
262
+ callbacks.error(TRPCClientError.TRPCClientError.from(err));
263
+ });
264
+ return this.requestManager.register(message, callbacks);
265
+ }
266
+ constructor(opts){
267
+ /**
268
+ * Observable tracking the current connection state, including errors.
269
+ */ _define_property(this, "connectionState", void 0);
270
+ _define_property(this, "allowReconnect", false);
271
+ _define_property(this, "requestManager", new requestManager.RequestManager());
272
+ _define_property(this, "activeConnection", void 0);
273
+ _define_property(this, "reconnectRetryDelay", void 0);
274
+ _define_property(this, "inactivityTimeout", void 0);
275
+ _define_property(this, "callbacks", void 0);
276
+ _define_property(this, "connectionParams", void 0);
277
+ _define_property(this, "lazyMode", void 0);
278
+ /**
279
+ * Manages the reconnection process for the WebSocket using retry logic.
280
+ * Ensures that only one reconnection attempt is active at a time by tracking the current
281
+ * reconnection state in the `reconnecting` promise.
282
+ */ _define_property(this, "reconnecting", null);
283
+ // Initialize callbacks, connection parameters, and options.
284
+ this.callbacks = {
285
+ onOpen: opts.onOpen,
286
+ onClose: opts.onClose,
287
+ onError: opts.onError
288
+ };
289
+ this.connectionParams = opts.connectionParams;
290
+ const lazyOptions = {
291
+ ...options.lazyDefaults,
292
+ ...opts.lazy
293
+ };
294
+ // Set up inactivity timeout for lazy connections.
295
+ this.inactivityTimeout = new utils.ResettableTimeout(()=>{
296
+ if (this.requestManager.hasOutgoingRequests() || this.requestManager.hasPendingRequests()) {
297
+ this.inactivityTimeout.reset();
298
+ return;
299
+ }
300
+ this.close().catch(()=>null);
301
+ }, lazyOptions.closeMs);
302
+ // Initialize the WebSocket connection.
303
+ this.activeConnection = new wsConnection.WsConnection({
304
+ WebSocketPonyfill: opts.WebSocket,
305
+ urlOptions: opts,
306
+ keepAlive: {
307
+ ...options.keepAliveDefaults,
308
+ ...opts.keepAlive
309
+ }
310
+ });
311
+ this.activeConnection.wsObservable.subscribe({
312
+ next: (ws)=>{
313
+ if (!ws) return;
314
+ this.setupWebSocketListeners(ws);
315
+ }
316
+ });
317
+ this.reconnectRetryDelay = opts.retryDelayMs ?? options.exponentialBackoff;
318
+ this.lazyMode = lazyOptions.enabled;
319
+ this.connectionState = observable.behaviorSubject({
320
+ type: 'state',
321
+ state: lazyOptions.enabled ? 'idle' : 'connecting',
322
+ error: null
323
+ });
324
+ // Automatically open the connection if lazy mode is disabled.
325
+ if (!this.lazyMode) {
326
+ this.open().catch(()=>null);
327
+ }
328
+ }
329
+ }
330
+
331
+ exports.WsClient = WsClient;
@@ -0,0 +1,329 @@
1
+ import { observable, behaviorSubject } from '@trpc/server/observable';
2
+ import { transformResult, run, sleep } from '@trpc/server/unstable-core-do-not-import';
3
+ import { TRPCClientError } from '../../../TRPCClientError.mjs';
4
+ import { lazyDefaults, keepAliveDefaults, exponentialBackoff } from './options.mjs';
5
+ import { RequestManager } from './requestManager.mjs';
6
+ import { TRPCWebSocketClosedError, buildConnectionMessage, ResettableTimeout } from './utils.mjs';
7
+ import { backwardCompatibility, WsConnection } from './wsConnection.mjs';
8
+
9
+ function _define_property(obj, key, value) {
10
+ if (key in obj) {
11
+ Object.defineProperty(obj, key, {
12
+ value: value,
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true
16
+ });
17
+ } else {
18
+ obj[key] = value;
19
+ }
20
+ return obj;
21
+ }
22
+ /**
23
+ * A WebSocket client for managing TRPC operations, supporting lazy initialization,
24
+ * reconnection, keep-alive, and request management.
25
+ */ class WsClient {
26
+ /**
27
+ * Opens the WebSocket connection. Handles reconnection attempts and updates
28
+ * the connection state accordingly.
29
+ */ async open() {
30
+ this.allowReconnect = true;
31
+ if (this.connectionState.get().state !== 'connecting') {
32
+ this.connectionState.next({
33
+ type: 'state',
34
+ state: 'connecting',
35
+ error: null
36
+ });
37
+ }
38
+ try {
39
+ await this.activeConnection.open();
40
+ } catch (error) {
41
+ this.reconnect(new TRPCWebSocketClosedError({
42
+ message: 'Initialization error',
43
+ cause: error
44
+ }));
45
+ return this.reconnecting;
46
+ }
47
+ }
48
+ /**
49
+ * Closes the WebSocket connection and stops managing requests.
50
+ * Ensures all outgoing and pending requests are properly finalized.
51
+ */ async close() {
52
+ this.allowReconnect = false;
53
+ this.inactivityTimeout.stop();
54
+ const requestsToAwait = [];
55
+ for (const request of this.requestManager.getRequests()){
56
+ if (request.message.method === 'subscription') {
57
+ request.callbacks.complete();
58
+ } else if (request.state === 'outgoing') {
59
+ request.callbacks.error(TRPCClientError.from(new TRPCWebSocketClosedError({
60
+ message: 'Closed before connection was established'
61
+ })));
62
+ } else {
63
+ requestsToAwait.push(request.end);
64
+ }
65
+ }
66
+ await Promise.all(requestsToAwait).catch(()=>null);
67
+ await this.activeConnection.close().catch(()=>null);
68
+ this.connectionState.next({
69
+ type: 'state',
70
+ state: 'idle',
71
+ error: null
72
+ });
73
+ }
74
+ /**
75
+ * Method to request the server.
76
+ * Handles data transformation, batching of requests, and subscription lifecycle.
77
+ *
78
+ * @param op - The operation details including id, type, path, input and signal
79
+ * @param transformer - Data transformer for serializing requests and deserializing responses
80
+ * @param lastEventId - Optional ID of the last received event for subscriptions
81
+ *
82
+ * @returns An observable that emits operation results and handles cleanup
83
+ */ request({ op: { id, type, path, input, signal }, transformer, lastEventId }) {
84
+ return observable((observer)=>{
85
+ const abort = this.batchSend({
86
+ id,
87
+ method: type,
88
+ params: {
89
+ input: transformer.input.serialize(input),
90
+ path,
91
+ lastEventId
92
+ }
93
+ }, {
94
+ ...observer,
95
+ next (event) {
96
+ const transformed = transformResult(event, transformer.output);
97
+ if (!transformed.ok) {
98
+ observer.error(TRPCClientError.from(transformed.error));
99
+ return;
100
+ }
101
+ observer.next({
102
+ result: transformed.result
103
+ });
104
+ }
105
+ });
106
+ return ()=>{
107
+ abort();
108
+ if (type === 'subscription' && this.activeConnection.isOpen()) {
109
+ this.send({
110
+ id,
111
+ method: 'subscription.stop'
112
+ });
113
+ }
114
+ signal?.removeEventListener('abort', abort);
115
+ };
116
+ });
117
+ }
118
+ get connection() {
119
+ return backwardCompatibility(this.activeConnection);
120
+ }
121
+ reconnect(closedError) {
122
+ this.connectionState.next({
123
+ type: 'state',
124
+ state: 'connecting',
125
+ error: TRPCClientError.from(closedError)
126
+ });
127
+ if (this.reconnecting) return;
128
+ const tryReconnect = async (attemptIndex)=>{
129
+ try {
130
+ await sleep(this.reconnectRetryDelay(attemptIndex));
131
+ if (this.allowReconnect) {
132
+ await this.activeConnection.close();
133
+ await this.activeConnection.open();
134
+ }
135
+ this.reconnecting = null;
136
+ } catch {
137
+ await tryReconnect(attemptIndex + 1);
138
+ }
139
+ };
140
+ this.reconnecting = tryReconnect(0);
141
+ }
142
+ setupWebSocketListeners(ws) {
143
+ const handleCloseOrError = (cause)=>{
144
+ const reqs = this.requestManager.getPendingRequests();
145
+ for (const { message, callbacks } of reqs){
146
+ if (message.method === 'subscription') continue;
147
+ callbacks.error(TRPCClientError.from(cause ?? new TRPCWebSocketClosedError({
148
+ message: 'WebSocket closed',
149
+ cause
150
+ })));
151
+ this.requestManager.delete(message.id);
152
+ }
153
+ };
154
+ ws.addEventListener('open', ()=>{
155
+ run(async ()=>{
156
+ if (this.lazyMode) {
157
+ this.inactivityTimeout.start();
158
+ }
159
+ if (this.connectionParams) {
160
+ ws.send(await buildConnectionMessage(this.connectionParams));
161
+ }
162
+ this.callbacks.onOpen?.();
163
+ this.connectionState.next({
164
+ type: 'state',
165
+ state: 'pending',
166
+ error: null
167
+ });
168
+ const messages = this.requestManager.getPendingRequests().map(({ message })=>message);
169
+ if (messages.length) {
170
+ ws.send(JSON.stringify(messages));
171
+ }
172
+ }).catch((error)=>{
173
+ ws.close(3000);
174
+ handleCloseOrError(error);
175
+ });
176
+ });
177
+ ws.addEventListener('message', ({ data })=>{
178
+ this.inactivityTimeout.reset();
179
+ if (typeof data !== 'string' || [
180
+ 'PING',
181
+ 'PONG'
182
+ ].includes(data)) return;
183
+ const incomingMessage = JSON.parse(data);
184
+ if ('method' in incomingMessage) {
185
+ this.handleIncomingRequest(incomingMessage);
186
+ return;
187
+ }
188
+ this.handleResponseMessage(incomingMessage);
189
+ });
190
+ ws.addEventListener('close', (event)=>{
191
+ handleCloseOrError(event);
192
+ this.callbacks.onClose?.(event);
193
+ if (!this.lazyMode) {
194
+ this.reconnect(new TRPCWebSocketClosedError({
195
+ message: 'WebSocket closed',
196
+ cause: event
197
+ }));
198
+ }
199
+ });
200
+ ws.addEventListener('error', (event)=>{
201
+ handleCloseOrError(event);
202
+ this.callbacks.onError?.(event);
203
+ this.reconnect(new TRPCWebSocketClosedError({
204
+ message: 'WebSocket closed',
205
+ cause: event
206
+ }));
207
+ });
208
+ }
209
+ handleResponseMessage(message) {
210
+ const request = this.requestManager.getPendingRequest(message.id);
211
+ if (!request) return;
212
+ request.callbacks.next(message);
213
+ let completed = true;
214
+ if ('result' in message && request.message.method === 'subscription') {
215
+ if (message.result.type === 'data') {
216
+ request.message.params.lastEventId = message.result.id;
217
+ }
218
+ if (message.result.type !== 'stopped') {
219
+ completed = false;
220
+ }
221
+ }
222
+ if (completed) {
223
+ request.callbacks.complete();
224
+ this.requestManager.delete(message.id);
225
+ }
226
+ }
227
+ handleIncomingRequest(message) {
228
+ if (message.method === 'reconnect') {
229
+ this.reconnect(new TRPCWebSocketClosedError({
230
+ message: 'Server requested reconnect'
231
+ }));
232
+ }
233
+ }
234
+ /**
235
+ * Sends a message or batch of messages directly to the server.
236
+ */ send(messageOrMessages) {
237
+ if (!this.activeConnection.isOpen()) {
238
+ throw new Error('Active connection is not open');
239
+ }
240
+ const messages = messageOrMessages instanceof Array ? messageOrMessages : [
241
+ messageOrMessages
242
+ ];
243
+ this.activeConnection.ws.send(JSON.stringify(messages.length === 1 ? messages[0] : messages));
244
+ }
245
+ /**
246
+ * Groups requests for batch sending.
247
+ *
248
+ * @returns A function to abort the batched request.
249
+ */ batchSend(message, callbacks) {
250
+ this.inactivityTimeout.reset();
251
+ run(async ()=>{
252
+ if (!this.activeConnection.isOpen()) {
253
+ await this.open();
254
+ }
255
+ await sleep(0);
256
+ if (!this.requestManager.hasOutgoingRequests()) return;
257
+ this.send(this.requestManager.flush().map(({ message })=>message));
258
+ }).catch((err)=>{
259
+ this.requestManager.delete(message.id);
260
+ callbacks.error(TRPCClientError.from(err));
261
+ });
262
+ return this.requestManager.register(message, callbacks);
263
+ }
264
+ constructor(opts){
265
+ /**
266
+ * Observable tracking the current connection state, including errors.
267
+ */ _define_property(this, "connectionState", void 0);
268
+ _define_property(this, "allowReconnect", false);
269
+ _define_property(this, "requestManager", new RequestManager());
270
+ _define_property(this, "activeConnection", void 0);
271
+ _define_property(this, "reconnectRetryDelay", void 0);
272
+ _define_property(this, "inactivityTimeout", void 0);
273
+ _define_property(this, "callbacks", void 0);
274
+ _define_property(this, "connectionParams", void 0);
275
+ _define_property(this, "lazyMode", void 0);
276
+ /**
277
+ * Manages the reconnection process for the WebSocket using retry logic.
278
+ * Ensures that only one reconnection attempt is active at a time by tracking the current
279
+ * reconnection state in the `reconnecting` promise.
280
+ */ _define_property(this, "reconnecting", null);
281
+ // Initialize callbacks, connection parameters, and options.
282
+ this.callbacks = {
283
+ onOpen: opts.onOpen,
284
+ onClose: opts.onClose,
285
+ onError: opts.onError
286
+ };
287
+ this.connectionParams = opts.connectionParams;
288
+ const lazyOptions = {
289
+ ...lazyDefaults,
290
+ ...opts.lazy
291
+ };
292
+ // Set up inactivity timeout for lazy connections.
293
+ this.inactivityTimeout = new ResettableTimeout(()=>{
294
+ if (this.requestManager.hasOutgoingRequests() || this.requestManager.hasPendingRequests()) {
295
+ this.inactivityTimeout.reset();
296
+ return;
297
+ }
298
+ this.close().catch(()=>null);
299
+ }, lazyOptions.closeMs);
300
+ // Initialize the WebSocket connection.
301
+ this.activeConnection = new WsConnection({
302
+ WebSocketPonyfill: opts.WebSocket,
303
+ urlOptions: opts,
304
+ keepAlive: {
305
+ ...keepAliveDefaults,
306
+ ...opts.keepAlive
307
+ }
308
+ });
309
+ this.activeConnection.wsObservable.subscribe({
310
+ next: (ws)=>{
311
+ if (!ws) return;
312
+ this.setupWebSocketListeners(ws);
313
+ }
314
+ });
315
+ this.reconnectRetryDelay = opts.retryDelayMs ?? exponentialBackoff;
316
+ this.lazyMode = lazyOptions.enabled;
317
+ this.connectionState = behaviorSubject({
318
+ type: 'state',
319
+ state: lazyOptions.enabled ? 'idle' : 'connecting',
320
+ error: null
321
+ });
322
+ // Automatically open the connection if lazy mode is disabled.
323
+ if (!this.lazyMode) {
324
+ this.open().catch(()=>null);
325
+ }
326
+ }
327
+ }
328
+
329
+ export { WsClient };