fetch-multipart-graphql
Advanced tools
@@ -26,5 +26,8 @@ 'use strict'; | ||
var textDecoder = new TextDecoder(); | ||
var patchResolver = new _PatchResolver.PatchResolver({ onResponse: function onResponse(r) { | ||
var patchResolver = new _PatchResolver.PatchResolver({ | ||
onResponse: function onResponse(r) { | ||
return onNext(r); | ||
}, applyToPrevious: applyToPrevious }); | ||
}, | ||
applyToPrevious: applyToPrevious | ||
}); | ||
return reader.read().then(function sendNext(_ref2) { | ||
@@ -31,0 +34,0 @@ var value = _ref2.value, |
@@ -1,2 +0,2 @@ | ||
"use strict"; | ||
'use strict'; | ||
@@ -8,7 +8,7 @@ Object.defineProperty(exports, "__esModule", { | ||
var _getTransport = require("./getTransport"); | ||
var _getTransport = require('./getTransport'); | ||
var _PatchResolver = require("./PatchResolver"); | ||
var _PatchResolver = require('./PatchResolver'); | ||
exports.PatchResolver = _PatchResolver.PatchResolver; | ||
exports.default = (0, _getTransport.getTransport)(); |
@@ -10,47 +10,6 @@ 'use strict'; | ||
function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
function insertPatch(obj, path, data) { | ||
if (Array.isArray(obj) && typeof path === 'number') { | ||
return [].concat(obj.slice(0, path), [data], obj.slice(path + 1)); | ||
} else { | ||
return Object.assign({}, obj, _defineProperty({}, path, data)); | ||
} | ||
} | ||
// recursive function to apply the patch to the previous response | ||
function applyPatch(previousResponse, patchPath, patchData) { | ||
var _patchPath = _toArray(patchPath), | ||
nextPath = _patchPath[0], | ||
rest = _patchPath.slice(1); | ||
if (rest.length === 0) { | ||
return insertPatch(previousResponse, nextPath, patchData); | ||
} | ||
return insertPatch(previousResponse, nextPath, applyPatch(previousResponse[nextPath], rest, patchData)); | ||
} | ||
function mergeErrors(previousErrors, patchErrors) { | ||
if (previousErrors && patchErrors) { | ||
return [].concat(previousErrors, patchErrors); | ||
} else if (previousErrors) { | ||
return previousErrors; | ||
} else if (patchErrors) { | ||
return patchErrors; | ||
} | ||
return undefined; | ||
} | ||
function PatchResolver(_ref) { | ||
var onResponse = _ref.onResponse, | ||
applyToPrevious = _ref.applyToPrevious, | ||
_ref$mergeExtensions = _ref.mergeExtensions, | ||
mergeExtensions = _ref$mergeExtensions === undefined ? function () {} : _ref$mergeExtensions; | ||
var onResponse = _ref.onResponse; | ||
this.applyToPrevious = typeof applyToPrevious === 'boolean' ? applyToPrevious : true; | ||
this.onResponse = onResponse; | ||
this.mergeExtensions = mergeExtensions; | ||
this.previousResponse = null; | ||
this.processedChunks = 0; | ||
@@ -61,4 +20,2 @@ this.chunkBuffer = ''; | ||
PatchResolver.prototype.handleChunk = function (data) { | ||
var _this = this; | ||
this.chunkBuffer += data; | ||
@@ -72,26 +29,4 @@ | ||
if (parts.length) { | ||
if (this.applyToPrevious) { | ||
parts.forEach(function (part) { | ||
if (_this.processedChunks === 0) { | ||
_this.previousResponse = part; | ||
} else { | ||
if (!(Array.isArray(part.path) && typeof part.data !== 'undefined')) { | ||
throw new Error('invalid patch format ' + JSON.stringify(part, null, 2)); | ||
} | ||
_this.previousResponse = Object.assign({}, _this.previousResponse, { | ||
data: applyPatch(_this.previousResponse.data, part.path, part.data), | ||
errors: mergeErrors(_this.previousResponse.errors, part.errors), | ||
extensions: _this.mergeExtensions(_this.previousResponse.extensions, part.extensions) | ||
}); | ||
} | ||
_this.processedChunks += 1; | ||
}); | ||
// don't need to re-trigger every intermediate state | ||
this.onResponse(this.previousResponse); | ||
} else { | ||
parts.forEach(function (part) { | ||
return _this.onResponse(part); | ||
}); | ||
} | ||
this.onResponse(parts); | ||
} | ||
}; |
{ | ||
"name": "fetch-multipart-graphql", | ||
"version": "2.0.0-rob.0", | ||
"version": "2.0.0", | ||
"description": "Cross browser function to fetch and parse streaming multipart graphql responses.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -27,3 +27,3 @@ # fetch-multipart-graphql | ||
credentials: 'same-origin', | ||
onNext: json => sink.next(json), | ||
onNext: parts => sink.next(parts), | ||
onError: err => sink.error(err), | ||
@@ -30,0 +30,0 @@ onComplete: () => sink.complete(), |
@@ -7,57 +7,45 @@ import { PatchResolver } from '../PatchResolver'; | ||
const chunk1 = [ | ||
'', | ||
'---', | ||
'Content-Type: application/json', | ||
'Content-Length: 142', | ||
'', | ||
'{"data":{"viewer":{"currencies":null,"user":{"profile":null,"items":{"edges":[{"node":{"isFavorite":null}},{"node":{"isFavorite":null}}]}}}}}\n', | ||
].join('\r\n'); | ||
function getMultiPartResponse(data) { | ||
const json = JSON.stringify(data); | ||
const chunk = Buffer.from(json, 'utf8'); | ||
const chunk1error = [ | ||
'', | ||
'---', | ||
'Content-Type: application/json', | ||
'Content-Length: 104', | ||
'', | ||
'{"data":{"viewer":{"currencies":null,"user":{"profile":null}}},"errors":[{"message":"Very Bad Error"}]}\n', | ||
].join('\r\n'); | ||
return [ | ||
'', | ||
'---', | ||
'Content-Type: application/json', | ||
`Content-Length: ${String(chunk.length)}`, | ||
'', | ||
json, | ||
'', | ||
].join('\r\n'); | ||
} | ||
const chunk2 = [ | ||
'', | ||
'---', | ||
'Content-Type: application/json', | ||
'Content-Length: 85', | ||
'', | ||
'{"path":["viewer","currencies"],"data":["USD","GBP","EUR","CAD","AUD","CHF","😂"]}\n', // test unicode | ||
].join('\r\n'); | ||
const chunk1Data = { | ||
data: { | ||
viewer: { | ||
currencies: null, | ||
user: { | ||
profile: null, | ||
items: { edges: [{ node: { isFavorite: null } }, { node: { isFavorite: null } }] }, | ||
}, | ||
}, | ||
}, | ||
}; | ||
const chunk1 = getMultiPartResponse(chunk1Data); | ||
const chunk2error = [ | ||
'', | ||
'---', | ||
'Content-Type: application/json', | ||
'Content-Length: 127', | ||
'', | ||
'{"path":["viewer","currencies"],"data":["USD","GBP","EUR","CAD","AUD","CHF","😂"],"errors":[{"message":"Not So Bad Error"}]}\n', | ||
].join('\r\n'); | ||
const chunk2Data = { | ||
path: ['viewer', 'currencies'], | ||
data: ['USD', 'GBP', 'EUR', 'CAD', 'AUD', 'CHF', '😂'], // test unicode | ||
errors: [{ message: 'Not So Bad Error' }], | ||
}; | ||
const chunk2 = getMultiPartResponse(chunk2Data); | ||
const chunk3 = [ | ||
'', | ||
'---', | ||
'Content-Type: application/json', | ||
'Content-Length: 76', | ||
'', | ||
'{"path":["viewer","user","profile"],"data":{"displayName":"Steven Seagal"}}\n', | ||
].join('\r\n'); | ||
const chunk3Data = { path: ['viewer', 'user', 'profile'], data: { displayName: 'Steven Seagal' } }; | ||
const chunk3 = getMultiPartResponse(chunk3Data); | ||
const chunk4 = [ | ||
'', | ||
'---', | ||
'Content-Type: application/json', | ||
'Content-Length: 78', | ||
'', | ||
'{"data":false,"path":["viewer","user","items","edges",1,"node","isFavorite"]}\n', | ||
'', | ||
'-----\r\n', | ||
].join('\r\n'); | ||
const chunk4Data = { | ||
data: false, | ||
path: ['viewer', 'user', 'items', 'edges', 1, 'node', 'isFavorite'], | ||
}; | ||
const chunk4 = getMultiPartResponse(chunk4Data); | ||
@@ -72,15 +60,15 @@ describe('PathResolver', function() { | ||
resolver.handleChunk(chunk1); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]); | ||
onResponse.mockClear(); | ||
resolver.handleChunk(chunk2); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]); | ||
onResponse.mockClear(); | ||
resolver.handleChunk(chunk3); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]); | ||
onResponse.mockClear(); | ||
resolver.handleChunk(chunk4); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]); | ||
}); | ||
@@ -103,3 +91,3 @@ | ||
resolver.handleChunk(chunk1c); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]); | ||
onResponse.mockClear(); | ||
@@ -113,3 +101,3 @@ | ||
resolver.handleChunk(chunk2b); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]); | ||
onResponse.mockClear(); | ||
@@ -126,3 +114,3 @@ | ||
resolver.handleChunk(chunk3c); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]); | ||
}); | ||
@@ -137,3 +125,3 @@ | ||
resolver.handleChunk(chunk1 + chunk2); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data, chunk2Data]); | ||
}); | ||
@@ -152,3 +140,3 @@ | ||
resolver.handleChunk(chunk1 + chunk2 + chunk3a); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data, chunk2Data]); | ||
onResponse.mockClear(); | ||
@@ -159,3 +147,3 @@ | ||
resolver.handleChunk(chunk3c); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]); | ||
}); | ||
@@ -173,40 +161,7 @@ | ||
resolver.handleChunk(chunk1 + chunk2a); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]); | ||
onResponse.mockClear(); | ||
resolver.handleChunk(chunk2b); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]); | ||
}); | ||
it('should merge errors', function() { | ||
const onResponse = jest.fn(); | ||
const resolver = new PatchResolver({ | ||
onResponse, | ||
}); | ||
resolver.handleChunk(chunk1error); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
onResponse.mockClear(); | ||
resolver.handleChunk(chunk2error); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
onResponse.mockClear(); | ||
resolver.handleChunk(chunk3); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
}); | ||
it('should work when not applying to previous', function() { | ||
const onResponse = jest.fn(); | ||
const resolver = new PatchResolver({ | ||
onResponse, | ||
applyToPrevious: false, | ||
}); | ||
const chunk2a = chunk2.substring(0, 35); | ||
const chunk2b = chunk2.substring(35); | ||
resolver.handleChunk(chunk1 + chunk2a); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
onResponse.mockClear(); | ||
resolver.handleChunk(chunk2b); | ||
expect(onResponse.mock.calls[0][0]).toMatchSnapshot(); | ||
}); | ||
}); |
import { PatchResolver } from './PatchResolver'; | ||
export function fetchImpl(url, { method, headers, credentials, body, onNext, onError, onComplete, applyToPrevious }) { | ||
return fetch(url, { method, headers, body, credentials }).then(response => { | ||
// @defer uses multipart responses to stream patches over HTTP | ||
if ( | ||
response.status < 300 && | ||
response.headers && | ||
response.headers.get('Content-Type') && | ||
response.headers.get('Content-Type').indexOf('multipart/mixed') >= 0 | ||
) { | ||
// For the majority of browsers with support for ReadableStream and TextDecoder | ||
const reader = response.body.getReader(); | ||
const textDecoder = new TextDecoder(); | ||
const patchResolver = new PatchResolver({ onResponse: r => onNext(r), applyToPrevious }); | ||
return reader.read().then(function sendNext({ value, done }) { | ||
if (!done) { | ||
let plaintext; | ||
try { | ||
plaintext = textDecoder.decode(value); | ||
// Read the header to get the Content-Length | ||
patchResolver.handleChunk(plaintext); | ||
} catch (err) { | ||
const parseError = err; | ||
parseError.response = response; | ||
parseError.statusCode = response.status; | ||
parseError.bodyText = plaintext; | ||
onError(parseError); | ||
export function fetchImpl( | ||
url, | ||
{ method, headers, credentials, body, onNext, onError, onComplete, applyToPrevious } | ||
) { | ||
return fetch(url, { method, headers, body, credentials }) | ||
.then(response => { | ||
// @defer uses multipart responses to stream patches over HTTP | ||
if ( | ||
response.status < 300 && | ||
response.headers && | ||
response.headers.get('Content-Type') && | ||
response.headers.get('Content-Type').indexOf('multipart/mixed') >= 0 | ||
) { | ||
// For the majority of browsers with support for ReadableStream and TextDecoder | ||
const reader = response.body.getReader(); | ||
const textDecoder = new TextDecoder(); | ||
const patchResolver = new PatchResolver({ | ||
onResponse: r => onNext(r), | ||
applyToPrevious, | ||
}); | ||
return reader.read().then(function sendNext({ value, done }) { | ||
if (!done) { | ||
let plaintext; | ||
try { | ||
plaintext = textDecoder.decode(value); | ||
// Read the header to get the Content-Length | ||
patchResolver.handleChunk(plaintext); | ||
} catch (err) { | ||
const parseError = err; | ||
parseError.response = response; | ||
parseError.statusCode = response.status; | ||
parseError.bodyText = plaintext; | ||
onError(parseError); | ||
} | ||
reader.read().then(sendNext); | ||
} else { | ||
onComplete(); | ||
} | ||
reader.read().then(sendNext); | ||
} else { | ||
}); | ||
} else { | ||
return response.json().then(json => { | ||
onNext(json); | ||
onComplete(); | ||
} | ||
}); | ||
} else { | ||
return response.json().then(json => { | ||
onNext(json); | ||
onComplete(); | ||
}); | ||
} | ||
}).catch(onError); | ||
}); | ||
} | ||
}) | ||
.catch(onError); | ||
} |
@@ -1,5 +0,5 @@ | ||
import { getTransport } from "./getTransport"; | ||
import { PatchResolver } from "./PatchResolver"; | ||
import { getTransport } from './getTransport'; | ||
import { PatchResolver } from './PatchResolver'; | ||
export { PatchResolver }; | ||
export default getTransport(); |
import { parseMultipartHttp } from './parseMultipartHttp'; | ||
function insertPatch(obj, path, data) { | ||
if (Array.isArray(obj) && typeof path === 'number') { | ||
return [].concat(obj.slice(0, path), [data], obj.slice(path + 1)); | ||
} else { | ||
return { | ||
...obj, | ||
[path]: data, | ||
}; | ||
} | ||
} | ||
// recursive function to apply the patch to the previous response | ||
function applyPatch(previousResponse, patchPath, patchData) { | ||
const [nextPath, ...rest] = patchPath; | ||
if (rest.length === 0) { | ||
return insertPatch(previousResponse, nextPath, patchData); | ||
} | ||
return insertPatch( | ||
previousResponse, | ||
nextPath, | ||
applyPatch(previousResponse[nextPath], rest, patchData) | ||
); | ||
} | ||
function mergeErrors(previousErrors, patchErrors) { | ||
if (previousErrors && patchErrors) { | ||
return [].concat(previousErrors, patchErrors); | ||
} else if (previousErrors) { | ||
return previousErrors; | ||
} else if (patchErrors) { | ||
return patchErrors; | ||
} | ||
return undefined; | ||
} | ||
export function PatchResolver({ onResponse, applyToPrevious, mergeExtensions = () => {} }) { | ||
this.applyToPrevious = typeof applyToPrevious === 'boolean' ? applyToPrevious : true; | ||
export function PatchResolver({ onResponse }) { | ||
this.onResponse = onResponse; | ||
this.mergeExtensions = mergeExtensions; | ||
this.previousResponse = null; | ||
this.processedChunks = 0; | ||
@@ -52,25 +14,4 @@ this.chunkBuffer = ''; | ||
if (parts.length) { | ||
if (this.applyToPrevious) { | ||
parts.forEach(part => { | ||
if (this.processedChunks === 0) { | ||
this.previousResponse = part; | ||
} else { | ||
if (!(Array.isArray(part.path) && typeof part.data !== 'undefined')) { | ||
throw new Error('invalid patch format ' + JSON.stringify(part, null, 2)); | ||
} | ||
this.previousResponse = { | ||
...this.previousResponse, | ||
data: applyPatch(this.previousResponse.data, part.path, part.data), | ||
errors: mergeErrors(this.previousResponse.errors, part.errors), | ||
extensions: this.mergeExtensions(this.previousResponse.extensions, part.extensions), | ||
}; | ||
} | ||
this.processedChunks += 1; | ||
}); | ||
// don't need to re-trigger every intermediate state | ||
this.onResponse(this.previousResponse); | ||
} else { | ||
parts.forEach(part => this.onResponse(part)); | ||
} | ||
this.onResponse(parts); | ||
} | ||
}; |
@@ -14,3 +14,6 @@ import { PatchResolver } from './PatchResolver'; | ||
export function xhrImpl(url, { method, headers, credentials, body, onNext, onError, onComplete, applyToPrevious }) { | ||
export function xhrImpl( | ||
url, | ||
{ method, headers, credentials, body, onNext, onError, onComplete, applyToPrevious } | ||
) { | ||
const xhr = new XMLHttpRequest(); | ||
@@ -17,0 +20,0 @@ xhr.withCredentials = credentials === 'include'; // follow behavior of fetch credentials param https://github.com/github/fetch#sending-cookies |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1
-50%28154
-86.83%18
-30.77%605
-17.69%