@graphql-tools/documents
Version:
Utilities for GraphQL documents.
103 lines (102 loc) • 3.62 kB
JavaScript
import { Kind, print, } from 'graphql';
import { normalizeWhiteSpace } from './normalize-whitespace.js';
// Cache the sorted nodes to avoid sorting the same nodes multiple times
const nodeSortCache = new WeakMap();
export function sortExecutableNodes(nodes) {
if (nodes) {
const shortcutNodes = nodeSortCache.get(nodes);
if (shortcutNodes) {
return shortcutNodes;
}
const cacheResult = (resultNodes) => {
nodeSortCache.set(nodes, resultNodes);
return resultNodes;
};
if (nodes.length === 0) {
return [];
}
if (isOfKindList(nodes, Kind.DIRECTIVE)) {
return cacheResult(sortNodesByStringKey(nodes, node => node.name.value));
}
if (isOfKindList(nodes, Kind.VARIABLE_DEFINITION)) {
return cacheResult(sortNodesByStringKey(nodes, node => node.variable.name.value));
}
if (isOfKindList(nodes, Kind.ARGUMENT)) {
return cacheResult(sortNodesByStringKey(nodes, node => node.name.value));
}
if (isOfKindList(nodes, [Kind.FIELD, Kind.FRAGMENT_SPREAD, Kind.INLINE_FRAGMENT])) {
return cacheResult(sortNodesByStringKey(nodes, node => {
if (node.kind === Kind.FIELD) {
return sortPrefixField + node.name.value;
}
else if (node.kind === Kind.FRAGMENT_SPREAD) {
return sortPrefixFragmentSpread + node.name.value;
}
else {
const typeCondition = node.typeCondition?.name.value ?? '';
// if you have a better idea, send a PR :)
const sortedNodes = buildInlineFragmentSelectionSetKey(cacheResult(sortExecutableNodes(node.selectionSet.selections)));
return sortPrefixInlineFragmentNode + typeCondition + sortedNodes;
}
}));
}
return cacheResult(nodes
.map((node, index) => ({
node,
index,
kind: node.kind,
name: getNodeNameValue(node),
}))
.sort((a, b) => {
const kindComparison = compareKeys(a.kind, b.kind);
if (kindComparison !== 0) {
return kindComparison;
}
const nameComparison = compareKeys(a.name, b.name);
if (nameComparison !== 0) {
return nameComparison;
}
return a.index - b.index;
})
.map(item => item.node));
}
}
const sortPrefixField = '0';
const sortPrefixFragmentSpread = '1';
const sortPrefixInlineFragmentNode = '2';
function isOfKindList(nodes, kind) {
return typeof kind === 'string' ? nodes[0].kind === kind : kind.includes(nodes[0].kind);
}
function buildInlineFragmentSelectionSetKey(nodes) {
return normalizeWhiteSpace(nodes.map(node => print(node)).join(' '));
}
function sortNodesByStringKey(nodes, getKey) {
return nodes
.map((node, index) => ({ node, index, key: getKey(node) }))
.sort((a, b) => {
const keyComparison = compareKeys(a.key, b.key);
if (keyComparison !== 0) {
return keyComparison;
}
return a.index - b.index;
})
.map(item => item.node);
}
function compareKeys(a, b) {
if (a == null) {
return b == null ? 0 : 1;
}
if (b == null) {
return -1;
}
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
function getNodeNameValue(node) {
return node.name?.value;
}