@trpc/client 10.27.3 → 10.28.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.
- package/dist/TRPCClientError.d.ts +25 -25
- package/dist/TRPCClientError.d.ts.map +1 -1
- package/dist/createTRPCClient.d.ts +19 -19
- package/dist/createTRPCClientProxy.d.ts +38 -38
- package/dist/createTRPCClientProxy.d.ts.map +1 -1
- package/dist/createTRPCUntypedClient.d.ts +4 -4
- package/dist/getFetch.d.ts +2 -2
- package/dist/{httpUtils-1a07b2e6.js → httpUtils-00c8b54e.js} +125 -125
- package/dist/index.d.ts +6 -6
- package/dist/internals/TRPCUntypedClient.d.ts +61 -61
- package/dist/internals/TRPCUntypedClient.d.ts.map +1 -1
- package/dist/internals/dataLoader.d.ts +17 -17
- package/dist/internals/dataLoader.d.ts.map +1 -1
- package/dist/internals/getAbortController.d.ts +3 -3
- package/dist/internals/retryDelay.d.ts +1 -1
- package/dist/internals/types.d.ts +75 -102
- package/dist/internals/types.d.ts.map +1 -1
- package/dist/links/dedupeLink.d.ts +3 -3
- package/dist/links/httpBatchLink.d.ts +15 -15
- package/dist/links/httpFormDataLink.d.ts +1 -1
- package/dist/links/httpLink.d.ts +16 -16
- package/dist/links/index.d.ts +7 -7
- package/dist/links/internals/createChain.d.ts +7 -7
- package/dist/links/internals/httpUtils.d.ts +59 -59
- package/dist/links/internals/httpUtils.d.ts.map +1 -1
- package/dist/links/internals/isObject.d.ts +1 -1
- package/dist/links/loggerLink.d.ts +39 -39
- package/dist/links/loggerLink.d.ts.map +1 -1
- package/dist/links/retryLink.d.ts +5 -5
- package/dist/links/splitLink.d.ts +13 -13
- package/dist/links/types.d.ts +69 -69
- package/dist/links/types.d.ts.map +1 -1
- package/dist/links/wsLink.d.ts +28 -28
- package/dist/links/wsLink.d.ts.map +1 -1
- package/dist/links/wsLink.js +9 -17
- package/dist/links/wsLink.mjs +9 -17
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/transformResult.d.ts +33 -33
- package/dist/{splitLink-aebd28df.js → splitLink-0df96fdc.js} +34 -34
- package/dist/{transformResult-fc4863b8.js → transformResult-ae8e970c.js} +80 -80
- package/package.json +11 -7
- package/src/internals/types.ts +0 -39
- package/src/links/wsLink.ts +10 -24
- package/src/internals/types.test.ts +0 -66
- package/src/links/dedupeLink.test.ts +0 -128
- package/src/links/internals/createChain.test.ts +0 -117
- package/src/links/splitLink.test.ts +0 -55
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
class TRPCClientError extends Error {
|
|
2
|
-
constructor(message, opts) {
|
|
3
|
-
const cause = opts?.cause;
|
|
4
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
5
|
-
// @ts-ignore https://github.com/tc39/proposal-error-cause
|
|
6
|
-
super(message, { cause });
|
|
7
|
-
this.meta = opts?.meta;
|
|
8
|
-
this.cause = cause;
|
|
9
|
-
this.shape = opts?.result?.error;
|
|
10
|
-
this.data = opts?.result?.error.data;
|
|
11
|
-
this.name = 'TRPCClientError';
|
|
12
|
-
Object.setPrototypeOf(this, TRPCClientError.prototype);
|
|
13
|
-
}
|
|
14
|
-
static from(cause, opts = {}) {
|
|
15
|
-
if (!(cause instanceof Error)) {
|
|
16
|
-
return new TRPCClientError(cause.error.message ?? '', {
|
|
17
|
-
...opts,
|
|
18
|
-
cause: undefined,
|
|
19
|
-
result: cause,
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
if (cause.name === 'TRPCClientError') {
|
|
23
|
-
return cause;
|
|
24
|
-
}
|
|
25
|
-
return new TRPCClientError(cause.message, {
|
|
26
|
-
...opts,
|
|
27
|
-
cause,
|
|
28
|
-
result: null,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
1
|
+
class TRPCClientError extends Error {
|
|
2
|
+
constructor(message, opts) {
|
|
3
|
+
const cause = opts?.cause;
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
5
|
+
// @ts-ignore https://github.com/tc39/proposal-error-cause
|
|
6
|
+
super(message, { cause });
|
|
7
|
+
this.meta = opts?.meta;
|
|
8
|
+
this.cause = cause;
|
|
9
|
+
this.shape = opts?.result?.error;
|
|
10
|
+
this.data = opts?.result?.error.data;
|
|
11
|
+
this.name = 'TRPCClientError';
|
|
12
|
+
Object.setPrototypeOf(this, TRPCClientError.prototype);
|
|
13
|
+
}
|
|
14
|
+
static from(cause, opts = {}) {
|
|
15
|
+
if (!(cause instanceof Error)) {
|
|
16
|
+
return new TRPCClientError(cause.error.message ?? '', {
|
|
17
|
+
...opts,
|
|
18
|
+
cause: undefined,
|
|
19
|
+
result: cause,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
if (cause.name === 'TRPCClientError') {
|
|
23
|
+
return cause;
|
|
24
|
+
}
|
|
25
|
+
return new TRPCClientError(cause.message, {
|
|
26
|
+
...opts,
|
|
27
|
+
cause,
|
|
28
|
+
result: null,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function isObject(value) {
|
|
34
|
-
// check that value is object
|
|
35
|
-
return !!value && !Array.isArray(value) && typeof value === 'object';
|
|
33
|
+
function isObject(value) {
|
|
34
|
+
// check that value is object
|
|
35
|
+
return !!value && !Array.isArray(value) && typeof value === 'object';
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// FIXME:
|
|
39
|
-
// - the generics here are probably unnecessary
|
|
40
|
-
// - the RPC-spec could probably be simplified to combine HTTP + WS
|
|
41
|
-
/** @internal */
|
|
42
|
-
function transformResultInner(response, runtime) {
|
|
43
|
-
if ('error' in response) {
|
|
44
|
-
const error = runtime.transformer.deserialize(response.error);
|
|
45
|
-
return {
|
|
46
|
-
ok: false,
|
|
47
|
-
error: {
|
|
48
|
-
...response,
|
|
49
|
-
error,
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
const result = {
|
|
54
|
-
...response.result,
|
|
55
|
-
...((!response.result.type || response.result.type === 'data') && {
|
|
56
|
-
type: 'data',
|
|
57
|
-
data: runtime.transformer.deserialize(response.result.data),
|
|
58
|
-
}),
|
|
59
|
-
};
|
|
60
|
-
return { ok: true, result };
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Transforms and validates that the result is a valid TRPCResponse
|
|
64
|
-
* @internal
|
|
65
|
-
*/
|
|
66
|
-
function transformResult(response, runtime) {
|
|
67
|
-
let result;
|
|
68
|
-
try {
|
|
69
|
-
// Use the data transformers on the JSON-response
|
|
70
|
-
result = transformResultInner(response, runtime);
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
throw new TRPCClientError('Unable to transform response from server');
|
|
74
|
-
}
|
|
75
|
-
// check that output of the transformers is a valid TRPCResponse
|
|
76
|
-
if (!result.ok &&
|
|
77
|
-
(!isObject(result.error.error) ||
|
|
78
|
-
typeof result.error.error.code !== 'number')) {
|
|
79
|
-
throw new TRPCClientError('Badly formatted response from server');
|
|
80
|
-
}
|
|
81
|
-
if (result.ok && !isObject(result.result)) {
|
|
82
|
-
throw new TRPCClientError('Badly formatted response from server');
|
|
83
|
-
}
|
|
84
|
-
return result;
|
|
38
|
+
// FIXME:
|
|
39
|
+
// - the generics here are probably unnecessary
|
|
40
|
+
// - the RPC-spec could probably be simplified to combine HTTP + WS
|
|
41
|
+
/** @internal */
|
|
42
|
+
function transformResultInner(response, runtime) {
|
|
43
|
+
if ('error' in response) {
|
|
44
|
+
const error = runtime.transformer.deserialize(response.error);
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
error: {
|
|
48
|
+
...response,
|
|
49
|
+
error,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const result = {
|
|
54
|
+
...response.result,
|
|
55
|
+
...((!response.result.type || response.result.type === 'data') && {
|
|
56
|
+
type: 'data',
|
|
57
|
+
data: runtime.transformer.deserialize(response.result.data),
|
|
58
|
+
}),
|
|
59
|
+
};
|
|
60
|
+
return { ok: true, result };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Transforms and validates that the result is a valid TRPCResponse
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
function transformResult(response, runtime) {
|
|
67
|
+
let result;
|
|
68
|
+
try {
|
|
69
|
+
// Use the data transformers on the JSON-response
|
|
70
|
+
result = transformResultInner(response, runtime);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
throw new TRPCClientError('Unable to transform response from server');
|
|
74
|
+
}
|
|
75
|
+
// check that output of the transformers is a valid TRPCResponse
|
|
76
|
+
if (!result.ok &&
|
|
77
|
+
(!isObject(result.error.error) ||
|
|
78
|
+
typeof result.error.error.code !== 'number')) {
|
|
79
|
+
throw new TRPCClientError('Badly formatted response from server');
|
|
80
|
+
}
|
|
81
|
+
if (result.ok && !isObject(result.result)) {
|
|
82
|
+
throw new TRPCClientError('Badly formatted response from server');
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
export { TRPCClientError as T, transformResult as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trpc/client",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.28.1",
|
|
4
4
|
"description": "The tRPC client library",
|
|
5
5
|
"author": "KATT",
|
|
6
6
|
"license": "MIT",
|
|
@@ -72,18 +72,19 @@
|
|
|
72
72
|
"README.md",
|
|
73
73
|
"package.json",
|
|
74
74
|
"links",
|
|
75
|
-
"shared"
|
|
75
|
+
"shared",
|
|
76
|
+
"!**/*.test.*"
|
|
76
77
|
],
|
|
77
78
|
"peerDependencies": {
|
|
78
|
-
"@trpc/server": "10.
|
|
79
|
+
"@trpc/server": "10.28.1"
|
|
79
80
|
},
|
|
80
81
|
"devDependencies": {
|
|
81
82
|
"@testing-library/dom": "^9.0.0",
|
|
82
|
-
"@trpc/server": "10.
|
|
83
|
+
"@trpc/server": "10.28.1",
|
|
83
84
|
"@types/isomorphic-fetch": "^0.0.36",
|
|
84
|
-
"@types/node": "^18.
|
|
85
|
+
"@types/node": "^18.16.16",
|
|
85
86
|
"eslint": "^8.40.0",
|
|
86
|
-
"expect-type": "^0.
|
|
87
|
+
"expect-type": "^0.16.0",
|
|
87
88
|
"isomorphic-fetch": "^3.0.0",
|
|
88
89
|
"node-fetch": "^3.3.0",
|
|
89
90
|
"rollup": "^2.79.1",
|
|
@@ -94,5 +95,8 @@
|
|
|
94
95
|
"publishConfig": {
|
|
95
96
|
"access": "public"
|
|
96
97
|
},
|
|
97
|
-
"
|
|
98
|
+
"funding": [
|
|
99
|
+
"https://trpc.io/sponsor"
|
|
100
|
+
],
|
|
101
|
+
"gitHead": "ea70b1c33899b66f964a7d11f5a8710148f03f8e"
|
|
98
102
|
}
|
package/src/internals/types.ts
CHANGED
|
@@ -43,33 +43,6 @@ export interface NodeFetchRequestInitEsque {
|
|
|
43
43
|
body?: string;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/**
|
|
47
|
-
* A subset of the standard Headers properties needed by tRPC internally.
|
|
48
|
-
* @see Headers from lib.dom.d.ts
|
|
49
|
-
* @remarks
|
|
50
|
-
* If you need a property that you know exists but doesn't exist on this
|
|
51
|
-
* interface, go ahead and add it.
|
|
52
|
-
*/
|
|
53
|
-
export interface HeadersEsque {
|
|
54
|
-
append(name: string, value: string): void;
|
|
55
|
-
delete(name: string): void;
|
|
56
|
-
get(name: string): string | null;
|
|
57
|
-
has(name: string): boolean;
|
|
58
|
-
set(name: string, value: string): void;
|
|
59
|
-
forEach(
|
|
60
|
-
callbackfn: (value: string, key: string) => void,
|
|
61
|
-
thisArg?: any,
|
|
62
|
-
): void;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export type ResponseType =
|
|
66
|
-
| 'basic'
|
|
67
|
-
| 'cors'
|
|
68
|
-
| 'default'
|
|
69
|
-
| 'error'
|
|
70
|
-
| 'opaque'
|
|
71
|
-
| 'opaqueredirect';
|
|
72
|
-
|
|
73
46
|
/**
|
|
74
47
|
* A subset of the standard RequestInit properties needed by tRPC internally.
|
|
75
48
|
* @see RequestInit from lib.dom.d.ts
|
|
@@ -102,20 +75,8 @@ export interface RequestInitEsque {
|
|
|
102
75
|
/**
|
|
103
76
|
* A subset of the standard Response properties needed by tRPC internally.
|
|
104
77
|
* @see Response from lib.dom.d.ts
|
|
105
|
-
* @remarks
|
|
106
|
-
* If you need a property that you know exists but doesn't exist on this
|
|
107
|
-
* interface, go ahead and add it.
|
|
108
78
|
*/
|
|
109
79
|
export interface ResponseEsque {
|
|
110
|
-
readonly headers: HeadersEsque;
|
|
111
|
-
readonly ok: boolean;
|
|
112
|
-
readonly redirected: boolean;
|
|
113
|
-
readonly status: number;
|
|
114
|
-
readonly statusText: string;
|
|
115
|
-
readonly type: ResponseType;
|
|
116
|
-
readonly url: string;
|
|
117
|
-
clone(): ResponseEsque;
|
|
118
|
-
|
|
119
80
|
/**
|
|
120
81
|
* @remarks
|
|
121
82
|
* The built-in Response::json() method returns Promise<any>, but
|
package/src/links/wsLink.ts
CHANGED
|
@@ -120,6 +120,14 @@ export function createWSClient(opts: WebSocketClientOptions) {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
function closeActiveSubscriptions() {
|
|
124
|
+
Object.values(pendingRequests).forEach((req) => {
|
|
125
|
+
if (req.type === 'subscription') {
|
|
126
|
+
req.callbacks.complete();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
123
131
|
function resumeSubscriptionOnReconnect(req: TRequest) {
|
|
124
132
|
if (outgoing.some((r) => r.id === req.op.id)) {
|
|
125
133
|
return;
|
|
@@ -281,6 +289,7 @@ export function createWSClient(opts: WebSocketClientOptions) {
|
|
|
281
289
|
close: () => {
|
|
282
290
|
state = 'closed';
|
|
283
291
|
onClose?.();
|
|
292
|
+
closeActiveSubscriptions();
|
|
284
293
|
closeIfNoPending(activeConnection);
|
|
285
294
|
clearTimeout(connectTimer as any);
|
|
286
295
|
connectTimer = null;
|
|
@@ -304,14 +313,6 @@ class TRPCWebSocketClosedError extends Error {
|
|
|
304
313
|
}
|
|
305
314
|
}
|
|
306
315
|
|
|
307
|
-
class TRPCSubscriptionEndedError extends Error {
|
|
308
|
-
constructor(message: string) {
|
|
309
|
-
super(message);
|
|
310
|
-
this.name = 'TRPCSubscriptionEndedError';
|
|
311
|
-
Object.setPrototypeOf(this, TRPCSubscriptionEndedError.prototype);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
316
|
export function wsLink<TRouter extends AnyRouter>(
|
|
316
317
|
opts: WebSocketLinkOptions,
|
|
317
318
|
): TRPCLink<TRouter> {
|
|
@@ -323,28 +324,15 @@ export function wsLink<TRouter extends AnyRouter>(
|
|
|
323
324
|
|
|
324
325
|
const input = runtime.transformer.serialize(op.input);
|
|
325
326
|
|
|
326
|
-
let isDone = false;
|
|
327
327
|
const unsub = client.request(
|
|
328
328
|
{ type, path, input, id, context },
|
|
329
329
|
{
|
|
330
330
|
error(err) {
|
|
331
|
-
isDone = true;
|
|
332
331
|
observer.error(err as TRPCClientError<any>);
|
|
333
332
|
unsub();
|
|
334
333
|
},
|
|
335
334
|
complete() {
|
|
336
|
-
|
|
337
|
-
isDone = true;
|
|
338
|
-
observer.error(
|
|
339
|
-
TRPCClientError.from(
|
|
340
|
-
new TRPCSubscriptionEndedError(
|
|
341
|
-
'Operation ended prematurely',
|
|
342
|
-
),
|
|
343
|
-
),
|
|
344
|
-
);
|
|
345
|
-
} else {
|
|
346
|
-
observer.complete();
|
|
347
|
-
}
|
|
335
|
+
observer.complete();
|
|
348
336
|
},
|
|
349
337
|
next(message) {
|
|
350
338
|
const transformed = transformResult(message, runtime);
|
|
@@ -360,7 +348,6 @@ export function wsLink<TRouter extends AnyRouter>(
|
|
|
360
348
|
if (op.type !== 'subscription') {
|
|
361
349
|
// if it isn't a subscription we don't care about next response
|
|
362
350
|
|
|
363
|
-
isDone = true;
|
|
364
351
|
unsub();
|
|
365
352
|
observer.complete();
|
|
366
353
|
}
|
|
@@ -368,7 +355,6 @@ export function wsLink<TRouter extends AnyRouter>(
|
|
|
368
355
|
},
|
|
369
356
|
);
|
|
370
357
|
return () => {
|
|
371
|
-
isDone = true;
|
|
372
358
|
unsub();
|
|
373
359
|
};
|
|
374
360
|
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import isomorphicFetch from 'isomorphic-fetch';
|
|
2
|
-
import nodeFetch from 'node-fetch';
|
|
3
|
-
import type { fetch as undiciFetch } from 'undici';
|
|
4
|
-
import { createTRPCProxyClient } from '../createTRPCClientProxy';
|
|
5
|
-
import { getFetch } from '../getFetch';
|
|
6
|
-
import { httpBatchLink } from '../links/httpBatchLink';
|
|
7
|
-
import { getAbortController } from './getAbortController';
|
|
8
|
-
import {
|
|
9
|
-
AbortControllerEsque,
|
|
10
|
-
AbortControllerInstanceEsque,
|
|
11
|
-
FetchEsque,
|
|
12
|
-
ResponseEsque,
|
|
13
|
-
} from './types';
|
|
14
|
-
|
|
15
|
-
describe('AbortController', () => {
|
|
16
|
-
test('AbortControllerEsque', () => {
|
|
17
|
-
expectTypeOf(
|
|
18
|
-
getAbortController,
|
|
19
|
-
).returns.toEqualTypeOf<AbortControllerEsque | null>();
|
|
20
|
-
|
|
21
|
-
expectTypeOf(() => {
|
|
22
|
-
const AbortController = getAbortController(undefined)!;
|
|
23
|
-
return new AbortController();
|
|
24
|
-
}).returns.toEqualTypeOf<AbortControllerInstanceEsque>();
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe('fetch', () => {
|
|
29
|
-
test('parameters', () => {
|
|
30
|
-
createTRPCProxyClient({
|
|
31
|
-
links: [
|
|
32
|
-
httpBatchLink({
|
|
33
|
-
url: 'YOUR_SERVER_URL',
|
|
34
|
-
fetch(url, options) {
|
|
35
|
-
return fetch(url, options);
|
|
36
|
-
},
|
|
37
|
-
}),
|
|
38
|
-
],
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('FetchEsque', () => {
|
|
43
|
-
expectTypeOf(getFetch).returns.toEqualTypeOf<FetchEsque>();
|
|
44
|
-
|
|
45
|
-
expectTypeOf(() =>
|
|
46
|
-
getFetch()('', {
|
|
47
|
-
body: '',
|
|
48
|
-
headers: Math.random() > 0.5 ? [['a', 'b']] : { a: 'b' },
|
|
49
|
-
method: 'GET',
|
|
50
|
-
signal: new AbortSignal(),
|
|
51
|
-
}),
|
|
52
|
-
).returns.toEqualTypeOf<Promise<ResponseEsque>>();
|
|
53
|
-
|
|
54
|
-
getFetch({} as unknown as typeof fetch);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('NativeFetchEsque', () => {
|
|
58
|
-
getFetch(isomorphicFetch);
|
|
59
|
-
getFetch(nodeFetch as any);
|
|
60
|
-
|
|
61
|
-
// Passing in undiciFetch directly in Node v18.7.0 gives:
|
|
62
|
-
// ReferenceError: TextEncoder is not defined
|
|
63
|
-
// 🤷
|
|
64
|
-
getFetch({} as unknown as typeof undiciFetch);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { waitFor } from '@testing-library/dom';
|
|
2
|
-
import { OperationLink } from '..';
|
|
3
|
-
import { AnyRouter } from '../../../server/src';
|
|
4
|
-
import { observable } from '../../../server/src/observable';
|
|
5
|
-
import { dedupeLink } from './dedupeLink';
|
|
6
|
-
import { createChain } from './internals/createChain';
|
|
7
|
-
|
|
8
|
-
test('dedupeLink', async () => {
|
|
9
|
-
const endingLinkTriggered = vi.fn();
|
|
10
|
-
const timerTriggered = vi.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
|
-
result: {
|
|
21
|
-
type: 'data',
|
|
22
|
-
data: {
|
|
23
|
-
input: op.input,
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
subscribe.complete();
|
|
28
|
-
}, 1);
|
|
29
|
-
return () => clearTimeout(timer);
|
|
30
|
-
});
|
|
31
|
-
},
|
|
32
|
-
];
|
|
33
|
-
{
|
|
34
|
-
const call1 = createChain<AnyRouter, unknown, unknown>({
|
|
35
|
-
links,
|
|
36
|
-
op: {
|
|
37
|
-
type: 'query',
|
|
38
|
-
id: 1,
|
|
39
|
-
input: 'world',
|
|
40
|
-
path: 'hello',
|
|
41
|
-
context: {},
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const call2 = createChain<AnyRouter, unknown, unknown>({
|
|
46
|
-
links,
|
|
47
|
-
op: {
|
|
48
|
-
type: 'query',
|
|
49
|
-
id: 1,
|
|
50
|
-
input: 'world',
|
|
51
|
-
path: 'hello',
|
|
52
|
-
context: {},
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
const next = vi.fn();
|
|
56
|
-
call1.subscribe({ next });
|
|
57
|
-
call2.subscribe({ next });
|
|
58
|
-
|
|
59
|
-
expect(endingLinkTriggered).toHaveBeenCalledTimes(1);
|
|
60
|
-
await waitFor(() => {
|
|
61
|
-
expect(timerTriggered).toHaveBeenCalledTimes(1);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
expect(next).toHaveBeenCalledTimes(2);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('dedupe - cancel one does not cancel the other', async () => {
|
|
69
|
-
const endingLinkTriggered = vi.fn();
|
|
70
|
-
const timerTriggered = vi.fn();
|
|
71
|
-
const links: OperationLink<AnyRouter, any, any>[] = [
|
|
72
|
-
// "dedupe link"
|
|
73
|
-
dedupeLink()(null as any),
|
|
74
|
-
({ op }) => {
|
|
75
|
-
return observable((subscribe) => {
|
|
76
|
-
endingLinkTriggered();
|
|
77
|
-
const timer = setTimeout(() => {
|
|
78
|
-
timerTriggered();
|
|
79
|
-
subscribe.next({
|
|
80
|
-
result: {
|
|
81
|
-
type: 'data',
|
|
82
|
-
data: {
|
|
83
|
-
input: op.input,
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
subscribe.complete();
|
|
88
|
-
}, 1);
|
|
89
|
-
return () => clearTimeout(timer);
|
|
90
|
-
});
|
|
91
|
-
},
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
const call1 = createChain<AnyRouter, unknown, unknown>({
|
|
96
|
-
links,
|
|
97
|
-
op: {
|
|
98
|
-
type: 'query',
|
|
99
|
-
id: 1,
|
|
100
|
-
input: 'world',
|
|
101
|
-
path: 'hello',
|
|
102
|
-
context: {},
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const call2 = createChain<AnyRouter, unknown, unknown>({
|
|
107
|
-
links,
|
|
108
|
-
op: {
|
|
109
|
-
type: 'query',
|
|
110
|
-
id: 1,
|
|
111
|
-
input: 'world',
|
|
112
|
-
path: 'hello',
|
|
113
|
-
context: {},
|
|
114
|
-
},
|
|
115
|
-
});
|
|
116
|
-
const next = vi.fn();
|
|
117
|
-
const call1$ = call1.subscribe({ next });
|
|
118
|
-
call2.subscribe({ next });
|
|
119
|
-
call1$.unsubscribe();
|
|
120
|
-
|
|
121
|
-
expect(endingLinkTriggered).toHaveBeenCalledTimes(1);
|
|
122
|
-
await waitFor(() => {
|
|
123
|
-
expect(timerTriggered).toHaveBeenCalledTimes(1);
|
|
124
|
-
|
|
125
|
-
expect(next).toHaveBeenCalledTimes(1);
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
});
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { observable } from '@trpc/server/observable';
|
|
2
|
-
import { AnyRouter } from '@trpc/server/src';
|
|
3
|
-
import { createChain } from './createChain';
|
|
4
|
-
|
|
5
|
-
describe('chain', () => {
|
|
6
|
-
test('trivial', () => {
|
|
7
|
-
const result$ = createChain<AnyRouter, unknown, unknown>({
|
|
8
|
-
links: [
|
|
9
|
-
({ next, op }) => {
|
|
10
|
-
return observable((observer) => {
|
|
11
|
-
const subscription = next(op).subscribe(observer);
|
|
12
|
-
return () => {
|
|
13
|
-
subscription.unsubscribe();
|
|
14
|
-
};
|
|
15
|
-
});
|
|
16
|
-
},
|
|
17
|
-
({ op }) => {
|
|
18
|
-
return observable((observer) => {
|
|
19
|
-
observer.next({
|
|
20
|
-
context: {},
|
|
21
|
-
result: {
|
|
22
|
-
type: 'data',
|
|
23
|
-
data: {
|
|
24
|
-
input: op.input,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
observer.complete();
|
|
29
|
-
});
|
|
30
|
-
},
|
|
31
|
-
],
|
|
32
|
-
op: {
|
|
33
|
-
type: 'query',
|
|
34
|
-
id: 1,
|
|
35
|
-
input: 'world',
|
|
36
|
-
path: 'hello',
|
|
37
|
-
context: {},
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const next = vi.fn();
|
|
42
|
-
|
|
43
|
-
result$.subscribe({ next });
|
|
44
|
-
// console.log(next.mock.calls);
|
|
45
|
-
expect(next).toHaveBeenCalledTimes(1);
|
|
46
|
-
});
|
|
47
|
-
test('multiple responses', () => {
|
|
48
|
-
const result$ = createChain<AnyRouter, unknown, unknown>({
|
|
49
|
-
links: [
|
|
50
|
-
({ next, op }) => {
|
|
51
|
-
return observable((observer) => {
|
|
52
|
-
observer.next({
|
|
53
|
-
context: {},
|
|
54
|
-
result: {
|
|
55
|
-
type: 'data',
|
|
56
|
-
data: 'from cache',
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
const subscription = next(op).subscribe(observer);
|
|
60
|
-
return () => {
|
|
61
|
-
subscription.unsubscribe();
|
|
62
|
-
};
|
|
63
|
-
});
|
|
64
|
-
},
|
|
65
|
-
({ op }) => {
|
|
66
|
-
return observable((observer) => {
|
|
67
|
-
observer.next({
|
|
68
|
-
result: {
|
|
69
|
-
type: 'data',
|
|
70
|
-
data: {
|
|
71
|
-
input: op.input,
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
observer.complete();
|
|
76
|
-
});
|
|
77
|
-
},
|
|
78
|
-
],
|
|
79
|
-
op: {
|
|
80
|
-
type: 'query',
|
|
81
|
-
id: 1,
|
|
82
|
-
input: 'world',
|
|
83
|
-
path: 'hello',
|
|
84
|
-
context: {},
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const next = vi.fn();
|
|
89
|
-
|
|
90
|
-
result$.subscribe({ next });
|
|
91
|
-
|
|
92
|
-
expect(next).toHaveBeenCalledTimes(2);
|
|
93
|
-
expect(next.mock.calls[0]).toMatchInlineSnapshot(`
|
|
94
|
-
Array [
|
|
95
|
-
Object {
|
|
96
|
-
"context": Object {},
|
|
97
|
-
"result": Object {
|
|
98
|
-
"data": "from cache",
|
|
99
|
-
"type": "data",
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
]
|
|
103
|
-
`);
|
|
104
|
-
expect(next.mock.calls[1]).toMatchInlineSnapshot(`
|
|
105
|
-
Array [
|
|
106
|
-
Object {
|
|
107
|
-
"result": Object {
|
|
108
|
-
"data": Object {
|
|
109
|
-
"input": "world",
|
|
110
|
-
},
|
|
111
|
-
"type": "data",
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
]
|
|
115
|
-
`);
|
|
116
|
-
});
|
|
117
|
-
});
|