Socket
Socket
Sign inDemoInstall

graphql-parse-resolve-info

Package Overview
Dependencies
Maintainers
1
Versions
63
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

graphql-parse-resolve-info - npm Package Compare versions

Comparing version 0.0.1-alpha2.0 to 0.0.1-alpha3.0

lib/index.js

233

index.js

@@ -1,226 +0,11 @@

"use strict";
const assert = require("assert");
const { getArgumentValues } = require("graphql/execution/values");
const { getNamedType } = require("graphql");
const debug = require("debug")("graphql-parse-resolve-info");
// This script detects if you're running on Node v8 or above; if so it runs the
// code directly, otherwise it falls back to the babel-compiled version
// Originally based on https://github.com/tjmehta/graphql-parse-fields
function getAlias(resolveInfo) {
const asts = resolveInfo.fieldASTs || resolveInfo.fieldNodes;
return asts.reduce(function(alias, val) {
if (!alias) {
if (val.kind === "Field") {
alias = val.alias ? val.alias.value : val.name && val.name.value;
}
}
return alias;
}, null);
if (process.versions.node.match(/^([89]|[1-9][0-9]+)\./)) {
// Modern node, run verbatim
module.exports = require("./src");
} else {
// Older node, run compiled code
require("babel-polyfill");
module.exports = require("./lib");
}
function parseFields(resolveInfo, options = {}) {
if (options.aliasOnly) {
return getAlias(resolveInfo);
}
const fieldNodes = resolveInfo.fieldASTs || resolveInfo.fieldNodes;
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;
}
let tree = fieldTreeFromAST(
fieldNodes,
resolveInfo,
undefined,
options,
parentType
);
if (!options.keepRoot) {
const typeKey = firstKey(tree);
tree = tree[typeKey];
const fieldKey = firstKey(tree);
tree = tree[fieldKey];
}
return tree;
}
function getFieldFromAST(ast, parentType) {
if (ast.kind === "Field") {
const fieldName = ast.name.value;
return parentType.getFields()[fieldName];
}
return;
}
let iNum = 1;
function fieldTreeFromAST(
inASTs,
resolveInfo,
initTree,
options,
parentType,
depth = ""
) {
const instance = iNum++;
debug(
"%s[%d] Entering fieldTreeFromAST with parent type '%s'",
depth,
instance,
parentType
);
let { fragments, variableValues } = resolveInfo;
fragments = fragments || {};
initTree = initTree || {};
options = options || {};
const asts = Array.isArray(inASTs) ? inASTs : [inASTs];
initTree[parentType.name] = initTree[parentType.name] || {};
const outerDepth = depth;
return asts.reduce(function(tree, val, idx) {
const depth = `${outerDepth} `;
const kind = val.kind;
debug(
"%s[%d] Processing AST %d of %d; kind = %s",
depth,
instance,
idx + 1,
asts.length,
kind
);
const name = val.name && val.name.value;
const isReserved = name && name.substr(0, 2) === "__";
if (kind === "Field" && !isReserved) {
const alias = val.alias ? val.alias.value : name;
debug("%s[%d] Field '%s' (alias = '%s')", depth, instance, name, alias);
const field = getFieldFromAST(val, parentType);
const fieldGqlType = getNamedType(field.type);
const args = getArgumentValues(field, val, variableValues) || {};
if (!tree[parentType.name][alias]) {
tree[parentType.name][alias] = {
ast: val,
alias,
name,
args,
fieldsByTypeName: {
[fieldGqlType.name]: {},
},
};
}
if (val.selectionSet && options.deep) {
debug("%s[%d] Recursing into subfields", depth, instance);
fieldTreeFromAST(
val.selectionSet.selections,
resolveInfo,
tree[parentType.name][alias].fieldsByTypeName,
options,
fieldGqlType,
`${depth} `
);
} else {
// No fields to add
debug("%s[%d] Exiting (no fields to add)", depth, instance);
}
} else if (kind === "FragmentSpread" && options.deep) {
debug("%s[%d] Fragment spread '%s'", depth, instance, name);
const fragment = fragments[name];
assert(fragment, 'unknown fragment "' + name + '"');
let fragmentType = parentType;
if (fragment.typeCondition) {
fragmentType = getType(resolveInfo, fragment.typeCondition);
}
if (fragmentType) {
fieldTreeFromAST(
fragment.selectionSet.selections,
resolveInfo,
tree,
options,
fragmentType,
`${depth} `
);
}
} else if (kind === "InlineFragment" && options.deep) {
const fragment = val;
let fragmentType = parentType;
if (fragment.typeCondition) {
fragmentType = getType(resolveInfo, fragment.typeCondition);
}
debug(
"%s[%d] Inline fragment (parent = '%s', type = '%s')",
depth,
instance,
parentType,
fragmentType
);
if (fragmentType) {
fieldTreeFromAST(
fragment.selectionSet.selections,
resolveInfo,
tree,
options,
fragmentType,
`${depth} `
);
}
} else if (isReserved) {
debug(
"%s[%d] IGNORING because field '%s' is reserved",
depth,
instance,
name
);
} else {
debug(
"%s[%d] IGNORING because kind '%s' not understood",
depth,
instance,
kind
);
}
// Ref: https://github.com/postgraphql/postgraphql/pull/342/files#diff-d6702ec9fed755c88b9d70b430fda4d8R148
return tree;
}, initTree);
}
function firstKey(obj) {
for (const key in obj) {
return key;
}
}
function getType(resolveInfo, typeCondition) {
const { schema } = resolveInfo;
const { kind, name } = typeCondition;
if (kind === "NamedType") {
const typeName = name.value;
return schema.getType(typeName);
}
}
function simplifyParsedResolveInfoFragmentWithType(
parsedResolveInfoFragment,
Type
) {
const { fieldsByTypeName } = parsedResolveInfoFragment;
const fields = {};
const StrippedType = getNamedType(Type);
Object.assign(fields, fieldsByTypeName[StrippedType.name]);
if (StrippedType.getInterfaces) {
// GraphQL ensures that the subfields cannot clash, so it's safe to simply overwrite them
for (const Interface of StrippedType.getInterfaces()) {
Object.assign(fields, fieldsByTypeName[Interface.name]);
}
}
return Object.assign({}, parsedResolveInfoFragment, {
fields,
});
}
parseFields.getAlias = getAlias;
module.exports = parseFields;
module.exports.simplifyParsedResolveInfoFragmentWithType = simplifyParsedResolveInfoFragmentWithType;
module.exports.simplify = simplifyParsedResolveInfoFragmentWithType;
{
"name": "graphql-parse-resolve-info",
"version": "0.0.1-alpha2.0",
"version": "0.0.1-alpha3.0",
"description": "Parse GraphQLResolveInfo (the 4th argument of resolve) into a simple tree",
"main": "index.js",
"scripts": {
"test": "jest ."
"test": "jest .",
"prepublish": "babel --out-dir lib src"
},

@@ -27,4 +28,11 @@ "repository": {

"peerDependencies": {
"babel-polyfill": ">=6 <7",
"graphql": ">=0.10 <1"
},
"devDependencies": {
"jest": "20.0.4"
},
"jest": {
"testRegex": "__tests__/.*\\.test\\.js$"
}
}

@@ -14,10 +14,42 @@ graphql-parse-resolve-info

Usage: requested subfields
--------------------------
API
---
To get the tree of subfields of the current field that are being requested:
### `parseResolveInfo(resolveInfo)`
Alias: `parse`
Gets the tree of subfields of the current field that is being requested,
returning the following properties (recursively):
- `name`: the name of the GraphQL field
- `alias`: the alias this GraphQL field has been requested as, or if no alias was specified then the `name`
- `args`: the arguments this field was called with; at the root level this
will be equivalent to the `args` that the `resolve(data, args, context,
resolveInfo) {}` method receives, at deeper levels this allows you to get the
`args` for the nested fields without waiting for their resolvers to be called.
- `fieldsByTypeName`: an object keyed by GraphQL object type names, where the
values are another object keyed by the aliases of the fields requested with
values of the same format as the root level (i.e. `{alias, name, args,
fieldsByTypeName}`); see below for an example
Note that because GraphQL supports interfaces a resolver may return items of
different types. For this reason, we key the fields by the GraphQL type name of
the various fragments that were requested into the `fieldsByTypeName` field.
Once you know which specific type the result is going to be, you can then use
this type (and its interfaces) to determine which sub-fields were requested -
we provide a `simplifyParsedResolveInfoFragmentWithType` helper to aid you with
this. In many cases you will know what type the result will be (because it can
only be one type) so you will probably use this helper heavily.
Example usage:
```js
const parseResolveInfo = require('graphql-parse-resolve-info');
// or import parseResolveInfo from 'graphql-parse-resolve-info';
const {
parseResolveInfo,
simplifyParsedResolveInfoFragmentWithType
} = require('graphql-parse-resolve-info');
// or import { parseResolveInfo, simplifyParsedResolveInfoFragmentWithType } from 'graphql-parse-resolve-info';

@@ -32,4 +64,6 @@ new GraphQLObjectType({

const parsedResolveInfoFragment = parseResolveInfo(resolveInfo);
const { fields } =
parseResolveInfo.simplifyParsedResolveInfoFragmentWithType(parsedResolveInfoFragment, ComplexType);
const { fields } = simplifyParsedResolveInfoFragmentWithType(
parsedResolveInfoFragment,
ComplexType
);
console.dir(fields);

@@ -43,17 +77,22 @@ ...

(Note that because GraphQL supports interfaces and hence a resolver may return
items of different types we key the fields by the GraphQL type name of the
various fragments that were requested. Once you know what type the result was,
you can then use this type (and its interfaces) to determine which sub-fields
were requested. It's quite commont to know that your result will be of a single
type, so we provide a helper that will simplify this for you by passing it the
expected type.)
### `simplifyParsedResolveInfoFragmentWithType(parsedResolveInfoFragment, ReturnType)`
Usage: alias
------------
Alias: `simplify`
To get the alias of the current field being resolved (defaults to the field name if no alias was specified):
Given an object of the form returned by `parseResolveInfo(...)` (which can be
the root-level instance, or it could be one of the nested subfields) and a
GraphQL type this method will return an object of the form above, with an
additional field `fields` which only contains the fields compatible with the
specified `ReturnType`.
Or, in other words, this simplifies the `fieldsByTypeName` to an object of only
the fields compatible with `ReturnType`.
Example usage:
```js
const parseResolveInfo = require('graphql-parse-resolve-info');
const {
parseResolveInfo,
simplifyParsedResolveInfoFragmentWithType
} = require('graphql-parse-resolve-info');

@@ -65,5 +104,37 @@ new GraphQLObjectType({

foo: {
type: new GraphQLNonNull(ComplexType),
resolve(data, args, context, resolveInfo) {
const parsedResolveInfoFragment = parseResolveInfo(resolveInfo);
const { fields } = simplifyParsedResolveInfoFragmentWithType(
parsedResolveInfoFragment,
ComplexType
);
...
}
}
}
});
```
### `getAliasFromResolveInfo(resolveInfo)`
Alias: `getAlias`
Returns the alias of the field being requested (or, if no alias was specified,
then the name of the field).
Example:
```js
const { getAliasFromResolveInfo } = require('graphql-parse-resolve-info');
new GraphQLObjectType({
name: ...
fields: {
...
foo: {
type: new GraphQLNonNull(GraphQLString),
resolve(data, args, context, resolveInfo) {
const alias = parseResolveInfo(resolveInfo, { aliasOnly: true });
const alias = getAliasFromResolveInfo(resolveInfo);
return alias;

@@ -76,2 +147,214 @@ }

Example
-------
For the following GraphQL query:
```graphql
{
allPosts {
edges {
cursor
node {
...PostDetails
author: personByAuthorId {
firstPost {
...PostDetails
}
friends {
nodes {
...PersonDetails
}
totalCount
pageInfo {
startCursor
}
}
}
}
}
}
}
fragment PersonDetails on Person {
id
name
firstName
}
fragment PostDetails on Post {
id
headline
headlineTrimmed
author: personByAuthorId {
...PersonDetails
}
}
```
The following resolver in the `allPosts` field:
```js
const Query = new GraphQLObjectType({
name: 'Query',
fields: {
allPosts: {
type: new GraphQLNonNull(PostsConnection),
resolve(parent, args, context, resolveInfo) {
const parsedResolveInfoFragment = parseResolveInfo(
resolveInfo
);
const simplifiedFragment = simplifyParsedResolveInfoFragmentWithType(
parsedResolveInfoFragment,
resolveInfo.returnType
);
// ...
},
}
// ...
}
});
```
has `parsedResolveInfoFragment`:
```js
{ alias: 'allPosts',
name: 'allPosts',
args: {},
fieldsByTypeName:
{ PostsConnection:
{ edges:
{ alias: 'edges',
name: 'edges',
args: {},
fieldsByTypeName:
{ PostsEdge:
{ cursor:
{ alias: 'cursor',
name: 'cursor',
args: {},
fieldsByTypeName: {} },
node:
{ alias: 'node',
name: 'node',
args: {},
fieldsByTypeName:
{ Post:
{ id: { alias: 'id', name: 'id', args: {}, fieldsByTypeName: {} },
headline:
{ alias: 'headline',
name: 'headline',
args: {},
fieldsByTypeName: {} },
headlineTrimmed:
{ alias: 'headlineTrimmed',
name: 'headlineTrimmed',
args: {},
fieldsByTypeName: {} },
author:
{ alias: 'author',
name: 'personByAuthorId',
args: {},
fieldsByTypeName:
{ Person:
{ id: { alias: 'id', name: 'id', args: {}, fieldsByTypeName: {} },
name: { alias: 'name', name: 'name', args: {}, fieldsByTypeName: {} },
firstName:
{ alias: 'firstName',
name: 'firstName',
args: {},
fieldsByTypeName: {} },
firstPost:
{ alias: 'firstPost',
name: 'firstPost',
args: {},
fieldsByTypeName:
{ Post:
{ id: { alias: 'id', name: 'id', args: {}, fieldsByTypeName: {} },
headline:
{ alias: 'headline',
name: 'headline',
args: {},
fieldsByTypeName: {} },
headlineTrimmed:
{ alias: 'headlineTrimmed',
name: 'headlineTrimmed',
args: {},
fieldsByTypeName: {} },
author:
{ alias: 'author',
name: 'personByAuthorId',
args: {},
fieldsByTypeName:
{ Person:
{ id: { alias: 'id', name: 'id', args: {}, fieldsByTypeName: {} },
name: { alias: 'name', name: 'name', args: {}, fieldsByTypeName: {} },
firstName:
{ alias: 'firstName',
name: 'firstName',
args: {},
fieldsByTypeName: {} } } } } } } },
friends:
{ alias: 'friends',
name: 'friends',
args: {},
fieldsByTypeName:
{ PeopleConnection:
{ nodes:
{ alias: 'nodes',
name: 'nodes',
args: {},
fieldsByTypeName:
{ Person:
{ id: { alias: 'id', name: 'id', args: {}, fieldsByTypeName: {} },
name: { alias: 'name', name: 'name', args: {}, fieldsByTypeName: {} },
firstName:
{ alias: 'firstName',
name: 'firstName',
args: {},
fieldsByTypeName: {} } } } },
totalCount:
{ alias: 'totalCount',
name: 'totalCount',
args: {},
fieldsByTypeName: {} },
pageInfo:
{ alias: 'pageInfo',
name: 'pageInfo',
args: {},
fieldsByTypeName:
{ PageInfo:
{ startCursor:
{ alias: 'startCursor',
name: 'startCursor',
args: {},
fieldsByTypeName: {} } } } } } } } } } } } } } } } } } },
```
and the simplified `simplifiedFragment` is the same as
`parsedResolveInfoFragment`, but with the additional root-level property
`fields` which compresses the root-level property `fieldsByTypeName` to a
single-level object containing only the fields compatible with
`resolveInfo.returnType` (in this case: only `edges`):
```js
{ alias: 'allPosts',
name: 'allPosts',
args: {},
fieldsByTypeName:
...as before...
fields:
{ edges:
{ alias: 'edges',
name: 'edges',
args: {},
fieldsByTypeName:
...as before...
```
Thanks

@@ -78,0 +361,0 @@ ------

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc