@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.
- package/dist/{TRPCClientError-bfee2bf5.cjs.dev.js → TRPCClientError-a5312a5f.cjs.dev.js} +14 -19
- package/dist/{TRPCClientError-09b8a26b.esm.js → TRPCClientError-e1da1ef0.esm.js} +14 -19
- package/dist/{TRPCClientError-e9bf96e9.cjs.prod.js → TRPCClientError-ea7d89b2.cjs.prod.js} +14 -19
- package/dist/createChain-93a38e1c.cjs.dev.js +27 -0
- package/dist/createChain-e45e052a.cjs.prod.js +27 -0
- package/dist/createChain-e45e6cf2.esm.js +25 -0
- package/dist/declarations/src/TRPCClientError.d.ts +6 -10
- package/dist/declarations/src/TRPCClientError.d.ts.map +1 -1
- package/dist/declarations/src/createTRPCClient.d.ts +1 -2
- package/dist/declarations/src/createTRPCClient.d.ts.map +1 -1
- package/dist/declarations/src/index.d.ts +1 -1
- package/dist/declarations/src/index.d.ts.map +1 -1
- package/dist/declarations/src/internals/TRPCClient.d.ts +17 -14
- package/dist/declarations/src/internals/TRPCClient.d.ts.map +1 -1
- package/dist/declarations/src/internals/dataLoader.d.ts +1 -1
- package/dist/declarations/src/internals/dataLoader.d.ts.map +1 -1
- package/dist/declarations/src/links/httpBatchLink.d.ts +2 -1
- package/dist/declarations/src/links/httpBatchLink.d.ts.map +1 -1
- package/dist/declarations/src/links/httpLink.d.ts +2 -1
- package/dist/declarations/src/links/httpLink.d.ts.map +1 -1
- package/dist/declarations/src/links/httpUtils.d.ts +22 -0
- package/dist/declarations/src/links/httpUtils.d.ts.map +1 -0
- package/dist/declarations/src/links/internals/createChain.d.ts +8 -0
- package/dist/declarations/src/links/internals/createChain.d.ts.map +1 -0
- package/dist/declarations/src/links/internals/transformOperationResult.d.ts +16 -0
- package/dist/declarations/src/links/internals/transformOperationResult.d.ts.map +1 -0
- package/dist/declarations/src/links/loggerLink.d.ts +4 -3
- package/dist/declarations/src/links/loggerLink.d.ts.map +1 -1
- package/dist/declarations/src/links/splitLink.d.ts +4 -14
- package/dist/declarations/src/links/splitLink.d.ts.map +1 -1
- package/dist/declarations/src/links/transformerLink.d.ts +4 -0
- package/dist/declarations/src/links/transformerLink.d.ts.map +1 -0
- package/dist/declarations/src/links/types.d.ts +40 -0
- package/dist/declarations/src/links/types.d.ts.map +1 -0
- package/dist/declarations/src/links/wsLink.d.ts +4 -14
- package/dist/declarations/src/links/wsLink.d.ts.map +1 -1
- package/dist/declarations/src/rx/index.d.ts +3 -0
- package/dist/declarations/src/rx/index.d.ts.map +1 -0
- package/dist/declarations/src/rx/observable.d.ts +4 -0
- package/dist/declarations/src/rx/observable.d.ts.map +1 -0
- package/dist/declarations/src/rx/operators/index.d.ts +4 -0
- package/dist/declarations/src/rx/operators/index.d.ts.map +1 -0
- package/dist/declarations/src/rx/operators/map.d.ts +3 -0
- package/dist/declarations/src/rx/operators/map.d.ts.map +1 -0
- package/dist/declarations/src/rx/operators/share.d.ts +6 -0
- package/dist/declarations/src/rx/operators/share.d.ts.map +1 -0
- package/dist/declarations/src/rx/operators/tap.d.ts +3 -0
- package/dist/declarations/src/rx/operators/tap.d.ts.map +1 -0
- package/dist/declarations/src/rx/types.d.ts +29 -0
- package/dist/declarations/src/rx/types.d.ts.map +1 -0
- package/dist/declarations/src/rx/util/identity.d.ts +2 -0
- package/dist/declarations/src/rx/util/identity.d.ts.map +1 -0
- package/dist/declarations/src/rx/util/observableToPromise.d.ts +10 -0
- package/dist/declarations/src/rx/util/observableToPromise.d.ts.map +1 -0
- package/dist/declarations/src/rx/util/pipe.d.ts +4 -0
- package/dist/declarations/src/rx/util/pipe.d.ts.map +1 -0
- package/dist/httpUtils-299a66f6.cjs.prod.js +89 -0
- package/dist/httpUtils-a1426e14.cjs.dev.js +89 -0
- package/dist/httpUtils-e45c9056.esm.js +87 -0
- package/dist/observable-01534ea8.esm.js +102 -0
- package/dist/observable-9aad8c3e.cjs.dev.js +104 -0
- package/dist/observable-ccb54234.cjs.prod.js +104 -0
- package/dist/share-5a8c2543.cjs.prod.js +106 -0
- package/dist/share-ca585761.esm.js +104 -0
- package/dist/share-f2ac9332.cjs.dev.js +106 -0
- package/dist/tap-2d565250.cjs.dev.js +32 -0
- package/dist/tap-606957cb.esm.js +30 -0
- package/dist/tap-d907998d.cjs.prod.js +32 -0
- package/dist/trpc-client.cjs.dev.js +136 -215
- package/dist/trpc-client.cjs.prod.js +136 -215
- package/dist/trpc-client.esm.js +137 -216
- package/links/httpBatchLink/dist/trpc-client-links-httpBatchLink.cjs.dev.js +34 -52
- package/links/httpBatchLink/dist/trpc-client-links-httpBatchLink.cjs.prod.js +34 -52
- package/links/httpBatchLink/dist/trpc-client-links-httpBatchLink.esm.js +33 -51
- package/links/httpLink/dist/trpc-client-links-httpLink.cjs.dev.js +29 -52
- package/links/httpLink/dist/trpc-client-links-httpLink.cjs.prod.js +29 -52
- package/links/httpLink/dist/trpc-client-links-httpLink.esm.js +29 -52
- package/links/loggerLink/dist/trpc-client-links-loggerLink.cjs.dev.js +31 -19
- package/links/loggerLink/dist/trpc-client-links-loggerLink.cjs.prod.js +31 -19
- package/links/loggerLink/dist/trpc-client-links-loggerLink.esm.js +31 -19
- package/links/splitLink/dist/trpc-client-links-splitLink.cjs.dev.js +20 -7
- package/links/splitLink/dist/trpc-client-links-splitLink.cjs.prod.js +20 -7
- package/links/splitLink/dist/trpc-client-links-splitLink.esm.js +20 -7
- package/links/transformerLink/package.json +4 -0
- package/links/wsLink/dist/trpc-client-links-wsLink.cjs.dev.js +57 -82
- package/links/wsLink/dist/trpc-client-links-wsLink.cjs.prod.js +57 -82
- package/links/wsLink/dist/trpc-client-links-wsLink.esm.js +57 -82
- package/package.json +12 -8
- package/rx/dist/trpc-client-rx.cjs.d.ts +1 -0
- package/rx/dist/trpc-client-rx.cjs.dev.js +9 -0
- package/rx/dist/trpc-client-rx.cjs.js +7 -0
- package/rx/dist/trpc-client-rx.cjs.prod.js +9 -0
- package/rx/dist/trpc-client-rx.esm.js +1 -0
- package/rx/operators/dist/trpc-client-rx-operators.cjs.d.ts +1 -0
- package/rx/operators/dist/trpc-client-rx-operators.cjs.dev.js +39 -0
- package/rx/operators/dist/trpc-client-rx-operators.cjs.js +7 -0
- package/rx/operators/dist/trpc-client-rx-operators.cjs.prod.js +39 -0
- package/rx/operators/dist/trpc-client-rx-operators.esm.js +33 -0
- package/rx/operators/package.json +4 -0
- package/rx/package.json +4 -0
- package/src/TRPCClientError.ts +17 -21
- package/src/createTRPCClient.ts +4 -3
- package/src/index.ts +1 -1
- package/src/internals/TRPCClient.ts +80 -88
- package/src/internals/dataLoader.ts +1 -1
- package/src/internals/transformRPCResponse.ts +2 -2
- package/src/links/dedupeLink.test.ts +134 -0
- package/src/links/dedupeLink.ts +53 -0
- package/src/links/httpBatchLink.ts +37 -37
- package/src/links/httpLink.ts +26 -37
- package/src/{internals/httpRequest.ts → links/httpUtils.ts} +30 -21
- package/src/links/internals/createChain.test.ts +132 -0
- package/src/links/internals/createChain.ts +31 -0
- package/src/links/internals/transformOperationResult.ts +25 -0
- package/src/links/loggerLink.ts +40 -22
- package/src/links/retryLink.ts +45 -14
- package/src/links/splitLink.test.ts +56 -0
- package/src/links/splitLink.ts +23 -38
- package/src/links/transformerLink.ts +70 -0
- package/src/links/{core.ts → types.ts} +40 -46
- package/src/links/wsLink.ts +55 -77
- package/src/rx/index.ts +2 -0
- package/src/rx/observable.test.ts +83 -0
- package/src/rx/observable.ts +70 -0
- package/src/rx/operators/index.ts +3 -0
- package/src/rx/operators/map.test.ts +92 -0
- package/src/rx/operators/map.ts +25 -0
- package/src/rx/operators/share.test.ts +60 -0
- package/src/rx/operators/share.ts +67 -0
- package/src/rx/operators/tap.ts +26 -0
- package/src/rx/types.ts +69 -0
- package/src/rx/util/identity.ts +3 -0
- package/src/rx/util/observableToPromise.ts +49 -0
- package/src/rx/util/pipe.ts +22 -0
- package/dist/declarations/src/internals/TRPCAbortError.d.ts +0 -4
- package/dist/declarations/src/internals/TRPCAbortError.d.ts.map +0 -1
- package/dist/declarations/src/internals/executeChain.d.ts +0 -9
- package/dist/declarations/src/internals/executeChain.d.ts.map +0 -1
- package/dist/declarations/src/internals/httpRequest.d.ts +0 -14
- package/dist/declarations/src/internals/httpRequest.d.ts.map +0 -1
- package/dist/declarations/src/internals/observable.d.ts +0 -18
- package/dist/declarations/src/internals/observable.d.ts.map +0 -1
- package/dist/declarations/src/internals/transformRPCResponse.d.ts +0 -15
- package/dist/declarations/src/internals/transformRPCResponse.d.ts.map +0 -1
- package/dist/declarations/src/links/core.d.ts +0 -45
- package/dist/declarations/src/links/core.d.ts.map +0 -1
- package/dist/httpRequest-672ff1f0.esm.js +0 -128
- package/dist/httpRequest-7bfaf300.cjs.dev.js +0 -132
- package/dist/httpRequest-8c422e7a.cjs.prod.js +0 -132
- package/src/internals/TRPCAbortError.ts +0 -7
- package/src/internals/executeChain.ts +0 -67
- package/src/internals/observable.ts +0 -89
|
@@ -1,51 +1,25 @@
|
|
|
1
|
-
import { AnyRouter,
|
|
2
|
-
import {
|
|
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
|
|
19
|
+
context?: OperationContext;
|
|
12
20
|
};
|
|
13
21
|
|
|
14
|
-
export type
|
|
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
|
|
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
|
|
39
|
+
export interface OperationResult<TRouter extends AnyRouter, TOutput> {
|
|
40
|
+
data: TRPCResponse<TOutput, inferRouterError<TRouter>>;
|
|
41
|
+
context?: OperationContext;
|
|
42
|
+
}
|
|
67
43
|
|
|
68
|
-
export type
|
|
69
|
-
|
|
70
|
-
|
|
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>;
|
package/src/links/wsLink.ts
CHANGED
|
@@ -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 './
|
|
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 =
|
|
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 = (
|
|
155
|
-
const req =
|
|
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.
|
|
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 (
|
|
174
|
-
|
|
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.
|
|
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.
|
|
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?.
|
|
236
|
+
callbacks?.complete?.();
|
|
247
237
|
if (op.type === 'subscription') {
|
|
248
238
|
outgoing.push({
|
|
249
239
|
id,
|
|
@@ -289,63 +279,51 @@ class TRPCSubscriptionEndedError extends Error {
|
|
|
289
279
|
export function wsLink<TRouter extends AnyRouter>(
|
|
290
280
|
opts: WebSocketLinkOptions,
|
|
291
281
|
): TRPCLink<TRouter> {
|
|
292
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
342
|
-
|
|
317
|
+
isDone = true;
|
|
318
|
+
unsub();
|
|
319
|
+
observer.complete();
|
|
320
|
+
}
|
|
321
|
+
},
|
|
343
322
|
},
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
unsub();
|
|
323
|
+
);
|
|
324
|
+
return () => {
|
|
325
|
+
unsub();
|
|
326
|
+
};
|
|
349
327
|
});
|
|
350
328
|
};
|
|
351
329
|
};
|
package/src/rx/index.ts
ADDED
|
@@ -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,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
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* eslint-disable no-var */
|
|
2
|
+
import { observable } from '../observable';
|
|
3
|
+
import { share } from './share';
|
|
4
|
+
|
|
5
|
+
test('share', () => {
|
|
6
|
+
const obs = share()(
|
|
7
|
+
observable<number, Error>((observer) => {
|
|
8
|
+
observer.next(1);
|
|
9
|
+
}),
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
{
|
|
13
|
+
const next = jest.fn();
|
|
14
|
+
const error = jest.fn();
|
|
15
|
+
const complete = jest.fn();
|
|
16
|
+
var subscription1 = obs.subscribe({
|
|
17
|
+
next,
|
|
18
|
+
error,
|
|
19
|
+
complete,
|
|
20
|
+
});
|
|
21
|
+
expect(next.mock.calls).toHaveLength(1);
|
|
22
|
+
expect(complete.mock.calls).toHaveLength(0);
|
|
23
|
+
expect(error.mock.calls).toHaveLength(0);
|
|
24
|
+
expect(next.mock.calls[0][0]).toBe(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
{
|
|
28
|
+
// subscribe again - it's shared so should not propagate any results
|
|
29
|
+
const next = jest.fn();
|
|
30
|
+
const error = jest.fn();
|
|
31
|
+
const complete = jest.fn();
|
|
32
|
+
var subscription2 = obs.subscribe({
|
|
33
|
+
next,
|
|
34
|
+
error,
|
|
35
|
+
complete,
|
|
36
|
+
});
|
|
37
|
+
expect(next.mock.calls).toHaveLength(0);
|
|
38
|
+
expect(complete.mock.calls).toHaveLength(0);
|
|
39
|
+
expect(error.mock.calls).toHaveLength(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
subscription1.unsubscribe();
|
|
43
|
+
subscription2.unsubscribe();
|
|
44
|
+
// now it should be reset so we can do a new subscription
|
|
45
|
+
{
|
|
46
|
+
const next = jest.fn();
|
|
47
|
+
const error = jest.fn();
|
|
48
|
+
const complete = jest.fn();
|
|
49
|
+
var subscription3 = obs.subscribe({
|
|
50
|
+
next,
|
|
51
|
+
error,
|
|
52
|
+
complete,
|
|
53
|
+
});
|
|
54
|
+
expect(next.mock.calls).toHaveLength(1);
|
|
55
|
+
expect(complete.mock.calls).toHaveLength(0);
|
|
56
|
+
expect(error.mock.calls).toHaveLength(0);
|
|
57
|
+
expect(next.mock.calls[0][0]).toBe(1);
|
|
58
|
+
subscription3.unsubscribe();
|
|
59
|
+
}
|
|
60
|
+
});
|