Dagger TypeScript SDK connect() 函数详解: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
connect()是 Dagger TypeScript SDK 中建立引擎连接的入口函数,它负责启动 GraphQL 服务端会话、初始化 GraphQL 客户端,并通过回调函数把可用的Client交给调用方执行查询。本文以 docs/versioned_docs/version-0.20/reference/typescript/connect/functions/connect.md 为主线,结合 sdk/typescript/src/connect.ts 等源码,深入讲解其签名、配置项、底层连接机制,以及它与connection()的区别,帮助你写出正确、可运行的 Dagger TypeScript 程序。
一、函数签名与基本语义
connect()的完整类型签名为:
connect(cb: CallbackFct, config?: ConnectOpts): Promise<void>根据 connect.md 的定义,它的语义是:
connect runs GraphQL server and initializes a GraphQL client to execute query on it through its callback. This implementation is based on the existing Go SDK.
即:connect运行 GraphQL 服务端,并初始化一个 GraphQL 客户端,通过回调函数在其上执行查询。官方注释明确指出,这一实现思路源自已有的 Go SDK。
在源码 sdk/typescript/src/connect.ts 中,实现完全对应上述语义:
export async function connect( cb: CallbackFct, config: ConnectOpts = {}, ): Promise<void> { await withGQLClient(config, async (gqlClient: GraphQLClient) => { const connection = new Connection(gqlClient) const ctx = new Context([], connection) const client = new Client(ctx) // Warning shall be throw if versions are not compatible try { await client.version() } catch (e) { console.error("failed to check version compatibility:", e) } return await cb(client) }) }从中可以梳理出connect()内部的关键流程:
- 调用
withGQLClient建立并获取底层的GraphQLClient(来自graphql-request库); - 用该客户端构造
Connection对象; - 用
Connection构造Context,进而构造 SDK 暴露给用户的Client; - 调用
client.version()做一次版本兼容性探测,失败时仅打印警告而不中断; - 将
Client传入用户回调cb并等待其完成。
参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
cb | CallbackFct | 无(必填) | 接收已初始化Client的异步回调,回调内编写所有 Dagger API 调用 |
config? | ConnectOpts | {} | 连接配置对象,可控制工作目录、工作区模块加载与日志输出 |
回调类型 CallbackFct
cb的类型是CallbackFct,定义于 CallbackFct.md,同时也在源码中显式导出:
export type CallbackFct = (client: Client) => Promise<void>它接收一个Client参数(自动生成的类型化 Dagger API 客户端),返回Promise<void>。这意味着回调必须是异步函数,所有 Dagger 操作(如container()、from()、withExec())都要在回调内完成。
返回值
connect()返回Promise<void>,即它不向调用方返回业务数据。所有结果都在回调内部通过Client的方法获取,或通过await client.xxx()的返回值在回调内处理。
二、连接配置 ConnectOpts 详解
config的类型是ConnectOpts,完整定义在 sdk/typescript/src/connectOpts.ts:
export interface ConnectOpts { /** * Use to overwrite Dagger workdir * @defaultValue process.cwd() */ Workdir?: string /** * Opt into loading workspace modules for this connection. * By default, only the core API is exposed. */ LoadWorkspaceModules?: boolean /** * Enable logs output */ LogOutput?: Writable }三个配置项的含义与使用场景如下:
1.Workdir:覆盖 Dagger 工作目录
- 类型:
string - 默认值:
process.cwd() - 用途:覆盖本次连接使用的 Dagger 工作目录,影响
client.host().workdir()等相对路径解析。 - 适用场景:当你的脚本从与目标项目不同的目录启动时,显式指定项目根目录,确保模块发现、路径挂载等行为符合预期。
2.LoadWorkspaceModules:加载工作区模块
- 类型:
boolean - 默认值:
false - 用途:是否为本连接加载工作区模块。默认情况下只暴露核心 API(core API);开启后,工作区中定义的模块(如自定义函数、SDK 模块)也会在连接中可用。
- 适用场景:在含有自定义 Dagger 模块(
dagger.toml/dagger.json配置的模块)的项目中运行脚本时,需要开启该选项才能调用模块暴露的自定义 API。
3.LogOutput:启用日志输出
- 类型:
node:stream的Writable(如process.stdout、process.stderr) - 默认值:未设置(无日志输出)
- 用途:将引擎会话的运行日志输出到指定流,便于排查执行过程。
- 适用场景:调试 CI 中不透明的执行失败,或希望把引擎日志并入统一的日志采集。
ConnectOpts的 JSDoc 示例给出了与LogOutput组合的用法:
connect(async (client: Client) => { const source = await client.host().workdir().id() // ... 其他 Dagger 调用 }, { LogOutput: process.stdout })三、connect() 与 connection() 的区别
在 connect 模块 下,除了connect(),还有一个语义相近但定位不同的函数connection()(见 connection.md):
connection(fct: () => Promise<void>, cfg?: ConnectOpts): Promise<void>两者的核心差异在于回调参数:
| 函数 | 回调签名 | 回调中使用的客户端 | 适用场景 |
|---|---|---|---|
connect(cb, config?) | (client: Client) => Promise<void> | 显式传入的Client | 需要精确控制客户端实例、在隔离上下文中执行的场景 |
connection(fct, cfg?) | () => Promise<void> | 全局单例dag | 直接使用 SDK 导出的全局dag对象,代码更简洁 |
connection()的文档示例(同时出现在 connection.md 与源码注释中):
await connection( async () => { await dag .container() .from("alpine") .withExec(["apk", "add", "curl"]) .withExec(["curl", "https://dagger.io/"]) .sync() }, { LogOutput: process.stderr }, )从源码 sdk/typescript/src/connect.ts 看,connection()的实现比connect()多做了几件事:
telemetry.initialize()初始化 OpenTelemetry 遥测,并把连接放入 telemetry 的 context 中以实现上下文传播;- 通过
globalConnection.setGQLClient(gqlClient)把 GraphQL 客户端注入全局单例连接(供dag对象使用); - 无论回调成功与否,
finally中都会调用globalConnection.resetClient()复位全局连接; - 最后
telemetry.close()关闭遥测。
全局连接与惰性求值
globalConnection定义于 sdk/typescript/src/common/graphql/connection.ts,它包装了可变的 GraphQL 客户端引用:
export class Connection { constructor(private _gqlClient?: GraphQLClient) {} resetClient() { this._gqlClient = undefined } setGQLClient(gqlClient: GraphQLClient) { this._gqlClient = gqlClient } getGQLClient(): GraphQLClient { if (!this._gqlClient) { throw new Error("GraphQL client is not set") } return this._gqlClient } } export const globalConnection = new Connection()这解释了为何connection()的测试(sdk/typescript/src/test/connect.spec.ts)中会反复断言dag["_ctx"]["_connection"]["_gqlClient"]在调用前为undefined、调用中非空、调用结束后又复位为undefined——全局dag对象采用惰性求值,只有在connection()(或connect()内部创建并注入的)连接生效期间才可执行查询,从而保证连接资源的生命周期可控。
四、底层连接机制:会话优先,自动供给兜底
connect()并不直接连接引擎,而是委托给withGQLClient(sdk/typescript/src/common/graphql/connect.ts)。它的核心逻辑是两种连接方式按优先级切换:
export async function withGQLClient<T>( connectOpts: ConnectOpts, cb: (gqlClient: GraphQLClient) => Promise<T>, ): Promise<T> { if (process.env["DAGGER_SESSION_PORT"]) { const port = process.env["DAGGER_SESSION_PORT"] if (!process.env["DAGGER_SESSION_TOKEN"]) { throw new Error( "DAGGER_SESSION_TOKEN must be set if DAGGER_SESSION_PORT is set", ) } const token = process.env["DAGGER_SESSION_TOKEN"] return await cb(createGQLClient(Number(port), token)) } try { const provisioning = await import("../../provisioning/index.js") return await provisioning.withEngineSession(connectOpts, cb) } catch (e) { throw new Error( `failed to execute function with automatic provisioning: ${e}`, { cause: e }, ) } }模式一:复用既有会话(DAGGER_SESSION_PORT)
当环境变量DAGGER_SESSION_PORT存在时,SDK 认为已存在一个由外部(如 Dagger CLI、其他 SDK)建立好的引擎会话,直接连向http://127.0.0.1:<port>/query,并用DAGGER_SESSION_TOKEN作为鉴权凭据。
关键约束(从源码可见):
DAGGER_SESSION_TOKEN必须同时设置,否则抛出"DAGGER_SESSION_TOKEN must be set if DAGGER_SESSION_PORT is set";- 该模式下跳过自动供给,不再尝试下载 CLI 或启动引擎。
对应测试见 connect.spec.ts:测试设置了DAGGER_SESSION_TOKEN=foo、DAGGER_SESSION_PORT=1234,随后断言客户端 URL 为http://127.0.0.1:1234/query,且请求头为{"Authorization":"Basic Zm9vOg=="}(即 token 经 Base64 编码后作为 Basic Auth 传递)。
模式二:自动供给引擎(Automatic Provisioning)
当未设置DAGGER_SESSION_PORT时,SDK 动态加载provisioning模块,调用withEngineSession(connectOpts, cb)自动完成:下载并解压 Dagger CLI 二进制(必要时启动未缓存引擎镜像)、建立会话、在回调结束后自动清理。若供给失败,会抛出带 cause 的包装错误"failed to execute function with automatic provisioning: ..."。
自动供给的端到端行为在 connect.spec.ts 中通过模拟下载服务器验证:它生成一个包含 CLI 二进制的 tar.gz 归档、按dagger_v<版本>_<OS>_<ARCH>.tar.gz命名并计算 sha256 校验和,然后用 Mock HTTP 服务器提供归档与 checksums 文件,最终调用connect()验证client.defaultPlatform()可正常执行。这证实了“无本地引擎时 SDK 也能自动拉起环境”的能力,也是connect()在本地开发、CI 和云端皆可运行的基础。
五、版本兼容性探测
在connect()把Client交给回调之前,会先执行一次版本检查:
// Warning shall be throw if versions are not compatible try { await client.version() } catch (e) { console.error("failed to check version compatibility:", e) }从源码注释看,其意图是:若 SDK 与引擎版本不兼容应抛出警告。当前实现采取“软失败”策略——版本探测失败只向console.error输出"failed to check version compatibility:",不会中断connect(),用户回调仍会被执行。如果你在集成时遇到此类警告输出,可据此判断是引擎与 SDK 版本不匹配所致。
六、完整实战示例
示例 1:显式 Client 模式(connect)
import { connect } from "@dagger.io/dagger" await connect( async (client) => { const out = await client .container() .from("alpine") .withExec(["echo", "hello", "world"]) .stdout() console.log(out) // hello world\n }, { LogOutput: process.stderr }, )示例 2:指定工作目录并加载工作区模块
import { connect } from "@dagger.io/dagger" await connect( async (client) => { // 相对路径基于 Workdir 解析 const source = client.host().directory(".") // ... 基于 source 构建镜像、运行测试等 }, { Workdir: "/path/to/project", LoadWorkspaceModules: true, LogOutput: process.stdout, }, )示例 3:直接使用 GraphQL 客户端(connection 模式)
connection()回调内不仅可以用类型安全的dagAPI,还可以通过dag.getGQLClient()获取底层 GraphQL 客户端执行原生查询。这正是 connect.spec.ts 验证的场景:
import { connection, dag } from "@dagger.io/dagger" await connection(async () => { const result = await dag.getGQLClient().request(` query { container { from(address: "alpine") { withExec(args: ["echo", "hello", "world"]) { stdout } } } } `) console.log(result.container.from.withExec.stdout) // hello world\n })示例 4:连接自动关闭验证
无论回调成功或抛错,connect()/connection()都会通过withGQLClient的收尾逻辑与globalConnection.resetClient()确保连接被释放。下面的模式可在脚本中安全复用:
import { connect } from "@dagger.io/dagger" async function runPipeline() { await connect( async (client) => { // 连接期内安全使用 client await client.container().from("alpine:3.16.2") .withExec(["echo", "pipeline", "ok"]).sync() }, { LogOutput: process.stderr }, ) // 退出回调后连接已自动关闭 } await runPipeline()七、使用要点与注意事项
- 回调必须可 await:
CallbackFct返回Promise<void>,回调体内不要遗漏await,否则 Dagger 查询不会被真正执行。 - 不要在回调外使用
Client或dag:连接生命周期与回调绑定,回调结束后连接即被复位(resetClient),在外部使用全局dag会触发"GraphQL client is not set"错误(见 connection.ts)。 - 会话复用优先:在 Dagger CLI 或 CI 已注入
DAGGER_SESSION_PORT/DAGGER_SESSION_TOKEN的环境中,SDK 直接复用会话,不会重复供给引擎,这既快又省资源;二者必须成对出现,否则报错。 - 无引擎环境自动供给:本地无 CLI、无引擎时,SDK 会自动下载对应平台(
normalizedOS/normalizedArch)的 CLI 二进制并启动会话,因此connect()在全新 CI 环境中也能开箱即用。 - 版本不一致只会告警:引擎与 SDK 版本不兼容时输出
"failed to check version compatibility"告警,不会阻断执行;生产环境建议固定 SDK 与引擎版本以消除潜在行为差异。 connection()与connect()二选一:在同一个进程内混用两种模式时注意全局dag的状态是共享的;推荐一个程序统一使用其中一种,保持连接生命周期清晰。
八、总结
connect()是 Dagger TypeScript SDK 所有程序运行的起点:它基于 Go SDK 的设计思想,通过withGQLClient完成“复用既有会话”与“自动供给引擎”的双通道连接,构建出类型安全的Client,并在回调结束后自动释放连接资源。理解ConnectOpts三个配置项(Workdir、LoadWorkspaceModules、LogOutput)与connect/connection的差异,即可在本地开发、CI 与云端场景中稳定地编写和运行 Dagger 自动化流水线。
相关源码与文档索引:
- 主文档:docs/versioned_docs/version-0.20/reference/typescript/connect/functions/connect.md
- 回调类型:docs/versioned_docs/version-0.20/reference/typescript/connect/type-aliases/CallbackFct.md
- 姊妹函数:docs/versioned_docs/version-0.20/reference/typescript/connect/functions/connection.md
- 核心实现:sdk/typescript/src/connect.ts、sdk/typescript/src/connectOpts.ts、sdk/typescript/src/common/graphql/connect.ts、sdk/typescript/src/common/graphql/connection.ts
- 行为测试:sdk/typescript/src/test/connect.spec.ts
【免费下载链接】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),仅供参考