Skip to content

Commit

Permalink
refactor: migrate over code from graphql-parse-resolve-info
Browse files Browse the repository at this point in the history
The library has stopped being maintained and is incorrectly published to npm

[skip publish]
  • Loading branch information
favna committed May 4, 2024
1 parent ce8e11c commit 4b46845
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 16 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"graphql-parse-resolve-info": "4.13.0",
"graphql-tag": "^2.12.6",
"json-stream-stringify": "^3.1.4",
"koa": "^2.15.3",
Expand Down
2 changes: 1 addition & 1 deletion src/lib/utils/getRequestedFields.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GraphQLSet } from '#utils/GraphQLSet';
import type { GraphQLResolveInfo } from 'graphql';
import { parseResolveInfo, type FieldsByTypeName, type ResolveTree } from 'graphql-parse-resolve-info';
import { parseResolveInfo, type FieldsByTypeName, type ResolveTree } from '#utils/graphql-parse-resolve-info';

/**
* Checks if the provided `info` object is an instance of `ResolveTree`.
Expand Down
208 changes: 208 additions & 0 deletions src/lib/utils/graphql-parse-resolve-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import { isNullish } from '@sapphire/utilities';
import {
GraphQLInterfaceType,
GraphQLObjectType,
GraphQLUnionType,
getNamedType,
isCompositeType,
type ASTNode,
type FieldNode,
type FragmentSpreadNode,
type GraphQLCompositeType,
type GraphQLField,
type GraphQLNamedType,
type GraphQLResolveInfo,
type InlineFragmentNode,
type NamedTypeNode,
type SelectionNode
} from 'graphql';
import { getArgumentValues } from 'graphql/execution/values';

/**
* Parses the provided GraphQLResolveInfo object.
* @param resolveInfo - The GraphQLResolveInfo object to parse.
* @param options - The options to use when parsing the resolve info.
* @returns The parsed resolve info.
*
* @license MIT
* @copyright 2018 Benjie Gillam - Retrieved on `2024-04-05` from {@linkplain https://github.com/graphile/graphile-engine/blob/v4.14.0/packages/graphql-parse-resolve-info/src/index.ts}
*/
export function parseResolveInfo(resolveInfo: GraphQLResolveInfo, options: ParseOptions = {}): ResolveTree | FieldsByTypeName | null | undefined {
const fieldNodes: ReadonlyArray<FieldNode> =
// @ts-expect-error Property 'fieldASTs' does not exist on type 'GraphQLResolveInfo'.
resolveInfo.fieldNodes || resolveInfo.fieldASTs;

const { parentType } = resolveInfo;

if (!fieldNodes) {
throw new Error('No fieldNodes provided!');
}

if (options.keepRoot === null) {
options.keepRoot = false;
}

if (options.deep === null) {
options.deep = true;
}

const tree = fieldTreeFromAST(fieldNodes, resolveInfo, parentType, undefined, options);
if (!options.keepRoot) {
const typeKey = firstKey(tree);
if (!typeKey) {
return null;
}

const fields = tree[typeKey];
const fieldKey = firstKey(fields);
if (!fieldKey) {
return null;
}

return fields[fieldKey];
}
return tree;
}

function getFieldFromAST<TContext>(ast: ASTNode, parentType: GraphQLCompositeType): GraphQLField<GraphQLCompositeType, TContext> | undefined {
if (ast.kind === 'Field') {
const fieldNode: FieldNode = ast;
const fieldName = fieldNode.name.value;
if (!(parentType instanceof GraphQLUnionType)) {
const type: GraphQLObjectType | GraphQLInterfaceType = parentType;
return type.getFields()[fieldName];
}
}

return undefined;
}

function fieldTreeFromAST<T extends SelectionNode>(
inASTs: ReadonlyArray<T> | T,
resolveInfo: GraphQLResolveInfo,
parentType: GraphQLCompositeType,
initTree: FieldsByTypeName = {},
options: ParseOptions = {}
): FieldsByTypeName {
const { variableValues } = resolveInfo;
const fragments = resolveInfo.fragments || {};
const asts: ReadonlyArray<T> = Array.isArray(inASTs) ? inASTs : [inASTs];
if (!initTree[parentType.name]) {
initTree[parentType.name] = {};
}
return asts.reduce((tree, selectionVal: SelectionNode) => {
if (selectionVal.kind === 'Field') {
const val: FieldNode = selectionVal;
const name = val.name.value;
const isReserved = name.startsWith('_') && name[1] === '_' && name !== '__id';
if (!isReserved) {
const alias: string = val.alias?.value ? val.alias.value : name;
const field = getFieldFromAST(val, parentType);
if (!field) {
return tree;
}

const fieldGqlTypeOrUndefined = getNamedType(field.type);
if (!fieldGqlTypeOrUndefined) {
return tree;
}

const fieldGqlType: GraphQLNamedType = fieldGqlTypeOrUndefined;
// @ts-expect-error field is typed stricter than GraphQL expects it, silly GraphQL
const args = getArgumentValues(field, val, variableValues) || {};

if (parentType.name && !tree[parentType.name][alias]) {
const newTreeRoot: ResolveTree = {
name,
alias,
args,
fieldsByTypeName: isCompositeType(fieldGqlType)
? {
[fieldGqlType.name]: {}
}
: {}
};
tree[parentType.name][alias] = newTreeRoot;
}

const { selectionSet } = val;

if (!isNullish(selectionSet) && options.deep && isCompositeType(fieldGqlType)) {
const newParentType: GraphQLCompositeType = fieldGqlType;
fieldTreeFromAST(selectionSet.selections, resolveInfo, newParentType, tree[parentType.name][alias].fieldsByTypeName, options);
}
}
} else if (selectionVal.kind === 'FragmentSpread' && options.deep) {
const val: FragmentSpreadNode = selectionVal;
const name = val.name?.value;
const fragment = fragments[name];
let fragmentType: GraphQLNamedType | null | undefined = parentType;

if (fragment.typeCondition) {
fragmentType = getType(resolveInfo, fragment.typeCondition);
}

if (fragmentType && isCompositeType(fragmentType)) {
const newParentType: GraphQLCompositeType = fragmentType;
fieldTreeFromAST(fragment.selectionSet.selections, resolveInfo, newParentType, tree, options);
}
} else if (selectionVal.kind === 'InlineFragment' && options.deep) {
const val: InlineFragmentNode = selectionVal;
const fragment = val;
let fragmentType: GraphQLNamedType | null | undefined = parentType;

if (fragment.typeCondition) {
fragmentType = getType(resolveInfo, fragment.typeCondition);
}

if (fragmentType && isCompositeType(fragmentType)) {
const newParentType: GraphQLCompositeType = fragmentType;
fieldTreeFromAST(fragment.selectionSet.selections, resolveInfo, newParentType, tree, options);
}
}

// Ref: https://github.com/graphile/postgraphile/pull/342/files#diff-d6702ec9fed755c88b9d70b430fda4d8R148
return tree;
}, initTree);
}

function firstKey(obj: Record<string, unknown>) {
for (const key in obj) {
if (Object.hasOwn(obj, key)) {
return key;
}
}

return undefined;
}

function getType(resolveInfo: GraphQLResolveInfo, typeCondition: NamedTypeNode) {
const { schema } = resolveInfo;
const { kind, name } = typeCondition;
if (kind === 'NamedType') {
const typeName = name.value;
return schema.getType(typeName);
}

return undefined;
}

export interface FieldsByTypeName {
[str: string]: {
[str: string]: ResolveTree;
};
}

export interface ResolveTree {
name: string;
alias: string;
args: {
[str: string]: unknown;
};
fieldsByTypeName: FieldsByTypeName;
}

interface ParseOptions {
keepRoot?: boolean;
deep?: boolean;
}
15 changes: 1 addition & 14 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1544,7 +1544,6 @@ __metadata:
eslint-config-prettier: "npm:^9.1.0"
eslint-plugin-prettier: "npm:^5.1.3"
graphql: "npm:^16.8.1"
graphql-parse-resolve-info: "npm:4.13.0"
graphql-tag: "npm:^2.12.6"
json-stream-stringify: "npm:^3.1.4"
koa: "npm:^2.15.3"
Expand Down Expand Up @@ -6188,18 +6187,6 @@ __metadata:
languageName: node
linkType: hard

"graphql-parse-resolve-info@npm:4.13.0":
version: 4.13.0
resolution: "graphql-parse-resolve-info@npm:4.13.0"
dependencies:
debug: "npm:^4.1.1"
tslib: "npm:^2.0.1"
peerDependencies:
graphql: ">=0.9 <0.14 || ^14.0.2 || ^15.4.0 || ^16.3.0"
checksum: 10/4859ec1abc8714463c26b627a7f8284f6eba5aa3acd88a7c53d1e939effd682681a23208274fe89d4fbe1165136a33e9e540461abca004fa9234c1f3c74727b8
languageName: node
linkType: hard

"graphql-request@npm:^6.0.0":
version: 6.1.0
resolution: "graphql-request@npm:6.1.0"
Expand Down Expand Up @@ -9619,7 +9606,7 @@ __metadata:
languageName: node
linkType: hard

"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.1, tslib@npm:^2.6.2, tslib@npm:~2.6.0":
"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.1, tslib@npm:^2.6.2, tslib@npm:~2.6.0":
version: 2.6.2
resolution: "tslib@npm:2.6.2"
checksum: 10/bd26c22d36736513980091a1e356378e8b662ded04204453d353a7f34a4c21ed0afc59b5f90719d4ba756e581a162ecbf93118dc9c6be5acf70aa309188166ca
Expand Down

0 comments on commit 4b46845

Please sign in to comment.