Skip to content

Commit c1b042c

Browse files
authored
Optionally disable incremental responses (#6)
* fix snaps * add applyToPrevious options
1 parent 04d1b27 commit c1b042c

File tree

5 files changed

+100
-22
lines changed

5 files changed

+100
-22
lines changed

src/PatchResolver.js

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ function mergeErrors(previousErrors, patchErrors) {
3535
return undefined;
3636
}
3737

38-
export function PatchResolver({ onResponse, mergeExtensions = () => {} }) {
38+
export function PatchResolver({ onResponse, applyToPrevious, mergeExtensions = () => {} }) {
39+
this.applyToPrevious = typeof applyToPrevious === 'boolean' ? applyToPrevious : true;
3940
this.onResponse = onResponse;
4041
this.mergeExtensions = mergeExtensions;
4142
this.previousResponse = null;
@@ -48,23 +49,27 @@ PatchResolver.prototype.handleChunk = function(data) {
4849
const { newBuffer, parts } = parseMultipartHttp(this.chunkBuffer);
4950
this.chunkBuffer = newBuffer;
5051
if (parts.length) {
51-
parts.forEach(part => {
52-
if (this.processedChunks === 0) {
53-
this.previousResponse = part;
54-
} else {
55-
if (!(Array.isArray(part.path) && typeof part.data !== 'undefined')) {
56-
throw new Error('invalid patch format ' + JSON.stringify(part, null, 2));
52+
if (this.applyToPrevious) {
53+
parts.forEach(part => {
54+
if (this.processedChunks === 0) {
55+
this.previousResponse = part;
56+
} else {
57+
if (!(Array.isArray(part.path) && typeof part.data !== 'undefined')) {
58+
throw new Error('invalid patch format ' + JSON.stringify(part, null, 2));
59+
}
60+
this.previousResponse = {
61+
...this.previousResponse,
62+
data: applyPatch(this.previousResponse.data, part.path, part.data),
63+
errors: mergeErrors(this.previousResponse.errors, part.errors),
64+
extensions: this.mergeExtensions(this.previousResponse.extensions, part.extensions),
65+
};
5766
}
58-
this.previousResponse = {
59-
...this.previousResponse,
60-
data: applyPatch(this.previousResponse.data, part.path, part.data),
61-
errors: mergeErrors(this.previousResponse.errors, part.errors),
62-
extensions: this.mergeExtensions(this.previousResponse.extensions, part.extensions),
63-
};
64-
}
65-
this.processedChunks += 1;
66-
});
67-
// don't need to re-trigger every intermediate state
68-
this.onResponse(this.previousResponse);
67+
this.processedChunks += 1;
68+
});
69+
// don't need to re-trigger every intermediate state
70+
this.onResponse(this.previousResponse);
71+
} else {
72+
parts.forEach(part => this.onResponse(part));
73+
}
6974
}
7075
};

src/__test__/PatchResolver.spec.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,21 @@ describe('PathResolver', function() {
183183
resolver.handleChunk(chunk3);
184184
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
185185
});
186+
187+
it('should work when not applying to previous', function() {
188+
const onResponse = jest.fn();
189+
const resolver = new PatchResolver({
190+
onResponse,
191+
applyToPrevious: false,
192+
});
193+
194+
const chunk2a = chunk2.substring(0, 35);
195+
const chunk2b = chunk2.substring(35);
196+
197+
resolver.handleChunk(chunk1 + chunk2a);
198+
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
199+
onResponse.mockClear();
200+
resolver.handleChunk(chunk2b);
201+
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
202+
});
186203
});

src/__test__/__snapshots__/PatchResolver.spec.js.snap

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Object {
4444
"message": "Not So Bad Error",
4545
},
4646
],
47+
"extensions": undefined,
4748
}
4849
`;
4950

@@ -75,6 +76,7 @@ Object {
7576
"message": "Not So Bad Error",
7677
},
7778
],
79+
"extensions": undefined,
7880
}
7981
`;
8082

@@ -138,6 +140,7 @@ Object {
138140
},
139141
},
140142
"errors": undefined,
143+
"extensions": undefined,
141144
}
142145
`;
143146

@@ -176,6 +179,7 @@ Object {
176179
},
177180
},
178181
"errors": undefined,
182+
"extensions": undefined,
179183
}
180184
`;
181185

@@ -214,6 +218,7 @@ Object {
214218
},
215219
},
216220
"errors": undefined,
221+
"extensions": undefined,
217222
}
218223
`;
219224

@@ -250,6 +255,7 @@ Object {
250255
},
251256
},
252257
"errors": undefined,
258+
"extensions": undefined,
253259
}
254260
`;
255261

@@ -313,6 +319,7 @@ Object {
313319
},
314320
},
315321
"errors": undefined,
322+
"extensions": undefined,
316323
}
317324
`;
318325

@@ -349,6 +356,7 @@ Object {
349356
},
350357
},
351358
"errors": undefined,
359+
"extensions": undefined,
352360
}
353361
`;
354362

@@ -387,6 +395,7 @@ Object {
387395
},
388396
},
389397
"errors": undefined,
398+
"extensions": undefined,
390399
}
391400
`;
392401

@@ -450,6 +459,7 @@ Object {
450459
},
451460
},
452461
"errors": undefined,
462+
"extensions": undefined,
453463
}
454464
`;
455465

@@ -488,5 +498,51 @@ Object {
488498
},
489499
},
490500
"errors": undefined,
501+
"extensions": undefined,
502+
}
503+
`;
504+
505+
exports[`PathResolver should work when not applying to previous 1`] = `
506+
Object {
507+
"data": Object {
508+
"viewer": Object {
509+
"currencies": null,
510+
"user": Object {
511+
"items": Object {
512+
"edges": Array [
513+
Object {
514+
"node": Object {
515+
"isFavorite": null,
516+
},
517+
},
518+
Object {
519+
"node": Object {
520+
"isFavorite": null,
521+
},
522+
},
523+
],
524+
},
525+
"profile": null,
526+
},
527+
},
528+
},
529+
}
530+
`;
531+
532+
exports[`PathResolver should work when not applying to previous 2`] = `
533+
Object {
534+
"data": Array [
535+
"USD",
536+
"GBP",
537+
"EUR",
538+
"CAD",
539+
"AUD",
540+
"CHF",
541+
"😂",
542+
],
543+
"path": Array [
544+
"viewer",
545+
"currencies",
546+
],
491547
}
492548
`;

src/fetch.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { PatchResolver } from './PatchResolver';
22

3-
export function fetchImpl(url, { method, headers, credentials, body, onNext, onError, onComplete }) {
3+
export function fetchImpl(url, { method, headers, credentials, body, onNext, onError, onComplete, applyToPrevious }) {
44
return fetch(url, { method, headers, body, credentials }).then(response => {
55
// @defer uses multipart responses to stream patches over HTTP
66
if (
@@ -12,7 +12,7 @@ export function fetchImpl(url, { method, headers, credentials, body, onNext, onE
1212
// For the majority of browsers with support for ReadableStream and TextDecoder
1313
const reader = response.body.getReader();
1414
const textDecoder = new TextDecoder();
15-
const patchResolver = new PatchResolver({ onResponse: r => onNext(r) });
15+
const patchResolver = new PatchResolver({ onResponse: r => onNext(r), applyToPrevious });
1616
return reader.read().then(function sendNext({ value, done }) {
1717
if (!done) {
1818
let plaintext;

src/xhr.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ function supportsXhrResponseType(type) {
1111
return false;
1212
}
1313

14-
export function xhrImpl(url, { method, headers, credentials, body, onNext, onError, onComplete }) {
14+
export function xhrImpl(url, { method, headers, credentials, body, onNext, onError, onComplete, applyToPrevious }) {
1515
const xhr = new XMLHttpRequest();
1616
xhr.withCredentials = credentials === 'include'; // follow behavior of fetch credentials param https://github.com/github/fetch#sending-cookies
1717
let index = 0;
1818
let isDeferred = false;
1919

20-
const patchResolver = new PatchResolver({ onResponse: r => onNext(r) });
20+
const patchResolver = new PatchResolver({ onResponse: r => onNext(r), applyToPrevious });
2121

2222
function onReadyStateChange() {
2323
// The request failed, do nothing and let the error event fire

0 commit comments

Comments
 (0)