Skip to content

Commit 2860aea

Browse files
authored
Remove support for applyToPrevious (#7)
* Remove support for `applyToPrevious` All patches will be returned as provided by the server. * return array of parts to onNext * add prettier * update tests and remove snapshots
1 parent 2f7be4a commit 2860aea

File tree

8 files changed

+119
-750
lines changed

8 files changed

+119
-750
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function fetchQuery(operation, variables) {
2424
variables,
2525
}),
2626
credentials: 'same-origin',
27-
onNext: json => sink.next(json),
27+
onNext: parts => sink.next(parts),
2828
onError: err => sink.error(err),
2929
onComplete: () => sink.complete(),
3030
});

prettier.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = {
2+
bracketSpacing: true,
3+
jsxBracketSameLine: false,
4+
printWidth: 100,
5+
requirePragma: false,
6+
singleQuote: true,
7+
tabWidth: 4,
8+
trailingComma: 'es5',
9+
useTabs: false,
10+
};

src/PatchResolver.js

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

3-
function insertPatch(obj, path, data) {
4-
if (Array.isArray(obj) && typeof path === 'number') {
5-
return [].concat(obj.slice(0, path), [data], obj.slice(path + 1));
6-
} else {
7-
return {
8-
...obj,
9-
[path]: data,
10-
};
11-
}
12-
}
13-
14-
// recursive function to apply the patch to the previous response
15-
function applyPatch(previousResponse, patchPath, patchData) {
16-
const [nextPath, ...rest] = patchPath;
17-
if (rest.length === 0) {
18-
return insertPatch(previousResponse, nextPath, patchData);
19-
}
20-
return insertPatch(
21-
previousResponse,
22-
nextPath,
23-
applyPatch(previousResponse[nextPath], rest, patchData)
24-
);
25-
}
26-
27-
function mergeErrors(previousErrors, patchErrors) {
28-
if (previousErrors && patchErrors) {
29-
return [].concat(previousErrors, patchErrors);
30-
} else if (previousErrors) {
31-
return previousErrors;
32-
} else if (patchErrors) {
33-
return patchErrors;
34-
}
35-
return undefined;
36-
}
37-
38-
export function PatchResolver({ onResponse, applyToPrevious, mergeExtensions = () => {} }) {
39-
this.applyToPrevious = typeof applyToPrevious === 'boolean' ? applyToPrevious : true;
3+
export function PatchResolver({ onResponse }) {
404
this.onResponse = onResponse;
41-
this.mergeExtensions = mergeExtensions;
42-
this.previousResponse = null;
435
this.processedChunks = 0;
446
this.chunkBuffer = '';
457
}
@@ -49,27 +11,6 @@ PatchResolver.prototype.handleChunk = function(data) {
4911
const { newBuffer, parts } = parseMultipartHttp(this.chunkBuffer);
5012
this.chunkBuffer = newBuffer;
5113
if (parts.length) {
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-
};
66-
}
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-
}
14+
this.onResponse(parts);
7415
}
7516
};

src/__test__/PatchResolver.spec.js

Lines changed: 55 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -4,61 +4,49 @@ import { TextEncoder, TextDecoder } from 'util';
44
global.TextEncoder = TextEncoder;
55
global.TextDecoder = TextDecoder;
66

7-
const chunk1 = [
8-
'',
9-
'---',
10-
'Content-Type: application/json',
11-
'Content-Length: 142',
12-
'',
13-
'{"data":{"viewer":{"currencies":null,"user":{"profile":null,"items":{"edges":[{"node":{"isFavorite":null}},{"node":{"isFavorite":null}}]}}}}}\n',
14-
].join('\r\n');
15-
16-
const chunk1error = [
17-
'',
18-
'---',
19-
'Content-Type: application/json',
20-
'Content-Length: 104',
21-
'',
22-
'{"data":{"viewer":{"currencies":null,"user":{"profile":null}}},"errors":[{"message":"Very Bad Error"}]}\n',
23-
].join('\r\n');
24-
25-
const chunk2 = [
26-
'',
27-
'---',
28-
'Content-Type: application/json',
29-
'Content-Length: 85',
30-
'',
31-
'{"path":["viewer","currencies"],"data":["USD","GBP","EUR","CAD","AUD","CHF","😂"]}\n', // test unicode
32-
].join('\r\n');
33-
34-
const chunk2error = [
35-
'',
36-
'---',
37-
'Content-Type: application/json',
38-
'Content-Length: 127',
39-
'',
40-
'{"path":["viewer","currencies"],"data":["USD","GBP","EUR","CAD","AUD","CHF","😂"],"errors":[{"message":"Not So Bad Error"}]}\n',
41-
].join('\r\n');
42-
43-
const chunk3 = [
44-
'',
45-
'---',
46-
'Content-Type: application/json',
47-
'Content-Length: 76',
48-
'',
49-
'{"path":["viewer","user","profile"],"data":{"displayName":"Steven Seagal"}}\n',
50-
].join('\r\n');
51-
52-
const chunk4 = [
53-
'',
54-
'---',
55-
'Content-Type: application/json',
56-
'Content-Length: 78',
57-
'',
58-
'{"data":false,"path":["viewer","user","items","edges",1,"node","isFavorite"]}\n',
59-
'',
60-
'-----\r\n',
61-
].join('\r\n');
7+
function getMultiPartResponse(data) {
8+
const json = JSON.stringify(data);
9+
const chunk = Buffer.from(json, 'utf8');
10+
11+
return [
12+
'',
13+
'---',
14+
'Content-Type: application/json',
15+
`Content-Length: ${String(chunk.length)}`,
16+
'',
17+
json,
18+
'',
19+
].join('\r\n');
20+
}
21+
22+
const chunk1Data = {
23+
data: {
24+
viewer: {
25+
currencies: null,
26+
user: {
27+
profile: null,
28+
items: { edges: [{ node: { isFavorite: null } }, { node: { isFavorite: null } }] },
29+
},
30+
},
31+
},
32+
};
33+
const chunk1 = getMultiPartResponse(chunk1Data);
34+
35+
const chunk2Data = {
36+
path: ['viewer', 'currencies'],
37+
data: ['USD', 'GBP', 'EUR', 'CAD', 'AUD', 'CHF', '😂'], // test unicode
38+
errors: [{ message: 'Not So Bad Error' }],
39+
};
40+
const chunk2 = getMultiPartResponse(chunk2Data);
41+
42+
const chunk3Data = { path: ['viewer', 'user', 'profile'], data: { displayName: 'Steven Seagal' } };
43+
const chunk3 = getMultiPartResponse(chunk3Data);
44+
45+
const chunk4Data = {
46+
data: false,
47+
path: ['viewer', 'user', 'items', 'edges', 1, 'node', 'isFavorite'],
48+
};
49+
const chunk4 = getMultiPartResponse(chunk4Data);
6250

6351
describe('PathResolver', function() {
6452
it('should work on each chunk', function() {
@@ -68,19 +56,19 @@ describe('PathResolver', function() {
6856
});
6957

7058
resolver.handleChunk(chunk1);
71-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
59+
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
7260

7361
onResponse.mockClear();
7462
resolver.handleChunk(chunk2);
75-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
63+
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
7664

7765
onResponse.mockClear();
7866
resolver.handleChunk(chunk3);
79-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
67+
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
8068

8169
onResponse.mockClear();
8270
resolver.handleChunk(chunk4);
83-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
71+
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
8472
});
8573

8674
it('should work when chunks are split', function() {
@@ -98,7 +86,7 @@ describe('PathResolver', function() {
9886
resolver.handleChunk(chunk1b);
9987
expect(onResponse).not.toHaveBeenCalled();
10088
resolver.handleChunk(chunk1c);
101-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
89+
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
10290
onResponse.mockClear();
10391

10492
const chunk2a = chunk2.substr(0, 35);
@@ -107,7 +95,7 @@ describe('PathResolver', function() {
10795
resolver.handleChunk(chunk2a);
10896
expect(onResponse).not.toHaveBeenCalled();
10997
resolver.handleChunk(chunk2b);
110-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
98+
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
11199
onResponse.mockClear();
112100

113101
const chunk3a = chunk3.substr(0, 10);
@@ -119,7 +107,7 @@ describe('PathResolver', function() {
119107
resolver.handleChunk(chunk3b);
120108
expect(onResponse).not.toHaveBeenCalled();
121109
resolver.handleChunk(chunk3c);
122-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
110+
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
123111
});
124112

125113
it('should work when chunks are combined', function() {
@@ -129,7 +117,7 @@ describe('PathResolver', function() {
129117
});
130118

131119
resolver.handleChunk(chunk1 + chunk2);
132-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
120+
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data, chunk2Data]);
133121
});
134122

135123
it('should work when chunks are combined and split', function() {
@@ -143,13 +131,13 @@ describe('PathResolver', function() {
143131
const chunk3c = chunk3.substr(11 + 20);
144132

145133
resolver.handleChunk(chunk1 + chunk2 + chunk3a);
146-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
134+
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data, chunk2Data]);
147135
onResponse.mockClear();
148136

149137
resolver.handleChunk(chunk3b);
150138
expect(onResponse).not.toHaveBeenCalled();
151139
resolver.handleChunk(chunk3c);
152-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
140+
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
153141
});
154142

155143
it('should work when chunks are combined across boundaries', function() {
@@ -162,42 +150,9 @@ describe('PathResolver', function() {
162150
const chunk2b = chunk2.substring(35);
163151

164152
resolver.handleChunk(chunk1 + chunk2a);
165-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
153+
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
166154
onResponse.mockClear();
167155
resolver.handleChunk(chunk2b);
168-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
169-
});
170-
171-
it('should merge errors', function() {
172-
const onResponse = jest.fn();
173-
const resolver = new PatchResolver({
174-
onResponse,
175-
});
176-
177-
resolver.handleChunk(chunk1error);
178-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
179-
onResponse.mockClear();
180-
resolver.handleChunk(chunk2error);
181-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
182-
onResponse.mockClear();
183-
resolver.handleChunk(chunk3);
184-
expect(onResponse.mock.calls[0][0]).toMatchSnapshot();
185-
});
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();
156+
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
202157
});
203158
});

0 commit comments

Comments
 (0)