@trpc/client 10.0.0-alpha.21 → 10.0.0-alpha.24

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 (90) hide show
  1. package/dist/index.js +329 -276
  2. package/dist/index.mjs +317 -263
  3. package/dist/internals/TRPCClient.d.ts +12 -7
  4. package/dist/internals/TRPCClient.d.ts.map +1 -1
  5. package/dist/internals/dataLoader.d.ts +7 -6
  6. package/dist/internals/dataLoader.d.ts.map +1 -1
  7. package/dist/links/dedupeLink.d.ts.map +1 -1
  8. package/dist/links/httpBatchLink.d.ts +1 -1
  9. package/dist/links/httpBatchLink.d.ts.map +1 -1
  10. package/dist/links/index.d.ts +0 -2
  11. package/dist/links/index.d.ts.map +1 -1
  12. package/dist/links/internals/httpUtils.d.ts +15 -7
  13. package/dist/links/internals/httpUtils.d.ts.map +1 -1
  14. package/dist/links/internals/transformOperationResult.d.ts +17 -3
  15. package/dist/links/internals/transformOperationResult.d.ts.map +1 -1
  16. package/dist/links/loggerLink.d.ts.map +1 -1
  17. package/dist/links/retryLink.d.ts.map +1 -1
  18. package/dist/links/types.d.ts +10 -7
  19. package/dist/links/types.d.ts.map +1 -1
  20. package/dist/links/wsLink.d.ts +1 -1
  21. package/dist/links/wsLink.d.ts.map +1 -1
  22. package/links/wsLink/dist/trpc-client-links-wsLink.cjs.dev.js +2 -0
  23. package/links/wsLink/dist/trpc-client-links-wsLink.cjs.prod.js +2 -0
  24. package/links/wsLink/dist/trpc-client-links-wsLink.esm.js +2 -0
  25. package/package.json +5 -11
  26. package/src/internals/TRPCClient.ts +48 -24
  27. package/src/internals/dataLoader.ts +116 -54
  28. package/src/links/dedupeLink.test.ts +1 -1
  29. package/src/links/dedupeLink.ts +1 -2
  30. package/src/links/httpBatchLink.ts +54 -31
  31. package/src/links/httpLink.ts +3 -3
  32. package/src/links/index.ts +0 -2
  33. package/src/links/internals/createChain.test.ts +1 -1
  34. package/src/links/internals/createChain.ts +1 -1
  35. package/src/links/internals/httpUtils.ts +52 -31
  36. package/src/links/internals/transformOperationResult.ts +40 -12
  37. package/src/links/loggerLink.ts +1 -1
  38. package/src/links/retryLink.ts +1 -2
  39. package/src/links/splitLink.test.ts +1 -1
  40. package/src/links/splitLink.ts +4 -4
  41. package/src/links/types.ts +12 -7
  42. package/src/links/wsLink.ts +13 -10
  43. package/dist/links/transformerLink.d.ts +0 -4
  44. package/dist/links/transformerLink.d.ts.map +0 -1
  45. package/dist/observable/index.d.ts +0 -4
  46. package/dist/observable/index.d.ts.map +0 -1
  47. package/dist/observable/index.js +0 -38
  48. package/dist/observable/index.mjs +0 -31
  49. package/dist/observable/index.ts.js +0 -1
  50. package/dist/observable/internals/identity.d.ts +0 -2
  51. package/dist/observable/internals/identity.d.ts.map +0 -1
  52. package/dist/observable/internals/observableToPromise.d.ts +0 -10
  53. package/dist/observable/internals/observableToPromise.d.ts.map +0 -1
  54. package/dist/observable/internals/pipe.d.ts +0 -4
  55. package/dist/observable/internals/pipe.d.ts.map +0 -1
  56. package/dist/observable/observable.d.ts +0 -4
  57. package/dist/observable/observable.d.ts.map +0 -1
  58. package/dist/observable/operators/index.d.ts +0 -4
  59. package/dist/observable/operators/index.d.ts.map +0 -1
  60. package/dist/observable/operators/map.d.ts +0 -3
  61. package/dist/observable/operators/map.d.ts.map +0 -1
  62. package/dist/observable/operators/share.d.ts +0 -6
  63. package/dist/observable/operators/share.d.ts.map +0 -1
  64. package/dist/observable/operators/tap.d.ts +0 -3
  65. package/dist/observable/operators/tap.d.ts.map +0 -1
  66. package/dist/observable/types.d.ts +0 -29
  67. package/dist/observable/types.d.ts.map +0 -1
  68. package/dist/tap-0dcdee86.js +0 -243
  69. package/dist/tap-136a9a90.mjs +0 -239
  70. package/observable/dist/trpc-client-rx.cjs.d.ts +0 -1
  71. package/observable/dist/trpc-client-rx.cjs.dev.js +0 -9
  72. package/observable/dist/trpc-client-rx.cjs.js +0 -7
  73. package/observable/dist/trpc-client-rx.cjs.prod.js +0 -9
  74. package/observable/dist/trpc-client-rx.esm.js +0 -1
  75. package/observable/index.d.ts +0 -1
  76. package/observable/index.js +0 -1
  77. package/src/links/transformerLink.ts +0 -74
  78. package/src/observable/index.ts +0 -3
  79. package/src/observable/internals/identity.ts +0 -3
  80. package/src/observable/internals/observableToPromise.ts +0 -49
  81. package/src/observable/internals/pipe.ts +0 -22
  82. package/src/observable/observable.test.ts +0 -101
  83. package/src/observable/observable.ts +0 -70
  84. package/src/observable/operators/index.ts +0 -3
  85. package/src/observable/operators/map.test.ts +0 -92
  86. package/src/observable/operators/map.ts +0 -25
  87. package/src/observable/operators/share.test.ts +0 -60
  88. package/src/observable/operators/share.ts +0 -67
  89. package/src/observable/operators/tap.ts +0 -26
  90. package/src/observable/types.ts +0 -69
@@ -1,18 +1,32 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
1
2
  import { CancelFn, PromiseAndCancel } from '../links/types';
2
3
 
3
4
  type BatchItem<TKey, TValue> = {
4
- cancelled: boolean;
5
+ aborted: boolean;
5
6
  key: TKey;
6
7
  resolve: (value: TValue) => void;
7
8
  reject: (error: Error) => void;
9
+ batch: Batch<TKey, TValue> | null;
8
10
  };
9
11
  type Batch<TKey, TValue> = {
10
12
  items: BatchItem<TKey, TValue>[];
11
13
  cancel: CancelFn;
12
14
  };
13
- type BatchLoadFn<TKey, TValue> = (keys: TKey[]) => {
14
- promise: Promise<TValue[]>;
15
- cancel: CancelFn;
15
+ type BatchLoader<TKey, TValue> = {
16
+ validate: (keys: TKey[]) => boolean;
17
+ fetch: (keys: TKey[]) => {
18
+ promise: Promise<TValue[]>;
19
+ cancel: CancelFn;
20
+ };
21
+ };
22
+
23
+ /**
24
+ * A function that should never be called unless we messed something up.
25
+ */
26
+ const throwFatalError = () => {
27
+ throw new Error(
28
+ 'Something went wrong. Please submit an issue at https://github.com/trpc/trpc/issues/new',
29
+ );
16
30
  };
17
31
 
18
32
  /**
@@ -21,79 +35,127 @@ type BatchLoadFn<TKey, TValue> = (keys: TKey[]) => {
21
35
  * When cancelling a single fetch the whole batch will be cancelled only when _all_ items are cancelled
22
36
  */
23
37
  export function dataLoader<TKey, TValue>(
24
- fetchMany: BatchLoadFn<TKey, TValue>,
25
- opts?: { maxBatchSize?: number },
38
+ batchLoader: BatchLoader<TKey, TValue>,
26
39
  ) {
27
- let batch: Batch<TKey, TValue> | null = null;
28
- let dispatchTimer: NodeJS.Timer | number | null = null;
40
+ let pendingItems: BatchItem<TKey, TValue>[] | null = null;
41
+ let dispatchTimer: ReturnType<typeof setTimeout> | null = null;
29
42
 
30
- const destroyTimerAndBatch = () => {
43
+ const destroyTimerAndPendingItems = () => {
31
44
  clearTimeout(dispatchTimer as any);
32
45
  dispatchTimer = null;
33
- batch = null;
46
+ pendingItems = null;
34
47
  };
48
+
49
+ /**
50
+ * Iterate through the items and split them into groups based on the `batchLoader`'s validate function
51
+ */
52
+ function groupItems(items: BatchItem<TKey, TValue>[]) {
53
+ const groupedItems: BatchItem<TKey, TValue>[][] = [[]];
54
+ let index = 0;
55
+ while (true) {
56
+ const item = items[index];
57
+ if (!item) {
58
+ // we're done
59
+ break;
60
+ }
61
+ const lastGroup = groupedItems[groupedItems.length - 1];
62
+
63
+ if (item.aborted) {
64
+ // Item was aborted before it was dispatched
65
+ item.reject(new Error('Aborted'));
66
+ index++;
67
+ continue;
68
+ }
69
+
70
+ const isValid = batchLoader.validate(
71
+ lastGroup.concat(item).map((it) => it.key),
72
+ );
73
+
74
+ if (isValid) {
75
+ lastGroup.push(item);
76
+ index++;
77
+ continue;
78
+ }
79
+
80
+ if (lastGroup.length === 0) {
81
+ item.reject(new Error('Input is too big for a single dispatch'));
82
+ index++;
83
+ continue;
84
+ }
85
+ // Create new group, next iteration will try to add the item to that
86
+ groupedItems.push([]);
87
+ }
88
+ return groupedItems;
89
+ }
90
+
35
91
  function dispatch() {
36
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
37
- const batchCopy = batch!;
38
- destroyTimerAndBatch();
39
- const { promise, cancel } = fetchMany(batchCopy.items.map((v) => v.key));
40
- batchCopy.cancel = cancel;
41
-
42
- promise
43
- .then((result) => {
44
- for (let i = 0; i < result.length; i++) {
45
- const value = result[i];
46
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
47
- batchCopy.items[i]!.resolve(value!);
48
- }
49
- })
50
- .catch((cause) => {
51
- for (const item of batchCopy.items) {
52
- item.reject(cause);
53
- }
54
- });
92
+ const groupedItems = groupItems(pendingItems!);
93
+ destroyTimerAndPendingItems();
94
+
95
+ // Create batches for each group of items
96
+ for (const items of groupedItems) {
97
+ if (!items.length) {
98
+ continue;
99
+ }
100
+ const batch: Batch<TKey, TValue> = {
101
+ items,
102
+ cancel: throwFatalError,
103
+ };
104
+ for (const item of items) {
105
+ item.batch = batch;
106
+ }
107
+ const { promise, cancel } = batchLoader.fetch(
108
+ batch.items.map((_item) => _item.key),
109
+ );
110
+ batch.cancel = cancel;
111
+
112
+ promise
113
+ .then((result) => {
114
+ for (let i = 0; i < result.length; i++) {
115
+ const value = result[i];
116
+ const item = batch.items[i];
117
+ item.resolve(value);
118
+ item.batch = null;
119
+ }
120
+ })
121
+ .catch((cause) => {
122
+ for (const item of batch.items) {
123
+ item.reject(cause);
124
+ item.batch = null;
125
+ }
126
+ });
127
+ }
55
128
  }
56
129
  function load(key: TKey): PromiseAndCancel<TValue> {
57
- const batchItem = {
58
- cancelled: false,
130
+ const item: BatchItem<TKey, TValue> = {
131
+ aborted: false,
59
132
  key,
133
+ batch: null,
134
+ resolve: throwFatalError,
135
+ reject: throwFatalError,
60
136
  };
61
137
 
62
- if (!batch) {
63
- batch = {
64
- items: [],
65
- cancel() {
66
- destroyTimerAndBatch();
67
- },
68
- };
69
- }
70
- const thisBatch = batch;
71
138
  const promise = new Promise<TValue>((resolve, reject) => {
72
- const item = batchItem as any as BatchItem<TKey, TValue>;
73
139
  item.reject = reject;
74
140
  item.resolve = resolve;
75
- thisBatch.items.push(item);
76
-
77
- if (
78
- typeof opts?.maxBatchSize !== 'undefined' &&
79
- thisBatch.items.length >= opts.maxBatchSize
80
- ) {
81
- dispatch();
82
- return;
141
+
142
+ if (!pendingItems) {
143
+ pendingItems = [];
83
144
  }
145
+ pendingItems.push(item);
84
146
  });
85
147
 
86
148
  if (!dispatchTimer) {
87
149
  dispatchTimer = setTimeout(dispatch);
88
150
  }
89
151
  const cancel = () => {
90
- batchItem.cancelled = true;
152
+ item.aborted = true;
91
153
 
92
- if (thisBatch.items.some((item) => !item.cancelled)) {
93
- // there are still things that can be resolved
94
- return;
154
+ if (item.batch?.items.every((item) => item.aborted)) {
155
+ // All items in the batch have been cancelled
156
+ item.batch.cancel();
157
+ item.batch = null;
95
158
  }
96
- thisBatch.cancel();
97
159
  };
98
160
 
99
161
  return { promise, cancel };
@@ -1,7 +1,7 @@
1
1
  import { waitFor } from '@testing-library/dom';
2
2
  import { AnyRouter } from '@trpc/server';
3
+ import { observable } from '@trpc/server/observable';
3
4
  import { OperationLink } from '..';
4
- import { observable } from '../observable';
5
5
  import { dedupeLink } from './dedupeLink';
6
6
  import { createChain } from './internals/createChain';
7
7
 
@@ -1,6 +1,5 @@
1
1
  import { AnyRouter } from '@trpc/server';
2
- import { observable, share } from '../observable';
3
- import { Observable } from '../observable/types';
2
+ import { Observable, observable, share } from '@trpc/server/observable';
4
3
  import { TRPCLink } from './types';
5
4
 
6
5
  export function dedupeLink<
@@ -1,57 +1,80 @@
1
1
  import { AnyRouter, ProcedureType } from '@trpc/server';
2
+ import { observable } from '@trpc/server/observable';
2
3
  import { dataLoader } from '../internals/dataLoader';
3
- import { observable } from '../observable';
4
4
  import {
5
5
  HTTPLinkOptions,
6
6
  ResponseShape,
7
+ getUrl,
7
8
  httpRequest,
8
9
  } from './internals/httpUtils';
9
10
  import { TRPCLink } from './types';
10
11
 
11
12
  export interface HttpBatchLinkOptions extends HTTPLinkOptions {
12
- maxBatchSize?: number;
13
+ maxURLLength?: number;
13
14
  }
15
+
14
16
  export function httpBatchLink<TRouter extends AnyRouter>(
15
17
  opts: HttpBatchLinkOptions,
16
18
  ): TRPCLink<TRouter> {
17
- const { url } = opts;
18
19
  // initialized config
19
20
  return (runtime) => {
20
- type Key = { id: number; path: string; input: unknown };
21
+ type BatchOperation = { id: number; path: string; input: unknown };
21
22
 
22
- const fetcher = (type: ProcedureType) => (keyInputPairs: Key[]) => {
23
- const path = keyInputPairs.map((op) => op.path).join(',');
24
- const inputs = keyInputPairs.map((op) => op.input);
23
+ const maxURLLength = opts.maxURLLength || Infinity;
25
24
 
26
- const { promise, cancel } = httpRequest({
27
- url,
28
- inputs,
29
- path,
30
- runtime,
31
- type,
32
- });
25
+ const batchLoader = (type: ProcedureType) => {
26
+ const validate = (batchOps: BatchOperation[]) => {
27
+ if (maxURLLength === Infinity) {
28
+ // escape hatch for quick calcs
29
+ return true;
30
+ }
31
+ const path = batchOps.map((op) => op.path).join(',');
32
+ const inputs = batchOps.map((op) => op.input);
33
+
34
+ const url = getUrl({ url: opts.url, runtime, type, path, inputs });
35
+ return url.length <= maxURLLength;
36
+ };
37
+
38
+ const fetch = (batchOps: BatchOperation[]) => {
39
+ const path = batchOps.map((op) => op.path).join(',');
40
+ const inputs = batchOps.map((op) => op.input);
41
+
42
+ const { promise, cancel } = httpRequest({
43
+ url: opts.url,
44
+ runtime,
45
+ type,
46
+ path,
47
+ inputs,
48
+ });
33
49
 
34
- return {
35
- promise: promise.then((res) => {
36
- const resJSON = Array.isArray(res.json)
37
- ? res.json
38
- : keyInputPairs.map(() => res.json);
50
+ return {
51
+ promise: promise.then((res) => {
52
+ const resJSON = Array.isArray(res.json)
53
+ ? res.json
54
+ : batchOps.map(() => res.json);
39
55
 
40
- const result = resJSON.map((item) => ({
41
- meta: res.meta,
42
- json: item,
43
- }));
56
+ const result = resJSON.map((item) => ({
57
+ meta: res.meta,
58
+ json: item,
59
+ }));
44
60
 
45
- return result;
46
- }),
47
- cancel,
61
+ return result;
62
+ }),
63
+ cancel,
64
+ };
48
65
  };
66
+
67
+ return { validate, fetch };
49
68
  };
50
- const query = dataLoader<Key, ResponseShape>(fetcher('query'), opts);
51
- const mutation = dataLoader<Key, ResponseShape>(fetcher('mutation'), opts);
52
- const subscription = dataLoader<Key, ResponseShape>(
53
- fetcher('subscription'),
54
- opts,
69
+
70
+ const query = dataLoader<BatchOperation, ResponseShape>(
71
+ batchLoader('query'),
72
+ );
73
+ const mutation = dataLoader<BatchOperation, ResponseShape>(
74
+ batchLoader('mutation'),
75
+ );
76
+ const subscription = dataLoader<BatchOperation, ResponseShape>(
77
+ batchLoader('subscription'),
55
78
  );
56
79
 
57
80
  const loaders = { query, subscription, mutation };
@@ -1,5 +1,5 @@
1
1
  import { AnyRouter } from '@trpc/server';
2
- import { observable } from '../observable';
2
+ import { observable } from '@trpc/server/observable';
3
3
  import { HTTPLinkOptions, httpRequest } from './internals/httpUtils';
4
4
  import { TRPCLink } from './types';
5
5
 
@@ -12,11 +12,11 @@ export function httpLink<TRouter extends AnyRouter>(
12
12
  observable((observer) => {
13
13
  const { path, input, type } = op;
14
14
  const { promise, cancel } = httpRequest({
15
+ url,
15
16
  runtime,
17
+ type,
16
18
  path,
17
19
  input,
18
- type,
19
- url,
20
20
  });
21
21
  promise
22
22
  .then((res) => {
@@ -2,9 +2,7 @@ export * from './dedupeLink';
2
2
  export * from './httpBatchLink';
3
3
  export * from './httpLink';
4
4
  export * from './loggerLink';
5
- export * from './loggerLink';
6
5
  export * from './retryLink';
7
6
  export * from './splitLink';
8
- export * from './transformerLink';
9
7
  export * from './wsLink';
10
8
  export * from './types';
@@ -1,5 +1,5 @@
1
+ import { observable } from '@trpc/server/observable';
1
2
  import { AnyRouter } from '@trpc/server/src';
2
- import { observable } from '../../observable';
3
3
  import { createChain } from './createChain';
4
4
 
5
5
  describe('chain', () => {
@@ -1,5 +1,5 @@
1
1
  import { AnyRouter } from '@trpc/server';
2
- import { observable } from '../../observable/observable';
2
+ import { observable } from '@trpc/server/observable';
3
3
  import { Operation, OperationLink, OperationResultObservable } from '../types';
4
4
 
5
5
  /** @internal */
@@ -1,5 +1,5 @@
1
1
  import { ProcedureType } from '@trpc/server';
2
- import { LinkRuntime, PromiseAndCancel } from '../types';
2
+ import { PromiseAndCancel, TRPCClientRuntime } from '../types';
3
3
 
4
4
  export interface HTTPLinkOptions {
5
5
  url: string;
@@ -14,6 +14,7 @@ function arrayToDict(array: unknown[]) {
14
14
  }
15
15
  return dict;
16
16
  }
17
+
17
18
  const METHOD = {
18
19
  query: 'GET',
19
20
  mutation: 'POST',
@@ -26,50 +27,70 @@ export interface ResponseShape {
26
27
  response: Response;
27
28
  };
28
29
  }
29
- export function httpRequest(
30
- props: {
31
- runtime: LinkRuntime;
30
+
31
+ type GetInputOptions = {
32
+ runtime: TRPCClientRuntime;
33
+ } & ({ inputs: unknown[] } | { input: unknown });
34
+
35
+ function getInput(opts: GetInputOptions) {
36
+ return 'input' in opts
37
+ ? opts.runtime.transformer.serialize(opts.input)
38
+ : arrayToDict(
39
+ opts.inputs.map((_input) => opts.runtime.transformer.serialize(_input)),
40
+ );
41
+ }
42
+
43
+ export type HTTPRequestOptions = HTTPLinkOptions &
44
+ GetInputOptions & {
32
45
  type: ProcedureType;
33
46
  path: string;
34
- url: string;
35
- } & ({ inputs: unknown[] } | { input: unknown }),
36
- ): PromiseAndCancel<ResponseShape> {
37
- const { type, runtime: rt, path } = props;
38
- const ac = rt.AbortController ? new rt.AbortController() : null;
39
-
40
- const input = 'input' in props ? props.input : arrayToDict(props.inputs);
47
+ };
41
48
 
42
- function getUrl() {
43
- let url = props.url + '/' + path;
44
- const queryParts: string[] = [];
45
- if ('inputs' in props) {
46
- queryParts.push('batch=1');
47
- }
48
- if (type === 'query' && input !== undefined) {
49
+ export function getUrl(opts: HTTPRequestOptions) {
50
+ let url = opts.url + '/' + opts.path;
51
+ const queryParts: string[] = [];
52
+ if ('inputs' in opts) {
53
+ queryParts.push('batch=1');
54
+ }
55
+ if (opts.type === 'query') {
56
+ const input = getInput(opts);
57
+ if (input !== undefined) {
49
58
  queryParts.push(`input=${encodeURIComponent(JSON.stringify(input))}`);
50
59
  }
51
- if (queryParts.length) {
52
- url += '?' + queryParts.join('&');
53
- }
54
- return url;
55
60
  }
56
- function getBody() {
57
- if (type === 'query') {
58
- return undefined;
59
- }
60
- return input !== undefined ? JSON.stringify(input) : undefined;
61
+ if (queryParts.length) {
62
+ url += '?' + queryParts.join('&');
63
+ }
64
+ return url;
65
+ }
66
+
67
+ type GetBodyOptions = { type: ProcedureType } & GetInputOptions;
68
+
69
+ export function getBody(opts: GetBodyOptions) {
70
+ if (opts.type === 'query') {
71
+ return undefined;
61
72
  }
73
+ const input = getInput(opts);
74
+ return input !== undefined ? JSON.stringify(input) : undefined;
75
+ }
76
+
77
+ export function httpRequest(
78
+ opts: HTTPRequestOptions,
79
+ ): PromiseAndCancel<ResponseShape> {
80
+ const { type, runtime } = opts;
81
+ const ac = runtime.AbortController ? new runtime.AbortController() : null;
62
82
 
63
83
  const promise = new Promise<ResponseShape>((resolve, reject) => {
64
- const url = getUrl();
84
+ const url = getUrl(opts);
85
+ const body = getBody(opts);
65
86
 
66
87
  const meta = {} as any as ResponseShape['meta'];
67
- Promise.resolve(rt.headers())
88
+ Promise.resolve(runtime.headers())
68
89
  .then((headers) =>
69
- rt.fetch(url, {
90
+ runtime.fetch(url, {
70
91
  method: METHOD[type],
71
92
  signal: ac?.signal,
72
- body: getBody(),
93
+ body: body,
73
94
  headers: {
74
95
  'content-type': 'application/json',
75
96
  ...headers,
@@ -1,25 +1,53 @@
1
1
  import { AnyRouter } from '@trpc/server';
2
+ import { TRPCResultMessage } from '@trpc/server/rpc';
3
+ import { TRPCClientRuntime } from '..';
2
4
  import { TRPCClientError } from '../../TRPCClientError';
3
5
  import { OperationResult } from '../types';
4
6
 
5
7
  /** @internal */
6
8
  export function transformOperationResult<TRouter extends AnyRouter, TOutput>(
7
9
  result: OperationResult<TRouter, TOutput>,
10
+ runtime: TRPCClientRuntime,
8
11
  ) {
9
12
  const { context } = result;
10
13
 
11
14
  if ('error' in result.data) {
12
- const error = TRPCClientError.from<TRouter>(result.data);
13
- return {
14
- ok: false,
15
- error,
16
- context,
17
- } as const;
15
+ const error = TRPCClientError.from<TRouter>({
16
+ ...result.data,
17
+ error: runtime.transformer.deserialize(result.data.error),
18
+ });
19
+ return { ok: false, error, context } as const;
18
20
  }
19
- const data = (result.data.result as any).data as TOutput;
20
- return {
21
- ok: true,
22
- data,
23
- context,
24
- } as const;
21
+
22
+ const data = runtime.transformer.deserialize(
23
+ (result.data.result as any).data,
24
+ ) as TOutput;
25
+ return { ok: true, data, context } as const;
26
+ }
27
+
28
+ /** @internal */
29
+ export function transformSubscriptionOperationResult<
30
+ TRouter extends AnyRouter,
31
+ TOutput,
32
+ >(result: OperationResult<TRouter, TOutput>, runtime: TRPCClientRuntime) {
33
+ const { context } = result;
34
+
35
+ if ('error' in result.data) {
36
+ const error = TRPCClientError.from<TRouter>(
37
+ {
38
+ ...result.data,
39
+ error: runtime.transformer.deserialize(result.data.error),
40
+ },
41
+ { meta: context },
42
+ );
43
+ return { ok: false, error, context } as const;
44
+ }
45
+
46
+ const data = {
47
+ ...result.data.result,
48
+ ...((result.data.result as any).type === 'data' && {
49
+ data: runtime.transformer.deserialize((result.data.result as any).data),
50
+ }),
51
+ } as TRPCResultMessage<TOutput>;
52
+ return { ok: true, data, context } as const;
25
53
  }
@@ -1,6 +1,6 @@
1
1
  import { AnyRouter } from '@trpc/server';
2
+ import { observable, tap } from '@trpc/server/observable';
2
3
  import { TRPCClientError } from '..';
3
- import { observable, tap } from '../observable';
4
4
  import { Operation, OperationResult, TRPCLink } from './types';
5
5
 
6
6
  type ConsoleEsque = {
@@ -1,6 +1,5 @@
1
1
  import { AnyRouter } from '@trpc/server';
2
- import { observable } from '../observable/observable';
3
- import { Unsubscribable } from '../observable/types';
2
+ import { Unsubscribable, observable } from '@trpc/server/observable';
4
3
  import { TRPCLink } from './types';
5
4
 
6
5
  export function retryLink<TRouter extends AnyRouter = AnyRouter>(opts: {
@@ -1,9 +1,9 @@
1
1
  /* eslint-disable @typescript-eslint/no-empty-function */
2
+ import { observable } from '@trpc/server/observable';
2
3
  import { splitLink } from '../';
3
4
  import { OperationLink, TRPCLink } from '../';
4
5
  import { AnyRouter } from '../../../server/src';
5
6
  import { createChain } from '../links/internals/createChain';
6
- import { observable } from '../observable';
7
7
 
8
8
  test('splitLink', () => {
9
9
  const wsLinkSpy = jest.fn();
@@ -1,5 +1,5 @@
1
1
  import { AnyRouter } from '@trpc/server';
2
- import { observable } from '../observable';
2
+ import { observable } from '@trpc/server/observable';
3
3
  import { createChain } from './internals/createChain';
4
4
  import { Operation, TRPCLink } from './types';
5
5
 
@@ -17,9 +17,9 @@ export function splitLink<TRouter extends AnyRouter = AnyRouter>(opts: {
17
17
  */
18
18
  false: TRPCLink<TRouter> | TRPCLink<TRouter>[];
19
19
  }): TRPCLink<TRouter> {
20
- return (rt) => {
21
- const yes = asArray(opts.true).map((link) => link(rt));
22
- const no = asArray(opts.false).map((link) => link(rt));
20
+ return (runtime) => {
21
+ const yes = asArray(opts.true).map((link) => link(runtime));
22
+ const no = asArray(opts.false).map((link) => link(runtime));
23
23
  return (props) => {
24
24
  return observable((observer) => {
25
25
  const links = opts.condition(props.op) ? yes : no;
@@ -1,7 +1,7 @@
1
- import { AnyRouter, inferRouterError } from '@trpc/server';
2
- import { TRPCResponse } from '@trpc/server/rpc';
1
+ import { AnyRouter, DataTransformer, inferRouterError } from '@trpc/server';
2
+ import { Observable, Observer } from '@trpc/server/observable';
3
+ import { TRPCResponse, TRPCResponseMessage } from '@trpc/server/rpc';
3
4
  import { TRPCClientError } from '../TRPCClientError';
4
- import { Observable, Observer } from '../observable/types';
5
5
 
6
6
  export type CancelFn = () => void;
7
7
 
@@ -30,14 +30,19 @@ export type TRPCFetch = (
30
30
  options?: RequestInit,
31
31
  ) => Promise<Response>;
32
32
 
33
- export interface LinkRuntime {
34
- headers: () => HTTPHeaders | Promise<HTTPHeaders>;
33
+ export interface TRPCClientRuntime {
35
34
  fetch: TRPCFetch;
36
35
  AbortController?: typeof AbortController;
36
+ headers: () => HTTPHeaders | Promise<HTTPHeaders>;
37
+ transformer: DataTransformer;
37
38
  }
38
39
 
40
+ type OperationResultData<TRouter extends AnyRouter, TOutput> =
41
+ | TRPCResponse<TOutput, inferRouterError<TRouter>>
42
+ | TRPCResponseMessage<TOutput, inferRouterError<TRouter>>;
43
+
39
44
  export interface OperationResult<TRouter extends AnyRouter, TOutput> {
40
- data: TRPCResponse<TOutput, inferRouterError<TRouter>>;
45
+ data: OperationResultData<TRouter, TOutput>;
41
46
  context?: OperationContext;
42
47
  }
43
48
 
@@ -61,5 +66,5 @@ export type OperationLink<
61
66
  }) => OperationResultObservable<TRouter, TOutput>;
62
67
 
63
68
  export type TRPCLink<TRouter extends AnyRouter> = (
64
- opts: LinkRuntime,
69
+ opts: TRPCClientRuntime,
65
70
  ) => OperationLink<TRouter>;