UNPKG

graphql-shield

Version:

GraphQL Server permissions as another layer of abstraction!

180 lines (179 loc) 7.11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ReplaceFieldWithFragment = void 0; // copied from https://github.com/ardatan/graphql-tools/blob/a233614ea5e76b855184c6d374e164904ff2ecb1/src/transforms/ReplaceFieldWithFragment.ts // TODO: return it back to graphql-tools? const graphql_1 = require("graphql"); function deduplicateSelection(nodes) { const selectionMap = nodes.reduce((map, node) => { switch (node.kind) { case 'Field': { if (node.alias != null) { if (node.alias.value in map) { return map; } return { ...map, [node.alias.value]: node, }; } if (node.name.value in map) { return map; } return { ...map, [node.name.value]: node, }; } case 'FragmentSpread': { if (node.name.value in map) { return map; } return { ...map, [node.name.value]: node, }; } case 'InlineFragment': { if (map.__fragment != null) { const fragment = map.__fragment; const typeCondition = fragment.typeCondition; if (!typeCondition) { throw new Error(`Fragment ${fragment} does not have a type condition`); } return { ...map, __fragment: concatInlineFragments(typeCondition.name.value, [fragment, node]), }; } return { ...map, __fragment: node, }; } default: { return map; } } }, Object.create(null)); const selection = Object.keys(selectionMap).reduce((selectionList, node) => selectionList.concat(selectionMap[node]), []); return selection; } function concatInlineFragments(type, fragments) { const fragmentSelections = fragments.reduce((selections, fragment) => selections.concat(fragment.selectionSet.selections), []); const deduplicatedFragmentSelection = deduplicateSelection(fragmentSelections); return { kind: graphql_1.Kind.INLINE_FRAGMENT, typeCondition: { kind: graphql_1.Kind.NAMED_TYPE, name: { kind: graphql_1.Kind.NAME, value: type, }, }, selectionSet: { kind: graphql_1.Kind.SELECTION_SET, selections: deduplicatedFragmentSelection, }, }; } class ReplaceFieldWithFragment { constructor(fragments) { this.mapping = {}; for (const { field, fragment } of fragments) { const parsedFragment = parseFragmentToInlineFragment(fragment); const typeCondition = parsedFragment.typeCondition; if (!typeCondition) { throw new Error(`Fragment ${fragment} does not have a type condition`); } const actualTypeName = typeCondition.name.value; if (!(actualTypeName in this.mapping)) { this.mapping[actualTypeName] = Object.create(null); } const typeMapping = this.mapping[actualTypeName]; if (!(field in typeMapping)) { typeMapping[field] = [parsedFragment]; } else { typeMapping[field].push(parsedFragment); } } } transformSchema(originalSchema) { this.targetSchema = originalSchema; return originalSchema; } transformRequest(originalRequest) { if (!this.targetSchema) { throw new Error(`The ReplaceFieldWithFragment transform's "transformRequest" and "transformResult" methods cannot be used without first calling "transformSchema".`); } const document = replaceFieldsWithFragments(this.targetSchema, originalRequest.document, this.mapping); return { ...originalRequest, document, }; } } exports.ReplaceFieldWithFragment = ReplaceFieldWithFragment; function replaceFieldsWithFragments(targetSchema, document, mapping) { const typeInfo = new graphql_1.TypeInfo(targetSchema); return (0, graphql_1.visit)(document, (0, graphql_1.visitWithTypeInfo)(typeInfo, { [graphql_1.Kind.INLINE_FRAGMENT](node) { // do not visit fragments added by this transform if (node.__isShieldInternalFragment) { return false; } return undefined; }, [graphql_1.Kind.SELECTION_SET](node) { const parentType = typeInfo.getParentType(); if (parentType != null) { const parentTypeName = parentType.name; let selections = node.selections; if (parentTypeName in mapping) { node.selections.forEach((selection) => { if (selection.kind === graphql_1.Kind.FIELD) { const name = selection.name.value; const fragments = mapping[parentTypeName][name]; if (fragments != null && fragments.length > 0) { const fragment = { ...concatInlineFragments(parentTypeName, fragments), __isShieldInternalFragment: true, }; selections = selections.concat(fragment); } } }); } if (selections !== node.selections) { return { ...node, selections: deduplicateSelection([...selections]), }; } } return undefined; }, })); } function parseFragmentToInlineFragment(definitions) { if (definitions.trim().startsWith('fragment')) { const document = (0, graphql_1.parse)(definitions); for (const definition of document.definitions) { if (definition.kind === graphql_1.Kind.FRAGMENT_DEFINITION) { return { kind: graphql_1.Kind.INLINE_FRAGMENT, typeCondition: definition.typeCondition, selectionSet: definition.selectionSet, }; } } } const query = (0, graphql_1.parse)(`{${definitions}}`).definitions[0]; for (const selection of query.selectionSet.selections) { if (selection.kind === graphql_1.Kind.INLINE_FRAGMENT) { return selection; } } throw new Error('Could not parse fragment'); }