@trpc/client 10.28.1 → 10.29.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.d.ts +1 -1
- package/dist/TRPCClientError.d.ts.map +1 -1
- package/dist/createTRPCClient.d.ts +1 -1
- package/dist/createTRPCClient.d.ts.map +1 -1
- package/dist/createTRPCClientProxy.d.ts +1 -1
- package/dist/createTRPCClientProxy.d.ts.map +1 -1
- package/dist/httpBatchLink-38dcf5f2.js +243 -0
- package/dist/httpBatchLink-e66c0674.mjs +240 -0
- package/dist/httpBatchLink-f35df745.js +239 -0
- package/dist/{httpUtils-00c8b54e.js → httpUtils-145e783b.js} +27 -23
- package/dist/{httpUtils-90a6bccb.js → httpUtils-21cbb35d.js} +27 -20
- package/dist/{httpUtils-8a5c637a.mjs → httpUtils-b2fcc20c.mjs} +26 -21
- package/dist/index.js +149 -4
- package/dist/index.mjs +151 -6
- package/dist/internals/TRPCUntypedClient.d.ts +1 -1
- package/dist/internals/TRPCUntypedClient.d.ts.map +1 -1
- package/dist/internals/dataLoader.d.ts +1 -1
- package/dist/internals/dataLoader.d.ts.map +1 -1
- package/dist/internals/types.d.ts +2 -0
- package/dist/internals/types.d.ts.map +1 -1
- package/dist/links/HTTPBatchLinkOptions.d.ts +20 -0
- package/dist/links/HTTPBatchLinkOptions.d.ts.map +1 -0
- package/dist/links/httpBatchLink.d.ts +1 -15
- package/dist/links/httpBatchLink.d.ts.map +1 -1
- package/dist/links/httpBatchLink.js +5 -217
- package/dist/links/httpBatchLink.mjs +4 -220
- package/dist/links/httpBatchStreamLink.d.ts +2 -0
- package/dist/links/httpBatchStreamLink.d.ts.map +1 -0
- package/dist/links/httpLink.d.ts.map +1 -1
- package/dist/links/httpLink.js +1 -1
- package/dist/links/httpLink.mjs +1 -1
- package/dist/links/index.d.ts +2 -0
- package/dist/links/index.d.ts.map +1 -1
- package/dist/links/internals/createHTTPBatchLink.d.ts +20 -0
- package/dist/links/internals/createHTTPBatchLink.d.ts.map +1 -0
- package/dist/links/internals/httpUtils.d.ts +3 -1
- package/dist/links/internals/httpUtils.d.ts.map +1 -1
- package/dist/links/internals/parseJSONStream.d.ts +36 -0
- package/dist/links/internals/parseJSONStream.d.ts.map +1 -0
- package/dist/links/types.d.ts +1 -1
- package/dist/links/types.d.ts.map +1 -1
- package/dist/links/wsLink.d.ts +1 -1
- package/dist/links/wsLink.d.ts.map +1 -1
- package/dist/shared/transformResult.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/TRPCClientError.ts +1 -1
- package/src/createTRPCClient.ts +1 -1
- package/src/createTRPCClientProxy.ts +1 -1
- package/src/internals/TRPCUntypedClient.ts +2 -2
- package/src/internals/dataLoader.ts +21 -7
- package/src/internals/types.ts +1 -0
- package/src/links/HTTPBatchLinkOptions.ts +21 -0
- package/src/links/httpBatchLink.ts +44 -133
- package/src/links/httpBatchStreamLink.ts +47 -0
- package/src/links/httpFormDataLink.ts +1 -1
- package/src/links/httpLink.ts +2 -2
- package/src/links/index.ts +2 -0
- package/src/links/internals/createHTTPBatchLink.ts +113 -0
- package/src/links/internals/httpUtils.ts +33 -24
- package/src/links/internals/parseJSONStream.ts +165 -0
- package/src/links/retryLink.ts +1 -1
- package/src/links/types.ts +1 -1
- package/src/links/wsLink.ts +3 -3
- package/src/shared/transformResult.ts +1 -1
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { observable } from '@trpc/server/observable';
|
|
2
|
+
import { t as transformResult, T as TRPCClientError } from './transformResult-ae8e970c.js';
|
|
3
|
+
import { r as resolveHTTPLinkOptions, g as getUrl, j as jsonHttpRequester } from './httpUtils-145e783b.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A function that should never be called unless we messed something up.
|
|
7
|
+
*/
|
|
8
|
+
const throwFatalError = () => {
|
|
9
|
+
throw new Error('Something went wrong. Please submit an issue at https://github.com/trpc/trpc/issues/new');
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Dataloader that's very inspired by https://github.com/graphql/dataloader
|
|
13
|
+
* Less configuration, no caching, and allows you to cancel requests
|
|
14
|
+
* When cancelling a single fetch the whole batch will be cancelled only when _all_ items are cancelled
|
|
15
|
+
*/
|
|
16
|
+
function dataLoader(batchLoader) {
|
|
17
|
+
let pendingItems = null;
|
|
18
|
+
let dispatchTimer = null;
|
|
19
|
+
const destroyTimerAndPendingItems = () => {
|
|
20
|
+
clearTimeout(dispatchTimer);
|
|
21
|
+
dispatchTimer = null;
|
|
22
|
+
pendingItems = null;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Iterate through the items and split them into groups based on the `batchLoader`'s validate function
|
|
26
|
+
*/
|
|
27
|
+
function groupItems(items) {
|
|
28
|
+
const groupedItems = [[]];
|
|
29
|
+
let index = 0;
|
|
30
|
+
while (true) {
|
|
31
|
+
const item = items[index];
|
|
32
|
+
if (!item) {
|
|
33
|
+
// we're done
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
const lastGroup = groupedItems[groupedItems.length - 1];
|
|
37
|
+
if (item.aborted) {
|
|
38
|
+
// Item was aborted before it was dispatched
|
|
39
|
+
item.reject?.(new Error('Aborted'));
|
|
40
|
+
index++;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const isValid = batchLoader.validate(lastGroup.concat(item).map((it) => it.key));
|
|
44
|
+
if (isValid) {
|
|
45
|
+
lastGroup.push(item);
|
|
46
|
+
index++;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (lastGroup.length === 0) {
|
|
50
|
+
item.reject?.(new Error('Input is too big for a single dispatch'));
|
|
51
|
+
index++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
// Create new group, next iteration will try to add the item to that
|
|
55
|
+
groupedItems.push([]);
|
|
56
|
+
}
|
|
57
|
+
return groupedItems;
|
|
58
|
+
}
|
|
59
|
+
function dispatch() {
|
|
60
|
+
const groupedItems = groupItems(pendingItems);
|
|
61
|
+
destroyTimerAndPendingItems();
|
|
62
|
+
// Create batches for each group of items
|
|
63
|
+
for (const items of groupedItems) {
|
|
64
|
+
if (!items.length) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const batch = {
|
|
68
|
+
items,
|
|
69
|
+
cancel: throwFatalError,
|
|
70
|
+
};
|
|
71
|
+
for (const item of items) {
|
|
72
|
+
item.batch = batch;
|
|
73
|
+
}
|
|
74
|
+
const unitResolver = (index, value) => {
|
|
75
|
+
const item = batch.items[index];
|
|
76
|
+
item.resolve?.(value);
|
|
77
|
+
item.batch = null;
|
|
78
|
+
item.reject = null;
|
|
79
|
+
item.resolve = null;
|
|
80
|
+
};
|
|
81
|
+
const { promise, cancel } = batchLoader.fetch(batch.items.map((_item) => _item.key), unitResolver);
|
|
82
|
+
batch.cancel = cancel;
|
|
83
|
+
promise
|
|
84
|
+
.then((result) => {
|
|
85
|
+
for (let i = 0; i < result.length; i++) {
|
|
86
|
+
const value = result[i];
|
|
87
|
+
unitResolver(i, value);
|
|
88
|
+
}
|
|
89
|
+
for (let i = 0; i < batch.items.length; i++) {
|
|
90
|
+
const item = batch.items[i];
|
|
91
|
+
item.reject?.(new Error('Missing result'));
|
|
92
|
+
item.batch = null;
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
.catch((cause) => {
|
|
96
|
+
for (const item of batch.items) {
|
|
97
|
+
item.reject?.(cause);
|
|
98
|
+
item.batch = null;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function load(key) {
|
|
104
|
+
const item = {
|
|
105
|
+
aborted: false,
|
|
106
|
+
key,
|
|
107
|
+
batch: null,
|
|
108
|
+
resolve: throwFatalError,
|
|
109
|
+
reject: throwFatalError,
|
|
110
|
+
};
|
|
111
|
+
const promise = new Promise((resolve, reject) => {
|
|
112
|
+
item.reject = reject;
|
|
113
|
+
item.resolve = resolve;
|
|
114
|
+
if (!pendingItems) {
|
|
115
|
+
pendingItems = [];
|
|
116
|
+
}
|
|
117
|
+
pendingItems.push(item);
|
|
118
|
+
});
|
|
119
|
+
if (!dispatchTimer) {
|
|
120
|
+
dispatchTimer = setTimeout(dispatch);
|
|
121
|
+
}
|
|
122
|
+
const cancel = () => {
|
|
123
|
+
item.aborted = true;
|
|
124
|
+
if (item.batch?.items.every((item) => item.aborted)) {
|
|
125
|
+
// All items in the batch have been cancelled
|
|
126
|
+
item.batch.cancel();
|
|
127
|
+
item.batch = null;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
return { promise, cancel };
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
load,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @internal
|
|
139
|
+
*/
|
|
140
|
+
function createHTTPBatchLink(requester) {
|
|
141
|
+
return function httpBatchLink(opts) {
|
|
142
|
+
const resolvedOpts = resolveHTTPLinkOptions(opts);
|
|
143
|
+
const maxURLLength = opts.maxURLLength || Infinity;
|
|
144
|
+
// initialized config
|
|
145
|
+
return (runtime) => {
|
|
146
|
+
const batchLoader = (type) => {
|
|
147
|
+
const validate = (batchOps) => {
|
|
148
|
+
if (maxURLLength === Infinity) {
|
|
149
|
+
// escape hatch for quick calcs
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
const path = batchOps.map((op) => op.path).join(',');
|
|
153
|
+
const inputs = batchOps.map((op) => op.input);
|
|
154
|
+
const url = getUrl({
|
|
155
|
+
...resolvedOpts,
|
|
156
|
+
runtime,
|
|
157
|
+
type,
|
|
158
|
+
path,
|
|
159
|
+
inputs,
|
|
160
|
+
});
|
|
161
|
+
return url.length <= maxURLLength;
|
|
162
|
+
};
|
|
163
|
+
const fetch = requester({
|
|
164
|
+
...resolvedOpts,
|
|
165
|
+
runtime,
|
|
166
|
+
type,
|
|
167
|
+
opts,
|
|
168
|
+
});
|
|
169
|
+
return { validate, fetch };
|
|
170
|
+
};
|
|
171
|
+
const query = dataLoader(batchLoader('query'));
|
|
172
|
+
const mutation = dataLoader(batchLoader('mutation'));
|
|
173
|
+
const subscription = dataLoader(batchLoader('subscription'));
|
|
174
|
+
const loaders = { query, subscription, mutation };
|
|
175
|
+
return ({ op }) => {
|
|
176
|
+
return observable((observer) => {
|
|
177
|
+
const loader = loaders[op.type];
|
|
178
|
+
const { promise, cancel } = loader.load(op);
|
|
179
|
+
promise
|
|
180
|
+
.then((res) => {
|
|
181
|
+
const transformed = transformResult(res.json, runtime);
|
|
182
|
+
if (!transformed.ok) {
|
|
183
|
+
observer.error(TRPCClientError.from(transformed.error, {
|
|
184
|
+
meta: res.meta,
|
|
185
|
+
}));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
observer.next({
|
|
189
|
+
context: res.meta,
|
|
190
|
+
result: transformed.result,
|
|
191
|
+
});
|
|
192
|
+
observer.complete();
|
|
193
|
+
})
|
|
194
|
+
.catch((err) => observer.error(TRPCClientError.from(err)));
|
|
195
|
+
return () => cancel();
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const batchRequester = (requesterOpts) => {
|
|
203
|
+
return (batchOps) => {
|
|
204
|
+
const path = batchOps.map((op) => op.path).join(',');
|
|
205
|
+
const inputs = batchOps.map((op) => op.input);
|
|
206
|
+
const { promise, cancel } = jsonHttpRequester({
|
|
207
|
+
...requesterOpts,
|
|
208
|
+
path,
|
|
209
|
+
inputs,
|
|
210
|
+
headers() {
|
|
211
|
+
if (!requesterOpts.opts.headers) {
|
|
212
|
+
return {};
|
|
213
|
+
}
|
|
214
|
+
if (typeof requesterOpts.opts.headers === 'function') {
|
|
215
|
+
return requesterOpts.opts.headers({
|
|
216
|
+
opList: batchOps,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return requesterOpts.opts.headers;
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
promise: promise.then((res) => {
|
|
224
|
+
const resJSON = Array.isArray(res.json)
|
|
225
|
+
? res.json
|
|
226
|
+
: batchOps.map(() => res.json);
|
|
227
|
+
const result = resJSON.map((item) => ({
|
|
228
|
+
meta: res.meta,
|
|
229
|
+
json: item,
|
|
230
|
+
}));
|
|
231
|
+
return result;
|
|
232
|
+
}),
|
|
233
|
+
cancel,
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
const httpBatchLink = createHTTPBatchLink(batchRequester);
|
|
238
|
+
|
|
239
|
+
export { createHTTPBatchLink as c, httpBatchLink as h };
|
|
@@ -86,31 +86,35 @@ const jsonHttpRequester = (opts) => {
|
|
|
86
86
|
getBody,
|
|
87
87
|
});
|
|
88
88
|
};
|
|
89
|
-
function
|
|
89
|
+
async function fetchHTTPResponse(opts, ac) {
|
|
90
|
+
const url = opts.getUrl(opts);
|
|
91
|
+
const body = opts.getBody(opts);
|
|
90
92
|
const { type } = opts;
|
|
93
|
+
const headers = await opts.headers();
|
|
94
|
+
/* istanbul ignore if -- @preserve */
|
|
95
|
+
if (type === 'subscription') {
|
|
96
|
+
throw new Error('Subscriptions should use wsLink');
|
|
97
|
+
}
|
|
98
|
+
return opts.fetch(url, {
|
|
99
|
+
method: METHOD[type],
|
|
100
|
+
signal: ac?.signal,
|
|
101
|
+
body: body,
|
|
102
|
+
headers: {
|
|
103
|
+
...(opts.contentTypeHeader
|
|
104
|
+
? { 'content-type': opts.contentTypeHeader }
|
|
105
|
+
: {}),
|
|
106
|
+
...(opts.batchModeHeader
|
|
107
|
+
? { 'trpc-batch-mode': opts.batchModeHeader }
|
|
108
|
+
: {}),
|
|
109
|
+
...headers,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function httpRequest(opts) {
|
|
91
114
|
const ac = opts.AbortController ? new opts.AbortController() : null;
|
|
115
|
+
const meta = {};
|
|
92
116
|
const promise = new Promise((resolve, reject) => {
|
|
93
|
-
|
|
94
|
-
const body = opts.getBody(opts);
|
|
95
|
-
const meta = {};
|
|
96
|
-
Promise.resolve(opts.headers())
|
|
97
|
-
.then((headers) => {
|
|
98
|
-
/* istanbul ignore if -- @preserve */
|
|
99
|
-
if (type === 'subscription') {
|
|
100
|
-
throw new Error('Subscriptions should use wsLink');
|
|
101
|
-
}
|
|
102
|
-
return opts.fetch(url, {
|
|
103
|
-
method: METHOD[type],
|
|
104
|
-
signal: ac?.signal,
|
|
105
|
-
body: body,
|
|
106
|
-
headers: {
|
|
107
|
-
...(opts.contentTypeHeader
|
|
108
|
-
? { 'content-type': opts.contentTypeHeader }
|
|
109
|
-
: {}),
|
|
110
|
-
...headers,
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
})
|
|
117
|
+
fetchHTTPResponse(opts, ac)
|
|
114
118
|
.then((_res) => {
|
|
115
119
|
meta.response = _res;
|
|
116
120
|
return _res.json();
|
|
@@ -129,4 +133,4 @@ function httpRequest(opts) {
|
|
|
129
133
|
return { promise, cancel };
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
export {
|
|
136
|
+
export { getBody as a, getFetch as b, fetchHTTPResponse as f, getUrl as g, httpRequest as h, jsonHttpRequester as j, resolveHTTPLinkOptions as r };
|
|
@@ -86,29 +86,34 @@ const jsonHttpRequester = (opts)=>{
|
|
|
86
86
|
getBody
|
|
87
87
|
});
|
|
88
88
|
};
|
|
89
|
-
function
|
|
89
|
+
async function fetchHTTPResponse(opts, ac) {
|
|
90
|
+
const url = opts.getUrl(opts);
|
|
91
|
+
const body = opts.getBody(opts);
|
|
90
92
|
const { type } = opts;
|
|
93
|
+
const headers = await opts.headers();
|
|
94
|
+
/* istanbul ignore if -- @preserve */ if (type === 'subscription') {
|
|
95
|
+
throw new Error('Subscriptions should use wsLink');
|
|
96
|
+
}
|
|
97
|
+
return opts.fetch(url, {
|
|
98
|
+
method: METHOD[type],
|
|
99
|
+
signal: ac?.signal,
|
|
100
|
+
body: body,
|
|
101
|
+
headers: {
|
|
102
|
+
...opts.contentTypeHeader ? {
|
|
103
|
+
'content-type': opts.contentTypeHeader
|
|
104
|
+
} : {},
|
|
105
|
+
...opts.batchModeHeader ? {
|
|
106
|
+
'trpc-batch-mode': opts.batchModeHeader
|
|
107
|
+
} : {},
|
|
108
|
+
...headers
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function httpRequest(opts) {
|
|
91
113
|
const ac = opts.AbortController ? new opts.AbortController() : null;
|
|
114
|
+
const meta = {};
|
|
92
115
|
const promise = new Promise((resolve, reject)=>{
|
|
93
|
-
|
|
94
|
-
const body = opts.getBody(opts);
|
|
95
|
-
const meta = {};
|
|
96
|
-
Promise.resolve(opts.headers()).then((headers)=>{
|
|
97
|
-
/* istanbul ignore if -- @preserve */ if (type === 'subscription') {
|
|
98
|
-
throw new Error('Subscriptions should use wsLink');
|
|
99
|
-
}
|
|
100
|
-
return opts.fetch(url, {
|
|
101
|
-
method: METHOD[type],
|
|
102
|
-
signal: ac?.signal,
|
|
103
|
-
body: body,
|
|
104
|
-
headers: {
|
|
105
|
-
...opts.contentTypeHeader ? {
|
|
106
|
-
'content-type': opts.contentTypeHeader
|
|
107
|
-
} : {},
|
|
108
|
-
...headers
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
}).then((_res)=>{
|
|
116
|
+
fetchHTTPResponse(opts, ac).then((_res)=>{
|
|
112
117
|
meta.response = _res;
|
|
113
118
|
return _res.json();
|
|
114
119
|
}).then((json)=>{
|
|
@@ -127,6 +132,8 @@ function httpRequest(opts) {
|
|
|
127
132
|
};
|
|
128
133
|
}
|
|
129
134
|
|
|
135
|
+
exports.fetchHTTPResponse = fetchHTTPResponse;
|
|
136
|
+
exports.getBody = getBody;
|
|
130
137
|
exports.getFetch = getFetch;
|
|
131
138
|
exports.getUrl = getUrl;
|
|
132
139
|
exports.httpRequest = httpRequest;
|
|
@@ -84,29 +84,34 @@ const jsonHttpRequester = (opts)=>{
|
|
|
84
84
|
getBody
|
|
85
85
|
});
|
|
86
86
|
};
|
|
87
|
-
function
|
|
87
|
+
async function fetchHTTPResponse(opts, ac) {
|
|
88
|
+
const url = opts.getUrl(opts);
|
|
89
|
+
const body = opts.getBody(opts);
|
|
88
90
|
const { type } = opts;
|
|
91
|
+
const headers = await opts.headers();
|
|
92
|
+
/* istanbul ignore if -- @preserve */ if (type === 'subscription') {
|
|
93
|
+
throw new Error('Subscriptions should use wsLink');
|
|
94
|
+
}
|
|
95
|
+
return opts.fetch(url, {
|
|
96
|
+
method: METHOD[type],
|
|
97
|
+
signal: ac?.signal,
|
|
98
|
+
body: body,
|
|
99
|
+
headers: {
|
|
100
|
+
...opts.contentTypeHeader ? {
|
|
101
|
+
'content-type': opts.contentTypeHeader
|
|
102
|
+
} : {},
|
|
103
|
+
...opts.batchModeHeader ? {
|
|
104
|
+
'trpc-batch-mode': opts.batchModeHeader
|
|
105
|
+
} : {},
|
|
106
|
+
...headers
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function httpRequest(opts) {
|
|
89
111
|
const ac = opts.AbortController ? new opts.AbortController() : null;
|
|
112
|
+
const meta = {};
|
|
90
113
|
const promise = new Promise((resolve, reject)=>{
|
|
91
|
-
|
|
92
|
-
const body = opts.getBody(opts);
|
|
93
|
-
const meta = {};
|
|
94
|
-
Promise.resolve(opts.headers()).then((headers)=>{
|
|
95
|
-
/* istanbul ignore if -- @preserve */ if (type === 'subscription') {
|
|
96
|
-
throw new Error('Subscriptions should use wsLink');
|
|
97
|
-
}
|
|
98
|
-
return opts.fetch(url, {
|
|
99
|
-
method: METHOD[type],
|
|
100
|
-
signal: ac?.signal,
|
|
101
|
-
body: body,
|
|
102
|
-
headers: {
|
|
103
|
-
...opts.contentTypeHeader ? {
|
|
104
|
-
'content-type': opts.contentTypeHeader
|
|
105
|
-
} : {},
|
|
106
|
-
...headers
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
}).then((_res)=>{
|
|
114
|
+
fetchHTTPResponse(opts, ac).then((_res)=>{
|
|
110
115
|
meta.response = _res;
|
|
111
116
|
return _res.json();
|
|
112
117
|
}).then((json)=>{
|
|
@@ -125,4 +130,4 @@ function httpRequest(opts) {
|
|
|
125
130
|
};
|
|
126
131
|
}
|
|
127
132
|
|
|
128
|
-
export {
|
|
133
|
+
export { getBody as a, getFetch as b, fetchHTTPResponse as f, getUrl as g, httpRequest as h, jsonHttpRequester as j, resolveHTTPLinkOptions as r };
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var observable = require('@trpc/server/observable');
|
|
6
|
-
var transformResult = require('./transformResult-70f95ffb.js');
|
|
7
6
|
var links_splitLink = require('./splitLink-f29e84be.js');
|
|
7
|
+
var transformResult = require('./transformResult-70f95ffb.js');
|
|
8
8
|
var shared = require('@trpc/server/shared');
|
|
9
|
-
var httpUtils = require('./httpUtils-
|
|
10
|
-
var links_httpBatchLink = require('./
|
|
9
|
+
var httpUtils = require('./httpUtils-21cbb35d.js');
|
|
10
|
+
var links_httpBatchLink = require('./httpBatchLink-38dcf5f2.js');
|
|
11
11
|
var links_httpLink = require('./links/httpLink.js');
|
|
12
12
|
var links_loggerLink = require('./links/loggerLink.js');
|
|
13
13
|
var links_wsLink = require('./links/wsLink.js');
|
|
@@ -163,6 +163,150 @@ function createTRPCProxyClient(opts) {
|
|
|
163
163
|
return proxy;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
// Stream parsing adapted from https://www.loginradius.com/blog/engineering/guest-post/http-streaming-with-nodejs-and-fetch-api/
|
|
167
|
+
/**
|
|
168
|
+
* @internal
|
|
169
|
+
* @description Take a stream of bytes and call `onLine` with
|
|
170
|
+
* a JSON object for each line in the stream. Expected stream
|
|
171
|
+
* format is:
|
|
172
|
+
* ```json
|
|
173
|
+
* {"1": {...}
|
|
174
|
+
* ,"0": {...}
|
|
175
|
+
* }
|
|
176
|
+
* ```
|
|
177
|
+
*/ async function parseJSONStream(opts) {
|
|
178
|
+
const parse = opts.parse ?? JSON.parse;
|
|
179
|
+
const onLine = (line)=>{
|
|
180
|
+
if (opts.signal?.aborted) return;
|
|
181
|
+
if (!line || line === '}') {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* At this point, `line` can be one of two things:
|
|
186
|
+
* - The first line of the stream `{"2":{...}`
|
|
187
|
+
* - A line in the middle of the stream `,"2":{...}`
|
|
188
|
+
*/ const indexOfColon = line.indexOf(':');
|
|
189
|
+
const indexAsStr = line.substring(2, indexOfColon - 1);
|
|
190
|
+
const text = line.substring(indexOfColon + 1);
|
|
191
|
+
opts.onSingle(Number(indexAsStr), parse(text));
|
|
192
|
+
};
|
|
193
|
+
await readLines(opts.readableStream, onLine);
|
|
194
|
+
}
|
|
195
|
+
const textDecoder = new TextDecoder();
|
|
196
|
+
/**
|
|
197
|
+
* Handle transforming a stream of bytes into lines of text.
|
|
198
|
+
* To avoid using AsyncIterators / AsyncGenerators,
|
|
199
|
+
* we use a callback for each line.
|
|
200
|
+
*
|
|
201
|
+
* @param readableStream can be a NodeJS stream or a WebAPI stream
|
|
202
|
+
* @param onLine will be called for every line ('\n' delimited) in the stream
|
|
203
|
+
*/ async function readLines(readableStream, onLine) {
|
|
204
|
+
let partOfLine = '';
|
|
205
|
+
const onChunk = (chunk)=>{
|
|
206
|
+
const chunkText = textDecoder.decode(chunk);
|
|
207
|
+
const chunkLines = chunkText.split('\n');
|
|
208
|
+
if (chunkLines.length === 1) {
|
|
209
|
+
partOfLine += chunkLines[0];
|
|
210
|
+
} else if (chunkLines.length > 1) {
|
|
211
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- length checked on line above
|
|
212
|
+
onLine(partOfLine + chunkLines[0]);
|
|
213
|
+
for(let i = 1; i < chunkLines.length - 1; i++){
|
|
214
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- length checked on line above
|
|
215
|
+
onLine(chunkLines[i]);
|
|
216
|
+
}
|
|
217
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- length doesn't change, so is necessarily > 1
|
|
218
|
+
partOfLine = chunkLines[chunkLines.length - 1];
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
// we handle 2 different types of streams, this if where we figure out which one we have
|
|
222
|
+
if ('getReader' in readableStream) {
|
|
223
|
+
await readStandardChunks(readableStream, onChunk);
|
|
224
|
+
} else {
|
|
225
|
+
await readNodeChunks(readableStream, onChunk);
|
|
226
|
+
}
|
|
227
|
+
onLine(partOfLine);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Handle NodeJS stream
|
|
231
|
+
*/ function readNodeChunks(stream, onChunk) {
|
|
232
|
+
return new Promise((resolve)=>{
|
|
233
|
+
stream.on('data', onChunk);
|
|
234
|
+
stream.on('end', resolve);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Handle WebAPI stream
|
|
239
|
+
*/ async function readStandardChunks(stream, onChunk) {
|
|
240
|
+
const reader = stream.getReader();
|
|
241
|
+
let readResult = await reader.read();
|
|
242
|
+
while(!readResult.done){
|
|
243
|
+
onChunk(readResult.value);
|
|
244
|
+
readResult = await reader.read();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const streamingJsonHttpRequester = (opts, onSingle)=>{
|
|
248
|
+
const ac = opts.AbortController ? new opts.AbortController() : null;
|
|
249
|
+
const responsePromise = httpUtils.fetchHTTPResponse({
|
|
250
|
+
...opts,
|
|
251
|
+
contentTypeHeader: 'application/json',
|
|
252
|
+
batchModeHeader: 'stream',
|
|
253
|
+
getUrl: httpUtils.getUrl,
|
|
254
|
+
getBody: httpUtils.getBody
|
|
255
|
+
}, ac);
|
|
256
|
+
const cancel = ()=>ac?.abort();
|
|
257
|
+
const promise = responsePromise.then(async (res)=>{
|
|
258
|
+
if (!res.body) throw new Error('Received response without body');
|
|
259
|
+
const meta = {
|
|
260
|
+
response: res
|
|
261
|
+
};
|
|
262
|
+
return parseJSONStream({
|
|
263
|
+
readableStream: res.body,
|
|
264
|
+
onSingle,
|
|
265
|
+
parse: (string)=>({
|
|
266
|
+
json: JSON.parse(string),
|
|
267
|
+
meta
|
|
268
|
+
}),
|
|
269
|
+
signal: ac?.signal
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
return {
|
|
273
|
+
cancel,
|
|
274
|
+
promise
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const streamRequester = (requesterOpts)=>{
|
|
279
|
+
return (batchOps, unitResolver)=>{
|
|
280
|
+
const path = batchOps.map((op)=>op.path).join(',');
|
|
281
|
+
const inputs = batchOps.map((op)=>op.input);
|
|
282
|
+
const { cancel , promise } = streamingJsonHttpRequester({
|
|
283
|
+
...requesterOpts,
|
|
284
|
+
path,
|
|
285
|
+
inputs,
|
|
286
|
+
headers () {
|
|
287
|
+
if (!requesterOpts.opts.headers) {
|
|
288
|
+
return {};
|
|
289
|
+
}
|
|
290
|
+
if (typeof requesterOpts.opts.headers === 'function') {
|
|
291
|
+
return requesterOpts.opts.headers({
|
|
292
|
+
opList: batchOps
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return requesterOpts.opts.headers;
|
|
296
|
+
}
|
|
297
|
+
}, (index, res)=>unitResolver(index, res));
|
|
298
|
+
return {
|
|
299
|
+
/**
|
|
300
|
+
* return an empty array because the batchLoader expects an array of results
|
|
301
|
+
* but we've already called the `unitResolver` for each of them, there's
|
|
302
|
+
* nothing left to do here.
|
|
303
|
+
*/ promise: promise.then(()=>[]),
|
|
304
|
+
cancel
|
|
305
|
+
};
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
const unstable_httpBatchStreamLink = links_httpBatchLink.createHTTPBatchLink(streamRequester);
|
|
309
|
+
|
|
166
310
|
const getBody = (opts)=>{
|
|
167
311
|
if (!('input' in opts)) {
|
|
168
312
|
return undefined;
|
|
@@ -189,8 +333,8 @@ const experimental_formDataLink = links_httpLink.httpLinkFactory({
|
|
|
189
333
|
requester: formDataRequester
|
|
190
334
|
});
|
|
191
335
|
|
|
192
|
-
exports.TRPCClientError = transformResult.TRPCClientError;
|
|
193
336
|
exports.splitLink = links_splitLink.splitLink;
|
|
337
|
+
exports.TRPCClientError = transformResult.TRPCClientError;
|
|
194
338
|
exports.getFetch = httpUtils.getFetch;
|
|
195
339
|
exports.httpBatchLink = links_httpBatchLink.httpBatchLink;
|
|
196
340
|
exports.httpLink = links_httpLink.httpLink;
|
|
@@ -204,3 +348,4 @@ exports.createTRPCClientProxy = createTRPCClientProxy;
|
|
|
204
348
|
exports.createTRPCProxyClient = createTRPCProxyClient;
|
|
205
349
|
exports.createTRPCUntypedClient = createTRPCUntypedClient;
|
|
206
350
|
exports.experimental_formDataLink = experimental_formDataLink;
|
|
351
|
+
exports.unstable_httpBatchStreamLink = unstable_httpBatchStreamLink;
|