news 2026/10/10 9:04:21

Graffle Envelope 输出模式下强制抛错:envelope.errors 配置详解与源码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Graffle Envelope 输出模式下强制抛错:envelope.errors 配置详解与源码解析
  • 后端

【免费下载链接】graffle

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

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

本篇指南讲解 Graffle(Simple GraphQL Client for JavaScript)中output.envelope输出模式下的错误通道配置。你将掌握如何通过envelope.errors.execution与envelope.errors.other两个开关,决定执行错误与“其他错误”(如网络错误、扩展抛出的错误)是嵌入信封(envelope)的errors字段,还是直接抛出异常;并结合 Graffle 源码理解这一决策在运行时与类型层面的完整实现路径。

示例场景:开启信封,但依然抛错

Graffle 的 envelope 输出模式会把结果包装为{ data, errors, extensions }结构。默认情况下,当错误类别对应的开关为true时,错误会被放入 envelope 的errors数组中而非抛出;而本示例(website/content/examples/20_output/envelope-error-throw.md)演示的则是相反的场景:即使启用了 envelope,也强制让错误以异常形式抛出。

完整示例代码(examples/20_output/output_envelope_envelope_error-throw__envelope-error-throw.ts):

import { Graffle } from '../$/graffle/_.js' // dprint-ignore const pokemon = Graffle .create({ output: { envelope: { errors: { execution: false, other: false, // default } }, }, }) .anyware(({ encode: _ }) => { throw new Error(`Something went wrong.`) //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ }) await pokemon.query.pokemons({ name: true })

运行后输出(实际运行产物见 examples/outputs/20_output/output_envelope_envelope_error-throw__envelope-error-throw.output.txt):

/some/path/to/runPipeline.ts:XX return new ContextualError(message, { ^ ContextualError: There was an error in the interceptor "anonymous" (use named functions to improve this error message) while running hook "encode". at runPipeline (/some/path/to/runPipeline.ts:XX:XX) at async <anonymous> (/some/path/to/runner.ts:XX:XX) at async Module.run (/some/path/to/run.ts:XX:XX) at async sendRequest (/some/path/to/send.ts:XX:XX) at async executeRootField (/some/path/to/requestMethods.ts:XX:XX) at async <anonymous> (/some/path/to/output_envelope_envelope_error-throw__envelope-error-throw.ts:XX:XX) { context: { hookName: 'encode', source: 'extension', interceptorName: 'anonymous' }, cause: Error: Something went wrong. at <anonymous> (/some/path/to/output_envelope_envelope_error-throw__envelope-error-throw.ts:XX:XX) at applyBody (/some/path/to/runner.ts:XX:XX) } Node.js vXX.XX.XX

可以看到,await pokemon.query.pokemons(...)并没有返回 envelope 对象,而是直接抛出了ContextualError——它携带了原始错误(cause: Error: Something went wrong.)以及丰富的错误上下文(hookName、source、interceptorName)。

envelope.errors 两个开关的含义与默认值

output.envelope.errors是长格式(longhand)配置,控制两类错误的去向,其类型定义位于 src/context/fragments/configuration/output/configuration.ts:

配置项含义默认值
envelope.errors.execution执行错误:即传统 GraphQL 执行结果中errors字段里的错误(如字段解析失败、校验错误)true(嵌入 envelope)
envelope.errors.other其他错误:包括 HTTP 传输层 fetch 抛出的网络错误、扩展(extension)内抛出的错误等false(不嵌入,走默认通道)

其归一化默认值在源码default_对象中(configuration.ts):

const default_ = { defaults: { errorChannel: `throw`, // 默认错误通道:抛出 }, envelope: { enabled: false, // 信封默认关闭 errors: { execution: true, // 执行错误默认嵌入信封 other: false, // 其他错误默认不嵌入 }, }, errors: { execution: `default`, other: `default`, }, }

注意这里的两层设计:

  1. errors.execution / errors.other(顶层)可显式指定'throw' | 'return' | 'default','default'表示回落到defaults.errorChannel(默认为'throw')。
  2. envelope.errors.execution / other(布尔)控制是否把对应类别错误嵌入信封;当其为false时,该类别错误不进入信封,转而按顶层错误通道处理。

本示例将两者都设为false,于是扩展抛出的错误既不进信封,又因默认通道是throw而直接抛出。

对比:嵌入信封的版本

为了看清“抛出”与“嵌入”的区别,可对比姊妹示例 website/content/examples/20_output/envelope-error.md 及其源码 examples/20_output/output_envelope_envelope-error__envelope-error.ts。它只把两个开关改为true:

const pokemon = Graffle .create({ output: { envelope: { errors: { execution: true, // default other: true, }, }, }, }) .anyware(({ encode: _ }) => { throw new Error(`Something went wrong.`) }) const result = await pokemon.query.pokemons({ name: true }) console.log(result)

此时同样的错误不再抛出,而是出现在返回结果的errors数组中(见 examples/outputs/20_output/output_envelope_envelope-error__envelope-error.output.txt):

{ errors: [ ContextualError: There was an error in the interceptor "anonymous" (use named functions to improve this error message) while running hook "encode". ... context: { hookName: 'encode', source: 'extension', interceptorName: 'anonymous' }, cause: Error: Something went wrong. ... } ] }

两张输出对比一目了然:同一处throw new Error(...),开关为true时错误被俘获进信封;开关为false时错误被原样抛出。这正是 Graffle 输出配置“细粒度控制”能力的体现。

源码解析:错误通道是如何被决策的

运行时:readErrorCategoryOutputChannel 与 handleOutput

错误最终的走向由客户端输出处理函数handleOutput决定(src/client/handle.ts)。它先从配置读取每个错误类别的实际通道:

export const readErrorCategoryOutputChannel = ( output: Normalized, errorCategory: ErrorCategory, ): OutputChannel | false => { if (output.errors[errorCategory] === `default`) { return output.defaults.errorChannel } return output.errors[errorCategory] }

(见 configuration.ts)——即'default'回落为defaults.errorChannel。

随后handleOutput依据“是否启用信封 + 该类别是否嵌入信封”组合出四个布尔判断(handle.ts):

const isThrowOther = readErrorCategoryOutputChannel(c, `other`) === `throw` && (!c.envelope.enabled || !c.envelope.errors.other) const isReturnOther = readErrorCategoryOutputChannel(c, `other`) === `return` && (!c.envelope.enabled || !c.envelope.errors.other) const isThrowExecution = readErrorCategoryOutputChannel(c, `execution`) === `throw` && (!c.envelope.enabled || !c.envelope.errors.execution) const isReturnExecution = readErrorCategoryOutputChannel(c, `execution`) === `return` && (!c.envelope.enabled || !c.envelope.errors.execution)

对“其他错误”(本示例由 anyware 扩展在encode钩子抛出)而言,当结果为Error时:

if (result instanceof Error) { if (isThrowOther) throw result if (isReturnOther) return result return isEnvelope ? { errors: [result] } : result }

在本示例配置(envelope.enabled为真、envelope.errors.other为false、默认通道为throw)下,isThrowOther === true,于是throw result——错误直接抛出,封装为ContextualError。ContextualError来自@wollybeard/kit的Err命名空间(handle.ts 中同类错误亦用new Err.ContextualError构造),其context字段记录了hookName: 'encode'、source: 'extension'、interceptorName: 'anonymous',cause指向原始Error: Something went wrong.。这也解释了输出信息中“use named functions to improve this error message”的提示——给 anyware 拦截器命名可以提升排错体验。

类型层面:HandleOutput 如何推导返回类型

handleOutput的运行时分支在类型系统中有对应的映射(handle.ts)。关键的ConfigGetOutputError会先判断信封是否启用:

export type ConfigGetOutputError< $OutputConfig extends Normalized, $ErrorCategory extends ErrorCategory, > = $OutputConfig['envelope']['enabled'] extends true ? ConfigGetOutputEnvelopeErrorChannel<$OutputConfig, $ErrorCategory> : ConfigResolveOutputErrorChannel<$OutputConfig, $OutputConfig['errors'][$ErrorCategory]>

而ConfigGetOutputEnvelopeErrorChannel在“该类别嵌入信封为true”时返回false(即不会作为错误返回),否则回落解析真实通道:

type ConfigGetOutputEnvelopeErrorChannel< $OutputConfig extends Normalized, $ErrorCategory extends ErrorCategory, > = $OutputConfig['envelope']['errors'][$ErrorCategory] extends true ? false : ConfigResolveOutputErrorChannel<$OutputConfig, $OutputConfig['errors'][$ErrorCategory]>

配合IfConfiguredGetOutputErrorReturns(handle.ts),当类别通道解析为'return'时返回类型会并入对应错误类型。因此本示例中envelope.errors.other: false意味着 TS 推导的返回类型不会包含“返回错误”的分支——调用方可以放心地按成功数据或异常处理。

进一步组合:return 通道与信封共存

除了throw,Graffle 还支持把错误放进返回值而非抛出。将默认错误通道改为'return'(不启用信封)的示例见 website/content/examples/20_output/return-error.md 与 website/content/examples/20_output/return-error-execution.md:

const pokemon = Graffle .create({ output: { envelope: false, defaults: { errorChannel: `return`, }, }, }) .anyware(({ encode: _ }) => { throw new Error(`Something went wrong.`) }) const pokemons = await pokemon.query.pokemons({ name: true })

此时pokemons的类型会推导为“数据 | ContextualError”的联合(示例中通过type _pokemons = typeof pokemons上的// ^?悬浮提示可见),调用方必须显式判别错误分支。这与本示例“信封 + 抛出”形成两种互补的错误处理风格:

  • 信封 +errors: { execution: false, other: false }:错误抛出(throw),异常路径清晰;
  • 信封 +errors: { execution: true, other: true }:错误嵌入信封errors数组,数据路径统一;
  • 关闭信封 +errorChannel: 'return':错误作为返回值返回,类型系统强制处理。

小结

Graffle 的output配置用“错误类别(execution/other)× 输出通道(throw/return/ 信封嵌入)”的二维模型,为错误处理提供了细粒度的控制:

  • envelope.errors.execution与envelope.errors.other控制对应错误是否嵌入信封;
  • 顶层errors.execution / errors.other与defaults.errorChannel决定未嵌入信封时的最终通道(默认throw);
  • 运行时由 handleOutput 执行分支决策,类型层面由HandleOutput系列类型同步推导返回类型,保证“配置即类型”。

当你在使用 envelope 输出模式却希望错误“藏不住”时,只需把对应类别开关设为false,就能得到本示例中直接抛出ContextualError的行为——既保留信封对成功数据{ data }的结构化包装,又让异常不被吞没。

  • 后端

【免费下载链接】graffle

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

项目地址:https://gitcode.com/gh_mirrors/gr/graffle
点击查看免费下载
上一篇:g2b-sanctioned-supplier 技能实战:经 k-skill-proxy 查询韩国调达厅(나라장터)不正当制裁企业信息
下一篇:`policy.useIsAuthorized`:在 Wagmi 中检查地址是否被转账策略授权

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 9:04:03

Java 线程 6 大状态详解与状态流转

线程一共6 种状态。线程在生命周期内&#xff0c;会随着代码执行、锁竞争、等待操作&#xff0c;在不同状态之间切换。注意&#xff1a;Java 线程状态和操作系统内核线程状态不是完全等同的&#xff0c;我们这里讨论的是 Java 虚拟机层面定义的线程状态。1. 线程的总数量和含义…

作者头像 李华
网站建设 2026/10/10 9:03:48

分享一个ZW3D二次开发查接口写示例的Agent_MCP工具

能干什么&#xff1a; 这是一个可用于AI Agent的MCP连接器&#xff0c;本地部署。 可查询ZW3D二次开发接口&#xff0c;编写示例&#xff0c;提供基于ZW3D功能的需求解决方案。 *本MCP有效期至20261031&#xff0c;届时视实际情况再更新版本 下载链接&#xff1a; MCP_ZW3DAPI…

作者头像 李华
网站建设 2026/10/10 9:00:43

Midjourney V6风格参考参数--sref详解:原理、调参与实战

用Midjourney V6出图的人&#xff0c;应该都有过这种经历&#xff1a;好不容易磨出一张满意的图&#xff0c;换个构图重新生成&#xff0c;风格却完全跑偏&#xff0c;同样的关键词在不同批次里出来的效果像换了个画师。直到V6版本把风格控制单独拎出来做成一个独立参数&#x…

作者头像 李华
网站建设 2026/10/10 9:00:29

VeapAI 实战(十二)多流程联动:公共服务清单与驱动规则

摘要&#xff1a;本文介绍 VeapAI 如何把「一个流程驱动另一个流程」的联动逻辑从散落的埋点代码&#xff0c;收敛为可视化配置。核心是两张表&#xff1a;公共服务清单&#xff08;wf_public_service&#xff09;声明可复用的联动能力&#xff0c;驱动规则&#xff08;wf_proc…

作者头像 李华
网站建设 2026/10/10 8:59:02

C语言实现小型编译器:从词法分析到四元式的完整指南

简介&#xff1a;面向编译原理课程设计与C语言进阶实践&#xff0c;这份资源以小型编译程序的完整实现为主线&#xff0c;覆盖词法分析、语法分析、语义分析与四元式生成等核心环节&#xff0c;适合计算机专业学生或需要动手理解编译过程的开发者参考。压缩包共4个文件&#xf…

作者头像 李华