apollostack/graphql-server - 如何从解析器获取查询中请求的字段
apollostack/graphql-server - how to get the fields requested in a query from resolver
我正在尝试找出一种处理查询和 mongdb 预测的简洁方法,这样我就不必从数据库中检索过多的信息。
所以假设我有:
// the query
type Query {
getUserByEmail(email: String!): User
}
我有一个 User
和一个 email
和一个 username
,以保持简单。如果我发送查询并且只想检索电子邮件,我可以执行以下操作:
query { getUserByEmail(email: "test@test.com") { email } }
但是在解析器中,我的数据库查询仍然检索到 username
和 email
,但只有其中一个作为查询结果被 apollo 服务器传回。
我只希望数据库检索查询要求的内容:
// the resolver
getUserByEmail(root, args, context, info) {
// check what fields the query requested
// create a projection to only request those fields
return db.collection('users').findOne({ email: args.email }, { /* projection */ });
}
当然,问题是,获取有关客户请求的信息并不是那么简单。
假设我将请求作为上下文传递 - 我考虑使用 context.payload
(hapi.js),它具有查询字符串,并通过各种 .split()
搜索它,但感觉有点脏。据我所知,info.fieldASTs[0].selectionSet.selections
有字段列表,我可以检查它是否存在。我不确定这有多可靠。尤其是当我开始使用更复杂的查询时。
有没有更简单的方法?
如果您不使用 mongDB,投影是您传递的一个附加参数,用于明确告诉它要检索的内容:
// telling mongoDB to not retrieve _id
db.collection('users').findOne({ email: 'test@test.com' }, { _id: 0 })
一如既往,感谢出色的社区。
当然可以。这实际上与基于 SQL 的数据库的 join-monster 包上实现的功能相同。他们的创作者有一个演讲:https://www.youtube.com/watch?v=Y7AdMIuXOgs
查看他们的 info
分析代码以帮助您入门 - https://github.com/stems/join-monster/blob/master/src/queryASTToSqlAST.js#L6-L30
很想为我们 mongo 用户看到一个 projection-monster 包 :)
更新:
在 npm 上有一个从 info
创建投影对象的包:https://www.npmjs.com/package/graphql-mongodb-projection
Apollo 服务器示例
const rootSchema = [`
type Person {
id: String!
name: String!
email: String!
picture: String!
type: Int!
status: Int!
createdAt: Float
updatedAt: Float
}
schema {
query: Query
mutation: Mutation
}
`];
const rootResolvers = {
Query: {
users(root, args, context, info) {
const topLevelFields = Object.keys(graphqlFields(info));
return fetch(`/api/user?fields=${topLevelFields.join(',')}`);
}
}
};
const schema = [...rootSchema];
const resolvers = Object.assign({}, rootResolvers);
// Create schema
const executableSchema = makeExecutableSchema({
typeDefs: schema,
resolvers,
});
2020 年 1 月回答
获取 GraphQL 查询中请求的字段的当前答案是使用 graphql-parse-resolve-info
库来解析 info
参数。
该库“", and is recommended 由其他顶级库的作者继续解析 info
字段,graphql-fields
。
您可以从 info
参数生成 MongoDB 投影。这是您可以遵循的示例代码
/**
* @description - Gets MongoDB projection from graphql query
*
* @return { object }
* @param { object } info
* @param { model } model - MongoDB model for referencing
*/
function getDBProjection(info, model) {
const {
schema: { obj }
} = model;
const keys = Object.keys(obj);
const projection = {};
const { selections } = info.fieldNodes[0].selectionSet;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const isSelected = selections.some(
selection => selection.name.value === key
);
projection[key] = isSelected;
}
console.log(projection);
}
module.exports = getDBProjection;
通过一些辅助函数,您可以像这样使用它(typescript 版本):
import { parceGqlInfo, query } from "@backend";
import { GraphQLResolveInfo } from "graphql";
export const user = async (parent: unknown, args: unknown, ctx: unknown, info: GraphQLResolveInfo): Promise<User | null> => {
const { dbQueryStr } = parceGqlInfo(info, userFields, "id");
const [user] = await query(`SELECT ${dbQueryStr} FROM users WHERE id=;`, [1]);
return user;
};
辅助功能。
几点:
gql_uid用作ID!来自主键的字符串类型不更改数据库类型
必需选项用于数据加载器(如果用户未请求字段)
allowedFields 用于过滤信息中的附加字段,例如“__typename”
queryPrefix 如果您需要为所选字段添加前缀,例如 select u.id from users u
const userFields = [
"gql_uid",
"id",
"email"
]
// merge arrays and delete duplicates
export const mergeDedupe = <T>(arr: any[][]): T => {
// @ts-ignore
return ([...new Set([].concat(...arr))] as unknown) as T;
};
import { parse, simplify, ResolveTree } from "graphql-parse-resolve-info";
import { GraphQLResolveInfo } from "graphql";
export const getQueryFieldsFromInfo = <Required = string>(info: GraphQLResolveInfo, options: { required?: Required[] } = {}): string[] => {
const { fields } = simplify(parse(info) as ResolveTree, info.returnType) as { fields: { [key: string]: { name: string } } };
let astFields = Object.entries(fields).map(([, v]) => v.name);
if (options.required) {
astFields = mergeDedupe([astFields, options.required]);
}
return astFields;
};
export const onlyAllowedFields = <T extends string | number>(raw: T[] | readonly T[], allowed: T[] | readonly T[]): T[] => {
return allowed.filter((f) => raw.includes(f));
};
export const parceGqlInfo = (
info: GraphQLResolveInfo,
allowedFields: string[] | readonly string[],
gqlUidDbAlliasField: string,
options: { required?: string[]; queryPrefix?: string } = {}
): { pureDbFields: string[]; gqlUidRequested: boolean; dbQueryStr: string } => {
const fieldsWithGqlUid = onlyAllowedFields(getQueryFieldsFromInfo(info, options), allowedFields);
return {
pureDbFields: fieldsWithGqlUid.filter((i) => i !== "gql_uid"),
gqlUidRequested: fieldsWithGqlUid.includes("gql_uid"),
dbQueryStr: fieldsWithGqlUid
.map((f) => {
const dbQueryStrField = f === "gql_uid" ? `${gqlUidDbAlliasField}::Text AS gql_uid` : f;
return options.queryPrefix ? `${options.queryPrefix}.${dbQueryStrField}` : dbQueryStrField;
})
.join(),
};
};
我正在尝试找出一种处理查询和 mongdb 预测的简洁方法,这样我就不必从数据库中检索过多的信息。 所以假设我有:
// the query
type Query {
getUserByEmail(email: String!): User
}
我有一个 User
和一个 email
和一个 username
,以保持简单。如果我发送查询并且只想检索电子邮件,我可以执行以下操作:
query { getUserByEmail(email: "test@test.com") { email } }
但是在解析器中,我的数据库查询仍然检索到 username
和 email
,但只有其中一个作为查询结果被 apollo 服务器传回。
我只希望数据库检索查询要求的内容:
// the resolver
getUserByEmail(root, args, context, info) {
// check what fields the query requested
// create a projection to only request those fields
return db.collection('users').findOne({ email: args.email }, { /* projection */ });
}
当然,问题是,获取有关客户请求的信息并不是那么简单。
假设我将请求作为上下文传递 - 我考虑使用 context.payload
(hapi.js),它具有查询字符串,并通过各种 .split()
搜索它,但感觉有点脏。据我所知,info.fieldASTs[0].selectionSet.selections
有字段列表,我可以检查它是否存在。我不确定这有多可靠。尤其是当我开始使用更复杂的查询时。
有没有更简单的方法?
如果您不使用 mongDB,投影是您传递的一个附加参数,用于明确告诉它要检索的内容:
// telling mongoDB to not retrieve _id
db.collection('users').findOne({ email: 'test@test.com' }, { _id: 0 })
一如既往,感谢出色的社区。
当然可以。这实际上与基于 SQL 的数据库的 join-monster 包上实现的功能相同。他们的创作者有一个演讲:https://www.youtube.com/watch?v=Y7AdMIuXOgs
查看他们的 info
分析代码以帮助您入门 - https://github.com/stems/join-monster/blob/master/src/queryASTToSqlAST.js#L6-L30
很想为我们 mongo 用户看到一个 projection-monster 包 :)
更新:
在 npm 上有一个从 info
创建投影对象的包:https://www.npmjs.com/package/graphql-mongodb-projection
Apollo 服务器示例
const rootSchema = [`
type Person {
id: String!
name: String!
email: String!
picture: String!
type: Int!
status: Int!
createdAt: Float
updatedAt: Float
}
schema {
query: Query
mutation: Mutation
}
`];
const rootResolvers = {
Query: {
users(root, args, context, info) {
const topLevelFields = Object.keys(graphqlFields(info));
return fetch(`/api/user?fields=${topLevelFields.join(',')}`);
}
}
};
const schema = [...rootSchema];
const resolvers = Object.assign({}, rootResolvers);
// Create schema
const executableSchema = makeExecutableSchema({
typeDefs: schema,
resolvers,
});
2020 年 1 月回答
获取 GraphQL 查询中请求的字段的当前答案是使用 graphql-parse-resolve-info
库来解析 info
参数。
该库“info
字段,graphql-fields
。
您可以从 info
参数生成 MongoDB 投影。这是您可以遵循的示例代码
/**
* @description - Gets MongoDB projection from graphql query
*
* @return { object }
* @param { object } info
* @param { model } model - MongoDB model for referencing
*/
function getDBProjection(info, model) {
const {
schema: { obj }
} = model;
const keys = Object.keys(obj);
const projection = {};
const { selections } = info.fieldNodes[0].selectionSet;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const isSelected = selections.some(
selection => selection.name.value === key
);
projection[key] = isSelected;
}
console.log(projection);
}
module.exports = getDBProjection;
通过一些辅助函数,您可以像这样使用它(typescript 版本):
import { parceGqlInfo, query } from "@backend";
import { GraphQLResolveInfo } from "graphql";
export const user = async (parent: unknown, args: unknown, ctx: unknown, info: GraphQLResolveInfo): Promise<User | null> => {
const { dbQueryStr } = parceGqlInfo(info, userFields, "id");
const [user] = await query(`SELECT ${dbQueryStr} FROM users WHERE id=;`, [1]);
return user;
};
辅助功能。
几点:
gql_uid用作ID!来自主键的字符串类型不更改数据库类型
必需选项用于数据加载器(如果用户未请求字段)
allowedFields 用于过滤信息中的附加字段,例如“__typename”
queryPrefix 如果您需要为所选字段添加前缀,例如
select u.id from users u
const userFields = [ "gql_uid", "id", "email" ] // merge arrays and delete duplicates export const mergeDedupe = <T>(arr: any[][]): T => { // @ts-ignore return ([...new Set([].concat(...arr))] as unknown) as T; }; import { parse, simplify, ResolveTree } from "graphql-parse-resolve-info"; import { GraphQLResolveInfo } from "graphql"; export const getQueryFieldsFromInfo = <Required = string>(info: GraphQLResolveInfo, options: { required?: Required[] } = {}): string[] => { const { fields } = simplify(parse(info) as ResolveTree, info.returnType) as { fields: { [key: string]: { name: string } } }; let astFields = Object.entries(fields).map(([, v]) => v.name); if (options.required) { astFields = mergeDedupe([astFields, options.required]); } return astFields; }; export const onlyAllowedFields = <T extends string | number>(raw: T[] | readonly T[], allowed: T[] | readonly T[]): T[] => { return allowed.filter((f) => raw.includes(f)); }; export const parceGqlInfo = ( info: GraphQLResolveInfo, allowedFields: string[] | readonly string[], gqlUidDbAlliasField: string, options: { required?: string[]; queryPrefix?: string } = {} ): { pureDbFields: string[]; gqlUidRequested: boolean; dbQueryStr: string } => { const fieldsWithGqlUid = onlyAllowedFields(getQueryFieldsFromInfo(info, options), allowedFields); return { pureDbFields: fieldsWithGqlUid.filter((i) => i !== "gql_uid"), gqlUidRequested: fieldsWithGqlUid.includes("gql_uid"), dbQueryStr: fieldsWithGqlUid .map((f) => { const dbQueryStrField = f === "gql_uid" ? `${gqlUidDbAlliasField}::Text AS gql_uid` : f; return options.queryPrefix ? `${options.queryPrefix}.${dbQueryStrField}` : dbQueryStrField; }) .join(), };
};