Skip to content

Commit 0fa0434

Browse files
committed
feat: add support for incremental multipart payloads
1 parent 9aaa958 commit 0fa0434

File tree

2 files changed

+56
-36
lines changed

2 files changed

+56
-36
lines changed

src/__test__/PatchResolver.spec.js

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ function getMultiPartResponse(data, boundary) {
1010
return ['Content-Type: application/json', '', json, `--${boundary}\r\n`].join('\r\n');
1111
}
1212

13+
function assertChunksRecieved(mockCall, chunks) {
14+
const nonIncrementalChunks = chunks.map((chunk) =>
15+
chunk.incremental ? chunk.incremental[0] : chunk
16+
);
17+
expect(mockCall).toEqual(nonIncrementalChunks);
18+
}
19+
1320
describe('PathResolver', function () {
1421
for (const boundary of ['-', 'gc0p4Jq0M2Yt08jU534c0p']) {
1522
describe(`boundary ${boundary}`, () => {
@@ -32,21 +39,33 @@ describe('PathResolver', function () {
3239
const chunk1 = getMultiPartResponse(chunk1Data, boundary);
3340

3441
const chunk2Data = {
35-
path: ['viewer', 'currencies'],
36-
data: ['USD', 'GBP', 'EUR', 'CAD', 'AUD', 'CHF', '😂'], // test unicode
37-
errors: [{ message: 'Not So Bad Error' }],
42+
incremental: [
43+
{
44+
path: ['viewer', 'currencies'],
45+
data: ['USD', 'GBP', 'EUR', 'CAD', 'AUD', 'CHF', '😂'], // test unicode
46+
errors: [{ message: 'Not So Bad Error' }],
47+
},
48+
],
3849
};
3950
const chunk2 = getMultiPartResponse(chunk2Data, boundary);
4051

4152
const chunk3Data = {
42-
path: ['viewer', 'user', 'profile'],
43-
data: { displayName: 'Steven Seagal' },
53+
incremental: [
54+
{
55+
path: ['viewer', 'user', 'profile'],
56+
data: { displayName: 'Steven Seagal' },
57+
},
58+
],
4459
};
4560
const chunk3 = getMultiPartResponse(chunk3Data, boundary);
4661

4762
const chunk4Data = {
48-
data: false,
49-
path: ['viewer', 'user', 'items', 'edges', 1, 'node', 'isFavorite'],
63+
incremental: [
64+
{
65+
data: false,
66+
path: ['viewer', 'user', 'items', 'edges', 1, 'node', 'isFavorite'],
67+
},
68+
],
5069
};
5170
const chunk4 = getMultiPartResponse(chunk4Data, boundary);
5271
it('should work on each chunk', function () {
@@ -61,19 +80,19 @@ describe('PathResolver', function () {
6180
expect(onResponse).not.toHaveBeenCalled();
6281

6382
resolver.handleChunk(chunk1);
64-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
83+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
6584

6685
onResponse.mockClear();
6786
resolver.handleChunk(chunk2);
68-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
87+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
6988

7089
onResponse.mockClear();
7190
resolver.handleChunk(chunk3);
72-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
91+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
7392

7493
onResponse.mockClear();
7594
resolver.handleChunk(chunk4);
76-
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
95+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk4Data]);
7796
});
7897

7998
it('should work when chunks are split', function () {
@@ -96,7 +115,7 @@ describe('PathResolver', function () {
96115
resolver.handleChunk(chunk1b);
97116
expect(onResponse).not.toHaveBeenCalled();
98117
resolver.handleChunk(chunk1c);
99-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
118+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
100119
onResponse.mockClear();
101120

102121
const chunk2a = chunk2.substr(0, 35);
@@ -105,7 +124,7 @@ describe('PathResolver', function () {
105124
resolver.handleChunk(chunk2a);
106125
expect(onResponse).not.toHaveBeenCalled();
107126
resolver.handleChunk(chunk2b);
108-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
127+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
109128
onResponse.mockClear();
110129

111130
const chunk3a = chunk3.substr(0, 10);
@@ -117,7 +136,7 @@ describe('PathResolver', function () {
117136
resolver.handleChunk(chunk3b);
118137
expect(onResponse).not.toHaveBeenCalled();
119138
resolver.handleChunk(chunk3c);
120-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
139+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
121140
});
122141

123142
it('should work when chunks are combined', function () {
@@ -132,7 +151,7 @@ describe('PathResolver', function () {
132151
expect(onResponse).not.toHaveBeenCalled();
133152

134153
resolver.handleChunk(chunk1 + chunk2);
135-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data, chunk2Data]);
154+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data, chunk2Data]);
136155
});
137156

138157
it('should work when chunks are combined and split', function () {
@@ -159,13 +178,13 @@ describe('PathResolver', function () {
159178
expect(onResponse).not.toHaveBeenCalled();
160179

161180
resolver.handleChunk(chunk1 + chunk2 + chunk3a);
162-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data, chunk2Data]);
181+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data, chunk2Data]);
163182
onResponse.mockClear();
164183

165184
resolver.handleChunk(chunk3b);
166185
expect(onResponse).not.toHaveBeenCalled();
167186
resolver.handleChunk(chunk3c);
168-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
187+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
169188
});
170189

171190
it('should work when chunks are combined across boundaries', function () {
@@ -183,10 +202,10 @@ describe('PathResolver', function () {
183202
const chunk2b = chunk2.substring(35);
184203

185204
resolver.handleChunk(chunk1 + chunk2a);
186-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
205+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
187206
onResponse.mockClear();
188207
resolver.handleChunk(chunk2b);
189-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
208+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
190209
});
191210
it('should work when final chunk ends with terminating boundary', function () {
192211
const onResponse = jest.fn();
@@ -200,20 +219,20 @@ describe('PathResolver', function () {
200219
expect(onResponse).not.toHaveBeenCalled();
201220

202221
resolver.handleChunk(chunk1);
203-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
222+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
204223

205224
onResponse.mockClear();
206225
resolver.handleChunk(chunk2);
207-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
226+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
208227

209228
onResponse.mockClear();
210229
resolver.handleChunk(chunk3);
211-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
230+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
212231

213232
onResponse.mockClear();
214233
const chunk4FinalBoundary = getMultiPartResponse(chunk4Data, `${boundary}--`);
215234
resolver.handleChunk(chunk4FinalBoundary);
216-
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
235+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk4Data]);
217236
});
218237

219238
it('should work with preamble', function () {
@@ -229,20 +248,20 @@ describe('PathResolver', function () {
229248
expect(onResponse).not.toHaveBeenCalled();
230249

231250
resolver.handleChunk(chunk1);
232-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
251+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
233252

234253
onResponse.mockClear();
235254
resolver.handleChunk(chunk2);
236-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
255+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
237256

238257
onResponse.mockClear();
239258
resolver.handleChunk(chunk3);
240-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
259+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
241260

242261
onResponse.mockClear();
243262
const chunk4FinalBoundary = getMultiPartResponse(chunk4Data, `${boundary}--`);
244263
resolver.handleChunk(chunk4FinalBoundary);
245-
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
264+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk4Data]);
246265
});
247266
it('should work with epilogue', function () {
248267
const onResponse = jest.fn();
@@ -256,20 +275,20 @@ describe('PathResolver', function () {
256275
expect(onResponse).not.toHaveBeenCalled();
257276

258277
resolver.handleChunk(chunk1);
259-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
278+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
260279

261280
onResponse.mockClear();
262281
resolver.handleChunk(chunk2);
263-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
282+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
264283

265284
onResponse.mockClear();
266285
resolver.handleChunk(chunk3);
267-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
286+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
268287

269288
onResponse.mockClear();
270289
resolver.handleChunk(chunk4);
271290
resolver.handleChunk(`This is some epilogue data that should be ignored\r\n`);
272-
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
291+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk4Data]);
273292
});
274293
it('should work with epilogue after chunk with terminating boundary', function () {
275294
const onResponse = jest.fn();
@@ -283,21 +302,21 @@ describe('PathResolver', function () {
283302
expect(onResponse).not.toHaveBeenCalled();
284303

285304
resolver.handleChunk(chunk1);
286-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
305+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
287306

288307
onResponse.mockClear();
289308
resolver.handleChunk(chunk2);
290-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
309+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
291310

292311
onResponse.mockClear();
293312
resolver.handleChunk(chunk3);
294-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
313+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
295314

296315
onResponse.mockClear();
297316
const chunk4FinalBoundary = getMultiPartResponse(chunk4Data, `${boundary}--`);
298317
resolver.handleChunk(chunk4FinalBoundary);
299318
resolver.handleChunk(`This is some epilogue data that should be ignored\r\n`);
300-
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
319+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk4Data]);
301320
});
302321
});
303322
}

src/parseMultipartHttp.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ export function parseMultipartHttp(buffer, boundary, previousParts = [], isPream
4747
// remove trailing boundary things
4848
body = body.replace(delimiter + '\r\n', '').replace(delimiter + '--\r\n', '');
4949

50-
const payload = JSON.parse(body);
50+
let payload = JSON.parse(body);
51+
if (payload.incremental) payload = payload.incremental[0];
5152
const parts = [...previousParts, payload];
5253

5354
if (next && next.length) {

0 commit comments

Comments
 (0)