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

noomiflow

v0.0.9

Published

轻量 Node.js / TypeScript 工作流引擎

Readme

乾渡(noomiflow)

乾渡是面向 Node.js / TypeScript 的轻量工作流引擎:用 JSON 描述流程,用 Relaen 持久化定义、实例与任务,适合审批、编排与业务自动化。

包名:noomiflow · 原项目演进自 noomiflow

为何叫乾渡

「乾」为天、为行;「渡」为抵达。乾渡希望用简洁的 API,把业务从起点安全地渡到终点——分支、汇聚、等待、子流程皆可。

特性一览

| 能力 | 说明 | |------|------| | 流程控制 | 顺序流;排他 / 并行 / 包容网关 | | 人工任务 | 指派、候选人、候选组、认领、改派 | | 自动任务 | 本地 JS 模块任务(moduletask) | | 等待与协作 | 定时 / 消息 / 信号等待;子流程调用 | | 变量与恢复 | 流程变量、网关汇聚计数、等待态持久化与恢复 | | 版本发布 | 定义版本自增;draft / published | | 事件钩子 | NFEventBus 监听流程与节点生命周期 | | 历史轨迹 | 按实例查询节点办理记录 |

环境要求

  • Node.js 18+(推荐 20+)
  • TypeScript 5+(开发)
  • MySQL 等 Relaen 支持的数据库(运行时持久化)

安装

npm install qiandu

依赖需自行初始化 Relaen,并将 entities 指向本包内实体路径(构建后一般为 node_modules/qiandu/dist/core/entity/**/*.js)。

快速开始

import { RelaenManager } from 'relaen';
import { NFEngine, NFUserManager, NFEventBus } from 'qiandu';

await RelaenManager.init({
  dialect: 'mysql',
  host: '127.0.0.1',
  port: 3306,
  username: 'root',
  password: '***',
  database: 'qiandu',
  entities: ['node_modules/qiandu/dist/core/entity/**/*.js'],
});

NFEventBus.on('process.end', ({ process }) => {
  console.log('流程结束', process.entity.processId);
});

// 1. 定义并发布流程
await NFEngine.defineProcess({
  name: '人工审核流程',
  status: 'published',
  nodes: [ /* 见下方 */ ],
});

// 2. 创建实例并启动(发起人 userId = 1)
const proc = await NFEngine.createProcess('人工审核流程', '订单-001', 1);
await proc.start();

// 3. 写流程变量 + 办理(userId 必填;agree 只进任务记录)
await proc.next({
  userId: 1,
  variables: { data: 1 },
});

// 或:认领后完成
const ts = NFEngine.getTaskService();
const todo = await NFUserManager.getUnHandleNodes(1);
await ts.claim(todo.rows[0].nodeId, 1);
await ts.finish(todo.rows[0].nodeId, {
  userId: 1,
  agree: 1,
  reason: '通过',
  variables: { approved: true },
});

流程定义

定义对象必须包含 namenodes{ name, nodes, dueTime?, version?, status? }
节点之间用 sequence 连接;条件写在顺序流的 cond 上。

{
  "name": "人工审核流程",
  "nodes": [
    { "type": "start", "id": "start" },
    { "type": "sequence", "src": "start", "dst": "task1" },
    { "type": "usertask", "id": "task1", "name": "提交审核" },
    { "type": "sequence", "src": "task1", "dst": "exgate1" },
    { "type": "exclusive", "id": "exgate1" },
    { "type": "sequence", "src": "exgate1", "dst": "end", "cond": "data==1" },
    { "type": "sequence", "src": "exgate1", "dst": "task2", "cond": "data==2" },
    {
      "type": "usertask",
      "id": "task2",
      "name": "审核",
      "candidateUsers": "2"
    },
    { "type": "sequence", "src": "task2", "dst": "end" },
    { "type": "end", "id": "end" }
  ]
}

节点类型

| type | 说明 | |------|------| | start / end | 开始 / 结束 | | sequence | 顺序流:src / dst;可选 conddefault | | usertask | 人工任务:assignee / candidateUsers / candidateGroups(均为用户或组 id) | | moduletask | 本地模块:必填 path | | exclusive | 排他:第一条满足条件的出口,否则 default | | parallel | 并行:分叉全出 / 汇聚等全部入边 | | inclusive | 包容:多选出边;汇聚按激活可达数等待 | | wait | 等待:waitKind = timer | message | signal | | subprocess | 子流程:callProcess 为已发布定义名 |

等待 / 子流程示例

{ "type": "wait", "id": "w1", "waitKind": "timer", "delay": 60000 }
{ "type": "wait", "id": "w2", "waitKind": "message", "messageName": "paid" }
{ "type": "wait", "id": "w3", "waitKind": "signal", "signalName": "go" }
{ "type": "subprocess", "id": "sub1", "callProcess": "子流程名" }
await proc.message('paid', { amount: 100 });
await proc.signal('go');
await proc.resumeWaits(); // 到期定时器、已结束子流程

核心 API

NFEngine

| 方法 | 说明 | |------|------| | defineProcess(cfg) | 保存定义(版本自动 +1,默认 published) | | publishProcess(name, ver?) | 发布草稿 | | listDefinitions(name?) | 列出定义 | | findDefinition(name, ver?) | 查找定义(默认最新已发布) | | createProcess(name, instName, userId?, ver?) | 创建实例 | | getInstance(id) | 加载并恢复运行态 | | getHistory(id) | 办理轨迹 | | closeProcess(id, reason?) | 强制关闭并清理待办 | | getTaskService() | 任务服务 |

NFProcess

| 方法 | 说明 | |------|------| | start / next / end / close | 生命周期 | | setParam / setParams / getParam | 流程变量 | | message / signal / resumeWaits | 等待唤醒 | | getHistory / getWaits | 历史与等待态 |

next 约定(重要)

  • userId:仅当前节点为 人工任务 时必填(办理权限校验);等待 / 模块 / 网关等节点可省略
  • agree / reason:只写入任务办理记录(NfNode),不会自动进入流程变量
  • 网关条件等业务变量:使用 setParamsnext({ variables: { ... } })
await proc.next({
  userId: 2,
  agree: 1,
  reason: '审核通过',
  variables: { approved: true }, // 供后续网关使用
});

NFTaskService

认领 / 取消认领 / 指派 / 候选人 / 候选组 / 查询 / finish(taskId, cfg?)
taskId 为任务实例 id(NfNode.nodeId),不是定义里的节点 id。

NFUserManager

用户与组维护;getUnHandleNodes / getHandledNodes / getCreatedProcess

NFEventBus

import { NFEventBus } from 'qiandu';

NFEventBus.on('process.start', ({ process }) => {});
NFEventBus.on('task.complete', ({ node, cfg }) => {});
NFEventBus.on('wait.resume', ({ nodeId }) => {});

| 事件 | 时机 | |------|------| | process.start / end / close / error | 流程生命周期 | | node.enter / leave / error | 节点进出 | | task.create / complete | 人工任务 | | wait.resume | 等待被唤醒 |

目录结构

dianqudev/
├── core/           # 引擎、节点、实体、任务服务
├── examples/       # 可运行示例与流程 JSON
├── test/           # 不依赖数据库的单元测试
├── index.ts        # 包导出
└── package.json    # name: qiandu

示例与本地开发

git clone <repo>
cd qiandu/diandu/dianqudev
npm install
npm run build
npm test                 # 单元测试(无需数据库)

带库示例(先改 examples/relaenconfig.ts):

node dist/examples/usertest.js          # STEP=init 初始化用户组
STEP=demo node dist/examples/usertasktest.js
STEP=demo node dist/examples/moduletest.js

| 示例 | 说明 | |------|------| | usertest.ts | 用户 / 组 | | usertasktest.ts | 人工审核(flow1) | | moduletest.ts | 模块求和(flow2) | | flows/*.json | 流程定义 |

版本与发布

// 草稿
await NFEngine.defineProcess({ name: '订单', nodes, status: 'draft' });
await NFEngine.publishProcess('订单');

// 或直接发布(默认)
await NFEngine.defineProcess({ name: '订单', nodes });

// 按版本创建实例
await NFEngine.createProcess('订单', 'inst-1', 1, 2);

旧数据若无 defTypecreateProcess 会按定义名回退查找。

设计约定速查

  1. 变量与办理分离:网关读 getParam;审批意见用 agree 落在任务行。
  2. 人工任务会停住:需 nextTaskService.finish 推进。
  3. 等待节点会挂起:用 message / signal / resumeWaits / next()(无需 userId)继续。
  4. 关闭流程会清待办:已关闭实例不再出现在未办列表。

许可证

MIT


乾渡 qiandu — 把流程从起点渡到终点。