You're Invited: Meet the Socket team at BSidesSF and RSAC - April 27 - May 1.RSVP

fetch-multipart-graphql

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fetch-multipart-graphql - npm Package Compare versions

Comparing version

to
2.0.0

@@ -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