Skip to content

Remove 'iterall' as dependency. 'graphql' becomes zero dependency lib 🎉 #2364

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"words": [
"jsutils",
"tsutils",
"iterall",
"noflow",

// Different names used inside tests
Expand Down Expand Up @@ -51,6 +50,7 @@
"filepaths",
"hardcoded",
"heredoc",
"iteratable",
"lexable",
"lexed",
"lexes",
Expand All @@ -63,6 +63,7 @@
"nullability",
"nullish",
"passthrough",
"polyfilled",
"promisify",
"pubsub",
"punctuator",
Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@
"version": "node resources/gen-version.js && npm test && git add src/version.js",
"gitpublish": ". ./resources/gitpublish.sh"
},
"dependencies": {
"iterall": "^1.3.0"
},
"dependencies": {},
"devDependencies": {
"@babel/core": "7.6.2",
"@babel/plugin-transform-flow-strip-types": "7.4.4",
Expand Down
9 changes: 5 additions & 4 deletions src/execution/execute.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow strict

import { forEach, isCollection } from 'iterall';
import arrayFrom from '../polyfills/arrayFrom';

import inspect from '../jsutils/inspect';
import memoize3 from '../jsutils/memoize3';
Expand All @@ -11,6 +11,7 @@ import isNullish from '../jsutils/isNullish';
import isPromise from '../jsutils/isPromise';
import { type ObjMap } from '../jsutils/ObjMap';
import isObjectLike from '../jsutils/isObjectLike';
import isCollection from '../jsutils/isCollection';
import promiseReduce from '../jsutils/promiseReduce';
import promiseForObject from '../jsutils/promiseForObject';
import { type PromiseOrValue } from '../jsutils/PromiseOrValue';
Expand Down Expand Up @@ -910,8 +911,7 @@ function completeListValue(
// where the list contains no Promises by avoiding creating another Promise.
const itemType = returnType.ofType;
let containsPromise = false;
const completedResults = [];
forEach((result: any), (item, index) => {
const completedResults = arrayFrom(result, (item, index) => {
// No need to modify the info object containing the path,
// since from here on it is not ever accessed by resolver functions.
const fieldPath = addPath(path, index);
Expand All @@ -927,7 +927,8 @@ function completeListValue(
if (!containsPromise && isPromise(completedItem)) {
containsPromise = true;
}
completedResults.push(completedItem);

return completedItem;
});

return containsPromise ? Promise.all(completedResults) : completedResults;
Expand Down
70 changes: 70 additions & 0 deletions src/jsutils/__tests__/isCollection-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// @flow strict

import { expect } from 'chai';
import { describe, it } from 'mocha';

import identityFunc from '../identityFunc';
import isCollection from '../isCollection';

describe('isCollection', () => {
it('should return `true` for collections', () => {
expect(isCollection([])).to.equal(true);
expect(isCollection(new Int8Array(1))).to.equal(true);

// eslint-disable-next-line no-new-wrappers
expect(isCollection(new String('ABC'))).to.equal(true);

function getArguments() {
return arguments;
}
expect(isCollection(getArguments())).to.equal(true);

const arrayLike = {
length: 3,
'0': 'Alpha',
'1': 'Bravo',
'2': 'Charlie',
};
expect(isCollection(arrayLike)).to.equal(true);

const iterator = { [Symbol.iterator]: identityFunc };
expect(isCollection(iterator)).to.equal(true);

// istanbul ignore next
function* generatorFunc() {
/* do nothing */
}
expect(isCollection(generatorFunc())).to.equal(true);
});

it('should return `false` for non-collections', () => {
expect(isCollection(null)).to.equal(false);
expect(isCollection(undefined)).to.equal(false);

expect(isCollection('ABC')).to.equal(false);
expect(isCollection('0')).to.equal(false);
expect(isCollection('')).to.equal(false);

expect(isCollection(1)).to.equal(false);
expect(isCollection(0)).to.equal(false);
expect(isCollection(NaN)).to.equal(false);
// eslint-disable-next-line no-new-wrappers
expect(isCollection(new Number(123))).to.equal(false);

expect(isCollection(true)).to.equal(false);
expect(isCollection(false)).to.equal(false);
// eslint-disable-next-line no-new-wrappers
expect(isCollection(new Boolean(true))).to.equal(false);

expect(isCollection({})).to.equal(false);
expect(isCollection({ iterable: true })).to.equal(false);

const iteratorWithoutSymbol = { next: identityFunc };
expect(isCollection(iteratorWithoutSymbol)).to.equal(false);

const iteratorWithInvalidTypedSymbol = {
[Symbol.iterator]: { next: identityFunc },
};
expect(isCollection(iteratorWithInvalidTypedSymbol)).to.equal(false);
});
});
39 changes: 39 additions & 0 deletions src/jsutils/isCollection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// @flow strict

import { SYMBOL_ITERATOR } from '../polyfills/symbols';

/**
* Returns true if the provided object is an Object (i.e. not a string literal)
* and is either Iterable or Array-like.
*
* This may be used in place of [Array.isArray()][isArray] to determine if an
* object should be iterated-over. It always excludes string literals and
* includes Arrays (regardless of if it is Iterable). It also includes other
* Array-like objects such as NodeList, TypedArray, and Buffer.
*
* @example
*
* isCollection([ 1, 2, 3 ]) // true
* isCollection('ABC') // false
* isCollection({ length: 1, 0: 'Alpha' }) // true
* isCollection({ key: 'value' }) // false
* isCollection(new Map()) // true
*
* @param obj
* An Object value which might implement the Iterable or Array-like protocols.
* @return {boolean} true if Iterable or Array-like Object.
*/
export default function isCollection(obj: mixed): boolean {
if (obj == null || typeof obj !== 'object') {
return false;
}

// Is Array like?
const length = obj.length;
if (typeof length === 'number' && length >= 0 && length % 1 === 0) {
return true;
}

// Is Iterable?
return typeof obj[SYMBOL_ITERATOR] === 'function';
}
57 changes: 57 additions & 0 deletions src/polyfills/arrayFrom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// @flow strict

import { SYMBOL_ITERATOR } from './symbols';

declare function arrayFrom<T: mixed>(
arrayLike: mixed,
mapFn?: (elem: mixed, index: number) => T,
thisArg?: mixed,
): Array<T>;

/* eslint-disable no-redeclare */
// $FlowFixMe
const arrayFrom =
Array.from ||
function(obj, mapFn, thisArg) {
if (obj == null) {
throw new TypeError(
'Array.from requires an array-like object - not null or undefined',
);
}

// Is Iterable?
const iteratorMethod = obj[SYMBOL_ITERATOR];
if (typeof iteratorMethod === 'function') {
const iterator = iteratorMethod.call(obj);
const result = [];
let step;

for (let i = 0; !(step = iterator.next()).done; ++i) {
result.push(mapFn.call(thisArg, step.value, i));
// Infinite Iterators could cause forEach to run forever.
// After a very large number of iterations, produce an error.
/* istanbul ignore if */
if (i > 9999999) {
throw new TypeError('Near-infinite iteration.');
}
}
return result;
}

// Is Array like?
const length = obj.length;
if (typeof length === 'number' && length >= 0 && length % 1 === 0) {
const result = [];

for (let i = 0; i < length; ++i) {
if (Object.prototype.hasOwnProperty.call(obj, i)) {
result.push(mapFn.call(thisArg, obj[i], i));
}
}
return result;
}

return [];
};

export default arrayFrom;
12 changes: 12 additions & 0 deletions src/polyfills/symbols.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// @flow strict

// In ES2015 (or a polyfilled) environment, this will be Symbol.iterator
/* istanbul ignore next (See: https://github.com/graphql/graphql-js/issues/2317) */
export const SYMBOL_ITERATOR: string =
typeof Symbol === 'function' ? Symbol.iterator : '@@iterator';

// In ES2017 (or a polyfilled) environment, this will be Symbol.asyncIterator
/* istanbul ignore next (See: https://github.com/graphql/graphql-js/issues/2317) */
export const SYMBOL_ASYNC_ITERATOR: string =
// $FlowFixMe Flow doesn't define `Symbol.asyncIterator` yet
typeof Symbol === 'function' ? Symbol.asyncIterator : '@@asyncIterator';
8 changes: 5 additions & 3 deletions src/subscription/mapAsyncIterator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow strict

import { $$asyncIterator, getAsyncIterator } from 'iterall';
import { SYMBOL_ASYNC_ITERATOR } from '../polyfills/symbols';

import { type PromiseOrValue } from '../jsutils/PromiseOrValue';

Expand All @@ -13,7 +13,9 @@ export default function mapAsyncIterator<T, U>(
callback: T => PromiseOrValue<U>,
rejectCallback?: any => PromiseOrValue<U>,
): AsyncGenerator<U, void, void> {
const iterator = getAsyncIterator(iterable);
// $FlowFixMe
const iteratorMethod = iterable[SYMBOL_ASYNC_ITERATOR];
const iterator: AsyncIterator<T> = iteratorMethod.call(iterable);
let $return;
let abruptClose;
// $FlowFixMe(>=0.68.0)
Expand Down Expand Up @@ -57,7 +59,7 @@ export default function mapAsyncIterator<T, U>(
}
return Promise.reject(error).catch(abruptClose);
},
[$$asyncIterator]() {
[SYMBOL_ASYNC_ITERATOR]() {
return this;
},
}: any);
Expand Down
14 changes: 13 additions & 1 deletion src/subscription/subscribe.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow strict

import { isAsyncIterable } from 'iterall';
import { SYMBOL_ASYNC_ITERATOR } from '../polyfills/symbols';

import inspect from '../jsutils/inspect';
import { addPath, pathToArray } from '../jsutils/Path';
Expand Down Expand Up @@ -298,3 +298,15 @@ export function createSourceEventStream(
: Promise.reject(error);
}
}

/**
* Returns true if the provided object implements the AsyncIterator protocol via
* either implementing a `Symbol.asyncIterator` or `"@@asyncIterator"` method.
*/
function isAsyncIterable(maybeAsyncIterable: mixed): boolean {
if (maybeAsyncIterable == null || typeof maybeAsyncIterable !== 'object') {
return false;
}

return typeof maybeAsyncIterable[SYMBOL_ASYNC_ITERATOR] === 'function';
}
12 changes: 7 additions & 5 deletions src/utilities/astFromValue.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// @flow strict

import { forEach, isCollection } from 'iterall';

import arrayFrom from '../polyfills/arrayFrom';
import objectValues from '../polyfills/objectValues';

import inspect from '../jsutils/inspect';
import invariant from '../jsutils/invariant';
import isNullish from '../jsutils/isNullish';
import isInvalid from '../jsutils/isInvalid';
import isObjectLike from '../jsutils/isObjectLike';
import isCollection from '../jsutils/isCollection';

import { Kind } from '../language/kinds';
import { type ValueNode } from '../language/ast';
Expand Down Expand Up @@ -69,12 +69,14 @@ export function astFromValue(value: mixed, type: GraphQLInputType): ?ValueNode {
const itemType = type.ofType;
if (isCollection(value)) {
const valuesNodes = [];
forEach((value: any), item => {
// Since we transpile for-of in loose mode it doesn't support iterators
// and it's required to first convert iteratable into array
for (const item of arrayFrom(value)) {
const itemNode = astFromValue(item, itemType);
if (itemNode) {
if (itemNode != null) {
valuesNodes.push(itemNode);
}
});
}
return { kind: Kind.LIST, values: valuesNodes };
}
return astFromValue(value, itemType);
Expand Down
18 changes: 5 additions & 13 deletions src/utilities/coerceInputValue.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// @flow strict

import { forEach, isCollection } from 'iterall';

import arrayFrom from '../polyfills/arrayFrom';
import objectValues from '../polyfills/objectValues';

import inspect from '../jsutils/inspect';
import invariant from '../jsutils/invariant';
import didYouMean from '../jsutils/didYouMean';
import isObjectLike from '../jsutils/isObjectLike';
import isCollection from '../jsutils/isCollection';
import suggestionList from '../jsutils/suggestionList';
import printPathArray from '../jsutils/printPathArray';
import { type Path, addPath, pathToArray } from '../jsutils/Path';
Expand Down Expand Up @@ -79,18 +79,10 @@ function coerceInputValueImpl(
if (isListType(type)) {
const itemType = type.ofType;
if (isCollection(inputValue)) {
const coercedValue = [];
forEach((inputValue: any), (itemValue, index) => {
coercedValue.push(
coerceInputValueImpl(
itemValue,
itemType,
onError,
addPath(path, index),
),
);
return arrayFrom(inputValue, (itemValue, index) => {
const itemPath = addPath(path, index);
return coerceInputValueImpl(itemValue, itemType, onError, itemPath);
});
return coercedValue;
}
// Lists accept a non-list value as a list of one.
return [coerceInputValueImpl(inputValue, itemType, onError, path)];
Expand Down
5 changes: 0 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2426,11 +2426,6 @@ iterable-to-stream@^1.0.1:
resolved "https://registry.yarnpkg.com/iterable-to-stream/-/iterable-to-stream-1.0.1.tgz#37e86baacf6b1a0e9233dad4eb526d0423d08bf3"
integrity sha512-O62gD5ADMUGtJoOoM9U6LQ7i4byPXUNoHJ6mqsmkQJcom331ZJGDApWgDESWyBMEHEJRjtHozgIiTzYo9RU4UA==

iterall@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea"
integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==

js-levenshtein@^1.1.3:
version "1.1.6"
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
Expand Down