@trpc/client 9.16.0 → 10.0.0-alpha.1

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 (157) 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 -11
  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/dist/trpc-client-links-transformerLink.cjs.d.ts +1 -0
  85. package/links/transformerLink/dist/trpc-client-links-transformerLink.cjs.dev.js +67 -0
  86. package/links/transformerLink/dist/trpc-client-links-transformerLink.cjs.js +7 -0
  87. package/links/transformerLink/dist/trpc-client-links-transformerLink.cjs.prod.js +67 -0
  88. package/links/transformerLink/dist/trpc-client-links-transformerLink.esm.js +63 -0
  89. package/links/transformerLink/package.json +4 -0
  90. package/links/wsLink/dist/trpc-client-links-wsLink.cjs.dev.js +57 -81
  91. package/links/wsLink/dist/trpc-client-links-wsLink.cjs.prod.js +57 -81
  92. package/links/wsLink/dist/trpc-client-links-wsLink.esm.js +57 -81
  93. package/package.json +12 -8
  94. package/rx/dist/trpc-client-rx.cjs.d.ts +1 -0
  95. package/rx/dist/trpc-client-rx.cjs.dev.js +9 -0
  96. package/rx/dist/trpc-client-rx.cjs.js +7 -0
  97. package/rx/dist/trpc-client-rx.cjs.prod.js +9 -0
  98. package/rx/dist/trpc-client-rx.esm.js +1 -0
  99. package/rx/operators/dist/trpc-client-rx-operators.cjs.d.ts +1 -0
  100. package/rx/operators/dist/trpc-client-rx-operators.cjs.dev.js +39 -0
  101. package/rx/operators/dist/trpc-client-rx-operators.cjs.js +7 -0
  102. package/rx/operators/dist/trpc-client-rx-operators.cjs.prod.js +39 -0
  103. package/rx/operators/dist/trpc-client-rx-operators.esm.js +33 -0
  104. package/rx/operators/package.json +4 -0
  105. package/rx/package.json +4 -0
  106. package/src/TRPCClientError.ts +17 -21
  107. package/src/createTRPCClient.ts +4 -3
  108. package/src/index.ts +1 -1
  109. package/src/internals/TRPCClient.ts +80 -88
  110. package/src/internals/dataLoader.ts +1 -1
  111. package/src/internals/transformRPCResponse.ts +2 -2
  112. package/src/links/dedupeLink.test.ts +134 -0
  113. package/src/links/dedupeLink.ts +53 -0
  114. package/src/links/httpBatchLink.ts +37 -37
  115. package/src/links/httpLink.ts +26 -37
  116. package/src/{internals/httpRequest.ts → links/httpUtils.ts} +30 -21
  117. package/src/links/internals/createChain.test.ts +132 -0
  118. package/src/links/internals/createChain.ts +31 -0
  119. package/src/links/internals/transformOperationResult.ts +25 -0
  120. package/src/links/loggerLink.ts +40 -22
  121. package/src/links/retryLink.ts +45 -14
  122. package/src/links/splitLink.test.ts +56 -0
  123. package/src/links/splitLink.ts +23 -38
  124. package/src/links/transformerLink.ts +70 -0
  125. package/src/links/{core.ts → types.ts} +40 -46
  126. package/src/links/wsLink.ts +56 -77
  127. package/src/rx/index.ts +2 -0
  128. package/src/rx/observable.test.ts +83 -0
  129. package/src/rx/observable.ts +70 -0
  130. package/src/rx/operators/index.ts +3 -0
  131. package/src/rx/operators/map.test.ts +92 -0
  132. package/src/rx/operators/map.ts +25 -0
  133. package/src/rx/operators/share.test.ts +60 -0
  134. package/src/rx/operators/share.ts +67 -0
  135. package/src/rx/operators/tap.ts +26 -0
  136. package/src/rx/types.ts +69 -0
  137. package/src/rx/util/identity.ts +3 -0
  138. package/src/rx/util/observableToPromise.ts +49 -0
  139. package/src/rx/util/pipe.ts +22 -0
  140. package/dist/declarations/src/internals/TRPCAbortError.d.ts +0 -4
  141. package/dist/declarations/src/internals/TRPCAbortError.d.ts.map +0 -1
  142. package/dist/declarations/src/internals/executeChain.d.ts +0 -8
  143. package/dist/declarations/src/internals/executeChain.d.ts.map +0 -1
  144. package/dist/declarations/src/internals/httpRequest.d.ts +0 -14
  145. package/dist/declarations/src/internals/httpRequest.d.ts.map +0 -1
  146. package/dist/declarations/src/internals/observable.d.ts +0 -18
  147. package/dist/declarations/src/internals/observable.d.ts.map +0 -1
  148. package/dist/declarations/src/internals/transformRPCResponse.d.ts +0 -7
  149. package/dist/declarations/src/internals/transformRPCResponse.d.ts.map +0 -1
  150. package/dist/declarations/src/links/core.d.ts +0 -45
  151. package/dist/declarations/src/links/core.d.ts.map +0 -1
  152. package/dist/httpRequest-672ff1f0.esm.js +0 -128
  153. package/dist/httpRequest-7bfaf300.cjs.dev.js +0 -132
  154. package/dist/httpRequest-8c422e7a.cjs.prod.js +0 -132
  155. package/src/internals/TRPCAbortError.ts +0 -7
  156. package/src/internals/executeChain.ts +0 -67
  157. package/src/internals/observable.ts +0 -89
@@ -0,0 +1,70 @@
1
+ import {
2
+ AnyRouter,
3
+ ClientDataTransformerOptions,
4
+ DataTransformer,
5
+ } from '@trpc/server';
6
+ import { observable } from '../rx/observable';
7
+ import { OperationResult, TRPCLink } from './types';
8
+
9
+ /**
10
+ * @internal
11
+ */
12
+ function transformOperationResult<TResult extends OperationResult<any, any>>(
13
+ result: TResult,
14
+ transformer: DataTransformer,
15
+ ) {
16
+ if ('error' in result.data) {
17
+ return {
18
+ ...result,
19
+ data: {
20
+ ...result.data,
21
+ error: transformer.deserialize(result.data.error),
22
+ },
23
+ };
24
+ }
25
+ if (result.data.result.type !== 'data') {
26
+ return result;
27
+ }
28
+ return {
29
+ ...result,
30
+ data: {
31
+ ...result.data,
32
+ result: {
33
+ ...result.data.result,
34
+ data: transformer.deserialize(result.data.result.data),
35
+ },
36
+ },
37
+ };
38
+ }
39
+ export function transformerLink<TRouter extends AnyRouter = AnyRouter>(
40
+ transformer: ClientDataTransformerOptions,
41
+ ): TRPCLink<TRouter> {
42
+ const _transformer: DataTransformer = transformer
43
+ ? 'input' in transformer
44
+ ? {
45
+ serialize: transformer.input.serialize,
46
+ deserialize: transformer.output.deserialize,
47
+ }
48
+ : transformer
49
+ : {
50
+ serialize: (data) => data,
51
+ deserialize: (data) => data,
52
+ };
53
+
54
+ return () => {
55
+ return (props) => {
56
+ const input = _transformer.serialize(props.op.input);
57
+ return observable((observer) => {
58
+ const next$ = props.next({ ...props.op, input }).subscribe({
59
+ next(value) {
60
+ const transformed = transformOperationResult(value, _transformer);
61
+ observer.next(transformed);
62
+ },
63
+ error: observer.error,
64
+ complete: observer.complete,
65
+ });
66
+ return next$;
67
+ });
68
+ };
69
+ };
70
+ }
@@ -1,51 +1,25 @@
1
- import { AnyRouter, DataTransformer } from '@trpc/server';
2
- import { TRPCResult } from '@trpc/server/rpc';
1
+ import { AnyRouter, inferRouterError, Dict } from '@trpc/server';
2
+ import { TRPCResponse } from '@trpc/server/rpc';
3
+ import { Observable, Observer } from '../rx/types';
3
4
  import { TRPCClientError } from '../TRPCClientError';
4
5
 
6
+ export type CancelFn = () => void;
7
+
8
+ export type PromiseAndCancel<TValue> = {
9
+ promise: Promise<TValue>;
10
+ cancel: CancelFn;
11
+ };
12
+
5
13
  export type OperationContext = Record<string, unknown>;
6
14
  export type Operation<TInput = unknown> = {
7
15
  id: number;
8
16
  type: 'query' | 'mutation' | 'subscription';
9
17
  input: TInput;
10
18
  path: string;
11
- context: OperationContext;
19
+ context?: OperationContext;
12
20
  };
13
21
 
14
- export type OperationResponse<TRouter extends AnyRouter, TOutput = unknown> =
15
- | TRPCResult<TOutput>
16
- | TRPCClientError<TRouter>;
17
-
18
- export type PrevCallback<TRouter extends AnyRouter, TOutput = unknown> = (
19
- result: OperationResponse<TRouter, TOutput>,
20
- ) => void;
21
- export type OperationLink<
22
- TRouter extends AnyRouter,
23
- TInput = unknown,
24
- TOutput = unknown,
25
- > = (opts: {
26
- op: Operation;
27
- prev: PrevCallback<TRouter, TOutput>;
28
- next: (
29
- op: Operation<TInput>,
30
- callback: PrevCallback<TRouter, TOutput>,
31
- ) => void;
32
- onDestroy: (callback: () => void) => void;
33
- }) => void;
34
-
35
- export type TRPCLink<TRouter extends AnyRouter> = (
36
- opts: LinkRuntimeOptions,
37
- ) => OperationLink<TRouter>;
38
-
39
- export interface HTTPLinkOptions {
40
- url: string;
41
- }
42
-
43
- /**
44
- * @deprecated use `HTTPLinkOptions`
45
- */
46
- export type HttpLinkOptions = HTTPLinkOptions;
47
-
48
- export type HTTPHeaders = Record<string, string | string[] | undefined>;
22
+ export type HTTPHeaders = Dict<string | string[]>;
49
23
 
50
24
  /**
51
25
  * The default `fetch` implementation has an overloaded signature. By convention this library
@@ -56,16 +30,36 @@ export type TRPCFetch = (
56
30
  options?: RequestInit,
57
31
  ) => Promise<Response>;
58
32
 
59
- export type LinkRuntimeOptions = Readonly<{
60
- transformer: DataTransformer;
33
+ export interface LinkRuntime {
61
34
  headers: () => HTTPHeaders | Promise<HTTPHeaders>;
62
35
  fetch: TRPCFetch;
63
36
  AbortController?: typeof AbortController;
64
- }>;
37
+ }
65
38
 
66
- export type CancelFn = () => void;
39
+ export interface OperationResult<TRouter extends AnyRouter, TOutput> {
40
+ data: TRPCResponse<TOutput, inferRouterError<TRouter>>;
41
+ context?: OperationContext;
42
+ }
67
43
 
68
- export type PromiseAndCancel<TValue> = {
69
- promise: Promise<TValue>;
70
- cancel: CancelFn;
71
- };
44
+ export type OperationResultObservable<
45
+ TRouter extends AnyRouter,
46
+ TOutput,
47
+ > = Observable<OperationResult<TRouter, TOutput>, TRPCClientError<TRouter>>;
48
+
49
+ export type OperationResultObserver<
50
+ TRouter extends AnyRouter,
51
+ TOutput,
52
+ > = Observer<OperationResult<TRouter, TOutput>, TRPCClientError<TRouter>>;
53
+
54
+ export type OperationLink<
55
+ TRouter extends AnyRouter,
56
+ TInput = unknown,
57
+ TOutput = unknown,
58
+ > = (opts: {
59
+ op: Operation<TInput>;
60
+ next: (op: Operation<TInput>) => OperationResultObservable<TRouter, TOutput>;
61
+ }) => OperationResultObservable<TRouter, TOutput>;
62
+
63
+ export type TRPCLink<TRouter extends AnyRouter> = (
64
+ opts: LinkRuntime,
65
+ ) => OperationLink<TRouter>;
@@ -4,20 +4,13 @@ import {
4
4
  TRPCClientIncomingRequest,
5
5
  TRPCRequest,
6
6
  TRPCResponse,
7
- TRPCResult,
8
7
  } from '@trpc/server/rpc';
9
- import { TRPCErrorResponse } from '@trpc/server/rpc';
10
- import { ObservableCallbacks, UnsubscribeFn } from '../internals/observable';
11
8
  import { retryDelay } from '../internals/retryDelay';
9
+ import { UnsubscribeFn } from '../rx';
10
+ import { observable } from '../rx/observable';
12
11
  import { TRPCClientError } from '../TRPCClientError';
13
- import { TRPCLink } from './core';
12
+ import { Operation, OperationResultObserver, TRPCLink } from './types';
14
13
 
15
- type Operation = {
16
- id: number | string;
17
- type: ProcedureType;
18
- input: unknown;
19
- path: string;
20
- };
21
14
  export interface WebSocketClientOptions {
22
15
  url: string;
23
16
  WebSocket?: WebSocket;
@@ -42,10 +35,7 @@ export function createWSClient(opts: WebSocketClientOptions) {
42
35
  /**
43
36
  * pending outgoing requests that are awaiting callback
44
37
  */
45
- type TCallbacks = ObservableCallbacks<
46
- TRPCResult,
47
- TRPCClientError<AnyRouter> | TRPCErrorResponse
48
- >;
38
+ type TCallbacks = OperationResultObserver<AnyRouter, unknown>;
49
39
  type TRequest = {
50
40
  /**
51
41
  * Reference to the WebSocket instance this request was made to
@@ -151,18 +141,14 @@ export function createWSClient(opts: WebSocketClientOptions) {
151
141
  }
152
142
  }
153
143
  };
154
- const handleIncomingResponse = (res: TRPCResponse) => {
155
- const req = res.id !== null && pendingRequests[res.id];
144
+ const handleIncomingResponse = (data: TRPCResponse) => {
145
+ const req = data.id !== null && pendingRequests[data.id];
156
146
  if (!req) {
157
147
  // do something?
158
148
  return;
159
149
  }
160
- if ('error' in res) {
161
- req.callbacks.onError?.(res);
162
- return;
163
- }
164
150
 
165
- req.callbacks.onNext?.(res.result);
151
+ req.callbacks.next?.({ data });
166
152
  if (req.ws !== activeConnection && conn === activeConnection) {
167
153
  const oldWs = req.ws;
168
154
  // gracefully replace old connection with this
@@ -170,8 +156,12 @@ export function createWSClient(opts: WebSocketClientOptions) {
170
156
  closeIfNoPending(oldWs);
171
157
  }
172
158
 
173
- if (res.result.type === 'stopped' && conn === activeConnection) {
174
- req.callbacks.onDone?.();
159
+ if (
160
+ 'result' in data &&
161
+ data.result.type === 'stopped' &&
162
+ conn === activeConnection
163
+ ) {
164
+ req.callbacks.complete();
175
165
  }
176
166
  };
177
167
  conn.addEventListener('message', ({ data }) => {
@@ -199,14 +189,14 @@ export function createWSClient(opts: WebSocketClientOptions) {
199
189
  if (req.ws !== conn) {
200
190
  continue;
201
191
  }
202
- req.callbacks.onError?.(
192
+ req.callbacks.error?.(
203
193
  TRPCClientError.from(
204
194
  new TRPCWebSocketClosedError('WebSocket closed prematurely'),
205
195
  ),
206
196
  );
207
197
  if (req.type !== 'subscription') {
208
198
  delete pendingRequests[key];
209
- req.callbacks.onDone?.();
199
+ req.callbacks.complete?.();
210
200
  } else if (state !== 'closed') {
211
201
  // request restart of sub with next connection
212
202
  resumeSubscriptionOnReconnect(req);
@@ -243,7 +233,7 @@ export function createWSClient(opts: WebSocketClientOptions) {
243
233
  delete pendingRequests[id];
244
234
  outgoing = outgoing.filter((msg) => msg.id !== id);
245
235
 
246
- callbacks?.onDone?.();
236
+ callbacks?.complete?.();
247
237
  if (op.type === 'subscription') {
248
238
  outgoing.push({
249
239
  id,
@@ -289,63 +279,52 @@ class TRPCSubscriptionEndedError extends Error {
289
279
  export function wsLink<TRouter extends AnyRouter>(
290
280
  opts: WebSocketLinkOptions,
291
281
  ): TRPCLink<TRouter> {
292
- // initialized config
293
- return (rt) => {
282
+ return () => {
294
283
  const { client } = opts;
284
+ return ({ op }) => {
285
+ return observable((observer) => {
286
+ const { type, input, path, id, context } = op;
295
287
 
296
- return ({ op, prev, onDestroy }) => {
297
- const { type, input: rawInput, path, id } = op;
298
- const input = rt.transformer.serialize(rawInput);
299
- let isDone = false;
300
- const unsub = client.request(
301
- { type, path, input, id },
302
- {
303
- onNext(result) {
304
- if (isDone) {
305
- return;
306
- }
307
- if ('data' in result) {
308
- const data = rt.transformer.deserialize(result.data);
309
- prev({ type: 'data', data });
310
- } else {
311
- prev(result);
312
- }
313
-
314
- if (op.type !== 'subscription') {
315
- // if it isn't a subscription we don't care about next response
288
+ let isDone = false;
289
+ const unsub = client.request(
290
+ { type, path, input, id, context },
291
+ {
292
+ error(err) {
316
293
  isDone = true;
294
+ observer.error(err);
317
295
  unsub();
318
- }
319
- },
320
- onError(err) {
321
- if (isDone) {
322
- return;
323
- }
324
- prev(
325
- err instanceof Error
326
- ? err
327
- : TRPCClientError.from({
328
- ...err,
329
- error: rt.transformer.deserialize(err.error),
330
- }),
331
- );
332
- },
333
- onDone() {
334
- if (isDone) {
335
- return;
336
- }
337
- const result = new TRPCSubscriptionEndedError(
338
- 'Operation ended prematurely',
339
- );
296
+ },
297
+ complete() {
298
+ if (!isDone) {
299
+ isDone = true;
300
+ observer.error(
301
+ TRPCClientError.from(
302
+ new TRPCSubscriptionEndedError(
303
+ 'Operation ended prematurely',
304
+ ),
305
+ ),
306
+ );
307
+ } else {
308
+ observer.complete();
309
+ }
310
+ },
311
+ next(result) {
312
+ observer.next(result);
313
+
314
+ if (op.type !== 'subscription') {
315
+ // if it isn't a subscription we don't care about next response
340
316
 
341
- prev(TRPCClientError.from(result, { isDone: true }));
342
- isDone = true;
317
+ isDone = true;
318
+ unsub();
319
+ observer.complete();
320
+ }
321
+ },
343
322
  },
344
- },
345
- );
346
- onDestroy(() => {
347
- isDone = true;
348
- unsub();
323
+ );
324
+ return () => {
325
+ isDone = true;
326
+ unsub();
327
+ };
349
328
  });
350
329
  };
351
330
  };
@@ -0,0 +1,2 @@
1
+ export * from './observable';
2
+ export * from './types';
@@ -0,0 +1,83 @@
1
+ /* eslint-disable @typescript-eslint/no-empty-function */
2
+ import { observable } from './observable';
3
+ import { tap } from './operators';
4
+ import { share } from './operators/share';
5
+
6
+ test('vanilla observable', () => {
7
+ const obs = observable<number, Error>((observer) => {
8
+ observer.next(1);
9
+ observer.complete();
10
+ });
11
+
12
+ const next = jest.fn();
13
+ const error = jest.fn();
14
+ const complete = jest.fn();
15
+ obs.subscribe({
16
+ next,
17
+ error,
18
+ complete,
19
+ });
20
+ expect(next.mock.calls).toHaveLength(1);
21
+ expect(complete.mock.calls).toHaveLength(1);
22
+ expect(error.mock.calls).toHaveLength(0);
23
+ expect(next.mock.calls[0][0]).toBe(1);
24
+ });
25
+
26
+ test('pipe - combine operators', () => {
27
+ const taps = {
28
+ next: jest.fn(),
29
+ complete: jest.fn(),
30
+ error: jest.fn(),
31
+ };
32
+ const obs = observable<number, Error>((observer) => {
33
+ observer.next(1);
34
+ }).pipe(
35
+ // operators:
36
+ share(),
37
+ tap(taps),
38
+ );
39
+ {
40
+ const next = jest.fn();
41
+ const error = jest.fn();
42
+ const complete = jest.fn();
43
+ obs.subscribe({
44
+ next,
45
+ error,
46
+ complete,
47
+ });
48
+ expect(next.mock.calls).toHaveLength(1);
49
+ expect(complete.mock.calls).toHaveLength(0);
50
+ expect(error.mock.calls).toHaveLength(0);
51
+ expect(next.mock.calls[0][0]).toBe(1);
52
+ }
53
+
54
+ {
55
+ const next = jest.fn();
56
+ const error = jest.fn();
57
+ const complete = jest.fn();
58
+ obs.subscribe({
59
+ next,
60
+ error,
61
+ complete,
62
+ });
63
+ expect(next.mock.calls).toHaveLength(0);
64
+ expect(complete.mock.calls).toHaveLength(0);
65
+ expect(error.mock.calls).toHaveLength(0);
66
+ }
67
+
68
+ expect({
69
+ next: taps.next.mock.calls,
70
+ error: taps.error.mock.calls,
71
+ complete: taps.complete.mock.calls,
72
+ }).toMatchInlineSnapshot(`
73
+ Object {
74
+ "complete": Array [],
75
+ "error": Array [],
76
+ "next": Array [
77
+ Array [
78
+ 1,
79
+ ],
80
+ ],
81
+ }
82
+ `);
83
+ });
@@ -0,0 +1,70 @@
1
+ import { pipeFromArray } from './util/pipe';
2
+ import { Observable, Observer, OperatorFunction, TeardownLogic } from './types';
3
+
4
+ export type inferObservableValue<TObservable extends Observable<any, any>> =
5
+ TObservable extends Observable<infer TValue, any> ? TValue : never;
6
+
7
+ export function observable<TValue, TError = unknown>(
8
+ subscribe: (observer: Observer<TValue, TError>) => TeardownLogic,
9
+ ): Observable<TValue, TError> {
10
+ const self: Observable<TValue, TError> = {
11
+ subscribe(observer) {
12
+ let teardownRef: TeardownLogic | null = null;
13
+ let isDone = false;
14
+ let unsubscribed = false;
15
+ let teardownImmediately = false;
16
+ function unsubscribe() {
17
+ if (teardownRef === null) {
18
+ teardownImmediately = true;
19
+ return;
20
+ }
21
+ if (unsubscribed) {
22
+ return;
23
+ }
24
+ unsubscribed = true;
25
+
26
+ if (typeof teardownRef === 'function') {
27
+ teardownRef();
28
+ } else if (teardownRef) {
29
+ teardownRef.unsubscribe();
30
+ }
31
+ }
32
+ teardownRef = subscribe({
33
+ next(value) {
34
+ if (isDone) {
35
+ return;
36
+ }
37
+ observer.next?.(value);
38
+ },
39
+ error(err) {
40
+ if (isDone) {
41
+ return;
42
+ }
43
+ isDone = true;
44
+ observer.error?.(err);
45
+ unsubscribe();
46
+ },
47
+ complete() {
48
+ if (isDone) {
49
+ return;
50
+ }
51
+ isDone = true;
52
+ observer.complete?.();
53
+ unsubscribe();
54
+ },
55
+ });
56
+ if (teardownImmediately) {
57
+ unsubscribe();
58
+ }
59
+ return {
60
+ unsubscribe,
61
+ };
62
+ },
63
+ pipe(
64
+ ...operations: OperatorFunction<any, any, any, any>[]
65
+ ): Observable<any, any> {
66
+ return pipeFromArray(operations)(self) as any;
67
+ },
68
+ };
69
+ return self;
70
+ }
@@ -0,0 +1,3 @@
1
+ export * from './share';
2
+ export * from './map';
3
+ export * from './tap';
@@ -0,0 +1,92 @@
1
+ import { expectTypeOf } from 'expect-type';
2
+ import { observable } from '../observable';
3
+ import { map } from '.';
4
+ import { EventEmitter } from 'events';
5
+
6
+ interface SubscriptionEvents<TOutput> {
7
+ data: (data: TOutput) => void;
8
+ }
9
+
10
+ declare interface CustomEventEmitter<TOutput> {
11
+ on<U extends keyof SubscriptionEvents<TOutput>>(
12
+ event: U,
13
+ listener: SubscriptionEvents<TOutput>[U],
14
+ ): this;
15
+
16
+ once<U extends keyof SubscriptionEvents<TOutput>>(
17
+ event: U,
18
+ listener: SubscriptionEvents<TOutput>[U],
19
+ ): this;
20
+
21
+ emit<U extends keyof SubscriptionEvents<TOutput>>(
22
+ event: U,
23
+ ...args: Parameters<SubscriptionEvents<TOutput>[U]>
24
+ ): boolean;
25
+ }
26
+ class CustomEventEmitter<TOutput>
27
+ extends EventEmitter
28
+ implements CustomEventEmitter<TOutput> {}
29
+ test('map', () => {
30
+ type EventShape = { num: number };
31
+ const ee = new CustomEventEmitter<EventShape>();
32
+ const eventObservable = observable<EventShape, unknown>((observer) => {
33
+ const callback = (data: EventShape) => {
34
+ observer.next(data);
35
+ };
36
+ ee.on('data', callback);
37
+
38
+ return () => {
39
+ ee.off('data', callback);
40
+ };
41
+ });
42
+ const pipeCalls = jest.fn();
43
+ const piped = eventObservable.pipe(
44
+ map((...args) => {
45
+ pipeCalls(...args);
46
+ const [value] = args;
47
+ return value.num;
48
+ }),
49
+ );
50
+
51
+ const next = jest.fn();
52
+ const subscription = piped.subscribe({
53
+ next(value) {
54
+ expectTypeOf<number>(value);
55
+ next(value);
56
+ },
57
+ });
58
+ expect(next).not.toHaveBeenCalled();
59
+ ee.emit('data', { num: 1 });
60
+ ee.emit('data', { num: 2 });
61
+ expect(next).toHaveBeenCalledTimes(2);
62
+ expect(next.mock.calls).toMatchInlineSnapshot(`
63
+ Array [
64
+ Array [
65
+ 1,
66
+ ],
67
+ Array [
68
+ 2,
69
+ ],
70
+ ]
71
+ `);
72
+ expect(pipeCalls.mock.calls).toMatchInlineSnapshot(`
73
+ Array [
74
+ Array [
75
+ Object {
76
+ "num": 1,
77
+ },
78
+ 0,
79
+ ],
80
+ Array [
81
+ Object {
82
+ "num": 2,
83
+ },
84
+ 1,
85
+ ],
86
+ ]
87
+ `);
88
+
89
+ expect(ee.listeners('data')).toHaveLength(1);
90
+ subscription.unsubscribe();
91
+ expect(ee.listeners('data')).toHaveLength(0);
92
+ });
@@ -0,0 +1,25 @@
1
+ import { OperatorFunction } from '../types';
2
+
3
+ export function map<TValueBefore, TError, TValueAfter>(
4
+ project: (value: TValueBefore, index: number) => TValueAfter,
5
+ ): OperatorFunction<TValueBefore, TError, TValueAfter, TError> {
6
+ return (originalObserver) => {
7
+ return {
8
+ subscribe(observer) {
9
+ let index = 0;
10
+ const subscription = originalObserver.subscribe({
11
+ next(value) {
12
+ observer.next?.(project(value, index++));
13
+ },
14
+ error(error) {
15
+ observer.error?.(error);
16
+ },
17
+ complete() {
18
+ observer.complete?.();
19
+ },
20
+ });
21
+ return subscription;
22
+ },
23
+ };
24
+ };
25
+ }