@graphql-hive/core
Version:
198 lines (197 loc) • 8.42 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractCoordinates = extractCoordinates;
const graphql_1 = require("graphql");
const add_hive_typenames_js_1 = require("../add-hive-typenames.js");
const docCache = new WeakMap();
const schemaOperationPlanCache = new WeakMap();
function extractCoordinates({ schema, document, resultData, }) {
const stats = {};
let docInfo = docCache.get(document);
if (!docInfo) {
let operation = null;
const fragments = {};
for (const def of document.definitions) {
if (def.kind === graphql_1.Kind.FRAGMENT_DEFINITION) {
fragments[def.name.value] = def;
}
else if (def.kind === graphql_1.Kind.OPERATION_DEFINITION) {
if (!operation)
operation = def;
}
}
docInfo = { operation, fragments };
docCache.set(document, docInfo);
}
const { operation, fragments } = docInfo;
if (!operation || !resultData)
return stats;
const rootType = operation.operation === 'mutation'
? schema.getMutationType()
: operation.operation === 'subscription'
? schema.getSubscriptionType()
: schema.getQueryType();
if (!rootType)
return stats;
let operationCache = schemaOperationPlanCache.get(schema);
if (!operationCache) {
operationCache = new WeakMap();
schemaOperationPlanCache.set(schema, operationCache);
}
let rootPlan = operationCache.get(operation);
if (!rootPlan) {
rootPlan = buildPlanNode(schema, [operation.selectionSet], rootType, fragments);
operationCache.set(operation, rootPlan);
}
traverseData(resultData, rootPlan, rootType.name, stats);
return stats;
}
function buildPlanNode(schema, selectionSets, parentType, fragments) {
const planNode = new Map();
const namedType = (0, graphql_1.getNamedType)(parentType);
let possibleTypes;
if ((0, graphql_1.isAbstractType)(namedType)) {
possibleTypes = schema.getPossibleTypes(namedType);
}
else {
possibleTypes = [namedType];
}
for (const concreteType of possibleTypes) {
const typePlan = {
typeCoords: [concreteType.name],
fields: {},
isEnum: (0, graphql_1.isEnumType)(concreteType),
enumName: (0, graphql_1.isEnumType)(concreteType) ? concreteType.name : null,
};
if ((0, graphql_1.isObjectType)(concreteType)) {
for (const iface of concreteType.getInterfaces()) {
if (iface.name !== namedType.name && iface.name !== concreteType.name) {
typePlan.typeCoords.push(iface.name);
}
}
if (namedType.name !== concreteType.name && !typePlan.typeCoords.includes(namedType.name)) {
typePlan.typeCoords.push(namedType.name);
}
}
else if (namedType.name !== concreteType.name) {
typePlan.typeCoords.push(namedType.name);
}
typePlan.typeCoords = Array.from(new Set(typePlan.typeCoords));
if (selectionSets.length > 0 && (0, graphql_1.isObjectType)(concreteType)) {
const mergedFields = collectFieldsForType(selectionSets, concreteType, schema, fragments);
for (const responseKey in mergedFields) {
const { fieldName, selectionSets: fieldSelSets } = mergedFields[responseKey];
const fieldDef = concreteType.getFields()[fieldName];
if (!fieldDef)
continue;
const fieldCoords = [`${concreteType.name}.${fieldName}`];
for (const iface of concreteType.getInterfaces()) {
if (iface.getFields()[fieldName]) {
fieldCoords.push(`${iface.name}.${fieldName}`);
}
}
const fieldNamedType = (0, graphql_1.getNamedType)(fieldDef.type);
const childPlan = buildPlanNode(schema, fieldSelSets, fieldNamedType, fragments);
typePlan.fields[responseKey] = {
fieldCoords,
childPlan,
expectedTypeName: fieldNamedType.name,
};
}
}
planNode.set(concreteType.name, typePlan);
}
return planNode;
}
function collectFieldsForType(selectionSets, concreteType, schema, fragments) {
const fields = {};
function visit(selectionSet) {
var _a;
for (const selection of selectionSet.selections) {
if (selection.kind === graphql_1.Kind.FIELD) {
const responseKey = selection.alias ? selection.alias.value : selection.name.value;
const fieldName = selection.name.value;
if (fieldName === add_hive_typenames_js_1.HIVE_INTERNAL_TYPENAME || fieldName === '__typename')
continue;
if (!fields[responseKey]) {
fields[responseKey] = { fieldName, selectionSets: [] };
}
if (selection.selectionSet) {
fields[responseKey].selectionSets.push(selection.selectionSet);
}
}
else if (selection.kind === graphql_1.Kind.FRAGMENT_SPREAD) {
const frag = fragments[selection.name.value];
if (frag && doesTypeMatch(frag.typeCondition.name.value, concreteType, schema)) {
visit(frag.selectionSet);
}
}
else if (selection.kind === graphql_1.Kind.INLINE_FRAGMENT) {
const typeCondition = (_a = selection.typeCondition) === null || _a === void 0 ? void 0 : _a.name.value;
if (!typeCondition || doesTypeMatch(typeCondition, concreteType, schema)) {
visit(selection.selectionSet);
}
}
}
}
for (const selSet of selectionSets) {
visit(selSet);
}
return fields;
}
function doesTypeMatch(typeCondition, concreteType, schema) {
if (typeCondition === concreteType.name)
return true;
const conditionType = schema.getType(typeCondition);
if (!conditionType)
return false;
if ((0, graphql_1.isInterfaceType)(conditionType) && (0, graphql_1.isObjectType)(concreteType)) {
return concreteType.getInterfaces().some(i => i.name === typeCondition);
}
if ((0, graphql_1.isAbstractType)(conditionType)) {
return schema.getPossibleTypes(conditionType).some(t => t.name === concreteType.name);
}
return false;
}
function traverseData(data, planNode, fallbackTypeName, stats) {
var _a, _b;
if (data === null || data === undefined || !planNode)
return;
if (Array.isArray(data)) {
for (let i = 0; i < data.length; i++) {
traverseData(data[i], planNode, fallbackTypeName, stats);
}
return;
}
const typeName = (_b = (_a = data[add_hive_typenames_js_1.HIVE_INTERNAL_TYPENAME]) !== null && _a !== void 0 ? _a : data.__typename) !== null && _b !== void 0 ? _b : fallbackTypeName;
const typePlan = planNode.get(typeName) || planNode.get(fallbackTypeName);
if (!typePlan) {
stats[fallbackTypeName] = (stats[fallbackTypeName] || 0) + 1;
return;
}
for (let i = 0; i < typePlan.typeCoords.length; i++) {
const coord = typePlan.typeCoords[i];
stats[coord] = (stats[coord] || 0) + 1;
}
if (typePlan.isEnum && typeof data === 'string' && typePlan.enumName) {
const enumCoord = `${typePlan.enumName}.${data}`;
stats[enumCoord] = (stats[enumCoord] || 0) + 1;
return;
}
if (typeof data !== 'object')
return;
for (const responseKey in data) {
if (responseKey === add_hive_typenames_js_1.HIVE_INTERNAL_TYPENAME || responseKey === '__typename')
continue;
const fieldPlan = typePlan.fields[responseKey];
if (!fieldPlan)
continue;
for (let i = 0; i < fieldPlan.fieldCoords.length; i++) {
const coord = fieldPlan.fieldCoords[i];
stats[coord] = (stats[coord] || 0) + 1;
}
if (fieldPlan.childPlan) {
traverseData(data[responseKey], fieldPlan.childPlan, fieldPlan.expectedTypeName, stats);
}
}
}