@trpc/react-query 11.0.0-alpha-tmp-issues-5851-take-two.499 → 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 +76 -54
  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
@@ -19,7 +19,10 @@ import type { SSRState, TRPCContextState } from '../../internals/context';
19
19
  import { TRPCContext } from '../../internals/context';
20
20
  import { getClientArgs } from '../../internals/getClientArgs';
21
21
  import type { TRPCQueryKey } from '../../internals/getQueryKey';
22
- import { getQueryKeyInternal } from '../../internals/getQueryKey';
22
+ import {
23
+ getMutationKeyInternal,
24
+ getQueryKeyInternal,
25
+ } from '../../internals/getQueryKey';
23
26
  import { useHookResult } from '../../internals/useHookResult';
24
27
  import type {
25
28
  TRPCUseQueries,
@@ -28,23 +31,46 @@ import type {
28
31
  import { createUtilityFunctions } from '../../utils/createUtilityFunctions';
29
32
  import { createUseQueries } from '../proxy/useQueriesProxy';
30
33
  import type { CreateTRPCReactOptions, UseMutationOverride } from '../types';
31
- import type {
32
- CreateClient,
33
- TRPCProvider,
34
- TRPCQueryOptions,
35
- UseTRPCInfiniteQueryOptions,
36
- UseTRPCInfiniteQueryResult,
37
- UseTRPCMutationOptions,
38
- UseTRPCMutationResult,
39
- UseTRPCQueryOptions,
40
- UseTRPCQueryResult,
41
- UseTRPCSubscriptionOptions,
42
- UseTRPCSuspenseInfiniteQueryOptions,
43
- UseTRPCSuspenseInfiniteQueryResult,
44
- UseTRPCSuspenseQueryOptions,
45
- UseTRPCSuspenseQueryResult,
34
+ // import type { restartSubscriptionFn } from './types';
35
+ import {
36
+ getConnectingResult,
37
+ getErrorResult,
38
+ getIdleResult,
39
+ getPendingResult,
40
+ getStartingResult,
41
+ type CreateClient,
42
+ type TRPCProvider,
43
+ type TRPCQueryOptions,
44
+ type UseTRPCInfiniteQueryOptions,
45
+ type UseTRPCInfiniteQueryResult,
46
+ type UseTRPCMutationOptions,
47
+ type UseTRPCMutationResult,
48
+ type UseTRPCQueryOptions,
49
+ type UseTRPCQueryResult,
50
+ type UseTRPCSubscriptionOptions,
51
+ type UseTRPCSubscriptionResult,
52
+ type UseTRPCSuspenseInfiniteQueryOptions,
53
+ type UseTRPCSuspenseInfiniteQueryResult,
54
+ type UseTRPCSuspenseQueryOptions,
55
+ type UseTRPCSuspenseQueryResult,
46
56
  } from './types';
47
57
 
58
+ const trackResult = <
59
+ T extends UseTRPCSubscriptionResult<unknown, unknown, unknown>,
60
+ >(
61
+ result: T,
62
+ onTrackResult: (key: keyof T) => void,
63
+ ): T => {
64
+ const trackedResult = new Proxy(result, {
65
+ get(target, prop) {
66
+ onTrackResult(prop as keyof T);
67
+ return target[prop as keyof T];
68
+ },
69
+ });
70
+
71
+ return trackedResult;
72
+ };
73
+
48
74
  /**
49
75
  * @internal
50
76
  */
@@ -267,7 +293,7 @@ export function createRootHooks<
267
293
  const { client } = useContext();
268
294
  const queryClient = useQueryClient();
269
295
 
270
- const mutationKey = [path];
296
+ const mutationKey = getMutationKeyInternal(path);
271
297
 
272
298
  const defaultOpts = queryClient.defaultMutationOptions(
273
299
  queryClient.getMutationDefaults(mutationKey),
@@ -306,9 +332,69 @@ export function createRootHooks<
306
332
  path: readonly string[],
307
333
  input: unknown,
308
334
  opts: UseTRPCSubscriptionOptions<unknown, TError>,
309
- ) {
335
+ ): UseTRPCSubscriptionResult<unknown, unknown, TError> {
310
336
  const enabled = opts?.enabled ?? input !== skipToken;
311
337
  const queryKey = hashKey(getQueryKeyInternal(path, input, 'any'));
338
+
339
+ const trackedProps = React.useRef(
340
+ new Set<keyof UseTRPCSubscriptionResult<unknown, unknown, TError>>([]),
341
+ );
342
+
343
+ const addTrackedProp = React.useCallback(
344
+ (key: keyof UseTRPCSubscriptionResult<unknown, unknown, TError>) => {
345
+ trackedProps.current.add(key);
346
+ },
347
+ [],
348
+ );
349
+
350
+ // const restart = React.useRef<restartSubscriptionFn<unknown>>(() => {
351
+ // throw new Error('not implemented');
352
+ // });
353
+
354
+ const currentResult = React.useRef<
355
+ UseTRPCSubscriptionResult<unknown, unknown, TError>
356
+ >(
357
+ enabled
358
+ ? getStartingResult(/* restart.current */)
359
+ : getIdleResult(/* restart.current */),
360
+ );
361
+
362
+ const [subscriptionState, setSubscriptionState] = React.useState(
363
+ trackResult(currentResult.current, addTrackedProp),
364
+ );
365
+
366
+ const updateSubscriptionState = React.useCallback(
367
+ (
368
+ opts:
369
+ | UseTRPCSubscriptionResult<unknown, unknown, TError>
370
+ | ((
371
+ prev: UseTRPCSubscriptionResult<unknown, unknown, TError>,
372
+ ) => UseTRPCSubscriptionResult<unknown, unknown, TError>),
373
+ ) => {
374
+ const oldResult = currentResult.current;
375
+
376
+ const newResult =
377
+ typeof opts === 'function' ? opts(currentResult.current) : opts;
378
+
379
+ currentResult.current = newResult;
380
+
381
+ let shouldUpdate = false;
382
+
383
+ for (const key of trackedProps.current) {
384
+ if (oldResult[key] !== newResult[key]) {
385
+ shouldUpdate = true;
386
+ break;
387
+ }
388
+ }
389
+
390
+ if (shouldUpdate) {
391
+ setSubscriptionState(trackResult(newResult, addTrackedProp));
392
+ return;
393
+ }
394
+ },
395
+ [addTrackedProp],
396
+ );
397
+
312
398
  const { client } = useContext();
313
399
 
314
400
  const optsRef = React.useRef<typeof opts>(opts);
@@ -319,6 +405,7 @@ export function createRootHooks<
319
405
  return;
320
406
  }
321
407
  let isStopped = false;
408
+
322
409
  const subscription = client.subscription(
323
410
  path.join('.'),
324
411
  input ?? undefined,
@@ -331,6 +418,14 @@ export function createRootHooks<
331
418
  onData: (data) => {
332
419
  if (!isStopped) {
333
420
  optsRef.current.onData(data);
421
+
422
+ updateSubscriptionState((prev) => {
423
+ if (prev.isPending) {
424
+ return getPendingResult(prev, data);
425
+ }
426
+
427
+ return prev;
428
+ });
334
429
  }
335
430
  },
336
431
  onError: (err) => {
@@ -338,14 +433,47 @@ export function createRootHooks<
338
433
  optsRef.current.onError?.(err);
339
434
  }
340
435
  },
436
+ onStateChange: (state) => {
437
+ if (state.state === 'idle') {
438
+ updateSubscriptionState(getIdleResult(/* restart.current */));
439
+
440
+ return;
441
+ }
442
+
443
+ if (state.state === 'connecting') {
444
+ updateSubscriptionState((prev) => {
445
+ return getConnectingResult(prev, state.data ?? null);
446
+ });
447
+
448
+ return;
449
+ }
450
+
451
+ if (state.state === 'pending') {
452
+ updateSubscriptionState((prev) => getPendingResult(prev));
453
+ }
454
+
455
+ if (state.state === 'error') {
456
+ updateSubscriptionState((prev) => {
457
+ return getErrorResult(prev, state.data);
458
+ });
459
+ }
460
+ },
341
461
  },
342
462
  );
463
+
464
+ // const effectRestart = restart.current;
465
+
343
466
  return () => {
344
467
  isStopped = true;
345
468
  subscription.unsubscribe();
469
+
470
+ updateSubscriptionState(getIdleResult(/* effectRestart */));
346
471
  };
472
+
347
473
  // eslint-disable-next-line react-hooks/exhaustive-deps
348
474
  }, [queryKey, enabled]);
475
+
476
+ return subscriptionState;
349
477
  }
350
478
 
351
479
  function useInfiniteQuery(
@@ -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]();