跳至主要内容

计划解析器

在规划 GraphQL 操作时,我们将每个操作中请求的字段的计划组合成一个执行计划,然后对其进行优化。 “字段计划解析器”是详细说明每个字段计划的函数(在参数、输入字段甚至枚举值上也可以有计划解析器;但这比大多数人需要的更高级)。

计划解析器是同步的(它们不能返回 promise),因为它们不处理实际的运行时数据,而是详细说明处理运行时将看到的所有可能数据的步骤。

字段计划解析器

在操作规划阶段,每次引用一个字段时,都会调用该字段的计划解析器,并将结果合并到操作计划中。在调用字段解析器时,Grafast 会传递“父步骤”和“字段参数”对象。计划解析器可以创建任意数量的中间步骤,但必须返回一个代表字段结果的步骤。

对于具有多态类型的字段,返回的步骤必须是多态能力计划(参见 多态)。对于具有列表类型的字段,返回的步骤在执行时必须生成列表。

计划解析器可以替代或补充传统解析器。由于计划解析器只在规划操作时运行,而不是在执行操作时运行,因此它们无法访问任何数据,只能访问其他步骤。

与常规解析器类似,计划解析器的第一个和第二个参数分别代表父数据和参数。但是,由于我们处理的是潜在数据而不是具体数据,因此这两个参数略有不同。

父步骤

第一个参数“父步骤”是一个步骤,代表来自父级的数据。当父字段具有对象类型时,“父步骤”只是父字段解析到的步骤。当父字段具有列表或多态类型时,“父步骤”将是解析后的步骤,代表列表中的一个条目或多态的具体对象类型。

字段参数

第二个参数“字段参数”(fieldArgs)是一个对象,具有访问方法来读取参数。我们将在下面的 FieldArgs 部分中详细介绍。

示例

计划解析器可能看起来像这样

function plan_resolver(
$parent: ExecutableStep,
args: FieldArgs,
): ExecutableStep {
const $friends = $parent.getRelation("friends");
$friends.limit(args.get("limit"));
return $friends;
}
注意

按照惯例,当变量代表一个步骤时,我们用 $ 开头变量名。

当然,计划解析器函数的实际主体将根据您自己的应用程序需求而有所不同。

指定字段计划解析器

在以编程方式构建 GraphQL 架构时,计划解析器存储在字段的 extensions.grafast.plan 中;例如

import { GraphQLSchema, GraphQLObjectType, GraphQLInt } from "graphql";
import { constant } from "grafast";

const Query = new GraphQLObjectType({
name: "Query",
fields: {
meaningOfLife: {
type: GraphQLInt,
extensions: {
grafast: {
plan() {
return constant(42);
},
},
},
},
},
});

export const schema = new GraphQLSchema({
query: Query,
});

如果您使用的是 makeGrafastSchema,则对象类型 typeName 上字段 fieldName 的字段计划解析器将通过 plans[typeName][fieldName] 属性指示。

import { makeGrafastSchema, constant } from "grafast";

export const schema = makeGrafastSchema({
typeDefs: /* GraphQL */ `
type Query {
meaningOfLife: Int
}
`,
plans: {
Query: {
meaningOfLife() {
return constant(42);
},
},
},
});

断言对象类型的步骤

Grafast 中的对象类型可以指示它们必须由特定步骤或一组步骤表示,以确保这些步骤上的方法可用于字段计划解析器;这有助于尽早发现错误。

这种指示采用两种形式之一,要么是显式地是步骤类本身,要么是断言函数,该函数检查传入的步骤是否为适当类型,否则抛出错误。

在以编程方式定义模式时,assertStep 通过 objectTypeConfig.extensions.grafast.assertStep 定义,例如

import { GraphQLObjectType } from "graphql";
import { ObjectStep } from "grafast";

const MyObject = new GraphQLObjectType({
name: "MyObject",
extensions: {
grafast: {
assertStep: ObjectStep,
/* Or:
assertStep($step) {
if ($step instanceof ObjectStep) return;
throw new Error(`Type 'MyObject' expects a step of type ObjectStep; instead received a step of type '${$step.constructor.name}'`);
}
*/
},
},
fields: {
a: {
extensions: {
grafast: {
plan($obj: ObjectStep) {
return $obj.get("a");
},
},
},
},
},
});

当通过 makeGrafastSchema 定义时,我们不能直接调用属性 assertStep,因为它可能与字段名称冲突,因此我们使用 __assertStep,知道 GraphQL 禁止字段以 __(两个下划线)开头,因为这些名称保留用于自省。

import { makeGrafastSchema, ObjectStep } from "grafast";

const schema = makeGrafastSchema({
typeDefs: /* GraphQL */ `
type MyObject {
a: Int
}
`,
plans: {
MyObject: {
__assertStep: ObjectStep,
/* Or:
__assertStep($step) {
if ($step instanceof ObjectStep) return;
throw new Error(`Type 'MyObject' expects a step of type ObjectStep; instead received a step of type '${$step.constructor.name}'`);
}
*/
a($obj: ObjectStep) {
return $obj.get("a");
},
},
},
});
提示

通常,添加步骤断言是可选的;但是,当存在联合类型或接口类型时,其中的所有类型都必须就是否期望步骤达成一致。如果您想在所有地方都要求步骤,但您不关心特定类型的步骤是什么,您可以使用 __assertStep: ExecutableStep__assertStep: () => true

参数和输入字段计划解析器

提示

本节非常高级,很少使用,因此您可以随意跳过下一小节。它包含在这里只是因为 FieldArgs(下一节)依赖于这些行为(如果存在)。

除了字段计划解析器,Grafast 还允许您将 inputPlan 和/或 applyPlan 附加到单个参数或输入对象上的输入字段。这些计划解析器的工作方式略有不同。

inputPlan 计划解析器

inputPlan 计划解析器可以存在于参数或输入字段上。它传递三个参数

  1. 父计划
  2. 相对于此参数或输入字段的 FieldArgs
  3. 附加信息(待办事项:文档化)

inputPlan 必须返回一个步骤,该步骤将在 FieldArgs.get 引用它时用作参数或输入值的原始值。

applyPlan 计划解析器

applyPlan 计划解析器可以存在于参数或输入字段上。它传递三个参数

  1. 目标步骤 - 要应用更改的步骤
  2. 相对于此参数或输入字段的 FieldArgs
  3. 附加信息(待办事项:文档化)

applyPlan 可以直接操作目标步骤,也可以返回一个 ModifierStep,它将收集更改,然后一次性应用所有更改。ModifierStep 传递给子输入字段的 applyPlan 计划解析器(如果有),以允许更改累积。这在构建用于突变的“补丁”对象或用于集合的“过滤器”对象时特别有用。

FieldArgs

FieldArgs 对象提供对字段参数(在字段计划解析器中使用)或输入对象的输入字段(在 applyPlan/inputPlan 中使用)或其他输入的类似访问权限。它包含三个方法

FieldArgs.get

将此方法传递您要获取的参数名称,或通过参数和输入对象(但不是列表)到您想要的值的路径,您将收到一个代表该值的步骤。

如果参数或输入对象字段具有 inputPlan 方法,它将被调用,并且它返回的步骤将用于代替代表值的原始步骤。

注意

您也可以在没有参数的情况下调用 fieldArgs.get() 来获取当前参数/输入字段的值;但这只能在 inputPlanapplyPlan 内部完成。

FieldArgs.getRaw

与 FieldArgs.get 相同,只是它会忽略任何 inputPlan,只返回一个表示原始值的步骤。

FieldArgs.apply

将此方法传递给要应用的步骤,以及要应用的参数名称或要应用的输入字段的路径(但不包括列表),以及该参数或输入字段的 applyPlan 将应用于给定的步骤。

TODO:用示例扩展本节,说明为什么要执行这些操作。

自动应用 applyPlan 计划解析器

危险

本节现在错误。我们更改了此行为,现在您需要添加 autoApplyAfterParentApplyPlan 或类似内容以触发您的字段自动应用(或 autoApplyAfterParentPlan 用于参数)。

TODO:修复此文档。

FieldArgs 会跟踪您 .get().getRaw().apply() 的参数/输入字段,如果存在任何未访问的字段具有 applyPlan 方法,则会自动调用这些方法,并将字段计划解析器生成的步骤作为参数传递。

例如,这允许您将“first”行为与参数关联,而不是与计划解析器关联,这样您就可以通过一个公共对象共享 first/last/before/after 行为,该对象会传播到连接上的每个参数中,而不是必须在每个计划解析器函数中重写逻辑。