☰
深入解析 Graffle anyware 的 pack 钩子与 body 插槽:如何改写 GraphQL 请求体
2026/10/10 11:57:31 网站建设 项目流程
  • 后端

【免费下载链接】graffle

Simple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.

项目地址:https://gitcode.com/gh_mirrors/gr/graffle
点击查看免费下载

Graffle 是 JavaScript 生态中一个极简、可扩展、类型安全的 GraphQL 客户端,其 anyware 机制让开发者可以像中间件一样介入请求管线的每个环节。本篇以官方示例 "Slot Body"(示例页面)为骨架,系统讲解在pack钩子上通过body插槽定制 POST 请求体的完整用法,并深入到请求管线与 HTTP 传输扩展的源码实现,帮助你掌握利用 anyware 改写请求体、控制 operationName 的实战能力。

背景:anyware 与请求管线的五个钩子

在使用body插槽之前,需要先理解 anyware 所围绕的请求管线。Graffle 的每次请求都会依次经过五个阶段,这在 src/requestPipeline/RequestPipeline.ts 中由stepName常量定义:

钩子职责
encode依据 Schema 驱动数据映射(SDDM)与标量配置,将请求变量编码、规范化查询
pack将请求打包成传输层需要的形态(HTTP 请求方法、URL、请求体等)
exchange执行真正的传输交互(如调用fetch)
unpack拆解传输层响应(如解析 JSON)
decode解码结果数据并返回最终执行结果

anyware允许你在任意钩子上注入中间件:既可以像 anyware_jump-start__jump-start.ts 那样跳过前序钩子直接从exchange起步,也可以像 anyware_short-circuit__short-circuit.ts 那样提前短路。而本篇文章的主角——body插槽,正是pack钩子暴露的定制点之一。

什么是 pack 钩子上的 body 插槽

在 Graffle 中,pack步骤负责把内部请求对象转换为传输层请求。对于 HTTP 传输(POST 模式)而言,最终发出去的请求体默认是一段 JSON 字符串。body插槽就是pack阶段用来生成这段请求体的扩展点:

  • 它的输入是一个graphqlRequest对象,包含operationName、variables和query字段;
  • 它的返回值会被直接用作fetch的body(BodyInit)。

在 TransportHttp.ts 中可以看到,HTTP 传输为pack注册了两个插槽:searchParams与body,其中body的默认实现是postRequestEncodeBody(POST 模式下的请求体编码函数)。实际组装请求时,run函数会调用slots.body(graphqlRequest)来产出 POST 请求体(见 TransportHttp.ts 附近源码)。换句话说:你通过using提供的body函数会覆盖默认编码逻辑,成为最终请求体的唯一来源。

官方示例:改写 operationName 让服务器执行另一条查询

下面就是 "Slot Body" 示例的完整可运行代码(仓库源码见 anyware_slot_slot-body__slot-body.ts):

import { Graffle } from 'graffle' const graffle = Graffle .create() .transport({ url: `http://localhost:3000/graphql` }) .anyware(async ({ pack }) => { return await pack({ using: { body(graphqlRequest) { return JSON.stringify({ ...graphqlRequest, operationName: `trainers`, }) }, }, }) }) const result = await graffle.gql(` query pokemons { pokemons { name } } query trainers { trainers { name } } `) .pokemons() console.log(result)

逐行拆解这段代码:

  1. 创建客户端并配置传输:.transport({ url })指定 GraphQL 端点,默认使用 POST 模式(methodMode默认值为post,见 TransportHttp.ts)。
  2. 注册 anyware 中间件:.anyware(async ({ pack }) => ...)解构出pack钩子。
  3. 调用pack并传入using:pack({ using: { body(...) } })中using是一个"插槽覆盖"配置对象,这里提供了自定义的body函数。
  4. 改写请求体:函数接收graphqlRequest,通过展开运算符保留原始字段,仅把operationName强制改为trainers,再JSON.stringify序列化为字符串。
  5. 发出请求:虽然链式调用选择的是.pokemons(),但由于请求体中的operationName已被改写为trainers,服务器实际执行的是trainers查询。

最终控制台输出(仓库中的记录见 anyware_slot_slot-body__slot-body.output.txt):

{ trainers: [ { name: 'Ash' }, { name: 'Misty' }, { name: 'Brock' }, { name: 'Gary' } ] }

这正是body插槽的核心价值:在请求真正发出前,你有机会对请求体做任意改写——换操作、注入自定义字段、加密、压缩、调整标量序列化方式等。

工作原理:body 插槽在源码中的调用链

为了准确理解插槽被调用的时机,我们可以追踪源码中的关键链路:

  • 在 RequestPipeline.ts 中,管线明确定义了encode → pack → exchange → unpack → decode五个步骤,其中pack步骤的输出(PackInput)携带内部请求对象与上下文状态。
  • 在 TransportHttp.ts 中,HTTP 传输的pack步骤先把内部请求整理成graphqlRequest(含operationName、variables、query),然后根据methodMode与操作类型决定 HTTP 方法:
    • POST 时:body: slots.body(graphqlRequest),即请求体来自body插槽;
    • GET(getReads)时:改用slots.searchParams(graphqlRequest)把负载拼进 URL 查询参数。
  • 自定义的body插槽通过pack({ using: { body } })注入后,会在该步骤运行时被调用,返回的字符串直接成为fetch的body。

从源码结构可以推断:using中的插槽覆盖是按钩子粒度生效的,body只影响请求体生成环节,不会干扰encode(变量编码)与exchange(网络交互)等其余阶段,因此你可以只定制请求体而保留其他默认行为。

对比实战:body 插槽与 searchParams 插槽

body并非pack钩子唯一的插槽。当传输配置为methodMode: 'getReads'时,Graffle 会把请求负载放进 URL 查询参数,对应的插槽是searchParams。仓库中的姊妹示例 anyware_slot_slot-body__slot-search-params.ts 展示了这种用法:

const graffle = Graffle .create() .transport({ url: `http://localhost:3000/graphql`, methodMode: `getReads` }) .anyware(async ({ pack }) => { return await pack({ using: { searchParams: (graphqlRequest) => { return { query: graphqlRequest.query, operationName: `getPokemons`, } }, }, }) })

两个插槽的差异可以总结为:

插槽适用场景返回值影响对象
bodyPOST 请求BodyInit(通常是 JSON 字符串)请求体
searchParamsgetReads模式下的 GET 请求查询参数对象(含query、operationName等)URL 查询字符串

值得注意的是,getReads模式下只有读取操作(read 类)走 GET,写入操作仍回退为 POST(见 TransportHttp.ts 的请求方法决策逻辑)。因此如果你的客户端同时存在读写操作,body插槽仍然会用于其中的 POST 请求。

更多插槽:exchange 钩子上的 fetch 插槽

pack之外,其他钩子也有各自的插槽,理解它们有助于建立完整的 anyware 心智模型。例如 anyware_slot_slot-fetch__slot-fetch.ts 演示了在exchange钩子上覆盖fetch插槽——完全接管网络请求,直接返回一个伪造的Response:

const graffle = Graffle .create() .transport({ url: `http://localhost:3000/graphql` }) .anyware(async ({ exchange }) => { return await exchange({ using: { fetch: () => { return new Response(JSON.stringify({ data: { trainers: [{ name: `Jason` }] } })) }, }, }) })

在 TransportHttp.ts 中可以看到,exchange步骤默认调用slots.fetch(url, init)发起网络请求;覆盖它之后,整个"网络往返"都被替换为本地模拟。这通常用于测试、缓存或离线场景,与body插槽形成互补:body改请求体,fetch改请求执行本身。

实战进阶:Upload 扩展如何复用 body 插槽

body插槽不仅出现在官方示例中,Graffle 自带的 Upload 扩展(src/extensions/Upload/Upload.ts)也用它实现了 multipart/form-data 文件上传。当检测到请求变量中包含Blob实例时(isUploadRequest),Upload 扩展通过pack({ using: { body(input) { ... } } })覆盖body插槽,返回createBody({ query, variables })生成的 multipart 表单体,同时清空content-type头,让fetch根据FormData自动设置带 boundary 的 Content-Type。

这说明一个重要的工程结论:anyware 的插槽是 Graffle 自身扩展系统的公共机制——内置扩展和用户代码使用完全相同的 API,你在掌握body插槽后,既可以直接写中间件,也可以借此理解甚至编写自定义扩展。

类型安全与测试保障

从源码测试可以看出这套机制在类型层面的严谨性。在 src/client/methods/anyware.test.ts 中:

  • anyware中间件会被收集到requestPipelineInterceptors,且不影响客户端上下文类型;
  • 在pack钩子内可以通过pack.input.transportType精确判断当前使用的传输(存在多个传输时,类型会被推断为联合类型),便于在中间件中针对 HTTP 传输做条件处理。

这意味着你在编写body插槽时,graphqlRequest参数具备完整的类型提示;而测试文件本身也验证了anyware中间件注册与调用的正确性,你可以参照它为自己的中间件编写类型断言与行为测试。

小结

围绕 Graffle 官方 "Slot Body" 示例,本文覆盖了以下核心内容:

  1. 管线模型:encode → pack → exchange → unpack → decode五个钩子,pack负责打包传输请求;
  2. body插槽:pack({ using: { body(graphqlRequest) {...} } })可完全接管 POST 请求体生成,示例中通过改写operationName让服务器执行目标查询;
  3. 源码依据:默认实现与调用点位于 TransportHttp.ts 的pack步骤(slots.body(graphqlRequest)),管线定义位于 RequestPipeline.ts;
  4. 横向对比:body(POST)与searchParams(GET)分别定制请求体与 URL 参数,exchange.fetch则可替换整个网络执行;
  5. 真实案例:Upload 扩展内部同样借助body插槽实现 multipart 上传,证明该机制是 Graffle 扩展体系的标准入口。

掌握了body插槽,你就掌握了 Graffle anyware 中"请求出站前最后一公里"的控制权。想要继续深入,建议依次阅读仓库中的 slot-fetch 示例、slot-search-params 示例 以及 jump-start 示例,并结合 anyware 测试 与 HTTP 传输源码 验证你的理解。

  • 后端

【免费下载链接】graffle

Simple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.

项目地址:https://gitcode.com/gh_mirrors/gr/graffle
点击查看免费下载
上一篇:ComfyUI-Manager 工作流分享 API 实战:5 分钟从画布配方到多平台发布通道
下一篇:GTA5线上小助手怎么用?一文看懂免费开源线上工具的五大功能

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询