npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@floken-io/engine

v0.0.3

Published

Token-based process engine with Chinese-style approval actions for floken

Downloads

90

Readme

@floken-io/engine

npm license

令牌制流程内核 + 中国式审批动作(会签 / 或签 / 票签 / 加签 / 转办 / 驳回 / 撤回)。

零 DOM、零数据库、零定时——持久化与调度全部经 SPI 注入,核心引擎零基础设施依赖。

安装

npm i @floken-io/engine @floken-io/moddle

@floken-io/moddle 是必需 peer(审批配置由它归一化);缺失时引擎抛 ENGINE_PEER_MISSING 并在 hint 里给出安装命令——不内置兜底实现。

@floken-io/feel 是可选 peer,装了即可在网关上写 amount > 5000 这样的条件,无需额外接线。

浏览器 / 打包器环境里 node:module 取不到,用 registerPeer() 显式注入:

import { registerPeer } from '@floken-io/engine';
import * as moddle from '@floken-io/moddle';

registerPeer('@floken-io/moddle', moddle);   // 任何 engine 调用之前执行一次

快速开始

一个三段报销流程,从发起到办结:

import { createEngine, createMemoryStore } from '@floken-io/engine';

// 流程定义:通常由 @floken-io/designer 产出,这里直接手写(Model JSON v2 形状)
const def = {
  schemaVersion: '2.0.0',
  id: 'Process_1',
  nodes: [
    { id: 'Start_1', type: 'startEvent' },
    { id: 'Task_1', type: 'userTask',
      approval: { approvers: [{ type: 'user', value: 'u_manager' }] } },
    { id: 'Task_2', type: 'userTask',
      approval: { approvers: [{ type: 'user', value: 'u_finance' }] } },
    { id: 'End_1', type: 'endEvent' },
  ],
  flows: [
    { id: 'f1', from: 'Start_1', to: 'Task_1' },
    { id: 'f2', from: 'Task_1', to: 'Task_2' },
    { id: 'f3', from: 'Task_2', to: 'End_1' },
  ],
};

const engine = createEngine({
  // 定义源是「图纸」:只读、按版本取。改版不影响在途实例
  definitionSource: {
    async getDefinition(processId, version) {
      return processId === 'Process_1' && version === 1 ? def : null;
    },
  },
  store: createMemoryStore(),        // 不传也是它
  clock: () => new Date().toISOString(), // 不传用 Date.now(),但显式传可让测试完全可复现
});

const id = await engine.start('Process_1', {
  definitionVersion: 1,
  starter: 'u_applicant',
  variables: { amount: 8600 },
});

await engine.submit(id, { action: 'approve', actor: 'u_manager', comment: '同意' });
await engine.submit(id, { action: 'approve', actor: 'u_finance' });

const trace = await engine.exportTrace(id);
console.log(trace.entries.map((e) => `${e.seq} ${e.actor} ${e.action} ${e.from} → ${e.to}`));
// [
//   '1 u_applicant start Start_1 → Task_1',
//   '2 u_manager approve Task_1 → Task_2',
//   '3 u_finance approve Task_2 → End_1'
// ]

API 面

| 方法 | 用途 | |---|---| | start(processId, opts) | 发起实例,返回 instanceId | | submit(instanceId, action) | 提交一次审批动作,返回待办差分 TaskDelta | | deliverMessage(instanceId, input) | 点对点投递消息,唤醒在等它的令牌 | | deliverSignal(instanceIds, input) | 广播信号,唤醒候选里所有在等的实例 | | exportTrace(instanceId) | 导出令牌轨迹(auditTrail 的只读投影,不新增存储) | | plan(state, action, options?) | ★ 纯函数入口:给宿主自己包事务用(不碰存储) |

submit() 只是 plan() + 落库的便利封装——两条路径的状态演化完全一致,由测试钉死。

19 项审批动作

approve / reject / rejectTo / rollback / rollbackTo / transfer / delegate / delegateBack / addSignBefore / addSignAfter / addSignParallel / reduceSign / takeBack / revoke / urge / suspend / resume / terminate / skip。

其中 17 项由内核原生执行;超时与暂存 2 项在内核外(由调度层经 Scheduler SPI 驱动)。 当前配置下哪些动作可用,用 enabledActionNames(config) 问——按钮该不该灰,只有这一处判据。

11 项 SPI

| 组 | 接口 | 默认 | |---|---|---| | 存储三线 | StateStore(真相)/ TaskProjection(视图)/ DefinitionSource(图纸) | 内存 store;后两项必须注入 | | 业务接入 | ApproverSource / ServiceHandler / AuthResolver / FormProvider | 无(不注入 = 相关节点走不通并显式报错) | | 求值 | conditionHandler / decisionHandler | 条件有默认 FEEL 实现;决策无默认 | | 出口 | EventSink / Scheduler | 无(不注入 Scheduler = 不排程,不假装做了) |

一致性

  • 快照 + rev CAS:save(next, expectedRev),expectedRev === 0 是 INSERT 信号,否则 CAS UPDATE;不匹配抛 ENGINE_PERSIST_CONFLICT,不静默覆盖。
  • 并发三道防线:进程内 per-instance 串行队列(主力)+ rev CAS(跨进程兜底);不开读从库、不加悲观锁。
  • 引擎内不做事务:要么走 submit()(门 1:save 后触发 hooks,至少一次 + 宿主幂等),要么用 plan()(门 2:纯函数,宿主自己包事务)。

能力边界(诚实清单)

已知但未实现的节点类型一律显式抛错并指名归属需求编号——引擎刻意不把它们降级成"自动直通", 因为那会让「这件事从来没发生过」变成一个没有报错的静默事实:

sendTask · intermediateThrowEvent · implicitThrowEvent · complexGateway · AdHocSubProcess · 补偿处理器(compensate);捕获事件目前只认 message / signal(等 timer / error 仍抛)。

endEvent 的 eventDefinition(terminate / message)尚未区分。

同样没有的:复杂查询与报表、作业执行器与重试框架、多租户、批量操作 API、历史归档、可视化运维台、 时间旅行重放、分布式锁(只有 CAS 重试)。这些归宿主——03-包需求 §9.6 写明这是设计取向,不是欠账。

相关包

| 包 | 用途 | |---|---| | @floken-io/feel | FEEL 表达式语言 | | @floken-io/moddle | Model JSON 数据模型与校验(必需 peer) | | @floken-io/dmn | DMN 1.5 决策引擎 | | @floken-io/engine | 流程内核与审批动作(本包) | | @floken-io/designer | 流程画布与审批配置面板(开发中) |

许可证

Apache-2.0