Initial Save

This commit is contained in:
jackbeeby
2025-03-28 12:30:19 +11:00
parent e381994f19
commit d8773925e8
9910 changed files with 982718 additions and 0 deletions

View File

@@ -0,0 +1,274 @@
import gql from 'graphql-tag';
import { cloneDeep } from 'lodash';
import { shouldInclude, hasDirectives } from '../directives';
import { getQueryDefinition } from '../getFromAST';
describe('hasDirective', () => {
it('should allow searching the ast for a directive', () => {
const query = gql`
query Simple {
field @live
}
`;
expect(hasDirectives(['live'], query)).toBe(true);
expect(hasDirectives(['defer'], query)).toBe(false);
});
it('works for all operation types', () => {
const query = gql`
{
field @live {
subField {
hello @live
}
}
}
`;
const mutation = gql`
mutation Directive {
mutate {
field {
subField {
hello @live
}
}
}
}
`;
const subscription = gql`
subscription LiveDirective {
sub {
field {
subField {
hello @live
}
}
}
}
`;
[query, mutation, subscription].forEach(x => {
expect(hasDirectives(['live'], x)).toBe(true);
expect(hasDirectives(['defer'], x)).toBe(false);
});
});
it('works for simple fragments', () => {
const query = gql`
query Simple {
...fieldFragment
}
fragment fieldFragment on Field {
foo @live
}
`;
expect(hasDirectives(['live'], query)).toBe(true);
expect(hasDirectives(['defer'], query)).toBe(false);
});
it('works for nested fragments', () => {
const query = gql`
query Simple {
...fieldFragment1
}
fragment fieldFragment1 on Field {
bar {
baz {
...nestedFragment
}
}
}
fragment nestedFragment on Field {
foo @live
}
`;
expect(hasDirectives(['live'], query)).toBe(true);
expect(hasDirectives(['defer'], query)).toBe(false);
});
});
describe('shouldInclude', () => {
it('should should not include a skipped field', () => {
const query = gql`
query {
fortuneCookie @skip(if: true)
}
`;
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(!shouldInclude(field, {})).toBe(true);
});
it('should include an included field', () => {
const query = gql`
query {
fortuneCookie @include(if: true)
}
`;
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(shouldInclude(field, {})).toBe(true);
});
it('should not include a not include: false field', () => {
const query = gql`
query {
fortuneCookie @include(if: false)
}
`;
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(!shouldInclude(field, {})).toBe(true);
});
it('should include a skip: false field', () => {
const query = gql`
query {
fortuneCookie @skip(if: false)
}
`;
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(shouldInclude(field, {})).toBe(true);
});
it('should not include a field if skip: true and include: true', () => {
const query = gql`
query {
fortuneCookie @skip(if: true) @include(if: true)
}
`;
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(!shouldInclude(field, {})).toBe(true);
});
it('should not include a field if skip: true and include: false', () => {
const query = gql`
query {
fortuneCookie @skip(if: true) @include(if: false)
}
`;
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(!shouldInclude(field, {})).toBe(true);
});
it('should include a field if skip: false and include: true', () => {
const query = gql`
query {
fortuneCookie @skip(if: false) @include(if: true)
}
`;
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(shouldInclude(field, {})).toBe(true);
});
it('should not include a field if skip: false and include: false', () => {
const query = gql`
query {
fortuneCookie @skip(if: false) @include(if: false)
}
`;
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(!shouldInclude(field, {})).toBe(true);
});
it('should leave the original query unmodified', () => {
const query = gql`
query {
fortuneCookie @skip(if: false) @include(if: false)
}
`;
const queryClone = cloneDeep(query);
const field = getQueryDefinition(query).selectionSet.selections[0];
shouldInclude(field, {});
expect(query).toEqual(queryClone);
});
it('does not throw an error on an unsupported directive', () => {
const query = gql`
query {
fortuneCookie @dosomething(if: true)
}
`;
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(() => {
shouldInclude(field, {});
}).not.toThrow();
});
it('throws an error on an invalid argument for the skip directive', () => {
const query = gql`
query {
fortuneCookie @skip(nothing: true)
}
`;
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(() => {
shouldInclude(field, {});
}).toThrow();
});
it('throws an error on an invalid argument for the include directive', () => {
const query = gql`
query {
fortuneCookie @include(nothing: true)
}
`;
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(() => {
shouldInclude(field, {});
}).toThrow();
});
it('throws an error on an invalid variable name within a directive argument', () => {
const query = gql`
query {
fortuneCookie @include(if: $neverDefined)
}
`;
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(() => {
shouldInclude(field, {});
}).toThrow();
});
it('evaluates variables on skip fields', () => {
const query = gql`
query($shouldSkip: Boolean) {
fortuneCookie @skip(if: $shouldSkip)
}
`;
const variables = {
shouldSkip: true,
};
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(!shouldInclude(field, variables)).toBe(true);
});
it('evaluates variables on include fields', () => {
const query = gql`
query($shouldSkip: Boolean) {
fortuneCookie @include(if: $shouldInclude)
}
`;
const variables = {
shouldInclude: false,
};
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(!shouldInclude(field, variables)).toBe(true);
});
it('throws an error if the value of the argument is not a variable or boolean', () => {
const query = gql`
query {
fortuneCookie @include(if: "string")
}
`;
const field = getQueryDefinition(query).selectionSet.selections[0];
expect(() => {
shouldInclude(field, {});
}).toThrow();
});
});

View File

@@ -0,0 +1,327 @@
import { print } from 'graphql/language/printer';
import gql from 'graphql-tag';
import { disableFragmentWarnings } from 'graphql-tag';
// Turn off warnings for repeated fragment names
disableFragmentWarnings();
import { getFragmentQueryDocument } from '../fragments';
describe('getFragmentQueryDocument', () => {
it('will throw an error if there is an operation', () => {
expect(() =>
getFragmentQueryDocument(
gql`
{
a
b
c
}
`,
),
).toThrowError(
'Found a query operation. No operations are allowed when using a fragment as a query. Only fragments are allowed.',
);
expect(() =>
getFragmentQueryDocument(
gql`
query {
a
b
c
}
`,
),
).toThrowError(
'Found a query operation. No operations are allowed when using a fragment as a query. Only fragments are allowed.',
);
expect(() =>
getFragmentQueryDocument(
gql`
query Named {
a
b
c
}
`,
),
).toThrowError(
"Found a query operation named 'Named'. No operations are allowed when using a fragment as a query. Only fragments are allowed.",
);
expect(() =>
getFragmentQueryDocument(
gql`
mutation Named {
a
b
c
}
`,
),
).toThrowError(
"Found a mutation operation named 'Named'. No operations are allowed when using a fragment as a query. " +
'Only fragments are allowed.',
);
expect(() =>
getFragmentQueryDocument(
gql`
subscription Named {
a
b
c
}
`,
),
).toThrowError(
"Found a subscription operation named 'Named'. No operations are allowed when using a fragment as a query. " +
'Only fragments are allowed.',
);
});
it('will throw an error if there is not exactly one fragment but no `fragmentName`', () => {
expect(() => {
getFragmentQueryDocument(gql`
fragment foo on Foo {
a
b
c
}
fragment bar on Bar {
d
e
f
}
`);
}).toThrowError(
'Found 2 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.',
);
expect(() => {
getFragmentQueryDocument(gql`
fragment foo on Foo {
a
b
c
}
fragment bar on Bar {
d
e
f
}
fragment baz on Baz {
g
h
i
}
`);
}).toThrowError(
'Found 3 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.',
);
expect(() => {
getFragmentQueryDocument(gql`
scalar Foo
`);
}).toThrowError(
'Found 0 fragments. `fragmentName` must be provided when there is not exactly 1 fragment.',
);
});
it('will create a query document where the single fragment is spread in the root query', () => {
expect(
print(
getFragmentQueryDocument(gql`
fragment foo on Foo {
a
b
c
}
`),
),
).toEqual(
print(gql`
{
...foo
}
fragment foo on Foo {
a
b
c
}
`),
);
});
it('will create a query document where the named fragment is spread in the root query', () => {
expect(
print(
getFragmentQueryDocument(
gql`
fragment foo on Foo {
a
b
c
}
fragment bar on Bar {
d
e
f
...foo
}
fragment baz on Baz {
g
h
i
...foo
...bar
}
`,
'foo',
),
),
).toEqual(
print(gql`
{
...foo
}
fragment foo on Foo {
a
b
c
}
fragment bar on Bar {
d
e
f
...foo
}
fragment baz on Baz {
g
h
i
...foo
...bar
}
`),
);
expect(
print(
getFragmentQueryDocument(
gql`
fragment foo on Foo {
a
b
c
}
fragment bar on Bar {
d
e
f
...foo
}
fragment baz on Baz {
g
h
i
...foo
...bar
}
`,
'bar',
),
),
).toEqual(
print(gql`
{
...bar
}
fragment foo on Foo {
a
b
c
}
fragment bar on Bar {
d
e
f
...foo
}
fragment baz on Baz {
g
h
i
...foo
...bar
}
`),
);
expect(
print(
getFragmentQueryDocument(
gql`
fragment foo on Foo {
a
b
c
}
fragment bar on Bar {
d
e
f
...foo
}
fragment baz on Baz {
g
h
i
...foo
...bar
}
`,
'baz',
),
),
).toEqual(
print(gql`
{
...baz
}
fragment foo on Foo {
a
b
c
}
fragment bar on Bar {
d
e
f
...foo
}
fragment baz on Baz {
g
h
i
...foo
...bar
}
`),
);
});
});

View File

@@ -0,0 +1,316 @@
import { print } from 'graphql/language/printer';
import gql from 'graphql-tag';
import { FragmentDefinitionNode, OperationDefinitionNode } from 'graphql';
import {
checkDocument,
getFragmentDefinitions,
getQueryDefinition,
getMutationDefinition,
createFragmentMap,
FragmentMap,
getDefaultValues,
getOperationName,
} from '../getFromAST';
describe('AST utility functions', () => {
it('should correctly check a document for correctness', () => {
const multipleQueries = gql`
query {
author {
firstName
lastName
}
}
query {
author {
address
}
}
`;
expect(() => {
checkDocument(multipleQueries);
}).toThrow();
const namedFragment = gql`
query {
author {
...authorDetails
}
}
fragment authorDetails on Author {
firstName
lastName
}
`;
expect(() => {
checkDocument(namedFragment);
}).not.toThrow();
});
it('should get fragment definitions from a document containing a single fragment', () => {
const singleFragmentDefinition = gql`
query {
author {
...authorDetails
}
}
fragment authorDetails on Author {
firstName
lastName
}
`;
const expectedDoc = gql`
fragment authorDetails on Author {
firstName
lastName
}
`;
const expectedResult: FragmentDefinitionNode[] = [
expectedDoc.definitions[0] as FragmentDefinitionNode,
];
const actualResult = getFragmentDefinitions(singleFragmentDefinition);
expect(actualResult.length).toEqual(expectedResult.length);
expect(print(actualResult[0])).toBe(print(expectedResult[0]));
});
it('should get fragment definitions from a document containing a multiple fragments', () => {
const multipleFragmentDefinitions = gql`
query {
author {
...authorDetails
...moreAuthorDetails
}
}
fragment authorDetails on Author {
firstName
lastName
}
fragment moreAuthorDetails on Author {
address
}
`;
const expectedDoc = gql`
fragment authorDetails on Author {
firstName
lastName
}
fragment moreAuthorDetails on Author {
address
}
`;
const expectedResult: FragmentDefinitionNode[] = [
expectedDoc.definitions[0] as FragmentDefinitionNode,
expectedDoc.definitions[1] as FragmentDefinitionNode,
];
const actualResult = getFragmentDefinitions(multipleFragmentDefinitions);
expect(actualResult.map(print)).toEqual(expectedResult.map(print));
});
it('should get the correct query definition out of a query containing multiple fragments', () => {
const queryWithFragments = gql`
fragment authorDetails on Author {
firstName
lastName
}
fragment moreAuthorDetails on Author {
address
}
query {
author {
...authorDetails
...moreAuthorDetails
}
}
`;
const expectedDoc = gql`
query {
author {
...authorDetails
...moreAuthorDetails
}
}
`;
const expectedResult: OperationDefinitionNode = expectedDoc
.definitions[0] as OperationDefinitionNode;
const actualResult = getQueryDefinition(queryWithFragments);
expect(print(actualResult)).toEqual(print(expectedResult));
});
it('should throw if we try to get the query definition of a document with no query', () => {
const mutationWithFragments = gql`
fragment authorDetails on Author {
firstName
lastName
}
mutation {
createAuthor(firstName: "John", lastName: "Smith") {
...authorDetails
}
}
`;
expect(() => {
getQueryDefinition(mutationWithFragments);
}).toThrow();
});
it('should get the correct mutation definition out of a mutation with multiple fragments', () => {
const mutationWithFragments = gql`
mutation {
createAuthor(firstName: "John", lastName: "Smith") {
...authorDetails
}
}
fragment authorDetails on Author {
firstName
lastName
}
`;
const expectedDoc = gql`
mutation {
createAuthor(firstName: "John", lastName: "Smith") {
...authorDetails
}
}
`;
const expectedResult: OperationDefinitionNode = expectedDoc
.definitions[0] as OperationDefinitionNode;
const actualResult = getMutationDefinition(mutationWithFragments);
expect(print(actualResult)).toEqual(print(expectedResult));
});
it('should create the fragment map correctly', () => {
const fragments = getFragmentDefinitions(gql`
fragment authorDetails on Author {
firstName
lastName
}
fragment moreAuthorDetails on Author {
address
}
`);
const fragmentMap = createFragmentMap(fragments);
const expectedTable: FragmentMap = {
authorDetails: fragments[0],
moreAuthorDetails: fragments[1],
};
expect(fragmentMap).toEqual(expectedTable);
});
it('should return an empty fragment map if passed undefined argument', () => {
expect(createFragmentMap(undefined)).toEqual({});
});
it('should get the operation name out of a query', () => {
const query = gql`
query nameOfQuery {
fortuneCookie
}
`;
const operationName = getOperationName(query);
expect(operationName).toEqual('nameOfQuery');
});
it('should get the operation name out of a mutation', () => {
const query = gql`
mutation nameOfMutation {
fortuneCookie
}
`;
const operationName = getOperationName(query);
expect(operationName).toEqual('nameOfMutation');
});
it('should return null if the query does not have an operation name', () => {
const query = gql`
{
fortuneCookie
}
`;
const operationName = getOperationName(query);
expect(operationName).toEqual(null);
});
it('should throw if type definitions found in document', () => {
const queryWithTypeDefination = gql`
fragment authorDetails on Author {
firstName
lastName
}
query($search: AuthorSearchInputType) {
author(search: $search) {
...authorDetails
}
}
input AuthorSearchInputType {
firstName: String
}
`;
expect(() => {
getQueryDefinition(queryWithTypeDefination);
}).toThrowError(
'Schema type definitions not allowed in queries. Found: "InputObjectTypeDefinition"',
);
});
describe('getDefaultValues', () => {
it('will create an empty variable object if no default values are provided', () => {
const basicQuery = gql`
query people($first: Int, $second: String) {
allPeople(first: $first) {
people {
name
}
}
}
`;
expect(getDefaultValues(getQueryDefinition(basicQuery))).toEqual({});
});
it('will create a variable object based on the definition node with default values', () => {
const basicQuery = gql`
query people($first: Int = 1, $second: String!) {
allPeople(first: $first) {
people {
name
}
}
}
`;
const complexMutation = gql`
mutation complexStuff(
$test: Input = { key1: ["value", "value2"], key2: { key3: 4 } }
) {
complexStuff(test: $test) {
people {
name
}
}
}
`;
expect(getDefaultValues(getQueryDefinition(basicQuery))).toEqual({
first: 1,
});
expect(getDefaultValues(getMutationDefinition(complexMutation))).toEqual({
test: { key1: ['value', 'value2'], key2: { key3: 4 } },
});
});
});
});

View File

@@ -0,0 +1,23 @@
import { getStoreKeyName } from '../storeUtils';
describe('getStoreKeyName', () => {
it(
'should return a deterministic version of the store key name no matter ' +
'which order the args object properties are in',
() => {
const validStoreKeyName =
'someField({"prop1":"value1","prop2":"value2"})';
let generatedStoreKeyName = getStoreKeyName('someField', {
prop1: 'value1',
prop2: 'value2',
});
expect(generatedStoreKeyName).toEqual(validStoreKeyName);
generatedStoreKeyName = getStoreKeyName('someField', {
prop2: 'value2',
prop1: 'value1',
});
expect(generatedStoreKeyName).toEqual(validStoreKeyName);
},
);
});

1242
node_modules/apollo-utilities/src/__tests__/transform.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff

1
node_modules/apollo-utilities/src/declarations.d.ts generated vendored Normal file
View File

@@ -0,0 +1 @@
declare module 'fast-json-stable-stringify';

127
node_modules/apollo-utilities/src/directives.ts generated vendored Normal file
View File

@@ -0,0 +1,127 @@
// Provides the methods that allow QueryManager to handle the `skip` and
// `include` directives within GraphQL.
import {
FieldNode,
SelectionNode,
VariableNode,
BooleanValueNode,
DirectiveNode,
DocumentNode,
ArgumentNode,
ValueNode,
} from 'graphql';
import { visit } from 'graphql/language/visitor';
import { invariant } from 'ts-invariant';
import { argumentsObjectFromField } from './storeUtils';
export type DirectiveInfo = {
[fieldName: string]: { [argName: string]: any };
};
export function getDirectiveInfoFromField(
field: FieldNode,
variables: Object,
): DirectiveInfo {
if (field.directives && field.directives.length) {
const directiveObj: DirectiveInfo = {};
field.directives.forEach((directive: DirectiveNode) => {
directiveObj[directive.name.value] = argumentsObjectFromField(
directive,
variables,
);
});
return directiveObj;
}
return null;
}
export function shouldInclude(
selection: SelectionNode,
variables: { [name: string]: any } = {},
): boolean {
return getInclusionDirectives(
selection.directives,
).every(({ directive, ifArgument }) => {
let evaledValue: boolean = false;
if (ifArgument.value.kind === 'Variable') {
evaledValue = variables[(ifArgument.value as VariableNode).name.value];
invariant(
evaledValue !== void 0,
`Invalid variable referenced in @${directive.name.value} directive.`,
);
} else {
evaledValue = (ifArgument.value as BooleanValueNode).value;
}
return directive.name.value === 'skip' ? !evaledValue : evaledValue;
});
}
export function getDirectiveNames(doc: DocumentNode) {
const names: string[] = [];
visit(doc, {
Directive(node) {
names.push(node.name.value);
},
});
return names;
}
export function hasDirectives(names: string[], doc: DocumentNode) {
return getDirectiveNames(doc).some(
(name: string) => names.indexOf(name) > -1,
);
}
export function hasClientExports(document: DocumentNode) {
return (
document &&
hasDirectives(['client'], document) &&
hasDirectives(['export'], document)
);
}
export type InclusionDirectives = Array<{
directive: DirectiveNode;
ifArgument: ArgumentNode;
}>;
function isInclusionDirective({ name: { value } }: DirectiveNode): boolean {
return value === 'skip' || value === 'include';
}
export function getInclusionDirectives(
directives: ReadonlyArray<DirectiveNode>,
): InclusionDirectives {
return directives ? directives.filter(isInclusionDirective).map(directive => {
const directiveArguments = directive.arguments;
const directiveName = directive.name.value;
invariant(
directiveArguments && directiveArguments.length === 1,
`Incorrect number of arguments for the @${directiveName} directive.`,
);
const ifArgument = directiveArguments[0];
invariant(
ifArgument.name && ifArgument.name.value === 'if',
`Invalid argument for the @${directiveName} directive.`,
);
const ifValue: ValueNode = ifArgument.value;
// means it has to be a variable value if this is a valid @skip or @include directive
invariant(
ifValue &&
(ifValue.kind === 'Variable' || ifValue.kind === 'BooleanValue'),
`Argument for the @${directiveName} directive must be a variable or a boolean value.`,
);
return { directive, ifArgument };
}) : [];
}

92
node_modules/apollo-utilities/src/fragments.ts generated vendored Normal file
View File

@@ -0,0 +1,92 @@
import { DocumentNode, FragmentDefinitionNode } from 'graphql';
import { invariant, InvariantError } from 'ts-invariant';
/**
* Returns a query document which adds a single query operation that only
* spreads the target fragment inside of it.
*
* So for example a document of:
*
* ```graphql
* fragment foo on Foo { a b c }
* ```
*
* Turns into:
*
* ```graphql
* { ...foo }
*
* fragment foo on Foo { a b c }
* ```
*
* The target fragment will either be the only fragment in the document, or a
* fragment specified by the provided `fragmentName`. If there is more than one
* fragment, but a `fragmentName` was not defined then an error will be thrown.
*/
export function getFragmentQueryDocument(
document: DocumentNode,
fragmentName?: string,
): DocumentNode {
let actualFragmentName = fragmentName;
// Build an array of all our fragment definitions that will be used for
// validations. We also do some validations on the other definitions in the
// document while building this list.
const fragments: Array<FragmentDefinitionNode> = [];
document.definitions.forEach(definition => {
// Throw an error if we encounter an operation definition because we will
// define our own operation definition later on.
if (definition.kind === 'OperationDefinition') {
throw new InvariantError(
`Found a ${definition.operation} operation${
definition.name ? ` named '${definition.name.value}'` : ''
}. ` +
'No operations are allowed when using a fragment as a query. Only fragments are allowed.',
);
}
// Add our definition to the fragments array if it is a fragment
// definition.
if (definition.kind === 'FragmentDefinition') {
fragments.push(definition);
}
});
// If the user did not give us a fragment name then let us try to get a
// name from a single fragment in the definition.
if (typeof actualFragmentName === 'undefined') {
invariant(
fragments.length === 1,
`Found ${
fragments.length
} fragments. \`fragmentName\` must be provided when there is not exactly 1 fragment.`,
);
actualFragmentName = fragments[0].name.value;
}
// Generate a query document with an operation that simply spreads the
// fragment inside of it.
const query: DocumentNode = {
...document,
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: {
kind: 'Name',
value: actualFragmentName,
},
},
],
},
},
...document.definitions,
],
};
return query;
}

233
node_modules/apollo-utilities/src/getFromAST.ts generated vendored Normal file
View File

@@ -0,0 +1,233 @@
import {
DocumentNode,
OperationDefinitionNode,
FragmentDefinitionNode,
ValueNode,
} from 'graphql';
import { invariant, InvariantError } from 'ts-invariant';
import { assign } from './util/assign';
import { valueToObjectRepresentation, JsonValue } from './storeUtils';
export function getMutationDefinition(
doc: DocumentNode,
): OperationDefinitionNode {
checkDocument(doc);
let mutationDef: OperationDefinitionNode | null = doc.definitions.filter(
definition =>
definition.kind === 'OperationDefinition' &&
definition.operation === 'mutation',
)[0] as OperationDefinitionNode;
invariant(mutationDef, 'Must contain a mutation definition.');
return mutationDef;
}
// Checks the document for errors and throws an exception if there is an error.
export function checkDocument(doc: DocumentNode) {
invariant(
doc && doc.kind === 'Document',
`Expecting a parsed GraphQL document. Perhaps you need to wrap the query \
string in a "gql" tag? http://docs.apollostack.com/apollo-client/core.html#gql`,
);
const operations = doc.definitions
.filter(d => d.kind !== 'FragmentDefinition')
.map(definition => {
if (definition.kind !== 'OperationDefinition') {
throw new InvariantError(
`Schema type definitions not allowed in queries. Found: "${
definition.kind
}"`,
);
}
return definition;
});
invariant(
operations.length <= 1,
`Ambiguous GraphQL document: contains ${operations.length} operations`,
);
return doc;
}
export function getOperationDefinition(
doc: DocumentNode,
): OperationDefinitionNode | undefined {
checkDocument(doc);
return doc.definitions.filter(
definition => definition.kind === 'OperationDefinition',
)[0] as OperationDefinitionNode;
}
export function getOperationDefinitionOrDie(
document: DocumentNode,
): OperationDefinitionNode {
const def = getOperationDefinition(document);
invariant(def, `GraphQL document is missing an operation`);
return def;
}
export function getOperationName(doc: DocumentNode): string | null {
return (
doc.definitions
.filter(
definition =>
definition.kind === 'OperationDefinition' && definition.name,
)
.map((x: OperationDefinitionNode) => x.name.value)[0] || null
);
}
// Returns the FragmentDefinitions from a particular document as an array
export function getFragmentDefinitions(
doc: DocumentNode,
): FragmentDefinitionNode[] {
return doc.definitions.filter(
definition => definition.kind === 'FragmentDefinition',
) as FragmentDefinitionNode[];
}
export function getQueryDefinition(doc: DocumentNode): OperationDefinitionNode {
const queryDef = getOperationDefinition(doc) as OperationDefinitionNode;
invariant(
queryDef && queryDef.operation === 'query',
'Must contain a query definition.',
);
return queryDef;
}
export function getFragmentDefinition(
doc: DocumentNode,
): FragmentDefinitionNode {
invariant(
doc.kind === 'Document',
`Expecting a parsed GraphQL document. Perhaps you need to wrap the query \
string in a "gql" tag? http://docs.apollostack.com/apollo-client/core.html#gql`,
);
invariant(
doc.definitions.length <= 1,
'Fragment must have exactly one definition.',
);
const fragmentDef = doc.definitions[0] as FragmentDefinitionNode;
invariant(
fragmentDef.kind === 'FragmentDefinition',
'Must be a fragment definition.',
);
return fragmentDef as FragmentDefinitionNode;
}
/**
* Returns the first operation definition found in this document.
* If no operation definition is found, the first fragment definition will be returned.
* If no definitions are found, an error will be thrown.
*/
export function getMainDefinition(
queryDoc: DocumentNode,
): OperationDefinitionNode | FragmentDefinitionNode {
checkDocument(queryDoc);
let fragmentDefinition;
for (let definition of queryDoc.definitions) {
if (definition.kind === 'OperationDefinition') {
const operation = (definition as OperationDefinitionNode).operation;
if (
operation === 'query' ||
operation === 'mutation' ||
operation === 'subscription'
) {
return definition as OperationDefinitionNode;
}
}
if (definition.kind === 'FragmentDefinition' && !fragmentDefinition) {
// we do this because we want to allow multiple fragment definitions
// to precede an operation definition.
fragmentDefinition = definition as FragmentDefinitionNode;
}
}
if (fragmentDefinition) {
return fragmentDefinition;
}
throw new InvariantError(
'Expected a parsed GraphQL query with a query, mutation, subscription, or a fragment.',
);
}
/**
* This is an interface that describes a map from fragment names to fragment definitions.
*/
export interface FragmentMap {
[fragmentName: string]: FragmentDefinitionNode;
}
// Utility function that takes a list of fragment definitions and makes a hash out of them
// that maps the name of the fragment to the fragment definition.
export function createFragmentMap(
fragments: FragmentDefinitionNode[] = [],
): FragmentMap {
const symTable: FragmentMap = {};
fragments.forEach(fragment => {
symTable[fragment.name.value] = fragment;
});
return symTable;
}
export function getDefaultValues(
definition: OperationDefinitionNode | undefined,
): { [key: string]: JsonValue } {
if (
definition &&
definition.variableDefinitions &&
definition.variableDefinitions.length
) {
const defaultValues = definition.variableDefinitions
.filter(({ defaultValue }) => defaultValue)
.map(
({ variable, defaultValue }): { [key: string]: JsonValue } => {
const defaultValueObj: { [key: string]: JsonValue } = {};
valueToObjectRepresentation(
defaultValueObj,
variable.name,
defaultValue as ValueNode,
);
return defaultValueObj;
},
);
return assign({}, ...defaultValues);
}
return {};
}
/**
* Returns the names of all variables declared by the operation.
*/
export function variablesInOperation(
operation: OperationDefinitionNode,
): Set<string> {
const names = new Set<string>();
if (operation.variableDefinitions) {
for (const definition of operation.variableDefinitions) {
names.add(definition.variable.name.value);
}
}
return names;
}

220
node_modules/apollo-utilities/src/index.js.flow generated vendored Normal file
View File

@@ -0,0 +1,220 @@
// @flow
import type {
DirectiveNode,
StringValueNode,
BooleanValueNode,
IntValueNode,
FloatValueNode,
EnumValueNode,
VariableNode,
FieldNode,
FragmentDefinitionNode,
OperationDefinitionNode,
SelectionNode,
DocumentNode,
DefinitionNode,
ValueNode,
NameNode,
} from 'graphql';
declare module 'apollo-utilities' {
// storeUtils
declare export interface IdValue {
type: 'id';
id: string;
generated: boolean;
typename: string | void;
}
declare export interface JsonValue {
type: 'json';
json: any;
}
declare export type ListValue = Array<?IdValue>;
declare export type StoreValue =
| number
| string
| Array<string>
| IdValue
| ListValue
| JsonValue
| null
| void
| Object;
declare export type ScalarValue =
| StringValueNode
| BooleanValueNode
| EnumValueNode;
declare export type NumberValue = IntValueNode | FloatValueNode;
declare type isValueFn = (value: ValueNode) => boolean;
declare export type isScalarValue = isValueFn;
declare export type isNumberValue = isValueFn;
declare export type isStringValue = isValueFn;
declare export type isBooleanValue = isValueFn;
declare export type isIntValue = isValueFn;
declare export type isFloatValue = isValueFn;
declare export type isVariable = isValueFn;
declare export type isObjectValue = isValueFn;
declare export type isListValue = isValueFn;
declare export type isEnumValue = isValueFn;
declare export function valueToObjectRepresentation(
argObj: any,
name: NameNode,
value: ValueNode,
variables?: Object,
): any;
declare export function storeKeyNameFromField(
field: FieldNode,
variables?: Object,
): string;
declare export type Directives = {
[directiveName: string]: {
[argName: string]: any,
},
};
declare export function getStoreKeyName(
fieldName: string,
args?: Object,
directives?: Directives,
): string;
declare export function argumentsObjectFromField(
field: FieldNode | DirectiveNode,
variables: Object,
): ?Object;
declare export function resultKeyNameFromField(field: FieldNode): string;
declare export function isField(selection: SelectionNode): boolean;
declare export function isInlineFragment(selection: SelectionNode): boolean;
declare export function isIdValue(idObject: StoreValue): boolean;
declare export type IdConfig = {
id: string,
typename: string | void,
};
declare export function toIdValue(
id: string | IdConfig,
generated?: boolean,
): IdValue;
declare export function isJsonValue(jsonObject: StoreValue): boolean;
declare export type VariableValue = (node: VariableNode) => any;
declare export function valueFromNode(
node: ValueNode,
onVariable: VariableValue,
): any;
// getFromAST
declare export function getMutationDefinition(
doc: DocumentNode,
): OperationDefinitionNode;
declare export function checkDocument(doc: DocumentNode): void;
declare export function getOperationDefinition(
doc: DocumentNode,
): ?OperationDefinitionNode;
declare export function getOperationDefinitionOrDie(
document: DocumentNode,
): OperationDefinitionNode;
declare export function getOperationName(doc: DocumentNode): ?string;
declare export function getFragmentDefinitions(
doc: DocumentNode,
): Array<FragmentDefinitionNode>;
declare export function getQueryDefinition(
doc: DocumentNode,
): OperationDefinitionNode;
declare export function getFragmentDefinition(
doc: DocumentNode,
): FragmentDefinitionNode;
declare export function getMainDefinition(
queryDoc: DocumentNode,
): OperationDefinitionNode | FragmentDefinitionNode;
declare export interface FragmentMap {
[fragmentName: string]: FragmentDefinitionNode;
}
declare export function createFragmentMap(
fragments: Array<FragmentDefinitionNode>,
): FragmentMap;
declare export function getDefaultValues(
definition: ?OperationDefinitionNode,
): { [key: string]: JsonValue };
// fragments
declare export function getFragmentQueryDocument(
document: DocumentNode,
fragmentName?: string,
): DocumentNode;
declare export function variablesInOperation(
operation: OperationDefinitionNode,
): Set<string>;
// directives
declare type DirectiveInfo = {
[fieldName: string]: { [argName: string]: any },
};
declare export function getDirectiveInfoFromField(
field: FieldNode,
object: Object,
): ?DirectiveInfo;
declare export function shouldInclude(
selection: SelectionNode,
variables: { [name: string]: any },
): boolean;
declare export function flattenSelections(
selection: SelectionNode,
): Array<SelectionNode>;
declare export function getDirectiveNames(doc: DocumentNode): Array<string>;
declare export function hasDirectives(
names: Array<string>,
doc: DocumentNode,
): boolean;
// transform
declare export type RemoveDirectiveConfig = {
name?: string,
test?: (directive: DirectiveNode) => boolean,
};
declare export function removeDirectivesFromDocument(
directives: Array<RemoveDirectiveConfig>,
doc: DocumentNode,
): DocumentNode;
declare export function addTypenameToDocument(doc: DocumentNode): void;
declare export function removeConnectionDirectiveFromDocument(
doc: DocumentNode,
): DocumentNode;
}

16
node_modules/apollo-utilities/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,16 @@
export * from './directives';
export * from './fragments';
export * from './getFromAST';
export * from './transform';
export * from './storeUtils';
export * from './util/assign';
export * from './util/canUse';
export * from './util/cloneDeep';
export * from './util/environment';
export * from './util/errorHandling';
export * from './util/isEqual';
export * from './util/maybeDeepFreeze';
export * from './util/mergeDeep';
export * from './util/warnOnce';
export * from './util/stripSymbols';
export * from './util/mergeDeep';

340
node_modules/apollo-utilities/src/storeUtils.ts generated vendored Normal file
View File

@@ -0,0 +1,340 @@
import {
DirectiveNode,
FieldNode,
IntValueNode,
FloatValueNode,
StringValueNode,
BooleanValueNode,
ObjectValueNode,
ListValueNode,
EnumValueNode,
NullValueNode,
VariableNode,
InlineFragmentNode,
ValueNode,
SelectionNode,
NameNode,
} from 'graphql';
import stringify from 'fast-json-stable-stringify';
import { InvariantError } from 'ts-invariant';
export interface IdValue {
type: 'id';
id: string;
generated: boolean;
typename: string | undefined;
}
export interface JsonValue {
type: 'json';
json: any;
}
export type ListValue = Array<null | IdValue>;
export type StoreValue =
| number
| string
| string[]
| IdValue
| ListValue
| JsonValue
| null
| undefined
| void
| Object;
export type ScalarValue = StringValueNode | BooleanValueNode | EnumValueNode;
export function isScalarValue(value: ValueNode): value is ScalarValue {
return ['StringValue', 'BooleanValue', 'EnumValue'].indexOf(value.kind) > -1;
}
export type NumberValue = IntValueNode | FloatValueNode;
export function isNumberValue(value: ValueNode): value is NumberValue {
return ['IntValue', 'FloatValue'].indexOf(value.kind) > -1;
}
function isStringValue(value: ValueNode): value is StringValueNode {
return value.kind === 'StringValue';
}
function isBooleanValue(value: ValueNode): value is BooleanValueNode {
return value.kind === 'BooleanValue';
}
function isIntValue(value: ValueNode): value is IntValueNode {
return value.kind === 'IntValue';
}
function isFloatValue(value: ValueNode): value is FloatValueNode {
return value.kind === 'FloatValue';
}
function isVariable(value: ValueNode): value is VariableNode {
return value.kind === 'Variable';
}
function isObjectValue(value: ValueNode): value is ObjectValueNode {
return value.kind === 'ObjectValue';
}
function isListValue(value: ValueNode): value is ListValueNode {
return value.kind === 'ListValue';
}
function isEnumValue(value: ValueNode): value is EnumValueNode {
return value.kind === 'EnumValue';
}
function isNullValue(value: ValueNode): value is NullValueNode {
return value.kind === 'NullValue';
}
export function valueToObjectRepresentation(
argObj: any,
name: NameNode,
value: ValueNode,
variables?: Object,
) {
if (isIntValue(value) || isFloatValue(value)) {
argObj[name.value] = Number(value.value);
} else if (isBooleanValue(value) || isStringValue(value)) {
argObj[name.value] = value.value;
} else if (isObjectValue(value)) {
const nestedArgObj = {};
value.fields.map(obj =>
valueToObjectRepresentation(nestedArgObj, obj.name, obj.value, variables),
);
argObj[name.value] = nestedArgObj;
} else if (isVariable(value)) {
const variableValue = (variables || ({} as any))[value.name.value];
argObj[name.value] = variableValue;
} else if (isListValue(value)) {
argObj[name.value] = value.values.map(listValue => {
const nestedArgArrayObj = {};
valueToObjectRepresentation(
nestedArgArrayObj,
name,
listValue,
variables,
);
return (nestedArgArrayObj as any)[name.value];
});
} else if (isEnumValue(value)) {
argObj[name.value] = (value as EnumValueNode).value;
} else if (isNullValue(value)) {
argObj[name.value] = null;
} else {
throw new InvariantError(
`The inline argument "${name.value}" of kind "${(value as any).kind}"` +
'is not supported. Use variables instead of inline arguments to ' +
'overcome this limitation.',
);
}
}
export function storeKeyNameFromField(
field: FieldNode,
variables?: Object,
): string {
let directivesObj: any = null;
if (field.directives) {
directivesObj = {};
field.directives.forEach(directive => {
directivesObj[directive.name.value] = {};
if (directive.arguments) {
directive.arguments.forEach(({ name, value }) =>
valueToObjectRepresentation(
directivesObj[directive.name.value],
name,
value,
variables,
),
);
}
});
}
let argObj: any = null;
if (field.arguments && field.arguments.length) {
argObj = {};
field.arguments.forEach(({ name, value }) =>
valueToObjectRepresentation(argObj, name, value, variables),
);
}
return getStoreKeyName(field.name.value, argObj, directivesObj);
}
export type Directives = {
[directiveName: string]: {
[argName: string]: any;
};
};
const KNOWN_DIRECTIVES: string[] = [
'connection',
'include',
'skip',
'client',
'rest',
'export',
];
export function getStoreKeyName(
fieldName: string,
args?: Object,
directives?: Directives,
): string {
if (
directives &&
directives['connection'] &&
directives['connection']['key']
) {
if (
directives['connection']['filter'] &&
(directives['connection']['filter'] as string[]).length > 0
) {
const filterKeys = directives['connection']['filter']
? (directives['connection']['filter'] as string[])
: [];
filterKeys.sort();
const queryArgs = args as { [key: string]: any };
const filteredArgs = {} as { [key: string]: any };
filterKeys.forEach(key => {
filteredArgs[key] = queryArgs[key];
});
return `${directives['connection']['key']}(${JSON.stringify(
filteredArgs,
)})`;
} else {
return directives['connection']['key'];
}
}
let completeFieldName: string = fieldName;
if (args) {
// We can't use `JSON.stringify` here since it's non-deterministic,
// and can lead to different store key names being created even though
// the `args` object used during creation has the same properties/values.
const stringifiedArgs: string = stringify(args);
completeFieldName += `(${stringifiedArgs})`;
}
if (directives) {
Object.keys(directives).forEach(key => {
if (KNOWN_DIRECTIVES.indexOf(key) !== -1) return;
if (directives[key] && Object.keys(directives[key]).length) {
completeFieldName += `@${key}(${JSON.stringify(directives[key])})`;
} else {
completeFieldName += `@${key}`;
}
});
}
return completeFieldName;
}
export function argumentsObjectFromField(
field: FieldNode | DirectiveNode,
variables: Object,
): Object {
if (field.arguments && field.arguments.length) {
const argObj: Object = {};
field.arguments.forEach(({ name, value }) =>
valueToObjectRepresentation(argObj, name, value, variables),
);
return argObj;
}
return null;
}
export function resultKeyNameFromField(field: FieldNode): string {
return field.alias ? field.alias.value : field.name.value;
}
export function isField(selection: SelectionNode): selection is FieldNode {
return selection.kind === 'Field';
}
export function isInlineFragment(
selection: SelectionNode,
): selection is InlineFragmentNode {
return selection.kind === 'InlineFragment';
}
export function isIdValue(idObject: StoreValue): idObject is IdValue {
return idObject &&
(idObject as IdValue | JsonValue).type === 'id' &&
typeof (idObject as IdValue).generated === 'boolean';
}
export type IdConfig = {
id: string;
typename: string | undefined;
};
export function toIdValue(
idConfig: string | IdConfig,
generated = false,
): IdValue {
return {
type: 'id',
generated,
...(typeof idConfig === 'string'
? { id: idConfig, typename: undefined }
: idConfig),
};
}
export function isJsonValue(jsonObject: StoreValue): jsonObject is JsonValue {
return (
jsonObject != null &&
typeof jsonObject === 'object' &&
(jsonObject as IdValue | JsonValue).type === 'json'
);
}
function defaultValueFromVariable(node: VariableNode) {
throw new InvariantError(`Variable nodes are not supported by valueFromNode`);
}
export type VariableValue = (node: VariableNode) => any;
/**
* Evaluate a ValueNode and yield its value in its natural JS form.
*/
export function valueFromNode(
node: ValueNode,
onVariable: VariableValue = defaultValueFromVariable,
): any {
switch (node.kind) {
case 'Variable':
return onVariable(node);
case 'NullValue':
return null;
case 'IntValue':
return parseInt(node.value, 10);
case 'FloatValue':
return parseFloat(node.value);
case 'ListValue':
return node.values.map(v => valueFromNode(v, onVariable));
case 'ObjectValue': {
const value: { [key: string]: any } = {};
for (const field of node.fields) {
value[field.name.value] = valueFromNode(field.value, onVariable);
}
return value;
}
default:
return node.value;
}
}

542
node_modules/apollo-utilities/src/transform.ts generated vendored Normal file
View File

@@ -0,0 +1,542 @@
import {
DocumentNode,
SelectionNode,
SelectionSetNode,
OperationDefinitionNode,
FieldNode,
DirectiveNode,
FragmentDefinitionNode,
ArgumentNode,
FragmentSpreadNode,
VariableDefinitionNode,
VariableNode,
} from 'graphql';
import { visit } from 'graphql/language/visitor';
import {
checkDocument,
getOperationDefinition,
getFragmentDefinition,
getFragmentDefinitions,
createFragmentMap,
FragmentMap,
getMainDefinition,
} from './getFromAST';
import { filterInPlace } from './util/filterInPlace';
import { invariant } from 'ts-invariant';
import { isField, isInlineFragment } from './storeUtils';
export type RemoveNodeConfig<N> = {
name?: string;
test?: (node: N) => boolean;
remove?: boolean;
};
export type GetNodeConfig<N> = {
name?: string;
test?: (node: N) => boolean;
};
export type RemoveDirectiveConfig = RemoveNodeConfig<DirectiveNode>;
export type GetDirectiveConfig = GetNodeConfig<DirectiveNode>;
export type RemoveArgumentsConfig = RemoveNodeConfig<ArgumentNode>;
export type GetFragmentSpreadConfig = GetNodeConfig<FragmentSpreadNode>;
export type RemoveFragmentSpreadConfig = RemoveNodeConfig<FragmentSpreadNode>;
export type RemoveFragmentDefinitionConfig = RemoveNodeConfig<
FragmentDefinitionNode
>;
export type RemoveVariableDefinitionConfig = RemoveNodeConfig<
VariableDefinitionNode
>;
const TYPENAME_FIELD: FieldNode = {
kind: 'Field',
name: {
kind: 'Name',
value: '__typename',
},
};
function isEmpty(
op: OperationDefinitionNode | FragmentDefinitionNode,
fragments: FragmentMap,
): boolean {
return op.selectionSet.selections.every(
selection =>
selection.kind === 'FragmentSpread' &&
isEmpty(fragments[selection.name.value], fragments),
);
}
function nullIfDocIsEmpty(doc: DocumentNode) {
return isEmpty(
getOperationDefinition(doc) || getFragmentDefinition(doc),
createFragmentMap(getFragmentDefinitions(doc)),
)
? null
: doc;
}
function getDirectiveMatcher(
directives: (RemoveDirectiveConfig | GetDirectiveConfig)[],
) {
return function directiveMatcher(directive: DirectiveNode) {
return directives.some(
dir =>
(dir.name && dir.name === directive.name.value) ||
(dir.test && dir.test(directive)),
);
};
}
export function removeDirectivesFromDocument(
directives: RemoveDirectiveConfig[],
doc: DocumentNode,
): DocumentNode | null {
const variablesInUse: Record<string, boolean> = Object.create(null);
let variablesToRemove: RemoveArgumentsConfig[] = [];
const fragmentSpreadsInUse: Record<string, boolean> = Object.create(null);
let fragmentSpreadsToRemove: RemoveFragmentSpreadConfig[] = [];
let modifiedDoc = nullIfDocIsEmpty(
visit(doc, {
Variable: {
enter(node, _key, parent) {
// Store each variable that's referenced as part of an argument
// (excluding operation definition variables), so we know which
// variables are being used. If we later want to remove a variable
// we'll fist check to see if it's being used, before continuing with
// the removal.
if (
(parent as VariableDefinitionNode).kind !== 'VariableDefinition'
) {
variablesInUse[node.name.value] = true;
}
},
},
Field: {
enter(node) {
if (directives && node.directives) {
// If `remove` is set to true for a directive, and a directive match
// is found for a field, remove the field as well.
const shouldRemoveField = directives.some(
directive => directive.remove,
);
if (
shouldRemoveField &&
node.directives &&
node.directives.some(getDirectiveMatcher(directives))
) {
if (node.arguments) {
// Store field argument variables so they can be removed
// from the operation definition.
node.arguments.forEach(arg => {
if (arg.value.kind === 'Variable') {
variablesToRemove.push({
name: (arg.value as VariableNode).name.value,
});
}
});
}
if (node.selectionSet) {
// Store fragment spread names so they can be removed from the
// docuemnt.
getAllFragmentSpreadsFromSelectionSet(node.selectionSet).forEach(
frag => {
fragmentSpreadsToRemove.push({
name: frag.name.value,
});
},
);
}
// Remove the field.
return null;
}
}
},
},
FragmentSpread: {
enter(node) {
// Keep track of referenced fragment spreads. This is used to
// determine if top level fragment definitions should be removed.
fragmentSpreadsInUse[node.name.value] = true;
},
},
Directive: {
enter(node) {
// If a matching directive is found, remove it.
if (getDirectiveMatcher(directives)(node)) {
return null;
}
},
},
}),
);
// If we've removed fields with arguments, make sure the associated
// variables are also removed from the rest of the document, as long as they
// aren't being used elsewhere.
if (
modifiedDoc &&
filterInPlace(variablesToRemove, v => !variablesInUse[v.name]).length
) {
modifiedDoc = removeArgumentsFromDocument(variablesToRemove, modifiedDoc);
}
// If we've removed selection sets with fragment spreads, make sure the
// associated fragment definitions are also removed from the rest of the
// document, as long as they aren't being used elsewhere.
if (
modifiedDoc &&
filterInPlace(fragmentSpreadsToRemove, fs => !fragmentSpreadsInUse[fs.name])
.length
) {
modifiedDoc = removeFragmentSpreadFromDocument(
fragmentSpreadsToRemove,
modifiedDoc,
);
}
return modifiedDoc;
}
export function addTypenameToDocument(doc: DocumentNode): DocumentNode {
return visit(checkDocument(doc), {
SelectionSet: {
enter(node, _key, parent) {
// Don't add __typename to OperationDefinitions.
if (
parent &&
(parent as OperationDefinitionNode).kind === 'OperationDefinition'
) {
return;
}
// No changes if no selections.
const { selections } = node;
if (!selections) {
return;
}
// If selections already have a __typename, or are part of an
// introspection query, do nothing.
const skip = selections.some(selection => {
return (
isField(selection) &&
(selection.name.value === '__typename' ||
selection.name.value.lastIndexOf('__', 0) === 0)
);
});
if (skip) {
return;
}
// If this SelectionSet is @export-ed as an input variable, it should
// not have a __typename field (see issue #4691).
const field = parent as FieldNode;
if (
isField(field) &&
field.directives &&
field.directives.some(d => d.name.value === 'export')
) {
return;
}
// Create and return a new SelectionSet with a __typename Field.
return {
...node,
selections: [...selections, TYPENAME_FIELD],
};
},
},
});
}
const connectionRemoveConfig = {
test: (directive: DirectiveNode) => {
const willRemove = directive.name.value === 'connection';
if (willRemove) {
if (
!directive.arguments ||
!directive.arguments.some(arg => arg.name.value === 'key')
) {
invariant.warn(
'Removing an @connection directive even though it does not have a key. ' +
'You may want to use the key parameter to specify a store key.',
);
}
}
return willRemove;
},
};
export function removeConnectionDirectiveFromDocument(doc: DocumentNode) {
return removeDirectivesFromDocument(
[connectionRemoveConfig],
checkDocument(doc),
);
}
function hasDirectivesInSelectionSet(
directives: GetDirectiveConfig[],
selectionSet: SelectionSetNode,
nestedCheck = true,
): boolean {
return (
selectionSet &&
selectionSet.selections &&
selectionSet.selections.some(selection =>
hasDirectivesInSelection(directives, selection, nestedCheck),
)
);
}
function hasDirectivesInSelection(
directives: GetDirectiveConfig[],
selection: SelectionNode,
nestedCheck = true,
): boolean {
if (!isField(selection)) {
return true;
}
if (!selection.directives) {
return false;
}
return (
selection.directives.some(getDirectiveMatcher(directives)) ||
(nestedCheck &&
hasDirectivesInSelectionSet(
directives,
selection.selectionSet,
nestedCheck,
))
);
}
export function getDirectivesFromDocument(
directives: GetDirectiveConfig[],
doc: DocumentNode,
): DocumentNode {
checkDocument(doc);
let parentPath: string;
return nullIfDocIsEmpty(
visit(doc, {
SelectionSet: {
enter(node, _key, _parent, path) {
const currentPath = path.join('-');
if (
!parentPath ||
currentPath === parentPath ||
!currentPath.startsWith(parentPath)
) {
if (node.selections) {
const selectionsWithDirectives = node.selections.filter(
selection => hasDirectivesInSelection(directives, selection),
);
if (hasDirectivesInSelectionSet(directives, node, false)) {
parentPath = currentPath;
}
return {
...node,
selections: selectionsWithDirectives,
};
} else {
return null;
}
}
},
},
}),
);
}
function getArgumentMatcher(config: RemoveArgumentsConfig[]) {
return function argumentMatcher(argument: ArgumentNode) {
return config.some(
(aConfig: RemoveArgumentsConfig) =>
argument.value &&
argument.value.kind === 'Variable' &&
argument.value.name &&
(aConfig.name === argument.value.name.value ||
(aConfig.test && aConfig.test(argument))),
);
};
}
export function removeArgumentsFromDocument(
config: RemoveArgumentsConfig[],
doc: DocumentNode,
): DocumentNode {
const argMatcher = getArgumentMatcher(config);
return nullIfDocIsEmpty(
visit(doc, {
OperationDefinition: {
enter(node) {
return {
...node,
// Remove matching top level variables definitions.
variableDefinitions: node.variableDefinitions.filter(
varDef =>
!config.some(arg => arg.name === varDef.variable.name.value),
),
};
},
},
Field: {
enter(node) {
// If `remove` is set to true for an argument, and an argument match
// is found for a field, remove the field as well.
const shouldRemoveField = config.some(argConfig => argConfig.remove);
if (shouldRemoveField) {
let argMatchCount = 0;
node.arguments.forEach(arg => {
if (argMatcher(arg)) {
argMatchCount += 1;
}
});
if (argMatchCount === 1) {
return null;
}
}
},
},
Argument: {
enter(node) {
// Remove all matching arguments.
if (argMatcher(node)) {
return null;
}
},
},
}),
);
}
export function removeFragmentSpreadFromDocument(
config: RemoveFragmentSpreadConfig[],
doc: DocumentNode,
): DocumentNode {
function enter(
node: FragmentSpreadNode | FragmentDefinitionNode,
): null | void {
if (config.some(def => def.name === node.name.value)) {
return null;
}
}
return nullIfDocIsEmpty(
visit(doc, {
FragmentSpread: { enter },
FragmentDefinition: { enter },
}),
);
}
function getAllFragmentSpreadsFromSelectionSet(
selectionSet: SelectionSetNode,
): FragmentSpreadNode[] {
const allFragments: FragmentSpreadNode[] = [];
selectionSet.selections.forEach(selection => {
if (
(isField(selection) || isInlineFragment(selection)) &&
selection.selectionSet
) {
getAllFragmentSpreadsFromSelectionSet(selection.selectionSet).forEach(
frag => allFragments.push(frag),
);
} else if (selection.kind === 'FragmentSpread') {
allFragments.push(selection);
}
});
return allFragments;
}
// If the incoming document is a query, return it as is. Otherwise, build a
// new document containing a query operation based on the selection set
// of the previous main operation.
export function buildQueryFromSelectionSet(
document: DocumentNode,
): DocumentNode {
const definition = getMainDefinition(document);
const definitionOperation = (<OperationDefinitionNode>definition).operation;
if (definitionOperation === 'query') {
// Already a query, so return the existing document.
return document;
}
// Build a new query using the selection set of the main operation.
const modifiedDoc = visit(document, {
OperationDefinition: {
enter(node) {
return {
...node,
operation: 'query',
};
},
},
});
return modifiedDoc;
}
// Remove fields / selection sets that include an @client directive.
export function removeClientSetsFromDocument(
document: DocumentNode,
): DocumentNode | null {
checkDocument(document);
let modifiedDoc = removeDirectivesFromDocument(
[
{
test: (directive: DirectiveNode) => directive.name.value === 'client',
remove: true,
},
],
document,
);
// After a fragment definition has had its @client related document
// sets removed, if the only field it has left is a __typename field,
// remove the entire fragment operation to prevent it from being fired
// on the server.
if (modifiedDoc) {
modifiedDoc = visit(modifiedDoc, {
FragmentDefinition: {
enter(node) {
if (node.selectionSet) {
const isTypenameOnly = node.selectionSet.selections.every(
selection =>
isField(selection) && selection.name.value === '__typename',
);
if (isTypenameOnly) {
return null;
}
}
},
},
});
}
return modifiedDoc;
}

View File

@@ -0,0 +1,47 @@
import { assign } from '../assign';
describe('assign', () => {
it('will merge many objects together', () => {
expect(assign({ a: 1 }, { b: 2 })).toEqual({ a: 1, b: 2 });
expect(assign({ a: 1 }, { b: 2 }, { c: 3 })).toEqual({
a: 1,
b: 2,
c: 3,
});
expect(assign({ a: 1 }, { b: 2 }, { c: 3 }, { d: 4 })).toEqual({
a: 1,
b: 2,
c: 3,
d: 4,
});
});
it('will merge many objects together shallowly', () => {
expect(assign({ x: { a: 1 } }, { x: { b: 2 } })).toEqual({ x: { b: 2 } });
expect(assign({ x: { a: 1 } }, { x: { b: 2 } }, { x: { c: 3 } })).toEqual({
x: { c: 3 },
});
expect(
assign(
{ x: { a: 1 } },
{ x: { b: 2 } },
{ x: { c: 3 } },
{ x: { d: 4 } },
),
).toEqual({ x: { d: 4 } });
});
it('will mutate and return the source objects', () => {
const source1 = { a: 1 };
const source2 = { a: 1 };
const source3 = { a: 1 };
expect(assign(source1, { b: 2 })).toEqual(source1);
expect(assign(source2, { b: 2 }, { c: 3 })).toEqual(source2);
expect(assign(source3, { b: 2 }, { c: 3 }, { d: 4 })).toEqual(source3);
expect(source1).toEqual({ a: 1, b: 2 });
expect(source2).toEqual({ a: 1, b: 2, c: 3 });
expect(source3).toEqual({ a: 1, b: 2, c: 3, d: 4 });
});
});

View File

@@ -0,0 +1,70 @@
import { cloneDeep } from '../cloneDeep';
describe('cloneDeep', () => {
it('will clone primitive values', () => {
expect(cloneDeep(undefined)).toEqual(undefined);
expect(cloneDeep(null)).toEqual(null);
expect(cloneDeep(true)).toEqual(true);
expect(cloneDeep(false)).toEqual(false);
expect(cloneDeep(-1)).toEqual(-1);
expect(cloneDeep(+1)).toEqual(+1);
expect(cloneDeep(0.5)).toEqual(0.5);
expect(cloneDeep('hello')).toEqual('hello');
expect(cloneDeep('world')).toEqual('world');
});
it('will clone objects', () => {
const value1 = {};
const value2 = { a: 1, b: 2, c: 3 };
const value3 = { x: { a: 1, b: 2, c: 3 }, y: { a: 1, b: 2, c: 3 } };
const clonedValue1 = cloneDeep(value1);
const clonedValue2 = cloneDeep(value2);
const clonedValue3 = cloneDeep(value3);
expect(clonedValue1).toEqual(value1);
expect(clonedValue2).toEqual(value2);
expect(clonedValue3).toEqual(value3);
expect(clonedValue1).toEqual(value1);
expect(clonedValue2).toEqual(value2);
expect(clonedValue3).toEqual(value3);
expect(clonedValue3.x).toEqual(value3.x);
expect(clonedValue3.y).toEqual(value3.y);
});
it('will clone arrays', () => {
const value1: Array<number> = [];
const value2 = [1, 2, 3];
const value3 = [[1, 2, 3], [1, 2, 3]];
const clonedValue1 = cloneDeep(value1);
const clonedValue2 = cloneDeep(value2);
const clonedValue3 = cloneDeep(value3);
expect(clonedValue1).toEqual(value1);
expect(clonedValue2).toEqual(value2);
expect(clonedValue3).toEqual(value3);
expect(clonedValue1).toEqual(value1);
expect(clonedValue2).toEqual(value2);
expect(clonedValue3).toEqual(value3);
expect(clonedValue3[0]).toEqual(value3[0]);
expect(clonedValue3[1]).toEqual(value3[1]);
});
it('should not attempt to follow circular references', () => {
const someObject = {
prop1: 'value1',
anotherObject: null,
};
const anotherObject = {
someObject,
};
someObject.anotherObject = anotherObject;
let chk;
expect(() => {
chk = cloneDeep(someObject);
}).not.toThrow();
});
});

View File

@@ -0,0 +1,70 @@
import { isEnv, isProduction, isDevelopment, isTest } from '../environment';
describe('environment', () => {
let keepEnv: string | undefined;
beforeEach(() => {
// save the NODE_ENV
keepEnv = process.env.NODE_ENV;
});
afterEach(() => {
// restore the NODE_ENV
process.env.NODE_ENV = keepEnv;
});
describe('isEnv', () => {
it(`should match when there's a value`, () => {
['production', 'development', 'test'].forEach(env => {
process.env.NODE_ENV = env;
expect(isEnv(env)).toBe(true);
});
});
it(`should treat no proces.env.NODE_ENV as it'd be in development`, () => {
delete process.env.NODE_ENV;
expect(isEnv('development')).toBe(true);
});
});
describe('isProduction', () => {
it('should return true if in production', () => {
process.env.NODE_ENV = 'production';
expect(isProduction()).toBe(true);
});
it('should return false if not in production', () => {
process.env.NODE_ENV = 'test';
expect(!isProduction()).toBe(true);
});
});
describe('isTest', () => {
it('should return true if in test', () => {
process.env.NODE_ENV = 'test';
expect(isTest()).toBe(true);
});
it('should return true if not in test', () => {
process.env.NODE_ENV = 'development';
expect(!isTest()).toBe(true);
});
});
describe('isDevelopment', () => {
it('should return true if in development', () => {
process.env.NODE_ENV = 'development';
expect(isDevelopment()).toBe(true);
});
it('should return true if not in development and environment is defined', () => {
process.env.NODE_ENV = 'test';
expect(!isDevelopment()).toBe(true);
});
it('should make development as the default environment', () => {
delete process.env.NODE_ENV;
expect(isDevelopment()).toBe(true);
});
});
});

View File

@@ -0,0 +1,174 @@
import { isEqual } from '../isEqual';
describe('isEqual', () => {
it('should return true for equal primitive values', () => {
expect(isEqual(undefined, undefined)).toBe(true);
expect(isEqual(null, null)).toBe(true);
expect(isEqual(true, true)).toBe(true);
expect(isEqual(false, false)).toBe(true);
expect(isEqual(-1, -1)).toBe(true);
expect(isEqual(+1, +1)).toBe(true);
expect(isEqual(42, 42)).toBe(true);
expect(isEqual(0, 0)).toBe(true);
expect(isEqual(0.5, 0.5)).toBe(true);
expect(isEqual('hello', 'hello')).toBe(true);
expect(isEqual('world', 'world')).toBe(true);
});
it('should return false for not equal primitive values', () => {
expect(!isEqual(undefined, null)).toBe(true);
expect(!isEqual(null, undefined)).toBe(true);
expect(!isEqual(true, false)).toBe(true);
expect(!isEqual(false, true)).toBe(true);
expect(!isEqual(-1, +1)).toBe(true);
expect(!isEqual(+1, -1)).toBe(true);
expect(!isEqual(42, 42.00000000000001)).toBe(true);
expect(!isEqual(0, 0.5)).toBe(true);
expect(!isEqual('hello', 'world')).toBe(true);
expect(!isEqual('world', 'hello')).toBe(true);
});
it('should return false when comparing primitives with objects', () => {
expect(!isEqual({}, null)).toBe(true);
expect(!isEqual(null, {})).toBe(true);
expect(!isEqual({}, true)).toBe(true);
expect(!isEqual(true, {})).toBe(true);
expect(!isEqual({}, 42)).toBe(true);
expect(!isEqual(42, {})).toBe(true);
expect(!isEqual({}, 'hello')).toBe(true);
expect(!isEqual('hello', {})).toBe(true);
});
it('should correctly compare shallow objects', () => {
expect(isEqual({}, {})).toBe(true);
expect(isEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 })).toBe(true);
expect(!isEqual({ a: 1, b: 2, c: 3 }, { a: 3, b: 2, c: 1 })).toBe(true);
expect(!isEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 })).toBe(true);
expect(!isEqual({ a: 1, b: 2 }, { a: 1, b: 2, c: 3 })).toBe(true);
});
it('should correctly compare deep objects', () => {
expect(isEqual({ x: {} }, { x: {} })).toBe(true);
expect(
isEqual({ x: { a: 1, b: 2, c: 3 } }, { x: { a: 1, b: 2, c: 3 } }),
).toBe(true);
expect(
!isEqual({ x: { a: 1, b: 2, c: 3 } }, { x: { a: 3, b: 2, c: 1 } }),
).toBe(true);
expect(!isEqual({ x: { a: 1, b: 2, c: 3 } }, { x: { a: 1, b: 2 } })).toBe(
true,
);
expect(!isEqual({ x: { a: 1, b: 2 } }, { x: { a: 1, b: 2, c: 3 } })).toBe(
true,
);
});
it('should correctly compare deep objects without object prototype ', () => {
// Solves https://github.com/apollographql/apollo-client/issues/2132
const objNoProto = Object.create(null);
objNoProto.a = { b: 2, c: [3, 4] };
objNoProto.e = Object.create(null);
objNoProto.e.f = 5;
expect(isEqual(objNoProto, { a: { b: 2, c: [3, 4] }, e: { f: 5 } })).toBe(
true,
);
expect(!isEqual(objNoProto, { a: { b: 2, c: [3, 4] }, e: { f: 6 } })).toBe(
true,
);
expect(!isEqual(objNoProto, { a: { b: 2, c: [3, 4] }, e: null })).toBe(
true,
);
expect(!isEqual(objNoProto, { a: { b: 2, c: [3] }, e: { f: 5 } })).toBe(
true,
);
expect(!isEqual(objNoProto, null)).toBe(true);
});
it('should correctly handle modified prototypes', () => {
Array.prototype.foo = null;
expect(isEqual([1, 2, 3], [1, 2, 3])).toBe(true);
expect(!isEqual([1, 2, 3], [1, 2, 4])).toBe(true);
delete Array.prototype.foo;
});
describe('comparing objects with circular refs', () => {
// copied with slight modification from lodash test suite
it('should compare objects with circular references', () => {
const object1 = {},
object2 = {};
object1.a = object1;
object2.a = object2;
expect(isEqual(object1, object2)).toBe(true);
object1.b = 0;
object2.b = Object(0);
expect(isEqual(object1, object2)).toBe(true);
object1.c = Object(1);
object2.c = Object(2);
expect(isEqual(object1, object2)).toBe(false);
object1 = { a: 1, b: 2, c: 3 };
object1.b = object1;
object2 = { a: 1, b: { a: 1, b: 2, c: 3 }, c: 3 };
expect(isEqual(object1, object2)).toBe(false);
});
it('should have transitive equivalence for circular references of objects', () => {
const object1 = {},
object2 = { a: object1 },
object3 = { a: object2 };
object1.a = object1;
expect(isEqual(object1, object2)).toBe(true);
expect(isEqual(object2, object3)).toBe(true);
expect(isEqual(object1, object3)).toBe(true);
});
it('should compare objects with multiple circular references', () => {
const array1 = [{}],
array2 = [{}];
(array1[0].a = array1).push(array1);
(array2[0].a = array2).push(array2);
expect(isEqual(array1, array2)).toBe(true);
array1[0].b = 0;
array2[0].b = Object(0);
expect(isEqual(array1, array2)).toBe(true);
array1[0].c = Object(1);
array2[0].c = Object(2);
expect(isEqual(array1, array2)).toBe(false);
});
it('should compare objects with complex circular references', () => {
const object1 = {
foo: { b: { c: { d: {} } } },
bar: { a: 2 },
};
const object2 = {
foo: { b: { c: { d: {} } } },
bar: { a: 2 },
};
object1.foo.b.c.d = object1;
object1.bar.b = object1.foo.b;
object2.foo.b.c.d = object2;
object2.bar.b = object2.foo.b;
expect(isEqual(object1, object2)).toBe(true);
});
});
});

View File

@@ -0,0 +1,17 @@
import { maybeDeepFreeze } from '../maybeDeepFreeze';
describe('maybeDeepFreeze', () => {
it('should deep freeze', () => {
const foo: any = { bar: undefined };
maybeDeepFreeze(foo);
expect(() => (foo.bar = 1)).toThrow();
expect(foo.bar).toBeUndefined();
});
it('should properly freeze objects without hasOwnProperty', () => {
const foo = Object.create(null);
foo.bar = undefined;
maybeDeepFreeze(foo);
expect(() => (foo.bar = 1)).toThrow();
});
});

View File

@@ -0,0 +1,139 @@
import { mergeDeep, mergeDeepArray } from '../mergeDeep';
describe('mergeDeep', function() {
it('should return an object if first argument falsy', function() {
expect(mergeDeep()).toEqual({});
expect(mergeDeep(null)).toEqual({});
expect(mergeDeep(null, { foo: 42 })).toEqual({ foo: 42 });
});
it('should preserve identity for single arguments', function() {
const arg = Object.create(null);
expect(mergeDeep(arg)).toBe(arg);
});
it('should preserve identity when merging non-conflicting objects', function() {
const a = { a: { name: 'ay' } };
const b = { b: { name: 'bee' } };
const c = mergeDeep(a, b);
expect(c.a).toBe(a.a);
expect(c.b).toBe(b.b);
expect(c).toEqual({
a: { name: 'ay' },
b: { name: 'bee' },
});
});
it('should shallow-copy conflicting fields', function() {
const a = { conflict: { fromA: [1, 2, 3] } };
const b = { conflict: { fromB: [4, 5] } };
const c = mergeDeep(a, b);
expect(c.conflict).not.toBe(a.conflict);
expect(c.conflict).not.toBe(b.conflict);
expect(c.conflict.fromA).toBe(a.conflict.fromA);
expect(c.conflict.fromB).toBe(b.conflict.fromB);
expect(c).toEqual({
conflict: {
fromA: [1, 2, 3],
fromB: [4, 5],
},
});
});
it('should resolve conflicts among more than two objects', function() {
const sources = [];
for (let i = 0; i < 100; ++i) {
sources.push({
['unique' + i]: { value: i },
conflict: {
['from' + i]: { value: i },
nested: {
['nested' + i]: { value: i },
},
},
});
}
const merged = mergeDeep(...sources);
sources.forEach((source, i) => {
expect(merged['unique' + i].value).toBe(i);
expect(source['unique' + i]).toBe(merged['unique' + i]);
expect(merged.conflict).not.toBe(source.conflict);
expect(merged.conflict['from' + i].value).toBe(i);
expect(merged.conflict['from' + i]).toBe(source.conflict['from' + i]);
expect(merged.conflict.nested).not.toBe(source.conflict.nested);
expect(merged.conflict.nested['nested' + i].value).toBe(i);
expect(merged.conflict.nested['nested' + i]).toBe(
source.conflict.nested['nested' + i],
);
});
});
it('can merge array elements', function() {
const a = [{ a: 1 }, { a: 'ay' }, 'a'];
const b = [{ b: 2 }, { b: 'bee' }, 'b'];
const c = [{ c: 3 }, { c: 'cee' }, 'c'];
const d = { 1: { d: 'dee' } };
expect(mergeDeep(a, b, c, d)).toEqual([
{ a: 1, b: 2, c: 3 },
{ a: 'ay', b: 'bee', c: 'cee', d: 'dee' },
'c',
]);
});
it('lets the last conflicting value win', function() {
expect(mergeDeep('a', 'b', 'c')).toBe('c');
expect(
mergeDeep(
{ a: 'a', conflict: 1 },
{ b: 'b', conflict: 2 },
{ c: 'c', conflict: 3 },
),
).toEqual({
a: 'a',
b: 'b',
c: 'c',
conflict: 3,
});
expect(mergeDeep(
['a', ['b', 'c'], 'd'],
[/*empty*/, ['B'], 'D'],
)).toEqual(
['a', ['B', 'c'], 'D'],
);
expect(mergeDeep(
['a', ['b', 'c'], 'd'],
['A', [/*empty*/, 'C']],
)).toEqual(
['A', ['b', 'C'], 'd'],
);
});
it('mergeDeep returns the intersection of its argument types', function() {
const abc = mergeDeep({ str: "hi", a: 1 }, { a: 3, b: 2 }, { b: 1, c: 2 });
// The point of this test is that the following lines type-check without
// resorting to any `any` loopholes:
expect(abc.str.slice(0)).toBe("hi");
expect(abc.a * 2).toBe(6);
expect(abc.b - 0).toBe(1);
expect(abc.c / 2).toBe(1);
});
it('mergeDeepArray returns the supertype of its argument types', function() {
class F {
check() { return "ok" };
}
const fs: F[] = [new F, new F, new F];
// Although mergeDeepArray doesn't have the same tuple type awareness as
// mergeDeep, it does infer that F should be the return type here:
expect(mergeDeepArray(fs).check()).toBe("ok");
});
});

View File

@@ -0,0 +1,15 @@
import { stripSymbols } from '../stripSymbols';
interface SymbolConstructor {
(description?: string | number): symbol;
}
declare const Symbol: SymbolConstructor;
describe('stripSymbols', () => {
it('should strip symbols (only)', () => {
const sym = Symbol('id');
const data = { foo: 'bar', [sym]: 'ROOT_QUERY' };
expect(stripSymbols(data)).toEqual({ foo: 'bar' });
});
});

View File

@@ -0,0 +1,61 @@
import { warnOnceInDevelopment } from '../warnOnce';
let lastWarning: string | null;
let keepEnv: string | undefined;
let numCalls = 0;
let oldConsoleWarn: any;
describe('warnOnce', () => {
beforeEach(() => {
keepEnv = process.env.NODE_ENV;
numCalls = 0;
lastWarning = null;
oldConsoleWarn = console.warn;
console.warn = (msg: any) => {
numCalls++;
lastWarning = msg;
};
});
afterEach(() => {
process.env.NODE_ENV = keepEnv;
console.warn = oldConsoleWarn;
});
it('actually warns', () => {
process.env.NODE_ENV = 'development';
warnOnceInDevelopment('hi');
expect(lastWarning).toBe('hi');
expect(numCalls).toEqual(1);
});
it('does not warn twice', () => {
process.env.NODE_ENV = 'development';
warnOnceInDevelopment('ho');
warnOnceInDevelopment('ho');
expect(lastWarning).toEqual('ho');
expect(numCalls).toEqual(1);
});
it('warns two different things once each', () => {
process.env.NODE_ENV = 'development';
warnOnceInDevelopment('slow');
expect(lastWarning).toEqual('slow');
warnOnceInDevelopment('mo');
expect(lastWarning).toEqual('mo');
expect(numCalls).toEqual(2);
});
it('does not warn in production', () => {
process.env.NODE_ENV = 'production';
warnOnceInDevelopment('lo');
warnOnceInDevelopment('lo');
expect(numCalls).toEqual(0);
});
it('warns many times in test', () => {
process.env.NODE_ENV = 'test';
warnOnceInDevelopment('yo');
warnOnceInDevelopment('yo');
expect(lastWarning).toEqual('yo');
expect(numCalls).toEqual(2);
});
});

31
node_modules/apollo-utilities/src/util/assign.ts generated vendored Normal file
View File

@@ -0,0 +1,31 @@
/**
* Adds the properties of one or more source objects to a target object. Works exactly like
* `Object.assign`, but as a utility to maintain support for IE 11.
*
* @see https://github.com/apollostack/apollo-client/pull/1009
*/
export function assign<A, B>(a: A, b: B): A & B;
export function assign<A, B, C>(a: A, b: B, c: C): A & B & C;
export function assign<A, B, C, D>(a: A, b: B, c: C, d: D): A & B & C & D;
export function assign<A, B, C, D, E>(
a: A,
b: B,
c: C,
d: D,
e: E,
): A & B & C & D & E;
export function assign(target: any, ...sources: Array<any>): any;
export function assign(
target: { [key: string]: any },
...sources: Array<{ [key: string]: any }>
): { [key: string]: any } {
sources.forEach(source => {
if (typeof source === 'undefined' || source === null) {
return;
}
Object.keys(source).forEach(key => {
target[key] = source[key];
});
});
return target;
}

4
node_modules/apollo-utilities/src/util/canUse.ts generated vendored Normal file
View File

@@ -0,0 +1,4 @@
export const canUseWeakMap = typeof WeakMap === 'function' && !(
typeof navigator === 'object' &&
navigator.product === 'ReactNative'
);

37
node_modules/apollo-utilities/src/util/cloneDeep.ts generated vendored Normal file
View File

@@ -0,0 +1,37 @@
const { toString } = Object.prototype;
/**
* Deeply clones a value to create a new instance.
*/
export function cloneDeep<T>(value: T): T {
return cloneDeepHelper(value, new Map());
}
function cloneDeepHelper<T>(val: T, seen: Map<any, any>): T {
switch (toString.call(val)) {
case "[object Array]": {
if (seen.has(val)) return seen.get(val);
const copy: T & any[] = (val as any).slice(0);
seen.set(val, copy);
copy.forEach(function (child, i) {
copy[i] = cloneDeepHelper(child, seen);
});
return copy;
}
case "[object Object]": {
if (seen.has(val)) return seen.get(val);
// High fidelity polyfills of Object.create and Object.getPrototypeOf are
// possible in all JS environments, so we will assume they exist/work.
const copy = Object.create(Object.getPrototypeOf(val));
seen.set(val, copy);
Object.keys(val).forEach(key => {
copy[key] = cloneDeepHelper((val as any)[key], seen);
});
return copy;
}
default:
return val;
}
}

24
node_modules/apollo-utilities/src/util/environment.ts generated vendored Normal file
View File

@@ -0,0 +1,24 @@
export function getEnv(): string | undefined {
if (typeof process !== 'undefined' && process.env.NODE_ENV) {
return process.env.NODE_ENV;
}
// default environment
return 'development';
}
export function isEnv(env: string): boolean {
return getEnv() === env;
}
export function isProduction(): boolean {
return isEnv('production') === true;
}
export function isDevelopment(): boolean {
return isEnv('development') === true;
}
export function isTest(): boolean {
return isEnv('test') === true;
}

View File

@@ -0,0 +1,15 @@
import { ExecutionResult } from 'graphql';
export function tryFunctionOrLogError(f: Function) {
try {
return f();
} catch (e) {
if (console.error) {
console.error(e);
}
}
}
export function graphQLResultHasError(result: ExecutionResult) {
return result.errors && result.errors.length;
}

View File

@@ -0,0 +1,14 @@
export function filterInPlace<T>(
array: T[],
test: (elem: T) => boolean,
context?: any,
): T[] {
let target = 0;
array.forEach(function (elem, i) {
if (test.call(this, elem, i, array)) {
array[target++] = elem;
}
}, context);
array.length = target;
return array;
}

1
node_modules/apollo-utilities/src/util/isEqual.ts generated vendored Normal file
View File

@@ -0,0 +1 @@
export { equal as isEqual } from '@wry/equality';

View File

@@ -0,0 +1,33 @@
import { isDevelopment, isTest } from './environment';
// Taken (mostly) from https://github.com/substack/deep-freeze to avoid
// import hassles with rollup.
function deepFreeze(o: any) {
Object.freeze(o);
Object.getOwnPropertyNames(o).forEach(function(prop) {
if (
o[prop] !== null &&
(typeof o[prop] === 'object' || typeof o[prop] === 'function') &&
!Object.isFrozen(o[prop])
) {
deepFreeze(o[prop]);
}
});
return o;
}
export function maybeDeepFreeze(obj: any) {
if (isDevelopment() || isTest()) {
// Polyfilled Symbols potentially cause infinite / very deep recursion while deep freezing
// which is known to crash IE11 (https://github.com/apollographql/apollo-client/issues/3043).
const symbolIsPolyfilled =
typeof Symbol === 'function' && typeof Symbol('') === 'string';
if (!symbolIsPolyfilled) {
return deepFreeze(obj);
}
}
return obj;
}

115
node_modules/apollo-utilities/src/util/mergeDeep.ts generated vendored Normal file
View File

@@ -0,0 +1,115 @@
const { hasOwnProperty } = Object.prototype;
// These mergeDeep and mergeDeepArray utilities merge any number of objects
// together, sharing as much memory as possible with the source objects, while
// remaining careful to avoid modifying any source objects.
// Logically, the return type of mergeDeep should be the intersection of
// all the argument types. The binary call signature is by far the most
// common, but we support 0- through 5-ary as well. After that, the
// resulting type is just the inferred array element type. Note to nerds:
// there is a more clever way of doing this that converts the tuple type
// first to a union type (easy enough: T[number]) and then converts the
// union to an intersection type using distributive conditional type
// inference, but that approach has several fatal flaws (boolean becomes
// true & false, and the inferred type ends up as unknown in many cases),
// in addition to being nearly impossible to explain/understand.
export type TupleToIntersection<T extends any[]> =
T extends [infer A] ? A :
T extends [infer A, infer B] ? A & B :
T extends [infer A, infer B, infer C] ? A & B & C :
T extends [infer A, infer B, infer C, infer D] ? A & B & C & D :
T extends [infer A, infer B, infer C, infer D, infer E] ? A & B & C & D & E :
T extends (infer U)[] ? U : any;
export function mergeDeep<T extends any[]>(
...sources: T
): TupleToIntersection<T> {
return mergeDeepArray(sources);
}
// In almost any situation where you could succeed in getting the
// TypeScript compiler to infer a tuple type for the sources array, you
// could just use mergeDeep instead of mergeDeepArray, so instead of
// trying to convert T[] to an intersection type we just infer the array
// element type, which works perfectly when the sources array has a
// consistent element type.
export function mergeDeepArray<T>(sources: T[]): T {
let target = sources[0] || {} as T;
const count = sources.length;
if (count > 1) {
const pastCopies: any[] = [];
target = shallowCopyForMerge(target, pastCopies);
for (let i = 1; i < count; ++i) {
target = mergeHelper(target, sources[i], pastCopies);
}
}
return target;
}
function isObject(obj: any): obj is Record<string | number, any> {
return obj !== null && typeof obj === 'object';
}
function mergeHelper(
target: any,
source: any,
pastCopies: any[],
) {
if (isObject(source) && isObject(target)) {
// In case the target has been frozen, make an extensible copy so that
// we can merge properties into the copy.
if (Object.isExtensible && !Object.isExtensible(target)) {
target = shallowCopyForMerge(target, pastCopies);
}
Object.keys(source).forEach(sourceKey => {
const sourceValue = source[sourceKey];
if (hasOwnProperty.call(target, sourceKey)) {
const targetValue = target[sourceKey];
if (sourceValue !== targetValue) {
// When there is a key collision, we need to make a shallow copy of
// target[sourceKey] so the merge does not modify any source objects.
// To avoid making unnecessary copies, we use a simple array to track
// past copies, since it's safe to modify copies created earlier in
// the merge. We use an array for pastCopies instead of a Map or Set,
// since the number of copies should be relatively small, and some
// Map/Set polyfills modify their keys.
target[sourceKey] = mergeHelper(
shallowCopyForMerge(targetValue, pastCopies),
sourceValue,
pastCopies,
);
}
} else {
// If there is no collision, the target can safely share memory with
// the source, and the recursion can terminate here.
target[sourceKey] = sourceValue;
}
});
return target;
}
// If source (or target) is not an object, let source replace target.
return source;
}
function shallowCopyForMerge<T>(value: T, pastCopies: any[]): T {
if (
value !== null &&
typeof value === 'object' &&
pastCopies.indexOf(value) < 0
) {
if (Array.isArray(value)) {
value = (value as any).slice(0);
} else {
value = {
__proto__: Object.getPrototypeOf(value),
...value,
};
}
pastCopies.push(value);
}
return value;
}

14
node_modules/apollo-utilities/src/util/stripSymbols.ts generated vendored Normal file
View File

@@ -0,0 +1,14 @@
/**
* In order to make assertions easier, this function strips `symbol`'s from
* the incoming data.
*
* This can be handy when running tests against `apollo-client` for example,
* since it adds `symbol`'s to the data in the store. Jest's `toEqual`
* function now covers `symbol`'s (https://github.com/facebook/jest/pull/3437),
* which means all test data used in a `toEqual` comparison would also have to
* include `symbol`'s, to pass. By stripping `symbol`'s from the cache data
* we can compare against more simplified test data.
*/
export function stripSymbols<T>(data: T): T {
return JSON.parse(JSON.stringify(data));
}

24
node_modules/apollo-utilities/src/util/warnOnce.ts generated vendored Normal file
View File

@@ -0,0 +1,24 @@
import { isProduction, isTest } from './environment';
const haveWarned = Object.create({});
/**
* Print a warning only once in development.
* In production no warnings are printed.
* In test all warnings are printed.
*
* @param msg The warning message
* @param type warn or error (will call console.warn or console.error)
*/
export function warnOnceInDevelopment(msg: string, type = 'warn') {
if (!isProduction() && !haveWarned[msg]) {
if (!isTest()) {
haveWarned[msg] = true;
}
if (type === 'error') {
console.error(msg);
} else {
console.warn(msg);
}
}
}