@trpc/client 9.17.0 → 10.0.0-alpha.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 (152) hide show
  1. package/dist/{TRPCClientError-bfee2bf5.cjs.dev.js → TRPCClientError-a5312a5f.cjs.dev.js} +14 -19
  2. package/dist/{TRPCClientError-09b8a26b.esm.js → TRPCClientError-e1da1ef0.esm.js} +14 -19
  3. package/dist/{TRPCClientError-e9bf96e9.cjs.prod.js → TRPCClientError-ea7d89b2.cjs.prod.js} +14 -19
  4. package/dist/createChain-93a38e1c.cjs.dev.js +27 -0
  5. package/dist/createChain-e45e052a.cjs.prod.js +27 -0
  6. package/dist/createChain-e45e6cf2.esm.js +25 -0
  7. package/dist/declarations/src/TRPCClientError.d.ts +6 -10
  8. package/dist/declarations/src/TRPCClientError.d.ts.map +1 -1
  9. package/dist/declarations/src/createTRPCClient.d.ts +1 -2
  10. package/dist/declarations/src/createTRPCClient.d.ts.map +1 -1
  11. package/dist/declarations/src/index.d.ts +1 -1
  12. package/dist/declarations/src/index.d.ts.map +1 -1
  13. package/dist/declarations/src/internals/TRPCClient.d.ts +17 -14
  14. package/dist/declarations/src/internals/TRPCClient.d.ts.map +1 -1
  15. package/dist/declarations/src/internals/dataLoader.d.ts +1 -1
  16. package/dist/declarations/src/internals/dataLoader.d.ts.map +1 -1
  17. package/dist/declarations/src/links/httpBatchLink.d.ts +2 -1
  18. package/dist/declarations/src/links/httpBatchLink.d.ts.map +1 -1
  19. package/dist/declarations/src/links/httpLink.d.ts +2 -1
  20. package/dist/declarations/src/links/httpLink.d.ts.map +1 -1
  21. package/dist/declarations/src/links/httpUtils.d.ts +22 -0
  22. package/dist/declarations/src/links/httpUtils.d.ts.map +1 -0
  23. package/dist/declarations/src/links/internals/createChain.d.ts +8 -0
  24. package/dist/declarations/src/links/internals/createChain.d.ts.map +1 -0
  25. package/dist/declarations/src/links/internals/transformOperationResult.d.ts +16 -0
  26. package/dist/declarations/src/links/internals/transformOperationResult.d.ts.map +1 -0
  27. package/dist/declarations/src/links/loggerLink.d.ts +4 -3
  28. package/dist/declarations/src/links/loggerLink.d.ts.map +1 -1
  29. package/dist/declarations/src/links/splitLink.d.ts +4 -14
  30. package/dist/declarations/src/links/splitLink.d.ts.map +1 -1
  31. package/dist/declarations/src/links/transformerLink.d.ts +4 -0
  32. package/dist/declarations/src/links/transformerLink.d.ts.map +1 -0
  33. package/dist/declarations/src/links/types.d.ts +40 -0
  34. package/dist/declarations/src/links/types.d.ts.map +1 -0
  35. package/dist/declarations/src/links/wsLink.d.ts +4 -14
  36. package/dist/declarations/src/links/wsLink.d.ts.map +1 -1
  37. package/dist/declarations/src/rx/index.d.ts +3 -0
  38. package/dist/declarations/src/rx/index.d.ts.map +1 -0
  39. package/dist/declarations/src/rx/observable.d.ts +4 -0
  40. package/dist/declarations/src/rx/observable.d.ts.map +1 -0
  41. package/dist/declarations/src/rx/operators/index.d.ts +4 -0
  42. package/dist/declarations/src/rx/operators/index.d.ts.map +1 -0
  43. package/dist/declarations/src/rx/operators/map.d.ts +3 -0
  44. package/dist/declarations/src/rx/operators/map.d.ts.map +1 -0
  45. package/dist/declarations/src/rx/operators/share.d.ts +6 -0
  46. package/dist/declarations/src/rx/operators/share.d.ts.map +1 -0
  47. package/dist/declarations/src/rx/operators/tap.d.ts +3 -0
  48. package/dist/declarations/src/rx/operators/tap.d.ts.map +1 -0
  49. package/dist/declarations/src/rx/types.d.ts +29 -0
  50. package/dist/declarations/src/rx/types.d.ts.map +1 -0
  51. package/dist/declarations/src/rx/util/identity.d.ts +2 -0
  52. package/dist/declarations/src/rx/util/identity.d.ts.map +1 -0
  53. package/dist/declarations/src/rx/util/observableToPromise.d.ts +10 -0
  54. package/dist/declarations/src/rx/util/observableToPromise.d.ts.map +1 -0
  55. package/dist/declarations/src/rx/util/pipe.d.ts +4 -0
  56. package/dist/declarations/src/rx/util/pipe.d.ts.map +1 -0
  57. package/dist/httpUtils-299a66f6.cjs.prod.js +89 -0
  58. package/dist/httpUtils-a1426e14.cjs.dev.js +89 -0
  59. package/dist/httpUtils-e45c9056.esm.js +87 -0
  60. package/dist/observable-01534ea8.esm.js +102 -0
  61. package/dist/observable-9aad8c3e.cjs.dev.js +104 -0
  62. package/dist/observable-ccb54234.cjs.prod.js +104 -0
  63. package/dist/share-5a8c2543.cjs.prod.js +106 -0
  64. package/dist/share-ca585761.esm.js +104 -0
  65. package/dist/share-f2ac9332.cjs.dev.js +106 -0
  66. package/dist/tap-2d565250.cjs.dev.js +32 -0
  67. package/dist/tap-606957cb.esm.js +30 -0
  68. package/dist/tap-d907998d.cjs.prod.js +32 -0
  69. package/dist/trpc-client.cjs.dev.js +136 -215
  70. package/dist/trpc-client.cjs.prod.js +136 -215
  71. package/dist/trpc-client.esm.js +137 -216
  72. package/links/httpBatchLink/dist/trpc-client-links-httpBatchLink.cjs.dev.js +34 -52
  73. package/links/httpBatchLink/dist/trpc-client-links-httpBatchLink.cjs.prod.js +34 -52
  74. package/links/httpBatchLink/dist/trpc-client-links-httpBatchLink.esm.js +33 -51
  75. package/links/httpLink/dist/trpc-client-links-httpLink.cjs.dev.js +29 -52
  76. package/links/httpLink/dist/trpc-client-links-httpLink.cjs.prod.js +29 -52
  77. package/links/httpLink/dist/trpc-client-links-httpLink.esm.js +29 -52
  78. package/links/loggerLink/dist/trpc-client-links-loggerLink.cjs.dev.js +31 -19
  79. package/links/loggerLink/dist/trpc-client-links-loggerLink.cjs.prod.js +31 -19
  80. package/links/loggerLink/dist/trpc-client-links-loggerLink.esm.js +31 -19
  81. package/links/splitLink/dist/trpc-client-links-splitLink.cjs.dev.js +20 -7
  82. package/links/splitLink/dist/trpc-client-links-splitLink.cjs.prod.js +20 -7
  83. package/links/splitLink/dist/trpc-client-links-splitLink.esm.js +20 -7
  84. package/links/transformerLink/package.json +4 -0
  85. package/links/wsLink/dist/trpc-client-links-wsLink.cjs.dev.js +57 -82
  86. package/links/wsLink/dist/trpc-client-links-wsLink.cjs.prod.js +57 -82
  87. package/links/wsLink/dist/trpc-client-links-wsLink.esm.js +57 -82
  88. package/package.json +12 -8
  89. package/rx/dist/trpc-client-rx.cjs.d.ts +1 -0
  90. package/rx/dist/trpc-client-rx.cjs.dev.js +9 -0
  91. package/rx/dist/trpc-client-rx.cjs.js +7 -0
  92. package/rx/dist/trpc-client-rx.cjs.prod.js +9 -0
  93. package/rx/dist/trpc-client-rx.esm.js +1 -0
  94. package/rx/operators/dist/trpc-client-rx-operators.cjs.d.ts +1 -0
  95. package/rx/operators/dist/trpc-client-rx-operators.cjs.dev.js +39 -0
  96. package/rx/operators/dist/trpc-client-rx-operators.cjs.js +7 -0
  97. package/rx/operators/dist/trpc-client-rx-operators.cjs.prod.js +39 -0
  98. package/rx/operators/dist/trpc-client-rx-operators.esm.js +33 -0
  99. package/rx/operators/package.json +4 -0
  100. package/rx/package.json +4 -0
  101. package/src/TRPCClientError.ts +17 -21
  102. package/src/createTRPCClient.ts +4 -3
  103. package/src/index.ts +1 -1
  104. package/src/internals/TRPCClient.ts +80 -88
  105. package/src/internals/dataLoader.ts +1 -1
  106. package/src/internals/transformRPCResponse.ts +2 -2
  107. package/src/links/dedupeLink.test.ts +134 -0
  108. package/src/links/dedupeLink.ts +53 -0
  109. package/src/links/httpBatchLink.ts +37 -37
  110. package/src/links/httpLink.ts +26 -37
  111. package/src/{internals/httpRequest.ts → links/httpUtils.ts} +30 -21
  112. package/src/links/internals/createChain.test.ts +132 -0
  113. package/src/links/internals/createChain.ts +31 -0
  114. package/src/links/internals/transformOperationResult.ts +25 -0
  115. package/src/links/loggerLink.ts +40 -22
  116. package/src/links/retryLink.ts +45 -14
  117. package/src/links/splitLink.test.ts +56 -0
  118. package/src/links/splitLink.ts +23 -38
  119. package/src/links/transformerLink.ts +70 -0
  120. package/src/links/{core.ts → types.ts} +40 -46
  121. package/src/links/wsLink.ts +55 -77
  122. package/src/rx/index.ts +2 -0
  123. package/src/rx/observable.test.ts +83 -0
  124. package/src/rx/observable.ts +70 -0
  125. package/src/rx/operators/index.ts +3 -0
  126. package/src/rx/operators/map.test.ts +92 -0
  127. package/src/rx/operators/map.ts +25 -0
  128. package/src/rx/operators/share.test.ts +60 -0
  129. package/src/rx/operators/share.ts +67 -0
  130. package/src/rx/operators/tap.ts +26 -0
  131. package/src/rx/types.ts +69 -0
  132. package/src/rx/util/identity.ts +3 -0
  133. package/src/rx/util/observableToPromise.ts +49 -0
  134. package/src/rx/util/pipe.ts +22 -0
  135. package/dist/declarations/src/internals/TRPCAbortError.d.ts +0 -4
  136. package/dist/declarations/src/internals/TRPCAbortError.d.ts.map +0 -1
  137. package/dist/declarations/src/internals/executeChain.d.ts +0 -9
  138. package/dist/declarations/src/internals/executeChain.d.ts.map +0 -1
  139. package/dist/declarations/src/internals/httpRequest.d.ts +0 -14
  140. package/dist/declarations/src/internals/httpRequest.d.ts.map +0 -1
  141. package/dist/declarations/src/internals/observable.d.ts +0 -18
  142. package/dist/declarations/src/internals/observable.d.ts.map +0 -1
  143. package/dist/declarations/src/internals/transformRPCResponse.d.ts +0 -15
  144. package/dist/declarations/src/internals/transformRPCResponse.d.ts.map +0 -1
  145. package/dist/declarations/src/links/core.d.ts +0 -45
  146. package/dist/declarations/src/links/core.d.ts.map +0 -1
  147. package/dist/httpRequest-672ff1f0.esm.js +0 -128
  148. package/dist/httpRequest-7bfaf300.cjs.dev.js +0 -132
  149. package/dist/httpRequest-8c422e7a.cjs.prod.js +0 -132
  150. package/src/internals/TRPCAbortError.ts +0 -7
  151. package/src/internals/executeChain.ts +0 -67
  152. package/src/internals/observable.ts +0 -89
@@ -1,28 +1,30 @@
1
1
  import {
2
2
  AnyRouter,
3
- ClientDataTransformerOptions,
4
- DataTransformer,
5
3
  inferHandlerInput,
6
4
  inferProcedureInput,
7
5
  inferProcedureOutput,
8
6
  inferSubscriptionOutput,
9
7
  } from '@trpc/server';
10
8
  import { TRPCResult } from '@trpc/server/rpc';
11
- import { executeChain } from './executeChain';
12
- import { getAbortController } from './fetchHelpers';
9
+ import { CancelFn } from '..';
13
10
  import { getFetch } from '../getFetch';
14
- import { ObservableCallbacks, UnsubscribeFn } from './observable';
15
- import { TRPCAbortError } from './TRPCAbortError';
11
+ import { httpBatchLink } from '../links/httpBatchLink';
12
+ import { createChain } from '../links/internals/createChain';
13
+ import { transformOperationResult } from '../links/internals/transformOperationResult';
16
14
  import {
17
- CancelFn,
18
15
  HTTPHeaders,
19
- LinkRuntimeOptions,
16
+ LinkRuntime,
20
17
  OperationContext,
21
18
  OperationLink,
22
19
  TRPCLink,
23
- } from '../links/core';
24
- import { httpBatchLink } from '../links/httpBatchLink';
20
+ } from '../links/types';
21
+ import { UnsubscribeFn } from '../rx';
22
+ import { inferObservableValue } from '../rx/observable';
23
+ import { share } from '../rx/operators';
24
+ import { Observer } from '../rx/types';
25
+ import { observableToPromise } from '../rx/util/observableToPromise';
25
26
  import { TRPCClientError } from '../TRPCClientError';
27
+ import { getAbortController } from './fetchHelpers';
26
28
 
27
29
  type CancellablePromise<T = unknown> = Promise<T> & {
28
30
  cancel: CancelFn;
@@ -39,8 +41,7 @@ let idCounter = 0;
39
41
  function getRequestId() {
40
42
  return ++idCounter;
41
43
  }
42
-
43
- export type CreateTRPCClientOptions<TRouter extends AnyRouter> = {
44
+ interface CreateTRPCClientBaseOptions {
44
45
  /**
45
46
  * Add ponyfill for fetch
46
47
  */
@@ -53,25 +54,26 @@ export type CreateTRPCClientOptions<TRouter extends AnyRouter> = {
53
54
  * headers to be set on outgoing requests / callback that of said headers
54
55
  */
55
56
  headers?: HTTPHeaders | (() => HTTPHeaders | Promise<HTTPHeaders>);
57
+ }
58
+
59
+ /** @internal */
60
+ export interface CreateTRPCClientWithURLOptions
61
+ extends CreateTRPCClientBaseOptions {
56
62
  /**
57
- * Data transformer
58
- * @link http://localhost:3000/docs/data-transformers
63
+ * HTTP URL of API
59
64
  **/
60
- transformer?: ClientDataTransformerOptions;
61
- } & (
62
- | {
63
- /**
64
- * HTTP URL of API
65
- **/
66
- url: string;
67
- }
68
- | {
69
- /**
70
- * @link http://localhost:3000/docs/links
71
- **/
72
- links: TRPCLink<TRouter>[];
73
- }
74
- );
65
+ url: string;
66
+ }
67
+
68
+ /** @internal */
69
+ export interface CreateTRPCClientWithLinksOptions<TRouter extends AnyRouter>
70
+ extends CreateTRPCClientBaseOptions {
71
+ /**
72
+ * @link http://localhost:3000/docs/links
73
+ **/
74
+ links: TRPCLink<TRouter>[];
75
+ }
76
+
75
77
  type TRPCType = 'subscription' | 'query' | 'mutation';
76
78
  export interface TRPCRequestOptions {
77
79
  /**
@@ -79,27 +81,20 @@ export interface TRPCRequestOptions {
79
81
  */
80
82
  context?: OperationContext;
81
83
  }
84
+
85
+ /** @internal */
86
+ export type CreateTRPCClientOptions<TRouter extends AnyRouter> =
87
+ | CreateTRPCClientWithLinksOptions<TRouter>
88
+ | CreateTRPCClientWithURLOptions;
82
89
  export class TRPCClient<TRouter extends AnyRouter> {
83
90
  private readonly links: OperationLink<TRouter>[];
84
- public readonly runtime: LinkRuntimeOptions;
91
+ public readonly runtime: LinkRuntime;
85
92
 
86
93
  constructor(opts: CreateTRPCClientOptions<TRouter>) {
87
- const transformer: DataTransformer = opts.transformer
88
- ? 'input' in opts.transformer
89
- ? {
90
- serialize: opts.transformer.input.serialize,
91
- deserialize: opts.transformer.output.deserialize,
92
- }
93
- : opts.transformer
94
- : {
95
- serialize: (data) => data,
96
- deserialize: (data) => data,
97
- };
98
-
99
94
  const _fetch = getFetch(opts?.fetch);
100
95
  const AC = getAbortController(opts?.AbortController);
101
96
 
102
- function getHeadersFn(): LinkRuntimeOptions['headers'] {
97
+ function getHeadersFn(): LinkRuntime['headers'] {
103
98
  if (opts.headers) {
104
99
  const headers = opts.headers;
105
100
  return typeof headers === 'function' ? headers : () => headers;
@@ -107,7 +102,6 @@ export class TRPCClient<TRouter extends AnyRouter> {
107
102
  return () => ({});
108
103
  }
109
104
  this.runtime = {
110
- transformer,
111
105
  AbortController: AC as any,
112
106
  fetch: _fetch,
113
107
  headers: getHeadersFn(),
@@ -135,8 +129,8 @@ export class TRPCClient<TRouter extends AnyRouter> {
135
129
  path: string;
136
130
  context?: OperationContext;
137
131
  }) {
138
- const $result = executeChain<TRouter, TInput, TOutput>({
139
- links: this.links as any,
132
+ const chain$ = createChain<TRouter, TInput, TOutput>({
133
+ links: this.links as OperationLink<any, any, any>[],
140
134
  op: {
141
135
  id: getRequestId(),
142
136
  type,
@@ -145,8 +139,7 @@ export class TRPCClient<TRouter extends AnyRouter> {
145
139
  context,
146
140
  },
147
141
  });
148
-
149
- return $result;
142
+ return chain$.pipe(share());
150
143
  }
151
144
  private requestAsPromise<TInput = unknown, TOutput = unknown>(opts: {
152
145
  type: TRPCType;
@@ -154,38 +147,29 @@ export class TRPCClient<TRouter extends AnyRouter> {
154
147
  path: string;
155
148
  context?: OperationContext;
156
149
  }): CancellablePromise<TOutput> {
157
- const $result = this.$request<TInput, TOutput>(opts);
150
+ const req$ = this.$request<TInput, TOutput>(opts);
151
+ type TValue = inferObservableValue<typeof req$>;
152
+ const { promise, abort } = observableToPromise<TValue>(req$);
158
153
 
159
- const promise = new Promise<TOutput>((resolve, reject) => {
160
- const res = $result.get();
161
- if (res?.type === 'data') {
162
- resolve(res.data);
163
- $result.done();
164
- return;
165
- }
166
- $result.subscribe({
167
- onNext: (result) => {
168
- if (result?.type !== 'data') {
169
- return;
170
- }
171
- resolve(result.data);
172
-
173
- $result.done();
174
- },
175
- onError(err) {
176
- reject(err);
177
- $result.done();
178
- },
179
- onDone() {
180
- reject(TRPCClientError.from(new TRPCAbortError()));
181
- },
182
- });
183
- }) as CancellablePromise<TOutput>;
184
- promise.cancel = () => {
185
- $result.done();
186
- };
154
+ const cancellablePromise: CancellablePromise<any> = new Promise<TOutput>(
155
+ (resolve, reject) => {
156
+ promise
157
+ .then((result) => {
158
+ const transformed = transformOperationResult(result);
159
+ if (transformed.ok) {
160
+ resolve(transformed.data as any);
161
+ return;
162
+ }
163
+ reject(transformed.error);
164
+ })
165
+ .catch((err) => {
166
+ reject(TRPCClientError.from(err));
167
+ });
168
+ },
169
+ ) as any;
170
+ cancellablePromise.cancel = abort;
187
171
 
188
- return promise;
172
+ return cancellablePromise;
189
173
  }
190
174
  public query<
191
175
  TQueries extends TRouter['_def']['queries'],
@@ -233,27 +217,35 @@ export class TRPCClient<TRouter extends AnyRouter> {
233
217
  path: TPath,
234
218
  input: TInput,
235
219
  opts: TRPCRequestOptions &
236
- ObservableCallbacks<TRPCResult<TOutput>, TRPCClientError<TRouter>>,
220
+ Partial<Observer<TRPCResult<TOutput>, TRPCClientError<TRouter>>>,
237
221
  ): UnsubscribeFn {
238
- const $res = this.$request<TInput, TOutput>({
222
+ const observable$ = this.$request<TInput, TOutput>({
239
223
  type: 'subscription',
240
224
  path,
241
225
  input,
242
226
  context: opts.context,
243
227
  });
244
- $res.subscribe({
245
- onNext(output) {
246
- if (output) {
247
- opts.onNext?.(output);
228
+ const observer = observable$.subscribe({
229
+ next(result) {
230
+ if ('error' in result.data) {
231
+ const err = TRPCClientError.from(result.data, {
232
+ meta: result.context,
233
+ });
234
+
235
+ opts.error?.(err);
236
+ return;
248
237
  }
238
+ opts.next?.(result.data.result);
239
+ },
240
+ error(err) {
241
+ opts.error?.(err);
249
242
  },
250
- onError(err) {
251
- opts.onError?.(err);
243
+ complete() {
244
+ opts.complete?.();
252
245
  },
253
- onDone: opts.onDone,
254
246
  });
255
247
  return () => {
256
- $res.done();
248
+ observer.unsubscribe();
257
249
  };
258
250
  }
259
251
  }
@@ -1,4 +1,4 @@
1
- import { CancelFn, PromiseAndCancel } from '../links/core';
1
+ import { CancelFn, PromiseAndCancel } from '../links/types';
2
2
 
3
3
  type BatchItem<TKey, TValue> = {
4
4
  cancelled: boolean;
@@ -1,13 +1,13 @@
1
1
  import { TRPCResponse } from '@trpc/server/rpc';
2
2
  import { TRPCClientError } from '../TRPCClientError';
3
- import { LinkRuntimeOptions } from '../links/core';
3
+ import { LinkRuntime } from '../links/types';
4
4
 
5
5
  export function transformRPCResponse({
6
6
  envelope,
7
7
  runtime,
8
8
  }: {
9
9
  envelope: TRPCResponse;
10
- runtime: LinkRuntimeOptions;
10
+ runtime: LinkRuntime;
11
11
  }) {
12
12
  if ('error' in envelope) {
13
13
  return TRPCClientError.from({
@@ -0,0 +1,134 @@
1
+ import { waitFor } from '@testing-library/dom';
2
+ import { AnyRouter } from '@trpc/server';
3
+ import { OperationLink } from '..';
4
+ import { observable } from '../rx/observable';
5
+ import { dedupeLink } from './dedupeLink';
6
+ import { createChain } from './internals/createChain';
7
+
8
+ test('dedupeLink', async () => {
9
+ const endingLinkTriggered = jest.fn();
10
+ const timerTriggered = jest.fn();
11
+ const links: OperationLink<AnyRouter, any, any>[] = [
12
+ // "dedupe link"
13
+ dedupeLink()(null as any),
14
+ ({ op }) => {
15
+ return observable((subscribe) => {
16
+ endingLinkTriggered();
17
+ const timer = setTimeout(() => {
18
+ timerTriggered();
19
+ subscribe.next({
20
+ data: {
21
+ id: null,
22
+ result: {
23
+ type: 'data',
24
+ data: {
25
+ input: op.input,
26
+ },
27
+ },
28
+ },
29
+ });
30
+ subscribe.complete();
31
+ }, 1);
32
+ return () => clearTimeout(timer);
33
+ });
34
+ },
35
+ ];
36
+ {
37
+ const call1 = createChain<AnyRouter, unknown, unknown>({
38
+ links,
39
+ op: {
40
+ type: 'query',
41
+ id: 1,
42
+ input: 'world',
43
+ path: 'hello',
44
+ context: {},
45
+ },
46
+ });
47
+
48
+ const call2 = createChain<AnyRouter, unknown, unknown>({
49
+ links,
50
+ op: {
51
+ type: 'query',
52
+ id: 1,
53
+ input: 'world',
54
+ path: 'hello',
55
+ context: {},
56
+ },
57
+ });
58
+ const next = jest.fn();
59
+ call1.subscribe({ next });
60
+ call2.subscribe({ next });
61
+
62
+ expect(endingLinkTriggered).toHaveBeenCalledTimes(1);
63
+ await waitFor(() => {
64
+ expect(timerTriggered).toHaveBeenCalledTimes(1);
65
+ });
66
+
67
+ expect(next).toHaveBeenCalledTimes(2);
68
+ }
69
+ });
70
+
71
+ test('dedupe - cancel one does not cancel the other', async () => {
72
+ const endingLinkTriggered = jest.fn();
73
+ const timerTriggered = jest.fn();
74
+ const links: OperationLink<AnyRouter, any, any>[] = [
75
+ // "dedupe link"
76
+ dedupeLink()(null as any),
77
+ ({ op }) => {
78
+ return observable((subscribe) => {
79
+ endingLinkTriggered();
80
+ const timer = setTimeout(() => {
81
+ timerTriggered();
82
+ subscribe.next({
83
+ data: {
84
+ id: null,
85
+ result: {
86
+ type: 'data',
87
+ data: {
88
+ input: op.input,
89
+ },
90
+ },
91
+ },
92
+ });
93
+ subscribe.complete();
94
+ }, 1);
95
+ return () => clearTimeout(timer);
96
+ });
97
+ },
98
+ ];
99
+
100
+ {
101
+ const call1 = createChain<AnyRouter, unknown, unknown>({
102
+ links,
103
+ op: {
104
+ type: 'query',
105
+ id: 1,
106
+ input: 'world',
107
+ path: 'hello',
108
+ context: {},
109
+ },
110
+ });
111
+
112
+ const call2 = createChain<AnyRouter, unknown, unknown>({
113
+ links,
114
+ op: {
115
+ type: 'query',
116
+ id: 1,
117
+ input: 'world',
118
+ path: 'hello',
119
+ context: {},
120
+ },
121
+ });
122
+ const next = jest.fn();
123
+ const call1$ = call1.subscribe({ next });
124
+ call2.subscribe({ next });
125
+ call1$.unsubscribe();
126
+
127
+ expect(endingLinkTriggered).toHaveBeenCalledTimes(1);
128
+ await waitFor(() => {
129
+ expect(timerTriggered).toHaveBeenCalledTimes(1);
130
+
131
+ expect(next).toHaveBeenCalledTimes(1);
132
+ });
133
+ }
134
+ });
@@ -0,0 +1,53 @@
1
+ import { AnyRouter } from '@trpc/server';
2
+ import { observable } from '../rx/observable';
3
+ import { share } from '../rx/operators';
4
+ import { Observable } from '../rx/types';
5
+ import { TRPCLink } from './types';
6
+
7
+ export function dedupeLink<
8
+ TRouter extends AnyRouter = AnyRouter,
9
+ >(): TRPCLink<TRouter> {
10
+ // initialized config
11
+ return () => {
12
+ // initialized in app
13
+ const pending: Record<string, Observable<any, any>> = {};
14
+ return ({ op, next }) => {
15
+ // initialized for request
16
+
17
+ if (op.type !== 'query') {
18
+ // pass through
19
+ return next(op);
20
+ }
21
+ const key = JSON.stringify([op.path, op.input]);
22
+ if (pending[key]) {
23
+ // console.log('hooking into pending', { op });
24
+ return observable((observer) => pending[key].subscribe(observer));
25
+ }
26
+
27
+ const shared$ = observable((observer) => {
28
+ function reset() {
29
+ delete pending[key];
30
+ }
31
+ const next$ = next(op).subscribe({
32
+ next(v) {
33
+ observer.next(v);
34
+ },
35
+ error(e) {
36
+ reset();
37
+ observer.error(e);
38
+ },
39
+ complete() {
40
+ reset();
41
+ observer.complete();
42
+ },
43
+ });
44
+ return () => {
45
+ reset();
46
+ next$.unsubscribe();
47
+ };
48
+ }).pipe(share());
49
+ pending[key] = shared$;
50
+ return shared$;
51
+ };
52
+ };
53
+ }
@@ -1,19 +1,14 @@
1
1
  import { AnyRouter, ProcedureType } from '@trpc/server';
2
- import { TRPCResponse } from '@trpc/server/rpc';
3
- import { TRPCClientError } from '../TRPCClientError';
4
2
  import { dataLoader } from '../internals/dataLoader';
5
- import { transformRPCResponse } from '../internals/transformRPCResponse';
6
- import { httpRequest } from '../internals/httpRequest';
7
- import { HTTPLinkOptions, TRPCLink } from './core';
8
- import { TRPCAbortError } from '../internals/TRPCAbortError';
3
+ import { observable } from '../rx/observable';
4
+ import { TRPCLink } from './types';
5
+ import { HTTPLinkOptions, httpRequest, ResponseShape } from './httpUtils';
9
6
 
10
7
  export function httpBatchLink<TRouter extends AnyRouter>(
11
8
  opts: HTTPLinkOptions,
12
9
  ): TRPCLink<TRouter> {
13
10
  const { url } = opts;
14
- // initialized config
15
11
  return (runtime) => {
16
- // initialized in app
17
12
  type Key = { id: number; path: string; input: unknown };
18
13
  const fetcher = (type: ProcedureType) => (keyInputPairs: Key[]) => {
19
14
  const path = keyInputPairs.map((op) => op.path).join(',');
@@ -28,42 +23,47 @@ export function httpBatchLink<TRouter extends AnyRouter>(
28
23
  });
29
24
 
30
25
  return {
31
- promise: promise.then((res: unknown[] | unknown) => {
32
- if (!Array.isArray(res)) {
33
- return keyInputPairs.map(() => res);
34
- }
35
- return res;
26
+ promise: promise.then((res) => {
27
+ const resJSON = Array.isArray(res.json)
28
+ ? res.json
29
+ : keyInputPairs.map(() => res.json);
30
+
31
+ const result = resJSON.map((item) => ({
32
+ meta: res.meta,
33
+ json: item,
34
+ }));
35
+
36
+ return result;
36
37
  }),
37
38
  cancel,
38
39
  };
39
40
  };
40
- const query = dataLoader<Key, TRPCResponse>(fetcher('query'));
41
- const mutation = dataLoader<Key, TRPCResponse>(fetcher('mutation'));
42
- const subscription = dataLoader<Key, TRPCResponse>(fetcher('subscription'));
41
+ const query = dataLoader<Key, ResponseShape>(fetcher('query'));
42
+ const mutation = dataLoader<Key, ResponseShape>(fetcher('mutation'));
43
+ const subscription = dataLoader<Key, ResponseShape>(
44
+ fetcher('subscription'),
45
+ );
43
46
 
44
47
  const loaders = { query, subscription, mutation };
45
- return ({ op, prev, onDestroy }) => {
46
- const loader = loaders[op.type];
47
- const { promise, cancel } = loader.load(op);
48
- let isDone = false;
49
- const prevOnce: typeof prev = (result) => {
50
- if (isDone) {
51
- return;
52
- }
53
- isDone = true;
54
- prev(result);
55
- };
56
- onDestroy(() => {
57
- prevOnce(TRPCClientError.from(new TRPCAbortError(), { isDone: true }));
58
- cancel();
48
+ return ({ op }) => {
49
+ return observable((observer) => {
50
+ const loader = loaders[op.type];
51
+ const { promise, cancel } = loader.load(op);
52
+
53
+ promise
54
+ .then((res) => {
55
+ observer.next({
56
+ context: res.meta,
57
+ data: res.json as any,
58
+ });
59
+ observer.complete();
60
+ })
61
+ .catch((err) => observer.error(err as any));
62
+
63
+ return () => {
64
+ cancel();
65
+ };
59
66
  });
60
- promise
61
- .then((envelope) => {
62
- prevOnce(transformRPCResponse({ envelope, runtime }));
63
- })
64
- .catch((cause) => {
65
- prevOnce(TRPCClientError.from<TRouter>(cause));
66
- });
67
67
  };
68
68
  };
69
69
  }
@@ -1,46 +1,35 @@
1
1
  import { AnyRouter } from '@trpc/server';
2
- import { TRPCClientError } from '../TRPCClientError';
3
- import { transformRPCResponse } from '../internals/transformRPCResponse';
4
- import { httpRequest } from '../internals/httpRequest';
5
- import { HTTPLinkOptions, TRPCLink } from './core';
6
- import { TRPCAbortError } from '../internals/TRPCAbortError';
2
+ import { observable } from '../rx/observable';
3
+ import { TRPCLink } from './types';
4
+ import { HTTPLinkOptions, httpRequest } from './httpUtils';
7
5
 
8
6
  export function httpLink<TRouter extends AnyRouter>(
9
7
  opts: HTTPLinkOptions,
10
8
  ): TRPCLink<TRouter> {
11
9
  const { url } = opts;
10
+ return (runtime) =>
11
+ ({ op }) =>
12
+ observable((observer) => {
13
+ const { path, input, type } = op;
14
+ const { promise, cancel } = httpRequest({
15
+ runtime,
16
+ path,
17
+ input,
18
+ type,
19
+ url,
20
+ });
21
+ promise
22
+ .then((res) => {
23
+ observer.next({
24
+ context: res.meta,
25
+ data: res.json as any,
26
+ });
27
+ observer.complete();
28
+ })
29
+ .catch(observer.error);
12
30
 
13
- // initialized config
14
- return (runtime) => {
15
- // initialized in app
16
- return ({ op, prev, onDestroy }) => {
17
- const { path, input, type } = op;
18
- const { promise, cancel } = httpRequest({
19
- runtime,
20
- type,
21
- input,
22
- url,
23
- path,
24
- });
25
- let isDone = false;
26
- const prevOnce: typeof prev = (result) => {
27
- if (isDone) {
28
- return;
29
- }
30
- isDone = true;
31
- prev(result);
32
- };
33
- onDestroy(() => {
34
- prevOnce(TRPCClientError.from(new TRPCAbortError(), { isDone: true }));
35
- cancel();
31
+ return () => {
32
+ cancel();
33
+ };
36
34
  });
37
- promise
38
- .then((envelope) => {
39
- prevOnce(transformRPCResponse({ envelope, runtime }));
40
- })
41
- .catch((cause) => {
42
- prevOnce(TRPCClientError.from(cause));
43
- });
44
- };
45
- };
46
35
  }