Dagger TypeScript SDK 中的 Error 对象:从 GraphQL 错误传播到结构化扩展值的完整指南
【免费下载链接】daggerAutomation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud项目地址: https://gitcode.com/GitHub_Trending/da/dagger
Dagger 是一个自动化引擎,用于构建、测试和交付任意代码库。在 Dagger 的 TypeScript SDK(@dagger.io/dagger)中,Error是一个特殊的客户端类:它并非普通 JS 的Error,而是映射到引擎 GraphQL 核心类型Error的懒求值对象,用于在 Dagger 调用链中以结构化方式承载错误消息与可机读的扩展值(key/value 扩展)。本文将基于 Dagger v0.21 TypeScript SDK 的官方 API 参考(见 Error.md),逐方法解析Error类与配套的ErrorValue类的用法,并深入 core/error.go 与 core/schema/error.go 的源码实现,帮助你理解错误对象在 Dagger 引擎中的真实行为,从而在自己的 Dagger 模块中正确创建、扩展和读取错误。
类定义与继承关系
Error类在 TypeScript SDK 中定义于 sdk/typescript/src/api/client.gen.ts,其类型声明位于:
Extends: BaseClient所有由 Dagger 代码生成器产出的客户端类(Container、Directory、Client等)都继承自内部的BaseClient。BaseClient持有 GraphQL 查询构建所需的Context与惰性选择器(Selection),因此Error对象本身只是一个"未执行的查询占位符":当你调用它的message()、values()等方法时,才真正向引擎发起 GraphQL 查询并返回结果。这正是 Dagger SDK 一贯的"惰性求值 + 可组合调用链"设计,本类也延续了这一模式。
在 client.gen.ts 中可以看到Error类的真实实现:它维护了_id与_message两个私有字段作为本地缓存,当对应字段已被构造函数注入时直接返回,避免多余的引擎往返。
构造函数:仅供内部使用
new Error(ctx?, _id?, _message?): Error构造函数接收三个可选参数:
| 参数 | 类型 | 说明 |
|---|---|---|
ctx? | Context | Dagger GraphQL 查询上下文,由 SDK 内部注入 |
_id? | ID | 该 Error 的唯一标识符,用于selectNode反查对象 |
_message? | string | 错误描述,注入后message()可免查询直接返回 |
官方文档明确标注:"Constructor is used for internal usage only, do not create object from it."(构造函数仅供内部使用,请勿自行创建对象)。对应的 TS 源码注释在 client.gen.ts 中完全一致。
在实际代码中你也不需要直接 new 一个Error:正确的创建入口是Client上的工厂方法error(),其签名为:
error = (message: string): Error对应实现见 client.gen.ts,它通过select("error", { message })构造一个指向 GraphQLerror(message: ...)字段的查询并包装为Error实例。服务端该字段的解析逻辑位于 core/schema/error.go:
func (s *errorSchema) error(ctx context.Context, _ *core.Query, args struct { Message string `doc:"A description of the error."` }) (*core.Error, error) { // We don't want to see these in the UI trace.SpanFromContext(ctx).SetAttributes(attribute.Bool(telemetry.UIInternalAttr, true)) return &core.Error{ Message: args.Message, }, nil }注意其中的细节:创建Error对象时会在当前 span 上标记UIInternalAttr = true,表示这类对象属于内部实现细节,不应展示在 Dagger UI 中。
实例方法详解
id():获取错误对象的唯一标识
id(): Promise<ID>返回该 Error 的唯一标识符,类型为ID(一个不透明字符串)。如果构造函数已经注入了_id,则直接返回缓存值;否则执行select("id")查询并返回结果(见 client.gen.ts)。
ID在 Dagger 中承担了"持久化对象句柄"的作用:core.Error实现了dagql.PersistedObject与dagql.PersistedObjectDecoder接口(见 core/error.go),EncodePersistedObject将Message与Values编码为 JSON payload,DecodePersistedObject负责反向还原,因此Error可以像Container、Directory一样被序列化后在引擎内传递、跨会话引用。
message():获取错误描述
message(): Promise<string>"A description of the error."(错误的描述文本)。与id()相同,若构造时已注入_message则直接返回,否则执行select("message")查询。在服务端,Message字段定义于 core/error.go:
type Error struct { Message string `field:"true" doc:"A description of the error."` Values []*ErrorValue `field:"true" doc:"The extensions of the error."` }values():读取错误的扩展值列表
values(): Promise<ErrorValue[]>"The extensions of the error."(错误的扩展)。该方法返回ErrorValue对象数组,每个ErrorValue是{ name: string; value: JSON }的键值对,用于承载附加的结构化信息。
其生成实现比较有代表性(client.gen.ts):SDK 首先执行select("values").select("id")拿到每个ErrorValue的 ID 列表,随后通过selectNode(r.id, "ErrorValue")为每个 ID 构建独立的懒查询对象——这是 Dagger TS SDK 处理"对象数组字段"的标准手法,最终返回的ErrorValue[]中的每个元素都可以继续链式调用name()、value()。
with():在不破坏调用链的前提下复用当前对象
with(arg: (param: Error) => Error): Error"Call the provided function with current Error. This is useful for reusability and readability by not breaking the calling chain."(以当前 Error 调用传入的函数,便于在不打断调用链的前提下实现复用与可读性)。
实现非常简单(client.gen.ts):
with = (arg: (param: Error) => Error) => { return arg(this) }这是 Dagger 各 SDK 通用的with()模式:把"对当前对象的后续操作"封装进回调函数,返回回调的产物,从而支持类似client.error("...").with(e => e.withValue("code", 42))的可读写法。
withValue():向错误附加结构化扩展值
withValue(name: string, value: JSON): Error| 参数 | 类型 | 说明 |
|---|---|---|
name | string | The name of the value.(值的名称) |
value | JSON | The value to store on the error.(要存储在错误上的值) |
返回一个新的Error。注意该方法是"返回新对象"而非原地修改——它通过select("withValue", { name, value })构造一条新的 GraphQL 选择器并包装成新的Error返回(client.gen.ts),这与整个 Dagger API 的不可变/惰性设计一致:每次调用都产生一条新的查询链路,最终由引擎统一求值。
服务端withValue的实现见 core/schema/error.go,它委托给core.Error.WithValue:
func (s *errorSchema) withValue(ctx context.Context, self *core.Error, args struct { Name string `doc:"The name of the value."` Value core.JSON `doc:"The value to store on the error."` }) (*core.Error, error) { return self.WithValue(args.Name, args.Value), nil }而core.Error.WithValue采用"克隆后追加"的不可变策略(core/error.go):
func (e *Error) Clone() *Error { cp := *e cp.Values = slices.Clone(e.Values) return &cp } func (e *Error) WithValue(name string, value JSON) *Error { cp := e.Clone() cp.Values = append(cp.Values, &ErrorValue{ Name: name, Value: value, }) return cp }Values切片被深拷贝后再追加新元素,保证原对象不受影响。
配套类型:ErrorValue
values()返回的元素类型ErrorValue同样继承自BaseClient,包含三个方法:
| 方法 | 返回类型 | 语义 |
|---|---|---|
id() | Promise<ID> | A unique identifier for this ErrorValue.(唯一标识) |
name() | Promise<string> | The name of the value.(值的名称) |
value() | Promise<JSON> | The value.(值本体,类型为 JSON) |
服务端ErrorValue是core/error.go中一个精简的持久化对象(core/error.go):
type ErrorValue struct { Name string `field:"true" doc:"The name of the value."` Value JSON `field:"true" doc:"The value."` }JSON是 Dagger 对任意 JSON 值的封装类型,因此withValue的第二个参数可以传入对象、数组、字符串、数字等任意可 JSON 序列化的数据,使得错误不仅能携带人类可读的message,还能附带"错误码、失败步骤、重试建议、结构化上下文"等供程序消费的数据。
底层原理:Error 如何参与 Dagger 的错误传播
理解Error客户端对象的最佳方式,是看它如何在引擎内部与 Go 的error体系打通。
core.Error同时实现了两个 Go 接口(core/error.go):
var _ error = (*Error)(nil) func (e *Error) Error() string { return e.Message } var _ dagql.ExtendedError = (*Error)(nil) func (e *Error) Extensions() map[string]any { ext := map[string]any{} for _, v := range e.Values { var val any json.Unmarshal(v.Value, &val) ext[v.Name] = val } return ext }这意味着:任何返回*core.Error的解析器同时也是一个标准error(其Error()返回Message),且它的Values会被映射为 GraphQL 错误扩展字段(extensions)——这正好与values()在文档中的描述 "The extensions of the error" 遥相呼应。
更关键的是转换函数NewErrorFromErr(core/error.go):当引擎需要把一个任意 Go error 包装成可返回给客户端的Error对象时,它会检查该 error 是否实现了dagql.ExtendedError接口:
- 若实现了,则通过
error(message: ...)字段创建Error,并按 key 排序后逐个调用withValue(name, value)把Extensions()中的全部键值对附加进去; - 若未实现,则仅用
error(message: fromErr.Error())创建只有message的Error。
这一转换由CurrentDagqlServer(ctx).Select在服务端执行,最终以 GraphQL 选择器序列的形式完成,与你用 TS SDK 手工构造的调用链在语义上完全等价。可以推断:当你在模块函数中抛出错误时,Dagger 引擎会借助类似机制将错误"投影"为 GraphQLError对象,使得错误信息能够以结构化的方式跨越引擎边界、出现在调用方的values()中。
完整使用示例
综合以上内容,一个典型的 TypeScript SDK 使用场景如下:
import { dag } from "@dagger.io/dagger" // 通过 Client 工厂方法创建 Error(不要直接 new Error) const err = dag .error("build failed") .withValue("stage", "compile") .withValue("exitCode", 1) // 惰性求值:真正执行时才发起 GraphQL 查询 const message = await err.message() const values = await err.values() for (const v of values) { console.log(await v.name(), await v.value()) } // with() 让复用和组合更简洁 const decorated = err.with((e) => e.withValue("retryable", false))需要再次强调的是:
- 创建:使用
dag.error(message)(对应 client.gen.ts),构造函数仅供 SDK 内部使用; - 惰性:
Error是查询占位符,字段方法在被await时才执行 GraphQL 查询(部分字段命中本地缓存时免查询); - 不可变:
withValue()返回新对象,原Error不受影响,适合构建可复用的错误模板; - 扩展:
values()读取的即 GraphQLextensions,与core.Error.Extensions()的双向映射保持一致,见 core/error.go。
补充说明与适用前提
本文基于仓库中version-0.21版本化文档目录下的 Error.md 及其关联的 ErrorValue.md。该页面是 SDK 自动生成的 API 参考的一部分,完整索引见 client.gen 参考首页;当前仓库主版本对应的 TS SDK 生成源码可在 sdk/typescript/src/api/client.gen.ts 中查阅,核心类型定义与 GraphQL 解析器分别在 core/error.go 与 core/schema/error.go 中。若你使用的 Dagger 版本不同,方法签名与行为可能有所差异,请以你所安装版本的 SDK 生成代码与文档为准。
【免费下载链接】daggerAutomation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud项目地址: https://gitcode.com/GitHub_Trending/da/dagger
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考