Initial Save
This commit is contained in:
274
node_modules/apollo-utilities/src/__tests__/directives.ts
generated
vendored
Normal file
274
node_modules/apollo-utilities/src/__tests__/directives.ts
generated
vendored
Normal 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();
|
||||
});
|
||||
});
|
||||
327
node_modules/apollo-utilities/src/__tests__/fragments.ts
generated
vendored
Normal file
327
node_modules/apollo-utilities/src/__tests__/fragments.ts
generated
vendored
Normal 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
|
||||
}
|
||||
`),
|
||||
);
|
||||
});
|
||||
});
|
||||
316
node_modules/apollo-utilities/src/__tests__/getFromAST.ts
generated
vendored
Normal file
316
node_modules/apollo-utilities/src/__tests__/getFromAST.ts
generated
vendored
Normal 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 } },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
23
node_modules/apollo-utilities/src/__tests__/storeUtils.ts
generated
vendored
Normal file
23
node_modules/apollo-utilities/src/__tests__/storeUtils.ts
generated
vendored
Normal 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
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
1
node_modules/apollo-utilities/src/declarations.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare module 'fast-json-stable-stringify';
|
||||
127
node_modules/apollo-utilities/src/directives.ts
generated
vendored
Normal file
127
node_modules/apollo-utilities/src/directives.ts
generated
vendored
Normal 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
92
node_modules/apollo-utilities/src/fragments.ts
generated
vendored
Normal 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
233
node_modules/apollo-utilities/src/getFromAST.ts
generated
vendored
Normal 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
220
node_modules/apollo-utilities/src/index.js.flow
generated
vendored
Normal 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
16
node_modules/apollo-utilities/src/index.ts
generated
vendored
Normal 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
340
node_modules/apollo-utilities/src/storeUtils.ts
generated
vendored
Normal 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
542
node_modules/apollo-utilities/src/transform.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
47
node_modules/apollo-utilities/src/util/__tests__/assign.ts
generated
vendored
Normal file
47
node_modules/apollo-utilities/src/util/__tests__/assign.ts
generated
vendored
Normal 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 });
|
||||
});
|
||||
});
|
||||
70
node_modules/apollo-utilities/src/util/__tests__/cloneDeep.ts
generated
vendored
Normal file
70
node_modules/apollo-utilities/src/util/__tests__/cloneDeep.ts
generated
vendored
Normal 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();
|
||||
});
|
||||
});
|
||||
70
node_modules/apollo-utilities/src/util/__tests__/environment.ts
generated
vendored
Normal file
70
node_modules/apollo-utilities/src/util/__tests__/environment.ts
generated
vendored
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
174
node_modules/apollo-utilities/src/util/__tests__/isEqual.ts
generated
vendored
Normal file
174
node_modules/apollo-utilities/src/util/__tests__/isEqual.ts
generated
vendored
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
17
node_modules/apollo-utilities/src/util/__tests__/maybeDeepFeeze.ts
generated
vendored
Normal file
17
node_modules/apollo-utilities/src/util/__tests__/maybeDeepFeeze.ts
generated
vendored
Normal 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();
|
||||
});
|
||||
});
|
||||
139
node_modules/apollo-utilities/src/util/__tests__/mergeDeep.ts
generated
vendored
Normal file
139
node_modules/apollo-utilities/src/util/__tests__/mergeDeep.ts
generated
vendored
Normal 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");
|
||||
});
|
||||
});
|
||||
15
node_modules/apollo-utilities/src/util/__tests__/stripSymbols.ts
generated
vendored
Normal file
15
node_modules/apollo-utilities/src/util/__tests__/stripSymbols.ts
generated
vendored
Normal 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' });
|
||||
});
|
||||
});
|
||||
61
node_modules/apollo-utilities/src/util/__tests__/warnOnce.ts
generated
vendored
Normal file
61
node_modules/apollo-utilities/src/util/__tests__/warnOnce.ts
generated
vendored
Normal 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
31
node_modules/apollo-utilities/src/util/assign.ts
generated
vendored
Normal 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
4
node_modules/apollo-utilities/src/util/canUse.ts
generated
vendored
Normal 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
37
node_modules/apollo-utilities/src/util/cloneDeep.ts
generated
vendored
Normal 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
24
node_modules/apollo-utilities/src/util/environment.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
15
node_modules/apollo-utilities/src/util/errorHandling.ts
generated
vendored
Normal file
15
node_modules/apollo-utilities/src/util/errorHandling.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
14
node_modules/apollo-utilities/src/util/filterInPlace.ts
generated
vendored
Normal file
14
node_modules/apollo-utilities/src/util/filterInPlace.ts
generated
vendored
Normal 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
1
node_modules/apollo-utilities/src/util/isEqual.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export { equal as isEqual } from '@wry/equality';
|
||||
33
node_modules/apollo-utilities/src/util/maybeDeepFreeze.ts
generated
vendored
Normal file
33
node_modules/apollo-utilities/src/util/maybeDeepFreeze.ts
generated
vendored
Normal 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
115
node_modules/apollo-utilities/src/util/mergeDeep.ts
generated
vendored
Normal 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
14
node_modules/apollo-utilities/src/util/stripSymbols.ts
generated
vendored
Normal 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
24
node_modules/apollo-utilities/src/util/warnOnce.ts
generated
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user