@trpc/react-query 11.0.0-alpha-tmp-issues-5851-take-two.496 → 11.0.0-alpha-tmp-subscription-connection-state.485

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 (39) hide show
  1. package/dist/bundle-analysis.json +64 -42
  2. package/dist/createTRPCReact.d.ts +3 -3
  3. package/dist/createTRPCReact.d.ts.map +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/index.mjs +1 -1
  8. package/dist/internals/context.d.ts +18 -2
  9. package/dist/internals/context.d.ts.map +1 -1
  10. package/dist/internals/getQueryKey.d.ts +8 -0
  11. package/dist/internals/getQueryKey.d.ts.map +1 -1
  12. package/dist/internals/getQueryKey.js +15 -0
  13. package/dist/internals/getQueryKey.mjs +14 -1
  14. package/dist/shared/hooks/createHooksInternal.d.ts +2 -2
  15. package/dist/shared/hooks/createHooksInternal.d.ts.map +1 -1
  16. package/dist/shared/hooks/createHooksInternal.js +67 -3
  17. package/dist/shared/hooks/createHooksInternal.mjs +68 -4
  18. package/dist/shared/hooks/types.d.ts +171 -2
  19. package/dist/shared/hooks/types.d.ts.map +1 -1
  20. package/dist/shared/hooks/types.js +133 -0
  21. package/dist/shared/hooks/types.mjs +126 -0
  22. package/dist/shared/index.js +7 -0
  23. package/dist/shared/index.mjs +1 -0
  24. package/dist/shared/proxy/utilsProxy.d.ts +12 -4
  25. package/dist/shared/proxy/utilsProxy.d.ts.map +1 -1
  26. package/dist/shared/proxy/utilsProxy.js +14 -2
  27. package/dist/shared/proxy/utilsProxy.mjs +15 -3
  28. package/dist/utils/createUtilityFunctions.d.ts.map +1 -1
  29. package/dist/utils/createUtilityFunctions.js +23 -0
  30. package/dist/utils/createUtilityFunctions.mjs +23 -0
  31. package/package.json +6 -6
  32. package/src/createTRPCReact.tsx +11 -2
  33. package/src/index.ts +1 -1
  34. package/src/internals/context.tsx +26 -1
  35. package/src/internals/getQueryKey.ts +21 -0
  36. package/src/shared/hooks/createHooksInternal.tsx +146 -18
  37. package/src/shared/hooks/types.ts +365 -1
  38. package/src/shared/proxy/utilsProxy.ts +47 -6
  39. package/src/utils/createUtilityFunctions.ts +26 -0
@@ -18,7 +18,9 @@ import type {
18
18
  UseSuspenseQueryResult,
19
19
  } from '@tanstack/react-query';
20
20
  import type {
21
+ ConnectionState,
21
22
  CreateTRPCClientOptions,
23
+ TRPCConnectionStateMessage,
22
24
  TRPCRequestOptions,
23
25
  TRPCUntypedClient,
24
26
  } from '@trpc/client';
@@ -139,9 +141,371 @@ export interface UseTRPCMutationOptions<
139
141
  export interface UseTRPCSubscriptionOptions<TOutput, TError> {
140
142
  enabled?: boolean;
141
143
  onStarted?: () => void;
142
- onData: (data: TOutput) => void;
144
+ /**
145
+ * @deprecated use onStateChange instead
146
+ */
143
147
  onError?: (err: TError) => void;
148
+ onData: (data: TOutput) => void;
149
+ onStateChange?: (state: TRPCConnectionStateMessage<TError>) => void;
150
+ }
151
+
152
+ export interface restartSubscriptionOptionsBase {
153
+ /**
154
+ * Cancel the current subscription and re-establish a new one
155
+ * - Defaults to `true`
156
+ * - Set to `false` no new subscription will be established if there is already an active subscription
157
+ */
158
+ cancelSubscription?: boolean;
159
+ }
160
+
161
+ export interface restartSubscriptionOptionsWithLastEventId
162
+ extends restartSubscriptionOptionsBase {
163
+ /**
164
+ * Defaults to `true` in case of failure or if the subscription is still active
165
+ * - When the susbscription has successfully completed, it will be set to `false`
166
+ */
167
+ sendLastEventId?: boolean;
144
168
  }
169
+
170
+ export type restartSubscriptionOptions<TInput> = TInput extends {
171
+ lastEventId?: string | null;
172
+ } | void
173
+ ? restartSubscriptionOptionsWithLastEventId
174
+ : restartSubscriptionOptionsBase;
175
+
176
+ export type restartSubscriptionFn<TInput> = (
177
+ options?: restartSubscriptionOptions<TInput>,
178
+ ) => void;
179
+
180
+ export interface TRPCSubscriptionBaseResult<_TInput, TOutput, TError> {
181
+ /**
182
+ * The last data received from the subscription
183
+ */
184
+ data: TOutput | null;
185
+ /**
186
+ * The timestamp for when the connection was last (re-)established
187
+ */
188
+ connectionStartedAt: number;
189
+ /**
190
+ * The timestamp for when the connection was initially established
191
+ */
192
+ initialConnectionStartedAt: number;
193
+ /**
194
+ * The error that caused the subscription to stop
195
+ * - Defaults to `null`
196
+ * - Resets to `null` after the subscription is restarted and the connection is re-established
197
+ */
198
+ error: TError | null;
199
+ /**
200
+ * The timestamp for when the last error was captured
201
+ */
202
+ errorUpdatedAt: number;
203
+ /**
204
+ * The reason for the reconnection
205
+ * - Resets to `null` after the subscription is restarted and the connection is re-established
206
+ */
207
+ connectionError: TError | null;
208
+ /**
209
+ * Reconnection attempts since last successful connection
210
+ */
211
+ connectionAttemptCount: number;
212
+ /**
213
+ * The timestamp for when the last reconnection error was captured
214
+ */
215
+ connectionErrorUpdatedAt: number;
216
+ /**
217
+ * Is `true` when the subscription is establishing the initial connection
218
+ */
219
+ isStarting: boolean;
220
+ /**
221
+ * Is `true` when the subscription has successfully connected at least once
222
+ */
223
+ isStarted: boolean;
224
+ /**
225
+ * Is `true` if the subscription is (re-)establishing a connection
226
+ * - Alias for `status === 'connecting'`
227
+ */
228
+ isConnecting: boolean;
229
+ /**
230
+ * Is `true` when the subscription is connected and listening for data
231
+ * - Alias for `status === 'pending'`
232
+ */
233
+ isPending: boolean;
234
+ /**
235
+ * Is `true` if the subscription is re-establishing a connection
236
+ * - Alias for `status === 'connecting' && isStarted === true`
237
+ */
238
+ isReconnecting: boolean;
239
+ /**
240
+ * Is `true` if the subscription ended in an error state
241
+ * - Alias for `status === 'error'`
242
+ */
243
+ isError: boolean;
244
+ /**
245
+ * The current state of the subscription
246
+ * - Will be:
247
+ * - `'idle'` when the subscription is not enabled
248
+ * - `'connecting'` when the subscription is (re-)establishing the connection
249
+ * - `'pending'` when the subscription is connected and receiving data
250
+ * - `'error'` when the subscription has stopped due to an error
251
+ */
252
+ status: ConnectionState;
253
+ /**
254
+ * Restart the subscription
255
+ */
256
+ // restart: restartSubscriptionFn<TInput>;
257
+ }
258
+
259
+ export interface TRPCSubscriptionIdleResult<TInput, TOutput, TError>
260
+ extends TRPCSubscriptionBaseResult<TInput, TOutput, TError> {
261
+ data: null;
262
+ error: null;
263
+ errorUpdatedAt: 0;
264
+ connectionError: null;
265
+ isStarting: false;
266
+ isStarted: false;
267
+ isConnecting: false;
268
+ isPending: false;
269
+ isReconnecting: false;
270
+ isError: false;
271
+ connectionAttemptCount: 0;
272
+ connectionErrorUpdatedAt: 0;
273
+ connectionStartedAt: 0;
274
+ initialConnectionStartedAt: 0;
275
+ status: 'idle';
276
+ }
277
+
278
+ const defaultIdleResult: Omit<
279
+ TRPCSubscriptionIdleResult<unknown, unknown, unknown>,
280
+ 'restart'
281
+ > = {
282
+ data: null,
283
+ connectionStartedAt: 0,
284
+ initialConnectionStartedAt: 0,
285
+ error: null,
286
+ errorUpdatedAt: 0,
287
+ connectionError: null,
288
+ connectionAttemptCount: 0,
289
+ isStarting: false,
290
+ isStarted: false,
291
+ isConnecting: false,
292
+ isPending: false,
293
+ isReconnecting: false,
294
+ isError: false,
295
+ connectionErrorUpdatedAt: 0,
296
+ status: 'idle',
297
+ };
298
+
299
+ export const getIdleResult = <
300
+ TInput,
301
+ TOutput,
302
+ TError,
303
+ >(): // restart: restartSubscriptionFn<TInput>,
304
+ TRPCSubscriptionIdleResult<TInput, TOutput, TError> => {
305
+ return {
306
+ ...defaultIdleResult,
307
+ data: null,
308
+ // restart,
309
+ };
310
+ };
311
+
312
+ export interface TRPCSubscriptionStartingResult<TInput, TOutput, TError>
313
+ extends TRPCSubscriptionBaseResult<TInput, TOutput, TError> {
314
+ connectionStartedAt: 0;
315
+ initialConnectionStartedAt: 0;
316
+ error: null;
317
+ data: null;
318
+ connectionError: TError | null;
319
+ isStarting: true;
320
+ isStarted: false;
321
+ isConnecting: true;
322
+ isPending: false;
323
+ isReconnecting: false;
324
+ isError: false;
325
+ status: 'connecting';
326
+ }
327
+
328
+ export const getStartingResult = <TInput, TOutput, TError>(
329
+ // restart: restartSubscriptionFn<TInput>,
330
+ previous?:
331
+ | TRPCSubscriptionIdleResult<TInput, TOutput, TError>
332
+ | TRPCSubscriptionErrorResult<TInput, TOutput, TError>
333
+ | TRPCSubscriptionStartingResult<TInput, TOutput, TError>,
334
+ error?: TError | null,
335
+ ): TRPCSubscriptionStartingResult<TInput, TOutput, TError> => {
336
+ const now = Date.now();
337
+
338
+ if (previous) {
339
+ return {
340
+ ...defaultIdleResult,
341
+ ...previous,
342
+ data: null,
343
+ connectionError: error ?? null,
344
+ isStarting: true,
345
+ isConnecting: true,
346
+ errorUpdatedAt: 0,
347
+ connectionAttemptCount: previous.connectionAttemptCount + 1,
348
+ connectionErrorUpdatedAt: error ? now : 0,
349
+ connectionStartedAt: 0,
350
+ initialConnectionStartedAt: 0,
351
+ error: null,
352
+ isStarted: false,
353
+ isError: false,
354
+ status: 'connecting',
355
+ };
356
+ }
357
+
358
+ return {
359
+ ...getIdleResult(/*restart*/),
360
+ connectionError: error ?? null,
361
+ isStarting: true,
362
+ isConnecting: true,
363
+ errorUpdatedAt: 0,
364
+ connectionAttemptCount: 0,
365
+ connectionErrorUpdatedAt: error ? now : 0,
366
+ status: 'connecting',
367
+ };
368
+ };
369
+
370
+ export interface TRPCSubscriptionPendingResult<TInput, TOutput, TError>
371
+ extends TRPCSubscriptionBaseResult<TInput, TOutput, TError> {
372
+ error: null;
373
+ connectionError: null;
374
+ connectionAttemptCount: 0;
375
+ isStarting: false;
376
+ isStarted: true;
377
+ isConnecting: false;
378
+ isPending: true;
379
+ isReconnecting: false;
380
+ isError: false;
381
+ status: 'pending';
382
+ }
383
+
384
+ export const getPendingResult = <TInput, TOutput, TError>(
385
+ previous: UseTRPCSubscriptionResult<TInput, TOutput, TError>,
386
+ data?: TOutput,
387
+ ): TRPCSubscriptionPendingResult<TInput, TOutput, TError> => {
388
+ const time = Date.now();
389
+
390
+ if (previous.isStarting) {
391
+ return {
392
+ ...previous,
393
+ error: null,
394
+ isStarting: false,
395
+ isStarted: true,
396
+ isConnecting: false,
397
+ isReconnecting: false,
398
+ isPending: true,
399
+ status: 'pending',
400
+ connectionStartedAt: time,
401
+ connectionAttemptCount: 0,
402
+ connectionError: null,
403
+ initialConnectionStartedAt: time,
404
+ };
405
+ }
406
+
407
+ return {
408
+ ...previous,
409
+ data: data ?? previous.data,
410
+ error: null,
411
+ isError: false,
412
+ isStarting: false,
413
+ isStarted: true,
414
+ isConnecting: false,
415
+ isReconnecting: false,
416
+ isPending: true,
417
+ status: 'pending',
418
+ connectionStartedAt: time,
419
+ connectionAttemptCount: 0,
420
+ connectionError: null,
421
+ };
422
+ };
423
+
424
+ export interface TRPCSubscriptionReconnectingResult<TInput, TOutput, TError>
425
+ extends TRPCSubscriptionBaseResult<TInput, TOutput, TError> {
426
+ error: null;
427
+ connectionError: TError;
428
+ isStarting: false;
429
+ isStarted: true;
430
+ isConnecting: true;
431
+ isPending: false;
432
+ isReconnecting: true;
433
+ isError: false;
434
+ status: 'connecting';
435
+ }
436
+
437
+ export const getReconnectingResult = <TInput, TOutput, TError>(
438
+ previous:
439
+ | TRPCSubscriptionPendingResult<TInput, TOutput, TError>
440
+ | TRPCSubscriptionReconnectingResult<TInput, TOutput, TError>,
441
+ error: TError,
442
+ ): TRPCSubscriptionReconnectingResult<TInput, TOutput, TError> => {
443
+ return {
444
+ ...previous,
445
+ isStarting: false,
446
+ isStarted: true,
447
+ isConnecting: true,
448
+ isReconnecting: true,
449
+ isPending: false,
450
+ status: 'connecting',
451
+ connectionError: error,
452
+ connectionAttemptCount: previous.connectionAttemptCount + 1,
453
+ };
454
+ };
455
+
456
+ export const getConnectingResult = <TInput, TOutput, TError>(
457
+ previous: UseTRPCSubscriptionResult<TInput, TOutput, TError>,
458
+ error: TError | null,
459
+ ): TRPCSubscriptionConnectingResult<TInput, TOutput, TError> => {
460
+ if (previous.isReconnecting || previous.isPending) {
461
+ if (!error) throw new Error('Reconnecting without error?');
462
+
463
+ return getReconnectingResult(previous, error);
464
+ }
465
+
466
+ return getStartingResult(/*previous.restart, */ previous, error);
467
+ };
468
+
469
+ export interface TRPCSubscriptionErrorResult<TInput, TOutput, TError>
470
+ extends TRPCSubscriptionBaseResult<TInput, TOutput, TError> {
471
+ error: TError;
472
+ isStarting: false;
473
+ isStarted: true; // Not sure about this one
474
+ isConnecting: false;
475
+ isPending: false;
476
+ isReconnecting: false;
477
+ isError: true;
478
+ status: 'error';
479
+ }
480
+
481
+ export const getErrorResult = <TInput, TOutput, TError>(
482
+ previous: UseTRPCSubscriptionResult<TInput, TOutput, TError>,
483
+ error: TError,
484
+ ): TRPCSubscriptionErrorResult<TInput, TOutput, TError> => {
485
+ return {
486
+ ...previous,
487
+ isStarting: false,
488
+ isStarted: true,
489
+ isConnecting: false,
490
+ isReconnecting: false,
491
+ isPending: false,
492
+ isError: true,
493
+ status: 'error',
494
+ error,
495
+ errorUpdatedAt: Date.now(),
496
+ };
497
+ };
498
+
499
+ export type TRPCSubscriptionConnectingResult<TInput, TOutput, TError> =
500
+ | TRPCSubscriptionStartingResult<TInput, TOutput, TError>
501
+ | TRPCSubscriptionReconnectingResult<TInput, TOutput, TError>;
502
+
503
+ export type UseTRPCSubscriptionResult<TInput, TOutput, TError> =
504
+ | TRPCSubscriptionIdleResult<TInput, TOutput, TError>
505
+ | TRPCSubscriptionPendingResult<TInput, TOutput, TError>
506
+ | TRPCSubscriptionConnectingResult<TInput, TOutput, TError>
507
+ | TRPCSubscriptionErrorResult<TInput, TOutput, TError>;
508
+
145
509
  export interface TRPCProviderProps<TRouter extends AnyRouter, TSSRContext>
146
510
  extends TRPCContextProps<TRouter, TSSRContext> {
147
511
  children: ReactNode;
@@ -15,6 +15,7 @@ import type {
15
15
  import type { TRPCClientError } from '@trpc/client';
16
16
  import { createTRPCClientProxy } from '@trpc/client';
17
17
  import type {
18
+ AnyMutationProcedure,
18
19
  AnyQueryProcedure,
19
20
  AnyRootTypes,
20
21
  AnyRouter,
@@ -37,10 +38,14 @@ import type {
37
38
  } from '../../internals/context';
38
39
  import { contextProps } from '../../internals/context';
39
40
  import type { QueryKeyKnown, QueryType } from '../../internals/getQueryKey';
40
- import { getQueryKeyInternal } from '../../internals/getQueryKey';
41
+ import {
42
+ getMutationKeyInternal,
43
+ getQueryKeyInternal,
44
+ } from '../../internals/getQueryKey';
45
+ import type { InferMutationOptions } from '../../utils/inferReactQueryProcedure';
41
46
  import type { ExtractCursorType } from '../hooks/types';
42
47
 
43
- type DecorateProcedure<
48
+ type DecorateQueryProcedure<
44
49
  TRoot extends AnyRootTypes,
45
50
  TProcedure extends AnyQueryProcedure,
46
51
  > = {
@@ -225,6 +230,25 @@ type DecorateProcedure<
225
230
  | undefined;
226
231
  };
227
232
 
233
+ type DecorateMutationProcedure<
234
+ TRoot extends AnyRootTypes,
235
+ TProcedure extends AnyMutationProcedure,
236
+ > = {
237
+ setMutationDefaults(
238
+ options:
239
+ | InferMutationOptions<TRoot, TProcedure>
240
+ | ((args: {
241
+ canonicalMutationFn: NonNullable<
242
+ InferMutationOptions<TRoot, TProcedure>['mutationFn']
243
+ >;
244
+ }) => InferMutationOptions<TRoot, TProcedure>),
245
+ ): void;
246
+
247
+ getMutationDefaults(): InferMutationOptions<TRoot, TProcedure> | undefined;
248
+
249
+ isMutating(): number;
250
+ };
251
+
228
252
  /**
229
253
  * this is the type that is used to add in procedures that can be used on
230
254
  * an entire router
@@ -252,14 +276,16 @@ export type DecoratedProcedureUtilsRecord<
252
276
  [TKey in keyof TRecord]: TRecord[TKey] extends infer $Value
253
277
  ? $Value extends RouterRecord
254
278
  ? DecoratedProcedureUtilsRecord<TRoot, $Value> & DecorateRouter
255
- : // utils only apply to queries
256
- $Value extends AnyQueryProcedure
257
- ? DecorateProcedure<TRoot, $Value>
279
+ : $Value extends AnyQueryProcedure
280
+ ? DecorateQueryProcedure<TRoot, $Value>
281
+ : $Value extends AnyMutationProcedure
282
+ ? DecorateMutationProcedure<TRoot, $Value>
258
283
  : never
259
284
  : never;
260
285
  }; // Add functions that should be available at utils root
261
286
 
262
- type AnyDecoratedProcedure = DecorateProcedure<any, any>;
287
+ type AnyDecoratedProcedure = DecorateQueryProcedure<any, any> &
288
+ DecorateMutationProcedure<any, any>;
263
289
 
264
290
  export type CreateReactUtils<
265
291
  TRouter extends AnyRouter,
@@ -296,6 +322,9 @@ export const getQueryType = (
296
322
  case 'setInfiniteData':
297
323
  return 'infinite';
298
324
 
325
+ case 'setMutationDefaults':
326
+ case 'getMutationDefaults':
327
+ case 'isMutating':
299
328
  case 'cancel':
300
329
  case 'invalidate':
301
330
  case 'refetch':
@@ -321,6 +350,9 @@ function createRecursiveUtilsProxy<TRouter extends AnyRouter>(
321
350
  const queryKey = getQueryKeyInternal(path, input, queryType);
322
351
 
323
352
  const contextMap: Record<keyof AnyDecoratedProcedure, () => unknown> = {
353
+ /**
354
+ * DecorateQueryProcedure
355
+ */
324
356
  fetch: () => context.fetchQuery(queryKey, ...args),
325
357
  fetchInfinite: () => context.fetchInfiniteQuery(queryKey, args[0]),
326
358
  prefetch: () => context.prefetchQuery(queryKey, ...args),
@@ -340,6 +372,15 @@ function createRecursiveUtilsProxy<TRouter extends AnyRouter>(
340
372
  },
341
373
  getData: () => context.getQueryData(queryKey),
342
374
  getInfiniteData: () => context.getInfiniteQueryData(queryKey),
375
+ /**
376
+ * DecorateMutationProcedure
377
+ */
378
+ setMutationDefaults: () =>
379
+ context.setMutationDefaults(getMutationKeyInternal(path), input),
380
+ getMutationDefaults: () =>
381
+ context.getMutationDefaults(getMutationKeyInternal(path)),
382
+ isMutating: () =>
383
+ context.isMutating({ mutationKey: getMutationKeyInternal(path) }),
343
384
  };
344
385
 
345
386
  return contextMap[utilName]();
@@ -145,5 +145,31 @@ export function createUtilityFunctions<TRouter extends AnyRouter>(
145
145
  getInfiniteQueryData: (queryKey) => {
146
146
  return queryClient.getQueryData(queryKey);
147
147
  },
148
+
149
+ setMutationDefaults: (mutationKey, options) => {
150
+ const path = mutationKey[0];
151
+ const canonicalMutationFn = (input: unknown) => {
152
+ return untypedClient.mutation(
153
+ ...getClientArgs([path, { input }], opts),
154
+ );
155
+ };
156
+ return queryClient.setMutationDefaults(
157
+ mutationKey,
158
+ typeof options === 'function'
159
+ ? options({ canonicalMutationFn })
160
+ : options,
161
+ );
162
+ },
163
+
164
+ getMutationDefaults: (mutationKey) => {
165
+ return queryClient.getMutationDefaults(mutationKey);
166
+ },
167
+
168
+ isMutating: (filters) => {
169
+ return queryClient.isMutating({
170
+ ...filters,
171
+ exact: true,
172
+ });
173
+ },
148
174
  };
149
175
  }