Dagger 模块自定义标量类型 ScalarTypeDef:TypeScript 客户端 API 完全指南
2026/9/14 22:07:25 网站建设 项目流程

Dagger 模块自定义标量类型 ScalarTypeDef:TypeScript 客户端 API 完全指南

【免费下载链接】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

导读

ScalarTypeDef是 Dagger 引擎中描述“模块内自定义标量类型(custom scalar)”的核心类型定义对象。在 Dagger 模块化体系中,模块可以对外暴露自定义数据类型(如自定义 ID、枚举之外的简单值类型),而ScalarTypeDef就是这些标量在 GraphQL/DAG 执行引擎中的统一载体。本文以 docs/versioned_docs/version-0.19/reference/typescript/api/client.gen/classes/ScalarTypeDef.md 为骨架,结合仓库源码(sdk/typescript/src/api/client.gen.ts、core/typedef.go、core/schema/module.go),完整讲解该类的构造约束、四个查询方法的语义、底层实现原理与真实调用链路,帮助读者在 TypeScript 模块开发中正确理解和使用标量类型定义。

类概述:什么是 ScalarTypeDef

根据 API 文档,ScalarTypeDef的官方定义是:

A definition of a custom scalar defined in a Module.

即:在一个 Module 中定义的自定义标量的类型定义。它是 Dagger 类型系统(TypeDef家族)中用于描述标量(scalar)的一等公民。

从源码结构看,Dagger 的 TypeDef 家族包含多种“子类型定义”,其中标量对应的正是ScalarTypeDef。在 core/typedef.go 中,其 Go 侧核心结构如下:

type ScalarTypeDef struct { Name string `field:"true" doc:"The name of the scalar." doNotCache:"simple field selection"` Description string `field:"true" doc:"A doc string for the scalar, if any." doNotCache:"simple field selection"` OriginalName string // SourceModuleName is currently only set when returning the TypeDef from the Scalars field on Module SourceModuleName string `field:"true" doc:"If this ScalarTypeDef is associated with a Module, the name of the module. Unset otherwise." doNotCache:"simple field selection"` }

可以看到,Go 侧字段与 TypeScript 客户端公开的四个属性一一对应:

字段含义
Name标量的名称(GraphQL 名称,由NewScalarTypeDef构造时经strcase.ToCamel规范化)
Description标量的文档字符串,可空
SourceModuleName若该标量与某个 Module 关联,则为该模块名;否则为空
OriginalName构造时传入的原始名称(仅存在于 Go 引擎侧,不暴露给 GraphQL API)

它在 TypeDef 类型体系中的位置

ScalarTypeDef并非孤立存在,它是TypeDef这个总类型(kind 判别联合)的一个分支。在 core/typedef.go 中,引擎通过以下方式把标量挂接到通用TypeDef上:

func (typeDef *TypeDef) WithScalar(scalar dagql.ObjectResult[*ScalarTypeDef]) *TypeDef { typeDef = typeDef.WithKind(TypeDefKindScalar) typeDef.AsScalar = dagql.NonNull(scalar) return typeDef.syncName() } func (typeDef *TypeDef) WithScalarTypeDef(scalar dagql.ObjectResult[*ScalarTypeDef]) *TypeDef { typeDef = typeDef.Clone() typeDef.Kind = TypeDefKindScalar typeDef.AsScalar = dagql.NonNull(scalar) return typeDef.syncName() }

其中TypeDefKindScalar的注册描述为 “A scalar value of any basic kind.”(见 core/typedef.go)。这意味着:当你看到TypeDef.Kind == SCALAR_KIND时,其AsScalar字段就是一个ScalarTypeDef

构造函数:仅供内部使用,禁止手动创建

new ScalarTypeDef( ctx?: Context, _id?: ScalarTypeDefID, _description?: string, _name?: string, _sourceModuleName?: string ): ScalarTypeDef

文档明确指出:

Constructor is used for internal usage only, do not create object from it.

该构造函数参数如下:

参数类型说明
ctx?ContextGraphQL 执行上下文(由BaseClient维护)
_id?ScalarTypeDefID持久化标识符,对应 GraphQL 的ScalarTypeDefID类型别名
_description?string标量文档字符串
_name?string标量名称
_sourceModuleName?string关联模块名称

在 TypeScript 实现中(sdk/typescript/src/api/client.gen.ts),构造函数只做两件事:调用super(ctx)继承BaseClient,并把四个可选值原样保存到私有只读字段(_id_description_name_sourceModuleName)。这些私有字段会在对应方法中被“短路”使用——见下文各方法实现。

实际上,客户端代码不会直接new一个ScalarTypeDef,而是通过两种途径获得实例:

  1. GraphQL 响应反序列化:在 sdk/typescript/src/api/client.gen.ts 中,TypeDef.asScalar()方法把查询结果节点封装为ScalarTypeDef实例:
asScalar = async (): Promise<ScalarTypeDef | null> => { const ctx = this._ctx.select("asScalar", ...) const response: Awaited<ScalarTypeDef | null> = await ctx.execute() return new ScalarTypeDef(ctx.copy().selectNode(response, "ScalarTypeDef")) }
  1. 模块 SDK 的withScalar构造链路:TypeScript 模块运行时通过dag.typeDef().withScalar(name)构造,见下文调用链分析。

实例方法详解

ScalarTypeDef共暴露四个异步方法,全部返回Promise,与文档一一对应。以下逐个结合实现说明。

id():获取唯一标识符

id = async (): Promise<ScalarTypeDefID> => { if (this._id) { return this._id } const ctx = this._ctx.select("id") const response: Awaited<ScalarTypeDefID> = await ctx.execute() return response }
  • 若构造时已携带_id(例如从反序列化结果中取得),直接返回缓存值,不发起网络请求
  • 否则向 GraphQL 服务器查询id字段并返回ScalarTypeDefID

ScalarTypeDefID是一个类型别名(type alias),它代表 Dagger 引擎生成的标量类型定义 ID。该 ID 在引擎侧对应持久化机制:ScalarTypeDef实现了EncodePersistedObject/DecodePersistedObject(见 core/typedef.go),可将自身编码为persistedScalarTypeDefJSON 载荷,通过 ID 在会话间恢复对象,这正是 Dagger“一切皆 ID、懒执行”的核心设计。

description():标量文档字符串

description = async (): Promise<string> => { if (this._description) { return this._description } const ctx = this._ctx.select("description") const response: Awaited<string> = await ctx.execute() return response }

文档语义为 “A doc string for the scalar, if any.”——即标量的文档字符串,可以为空。同样遵循“先查缓存、再发查询”的模式。

name():标量名称

name = async (): Promise<string> => { if (this._name) { return this._name } const ctx = this._ctx.select("name") const response: Awaited<string> = await ctx.execute() return response }

返回标量的名称(The name of the scalar)。注意引擎侧的NewScalarTypeDef(core/typedef.go)会将传入名称通过strcase.ToCamel规范化为最终 GraphQL 名称,原始名称保留在OriginalName中;而WithName(core/typedef.go)则用于在重命名场景下直接写入已规范化的名称(与ObjectTypeDef.WithName的处理方式一致,避免二次规范化)。

sourceModuleName():来源模块名

sourceModuleName = async (): Promise<string> => { if (this._sourceModuleName) { return this._sourceModuleName } const ctx = this._ctx.select("sourceModuleName") const response: Awaited<string> = await ctx.execute() return response }

文档语义为:

If this ScalarTypeDef is associated with a Module, the name of the module. Unset otherwise.

即:若该标量类型定义与某个 Module 关联,返回该模块名;否则为空字符串

从引擎注释(core/typedef.go)可知:SourceModuleName目前只在通过Module上的Scalars字段返回TypeDef时才会被设置。而创建标量的入口scalarTypeDef(core/schema/module.go)也支持可选的SourceModuleName内部参数:

func (s *moduleSchema) scalarTypeDef(ctx context.Context, _ *core.Query, args struct { Name string Description string `default:""` SourceModuleName dagql.Optional[dagql.String] `internal:"true"` }) (*core.ScalarTypeDef, error) { scalar := core.NewScalarTypeDef(args.Name, args.Description) if args.SourceModuleName.Valid { scalar.SourceModuleName = string(args.SourceModuleName.Value) } return scalar, nil }

统一的“短路”执行模式

四个方法共享同一实现模式:若构造时已注入对应值则直接返回,否则向引擎发起字段查询。这在 Dagger 的 TypeScript 客户端(client.gen.ts)中是一种通用优化——SDK 在反序列化时会把引擎已返回的字段缓存到实例上,避免重复的 GraphQL 往返。所有方法均通过this._ctx.select(...)选择字段并execute(),遵循BaseClient的懒加载(lazy)执行模型:方法调用只记录查询意图,真正执行发生在最终await时。

引擎侧完整解析链路

1. GraphQL Schema 注册

ScalarTypeDef在引擎侧通过 dagql 注册为 GraphQL 对象类型。在 core/schema/module.go 附近可以看到dagql.Fields[*core.ScalarTypeDef]{...}的字段注册,且模块 Schema 暴露了构造标量定义的顶层入口scalarTypeDef以及__withScalarTypeDef等内部工具函数(core/schema/module.go)。

2. TypeDef 的 asScalar 分支

当对TypeDef查询asScalar字段时,引擎会取出TypeDef.AsScalar并返回其ScalarTypeDef实例(对应 TypeScript 客户端 sdk/typescript/src/api/client.gen.ts 的asScalar()方法)。在 core/typedef.go 附近可以看到引擎侧对该分支的类型断言处理:attached.(dagql.ObjectResult[*ScalarTypeDef])

3. 模块 SDK 的 withScalar 注册链路

在 TypeScript 模块运行时中,当模块开发者把自定义类型注册进模块时,addTypeDef函数(sdk/typescript/src/module/entrypoint/register.ts)会按 kind 分发:

case TypeDefKind.ScalarKind: return dag.typeDef().withScalar((type as ScalarTypeDef).name)

即:对SCALAR_KIND类型的 typedef,仅取其.name调用dag.typeDef().withScalar(name),由引擎侧typeDefWithScalar(core/schema/module.go)完成校验与构造。该处理器要求名称非空:

if args.Name == "" { return nil, fmt.Errorf("scalar type def must have a name") }

这印证了文档中name()是必填语义字段,而description()是可选字段。

4. 类型内省(Introspection)与标量

在 TypeScript 模块的 introspection 工具中,ScalarTypeDef被建模为“基础 typedef 的扩展”(sdk/typescript/src/module/introspector/typedef.ts):

Extends the base typedef if it's a scalar to add its name and real type.

即:当内省到 kind 为scalar时,基础TypeDef会被扩展为ScalarTypeDef,补上 name 与真实类型信息。这说明ScalarTypeDef在“引擎类型系统 ↔ 模块 SDK 类型系统”之间承担着标准化的桥梁作用。

真实调用场景示例

在 Dagger 模块中,标量类型最常见的出现场景是:模块定义自定义函数、参数或返回值时,引擎需要为它们建立类型描述。例如一个返回自定义标量的模块函数,其类型描述最终会以TypeDef{Kind: SCALAR_KIND, AsScalar: ScalarTypeDef{Name: "MyScalar", Description: "..."}}的形式被引擎持久化并暴露给客户端。

在 TypeScript 客户端侧,一个典型的查询片段如下(示意,对应四个方法):

import { connect } from "@dagger.io/dagger" connect(async (client) => { // 通过模块的类型信息获得 TypeDef 后,取标量分支 const typeDef = await client.module().scalars().name("MyScalar").typeDef() const scalar = await typeDef.asScalar() const id = await scalar?.id() // 唯一标识符 const name = await scalar?.name() // 标量名 const desc = await scalar?.description() // 文档字符串 const mod = await scalar?.sourceModuleName() // 来源模块名(未关联则为空) })

注意:实际获取ScalarTypeDef的方式取决于你的查询入口(如通过module.scalars()typeDef().asScalar()或自定义模块的构造链路),上述代码用于展示方法语义,具体字段路径请以你使用的 SDK 版本生成的 client 为准。

测试与验证依据

仓库中多处集成测试数据验证了标量类型定义的实际行为:

  • core/typedef_test.go 与 core/typedef_test_helpers_test.go:覆盖TypeDef系列(含标量分支)的构造、序列化与往返;
  • core/schema/testdata/base_schema.graphqls:Schema 基准测试中包含ScalarTypeDef的 GraphQL 类型定义;
  • 大量生成代码(如 core/integration/testdata/modules/go/ifaces/internal/dagger/dagger.gen.go)展示了各语言 SDK 生成的ScalarTypeDef相关客户端代码,可作为跨语言对照。

关键要点总结

  1. ScalarTypeDef是 Dagger 模块类型系统中描述自定义标量的标准对象,与ObjectTypeDefEnumTypeDefListTypeDef等并列,通过TypeDef.AsScalar挂载在统一的TypeDef之上(Kind 为SCALAR_KIND)。
  2. 构造函数仅供 SDK 内部使用,业务代码不应手动new;实例应通过 GraphQL 查询结果(如typeDef.asScalar())或模块运行时构造链路获得。
  3. 四个方法语义明确id()返回持久化标识符、name()返回标量名、description()返回可选文档字符串、sourceModuleName()返回关联模块名(未关联时为空)。
  4. 全部方法采用“缓存短路 + 懒查询”模式:SDK 反序列化时已缓存的字段直接返回,未缓存的字段才会触发一次 GraphQL 查询。
  5. 名称规范化发生在引擎侧NewScalarTypeDef会经strcase.ToCamel生成最终 GraphQL 名称,且名称不可为空(引擎会显式报错)。
  6. SourceModuleName仅在通过ModuleScalars字段返回TypeDef时设置,这是判断标量“归属模块”的关键信号。

通过本文,读者应能准确理解 Dagger TypeScript 客户端中ScalarTypeDef的完整语义、实现原理与调用场景,从而在自定义 Dagger 模块的类型设计中正确使用这一 API。

【免费下载链接】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),仅供参考

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

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

立即咨询