@trpc/client 10.0.0-alpha.9 → 10.0.0-proxy-alpha.54
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/README.md +58 -1
- package/dist/TRPCClientError.d.ts +16 -20
- package/dist/TRPCClientError.d.ts.map +1 -1
- package/dist/createTRPCClient.d.ts +5 -3
- package/dist/createTRPCClient.d.ts.map +1 -1
- package/dist/createTRPCClientProxy.d.ts +37 -0
- package/dist/createTRPCClientProxy.d.ts.map +1 -0
- package/dist/httpUtils-44781613.mjs +76 -0
- package/dist/httpUtils-801112a6.js +79 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +262 -280
- package/dist/index.mjs +255 -271
- package/dist/internals/TRPCClient.d.ts +39 -21
- package/dist/internals/TRPCClient.d.ts.map +1 -1
- package/dist/internals/dataLoader.d.ts +7 -6
- package/dist/internals/dataLoader.d.ts.map +1 -1
- package/dist/internals/fetchHelpers.d.ts +1 -1
- package/dist/internals/fetchHelpers.d.ts.map +1 -1
- package/dist/internals/retryDelay.d.ts.map +1 -1
- package/dist/links/dedupeLink.d.ts.map +1 -1
- package/dist/links/httpBatchLink.d.ts +2 -2
- package/dist/links/httpBatchLink.d.ts.map +1 -1
- package/dist/links/httpBatchLink.js +189 -172
- package/dist/links/httpBatchLink.mjs +189 -172
- package/dist/links/httpLink.d.ts +1 -1
- package/dist/links/httpLink.d.ts.map +1 -1
- package/dist/links/httpLink.js +31 -35
- package/dist/links/httpLink.mjs +31 -35
- package/dist/links/index.d.ts +9 -0
- package/dist/links/index.d.ts.map +1 -0
- package/dist/links/internals/createChain.d.ts.map +1 -1
- package/dist/links/internals/httpUtils.d.ts +31 -0
- package/dist/links/internals/httpUtils.d.ts.map +1 -0
- package/dist/links/internals/transformResult.d.ts +28 -0
- package/dist/links/internals/transformResult.d.ts.map +1 -0
- package/dist/links/loggerLink.d.ts +3 -3
- package/dist/links/loggerLink.d.ts.map +1 -1
- package/dist/links/loggerLink.js +90 -96
- package/dist/links/loggerLink.mjs +90 -91
- package/dist/links/retryLink.d.ts.map +1 -1
- package/dist/links/splitLink.d.ts +1 -1
- package/dist/links/splitLink.d.ts.map +1 -1
- package/dist/links/splitLink.js +3 -25
- package/dist/links/splitLink.mjs +2 -28
- package/dist/links/types.d.ts +13 -12
- package/dist/links/types.d.ts.map +1 -1
- package/dist/links/wsLink.d.ts +12 -5
- package/dist/links/wsLink.d.ts.map +1 -1
- package/dist/links/wsLink.js +250 -358
- package/dist/links/wsLink.mjs +250 -348
- package/dist/splitLink-695ae288.js +48 -0
- package/dist/splitLink-ad44a55d.mjs +45 -0
- package/dist/transformResult-106791d7.mjs +62 -0
- package/dist/transformResult-e15ccdf6.js +65 -0
- package/links/httpBatchLink/dist/trpc-client-links-httpBatchLink.cjs.dev.js +15 -13
- package/links/httpBatchLink/dist/trpc-client-links-httpBatchLink.cjs.prod.js +15 -13
- package/links/httpBatchLink/dist/trpc-client-links-httpBatchLink.esm.js +12 -10
- package/links/httpLink/dist/trpc-client-links-httpLink.cjs.dev.js +4 -4
- package/links/httpLink/dist/trpc-client-links-httpLink.cjs.prod.js +4 -4
- package/links/httpLink/dist/trpc-client-links-httpLink.esm.js +1 -1
- package/links/wsLink/dist/trpc-client-links-wsLink.cjs.dev.js +3 -1
- package/links/wsLink/dist/trpc-client-links-wsLink.cjs.prod.js +3 -1
- package/links/wsLink/dist/trpc-client-links-wsLink.esm.js +3 -1
- package/package.json +23 -34
- package/src/TRPCClientError.ts +38 -28
- package/src/createTRPCClient.ts +11 -3
- package/src/createTRPCClientProxy.ts +113 -0
- package/src/index.ts +2 -1
- package/src/internals/TRPCClient.ts +118 -77
- package/src/internals/dataLoader.ts +116 -53
- package/src/internals/fetchHelpers.ts +2 -2
- package/src/links/dedupeLink.test.ts +9 -15
- package/src/links/dedupeLink.ts +6 -7
- package/src/links/httpBatchLink.ts +73 -36
- package/src/links/httpLink.ts +18 -6
- package/src/links/index.ts +8 -0
- package/src/links/internals/createChain.test.ts +23 -38
- package/src/links/internals/createChain.ts +8 -3
- package/src/links/internals/httpUtils.ts +117 -0
- package/src/links/internals/transformResult.ts +40 -0
- package/src/links/loggerLink.ts +6 -7
- package/src/links/retryLink.ts +2 -3
- package/src/links/splitLink.test.ts +3 -3
- package/src/links/splitLink.ts +5 -5
- package/src/links/types.ts +15 -12
- package/src/links/wsLink.ts +57 -23
- package/dist/TRPCClientError-17b117e1.mjs +0 -81
- package/dist/TRPCClientError-ec6bd2ca.js +0 -94
- package/dist/createChain-389cc116.mjs +0 -25
- package/dist/createChain-864dbb30.js +0 -27
- package/dist/httpUtils-1f9e4e3c.js +0 -97
- package/dist/httpUtils-c09d7b79.mjs +0 -91
- package/dist/index.ts.js +0 -1
- package/dist/links/httpBatchLink.ts.js +0 -1
- package/dist/links/httpLink.ts.js +0 -1
- package/dist/links/httpUtils.d.ts +0 -22
- package/dist/links/httpUtils.d.ts.map +0 -1
- package/dist/links/internals/transformOperationResult.d.ts +0 -16
- package/dist/links/internals/transformOperationResult.d.ts.map +0 -1
- package/dist/links/loggerLink.ts.js +0 -1
- package/dist/links/splitLink.ts.js +0 -1
- package/dist/links/transformerLink.d.ts +0 -4
- package/dist/links/transformerLink.d.ts.map +0 -1
- package/dist/links/transformerLink.js +0 -75
- package/dist/links/transformerLink.mjs +0 -67
- package/dist/links/transformerLink.ts.js +0 -1
- package/dist/links/wsLink.ts.js +0 -1
- package/dist/observable-7e66cb56.mjs +0 -102
- package/dist/observable-85669100.js +0 -104
- package/dist/rx/index.d.ts +0 -3
- package/dist/rx/index.d.ts.map +0 -1
- package/dist/rx/index.js +0 -9
- package/dist/rx/index.mjs +0 -1
- package/dist/rx/index.ts.js +0 -1
- package/dist/rx/observable.d.ts +0 -4
- package/dist/rx/observable.d.ts.map +0 -1
- package/dist/rx/operators/index.d.ts +0 -4
- package/dist/rx/operators/index.d.ts.map +0 -1
- package/dist/rx/operators/index.js +0 -38
- package/dist/rx/operators/index.mjs +0 -32
- package/dist/rx/operators/index.ts.js +0 -1
- package/dist/rx/operators/map.d.ts +0 -3
- package/dist/rx/operators/map.d.ts.map +0 -1
- package/dist/rx/operators/share.d.ts +0 -6
- package/dist/rx/operators/share.d.ts.map +0 -1
- package/dist/rx/operators/tap.d.ts +0 -3
- package/dist/rx/operators/tap.d.ts.map +0 -1
- package/dist/rx/types.d.ts +0 -29
- package/dist/rx/types.d.ts.map +0 -1
- package/dist/rx/util/identity.d.ts +0 -2
- package/dist/rx/util/identity.d.ts.map +0 -1
- package/dist/rx/util/observableToPromise.d.ts +0 -10
- package/dist/rx/util/observableToPromise.d.ts.map +0 -1
- package/dist/rx/util/pipe.d.ts +0 -4
- package/dist/rx/util/pipe.d.ts.map +0 -1
- package/dist/share-4d79dc49.js +0 -110
- package/dist/share-671b63fa.mjs +0 -108
- package/dist/tap-538fa07f.mjs +0 -30
- package/dist/tap-799a2d33.js +0 -32
- package/links/transformerLink/dist/trpc-client-links-transformerLink.cjs.d.ts +0 -1
- package/links/transformerLink/dist/trpc-client-links-transformerLink.cjs.dev.js +0 -67
- package/links/transformerLink/dist/trpc-client-links-transformerLink.cjs.js +0 -7
- package/links/transformerLink/dist/trpc-client-links-transformerLink.cjs.prod.js +0 -67
- package/links/transformerLink/dist/trpc-client-links-transformerLink.esm.js +0 -63
- package/links/transformerLink/index.d.ts +0 -1
- package/links/transformerLink/index.js +0 -1
- package/rx/dist/trpc-client-rx.cjs.d.ts +0 -1
- package/rx/dist/trpc-client-rx.cjs.dev.js +0 -9
- package/rx/dist/trpc-client-rx.cjs.js +0 -7
- package/rx/dist/trpc-client-rx.cjs.prod.js +0 -9
- package/rx/dist/trpc-client-rx.esm.js +0 -1
- package/rx/index.d.ts +0 -1
- package/rx/index.js +0 -1
- package/rx/operators/dist/trpc-client-rx-operators.cjs.d.ts +0 -1
- package/rx/operators/dist/trpc-client-rx-operators.cjs.dev.js +0 -39
- package/rx/operators/dist/trpc-client-rx-operators.cjs.js +0 -7
- package/rx/operators/dist/trpc-client-rx-operators.cjs.prod.js +0 -39
- package/rx/operators/dist/trpc-client-rx-operators.esm.js +0 -33
- package/rx/operators/index.d.ts +0 -1
- package/rx/operators/index.js +0 -1
- package/src/links/httpUtils.ts +0 -94
- package/src/links/internals/transformOperationResult.ts +0 -25
- package/src/links/transformerLink.ts +0 -70
- package/src/rx/index.ts +0 -2
- package/src/rx/observable.test.ts +0 -83
- package/src/rx/observable.ts +0 -70
- package/src/rx/operators/index.ts +0 -3
- package/src/rx/operators/map.test.ts +0 -92
- package/src/rx/operators/map.ts +0 -25
- package/src/rx/operators/share.test.ts +0 -60
- package/src/rx/operators/share.ts +0 -67
- package/src/rx/operators/tap.ts +0 -26
- package/src/rx/types.ts +0 -69
- package/src/rx/util/identity.ts +0 -3
- package/src/rx/util/observableToPromise.ts +0 -49
- package/src/rx/util/pipe.ts +0 -22
|
@@ -1,18 +1,32 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
1
2
|
import { CancelFn, PromiseAndCancel } from '../links/types';
|
|
2
3
|
|
|
3
4
|
type BatchItem<TKey, TValue> = {
|
|
4
|
-
|
|
5
|
+
aborted: boolean;
|
|
5
6
|
key: TKey;
|
|
6
7
|
resolve: (value: TValue) => void;
|
|
7
8
|
reject: (error: Error) => void;
|
|
9
|
+
batch: Batch<TKey, TValue> | null;
|
|
8
10
|
};
|
|
9
11
|
type Batch<TKey, TValue> = {
|
|
10
12
|
items: BatchItem<TKey, TValue>[];
|
|
11
13
|
cancel: CancelFn;
|
|
12
14
|
};
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
type BatchLoader<TKey, TValue> = {
|
|
16
|
+
validate: (keys: TKey[]) => boolean;
|
|
17
|
+
fetch: (keys: TKey[]) => {
|
|
18
|
+
promise: Promise<TValue[]>;
|
|
19
|
+
cancel: CancelFn;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A function that should never be called unless we messed something up.
|
|
25
|
+
*/
|
|
26
|
+
const throwFatalError = () => {
|
|
27
|
+
throw new Error(
|
|
28
|
+
'Something went wrong. Please submit an issue at https://github.com/trpc/trpc/issues/new',
|
|
29
|
+
);
|
|
16
30
|
};
|
|
17
31
|
|
|
18
32
|
/**
|
|
@@ -21,78 +35,127 @@ type BatchLoadFn<TKey, TValue> = (keys: TKey[]) => {
|
|
|
21
35
|
* When cancelling a single fetch the whole batch will be cancelled only when _all_ items are cancelled
|
|
22
36
|
*/
|
|
23
37
|
export function dataLoader<TKey, TValue>(
|
|
24
|
-
|
|
25
|
-
opts?: { maxBatchSize?: number },
|
|
38
|
+
batchLoader: BatchLoader<TKey, TValue>,
|
|
26
39
|
) {
|
|
27
|
-
let
|
|
28
|
-
let dispatchTimer:
|
|
40
|
+
let pendingItems: BatchItem<TKey, TValue>[] | null = null;
|
|
41
|
+
let dispatchTimer: ReturnType<typeof setTimeout> | null = null;
|
|
29
42
|
|
|
30
|
-
const
|
|
43
|
+
const destroyTimerAndPendingItems = () => {
|
|
31
44
|
clearTimeout(dispatchTimer as any);
|
|
32
45
|
dispatchTimer = null;
|
|
33
|
-
|
|
46
|
+
pendingItems = null;
|
|
34
47
|
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Iterate through the items and split them into groups based on the `batchLoader`'s validate function
|
|
51
|
+
*/
|
|
52
|
+
function groupItems(items: BatchItem<TKey, TValue>[]) {
|
|
53
|
+
const groupedItems: BatchItem<TKey, TValue>[][] = [[]];
|
|
54
|
+
let index = 0;
|
|
55
|
+
while (true) {
|
|
56
|
+
const item = items[index];
|
|
57
|
+
if (!item) {
|
|
58
|
+
// we're done
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
const lastGroup = groupedItems[groupedItems.length - 1]!;
|
|
62
|
+
|
|
63
|
+
if (item.aborted) {
|
|
64
|
+
// Item was aborted before it was dispatched
|
|
65
|
+
item.reject(new Error('Aborted'));
|
|
66
|
+
index++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const isValid = batchLoader.validate(
|
|
71
|
+
lastGroup.concat(item).map((it) => it.key),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (isValid) {
|
|
75
|
+
lastGroup.push(item);
|
|
76
|
+
index++;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (lastGroup.length === 0) {
|
|
81
|
+
item.reject(new Error('Input is too big for a single dispatch'));
|
|
82
|
+
index++;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
// Create new group, next iteration will try to add the item to that
|
|
86
|
+
groupedItems.push([]);
|
|
87
|
+
}
|
|
88
|
+
return groupedItems;
|
|
89
|
+
}
|
|
90
|
+
|
|
35
91
|
function dispatch() {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
92
|
+
const groupedItems = groupItems(pendingItems!);
|
|
93
|
+
destroyTimerAndPendingItems();
|
|
94
|
+
|
|
95
|
+
// Create batches for each group of items
|
|
96
|
+
for (const items of groupedItems) {
|
|
97
|
+
if (!items.length) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const batch: Batch<TKey, TValue> = {
|
|
101
|
+
items,
|
|
102
|
+
cancel: throwFatalError,
|
|
103
|
+
};
|
|
104
|
+
for (const item of items) {
|
|
105
|
+
item.batch = batch;
|
|
106
|
+
}
|
|
107
|
+
const { promise, cancel } = batchLoader.fetch(
|
|
108
|
+
batch.items.map((_item) => _item.key),
|
|
109
|
+
);
|
|
110
|
+
batch.cancel = cancel;
|
|
111
|
+
|
|
112
|
+
promise
|
|
113
|
+
.then((result) => {
|
|
114
|
+
for (let i = 0; i < result.length; i++) {
|
|
115
|
+
const value = result[i]!;
|
|
116
|
+
const item = batch.items[i]!;
|
|
117
|
+
item.resolve(value);
|
|
118
|
+
item.batch = null;
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
.catch((cause) => {
|
|
122
|
+
for (const item of batch.items) {
|
|
123
|
+
item.reject(cause);
|
|
124
|
+
item.batch = null;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
54
128
|
}
|
|
55
129
|
function load(key: TKey): PromiseAndCancel<TValue> {
|
|
56
|
-
const
|
|
57
|
-
|
|
130
|
+
const item: BatchItem<TKey, TValue> = {
|
|
131
|
+
aborted: false,
|
|
58
132
|
key,
|
|
133
|
+
batch: null,
|
|
134
|
+
resolve: throwFatalError,
|
|
135
|
+
reject: throwFatalError,
|
|
59
136
|
};
|
|
60
137
|
|
|
61
|
-
if (!batch) {
|
|
62
|
-
batch = {
|
|
63
|
-
items: [],
|
|
64
|
-
cancel() {
|
|
65
|
-
destroyTimerAndBatch();
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
const thisBatch = batch;
|
|
70
138
|
const promise = new Promise<TValue>((resolve, reject) => {
|
|
71
|
-
const item = batchItem as any as BatchItem<TKey, TValue>;
|
|
72
139
|
item.reject = reject;
|
|
73
140
|
item.resolve = resolve;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
typeof opts?.maxBatchSize !== 'undefined' &&
|
|
78
|
-
thisBatch.items.length >= opts.maxBatchSize
|
|
79
|
-
) {
|
|
80
|
-
dispatch();
|
|
81
|
-
return;
|
|
141
|
+
|
|
142
|
+
if (!pendingItems) {
|
|
143
|
+
pendingItems = [];
|
|
82
144
|
}
|
|
145
|
+
pendingItems.push(item);
|
|
83
146
|
});
|
|
84
147
|
|
|
85
148
|
if (!dispatchTimer) {
|
|
86
149
|
dispatchTimer = setTimeout(dispatch);
|
|
87
150
|
}
|
|
88
151
|
const cancel = () => {
|
|
89
|
-
|
|
152
|
+
item.aborted = true;
|
|
90
153
|
|
|
91
|
-
if (
|
|
92
|
-
//
|
|
93
|
-
|
|
154
|
+
if (item.batch?.items.every((item) => item.aborted)) {
|
|
155
|
+
// All items in the batch have been cancelled
|
|
156
|
+
item.batch.cancel();
|
|
157
|
+
item.batch = null;
|
|
94
158
|
}
|
|
95
|
-
thisBatch.cancel();
|
|
96
159
|
};
|
|
97
160
|
|
|
98
161
|
return { promise, cancel };
|
|
@@ -6,7 +6,7 @@ export function getWindow() {
|
|
|
6
6
|
}
|
|
7
7
|
export function getAbortController(
|
|
8
8
|
ac?: typeof AbortController,
|
|
9
|
-
): typeof AbortController |
|
|
9
|
+
): typeof AbortController | undefined {
|
|
10
10
|
const win = getWindow();
|
|
11
|
-
return ac
|
|
11
|
+
return ac ?? win.AbortController ?? undefined;
|
|
12
12
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { waitFor } from '@testing-library/dom';
|
|
2
2
|
import { AnyRouter } from '@trpc/server';
|
|
3
|
+
import { observable } from '@trpc/server/observable';
|
|
3
4
|
import { OperationLink } from '..';
|
|
4
|
-
import { observable } from '../rx/observable';
|
|
5
5
|
import { dedupeLink } from './dedupeLink';
|
|
6
6
|
import { createChain } from './internals/createChain';
|
|
7
7
|
|
|
@@ -17,13 +17,10 @@ test('dedupeLink', async () => {
|
|
|
17
17
|
const timer = setTimeout(() => {
|
|
18
18
|
timerTriggered();
|
|
19
19
|
subscribe.next({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
data: {
|
|
25
|
-
input: op.input,
|
|
26
|
-
},
|
|
20
|
+
result: {
|
|
21
|
+
type: 'data',
|
|
22
|
+
data: {
|
|
23
|
+
input: op.input,
|
|
27
24
|
},
|
|
28
25
|
},
|
|
29
26
|
});
|
|
@@ -80,13 +77,10 @@ test('dedupe - cancel one does not cancel the other', async () => {
|
|
|
80
77
|
const timer = setTimeout(() => {
|
|
81
78
|
timerTriggered();
|
|
82
79
|
subscribe.next({
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
data: {
|
|
88
|
-
input: op.input,
|
|
89
|
-
},
|
|
80
|
+
result: {
|
|
81
|
+
type: 'data',
|
|
82
|
+
data: {
|
|
83
|
+
input: op.input,
|
|
90
84
|
},
|
|
91
85
|
},
|
|
92
86
|
});
|
package/src/links/dedupeLink.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { AnyRouter } from '@trpc/server';
|
|
2
|
-
import { observable } from '
|
|
3
|
-
import { share } from '../rx/operators';
|
|
4
|
-
import { Observable } from '../rx/types';
|
|
2
|
+
import { Observable, observable, share } from '@trpc/server/observable';
|
|
5
3
|
import { TRPCLink } from './types';
|
|
6
4
|
|
|
7
5
|
export function dedupeLink<
|
|
@@ -19,16 +17,17 @@ export function dedupeLink<
|
|
|
19
17
|
return next(op);
|
|
20
18
|
}
|
|
21
19
|
const key = JSON.stringify([op.path, op.input]);
|
|
22
|
-
|
|
20
|
+
const obs$ = pending[key];
|
|
21
|
+
if (obs$) {
|
|
23
22
|
// console.log('hooking into pending', { op });
|
|
24
|
-
return observable((observer) =>
|
|
23
|
+
return observable((observer) => obs$.subscribe(observer));
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
const shared$ = observable((observer) => {
|
|
28
27
|
function reset() {
|
|
29
28
|
delete pending[key];
|
|
30
29
|
}
|
|
31
|
-
const
|
|
30
|
+
const subscription = next(op).subscribe({
|
|
32
31
|
next(v) {
|
|
33
32
|
observer.next(v);
|
|
34
33
|
},
|
|
@@ -43,7 +42,7 @@ export function dedupeLink<
|
|
|
43
42
|
});
|
|
44
43
|
return () => {
|
|
45
44
|
reset();
|
|
46
|
-
|
|
45
|
+
subscription.unsubscribe();
|
|
47
46
|
};
|
|
48
47
|
}).pipe(share());
|
|
49
48
|
pending[key] = shared$;
|
|
@@ -1,53 +1,80 @@
|
|
|
1
1
|
import { AnyRouter, ProcedureType } from '@trpc/server';
|
|
2
|
+
import { observable } from '@trpc/server/observable';
|
|
3
|
+
import { TRPCClientError } from '../TRPCClientError';
|
|
2
4
|
import { dataLoader } from '../internals/dataLoader';
|
|
3
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
HTTPLinkOptions,
|
|
7
|
+
HTTPResult,
|
|
8
|
+
getUrl,
|
|
9
|
+
httpRequest,
|
|
10
|
+
} from './internals/httpUtils';
|
|
11
|
+
import { transformResult } from './internals/transformResult';
|
|
4
12
|
import { TRPCLink } from './types';
|
|
5
|
-
import { HTTPLinkOptions, httpRequest, ResponseShape } from './httpUtils';
|
|
6
13
|
|
|
7
14
|
export interface HttpBatchLinkOptions extends HTTPLinkOptions {
|
|
8
|
-
|
|
15
|
+
maxURLLength?: number;
|
|
9
16
|
}
|
|
17
|
+
|
|
10
18
|
export function httpBatchLink<TRouter extends AnyRouter>(
|
|
11
19
|
opts: HttpBatchLinkOptions,
|
|
12
20
|
): TRPCLink<TRouter> {
|
|
13
|
-
const { url } = opts;
|
|
14
21
|
// initialized config
|
|
15
22
|
return (runtime) => {
|
|
16
|
-
type
|
|
17
|
-
|
|
18
|
-
const fetcher = (type: ProcedureType) => (keyInputPairs: Key[]) => {
|
|
19
|
-
const path = keyInputPairs.map((op) => op.path).join(',');
|
|
20
|
-
const inputs = keyInputPairs.map((op) => op.input);
|
|
21
|
-
|
|
22
|
-
const { promise, cancel } = httpRequest({
|
|
23
|
-
url,
|
|
24
|
-
inputs,
|
|
25
|
-
path,
|
|
26
|
-
runtime,
|
|
27
|
-
type,
|
|
28
|
-
});
|
|
23
|
+
type BatchOperation = { id: number; path: string; input: unknown };
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
promise: promise.then((res) => {
|
|
32
|
-
const resJSON = Array.isArray(res.json)
|
|
33
|
-
? res.json
|
|
34
|
-
: keyInputPairs.map(() => res.json);
|
|
25
|
+
const maxURLLength = opts.maxURLLength || Infinity;
|
|
35
26
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
27
|
+
const batchLoader = (type: ProcedureType) => {
|
|
28
|
+
const validate = (batchOps: BatchOperation[]) => {
|
|
29
|
+
if (maxURLLength === Infinity) {
|
|
30
|
+
// escape hatch for quick calcs
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
const path = batchOps.map((op) => op.path).join(',');
|
|
34
|
+
const inputs = batchOps.map((op) => op.input);
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
cancel,
|
|
36
|
+
const url = getUrl({ url: opts.url, runtime, type, path, inputs });
|
|
37
|
+
return url.length <= maxURLLength;
|
|
44
38
|
};
|
|
39
|
+
|
|
40
|
+
const fetch = (batchOps: BatchOperation[]) => {
|
|
41
|
+
const path = batchOps.map((op) => op.path).join(',');
|
|
42
|
+
const inputs = batchOps.map((op) => op.input);
|
|
43
|
+
|
|
44
|
+
const { promise, cancel } = httpRequest({
|
|
45
|
+
url: opts.url,
|
|
46
|
+
runtime,
|
|
47
|
+
type,
|
|
48
|
+
path,
|
|
49
|
+
inputs,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
promise: promise.then((res) => {
|
|
54
|
+
const resJSON = Array.isArray(res.json)
|
|
55
|
+
? res.json
|
|
56
|
+
: batchOps.map(() => res.json);
|
|
57
|
+
|
|
58
|
+
const result = resJSON.map((item) => ({
|
|
59
|
+
meta: res.meta,
|
|
60
|
+
json: item,
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
return result;
|
|
64
|
+
}),
|
|
65
|
+
cancel,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return { validate, fetch };
|
|
45
70
|
};
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
71
|
+
|
|
72
|
+
const query = dataLoader<BatchOperation, HTTPResult>(batchLoader('query'));
|
|
73
|
+
const mutation = dataLoader<BatchOperation, HTTPResult>(
|
|
74
|
+
batchLoader('mutation'),
|
|
75
|
+
);
|
|
76
|
+
const subscription = dataLoader<BatchOperation, HTTPResult>(
|
|
77
|
+
batchLoader('subscription'),
|
|
51
78
|
);
|
|
52
79
|
|
|
53
80
|
const loaders = { query, subscription, mutation };
|
|
@@ -58,13 +85,23 @@ export function httpBatchLink<TRouter extends AnyRouter>(
|
|
|
58
85
|
|
|
59
86
|
promise
|
|
60
87
|
.then((res) => {
|
|
88
|
+
const transformed = transformResult(res.json, runtime);
|
|
89
|
+
|
|
90
|
+
if (!transformed.ok) {
|
|
91
|
+
observer.error(
|
|
92
|
+
TRPCClientError.from(transformed.error, {
|
|
93
|
+
meta: res.meta,
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
61
98
|
observer.next({
|
|
62
99
|
context: res.meta,
|
|
63
|
-
|
|
100
|
+
result: transformed.result,
|
|
64
101
|
});
|
|
65
102
|
observer.complete();
|
|
66
103
|
})
|
|
67
|
-
.catch((err) => observer.error(err
|
|
104
|
+
.catch((err) => observer.error(TRPCClientError.from(err)));
|
|
68
105
|
|
|
69
106
|
return () => {
|
|
70
107
|
cancel();
|
package/src/links/httpLink.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { AnyRouter } from '@trpc/server';
|
|
2
|
-
import { observable } from '
|
|
2
|
+
import { observable } from '@trpc/server/observable';
|
|
3
|
+
import { TRPCClientError } from '../TRPCClientError';
|
|
4
|
+
import { HTTPLinkOptions, httpRequest } from './internals/httpUtils';
|
|
5
|
+
import { transformResult } from './internals/transformResult';
|
|
3
6
|
import { TRPCLink } from './types';
|
|
4
|
-
import { HTTPLinkOptions, httpRequest } from './httpUtils';
|
|
5
7
|
|
|
6
8
|
export function httpLink<TRouter extends AnyRouter>(
|
|
7
9
|
opts: HTTPLinkOptions,
|
|
@@ -12,21 +14,31 @@ export function httpLink<TRouter extends AnyRouter>(
|
|
|
12
14
|
observable((observer) => {
|
|
13
15
|
const { path, input, type } = op;
|
|
14
16
|
const { promise, cancel } = httpRequest({
|
|
17
|
+
url,
|
|
15
18
|
runtime,
|
|
19
|
+
type,
|
|
16
20
|
path,
|
|
17
21
|
input,
|
|
18
|
-
type,
|
|
19
|
-
url,
|
|
20
22
|
});
|
|
21
23
|
promise
|
|
22
24
|
.then((res) => {
|
|
25
|
+
const transformed = transformResult(res.json, runtime);
|
|
26
|
+
|
|
27
|
+
if (!transformed.ok) {
|
|
28
|
+
observer.error(
|
|
29
|
+
TRPCClientError.from(transformed.error, {
|
|
30
|
+
meta: res.meta,
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
23
35
|
observer.next({
|
|
24
36
|
context: res.meta,
|
|
25
|
-
|
|
37
|
+
result: transformed.result,
|
|
26
38
|
});
|
|
27
39
|
observer.complete();
|
|
28
40
|
})
|
|
29
|
-
.catch(observer.error);
|
|
41
|
+
.catch((cause) => observer.error(TRPCClientError.from(cause)));
|
|
30
42
|
|
|
31
43
|
return () => {
|
|
32
44
|
cancel();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { observable } from '@trpc/server/observable';
|
|
1
2
|
import { AnyRouter } from '@trpc/server/src';
|
|
2
3
|
import { createChain } from './createChain';
|
|
3
|
-
import { observable } from '../../rx/observable';
|
|
4
4
|
|
|
5
5
|
describe('chain', () => {
|
|
6
6
|
test('trivial', () => {
|
|
@@ -8,9 +8,9 @@ describe('chain', () => {
|
|
|
8
8
|
links: [
|
|
9
9
|
({ next, op }) => {
|
|
10
10
|
return observable((observer) => {
|
|
11
|
-
const
|
|
11
|
+
const subscription = next(op).subscribe(observer);
|
|
12
12
|
return () => {
|
|
13
|
-
|
|
13
|
+
subscription.unsubscribe();
|
|
14
14
|
};
|
|
15
15
|
});
|
|
16
16
|
},
|
|
@@ -18,13 +18,10 @@ describe('chain', () => {
|
|
|
18
18
|
return observable((observer) => {
|
|
19
19
|
observer.next({
|
|
20
20
|
context: {},
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
data: {
|
|
26
|
-
input: op.input,
|
|
27
|
-
},
|
|
21
|
+
result: {
|
|
22
|
+
type: 'data',
|
|
23
|
+
data: {
|
|
24
|
+
input: op.input,
|
|
28
25
|
},
|
|
29
26
|
},
|
|
30
27
|
});
|
|
@@ -54,30 +51,24 @@ describe('chain', () => {
|
|
|
54
51
|
return observable((observer) => {
|
|
55
52
|
observer.next({
|
|
56
53
|
context: {},
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
type: 'data',
|
|
61
|
-
data: 'from cache',
|
|
62
|
-
},
|
|
54
|
+
result: {
|
|
55
|
+
type: 'data',
|
|
56
|
+
data: 'from cache',
|
|
63
57
|
},
|
|
64
58
|
});
|
|
65
|
-
const
|
|
59
|
+
const subscription = next(op).subscribe(observer);
|
|
66
60
|
return () => {
|
|
67
|
-
|
|
61
|
+
subscription.unsubscribe();
|
|
68
62
|
};
|
|
69
63
|
});
|
|
70
64
|
},
|
|
71
65
|
({ op }) => {
|
|
72
66
|
return observable((observer) => {
|
|
73
67
|
observer.next({
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
data: {
|
|
79
|
-
input: op.input,
|
|
80
|
-
},
|
|
68
|
+
result: {
|
|
69
|
+
type: 'data',
|
|
70
|
+
data: {
|
|
71
|
+
input: op.input,
|
|
81
72
|
},
|
|
82
73
|
},
|
|
83
74
|
});
|
|
@@ -103,12 +94,9 @@ describe('chain', () => {
|
|
|
103
94
|
Array [
|
|
104
95
|
Object {
|
|
105
96
|
"context": Object {},
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"data": "from cache",
|
|
110
|
-
"type": "data",
|
|
111
|
-
},
|
|
97
|
+
"result": Object {
|
|
98
|
+
"data": "from cache",
|
|
99
|
+
"type": "data",
|
|
112
100
|
},
|
|
113
101
|
},
|
|
114
102
|
]
|
|
@@ -116,14 +104,11 @@ describe('chain', () => {
|
|
|
116
104
|
expect(next.mock.calls[1]).toMatchInlineSnapshot(`
|
|
117
105
|
Array [
|
|
118
106
|
Object {
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
|
|
122
|
-
"data": Object {
|
|
123
|
-
"input": "world",
|
|
124
|
-
},
|
|
125
|
-
"type": "data",
|
|
107
|
+
"result": Object {
|
|
108
|
+
"data": Object {
|
|
109
|
+
"input": "world",
|
|
126
110
|
},
|
|
111
|
+
"type": "data",
|
|
127
112
|
},
|
|
128
113
|
},
|
|
129
114
|
]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AnyRouter } from '@trpc/server';
|
|
2
|
-
import { observable } from '
|
|
2
|
+
import { observable } from '@trpc/server/observable';
|
|
3
3
|
import { Operation, OperationLink, OperationResultObservable } from '../types';
|
|
4
4
|
|
|
5
5
|
/** @internal */
|
|
@@ -14,7 +14,12 @@ export function createChain<
|
|
|
14
14
|
return observable((observer) => {
|
|
15
15
|
function execute(index = 0, op = opts.op) {
|
|
16
16
|
const next = opts.links[index];
|
|
17
|
-
|
|
17
|
+
if (!next) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'No more links to execute - did you forget to add an ending link?',
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
const subscription = next({
|
|
18
23
|
op,
|
|
19
24
|
next(nextOp) {
|
|
20
25
|
const nextObserver = execute(index + 1, nextOp);
|
|
@@ -22,7 +27,7 @@ export function createChain<
|
|
|
22
27
|
return nextObserver;
|
|
23
28
|
},
|
|
24
29
|
});
|
|
25
|
-
return
|
|
30
|
+
return subscription;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
const obs$ = execute();
|