UNPKG

@graphql-hive/core

Version:
152 lines (151 loc) • 6.36 kB
import { Kind, print, separateOperations, stripIgnoredCharacters, TypeInfo, visit, } from 'graphql'; import { md5 } from 'js-md5'; import sortBy from 'lodash.sortby'; import { collectSchemaCoordinates } from '../client/collect-schema-coordinates.js'; /** * Normalize a operation document. */ export function normalizeOperation({ document, operationName, hideLiterals = true, removeAliases = true, }) { var _a, _b; return stripIgnoredCharacters(print(visit(dropUnusedDefinitions(document, operationName !== null && operationName !== void 0 ? operationName : (_b = (_a = document.definitions.find(isOperationDef)) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.value), { // hide literals IntValue(node) { return hideLiterals ? Object.assign(Object.assign({}, node), { value: '0' }) : node; }, FloatValue(node) { return hideLiterals ? Object.assign(Object.assign({}, node), { value: '0' }) : node; }, StringValue(node) { return hideLiterals ? Object.assign(Object.assign({}, node), { value: '', block: false }) : node; }, Field(node) { return Object.assign(Object.assign({}, node), { // remove aliases alias: removeAliases ? undefined : node.alias, // sort arguments arguments: sortNodes(node.arguments) }); }, Document(node) { return Object.assign(Object.assign({}, node), { definitions: sortNodes(node.definitions) }); }, OperationDefinition(node) { return Object.assign(Object.assign({}, node), { variableDefinitions: sortNodes(node.variableDefinitions) }); }, SelectionSet(node) { return Object.assign(Object.assign({}, node), { selections: sortNodes(node.selections) }); }, FragmentSpread(node) { return Object.assign(Object.assign({}, node), { directives: sortNodes(node.directives) }); }, InlineFragment(node) { return Object.assign(Object.assign({}, node), { directives: sortNodes(node.directives) }); }, FragmentDefinition(node) { return Object.assign(Object.assign({}, node), { directives: sortNodes(node.directives), variableDefinitions: sortNodes(node.variableDefinitions) }); }, Directive(node) { return Object.assign(Object.assign({}, node), { arguments: sortNodes(node.arguments) }); }, }))); } function sortNodes(nodes) { if (nodes) { if (nodes.length === 0) { return []; } if (isOfKindList(nodes, Kind.DIRECTIVE)) { return sortBy(nodes, 'name.value'); } if (isOfKindList(nodes, Kind.VARIABLE_DEFINITION)) { return sortBy(nodes, 'variable.name.value'); } if (isOfKindList(nodes, Kind.ARGUMENT)) { return sortBy(nodes, 'name.value'); } if (isOfKindList(nodes, [Kind.FIELD, Kind.FRAGMENT_SPREAD, Kind.INLINE_FRAGMENT])) { return sortBy(nodes, 'kind', 'name.value'); } return sortBy(nodes, 'kind', 'name.value'); } return; } function isOfKindList(nodes, kind) { return typeof kind === 'string' ? nodes[0].kind === kind : kind.includes(nodes[0].kind); } function isOperationDef(def) { return def.kind === Kind.OPERATION_DEFINITION; } function dropUnusedDefinitions(doc, operationName) { var _a; if (!operationName) { return doc; } return (_a = separateOperations(doc)[operationName]) !== null && _a !== void 0 ? _a : doc; } function findOperationDefinition(doc) { return doc.definitions.find(isOperationDef); } /** normalize a graphql operation into a stable hash as used internally within our ClickHouse Database. */ export function preprocessOperation(operation) { var _a, _b; const body = normalizeOperation({ document: operation.document, hideLiterals: true, removeAliases: true, }); // Two operations with the same hash has to be equal: // 1. body is the same // 2. name is the same // 3. used schema coordinates are equal - this is important to assign schema coordinate to an operation const uniqueCoordinatesSet = new Set(); for (const field of operation.schemaCoordinates) { uniqueCoordinatesSet.add(field); // Add types as well: // `Query.foo` -> `Query` const at = field.indexOf('.'); if (at > -1) { uniqueCoordinatesSet.add(field.substring(0, at)); } } const sortedCoordinates = Array.from(uniqueCoordinatesSet).sort(); const operationDefinition = findOperationDefinition(operation.document); if (!operationDefinition) { return null; } const operationName = (_a = operation.operationName) !== null && _a !== void 0 ? _a : (_b = operationDefinition.name) === null || _b === void 0 ? void 0 : _b.value; const hash = md5 .create() .update(body) .update(operationName !== null && operationName !== void 0 ? operationName : '') .update(sortedCoordinates.join(';')) // we do not need to sort from A to Z, default lexicographic sorting is enough .hex(); return { type: operationDefinition.operation, hash, body, coordinates: sortedCoordinates, name: operationName || null, }; } /** * Hash a executable GraphQL document according to Hive platforms algorithm * for identification. * * Return null if no executable operation definition was found. */ export function hashOperation(args) { var _a, _b, _c; const schemaCoordinates = collectSchemaCoordinates({ documentNode: args.documentNode, processVariables: args.variables !== null, variables: (_a = args.variables) !== null && _a !== void 0 ? _a : {}, schema: args.schema, typeInfo: (_b = args.typeInfo) !== null && _b !== void 0 ? _b : new TypeInfo(args.schema), }); const result = preprocessOperation({ document: args.documentNode, schemaCoordinates: schemaCoordinates, operationName: args.operationName, }); return (_c = result === null || result === void 0 ? void 0 : result.hash) !== null && _c !== void 0 ? _c : null; }