Skip to content

Commit 833822a

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

File tree

2 files changed

+50
-36
lines changed

2 files changed

+50
-36
lines changed

src/__test__/PatchResolver.spec.js

Lines changed: 48 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 : 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,27 @@ 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+
path: ['viewer', 'currencies'],
44+
data: ['USD', 'GBP', 'EUR', 'CAD', 'AUD', 'CHF', '😂'], // test unicode
45+
errors: [{ message: 'Not So Bad Error' }],
46+
},
3847
};
3948
const chunk2 = getMultiPartResponse(chunk2Data, boundary);
4049

4150
const chunk3Data = {
42-
path: ['viewer', 'user', 'profile'],
43-
data: { displayName: 'Steven Seagal' },
51+
incremental: {
52+
path: ['viewer', 'user', 'profile'],
53+
data: { displayName: 'Steven Seagal' },
54+
},
4455
};
4556
const chunk3 = getMultiPartResponse(chunk3Data, boundary);
4657

4758
const chunk4Data = {
48-
data: false,
49-
path: ['viewer', 'user', 'items', 'edges', 1, 'node', 'isFavorite'],
59+
incremental: {
60+
data: false,
61+
path: ['viewer', 'user', 'items', 'edges', 1, 'node', 'isFavorite'],
62+
},
5063
};
5164
const chunk4 = getMultiPartResponse(chunk4Data, boundary);
5265
it('should work on each chunk', function () {
@@ -61,19 +74,19 @@ describe('PathResolver', function () {
6174
expect(onResponse).not.toHaveBeenCalled();
6275

6376
resolver.handleChunk(chunk1);
64-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
77+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
6578

6679
onResponse.mockClear();
6780
resolver.handleChunk(chunk2);
68-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
81+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
6982

7083
onResponse.mockClear();
7184
resolver.handleChunk(chunk3);
72-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
85+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
7386

7487
onResponse.mockClear();
7588
resolver.handleChunk(chunk4);
76-
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
89+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk4Data]);
7790
});
7891

7992
it('should work when chunks are split', function () {
@@ -96,7 +109,7 @@ describe('PathResolver', function () {
96109
resolver.handleChunk(chunk1b);
97110
expect(onResponse).not.toHaveBeenCalled();
98111
resolver.handleChunk(chunk1c);
99-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
112+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
100113
onResponse.mockClear();
101114

102115
const chunk2a = chunk2.substr(0, 35);
@@ -105,7 +118,7 @@ describe('PathResolver', function () {
105118
resolver.handleChunk(chunk2a);
106119
expect(onResponse).not.toHaveBeenCalled();
107120
resolver.handleChunk(chunk2b);
108-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
121+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
109122
onResponse.mockClear();
110123

111124
const chunk3a = chunk3.substr(0, 10);
@@ -117,7 +130,7 @@ describe('PathResolver', function () {
117130
resolver.handleChunk(chunk3b);
118131
expect(onResponse).not.toHaveBeenCalled();
119132
resolver.handleChunk(chunk3c);
120-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
133+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
121134
});
122135

123136
it('should work when chunks are combined', function () {
@@ -132,7 +145,7 @@ describe('PathResolver', function () {
132145
expect(onResponse).not.toHaveBeenCalled();
133146

134147
resolver.handleChunk(chunk1 + chunk2);
135-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data, chunk2Data]);
148+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data, chunk2Data]);
136149
});
137150

138151
it('should work when chunks are combined and split', function () {
@@ -159,13 +172,13 @@ describe('PathResolver', function () {
159172
expect(onResponse).not.toHaveBeenCalled();
160173

161174
resolver.handleChunk(chunk1 + chunk2 + chunk3a);
162-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data, chunk2Data]);
175+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data, chunk2Data]);
163176
onResponse.mockClear();
164177

165178
resolver.handleChunk(chunk3b);
166179
expect(onResponse).not.toHaveBeenCalled();
167180
resolver.handleChunk(chunk3c);
168-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
181+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
169182
});
170183

171184
it('should work when chunks are combined across boundaries', function () {
@@ -183,10 +196,10 @@ describe('PathResolver', function () {
183196
const chunk2b = chunk2.substring(35);
184197

185198
resolver.handleChunk(chunk1 + chunk2a);
186-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
199+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
187200
onResponse.mockClear();
188201
resolver.handleChunk(chunk2b);
189-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
202+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
190203
});
191204
it('should work when final chunk ends with terminating boundary', function () {
192205
const onResponse = jest.fn();
@@ -200,20 +213,20 @@ describe('PathResolver', function () {
200213
expect(onResponse).not.toHaveBeenCalled();
201214

202215
resolver.handleChunk(chunk1);
203-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
216+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
204217

205218
onResponse.mockClear();
206219
resolver.handleChunk(chunk2);
207-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
220+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
208221

209222
onResponse.mockClear();
210223
resolver.handleChunk(chunk3);
211-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
224+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
212225

213226
onResponse.mockClear();
214227
const chunk4FinalBoundary = getMultiPartResponse(chunk4Data, `${boundary}--`);
215228
resolver.handleChunk(chunk4FinalBoundary);
216-
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
229+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk4Data]);
217230
});
218231

219232
it('should work with preamble', function () {
@@ -229,20 +242,20 @@ describe('PathResolver', function () {
229242
expect(onResponse).not.toHaveBeenCalled();
230243

231244
resolver.handleChunk(chunk1);
232-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
245+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
233246

234247
onResponse.mockClear();
235248
resolver.handleChunk(chunk2);
236-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
249+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
237250

238251
onResponse.mockClear();
239252
resolver.handleChunk(chunk3);
240-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
253+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
241254

242255
onResponse.mockClear();
243256
const chunk4FinalBoundary = getMultiPartResponse(chunk4Data, `${boundary}--`);
244257
resolver.handleChunk(chunk4FinalBoundary);
245-
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
258+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk4Data]);
246259
});
247260
it('should work with epilogue', function () {
248261
const onResponse = jest.fn();
@@ -256,20 +269,20 @@ describe('PathResolver', function () {
256269
expect(onResponse).not.toHaveBeenCalled();
257270

258271
resolver.handleChunk(chunk1);
259-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
272+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
260273

261274
onResponse.mockClear();
262275
resolver.handleChunk(chunk2);
263-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
276+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
264277

265278
onResponse.mockClear();
266279
resolver.handleChunk(chunk3);
267-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
280+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
268281

269282
onResponse.mockClear();
270283
resolver.handleChunk(chunk4);
271284
resolver.handleChunk(`This is some epilogue data that should be ignored\r\n`);
272-
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
285+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk4Data]);
273286
});
274287
it('should work with epilogue after chunk with terminating boundary', function () {
275288
const onResponse = jest.fn();
@@ -283,21 +296,21 @@ describe('PathResolver', function () {
283296
expect(onResponse).not.toHaveBeenCalled();
284297

285298
resolver.handleChunk(chunk1);
286-
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
299+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk1Data]);
287300

288301
onResponse.mockClear();
289302
resolver.handleChunk(chunk2);
290-
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
303+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk2Data]);
291304

292305
onResponse.mockClear();
293306
resolver.handleChunk(chunk3);
294-
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
307+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk3Data]);
295308

296309
onResponse.mockClear();
297310
const chunk4FinalBoundary = getMultiPartResponse(chunk4Data, `${boundary}--`);
298311
resolver.handleChunk(chunk4FinalBoundary);
299312
resolver.handleChunk(`This is some epilogue data that should be ignored\r\n`);
300-
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
313+
assertChunksRecieved(onResponse.mock.calls[0][0], [chunk4Data]);
301314
});
302315
});
303316
}

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;
5152
const parts = [...previousParts, payload];
5253

5354
if (next && next.length) {

0 commit comments

Comments
 (0)