Dagger TypeScript SDK 中 FunctionCachePolicy 枚举详解:函数结果缓存的三级策略与源码级实现
【免费下载链接】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 仓库中 TypeScript SDK 参考文档里的FunctionCachePolicy枚举为核心,完整解读Default、Never、PerSession三个枚举值的语义与取值,并结合 sdk/typescript/src/api/client.gen.ts 与 core/modfunc.go 的源码实现,说明每个策略在引擎侧到底如何影响函数调用的缓存行为,以及模块开发者在注册函数时如何指定缓存策略与 TTL。读完本文后,你将能够准确选择缓存策略、正确传递timeToLive参数,并理解策略从 SDK 到引擎的完整落地链路。
枚举定义:函数结果缓存的行为开关
Dagger 官方 TypeScript SDK 参考文档(见 FunctionCachePolicy 文档)将该枚举描述为:
The behavior configured for function result caching.(为函数结果缓存配置的行为。)
对应到 TypeScript 客户端生成代码中,枚举定义非常精简(sdk/typescript/src/api/client.gen.ts#L1894-L1898):
/** * The behavior configured for function result caching. */ export enum FunctionCachePolicy { Default = "Default", Never = "Never", PerSession = "PerSession", }三个枚举成员均为字符串字面量,且成员名与值相同:
| 枚举成员 | 字符串值 | 语义 |
|---|---|---|
FunctionCachePolicy.Default | "Default" | 使用默认的缓存行为(跨调用缓存,可配合 TTL 使用) |
FunctionCachePolicy.Never | "Never" | 从不缓存函数结果,每次调用都重新执行 |
FunctionCachePolicy.PerSession | "PerSession" | 仅在当前会话(session)内缓存函数结果 |
这种“成员名等于值”的设计是 Dagger SDK 生成枚举的通用约定:SDK 通过 GraphQL 调用引擎时,枚举以字符串形式序列化传输,客户端代码中使用符号化的枚举成员即可,无需手写魔法字符串。
使用场景:Function_.withCachePolicy 与 TTL 参数
该枚举唯一的直接消费点是Function_对象的withCachePolicy方法(sdk/typescript/src/api/client.gen.ts#L9026-L9045):
/** * Returns the function updated to use the provided cache policy. * @param policy The cache policy to use. * @param opts.timeToLive The TTL for the cache policy, if applicable. * Provided as a duration string, e.g. "5m", "1h30s". */ withCachePolicy = ( policy: FunctionCachePolicy, opts?: FunctionWithCachePolicyOpts, ): Function_ => { const metadata = { policy: { is_enum: true, value_to_name: FunctionCachePolicyValueToName }, } const ctx = this._ctx.select("withCachePolicy", { policy, ...opts, __metadata: metadata, }) return new Function_(ctx) }两个关键细节值得注意:
Function_是“函数定义”构造器,属于模块开发(module codegen)链路的一部分。它描述的是“某个模块函数在被调用时应当如何缓存其结果”,而不是普通container().withExec()这类核心 API 调用。opts.timeToLive是可选参数,类型为时长字符串,例如"5m"、"1h30s",其声明见 FunctionWithCachePolicyOpts:
export type FunctionWithCachePolicyOpts = { /** * The TTL for the cache policy, if applicable. Provided as a duration string, e.g. "5m", "1h30s". */ timeToLive?: string }注释中 “if applicable” 的措辞表明:TTL 并非所有策略下都有意义,从模块注册链路(下文详述)可以推断,TTL 主要配合Default策略使用,为缓存结果设置过期时间。
引擎侧实现:策略如何转化为隐式输入
FunctionCachePolicy不只是客户端的序列化载体,引擎会将其翻译为 DAG 计算图中的“隐式输入(implicit input)”,从而决定缓存命中粒度。核心实现在 core/modfunc.go#L124-L139:
func (fn *ModuleFunction) cacheImplicitInputs() []dagql.ImplicitInput { if fn == nil || fn.mod.Self() == nil || fn.metadata == nil { return nil } var implicitInputs []dagql.ImplicitInput cachePolicy := fn.metadata.derivedCachePolicy(fn.mod.Self()) switch cachePolicy { case FunctionCachePolicyNever: implicitInputs = append(implicitInputs, dagql.PerCallInput) case FunctionCachePolicyPerSession: implicitInputs = append(implicitInputs, dagql.PerSessionInput) } return implicitInputs }从这段源码结构可以清晰读出三种策略的落地差异:
Never→dagql.PerCallInput:为该函数注入一个“每次调用都变化”的隐式输入。由于调用输入是缓存键的组成部分,每次调用的键都不同,结果必然无法命中缓存——等价于“每次调用都重新执行”。PerSession→dagql.PerSessionInput:注入一个“会话内稳定、跨会话变化”的隐式输入。同一会话中相同参数调用会命中缓存,会话结束或新会话开始时缓存自然失效。Default(或策略为空)→ 不追加隐式输入:缓存键完全由显式调用输入决定,只要输入不变,结果可跨会话复用,并可受 TTL 约束。
这种“用隐式输入操纵缓存键”的实现方式,与 Dagger 引擎基于内容寻址的 e-graph 缓存模型一脉相承,开发者无需关心底层存储细节,只需选择策略即可。
模块开发视角:策略值从装饰器到枚举的映射
对于编写 Dagger 模块的开发者,通常不直接手写FunctionCachePolicy,而是通过模块函数上的缓存声明(由 introspector 解析为方法元数据中的cache字段)来指定策略。模块注册时的映射逻辑见 sdk/typescript/src/module/entrypoint/register.ts#L145-L161:
switch (fct.cache) { case "never": { fnDef = fnDef.withCachePolicy(FunctionCachePolicy.Never) break } case "session": { fnDef = fnDef.withCachePolicy(FunctionCachePolicy.PerSession) break } case "": { break } default: { const opts: FunctionWithCachePolicyOpts = { timeToLive: fct.cache } fnDef = fnDef.withCachePolicy(FunctionCachePolicy.Default, opts) } }从这段分支结构看,用户侧cache声明与枚举的对应关系是:
| 模块中声明的 cache 值 | 映射到的 FunctionCachePolicy | 附加行为 |
|---|---|---|
"never" | Never | — |
"session" | PerSession | — |
| 空(未声明) | 不调用withCachePolicy,沿用引擎默认行为 | — |
时长字符串(如"5m") | Default | 将该字符串作为timeToLive传入 |
最后一条分支恰好解释了FunctionWithCachePolicyOpts.timeToLive的典型用法:当开发者希望“使用默认缓存行为但限定结果有效期”时,声明一个时长字符串即可,注册流程会自动将其包装为Default策略加 TTL 的组合。
辅助工具:枚举值与名称的相互转换
生成代码同时提供了两个方向相反的转换函数,服务于模块运行时的序列化边界(sdk/typescript/src/api/client.gen.ts#L1900-L1936):
FunctionCachePolicyValueToName(value):将枚举值转换为名称字符串("Default"/"Never"/"PerSession"),用于作为暴露函数的参数传递;未识别的值原样返回。FunctionCachePolicyNameToValue(name):将名称字符串还原为枚举值,用于在模块运行时内部正确消费该值;未知名称直接as断言透传,保持前向兼容。
两者均对未知取值采取“原样透传”的宽松策略,这意味着即使引擎侧未来新增枚举成员,旧版 SDK 也不会直接抛错,而是按字符串继续流转。
同一枚举在 Go 运行时的镜像定义
TypeScript 模块的运行时本身以 Go 形式执行,因此同一套 API 在生成的 Go 客户端中也存在镜像枚举(sdk/typescript/runtime/internal/dagger/dagger.gen.go#L16617-L16674):
type FunctionCachePolicy string const ( FunctionCachePolicyDefault FunctionCachePolicy = "Default" FunctionCachePolicyPerSession FunctionCachePolicy = "PerSession" FunctionCachePolicyNever FunctionCachePolicy = "Never" )Go 版本额外提供Name()、Value()以及MarshalJSON/UnmarshalJSON方法,保证 JSON 边界上的字符串编码与 TypeScript 侧完全一致(同样以"Default"、"Never"、"PerSession"三个字符串为准)。这确保了无论策略是在 TypeScript 层还是在 Go 运行时层被读取,语义都不会漂移。
小结:选型建议与适用前提
- 需要结果可跨会话复用的纯计算型函数,保持默认或显式指定
Default即可;若结果会随时间失效(例如拉取外部状态),建议配合timeToLive使用Default策略。 - 结果只应在当前会话内有效、跨会话必须重新求值时,选择
PerSession(引擎注入PerSessionInput隐式输入)。 - 函数具有强副作用或需要每次调用都真实执行时,选择
Never(引擎注入PerCallInput,彻底绕开缓存命中)。
以上结论均以当前仓库代码为准:枚举与转换函数定义于 sdk/typescript/src/api/client.gen.ts,模块注册映射位于 sdk/typescript/src/module/entrypoint/register.ts,引擎侧隐式输入逻辑位于 core/modfunc.go。文档路径位于 version-0.21 版本化参考目录,读者在对照其他 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),仅供参考