自动化生产-elysia-方法流
针对你描述的 “改了源头,后续自动变化” 以及 “构建有向无环图(DAG)状态机” 的需求,单纯的“流(Stream)”库可能是不够的。你需要的是 响应式(Reactivity) 或 事件编排(Event Orchestration) 库。
根据你的场景(Bun 环境、TypeScript、AST 转换、自动化流水线),我为你精选了三个梯队的库,它们代表了三种不同的实现哲学:
第一梯队:函数式响应流 (FRP) —— 最适合处理“事件”
如果你关注的是 “时序”(例如:文件变化了 -> 等待 100ms 防抖 -> 取消上一次未完成的生成 -> 开始新的生成),RxJS 是王者。
1. RxJS (Reactive Extensions for JavaScript)
-
地位:行业标准,功能最强,但学习曲线陡峭。
-
为什么适合你:
-
流的控制:它有
switchMap,非常适合你的场景。当 Schema 再次改变时,它可以自动“切断”正在进行的、已经过时的 Service 生成任务,直接开始新的,避免资源浪费。 -
防抖与节流:
debounceTime可以防止用户打字时频繁触发生成。
-
-
代码感觉:
TypeScript
import { watch } from 'fs'; import { fromEvent, switchMap, debounceTime } from 'rxjs'; const fileChange$ = fromEvent(watcher, 'change'); fileChange$.pipe( debounceTime(300), // 等用户停手 300ms switchMap(async (file) => { // switchMap 会自动取消上一次还没跑完的 async 任务 const ast = await parseAST(file); return generateCode(ast); }) ).subscribe(result => { saveToDisk(result); });
2. Callbag / Wonka
-
地位:RxJS 的轻量级替代品。
Wonka是 ReasonML 社区搞的,被urql使用。 -
特点:体积极小,比 RxJS 快,没有那么重的历史包袱,非常适合做库的底层依赖。
第二梯队:细粒度响应式 (Signals) —— 最适合处理“依赖关系”
如果你关注的是 “拓扑结构”(例如:A 变了,B 和 C 必须变,像 Excel 一样),那么 Signals 是目前最高效的方案。这比 RxJS 更直观,更像你描述的“状态工具”。
3. @preact/signals-core (或 SolidJS Signals)
-
地位:现代前端框架性能起飞的秘密,但完全可以用在 Node/Bun 后端。
-
为什么适合你:
-
自动依赖追踪:你不需要手动
subscribe。只要你在 B 的计算逻辑里用了 A,A 变了 B 就会自动更新。 -
惰性计算 (Lazy Evaluation):如果 Controller 还没被需要,即便 Schema 变了,Controller 的生成逻辑可能暂时不会跑,节省资源。
-
-
代码感觉:
TypeScript
import { signal, computed, effect } from "@preact/signals-core"; // 1. 源头 (Source) const schemaContent = signal("initial schema..."); // 2. 中间态 (Derived) - 自动追踪 schemaContent const typeDefinition = computed(() => { console.log("正在解析 Schema..."); return parseTypeBox(schemaContent.value); }); // 3. 终端 (Effect) - 自动追踪 typeDefinition effect(() => { console.log("检测到类型变化,正在写入文件..."); Bun.write("user.model.ts", typeDefinition.value); }); // 触发:当你修改源头,上面的 computed 和 effect 会自动连锁反应 schemaContent.value = "new schema...";
4. MobX
-
地位:老牌的透明响应式库。
-
特点:功能比 Signals 更丰富,支持更复杂的对象图(Object Graph)观测。如果你要在内存里维护一整棵 AST 树,并且希望修改树上的一个节点,整个树的相关部分自动重算,MobX 非常强。
第三梯队:副作用管理与编排 —— 最适合做“基础设施”
如果你要把这个工具做成企业级产品,需要极其严谨的错误处理、并发控制和依赖注入。
5. Effect (Effect-TS)
-
地位:TypeScript 生态中正在崛起的“标准库”,被誉为“下一代 TypeScript 开发范式”。
-
为什么适合你:
-
它不是简单的流,它是一个运行时系统。它把“流”、“依赖注入”、“错误处理”、“并发”全部统一了。
-
结构化并发:如果在生成过程中用户强制退出了,或者某个步骤出错了,Effect 能保证资源(文件句柄、内存)被完美清理。
-
它非常适合构建你所谓的 Compiler Pipeline。
-
-
缺点:学习门槛极高(需要懂函数式编程概念),但一旦学会,写出来的代码坚如磐石。
第四梯队:构建系统专用 (DAG)
如果你的依赖关系极其复杂,你可以直接用构建系统的内核。
6. Nx (DevKit) / Turborepo (Core)
虽然它们是工具,但它们内部通过计算 Hash 图来决定是否重做的逻辑,其实就是你想要的。你可以参考 graphlib 或者简单的 toposort 算法来自己实现一个简易的 DAG 调度器。
💡 我的推荐组合
针对你的 Codegen (代码工厂) 场景,我建议采用 混合架构:
-
事件源头使用 Chokidar + RxJS:
用来监听文件系统,利用 RxJS 的 debounce 和 buffer 处理高频的文件保存操作(不要一按 Ctrl+S 就炸,缓冲个 300ms)。
-
核心状态图使用 Signals (@preact/signals-core):
用来构建你的 Schema -> Model -> Service -> Controller 依赖链。
- 因为这种依赖关系是静态的、同步的(逻辑上),用 Signals 写起来比 RxJS 清晰得多,心智负担小,完全符合“Excel”模型。
架构伪代码:
TypeScript
// 1. 事件层 (RxJS)
const fileSave$ = fromEvent(watcher, 'change').pipe(debounceTime(300));
// 2. 状态层 (Signals)
const schemaNode = signal(read(schemaPath));
const modelNode = computed(() => transformToModel(schemaNode.value));
const serviceNode = computed(() => smartMerge(read(servicePath), modelNode.value));
// 3. 桥接
fileSave$.subscribe((path) => {
if (path === schemaPath) {
// 更新信号,自动触发后续的 computed 链条
schemaNode.value = read(path);
}
});
// 4. 副作用 (Effect)
effect(() => {
// 只要 serviceNode 变了,就写入磁盘
// Signals 保证了只有当真正内容变了才会触发这里
write(servicePath, serviceNode.value);
});
你觉得这种 RxJS (处理 IO 事件) + Signals (处理逻辑依赖) 的组合方式是否符合你的构想?