@trpc/client 10.33.0 → 10.34.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.
Files changed (42) hide show
  1. package/dist/TRPCClientError-8462a9bf.js +48 -0
  2. package/dist/TRPCClientError-dffb44e0.js +50 -0
  3. package/dist/TRPCClientError-fef6cf44.mjs +48 -0
  4. package/dist/TRPCClientError.d.ts +5 -1
  5. package/dist/TRPCClientError.d.ts.map +1 -1
  6. package/dist/getFetch.d.ts.map +1 -1
  7. package/dist/{httpBatchLink-3e9aaa76.js → httpBatchLink-2abec973.js} +9 -4
  8. package/dist/{httpBatchLink-5c66d3c6.js → httpBatchLink-579b00eb.js} +8 -3
  9. package/dist/{httpBatchLink-5d821c7a.mjs → httpBatchLink-fbd7b43c.mjs} +8 -3
  10. package/dist/{httpUtils-9d0d09cb.mjs → httpUtils-1efcb902.mjs} +12 -9
  11. package/dist/{httpUtils-c937fe75.js → httpUtils-676ede95.js} +10 -9
  12. package/dist/{httpUtils-4e5cf721.js → httpUtils-cc69e11f.js} +12 -9
  13. package/dist/index.js +6 -5
  14. package/dist/index.mjs +7 -6
  15. package/dist/links/httpBatchLink.js +4 -3
  16. package/dist/links/httpBatchLink.mjs +4 -3
  17. package/dist/links/httpLink.d.ts.map +1 -1
  18. package/dist/links/httpLink.js +10 -5
  19. package/dist/links/httpLink.mjs +9 -4
  20. package/dist/links/internals/createHTTPBatchLink.d.ts.map +1 -1
  21. package/dist/links/internals/httpUtils.d.ts +2 -1
  22. package/dist/links/internals/httpUtils.d.ts.map +1 -1
  23. package/dist/links/loggerLink.d.ts +5 -0
  24. package/dist/links/loggerLink.d.ts.map +1 -1
  25. package/dist/links/loggerLink.js +109 -46
  26. package/dist/links/loggerLink.mjs +109 -46
  27. package/dist/links/wsLink.js +4 -3
  28. package/dist/links/wsLink.mjs +2 -1
  29. package/dist/shared/index.js +1 -1
  30. package/dist/shared/index.mjs +1 -1
  31. package/dist/shared/transformResult.d.ts.map +1 -1
  32. package/dist/{transformResult-70f95ffb.js → transformResult-26a22823.js} +8 -38
  33. package/dist/{transformResult-9a244fe7.mjs → transformResult-7ab522e6.mjs} +9 -38
  34. package/dist/{transformResult-ae8e970c.js → transformResult-82684494.js} +9 -36
  35. package/package.json +4 -4
  36. package/src/TRPCClientError.ts +26 -3
  37. package/src/getFetch.ts +2 -7
  38. package/src/links/httpLink.ts +5 -2
  39. package/src/links/internals/createHTTPBatchLink.ts +7 -1
  40. package/src/links/internals/httpUtils.ts +9 -4
  41. package/src/links/loggerLink.ts +114 -41
  42. package/src/shared/transformResult.ts +9 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trpc/client",
3
- "version": "10.33.0",
3
+ "version": "10.34.0",
4
4
  "description": "The tRPC client library",
5
5
  "author": "KATT",
6
6
  "license": "MIT",
@@ -76,11 +76,11 @@
76
76
  "!**/*.test.*"
77
77
  ],
78
78
  "peerDependencies": {
79
- "@trpc/server": "10.33.0"
79
+ "@trpc/server": "10.34.0"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@testing-library/dom": "^9.0.0",
83
- "@trpc/server": "10.33.0",
83
+ "@trpc/server": "10.34.0",
84
84
  "@types/isomorphic-fetch": "^0.0.36",
85
85
  "@types/node": "^18.16.16",
86
86
  "eslint": "^8.40.0",
@@ -98,5 +98,5 @@
98
98
  "funding": [
99
99
  "https://trpc.io/sponsor"
100
100
  ],
101
- "gitHead": "b92f34aacd300402f4c41e3f4a5b1f236f490672"
101
+ "gitHead": "4b4bb80470b0f03626b6861c2b4cdfc8bb3afd81"
102
102
  }
@@ -24,6 +24,17 @@ export interface TRPCClientErrorBase<TShape extends DefaultErrorShape> {
24
24
  export type TRPCClientErrorLike<TRouterOrProcedure extends ErrorInferrable> =
25
25
  TRPCClientErrorBase<inferErrorShape<TRouterOrProcedure>>;
26
26
 
27
+ function isTRPCClientError(cause: Error): cause is TRPCClientError<any> {
28
+ return (
29
+ cause instanceof TRPCClientError ||
30
+ /**
31
+ * @deprecated
32
+ * Delete in next major
33
+ */
34
+ cause.name === 'TRPCClientError'
35
+ );
36
+ }
37
+
27
38
  export class TRPCClientError<TRouterOrProcedure extends ErrorInferrable>
28
39
  extends Error
29
40
  implements TRPCClientErrorBase<inferErrorShape<TRouterOrProcedure>>
@@ -31,7 +42,12 @@ export class TRPCClientError<TRouterOrProcedure extends ErrorInferrable>
31
42
  public readonly cause;
32
43
  public readonly shape: Maybe<inferErrorShape<TRouterOrProcedure>>;
33
44
  public readonly data: Maybe<inferErrorShape<TRouterOrProcedure>['data']>;
34
- public readonly meta;
45
+
46
+ /**
47
+ * Additional meta data about the error
48
+ * In the case of HTTP-errors, we'll have `response` and potentially `responseJSON` here
49
+ */
50
+ public meta;
35
51
 
36
52
  constructor(
37
53
  message: string,
@@ -71,8 +87,15 @@ export class TRPCClientError<TRouterOrProcedure extends ErrorInferrable>
71
87
  },
72
88
  );
73
89
  }
74
- if (cause.name === 'TRPCClientError') {
75
- return cause as TRPCClientError<any>;
90
+ if (isTRPCClientError(cause)) {
91
+ if (opts.meta) {
92
+ // Decorate with meta error data
93
+ cause.meta = {
94
+ ...cause.meta,
95
+ ...opts.meta,
96
+ };
97
+ }
98
+ return cause;
76
99
  }
77
100
 
78
101
  return new TRPCClientError<TRouterOrProcedure>(cause.message, {
package/src/getFetch.ts CHANGED
@@ -4,11 +4,6 @@ type AnyFn = (...args: any[]) => unknown;
4
4
 
5
5
  const isFunction = (fn: unknown): fn is AnyFn => typeof fn === 'function';
6
6
 
7
- function _bind(fn: AnyFn, thisArg: unknown): AnyFn {
8
- // eslint-disable-next-line @typescript-eslint/unbound-method
9
- return isFunction(fn.bind) ? fn.bind(thisArg) : fn;
10
- }
11
-
12
7
  export function getFetch(
13
8
  customFetchImpl?: FetchEsque | NativeFetchEsque,
14
9
  ): FetchEsque {
@@ -17,11 +12,11 @@ export function getFetch(
17
12
  }
18
13
 
19
14
  if (typeof window !== 'undefined' && isFunction(window.fetch)) {
20
- return _bind(window.fetch, window) as FetchEsque;
15
+ return window.fetch as FetchEsque;
21
16
  }
22
17
 
23
18
  if (typeof globalThis !== 'undefined' && isFunction(globalThis.fetch)) {
24
- return _bind(globalThis.fetch, globalThis) as FetchEsque;
19
+ return globalThis.fetch as FetchEsque;
25
20
  }
26
21
 
27
22
  throw new Error('No fetch implementation found');
@@ -4,6 +4,7 @@ import { transformResult } from '../shared/transformResult';
4
4
  import { TRPCClientError } from '../TRPCClientError';
5
5
  import {
6
6
  HTTPLinkBaseOptions,
7
+ HTTPResult,
7
8
  jsonHttpRequester,
8
9
  Requester,
9
10
  resolveHTTPLinkOptions,
@@ -48,14 +49,16 @@ export function httpLinkFactory(factoryOpts: { requester: Requester }) {
48
49
  return opts.headers;
49
50
  },
50
51
  });
52
+ let meta: HTTPResult['meta'] | undefined = undefined;
51
53
  promise
52
54
  .then((res) => {
55
+ meta = res.meta;
53
56
  const transformed = transformResult(res.json, runtime);
54
57
 
55
58
  if (!transformed.ok) {
56
59
  observer.error(
57
60
  TRPCClientError.from(transformed.error, {
58
- meta: res.meta,
61
+ meta,
59
62
  }),
60
63
  );
61
64
  return;
@@ -67,7 +70,7 @@ export function httpLinkFactory(factoryOpts: { requester: Requester }) {
67
70
  observer.complete();
68
71
  })
69
72
  .catch((cause) => {
70
- observer.error(TRPCClientError.from(cause));
73
+ observer.error(TRPCClientError.from(cause, { meta }));
71
74
  });
72
75
 
73
76
  return () => {
@@ -87,8 +87,10 @@ export function createHTTPBatchLink<TOptions extends HTTPBatchLinkOptions>(
87
87
  const loader = loaders[op.type];
88
88
  const { promise, cancel } = loader.load(op);
89
89
 
90
+ let _res = undefined as HTTPResult | undefined;
90
91
  promise
91
92
  .then((res) => {
93
+ _res = res;
92
94
  const transformed = transformResult(res.json, runtime);
93
95
 
94
96
  if (!transformed.ok) {
@@ -106,7 +108,11 @@ export function createHTTPBatchLink<TOptions extends HTTPBatchLinkOptions>(
106
108
  observer.complete();
107
109
  })
108
110
  .catch((err) => {
109
- observer.error(TRPCClientError.from(err));
111
+ observer.error(
112
+ TRPCClientError.from(err, {
113
+ meta: _res?.meta,
114
+ }),
115
+ );
110
116
  });
111
117
 
112
118
  return () => {
@@ -9,6 +9,7 @@ import {
9
9
  RequestInitEsque,
10
10
  ResponseEsque,
11
11
  } from '../../internals/types';
12
+ import { TRPCClientError } from '../../TRPCClientError';
12
13
  import { TextDecoderEsque } from '../internals/streamingUtils';
13
14
  import { HTTPHeaders, PromiseAndCancel, TRPCClientRuntime } from '../types';
14
15
 
@@ -29,7 +30,7 @@ export interface HTTPLinkBaseOptions {
29
30
 
30
31
  export interface ResolvedHTTPLinkOptions {
31
32
  url: string;
32
- fetch: FetchEsque;
33
+ fetch?: FetchEsque;
33
34
  AbortController: AbortControllerEsque | null;
34
35
  }
35
36
 
@@ -38,7 +39,7 @@ export function resolveHTTPLinkOptions(
38
39
  ): ResolvedHTTPLinkOptions {
39
40
  return {
40
41
  url: opts.url,
41
- fetch: getFetch(opts.fetch),
42
+ fetch: opts.fetch,
42
43
  AbortController: getAbortController(opts.AbortController),
43
44
  };
44
45
  }
@@ -62,6 +63,7 @@ export interface HTTPResult {
62
63
  json: TRPCResponse;
63
64
  meta: {
64
65
  response: ResponseEsque;
66
+ responseJSON?: unknown;
65
67
  };
66
68
  }
67
69
 
@@ -164,7 +166,7 @@ export async function fetchHTTPResponse(
164
166
  ...resolvedHeaders,
165
167
  };
166
168
 
167
- return opts.fetch(url, {
169
+ return getFetch(opts.fetch)(url, {
168
170
  method: METHOD[type],
169
171
  signal: ac?.signal,
170
172
  body: body,
@@ -185,12 +187,15 @@ export function httpRequest(
185
187
  return _res.json();
186
188
  })
187
189
  .then((json) => {
190
+ meta.responseJSON = json;
188
191
  resolve({
189
192
  json: json as TRPCResponse,
190
193
  meta,
191
194
  });
192
195
  })
193
- .catch(reject);
196
+ .catch((err) => {
197
+ reject(TRPCClientError.from(err, { meta }));
198
+ });
194
199
  });
195
200
  const cancel = () => {
196
201
  ac?.abort();
@@ -49,11 +49,6 @@ type LoggerLinkFn<TRouter extends AnyRouter> = (
49
49
  opts: LoggerLinkFnOptions<TRouter>,
50
50
  ) => void;
51
51
 
52
- const palette = {
53
- query: ['72e3ff', '3fb0d8'],
54
- mutation: ['c5a3fc', '904dfc'],
55
- subscription: ['ff49e1', 'd83fbe'],
56
- };
57
52
  export interface LoggerLinkOptions<TRouter extends AnyRouter> {
58
53
  logger?: LoggerLinkFn<TRouter>;
59
54
  enabled?: EnabledFn<TRouter>;
@@ -61,6 +56,11 @@ export interface LoggerLinkOptions<TRouter extends AnyRouter> {
61
56
  * Used in the built-in defaultLogger
62
57
  */
63
58
  console?: ConsoleEsque;
59
+ /**
60
+ * Color mode
61
+ * @default typeof window === 'undefined' ? 'ansi' : 'css'
62
+ */
63
+ colorMode?: 'ansi' | 'css';
64
64
  }
65
65
 
66
66
  function isFormData(value: unknown): value is FormData {
@@ -71,50 +71,120 @@ function isFormData(value: unknown): value is FormData {
71
71
  return value instanceof FormData;
72
72
  }
73
73
 
74
+ const palettes = {
75
+ css: {
76
+ query: ['72e3ff', '3fb0d8'],
77
+ mutation: ['c5a3fc', '904dfc'],
78
+ subscription: ['ff49e1', 'd83fbe'],
79
+ },
80
+ ansi: {
81
+ regular: {
82
+ // Cyan background, black and white text respectively
83
+ query: ['\x1b[30;46m', '\x1b[97;46m'],
84
+ // Magenta background, black and white text respectively
85
+ mutation: ['\x1b[30;45m', '\x1b[97;45m'],
86
+ // Green background, black and white text respectively
87
+ subscription: ['\x1b[30;42m', '\x1b[97;42m'],
88
+ },
89
+ bold: {
90
+ query: ['\x1b[1;30;46m', '\x1b[1;97;46m'],
91
+ mutation: ['\x1b[1;30;45m', '\x1b[1;97;45m'],
92
+ subscription: ['\x1b[1;30;42m', '\x1b[1;97;42m'],
93
+ },
94
+ },
95
+ } as const;
96
+
97
+ function constructPartsAndArgs(
98
+ opts: LoggerLinkFnOptions<any> & {
99
+ colorMode: 'ansi' | 'css';
100
+ },
101
+ ) {
102
+ const { direction, type, path, id, input } = opts;
103
+
104
+ const parts: string[] = [];
105
+ const args: any[] = [];
106
+
107
+ if (opts.colorMode === 'ansi') {
108
+ const [lightRegular, darkRegular] = palettes.ansi.regular[type];
109
+ const [lightBold, darkBold] = palettes.ansi.bold[type];
110
+ const reset = '\x1b[0m';
111
+
112
+ parts.push(
113
+ direction === 'up' ? lightRegular : darkRegular,
114
+ direction === 'up' ? '>>' : '<<',
115
+ type,
116
+ direction === 'up' ? lightBold : darkBold,
117
+ `#${id}`,
118
+ path,
119
+ reset,
120
+ );
121
+
122
+ if (direction === 'up') {
123
+ args.push({ input: opts.input });
124
+ } else {
125
+ args.push({
126
+ input: opts.input,
127
+ // strip context from result cause it's too noisy in terminal wihtout collapse mode
128
+ result: 'result' in opts.result ? opts.result.result : opts.result,
129
+ elapsedMs: opts.elapsedMs,
130
+ });
131
+ }
132
+
133
+ return { parts, args };
134
+ }
135
+
136
+ const [light, dark] = palettes.css[type];
137
+ const css = `
138
+ background-color: #${direction === 'up' ? light : dark};
139
+ color: ${direction === 'up' ? 'black' : 'white'};
140
+ padding: 2px;
141
+ `;
142
+
143
+ parts.push(
144
+ '%c',
145
+ direction === 'up' ? '>>' : '<<',
146
+ type,
147
+ `#${id}`,
148
+ `%c${path}%c`,
149
+ '%O',
150
+ );
151
+ args.push(css, `${css}; font-weight: bold;`, `${css}; font-weight: normal;`);
152
+
153
+ if (direction === 'up') {
154
+ args.push({ input, context: opts.context });
155
+ } else {
156
+ args.push({
157
+ input,
158
+ result: opts.result,
159
+ elapsedMs: opts.elapsedMs,
160
+ context: opts.context,
161
+ });
162
+ }
163
+
164
+ return { parts, args };
165
+ }
166
+
74
167
  // maybe this should be moved to it's own package
75
168
  const defaultLogger =
76
- <TRouter extends AnyRouter>(
77
- c: ConsoleEsque = console,
78
- ): LoggerLinkFn<TRouter> =>
169
+ <TRouter extends AnyRouter>({
170
+ c = console,
171
+ colorMode = 'css',
172
+ }: {
173
+ c?: ConsoleEsque;
174
+ colorMode?: 'ansi' | 'css';
175
+ }): LoggerLinkFn<TRouter> =>
79
176
  (props) => {
80
- const { direction, type, path, context, id } = props;
81
- const [light, dark] = palette[type];
82
-
83
177
  const rawInput = props.input;
84
-
85
178
  const input = isFormData(rawInput)
86
179
  ? Object.fromEntries(rawInput)
87
180
  : rawInput;
88
181
 
89
- const css = `
90
- background-color: #${direction === 'up' ? light : dark};
91
- color: ${direction === 'up' ? 'black' : 'white'};
92
- padding: 2px;
93
- `;
182
+ const { parts, args } = constructPartsAndArgs({
183
+ ...props,
184
+ colorMode,
185
+ input,
186
+ });
94
187
 
95
- const parts = [
96
- '%c',
97
- direction === 'up' ? '>>' : '<<',
98
- type,
99
- `#${id}`,
100
- `%c${path}%c`,
101
- '%O',
102
- ];
103
- const args: any[] = [
104
- css,
105
- `${css}; font-weight: bold;`,
106
- `${css}; font-weight: normal;`,
107
- ];
108
- if (props.direction === 'up') {
109
- args.push({ input, context: context });
110
- } else {
111
- args.push({
112
- input,
113
- result: props.result,
114
- elapsedMs: props.elapsedMs,
115
- context,
116
- });
117
- }
118
188
  const fn: 'error' | 'log' =
119
189
  props.direction === 'down' &&
120
190
  props.result &&
@@ -124,12 +194,15 @@ const defaultLogger =
124
194
 
125
195
  c[fn].apply(null, [parts.join(' ')].concat(args));
126
196
  };
197
+
127
198
  export function loggerLink<TRouter extends AnyRouter = AnyRouter>(
128
199
  opts: LoggerLinkOptions<TRouter> = {},
129
200
  ): TRPCLink<TRouter> {
130
201
  const { enabled = () => true } = opts;
131
202
 
132
- const { logger = defaultLogger(opts.console) } = opts;
203
+ const colorMode =
204
+ opts.colorMode ?? (typeof window === 'undefined' ? 'ansi' : 'css');
205
+ const { logger = defaultLogger({ c: opts.console, colorMode }) } = opts;
133
206
 
134
207
  return () => {
135
208
  return ({ op, next }) => {
@@ -6,7 +6,6 @@ import type {
6
6
  } from '@trpc/server/rpc';
7
7
  import type { TRPCClientRuntime } from '../links';
8
8
  import { isObject } from '../links/internals/isObject';
9
- import { TRPCClientError } from '../TRPCClientError';
10
9
 
11
10
  // FIXME:
12
11
  // - the generics here are probably unnecessary
@@ -41,6 +40,12 @@ function transformResultInner<TRouter extends AnyRouter, TOutput>(
41
40
  return { ok: true, result } as const;
42
41
  }
43
42
 
43
+ class TransformResultError extends Error {
44
+ constructor() {
45
+ super('Unable to transform response from server');
46
+ }
47
+ }
48
+
44
49
  /**
45
50
  * Transforms and validates that the result is a valid TRPCResponse
46
51
  * @internal
@@ -56,7 +61,7 @@ export function transformResult<TRouter extends AnyRouter, TOutput>(
56
61
  // Use the data transformers on the JSON-response
57
62
  result = transformResultInner(response, runtime);
58
63
  } catch (err) {
59
- throw new TRPCClientError('Unable to transform response from server');
64
+ throw new TransformResultError();
60
65
  }
61
66
 
62
67
  // check that output of the transformers is a valid TRPCResponse
@@ -65,10 +70,10 @@ export function transformResult<TRouter extends AnyRouter, TOutput>(
65
70
  (!isObject(result.error.error) ||
66
71
  typeof result.error.error.code !== 'number')
67
72
  ) {
68
- throw new TRPCClientError('Badly formatted response from server');
73
+ throw new TransformResultError();
69
74
  }
70
75
  if (result.ok && !isObject(result.result)) {
71
- throw new TRPCClientError('Badly formatted response from server');
76
+ throw new TransformResultError();
72
77
  }
73
78
  return result;
74
79
  }