@trpc/client 11.0.0-rc.370 → 11.0.0-rc.374
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/bundle-analysis.json +60 -106
- package/dist/index.js +0 -1
- package/dist/index.mjs +1 -1
- package/dist/internals/dataLoader.d.ts +3 -4
- package/dist/internals/dataLoader.d.ts.map +1 -1
- package/dist/internals/dataLoader.js +14 -13
- package/dist/internals/dataLoader.mjs +14 -13
- package/dist/links/httpBatchLink.d.ts +6 -1
- package/dist/links/httpBatchLink.d.ts.map +1 -1
- package/dist/links/httpBatchLink.js +95 -30
- package/dist/links/httpBatchLink.mjs +96 -31
- package/dist/links/httpBatchStreamLink.d.ts +9 -6
- package/dist/links/httpBatchStreamLink.d.ts.map +1 -1
- package/dist/links/httpBatchStreamLink.js +132 -34
- package/dist/links/httpBatchStreamLink.mjs +132 -34
- package/dist/links/httpLink.d.ts +4 -7
- package/dist/links/httpLink.d.ts.map +1 -1
- package/dist/links/httpLink.js +72 -46
- package/dist/links/httpLink.mjs +74 -47
- package/dist/links/internals/httpUtils.d.ts +2 -4
- package/dist/links/internals/httpUtils.d.ts.map +1 -1
- package/dist/links/internals/httpUtils.js +4 -32
- package/dist/links/internals/httpUtils.mjs +4 -32
- package/package.json +4 -4
- package/src/internals/dataLoader.ts +21 -19
- package/src/links/httpBatchLink.ts +132 -46
- package/src/links/httpBatchStreamLink.ts +173 -48
- package/src/links/httpLink.ts +100 -60
- package/src/links/internals/httpUtils.ts +5 -41
- package/dist/links/internals/createHTTPBatchLink.d.ts +0 -20
- package/dist/links/internals/createHTTPBatchLink.d.ts.map +0 -1
- package/dist/links/internals/createHTTPBatchLink.js +0 -85
- package/dist/links/internals/createHTTPBatchLink.mjs +0 -83
- package/dist/links/internals/getTextDecoder.d.ts +0 -3
- package/dist/links/internals/getTextDecoder.d.ts.map +0 -1
- package/dist/links/internals/getTextDecoder.js +0 -16
- package/dist/links/internals/getTextDecoder.mjs +0 -14
- package/dist/links/internals/parseJSONStream.d.ts +0 -39
- package/dist/links/internals/parseJSONStream.d.ts.map +0 -1
- package/dist/links/internals/parseJSONStream.js +0 -118
- package/dist/links/internals/parseJSONStream.mjs +0 -115
- package/dist/links/internals/streamingUtils.d.ts +0 -7
- package/dist/links/internals/streamingUtils.d.ts.map +0 -1
- package/src/links/internals/createHTTPBatchLink.ts +0 -133
- package/src/links/internals/getTextDecoder.ts +0 -19
- package/src/links/internals/parseJSONStream.ts +0 -166
- package/src/links/internals/streamingUtils.ts +0 -6
|
@@ -1,51 +1,137 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AnyRouter, ProcedureType } from '@trpc/server';
|
|
2
|
+
import { observable } from '@trpc/server/observable';
|
|
3
|
+
import { transformResult } from '@trpc/server/unstable-core-do-not-import';
|
|
4
|
+
import type { BatchLoader } from '../internals/dataLoader';
|
|
5
|
+
import { dataLoader } from '../internals/dataLoader';
|
|
2
6
|
import type { NonEmptyArray } from '../internals/types';
|
|
7
|
+
import { TRPCClientError } from '../TRPCClientError';
|
|
3
8
|
import type { HTTPBatchLinkOptions } from './HTTPBatchLinkOptions';
|
|
4
|
-
import type {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
9
|
+
import type { HTTPResult } from './internals/httpUtils';
|
|
10
|
+
import {
|
|
11
|
+
getUrl,
|
|
12
|
+
jsonHttpRequester,
|
|
13
|
+
resolveHTTPLinkOptions,
|
|
14
|
+
} from './internals/httpUtils';
|
|
15
|
+
import type { Operation, TRPCLink } from './types';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @see https://trpc.io/docs/client/links/httpBatchLink
|
|
19
|
+
*/
|
|
20
|
+
export function httpBatchLink<TRouter extends AnyRouter>(
|
|
21
|
+
opts: HTTPBatchLinkOptions<TRouter['_def']['_config']['$types']>,
|
|
22
|
+
): TRPCLink<TRouter> {
|
|
23
|
+
const resolvedOpts = resolveHTTPLinkOptions(opts);
|
|
24
|
+
const maxURLLength = opts.maxURLLength ?? Infinity;
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
const batchLoader = (
|
|
28
|
+
type: ProcedureType,
|
|
29
|
+
): BatchLoader<Operation, HTTPResult> => {
|
|
30
|
+
return {
|
|
31
|
+
validate(batchOps) {
|
|
32
|
+
if (maxURLLength === Infinity) {
|
|
33
|
+
// escape hatch for quick calcs
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
const path = batchOps.map((op) => op.path).join(',');
|
|
37
|
+
const inputs = batchOps.map((op) => op.input);
|
|
38
|
+
|
|
39
|
+
const url = getUrl({
|
|
40
|
+
...resolvedOpts,
|
|
41
|
+
type,
|
|
42
|
+
path,
|
|
43
|
+
inputs,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return url.length <= maxURLLength;
|
|
47
|
+
},
|
|
48
|
+
fetch(batchOps) {
|
|
49
|
+
const path = batchOps.map((op) => op.path).join(',');
|
|
50
|
+
const inputs = batchOps.map((op) => op.input);
|
|
51
|
+
|
|
52
|
+
const requester = jsonHttpRequester({
|
|
53
|
+
...resolvedOpts,
|
|
54
|
+
path,
|
|
55
|
+
inputs,
|
|
56
|
+
type,
|
|
57
|
+
headers() {
|
|
58
|
+
if (!opts.headers) {
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
if (typeof opts.headers === 'function') {
|
|
62
|
+
return opts.headers({
|
|
63
|
+
opList: batchOps as NonEmptyArray<Operation>,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return opts.headers;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
cancel: requester.cancel,
|
|
71
|
+
promise: requester.promise.then((res) => {
|
|
72
|
+
const resJSON = Array.isArray(res.json)
|
|
73
|
+
? res.json
|
|
74
|
+
: batchOps.map(() => res.json);
|
|
75
|
+
|
|
76
|
+
const result = resJSON.map((item) => ({
|
|
77
|
+
meta: res.meta,
|
|
78
|
+
json: item,
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
}),
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const query = dataLoader(batchLoader('query'));
|
|
89
|
+
const mutation = dataLoader<Operation, HTTPResult>(batchLoader('mutation'));
|
|
90
|
+
const subscription = dataLoader<Operation, HTTPResult>(
|
|
91
|
+
batchLoader('subscription'),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const loaders = { query, subscription, mutation };
|
|
95
|
+
return ({ op }) => {
|
|
96
|
+
return observable((observer) => {
|
|
97
|
+
const loader = loaders[op.type];
|
|
98
|
+
const { promise, cancel } = loader.load(op);
|
|
99
|
+
|
|
100
|
+
let _res = undefined as HTTPResult | undefined;
|
|
101
|
+
promise
|
|
102
|
+
.then((res) => {
|
|
103
|
+
_res = res;
|
|
104
|
+
const transformed = transformResult(
|
|
105
|
+
res.json,
|
|
106
|
+
resolvedOpts.transformer.output,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (!transformed.ok) {
|
|
110
|
+
observer.error(
|
|
111
|
+
TRPCClientError.from(transformed.error, {
|
|
112
|
+
meta: res.meta,
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
observer.next({
|
|
118
|
+
context: res.meta,
|
|
119
|
+
result: transformed.result,
|
|
120
|
+
});
|
|
121
|
+
observer.complete();
|
|
122
|
+
})
|
|
123
|
+
.catch((err) => {
|
|
124
|
+
observer.error(
|
|
125
|
+
TRPCClientError.from(err, {
|
|
126
|
+
meta: _res?.meta,
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
27
129
|
});
|
|
28
|
-
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
promise: promise.then((res) => {
|
|
35
|
-
const resJSON = Array.isArray(res.json)
|
|
36
|
-
? res.json
|
|
37
|
-
: batchOps.map(() => res.json);
|
|
38
|
-
|
|
39
|
-
const result = resJSON.map((item) => ({
|
|
40
|
-
meta: res.meta,
|
|
41
|
-
json: item,
|
|
42
|
-
}));
|
|
43
|
-
|
|
44
|
-
return result;
|
|
45
|
-
}),
|
|
46
|
-
cancel,
|
|
130
|
+
|
|
131
|
+
return () => {
|
|
132
|
+
cancel();
|
|
133
|
+
};
|
|
134
|
+
});
|
|
47
135
|
};
|
|
48
136
|
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const httpBatchLink = createHTTPBatchLink(batchRequester);
|
|
137
|
+
}
|
|
@@ -1,65 +1,190 @@
|
|
|
1
|
+
import type { AnyRouter, ProcedureType } from '@trpc/server';
|
|
2
|
+
import { observable } from '@trpc/server/observable';
|
|
3
|
+
import type { TRPCResponse } from '@trpc/server/rpc';
|
|
1
4
|
import type { AnyRootTypes } from '@trpc/server/unstable-core-do-not-import';
|
|
5
|
+
import { jsonlStreamConsumer } from '@trpc/server/unstable-core-do-not-import';
|
|
6
|
+
import type { BatchLoader } from '../internals/dataLoader';
|
|
7
|
+
import { dataLoader } from '../internals/dataLoader';
|
|
2
8
|
import type { NonEmptyArray } from '../internals/types';
|
|
9
|
+
import { TRPCClientError } from '../TRPCClientError';
|
|
3
10
|
import type { HTTPBatchLinkOptions } from './HTTPBatchLinkOptions';
|
|
4
|
-
import type {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
import type { HTTPResult } from './internals/httpUtils';
|
|
12
|
+
import {
|
|
13
|
+
fetchHTTPResponse,
|
|
14
|
+
getBody,
|
|
15
|
+
getUrl,
|
|
16
|
+
resolveHTTPLinkOptions,
|
|
17
|
+
} from './internals/httpUtils';
|
|
18
|
+
import type { Operation, TRPCLink } from './types';
|
|
10
19
|
|
|
11
20
|
export type HTTPBatchStreamLinkOptions<TRoot extends AnyRootTypes> =
|
|
12
21
|
HTTPBatchLinkOptions<TRoot> & {
|
|
13
22
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* runtime doesn't provide it.
|
|
23
|
+
* Maximum number of calls in a single batch request
|
|
24
|
+
* @default Infinity
|
|
17
25
|
*/
|
|
18
|
-
|
|
26
|
+
maxItems?: number;
|
|
19
27
|
};
|
|
20
28
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
/**
|
|
30
|
+
* @see https://trpc.io/docs/client/links/httpBatchStreamLink
|
|
31
|
+
*/
|
|
32
|
+
export function unstable_httpBatchStreamLink<TRouter extends AnyRouter>(
|
|
33
|
+
opts: HTTPBatchStreamLinkOptions<TRouter['_def']['_config']['$types']>,
|
|
34
|
+
): TRPCLink<TRouter> {
|
|
35
|
+
const resolvedOpts = resolveHTTPLinkOptions(opts);
|
|
36
|
+
const maxURLLength = opts.maxURLLength ?? Infinity;
|
|
37
|
+
const maxItems = opts.maxItems ?? Infinity;
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return
|
|
39
|
+
return () => {
|
|
40
|
+
const batchLoader = (
|
|
41
|
+
type: ProcedureType,
|
|
42
|
+
): BatchLoader<Operation, HTTPResult> => {
|
|
43
|
+
return {
|
|
44
|
+
validate(batchOps) {
|
|
45
|
+
if (maxURLLength === Infinity && maxItems === Infinity) {
|
|
46
|
+
// escape hatch for quick calcs
|
|
47
|
+
return true;
|
|
38
48
|
}
|
|
39
|
-
if (
|
|
40
|
-
return
|
|
41
|
-
opList: batchOps as NonEmptyArray<Operation>,
|
|
42
|
-
});
|
|
49
|
+
if (batchOps.length > maxItems) {
|
|
50
|
+
return false;
|
|
43
51
|
}
|
|
44
|
-
|
|
52
|
+
const path = batchOps.map((op) => op.path).join(',');
|
|
53
|
+
const inputs = batchOps.map((op) => op.input);
|
|
54
|
+
|
|
55
|
+
const url = getUrl({
|
|
56
|
+
...resolvedOpts,
|
|
57
|
+
type,
|
|
58
|
+
path,
|
|
59
|
+
inputs,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return url.length <= maxURLLength;
|
|
63
|
+
},
|
|
64
|
+
fetch(batchOps) {
|
|
65
|
+
const path = batchOps.map((op) => op.path).join(',');
|
|
66
|
+
const inputs = batchOps.map((op) => op.input);
|
|
67
|
+
|
|
68
|
+
const ac = resolvedOpts.AbortController
|
|
69
|
+
? new resolvedOpts.AbortController()
|
|
70
|
+
: null;
|
|
71
|
+
const responsePromise = fetchHTTPResponse(
|
|
72
|
+
{
|
|
73
|
+
...resolvedOpts,
|
|
74
|
+
type,
|
|
75
|
+
contentTypeHeader: 'application/json',
|
|
76
|
+
trpcAcceptHeader: 'application/jsonl',
|
|
77
|
+
getUrl,
|
|
78
|
+
getBody,
|
|
79
|
+
inputs,
|
|
80
|
+
path,
|
|
81
|
+
headers() {
|
|
82
|
+
if (!opts.headers) {
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
if (typeof opts.headers === 'function') {
|
|
86
|
+
return opts.headers({
|
|
87
|
+
opList: batchOps as NonEmptyArray<Operation>,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return opts.headers;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
ac,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
promise: responsePromise.then(async (res) => {
|
|
98
|
+
if (!res.body) {
|
|
99
|
+
throw new Error('Received response without body');
|
|
100
|
+
}
|
|
101
|
+
const [head] = await jsonlStreamConsumer<
|
|
102
|
+
Record<string, Promise<any>>
|
|
103
|
+
>({
|
|
104
|
+
from: res.body,
|
|
105
|
+
deserialize: resolvedOpts.transformer.output.deserialize,
|
|
106
|
+
// onError: console.error,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const promises = Object.keys(batchOps).map(
|
|
110
|
+
async (key): Promise<HTTPResult> => {
|
|
111
|
+
let json: TRPCResponse = await head[key];
|
|
112
|
+
|
|
113
|
+
if ('result' in json) {
|
|
114
|
+
/**
|
|
115
|
+
* Not very pretty, but we need to unwrap nested data as promises
|
|
116
|
+
* Our stream producer will only resolve top-level async values or async values that are directly nested in another async value
|
|
117
|
+
*/
|
|
118
|
+
const result = await Promise.resolve(json.result);
|
|
119
|
+
json = {
|
|
120
|
+
result: {
|
|
121
|
+
data: await Promise.resolve(result.data),
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
json,
|
|
128
|
+
meta: {
|
|
129
|
+
response: res,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
return promises;
|
|
135
|
+
}),
|
|
136
|
+
cancel() {
|
|
137
|
+
ac?.abort();
|
|
138
|
+
},
|
|
139
|
+
};
|
|
45
140
|
},
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
);
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const query = dataLoader(batchLoader('query'));
|
|
145
|
+
const mutation = dataLoader(batchLoader('mutation'));
|
|
146
|
+
const subscription = dataLoader(batchLoader('subscription'));
|
|
147
|
+
|
|
148
|
+
const loaders = { query, subscription, mutation };
|
|
149
|
+
return ({ op }) => {
|
|
150
|
+
return observable((observer) => {
|
|
151
|
+
const loader = loaders[op.type];
|
|
152
|
+
const { promise, cancel } = loader.load(op);
|
|
51
153
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
154
|
+
let _res = undefined as HTTPResult | undefined;
|
|
155
|
+
promise
|
|
156
|
+
.then((res) => {
|
|
157
|
+
_res = res;
|
|
158
|
+
if ('error' in res.json) {
|
|
159
|
+
observer.error(
|
|
160
|
+
TRPCClientError.from(res.json, {
|
|
161
|
+
meta: res.meta,
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
return;
|
|
165
|
+
} else if ('result' in res.json) {
|
|
166
|
+
observer.next({
|
|
167
|
+
context: res.meta,
|
|
168
|
+
result: res.json.result,
|
|
169
|
+
});
|
|
170
|
+
observer.complete();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
observer.complete();
|
|
175
|
+
})
|
|
176
|
+
.catch((err) => {
|
|
177
|
+
observer.error(
|
|
178
|
+
TRPCClientError.from(err, {
|
|
179
|
+
meta: _res?.meta,
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return () => {
|
|
185
|
+
cancel();
|
|
186
|
+
};
|
|
187
|
+
});
|
|
60
188
|
};
|
|
61
189
|
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export const unstable_httpBatchStreamLink =
|
|
65
|
-
createHTTPBatchLink(streamRequester);
|
|
190
|
+
}
|
package/src/links/httpLink.ts
CHANGED
|
@@ -11,10 +11,19 @@ import type {
|
|
|
11
11
|
Requester,
|
|
12
12
|
} from './internals/httpUtils';
|
|
13
13
|
import {
|
|
14
|
+
getInput,
|
|
15
|
+
getUrl,
|
|
16
|
+
httpRequest,
|
|
17
|
+
jsonHttpRequester,
|
|
14
18
|
resolveHTTPLinkOptions,
|
|
15
|
-
universalRequester,
|
|
16
19
|
} from './internals/httpUtils';
|
|
17
|
-
import
|
|
20
|
+
import {
|
|
21
|
+
isFormData,
|
|
22
|
+
isOctetType,
|
|
23
|
+
type HTTPHeaders,
|
|
24
|
+
type Operation,
|
|
25
|
+
type TRPCLink,
|
|
26
|
+
} from './types';
|
|
18
27
|
|
|
19
28
|
export type HTTPLinkOptions<TRoot extends AnyRootTypes> =
|
|
20
29
|
HTTPLinkBaseOptions<TRoot> & {
|
|
@@ -27,68 +36,99 @@ export type HTTPLinkOptions<TRoot extends AnyRootTypes> =
|
|
|
27
36
|
| ((opts: { op: Operation }) => HTTPHeaders | Promise<HTTPHeaders>);
|
|
28
37
|
};
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
opts: HTTPLinkOptions<TRouter['_def']['_config']['$types']>,
|
|
33
|
-
): TRPCLink<TRouter> => {
|
|
34
|
-
const resolvedOpts = resolveHTTPLinkOptions(opts);
|
|
39
|
+
const universalRequester: Requester = (opts) => {
|
|
40
|
+
const input = getInput(opts);
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
42
|
+
if (isFormData(input)) {
|
|
43
|
+
if (opts.type !== 'mutation' && opts.methodOverride !== 'POST') {
|
|
44
|
+
throw new Error('FormData is only supported for mutations');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return httpRequest({
|
|
48
|
+
...opts,
|
|
49
|
+
// The browser will set this automatically and include the boundary= in it
|
|
50
|
+
contentTypeHeader: undefined,
|
|
51
|
+
getUrl,
|
|
52
|
+
getBody: () => input,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (isOctetType(input)) {
|
|
57
|
+
if (opts.type !== 'mutation' && opts.methodOverride !== 'POST') {
|
|
58
|
+
throw new Error('Octet type input is only supported for mutations');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return httpRequest({
|
|
62
|
+
...opts,
|
|
63
|
+
contentTypeHeader: 'application/octet-stream',
|
|
64
|
+
getUrl,
|
|
65
|
+
getBody: () => input,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return jsonHttpRequester(opts);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @link https://trpc.io/docs/client/links/httpLink
|
|
74
|
+
*/
|
|
75
|
+
export function httpLink<TRouter extends AnyRouter = AnyRouter>(
|
|
76
|
+
opts: HTTPLinkOptions<TRouter['_def']['_config']['$types']>,
|
|
77
|
+
): TRPCLink<TRouter> {
|
|
78
|
+
const resolvedOpts = resolveHTTPLinkOptions(opts);
|
|
79
|
+
return () => {
|
|
80
|
+
return ({ op }) => {
|
|
81
|
+
return observable((observer) => {
|
|
82
|
+
const { path, input, type } = op;
|
|
65
83
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
const request = universalRequester({
|
|
85
|
+
...resolvedOpts,
|
|
86
|
+
type,
|
|
87
|
+
path,
|
|
88
|
+
input,
|
|
89
|
+
headers() {
|
|
90
|
+
if (!opts.headers) {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
if (typeof opts.headers === 'function') {
|
|
94
|
+
return opts.headers({
|
|
95
|
+
op,
|
|
77
96
|
});
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
97
|
+
}
|
|
98
|
+
return opts.headers;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
let meta: HTTPResult['meta'] | undefined = undefined;
|
|
102
|
+
request.promise
|
|
103
|
+
.then((res) => {
|
|
104
|
+
meta = res.meta;
|
|
105
|
+
const transformed = transformResult(
|
|
106
|
+
res.json,
|
|
107
|
+
resolvedOpts.transformer.output,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (!transformed.ok) {
|
|
111
|
+
observer.error(
|
|
112
|
+
TRPCClientError.from(transformed.error, {
|
|
113
|
+
meta,
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
observer.next({
|
|
119
|
+
context: res.meta,
|
|
120
|
+
result: transformed.result,
|
|
82
121
|
});
|
|
122
|
+
observer.complete();
|
|
123
|
+
})
|
|
124
|
+
.catch((cause) => {
|
|
125
|
+
observer.error(TRPCClientError.from(cause, { meta }));
|
|
126
|
+
});
|
|
83
127
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
128
|
+
return () => {
|
|
129
|
+
request.cancel();
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
};
|
|
88
133
|
};
|
|
89
134
|
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* @link https://trpc.io/docs/v11/client/links/httpLink
|
|
93
|
-
*/
|
|
94
|
-
export const httpLink = httpLinkFactory({ requester: universalRequester });
|
|
@@ -16,9 +16,7 @@ import type {
|
|
|
16
16
|
import { TRPCClientError } from '../../TRPCClientError';
|
|
17
17
|
import type { TransformerOptions } from '../../unstable-internals';
|
|
18
18
|
import { getTransformer } from '../../unstable-internals';
|
|
19
|
-
import type { TextDecoderEsque } from '../internals/streamingUtils';
|
|
20
19
|
import type { HTTPHeaders, PromiseAndCancel } from '../types';
|
|
21
|
-
import { isFormData, isOctetType } from './contentTypes';
|
|
22
20
|
|
|
23
21
|
/**
|
|
24
22
|
* @internal
|
|
@@ -90,7 +88,7 @@ type GetInputOptions = {
|
|
|
90
88
|
transformer: CombinedDataTransformer;
|
|
91
89
|
} & ({ input: unknown } | { inputs: unknown[] });
|
|
92
90
|
|
|
93
|
-
function getInput(opts: GetInputOptions) {
|
|
91
|
+
export function getInput(opts: GetInputOptions) {
|
|
94
92
|
return 'input' in opts
|
|
95
93
|
? opts.transformer.input.serialize(opts.input)
|
|
96
94
|
: arrayToDict(
|
|
@@ -108,7 +106,7 @@ type GetUrl = (opts: HTTPBaseRequestOptions) => string;
|
|
|
108
106
|
type GetBody = (opts: HTTPBaseRequestOptions) => RequestInitEsque['body'];
|
|
109
107
|
|
|
110
108
|
export type ContentOptions = {
|
|
111
|
-
|
|
109
|
+
trpcAcceptHeader?: 'application/jsonl';
|
|
112
110
|
contentTypeHeader?: string;
|
|
113
111
|
getUrl: GetUrl;
|
|
114
112
|
getBody: GetBody;
|
|
@@ -155,43 +153,9 @@ export const jsonHttpRequester: Requester = (opts) => {
|
|
|
155
153
|
});
|
|
156
154
|
};
|
|
157
155
|
|
|
158
|
-
export const universalRequester: Requester = (opts) => {
|
|
159
|
-
const input = getInput(opts);
|
|
160
|
-
|
|
161
|
-
if (isFormData(input)) {
|
|
162
|
-
if (opts.type !== 'mutation' && opts.methodOverride !== 'POST') {
|
|
163
|
-
throw new Error('FormData is only supported for mutations');
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return httpRequest({
|
|
167
|
-
...opts,
|
|
168
|
-
// The browser will set this automatically and include the boundary= in it
|
|
169
|
-
contentTypeHeader: undefined,
|
|
170
|
-
getUrl,
|
|
171
|
-
getBody: () => input,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (isOctetType(input)) {
|
|
176
|
-
if (opts.type !== 'mutation' && opts.methodOverride !== 'POST') {
|
|
177
|
-
throw new Error('Octet type input is only supported for mutations');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return httpRequest({
|
|
181
|
-
...opts,
|
|
182
|
-
contentTypeHeader: 'application/octet-stream',
|
|
183
|
-
getUrl,
|
|
184
|
-
getBody: () => input,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return jsonHttpRequester(opts);
|
|
189
|
-
};
|
|
190
|
-
|
|
191
156
|
export type HTTPRequestOptions = ContentOptions &
|
|
192
157
|
HTTPBaseRequestOptions & {
|
|
193
158
|
headers: () => HTTPHeaders | Promise<HTTPHeaders>;
|
|
194
|
-
TextDecoder?: TextDecoderEsque;
|
|
195
159
|
};
|
|
196
160
|
|
|
197
161
|
export async function fetchHTTPResponse(
|
|
@@ -216,9 +180,9 @@ export async function fetchHTTPResponse(
|
|
|
216
180
|
...(opts.contentTypeHeader
|
|
217
181
|
? { 'content-type': opts.contentTypeHeader }
|
|
218
182
|
: {}),
|
|
219
|
-
...(opts.
|
|
220
|
-
? { 'trpc-
|
|
221
|
-
:
|
|
183
|
+
...(opts.trpcAcceptHeader
|
|
184
|
+
? { 'trpc-accept': opts.trpcAcceptHeader }
|
|
185
|
+
: undefined),
|
|
222
186
|
...resolvedHeaders,
|
|
223
187
|
};
|
|
224
188
|
|