
后端API网关【免费下载链接】crystal Graphiles Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址https://gitcode.com/gh_mirrors/cry/crystal点击查看免费下载导读Graphile Build 是 Graphile 生态中用于从任意数据源自动生成灵活、可扩展 GraphQL API的核心工具库项目入口文档。本文以 v5 版官方文档 plugins.md 为主线系统讲解其插件与预设Preset机制插件如何通过inflection、gather、schema三个 scope 介入 Schema 构建的三个阶段Preset 如何组合与继承以及如何从零编写一个可运行的 Graphile Build 插件。读完本文你将掌握插件对象的结构、三种 scope 的职责边界、hooks 的注册与调用方式并能结合仓库源码理解这些机制在 graphile-build 源码 中的真实实现。插件系统与 Preset 的基础认知Graphile Build 的插件系统并非自研而是由独立的 graphile-config 模块提供。graphile-config与任何具体项目无关——它被 Graphile Build、PostGraphile、Gra*fast*、Grafserv 等 Graphile 生态成员共同使用统一处理插件与预设系统的通用需求组合多个插件composing plugins对插件进行排序ordering处理插件之间的依赖关系handling dependencies禁用插件disabling plugins而每个具体场景的细节则由命名作用域named scopes来承载。作用域就是插件对象上的根级键。Graphile Build 插件就是实现了以下一个或多个 scope 的graphile-config插件inflection—— 负责命名gather—— 负责收集数据schema—— 负责构建 GraphQL Schema说明完全没有上述任一 scope 的插件依然可以被放进 Graphile Build 的 preset 中只是不会产生任何效果。这让用户可以在多个项目之间共享同一份配置。从源码看graphile-config通过resolvePreset将原始 preset 解析为已解析的 presetResolvedPreset再交给getBuilder创建SchemaBuilder实例getBuilder 中还会检查 preset 是否包含插件否则直接抛错并警告缺少QueryPlugin的配置——这印证了插件是 Schema 构建的必备要素这一事实。Presets配置的集合与继承graphile-config同时提供 Graphile Build 使用的 preset 系统。preset 就是一组 preset、插件和配置选项的集合。preset 可以继承自其他 preset通过extends数组最典型的做法是让自定义 preset 先继承 Graphile Build 的defaultPresetimport { defaultPreset } from graphile-build; export default { extends: [defaultPreset], };defaultPreset由仓库在 preset.ts 中定义并随graphile-build包一并导出见 index.ts 的export { defaultPreset }。它聚合了 QueryPlugin、MutationPlugin、SubscriptionPlugin、NodePlugin、CommonTypesPlugin 等一批内置插件为构建一个可用的 GraphQL Schema 提供基线能力。有了 preset 之后把它喂给 Graphile Build 的相关方法即可例如buildSchemaimport { buildSchema } from graphile-build; import { printSchema } from graphql; import preset from ./graphile.config.mjs; const schema await buildSchema(preset); console.log(printSchema(schema));buildSchema是 Graphile Build 对外暴露的核心入口之一其实现位于 index.ts它会将GraphileBuildLibPreset与传入的 rawPreset 合并创建SchemaBuilder并调用builder.buildSchema(input)。若 preset 配置了exportSchemaSDLPath或exportSchemaIntrospectionResultPath构建完成后还会把 Schema 以 SDL 或 introspection JSON 的形式写入磁盘见writeOutputsindex.ts。三个阶段inflection → gather → schema理解插件 scope 之前必须先理解 Schema 构建的三个阶段。它们顺序执行各自对应一类插件职责inflection阶段注册和定制各类 inflector控制事物命名的函数。这个阶段是第一个发生的阶段index.ts 的注释明确写道inflection 阶段是使用 Graphile Build 构建 Schema 时发生的第一个阶段它负责命名事物——既包括 gather 阶段生成的东西也包括 GraphQL Schema 中最终的 types、fields、arguments、directives 等。gather阶段收集数据例如对数据库执行 introspection。源码注释index.ts称其为第二阶段负责审视所有可能影响 Schema 形态的东西并把它们转化为 schema 阶段的输入。schema阶段确定 gather 阶段所有实体的行为然后生成最终的 GraphQL Schema——拿到 gather 阶段的输入并使用 inflection 阶段的 inflector生成最终 GraphQL Schema。inflectionscope控制命名如果插件想给事物命名或改变命名方式就实现inflectionscope。下面这个插件替换了builtininflector使根类型Query、Mutation、Subscription被重命名为RootQuery、RootMutation、RootSubscriptionconst RootNamingPlugin { name: RootNamingPlugin, version: 0.0.0, description: Prefixes Root to the root operation types, inflection: { replace: { builtin(previous, options, text) { if ([Query, Mutation, Subscription].includes(text)) { return Root${text}; } else { return previous(text); } }, }, }, };注意replace中的回调签名第一个参数previous是被替换的旧 inflector当需要透传给其他情况时调用它第二个参数options是已解析的 preset之后才是 inflector 自身的参数这里是text。在源码层面buildInflection 完整实现了这一机制先用makeInitialInflection()生成一组内置 inflector 作为基础然后遍历所有插件执行plugin.inflection?.add注册新 inflectoradd语义若同名已存在则不会覆盖最后通过orderedApply按顺序执行plugin.inflection?.replacereplace语义直接覆盖已有 inflector。如果尝试替换一个不存在的 inflector会输出警告若插件设置了ignoreReplaceIfNotExists且包含该名字则不警告。这里也印证了接口定义index.tsinflection配置包含add定义新 inflector、replace覆盖已有 inflector、ignoreReplaceIfNotExists抑制不存在警告三个可选键。gatherscope收集数据如果插件需要做异步工作——例如从远程数据源收集数据或从文件读取内容——就应通过gatherscope 完成。源码中gather的完整生命周期由 gatherBase 实现它揭示了该 scope 的丰富结构namespace插件用于在共享状态中注册的唯一命名空间。两个插件注册相同命名空间会直接抛错namespaces must be unique。initialCache若插件支持持久化内部状态这是 watch 模式的优化手段返回缓存初始值注意它不能直接返回 Promise。initialState每次新一轮 gather 执行时插件的初始状态可以是异步的。helpers插件必须注册 helper让其他插件能访问其内部状态注册 helper 的插件必须显式提供namespace。hooksgather 阶段可注册的钩子通过orderedApply有序注册。main负责启动数据收集向output对象写入 schema 阶段需要的数据。watch插件进入 watch 模式时被调用应注册监听并在检测到变化时调用回调返回一个取消监听的函数。对外暴露的gather一次性收集和watchGatherwatch 模式保证 callback 至少在 resolve 前被调用一次都在 index.ts 中定义后者是watchSchema实现热重载 Schema 的基础。schemascope影响生成的 GraphQL Schema绝大多数 Graphile Build 插件都会实现schemascope以影响正在构建的 GraphQL Schema。Schema 的构建方式是hook钩住传给 GraphQL.js 各个构造函数如GraphQLObjectType、GraphQLInputObjectType、GraphQLUnionType等的配置对象以及它们的一些配置字段如GraphQLObjectType的fields或interfaces字段有时甚至更深——最深的钩子是GraphQLObjectType_fields_field_args_arg它用于操控某个 GraphQL 对象类型上的某个字段的参数列表中的某个具体参数。从 index.ts 的接口定义可以看到完整的钩子层级GraphQLObjectType→GraphQLObjectType_interfaces→GraphQLObjectType_fields→GraphQLObjectType_fields_field→GraphQLObjectType_fields_field_args→GraphQLObjectType_fields_field_args_argGraphQLInputObjectType→GraphQLInputObjectType_fields→GraphQLInputObjectType_fields_fieldGraphQLEnumType→GraphQLEnumType_values→GraphQLEnumType_values_valueGraphQLUnionType→GraphQLUnionType_typesGraphQLInterfaceType→GraphQLInterfaceType_interfaces→GraphQLInterfaceType_fields→..._field→..._args→..._argGraphQLScalarType每个 hook 都接收三个参数被操作的实体配置first parameterbuild 对象对所有 hook 通用该 hook 专属的 context 对象它通过scope描述当前被 hook 的到底是什么。示例 1给根 Query 类型添加字段下面的插件 hook 住GraphQLObjectType_fields并通过context.scope.isRootQuery判断是否是要修改的对象从而给根查询类型新增一个字段import { constant } from grafast; const RootQueryFieldPlugin { name: RootQueryFieldPlugin, version: 0.0.0, description: Adds a field to the root Query type, schema: { hooks: { GraphQLObjectType_fields(fields, build, context) { // Only add the field to the root query type if (!context.scope.isRootQuery) return fields; // Add a field called meaningOfLife fields.meaningOfLife { // Its an integer type: build.graphql.GraphQLInt, // When you call the field, you should always return the number 42 plan() { return constant(42); }, }; return fields; }, }, }, };注意这里字段使用的是plan()而非resolve()——这是 Gra*fast* 生态的字段配置特征字段返回的是一个 Gra*fast* 步骤step由 Gra*fast* 引擎负责执行与批处理。build.graphql是相当于require(graphql)但能避免 GraphQL 版本冲突的引用方式。示例 2给所有 GraphQLObjectType 添加random字段这个插件给每一个生成的GraphQLObjectType都添加一个random(sides: Int)字段// No imports required! const MyRandomFieldPlugin { name: MyRandomFieldPlugin, version: 0.0.0, schema: { GraphQLObjectType_fields(fields, build, context) { const { extend, graphql: { GraphQLInt }, options: { myDefaultMin 1, myDefaultMax 100 }, } build; return extend(fields, { random: { type: GraphQLInt, args: { sides: { type: GraphQLInt, }, }, plan(_, fieldArgs) { const $sides fieldArgs.get(sides); return lambda( $sides, (sides) Math.floor( Math.random() * ((sides ?? myDefaultMax) - myDefaultMin 1), ) myDefaultMin, ); }, }, }); }, }, };这个示例展示了几个关键点注册位置在schema键下直接写GraphQLObjectType_fields(...)与写在schema.hooks.GraphQLObjectType_fields(...)等价它会对每个被构造的GraphQLObjectType的fields属性生效。回调三参数输入对象fields基本就是 graphql-js 的GraphQLFieldConfigMapBuild对象这里用到了extend和graphql.GraphQLIntContext对象此处被忽略但如果想筛选哪些对象获得random字段就用它。extend的用法build.extend(fields, {...})是非破坏性合并——把后者并入前者且不覆盖已有键返回前者。源码中 extend.ts 实现这是对象类 hook 的惯用返回值。自定义选项的读取build.options.myDefaultMin/myDefaultMax分别默认 1 和 100说明插件可以约定 preset 中的自定义配置项。Gra*fast* 字段配置返回的字段是GraphQLFieldConfig参见 graphql-js 文档但混入了 Gra*fast* 特性——最显著的是plan取代了resolve。fieldArgs.get(sides)拿到参数步骤lambda($sides, fn)将步骤值注入普通函数。Hooks 的调用语义与 Build 流程由于插件最常见的动作就是注册 schema hooks理解 hooks 文档 中的语义对写插件至关重要。核心模型GraphQL hooks 允许你在对象真正被构造之前操控传给 GraphQL 对象构造函数的参数specification。可以把 hooks 想象成原始对象规格外的一层层包装const MyType newWithHooks(GraphQLObjectType, spec); // 等价于 const MyType new GraphQLObjectType(hook3(hook2(hook1(spec))));同步返回每个 hook 回调必须同步返回一个值——要么是它收到的第一个参数要么是该参数的衍生物。出于性能考虑官方推荐直接修改输入对象mutating the input object。执行顺序同一 hook 名的多个钩子默认按注册顺序执行因此插件顺序有时很关键。插件作者应使用graphile-config的before/after特性声明插件或单个 hook 之间的顺序关系。Build 过程的七个阶段hookName必须匹配支持的 hooks 列表之一。构建的整体流程为创建带有基础功能的新 Build 对象。buildhook允许插件给 build 对象添加新的工具方法或覆盖已声明的方法注意此阶段禁止生成 GraphQL 对象。向 Build 对象添加一个Behavior 实例并为所有相关实体注册行为。冻结build 对象防止后续修改。inithook作为设置阶段所有可能的类型都通过build.registerObjectType、build.registerUnionType等注册这是唯一允许注册 GraphQL 类型的阶段。内部用newWithHooks(GraphQLSchema, …)构建 Schema依次执行GraphQLSchema、GraphQLSchema_typeshooks并按需触发 type、field、argument、value 等钩子。finalizehook允许插件用替代通常是衍生的Schema 替换已构建的 Schema或在返回前观察它一般仅用于断言——例如确保所有输入都被处理。这套 hook 系统让库变得强大灵活代价是可追踪性下降不再有清晰的声明式import被调用方法的来源可能在任何插件中、甚至多个插件中。官方建议参阅 PostGraphile 的调试文档debug envvars来缓解这一问题。延迟 hooksDeferred hooks凡位于 GraphQL 接受 thunk 位置的 hooks 都是延迟执行的GraphQL 在需要相关实体时才调用 thunk可能就在同一个 tick这允许类型通过字段彼此循环引用。这类 hooks 会通过context.Self拿到已经创建好的类型实例。Build 对象与 Context 对象Build 对象Build包含与当前 GraphQL API 构建相关的辅助工具和信息源。若处于 watch 模式每次生成新 Schema 都会使用新的 build 对象。插件可通过buildhook 扩展它一旦buildhook 完成对象即被冻结。最常用的方法build.extend(obj1, obj2, reason)—— 把obj2非破坏性合并进obj1不覆盖已有键并返回obj1通常作为对象类 hook 的返回值。build.append(array1, array2, key, reason)—— 把array2的条目推入array1用key识别并拒绝重复通常作为列表类 hook 的返回值。build.inflection—— 承载所有命名用的 inflector 函数。build.graphql—— 相当于require(graphql)但有助于避免 GraphQL 版本冲突。build.grafast—— 相当于require(grafast)同样避免版本冲突。Context 对象Context包含当前 hook 相关的信息。最重要的是scope描述对象为何存在便于其他 hook 检测对较深的 hook来自较浅 hook 的 scope 会合并进来此外还有Self—— 仅延迟 hooks 可用实体创建后才调用如GraphQLObjectType_fields即已创建的对象允许递归引用。fieldWithHooks(scope, spec)—— 在GraphQLObjectType_fields、GraphQLInputObjectType_fields、GraphQLInterfaceType_fields上可用当需要字段辅助函数或想定义 scope时用于添加字段。命名空间约定添加到 Build 对象或设置在Context.scope上的属性应该命名空间化以避免冲突。例如 PostGraphile 使用pg命名空间pgSql、pgIntrospection、isPgTableType等。第三方插件应使用不同命名空间避免与核心插件冲突。实战把插件与 Preset 组合起来综合以上内容一个完整的preset 自定义插件用法如下// graphile.config.mjs import { defaultPreset } from graphile-build; import { RootNamingPlugin, MyRandomFieldPlugin } from ./my-plugins.mjs; export default { extends: [defaultPreset], plugins: [ RootNamingPlugin, MyRandomFieldPlugin, // 可按需调整顺序存在先后依赖时使用 before/after 声明 ], options: { // 供插件通过 build.options 读取的自定义配置 myDefaultMin: 1, myDefaultMax: 100, }, };然后通过buildSchema(preset)或实验性的makeSchema(preset)index.ts支持retryOnInitFail自动重试生成 SchemawatchSchemaindex.ts则可在数据库结构变化时自动重建 Schema无需重启服务器。延伸阅读Hooks 详解hook 的三参数、Build/Context 对象与构建阶段Context 对象scope 与各类 context 属性支持的 hooks 全列表所有可注册的 hookNameGraphile Build 入门项目定位与最小示例核心实现index.tsbuildInflection/gatherBase/buildSchema/makeSchema、preset.tsdefaultPreset、SchemaBuilder.tsSchema 构建器、extend.ts非破坏性合并graphile-config 通用插件基础设施graphile-configresolvePreset、orderedApply、AsyncHooks实用的高阶插件工厂如makeExtendSchemaPlugin、makeAddInflectorsPlugin、makeWrapPlansPlugingraphile-utils赞分享后端API网关【免费下载链接】crystal Graphiles Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址https://gitcode.com/gh_mirrors/cry/crystal点击查看免费下载相关推荐graphile-build 模块深入指南从 buildSchema 到 getBuilder 的插件化 GraphQL Schema 构建graphile build 模块深入指南从 buildSchema 到 getBuilder 的插件化 GraphQL Schema 构建 graphile后端API网关Graphile Build 插件系统完全指南基于 graphile-config 的插件、预设与 Schema Hooks 深入解析Graphile Build 插件系统完全指南基于 graphile config 的插件、预设与 Schema Hooks 深入解析 Graphile Bu后端API网关Graphile Build Schema Hooks 完全指南基于 graphile-build 的插件化 GraphQL Schema 构建机制Graphile Build Schema Hooks 完全指南基于 graphile build 的插件化 GraphQL Schema 构建机制 Grap后端API网关创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考