Skip to content

Instantly share code, notes, and snippets.

@runjak
Created August 8, 2018 22:34
Show Gist options
  • Save runjak/ecaf3f8807d562ccff2d2be17267e9c7 to your computer and use it in GitHub Desktop.
Save runjak/ecaf3f8807d562ccff2d2be17267e9c7 to your computer and use it in GitHub Desktop.
GraphQL query split experiment
const split = [
{
directives: [],
kind: "OperationDefinition",
name: {
kind: "Name", value: "t_magic",
},
operation: "query",
selectionSet: {
kind: "SelectionSet",
selections: [{
alias: undefined,
arguments: [{
kind: "Argument",
name: {
kind: "Name",
value: "a",
},
value: {
kind: "Variable",
name: {
kind: "Name",
value: "a",
},
},
},
{
kind: "Argument",
name: {
kind: "Name",
value: "b",
},
value: {
kind: "Variable",
name: {
kind: "Name",
value: "b",
},
},
}],
directives: [],
kind: "Field",
name: {
kind: "Name",
value: "magic",
},
selectionSet: {
kind: "SelectionSet",
selections: [{
alias: undefined,
arguments: [],
directives: [],
kind: "Field",
name: {
kind: "Name",
value: "id",
},
selectionSet: undefined,
},
{
alias: undefined,
arguments: [],
directives: [],
kind: "Field",
name: {
kind: "Name",
value: "spellName",
},
selectionSet: undefined,
}],
},
}],
},
variableDefinitions: [],
},
{
directives: [],
kind: "OperationDefinition",
name: {
kind: "Name",
value: "t_person",
},
operation: "query",
selectionSet: {
kind: "SelectionSet",
selections: [{
alias: undefined,
arguments: [{
kind: "Argument",
name: {
kind: "Name",
value: "foo",
},
value: {
kind: "Variable",
name: {
kind: "Name",
value: "b",
},
},
}],
directives: [],
kind: "Field",
name: {
kind: "Name",
value: "person",
},
selectionSet: {
kind: "SelectionSet",
selections: [{
alias: undefined,
arguments: [],
directives: [],
kind: "Field",
name: {
kind: "Name",
value: "id",
},
selectionSet: undefined,
},
{
alias: undefined,
arguments: [{
kind: "Argument",
name: {
kind: "Name",
value: "bar",
},
value: {
kind: "Variable",
name: {
kind: "Name",
value: "c",
},
},
}],
directives: [],
kind: "Field",
name: {
kind: "Name",
value: "name",
},
selectionSet: undefined,
}],
},
}],
},
variableDefinitions: [],
}];
export default split;
import gql from 'graphql-tag';
import uniq from 'lodash/uniq';
// Where it comes from:
// https://github.com/apollographql/graphql-tag
// How we should lint it:
// https://github.com/apollographql/eslint-plugin-graphql
// Astexplorer
// https://astexplorer.net/#/drYr8X1rnP/1
export const sourceQuery = gql`
query t($a: Int!, $b: Int!, $c: Int!) {
magic(a: $a, b: $b) {
id
spellName
}
person(foo: $b) {
id
name(bar: $c)
}
}
`;
export const targetQueries = {
tMagic: gql`
query t_magic($a: Int!, $b: Int!) {
magic(a: $a, b: $b) {
id
spellName
}
}
`,
tPerson: gql`
query t_person($b: Int!, $c: Int!) {
person(foo: $b) {
id
name(bar: $c)
}
}
`,
};
export function operationDefinitions(queryDocument) {
return queryDocument.definitions;
}
export function queryName(operationDefinition) {
return operationDefinition.name.value;
}
export function isQuery(operationDefinition) {
return operationDefinition.operation === 'query';
}
export function queryOperationDefinitions(queryDocument) {
return (operationDefinitions(queryDocument) || []).filter(isQuery);
}
export function variableName(variableDefinition) {
const {variable: { name: { value }}} = variableDefinition;
return value;
}
export function variableDefinitions(operationDefinition) {
return operationDefinition.variableDefinitions;
}
export function operationSelectionSet(operationDefinition) {
return operationDefinition.selectionSet;
}
export function variableNames(operationDefinition) {
return variableDefinitions(operationDefinition).map(variableName);
}
export function getVariableDefinitions(...variableNames) {
return (operationDefinition) => {
return variableDefinitions(operationDefinition)
.filter((vDef) => (variableNames.includes(variableName(vDef))));
};
}
export function selectionSetArgumentNames(selectionSet) {
return (selectionSet.selections || []).reduce(
(acc, field) => {
const { kind, arguments: args, selectionSet } = field;
if (kind !== 'Field') {
return acc;
}
return [
...acc,
...args.map(({ value: {name: { value }}}) => (value)),
...((selectionSet && selectionSetArgumentNames(selectionSet)) || []),
];
},
[],
);
}
export function selectionName(selection) {
const {name: { value }} = selection;
return value;
}
export function splitQuery(operationDefinition) {
const {
selectionSet: { selections, ...selectionSetRest},
name: { value: qName, ...nameRest},
...definitionBase,
} = operationDefinition;
const opDefsPerSelection = selections.map((selection) => ({
...definitionBase,
selectionSet: {
...selectionSetRest,
selections: [selection],
},
name: {
...nameRest,
value: `${qName}_${selectionName(selection)}`
},
}));
return opDefsPerSelection.map((opDef) => {
const requiredArguments = uniq(selectionSetArgumentNames(operationSelectionSet(opDef)));
return {
...opDef,
variableDefinitions: getVariableDefinitions(requiredArguments)(opDef),
};
});
}
import {
sourceQuery,
targetQueries,
queryOperationDefinitions,
variableNames,
getVariableDefinitions,
operationSelectionSet,
selectionSetArgumentNames,
splitQuery,
} from './gql';
import expectedSplitQuery from './gql-sourceQuery-split';
describe('gql', () => {
describe('variableNames', () => {
it('should deliver the expected variable names', () => {
const expected = [['a','b','c']];
const actual = queryOperationDefinitions(sourceQuery).map(variableNames);
expect(actual).toEqual(expected);
});
});
describe('getVariableDefinitions', () => {
it('should fetch the correct variableDefinitions', () => {
const expected = [[
{defaultValue: undefined, kind: "VariableDefinition", type:
{kind: "NonNullType", type: {kind: "NamedType", name: {kind: "Name", value: "Int"}}},
variable: {kind: "Variable", name: {kind: "Name", value: "a"}},
},
{defaultValue: undefined, kind: "VariableDefinition", type:
{kind: "NonNullType", type: {kind: "NamedType", name: {kind: "Name", value: "Int"}}},
variable: {kind: "Variable", name: {kind: "Name", value: "b"}},
},
]];
const actual = queryOperationDefinitions(sourceQuery)
.map(getVariableDefinitions('a', 'b'));
expect(actual).toEqual(expected);
});
});
describe('selectionSetArguments', () => {
it('should identify the variables used by some selectionSets', () => {
const queries = [
sourceQuery,
...Object.values(targetQueries),
];
const opDefs = queries.reduce(
(acc, query) => ([
...acc,
...queryOperationDefinitions(query),
]),
[],
);
const selectionSets = opDefs.reduce(
(acc, opDef) => ([
...acc,
operationSelectionSet(opDef),
]),
[],
);
const actual = selectionSets.map(selectionSetArgumentNames);
const expected = [
['a','b','b','c'],
['a','b'],
['b','c'],
];
expect(actual).toEqual(expected);
});
});
describe('splitQuery', () => {
it('should split a query as expected', () => {
const [opDef] = queryOperationDefinitions(sourceQuery);
const expected = expectedSplitQuery;
const actual = splitQuery(opDef);
expect(actual).toEqual(expected);
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment