kiosk-flow-processor
v0.1.1
Published
Runtime that executes Kiosk Flow Builder .flw flows
Downloads
10
Maintainers
Readme
Kiosk Flow Processor
框架无关的流程运行时:解释 .flw 流程图,驱动节点之间的跳转,并执行每个节点对应的脚本。纯 TypeScript、零 UI 依赖,可嵌入任意前端框架(Angular / React / Vue …)或 Node 环境。
命名关系:kiosk-flow-builder 与 kiosk-flow-processor
这两个名字经常被混淆,但它们是两个不同的东西,职责完全分开:
| | kiosk-flow-builder | kiosk-flow-processor(本仓库) |
|---|---|---|
| 是什么 | 可视化授权插件(VS Code 扩展 "Kiosk Flow Builder") | 运行时/引擎 |
| 干什么 | 让你用图形化方式画流程,产出 .flw 文档 + 节点脚本骨架 | 执行 .flw:按边推进节点、调用节点脚本的 run(ctx) |
| 何时用 | 开发期(设计流程) | 运行期(跑流程) |
| 依赖关系 | 只是个编辑器 | 不依赖 kiosk-flow-builder,能跑任何合法 .flw,无论它怎么产生 |
一句话:kiosk-flow-builder 画流程(.flw)→ kiosk-flow-processor 执行流程。
历史:本包早期叫
@flow-builder/runtime,那个名字把「授权工具」和「运行时」混为一谈。现已改名为kiosk-flow-processor,让运行时的命名与授权插件解耦。项目引用本包时,与 kiosk-flow-builder 没有任何运行期关系——.flw只是一种输入数据格式。
核心概念
.flw文档:纯 JSON(FlowDoc=nodes+edges)。节点类型有start / end / operation / decision / subflow;边可带event标签。- 节点脚本:
operation/decision节点各对应一个export default async function run(ctx: FlowContext)。脚本通过ctx与外界交互,永不直接 import 框架。 - 跳转:脚本调
ctx.resolve(event),引擎在当前节点的出边里选event匹配的那条,进入目标节点。 - 子流程(subflow):
subflow节点进入targetFlow;子流程end节点的name= 返回事件,父 subflow 节点用同名出边接住。 - 宿主推事件:UI/宿主用
engine.emit(event, payload)把用户事件推给正在监听的节点(ctx.on(event, …))。
快速上手
import { createFlowEngine } from 'kiosk-flow-processor';
const engine = createFlowEngine({
services, // 你的领域服务,节点脚本经 ctx.services 访问
state, // 唯一状态容器,节点脚本经 ctx.state 读写
logger, // 可选:ctx.logger
i18n, // 可选:ctx.i18n
});
// 注册流程图与节点脚本(key 为类 glob 路径)
engine.registerFlows({ 'flows/Session.flw': sessionFlowDoc /* 或 JSON 字符串 */ });
engine.registerScripts({ 'flow-scripts/Session/OpenSession.ts': { default: openSessionRun } });
// 启动,返回顶层流程的结束事件名
const exit = await engine.run('Session');
// 运行期把用户事件推给当前节点
engine.emit('submitted', { pin: '1234' });一个节点脚本长这样:
import type { FlowContext } from 'kiosk-flow-processor';
export default async function run(ctx: FlowContext): Promise<void> {
ctx.services.screen.show('common/PinPad'); // 调宿主服务
ctx.on('submitted', ({ pin }) => { // 收宿主事件
ctx.state.pin = pin; // 写状态
ctx.resolve('continue'); // 选出边推进
});
ctx.on('cancelled', () => ctx.resolve('cancel'));
}强类型(声明合并)
FlowServices / FlowState / FlowEventMap 通过 declare module 'kiosk-flow-processor' 由使用方扩展,节点脚本因此获得精确类型,无需 any:
declare module 'kiosk-flow-processor' {
interface FlowServices { screen: ScreenService; /* … */ }
interface FlowState { pin: string | null; /* … */ }
interface FlowEventMap { submitted: { pin: string }; cancelled: void; }
}可观测性与健壮性
节点生命周期与当前位置
const engine = createFlowEngine({
onNodeEnter: ({ flowName, node, seq }) => log(`→ ${flowName}/${node.name} #${seq}`),
onNodeExit: ({ node, event }) => log(`← ${node.name} =${event}`),
});
engine.current; // { flowName, node } | null —— 运行中的当前节点;未运行/已结束为 null- 对所有节点类型触发,
seq在单次run()内单调递增。 - exit 的
event:start=出边事件、operation/decision=resolve的事件、end=该 end 节点名;subflow 的 exit 在子流程返回时才触发(event=子流程 end 名),形成正确的嵌套 trace。 - 回调抛错非致命(记
logger.error,不打断流程)。适合日志、埋点、屏幕切换钩子、测试断言。
节点清理钩子 ctx.onDestroy
export default async function run(ctx: FlowContext) {
const sub = external.subscribe(/* … */);
ctx.onDestroy(() => sub.unsubscribe()); // 节点离开(resolve 或错误)时触发一次
}多个句柄按注册顺序执行,先于 timer/订阅的自动清理,单个句柄抛错互不影响。
Runaway 守卫
maxSyncTransitions(默认 10000):两次 await 之间的连续同步转移(start/subflow/end)超过阈值即抛 FlowRunawayError,拦住"无 await 的结构环"(如子流程立即 end 的自返回环)造成的事件循环卡死。含 operation/decision 的合法长流程或无限主循环不受影响(每个 await 都会清零计数)。与 maxCallDepth(嵌套深度)正交。
节点错误:onNodeError + error 出边路由
createFlowEngine({ onNodeError: ({ node, error }) => report(node.name, error) });节点脚本抛异常时:先触发 onNodeError(非致命);若该节点有一条显式 error 出边则路由过去、流程继续,否则抛出原始错误 → onError → run() reject。resolve 到未知事件仍是致命错误,且不触发 onNodeError。
注册期出边校验
convertFlowDoc(经 registerFlows 调用)对以下歧义直接抛 FlowConvertError:同节点多条默认(空 event)出边、同节点重复 event、start 节点非单出边。Kiosk Flow Builder 画布也镜像了这三条(标为 error),授权期即可发现,不必等到运行期。
公开 API(节选)
createFlowEngine(options?)→FlowEngine(registerFlows/registerScripts/run/emit/current)FlowEngineOptions:services/state/logger/i18n/maxCallDepth/maxSyncTransitions/onError/onNodeEnter/onNodeExit/onNodeErrorFlowContext:resolve/on/off/setTimeout/setInterval/onDestroy/services/state/logger/flowName/nodeparseFlow(text)、convertFlowDoc(doc)、normalizeFlowModule/normalizeScriptModule、flowKeyFromPath/scriptKeyFromPath- 错误类型:
FlowParseError、FlowConvertError、FlowRunawayError - 类型:
FlowContext、FlowDoc、FlowNode、FlowEdge、FlowEngine、FlowEngineOptions、FlowServices、FlowState、FlowEventMap、FlowLogger、FlowI18n、NodeLifecycleInfo、NodeExitInfo、NodeErrorInfo… VERSION:构建版本串,semver + git 短哈希(如0.1.0+6f573fb)。运行期查当前引擎版本:import { VERSION } from 'kiosk-flow-processor'。
构建与版本
npm run build # tsup 打包到 dist/(esm + cjs + d.ts)
npm test # vitest版本以 package.json 的 version 为单一真相源,tsup 构建时注入 package.json version + git 短哈希 成为导出的 VERSION,便于把任意一次运行精确定位到源码 commit。
