UNPKG

@graphql-hive/core

Version:
212 lines (211 loc) • 9.25 kB
import { getNamedType, isInterfaceType, isObjectType, isUnionType, } from 'graphql'; /** * Extracts true schema coordinates and counts their execution volume, * including resolved types, aliases, unions, interfaces, and scalars. */ export function extractCoordinates(schema, document, resultData, /** Used to resolve an abstract type's real definition */ contextValue, infoValue) { // Using Maps internally for optimized high-frequency writes const counts = new Map(); const fragments = new Map(); let operation; for (let i = 0; i < document.definitions.length; i++) { const def = document.definitions[i]; if (def.kind === 'OperationDefinition' && !operation) { operation = def; } else if (def.kind === 'FragmentDefinition') { fragments.set(def.name.value, def); } } // Convert empty Map to Record to maintain return signature if (!operation || !resultData) return {}; let rootType; if (operation.operation === 'query') rootType = schema.getQueryType(); else if (operation.operation === 'mutation') rootType = schema.getMutationType(); else if (operation.operation === 'subscription') rootType = schema.getSubscriptionType(); if (!rootType) return {}; const typeCache = new Map(); const getType = (name) => { let type = typeCache.get(name); if (!type) { const schemaType = schema.getType(name); if (schemaType) { typeCache.set(name, schemaType); type = schemaType; } } return type; }; const matchCache = new Map(); const doesTypeMatch = (runtimeTypeName, typeConditionName) => { if (runtimeTypeName === typeConditionName) return true; const cacheKey = runtimeTypeName + '|' + typeConditionName; const cached = matchCache.get(cacheKey); if (cached !== undefined) return cached; const runtimeType = getType(runtimeTypeName); const conditionType = getType(typeConditionName); let isMatch = false; if (runtimeType && conditionType) { if (isInterfaceType(conditionType) && isObjectType(runtimeType)) { isMatch = runtimeType.getInterfaces().some(i => i.name === conditionType.name); } else if (isUnionType(conditionType) && isObjectType(runtimeType)) { isMatch = conditionType.getTypes().some(t => t.name === runtimeType.name); } } matchCache.set(cacheKey, isMatch); return isMatch; }; const ctx = { counts, getType, doesTypeMatch, fragments, fieldMetaCache: new Map(), contextValue, infoValue, }; walkNode(resultData, operation.selectionSet.selections, rootType, ctx); // Convert internal Map to a plain object for the public API boundary const resultRecord = Object.create(null); for (const [key, value] of counts.entries()) { resultRecord[key] = value; } return resultRecord; } function walkNode(data, selections, parentType, ctx) { if (data === null || typeof data !== 'object') return; const namedType = getNamedType(parentType); if (!isObjectType(namedType) && !isInterfaceType(namedType) && !isUnionType(namedType)) { return; } if (Array.isArray(data)) { for (let i = 0; i < data.length; i++) { const item = data[i]; if (item !== null && typeof item === 'object') { walkObject(item, selections, namedType, false, ctx); } } return; } walkObject(data, selections, namedType, false, ctx); } function walkObject(data, selections, namedType, isFragmentRecurse, ctx) { var _a, _b, _c, _d; const namedTypeName = namedType.name; let runtimeTypeName = data.__typename; const isAbstractType = isInterfaceType(namedType) || isUnionType(namedType); // Use resolveType for abstract types if __typename is missing from the payload if (!runtimeTypeName && isAbstractType) { runtimeTypeName = (_b = (ctx.infoValue && ((_a = namedType.resolveType) === null || _a === void 0 ? void 0 : _a.call(namedType, data, ctx.contextValue, ctx.infoValue, namedType)))) !== null && _b !== void 0 ? _b : namedTypeName; } runtimeTypeName = runtimeTypeName || namedTypeName; /** Track the abstract type as having been used */ if (runtimeTypeName !== namedTypeName && isAbstractType) { ctx.counts.set(namedTypeName, (ctx.counts.get(namedTypeName) || 0) + 1); } if (!isFragmentRecurse) { // This accurately registers the resolved implementation to the counts map ctx.counts.set(runtimeTypeName, (ctx.counts.get(runtimeTypeName) || 0) + 1); } let fields = null; for (let i = 0; i < selections.length; i++) { const selection = selections[i]; if (selection.kind === 'Field') { const realFieldName = selection.name.value; if (realFieldName === '__typename') continue; const responseKey = selection.alias ? selection.alias.value : realFieldName; const val = data[responseKey]; if (val !== undefined) { const coordinate = namedTypeName + '.' + realFieldName; ctx.counts.set(coordinate, (ctx.counts.get(coordinate) || 0) + 1); if (val !== null) { // Attempt to pull the resolved schema definitions from our short-lived cache let meta = ctx.fieldMetaCache.get(coordinate); if (!meta) { if (!fields) { fields = 'getFields' in namedType ? namedType.getFields() : {}; } const fieldDef = fields[realFieldName]; if (fieldDef) { meta = { type: fieldDef.type, leafTypeName: selection.selectionSet ? undefined : (_c = getNamedType(fieldDef.type)) === null || _c === void 0 ? void 0 : _c.name, }; ctx.fieldMetaCache.set(coordinate, meta); } } if (meta) { if (selection.selectionSet) { walkNode(val, selection.selectionSet.selections, meta.type, ctx); } else if (meta.leafTypeName) { // Fast path for arrays of Leaf Types (Scalars / Enums) const leafType = meta.leafTypeName; if (Array.isArray(val)) { let leafCount = 0; for (let j = 0; j < val.length; j++) { if (val[j] !== null) leafCount++; } if (leafCount > 0) { ctx.counts.set(leafType, (ctx.counts.get(leafType) || 0) + leafCount); } } else { ctx.counts.set(leafType, (ctx.counts.get(leafType) || 0) + 1); } } } } } } else if (selection.kind === 'InlineFragment') { const typeConditionName = (_d = selection.typeCondition) === null || _d === void 0 ? void 0 : _d.name.value; let matchesType = true; let nextType = namedType; if (typeConditionName) { matchesType = ctx.doesTypeMatch(runtimeTypeName, typeConditionName); if (matchesType) { nextType = ctx.getType(typeConditionName) || namedType; } } if (matchesType && selection.selectionSet) { walkObject(data, selection.selectionSet.selections, nextType, true, ctx); } } else if (selection.kind === 'FragmentSpread') { const fragmentName = selection.name.value; const fragmentDef = ctx.fragments.get(fragmentName); if (fragmentDef) { const typeConditionName = fragmentDef.typeCondition.name.value; let matchesType = true; let nextType = namedType; if (typeConditionName) { matchesType = ctx.doesTypeMatch(runtimeTypeName, typeConditionName); if (matchesType) { nextType = ctx.getType(typeConditionName) || namedType; } } if (matchesType && fragmentDef.selectionSet) { walkObject(data, fragmentDef.selectionSet.selections, nextType, true, ctx); } } } } }