@trpc/tanstack-react-query 11.6.1-canary.5 → 11.7.0

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.
@@ -6,11 +6,13 @@ import type { Unsubscribable } from '@trpc/server/observable';
6
6
  import type { inferAsyncIterableYield } from '@trpc/server/unstable-core-do-not-import';
7
7
  import * as React from 'react';
8
8
  import type {
9
+ DefaultFeatureFlags,
10
+ FeatureFlags,
9
11
  ResolverDef,
10
12
  TRPCQueryKey,
11
13
  TRPCQueryOptionsResult,
12
14
  } from './types';
13
- import { createTRPCOptionsResult } from './utils';
15
+ import { createTRPCOptionsResult, readQueryKey } from './utils';
14
16
 
15
17
  interface BaseTRPCSubscriptionOptionsIn<TOutput, TError> {
16
18
  enabled?: boolean;
@@ -27,17 +29,23 @@ interface UnusedSkipTokenTRPCSubscriptionOptionsIn<TOutput, TError> {
27
29
  onConnectionStateChange?: (state: TRPCConnectionState<TError>) => void;
28
30
  }
29
31
 
30
- interface TRPCSubscriptionOptionsOut<TOutput, TError>
31
- extends UnusedSkipTokenTRPCSubscriptionOptionsIn<TOutput, TError>,
32
+ interface TRPCSubscriptionOptionsOut<
33
+ TOutput,
34
+ TError,
35
+ TFeatureFlags extends FeatureFlags,
36
+ > extends UnusedSkipTokenTRPCSubscriptionOptionsIn<TOutput, TError>,
32
37
  TRPCQueryOptionsResult {
33
38
  enabled: boolean;
34
- queryKey: TRPCQueryKey;
39
+ queryKey: TRPCQueryKey<TFeatureFlags['keyPrefix']>;
35
40
  subscribe: (
36
41
  innerOpts: UnusedSkipTokenTRPCSubscriptionOptionsIn<TOutput, TError>,
37
42
  ) => Unsubscribable;
38
43
  }
39
44
 
40
- export interface TRPCSubscriptionOptions<TDef extends ResolverDef> {
45
+ export interface TRPCSubscriptionOptions<
46
+ TDef extends ResolverDef,
47
+ TFeatureFlags extends FeatureFlags = DefaultFeatureFlags,
48
+ > {
41
49
  (
42
50
  input: TDef['input'],
43
51
  opts?: UnusedSkipTokenTRPCSubscriptionOptionsIn<
@@ -46,7 +54,8 @@ export interface TRPCSubscriptionOptions<TDef extends ResolverDef> {
46
54
  >,
47
55
  ): TRPCSubscriptionOptionsOut<
48
56
  inferAsyncIterableYield<TDef['output']>,
49
- TRPCClientErrorLike<TDef>
57
+ TRPCClientErrorLike<TDef>,
58
+ TFeatureFlags
50
59
  >;
51
60
  (
52
61
  input: TDef['input'] | SkipToken,
@@ -56,7 +65,8 @@ export interface TRPCSubscriptionOptions<TDef extends ResolverDef> {
56
65
  >,
57
66
  ): TRPCSubscriptionOptionsOut<
58
67
  inferAsyncIterableYield<TDef['output']>,
59
- TRPCClientErrorLike<TDef>
68
+ TRPCClientErrorLike<TDef>,
69
+ TFeatureFlags
60
70
  >;
61
71
  }
62
72
  export type TRPCSubscriptionStatus =
@@ -113,27 +123,27 @@ type AnyTRPCSubscriptionOptionsIn =
113
123
  | BaseTRPCSubscriptionOptionsIn<unknown, unknown>
114
124
  | UnusedSkipTokenTRPCSubscriptionOptionsIn<unknown, unknown>;
115
125
 
116
- type AnyTRPCSubscriptionOptionsOut = TRPCSubscriptionOptionsOut<
117
- unknown,
118
- unknown
119
- >;
126
+ type AnyTRPCSubscriptionOptionsOut<TFeatureFlags extends FeatureFlags> =
127
+ TRPCSubscriptionOptionsOut<unknown, unknown, TFeatureFlags>;
120
128
 
121
129
  /**
122
130
  * @internal
123
131
  */
124
- export const trpcSubscriptionOptions = (args: {
132
+ export const trpcSubscriptionOptions = <
133
+ TFeatureFlags extends FeatureFlags,
134
+ >(args: {
125
135
  subscribe: typeof TRPCUntypedClient.prototype.subscription;
126
- path: readonly string[];
127
- queryKey: TRPCQueryKey;
136
+ path: string[];
137
+ queryKey: TRPCQueryKey<TFeatureFlags['keyPrefix']>;
128
138
  opts?: AnyTRPCSubscriptionOptionsIn;
129
- }): AnyTRPCSubscriptionOptionsOut => {
139
+ }): AnyTRPCSubscriptionOptionsOut<TFeatureFlags> => {
130
140
  const { subscribe, path, queryKey, opts = {} } = args;
131
- const input = queryKey[1]?.input;
141
+ const input = readQueryKey(queryKey)?.args?.input;
132
142
  const enabled = 'enabled' in opts ? !!opts.enabled : input !== skipToken;
133
143
 
134
- const _subscribe: ReturnType<TRPCSubscriptionOptions<any>>['subscribe'] = (
135
- innerOpts,
136
- ) => {
144
+ const _subscribe: ReturnType<
145
+ TRPCSubscriptionOptions<any, TFeatureFlags>
146
+ >['subscribe'] = (innerOpts) => {
137
147
  return subscribe(path.join('.'), input ?? undefined, innerOpts);
138
148
  };
139
149
 
@@ -147,7 +157,7 @@ export const trpcSubscriptionOptions = (args: {
147
157
  };
148
158
 
149
159
  export function useSubscription<TOutput, TError>(
150
- opts: TRPCSubscriptionOptionsOut<TOutput, TError>,
160
+ opts: TRPCSubscriptionOptionsOut<TOutput, TError, any>,
151
161
  ): TRPCSubscriptionResult<TOutput, TError> {
152
162
  type $Result = TRPCSubscriptionResult<TOutput, TError>;
153
163
 
@@ -17,6 +17,7 @@ export type ResolverDef = {
17
17
  output: any;
18
18
  transformer: boolean;
19
19
  errorShape: any;
20
+ featureFlags: FeatureFlags;
20
21
  };
21
22
 
22
23
  /**
@@ -83,12 +84,82 @@ export type QueryType = 'any' | 'infinite' | 'query';
83
84
  /**
84
85
  * @public
85
86
  */
86
- export type TRPCQueryKey = [
87
- readonly string[],
88
- { input?: unknown; type?: Exclude<QueryType, 'any'> }?,
87
+ export type TRPCQueryKeyWithoutPrefix = [
88
+ path: string[],
89
+ opts?: { input?: unknown; type?: Exclude<QueryType, 'any'> },
89
90
  ];
90
91
 
91
92
  /**
92
93
  * @public
93
94
  */
94
- export type TRPCMutationKey = [readonly string[]]; // = [TRPCQueryKey[0]]
95
+ export type TRPCQueryKeyWithPrefix = [
96
+ prefix: string[],
97
+ ...TRPCQueryKeyWithoutPrefix,
98
+ ];
99
+
100
+ export type TRPCQueryKey<TPrefixEnabled extends boolean = false> =
101
+ TPrefixEnabled extends true
102
+ ? TRPCQueryKeyWithPrefix
103
+ : TRPCQueryKeyWithoutPrefix;
104
+
105
+ export type AnyTRPCQueryKey =
106
+ | TRPCQueryKeyWithoutPrefix
107
+ | TRPCQueryKeyWithPrefix;
108
+
109
+ /**
110
+ * @public
111
+ */
112
+ export type TRPCMutationKeyWithPrefix = [
113
+ prefix: string[],
114
+ ...TRPCMutationKeyWithoutPrefix,
115
+ ];
116
+
117
+ /**
118
+ * @public
119
+ */
120
+ export type TRPCMutationKeyWithoutPrefix = [path: string[]];
121
+
122
+ export type AnyTRPCMutationKey =
123
+ | TRPCMutationKeyWithoutPrefix
124
+ | TRPCMutationKeyWithPrefix;
125
+
126
+ /**
127
+ * @public
128
+ */
129
+ export type TRPCMutationKey<TPrefixEnabled extends boolean = false> =
130
+ TPrefixEnabled extends true
131
+ ? TRPCMutationKeyWithPrefix
132
+ : TRPCMutationKeyWithoutPrefix;
133
+
134
+ /**
135
+ * Feature flags for configuring tRPC behavior
136
+ * @public
137
+ */
138
+ export type FeatureFlags = { keyPrefix: boolean };
139
+
140
+ /**
141
+ * @internal
142
+ */
143
+ export type ofFeatureFlags<T extends FeatureFlags> = T;
144
+
145
+ /**
146
+ * @internal
147
+ */
148
+ export type KeyPrefixOptions<TFeatureFlags extends FeatureFlags> =
149
+ TFeatureFlags['keyPrefix'] extends true
150
+ ? {
151
+ keyPrefix: string;
152
+ }
153
+ : {
154
+ /**
155
+ * In order to use a query key prefix, you have to initialize the context with the `keyPrefix`
156
+ */
157
+ keyPrefix?: never;
158
+ };
159
+ /**
160
+ * Default feature flags with query key prefix disabled
161
+ * @public
162
+ */
163
+ export type DefaultFeatureFlags = ofFeatureFlags<{
164
+ keyPrefix: false;
165
+ }>;
@@ -1,9 +1,18 @@
1
1
  import { skipToken, type QueryClient } from '@tanstack/react-query';
2
- import { isFunction, isObject } from '@trpc/server/unstable-core-do-not-import';
2
+ import {
3
+ isFunction,
4
+ isObject,
5
+ run,
6
+ } from '@trpc/server/unstable-core-do-not-import';
3
7
  import type {
8
+ AnyTRPCMutationKey,
9
+ AnyTRPCQueryKey,
10
+ FeatureFlags,
4
11
  QueryType,
5
- TRPCMutationKey,
12
+ TRPCMutationKeyWithoutPrefix,
6
13
  TRPCQueryKey,
14
+ TRPCQueryKeyWithoutPrefix,
15
+ TRPCQueryKeyWithPrefix,
7
16
  TRPCQueryOptionsResult,
8
17
  } from './types';
9
18
 
@@ -11,7 +20,7 @@ import type {
11
20
  * @internal
12
21
  */
13
22
  export function createTRPCOptionsResult(value: {
14
- path: readonly string[];
23
+ path: string[];
15
24
  }): TRPCQueryOptionsResult['trpc'] {
16
25
  const path = value.path.join('.');
17
26
 
@@ -20,38 +29,65 @@ export function createTRPCOptionsResult(value: {
20
29
  };
21
30
  }
22
31
 
32
+ export function isPrefixedQueryKey(
33
+ queryKey: TRPCQueryKey<any>,
34
+ ): queryKey is TRPCQueryKeyWithPrefix {
35
+ return queryKey.length >= 3;
36
+ }
37
+
38
+ export function readQueryKey(queryKey: AnyTRPCQueryKey) {
39
+ if (isPrefixedQueryKey(queryKey)) {
40
+ return {
41
+ type: 'prefixed' as const,
42
+ prefix: queryKey[0],
43
+ path: queryKey[1],
44
+ args: queryKey[2],
45
+ };
46
+ } else {
47
+ return {
48
+ type: 'unprefixed' as const,
49
+ prefix: undefined,
50
+ path: queryKey[0],
51
+ args: queryKey[1],
52
+ };
53
+ }
54
+ }
55
+
23
56
  /**
24
57
  * @internal
25
58
  */
26
- export function getClientArgs<TOptions>(
27
- queryKey: TRPCQueryKey,
59
+ export function getClientArgs<TOptions, TFeatureFlags extends FeatureFlags>(
60
+ queryKey: TRPCQueryKey<TFeatureFlags['keyPrefix']>,
28
61
  opts: TOptions,
29
62
  infiniteParams?: {
30
63
  pageParam: any;
31
64
  direction: 'forward' | 'backward';
32
65
  },
33
66
  ) {
34
- const path = queryKey[0];
35
- let input = queryKey[1]?.input;
67
+ const queryKeyData = readQueryKey(queryKey);
68
+
69
+ let input = queryKeyData.args?.input;
36
70
  if (infiniteParams) {
37
71
  input = {
38
- ...(input ?? {}),
72
+ ...(queryKeyData.args?.input ?? {}),
39
73
  ...(infiniteParams.pageParam !== undefined
40
74
  ? { cursor: infiniteParams.pageParam }
41
75
  : {}),
42
76
  direction: infiniteParams.direction,
43
77
  };
44
78
  }
45
- return [path.join('.'), input, (opts as any)?.trpc] as const;
79
+ return [queryKeyData.path.join('.'), input, (opts as any)?.trpc] as const;
46
80
  }
47
81
 
48
82
  /**
49
83
  * @internal
50
84
  */
51
- export async function buildQueryFromAsyncIterable(
85
+ export async function buildQueryFromAsyncIterable<
86
+ TQueryKey extends TRPCQueryKey<any>,
87
+ >(
52
88
  asyncIterable: AsyncIterable<unknown>,
53
89
  queryClient: QueryClient,
54
- queryKey: TRPCQueryKey,
90
+ queryKey: TQueryKey,
55
91
  ) {
56
92
  const queryCache = queryClient.getQueryCache();
57
93
 
@@ -82,65 +118,81 @@ export async function buildQueryFromAsyncIterable(
82
118
  *
83
119
  * @internal
84
120
  */
85
- export function getQueryKeyInternal(
86
- path: readonly string[],
87
- input?: unknown,
88
- type?: QueryType,
89
- ): TRPCQueryKey {
90
- // Construct a query key that is easy to destructure and flexible for
91
- // partial selecting etc.
92
- // https://github.com/trpc/trpc/issues/3128
93
-
94
- // some parts of the path may be dot-separated, split them up
95
- const splitPath = path.flatMap((part) => part.split('.'));
121
+ export function getQueryKeyInternal(opts: {
122
+ path: string[];
123
+ input?: unknown;
124
+ type: QueryType;
125
+ prefix: string | undefined;
126
+ }): AnyTRPCQueryKey {
127
+ const key = run((): TRPCQueryKeyWithoutPrefix => {
128
+ const { input, type } = opts;
129
+
130
+ // Construct a query key that is easy to destructure and flexible for
131
+ // partial selecting etc.
132
+ // https://github.com/trpc/trpc/issues/3128
133
+
134
+ // some parts of the path may be dot-separated, split them up
135
+ const splitPath = opts.path.flatMap((part) => part.split('.'));
136
+
137
+ if (!input && type === 'any') {
138
+ // this matches also all mutations (see `getMutationKeyInternal`)
139
+
140
+ // for `utils.invalidate()` to match all queries (including vanilla react-query)
141
+ // we don't want nested array if path is empty, i.e. `[]` instead of `[[]]`
142
+ return splitPath.length ? [splitPath] : ([] as unknown as TRPCQueryKey);
143
+ }
144
+
145
+ if (
146
+ type === 'infinite' &&
147
+ isObject(input) &&
148
+ ('direction' in input || 'cursor' in input)
149
+ ) {
150
+ const {
151
+ cursor: _,
152
+ direction: __,
153
+ ...inputWithoutCursorAndDirection
154
+ } = input;
155
+ return [
156
+ splitPath,
157
+ {
158
+ input: inputWithoutCursorAndDirection,
159
+ type: 'infinite',
160
+ },
161
+ ];
162
+ }
96
163
 
97
- if (!input && (!type || type === 'any')) {
98
- // this matches also all mutations (see `getMutationKeyInternal`)
99
-
100
- // for `utils.invalidate()` to match all queries (including vanilla react-query)
101
- // we don't want nested array if path is empty, i.e. `[]` instead of `[[]]`
102
- return splitPath.length ? [splitPath] : ([] as unknown as TRPCQueryKey);
103
- }
104
-
105
- if (
106
- type === 'infinite' &&
107
- isObject(input) &&
108
- ('direction' in input || 'cursor' in input)
109
- ) {
110
- const {
111
- cursor: _,
112
- direction: __,
113
- ...inputWithoutCursorAndDirection
114
- } = input;
115
164
  return [
116
165
  splitPath,
117
166
  {
118
- input: inputWithoutCursorAndDirection,
119
- type: 'infinite',
167
+ ...(typeof input !== 'undefined' &&
168
+ input !== skipToken && { input: input }),
169
+ ...(type && type !== 'any' && { type: type }),
120
170
  },
121
171
  ];
122
- }
172
+ });
123
173
 
124
- return [
125
- splitPath,
126
- {
127
- ...(typeof input !== 'undefined' &&
128
- input !== skipToken && { input: input }),
129
- ...(type && type !== 'any' && { type: type }),
130
- },
131
- ];
174
+ if (opts.prefix) {
175
+ key.unshift([opts.prefix]);
176
+ }
177
+ return key;
132
178
  }
133
179
 
134
180
  /**
135
181
  * @internal
136
182
  */
137
- export function getMutationKeyInternal(
138
- path: readonly string[],
139
- ): TRPCMutationKey {
183
+ export function getMutationKeyInternal(opts: {
184
+ prefix: string | undefined;
185
+ path: string[];
186
+ }): AnyTRPCMutationKey {
140
187
  // some parts of the path may be dot-separated, split them up
141
- const splitPath = path.flatMap((part) => part.split('.'));
188
+ const key: TRPCMutationKeyWithoutPrefix = [
189
+ opts.path.flatMap((part) => part.split('.')),
190
+ ];
142
191
 
143
- return splitPath.length ? [splitPath] : ([] as unknown as TRPCMutationKey);
192
+ if (opts.prefix) {
193
+ key.unshift([opts.prefix]);
194
+ }
195
+ return key;
144
196
  }
145
197
 
146
198
  /**