Activepieces 公式求值器 core-formula 源码解析:`{{ }}` 表达式如何被解析与执行

发布时间:2026/9/13 11:57:40
Activepieces 公式求值器 core-formula 源码解析:`{{ }}` 表达式如何被解析与执行 Activepieces 公式求值器 core-formula 源码解析{{ }}表达式如何被解析与执行【免费下载链接】activepiecesAI Agents MCPs AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces导读本文深入剖析 Activepieces 开源仓库中负责「公式 / 表达式求值」的独立核心包activepieces/core-formula。它是整个自动化平台的表达式中枢引擎engine在运行流程时用它把步骤输入里的{{ ... }}模板解析成真实值API 与 Web 前端则借助它做校验与类型检查。读完本文你将掌握该包的模块划分、{{ }}模板的完整求值管线包装、分词、预处理、求值、错误友好化、内置函数体系的组织方式以及它被强制保持「可摇树tree-shakeable、零副作用、无循环依赖」的依赖边界设计。包定位表达式求值的唯一权威activepieces/core-formula在仓库中位于 packages/core/formula其 CLAUDE.md 第一段就点明了它的双重职责引擎侧在流程运行期把步骤step输入中的公式 / 表达式模板解析为实际值API / Web 侧用同一套逻辑对表达式做校验保证「构建时所见」与「运行时所得」完全一致。它不是一个被所有模块随意引用的通用工具而是一个被刻意收敛的核心包。从依赖关系看packages/server/engine、packages/server/api、packages/server/worker、packages/server/utils四个服务端包都在package.json中声明了activepieces/core-formula: workspace:*且引擎的 esbuild 配置 esbuild.config.mjs 直接将activepieces/core-formula指向../../core/formula/src源码进行打包说明公式求值器是随引擎进程一同分发的运行期组件而非仅停留在类型层的抽象。包的package.json见 packages/core/formula/package.json显示它的第三方依赖被压缩到最小dayjs日期处理、expr-eval表达式求值引擎、tslibTypeScript 运行时辅助体现了「小而专注」的设计取向。依赖边界为什么只能依赖core-*且必须可摇树原文档 CLAUDE.md 用两条原则划定了这个包的生存红线必须可摇树tree-shakeable公式求值器会被打包进 pieces/engine 等执行环境因此必须保持体积小、无副作用sideEffects: false、依赖图无环只能导入activepieces/core-*系列包严禁反向依赖server、web、pieces或shared该边界由.eslintrc.json中的no-restricted-imports规则在 lint 阶段强制。这两条原则的工程含义可以从源码印证从 function-implementations.ts 的 import 语句看该文件只引入dayjs、expr-eval与同包的function-registry没有任何对 server/web/shared 的引用公式求值器被引擎打包后运行在流程执行环境中若它反向依赖 server 或 web 中的任何模块会引发打包循环、体积膨胀甚至运行时初始化顺序问题这正是「有向无环 只出不进」边界要防住的场景formula-evaluator.ts中的模块级求值器对象以export const formulaEvaluator { ... }形式导出见 formula-evaluator.ts纯函数、无模块级副作用为摇树与 tree-shaking 提供了前提。模块地图四个源文件各司其职包入口 src/index.ts 只做一件事——导出lib下全部四个模块文件职责function-registry.ts声明全部内置函数的元数据名称、分类、语法、示例、参数数量/类型、返回类型是函数体系的唯一事实来源function-implementations.ts用expr-eval的Parser注册每个函数的运行时实现并内置了若干健壮性防护formula-evaluator.ts求值管线模板分词、变量解析、字符串参数包裹、保留字重写、惰性 if、错误友好化function-type-checker.ts面向富文本编辑器Tiptap doc 结构的参数个数与类型检查供 API/Web 校验使用这种「元数据声明」与「运行时实现」分离的设计使得编辑器侧可以在不执行任何函数的情况下完成静态类型检查与参数合法性提示。{{ }}模板的完整求值管线引擎运行流程时传入的表达式并不只有裸公式而是可能夹杂普通文本的模板例如您好 {{ combine( firstName ; lastName ; ) }}您的订单号是 ORD-{{ uppercase( orderId ) }}formula-evaluator.ts 将这类输入拆解为一条清晰的管线1. 包装与识别ap-formula-vN::{ ... }::ap-formula-vN求值器并不直接识别裸的{{ }}而是使用一个带版本号的自定义包装格式const CURRENT_FORMULA_VERSION 1 const FORMULA_PREFIX ap-formula-v${CURRENT_FORMULA_VERSION}::{ const FORMULA_SUFFIX }::ap-formula-v${CURRENT_FORMULA_VERSION} const FORMULA_REGEX /ap-formula-v(\d)::\{([\s\S]*?)\}::ap-formula-v\1/gwrap()/unwrap()/containsWrapper()分别负责包装、还原与判断。源码注释解释了这一设计的关键考量见 formula-evaluator.ts镜像闭合标记让分词退化为普通正则切分无需在包装层做花括号配对或字符串字面量追踪[\s\S]*?非贪婪匹配换行且相邻公式不会合并进同一个捕获组v(\d)版本号让未来升级到 v2 时已保存的旧版本流程可以按版本路由到对应求值器无需数据迁移。2. 分词text 与 formula 的混合切割tokenizeFormulaTemplate()基于上述正则用matchAll扫描模板落在公式段之间的内容标记为text匹配段标记为formula携带版本号。evaluate()的合并策略是整段模板只有一个公式段时走evaluateSingleFormula直接返回原始类型值不强制字符串化混合模板中text段通过resolveTextVars()解析其内部的{{ path }}变量引用formula段求值后按类型拼接对象用JSON.stringify其余转字符串最后parts.join()得到最终结果见 formula-evaluator.ts。3. 变量解析点路径直达嵌套字段resolveVariable()将{{ a.b.c }}形式的路径按.切分逐层从sampleData步骤的示例数据/运行数据中取字段任一层缺失即返回undefined不会抛异常见 formula-evaluator.ts。预处理阶段preprocessExpression()会把每个{{ path }}替换成__ap_vN__形式的占位符并注入vars字典让 expr-eval 可以将其作为变量名参与运算。4. 预处理三件套字符串包裹、内联 JSON 数组、保留字重写求值前要经过三个关键转换preprocessExpression→wrapStringArgs→replaceInlineJsonArrays→normalizeExpression→rewriteLazyIf字符串参数包裹wrapStringArgs/quoteIfBare根据function-registry中每个函数的argTypes元数据把期望是字符串的裸参数自动加引号。例如combine( John ; Smith ; )中的John、Smith会被改写为John、Smith而已经是引号、变量占位符__ap_前缀或嵌套函数调用的参数则跳过内联 JSON 数组识别replaceInlineJsonArrays遇到[ { ... } ]形态的文本时尝试JSON.parse成功且为数组则整体替换为变量占位符让列表字面量可以真正作为数组参与filter_list、join_list等列表函数保留字重写normalizeExpressionand、or、not是 expr-eval 的保留运算符直接注册为函数名会冲突因此实现注册为ap_and/ap_or/ap_not表达式中的and(、or(、not(在求值前被改写成别名round(因与 expr-eval 内置单参版本冲突也被改写为ap_round。改写时判断前一个字符不是单词字符避免误伤understand(、anderson(这类自然词见 formula-evaluator.ts。5. 惰性 if让条件分支安全短路if函数刻意不注册为 JS 函数见 function-implementations.ts如果按普通函数注册参数会在调用前被急切求值破坏短路语义。典型反例是if( is_empty(x) ; safe ; divide(x; 0) )——x为空时divide会先抛除零错误。rewriteLazyIf()把if(c; a; b)在词法层面重写为 expr-eval 的三元表达式((c) ? (a) : (b))实现真正的惰性求值见 formula-evaluator.ts。6. 参数校验与友好错误validateFunctionArgs()在求值前扫描所有函数调用若存在空参数如combine( John ; ; )直接返回combine() is missing value 2 — fill in all values零参数函数如now()不受影响friendlyError()把 expr-eval 的底层异常翻译成用户可读文案除零 →Cannot divide by zero解析错误 →Invalid formula — check for empty values or mismatched parentheses未定义标识符 →xxx is not a known function or variable — check for typos参数个数错误 →Wrong number of values — check the function reference for the expected inputs见 formula-evaluator.ts。最终evaluate()返回{ result, error }结构其中result为null时代表求值失败调用方据此决定回退行为。内置函数体系五类百余个函数的元数据驱动function-registry.ts 以AP_FUNCTIONS数组集中声明全部函数元数据ApFunction类型见 function-registry.ts包含名称、分类、描述、语法、示例、示例结果、minArgs、maxArgs、argTypes、returnType等字段并按五类组织分类代表性函数text文本combine、uppercase、lowercase、titlecase、trim、prefix、suffix、replace、remove、first_n、last_n、truncate、split、extract_between、extract_email、extract_url、length、contains、starts_with、ends_with、remove_spaces、word_count、pad_left、pad_right、repeat、reverse、slugnumber数值add、subtract、multiply、divide、round、round_up、round_down、absolute、percentage、format_number、format_currency、cents_to_dollars、min、max、to_number、random、random_int、power、sqrt、modulo、clamp、signdate日期format_date、format_date_long、format_time、relative_time、add_days、subtract_days、add_hours、add_minutes、days_between、hours_between、get_day、get_month、get_year、get_day_of_week、start_of_month、end_of_month、start_of_day、end_of_day、convert_timezone、now、today、to_date、is_before、is_after、is_same_daylist列表filter_list、sort_list、pluck、join_list、first_item、last_item、item_at、count、sum、average、max_in_list、min_in_list、deduplicate、flatten、split_text_to_list、reverse_list、contains_itemlogic逻辑if、if_empty、if_null、switch、is_empty、is_not_empty、is_equal、and、or、not、coalesce、is_number、is_list其中两类变参与兼容设计值得注意maxArgs: -1表示可变参数switch(value; key1; result1; key2; result2; ...)与coalesce(value1; value2; value3; ...)允许任意多参数求值与类型检查逻辑均对该值做特殊分支处理argCompatibility.defaultArgs后向兼容函数实现全部注册完毕后会遍历注册表为声明了defaultArgs的函数包一层补齐逻辑——在函数新增参数后旧版本保存的流程按旧参数个数调用时自动补上默认值避免运行时抛「参数个数错误」见 function-implementations.ts。编辑器侧静态检查在用户输入时就报错function-type-checker.ts 实现了对 Tiptap 富文本文档结构DocNode的类型检查typeCheckTiptapDoc()遍历文档节点对每个function_start节点其function_end闭合节点存在避免在用户输入中途干扰执行参数个数检查超过maxArgs或少于minArgs且参数区非空时报错逐参数类型推断inferArgType()对文本参数判断是数字、布尔还是字符串若参数是单一顶层函数调用则用其returnType作为推断类型例如uppercase(...)的返回类型是 string含表达式运算符、、等的文本跳过静态推断避免把3 9误判成字符串类型不匹配提示如Value 1 of add() should be a number, but got a string。一个值得一提的实现细节编辑器会在函数括号间插入零宽空格\u200B作为光标锚点而String.trim()并不清除它stripCursorAnchors()专门剥离这些字符后再做类型判断见 function-type-checker.ts否则字面量3会被误判为字符串。引擎集成props-resolver 如何消费求值器表达式求值器在引擎侧的入口是 props-resolver.ts它 importformulaEvaluator用于解析步骤输入。结合 props-resolver.test.ts 可以看到该模块负责把流程运行期每个步骤的输入属性props从「模板字符串 运行数据」解析为实际值——这正是 CLAUDE.md 所述「engine resolves step inputs」的落点。由于公式求值器同时被引擎运行期与 API/Web构建期校验使用用户在编辑器中预览的求值结果与流程真正运行时得到的结果保持一致。健壮性设计从除零到防 OOM 的边界防护function-implementations.ts 中隐藏了多处面向生产环境的防御性实现除零防护divide与percentage在除数为 0 时显式throw new Error(Division by zero)由求值器捕获并翻译为友好错误见 function-implementations.ts防 OOM 上限pad_left/pad_right/repeat的结果长度被限制在MAX_PAD_OR_REPEAT_LENGTH 10_000超出时按 no-op 返回原值而非抛RangeError防止恶意参数打爆 V8 字符串长度上限或拖垮流程步骤见 function-implementations.ts时区确定性start_of_day、end_of_day、is_same_day使用dayjs.utc()解析后再对齐日界避免同一时间戳在 UTC8 等非零时区服务器上产生与文档示例不一致的结果见 function-implementations.ts宽松等值loose equality是刻意为之filter_list、contains_item、is_equal、switch使用而非因为公式参数经文本输入到达时是字符串而列表项字段可能是类型化数值严格等值会导致数字字段过滤永远命中零条见 function-implementations.ts。总结activepieces/core-formula是 Activepieces 自动化执行链中「小而关键」的一环它用一份集中式的函数注册表驱动求值、校验与类型检查以ap-formula-vN::{ ... }的版本化包装格式解耦模板分词与求值通过惰性 if、保留字重写、字符串参数包裹等预处理手段驯服通用表达式引擎 expr-eval并以「可摇树、零副作用、只依赖 core-* 包」的边界约束保证它能安全地被打进引擎与编辑器两套截然不同的运行环境。理解这个包也就理解了 Activepieces 中{{ }}表达式从构建器到运行时的完整生命周期。【免费下载链接】activepiecesAI Agents MCPs AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考