@type32/workflows

中文文档
0.1.1发布于 2026/11/03MITES Module零依赖

工作流脚本引擎

中文使用文档
文档目录6 节
  1. 使用
  2. 测试一门语言
  3. 在画布上编辑
  4. 脚本
  5. 释放
  6. 许可证

@type32 /工作流

一个可视化的节点脚本工具包。语言开发者在TypeScript中定义类型、节点和效果;最终用户从画布上的这些节点构建文档;引擎验证并运行它们。文档是不可信的数据:引擎从不评估文档中的代码。

设计: 节点引擎规格。Vertex是第一种语言。

状态:M5。 @type32/workflows/core 具有类型系统、具有类型化节点上下文的语言定义、 validate、运行时、函数和变量; migrate 和 hashProgram; @type32/workflows/core/std 有标准节点; @type32/workflows/core/testing 有测试助手; @type32/workflows/vue 具有画布编辑器。

使用

import { compile, defineLanguage, t, validate } from "@type32/workflows/core";
import { std } from "@type32/workflows/core/std";

// Effects keyed by capability id, and the TypeScript value type of each of the language's own types.
const vertex = defineLanguage<
  { fs: { rename(file: string, name: string): Promise<string> } },
  { "vertex.File": string }
>({
  id: "vertex",
  version: 1,
  include: [std], // control, operators (logic, math, compare, dates), text, lists (filter, map, sort, …), dictionaries
})
  .type({ id: "vertex.File", label: "File", schema: { type: "string", pattern: "^/" } }) // a schema allows literals
  .capability({ id: "fs", mutates: true, label: "Modify files" })
  .node({
    type: "vertex.onManual",
    version: 1,
    mode: "event",
    label: "On Run",
    exec: { out: ["then"] },
  })
  .node({
    type: "vertex.rename",
    version: 1,
    mode: "exec",
    label: "Rename",
    exec: { out: ["then"] },
    inputs: {
      file: { type: t("vertex.File"), required: true },
      newName: { type: t("string"), required: true },
    },
    outputs: { file: { type: t("vertex.File") } },
    requires: ["fs"],
    async run(ctx) {
      const { file, newName } = await ctx.inputs(); // file: string, newName: string
      ctx.output("file", await ctx.effects.fs.rename(file, newName)); // ctx.effects has only `fs`
      return "then"; // only declared exec outputs compile
    },
  })
  .build(); // throws on any manifest problem

// Editors need only the JSON manifest.
const analysis = validate(document, vertex.manifest);
analysis.diagnostics; // [{ code: "E_TYPE_MISMATCH", message: "e7: string → vertex.File", at: { edge: "e7" } }, …]
analysis.typeOf("n3", "item", "out"); // resolved port type, e.g. { kind: "con", id: "vertex.File" }
analysis.capabilities; // Set { "fs", … } reachable from event nodes

// Hosts run it. compile throws CompileError on any error diagnostic; dispatch never rejects for program errors.
const result = await compile(document, vertex).dispatch("vertex.onManual", undefined, {
  effects: { fs: { rename: async (file, name) => `/vault/${name}.md` } }, // each node sees only its `requires`
  signal, // abort → { status: "aborted" }
  limits: { steps: 1_000, timeoutMs: 10_000 },
  trace: "nodes",
  onTrace: (event) => console.log(event), // { t: "enter" | "exit" | "fire" | "value" | "fault", node, … }
});
result; // { status: "ok" | "fault" | "aborted", fault?: { code: "E_UNSET_VALUE", message, node }, steps }

ctx 根据定义键入:端口名称,值类型(List → readonly T[], Dictionary → ReadonlyMap、通用件 unknown),在编译时检查执行输出和功能。输入没有 required 或者一个 default 可能是 undefined.

测试一门语言

import { buildDocument, formatTrace, record, run } from "@type32/workflows/core/testing";

const doc = buildDocument(vertex, {
  nodes: {
    go: "vertex.onManual",
    r: { type: "vertex.rename", literals: { file: "/v/a.md", newName: "b" } },
  },
  edges: ["go.then > r.exec"],
});
const rec = record({ fs: { rename: async (file: string, name: string) => `/v/${name}.md` } });
const { result, trace } = await run(vertex, doc, {
  event: "vertex.onManual",
  effects: rec.effects,
});
rec.log(); // ["fs.rename(/v/a.md, b)"]
formatTrace(trace); // [">go", "<go.then", ">r", "<r.then"]; `~n3.body` is a fire

assertValid(doc, language) 抛出一个错误,列出每个诊断。这些助手不依赖于测试框架。

通用节点声明 typeParams: [{ name: "T", bound?: t("vertex.Entry") }] 和使用 tvar("T") 在端口类型中; validate 推断每个实例的 T 从它的边缘。

运行时的值: List 是一个数组, Dictionary 是一个 ReadonlyMap (Dictionary 字面量被解码为 Map). 极限故障和中止是致命的; std.try 只捕获节点错误和其他错误。

加载文件: migrate(JSON.parse(text), language) 是不可信输入的边界。它返回 { doc, changed } (一个干净 DocumentV1 只有已知字段(已迁移到当前格式、语言和节点版本)或 { diagnostics } 当文件无效或比代码更新时,在这种情况下,主机以只读方式打开它。 hashProgram(doc) 是程序的SHA-256,忽略view和meta;由它托管密钥信任记录。

函数存在于 doc.program.functions。一个身体有一个 core.entry 和一个 core.return; core.call 调用它, core.get/core.set 读取和写入文档变量或函数的局部变量。声明泛型函数 typeParams (具有可选边界),使用那些作为刚性类型检查一次,并且在每次调用时都被实例化。达到 core.return 立即结束通话;一个从未到达它的exec函数是错误的 E_NO_RETURN.

在画布上编辑

同行依赖性: vue, @vue-flow/core 和 @nuxt/ui。组件是从next UI构建的,因此在主机中设置它 @nuxt/ui 模块,或者它的Vite和Vue插件 <UApp>),然后让Tailwind从你的CSS条目中扫描这个包:

@import "tailwindcss";
@import "@nuxt/ui";
@source "../node_modules/@type32/workflows/dist"; /* relative to this file */
<script setup lang="ts">
import "@type32/workflows/vue/style.css";
import { useWorkflowEditor, WorkflowWidget, WorkflowWorkbench } from "@type32/workflows/vue";

const editor = useWorkflowEditor(vertex.manifest, document); // the manifest is enough to edit
async function start() {
  const { trace } = await run(vertex, editor.snapshot(), {
    event: "vertex.onManual",
    effects,
    trace: "values",
  });
  editor.trace.value = trace; // run counts, values, taken edges and faults on the canvas
}
</script>

<template>
  <!-- canvas, node library and inspector; or compose WorkflowCanvas, WorkflowPalette, WorkflowInspector yourself -->
  <WorkflowWorkbench :editor="editor">
    <template #toolbar><UButton label="Run" @click="start" /></template>
    <!-- optional: replace one type's literal editor (on the canvas and in the inspector), keep the default for the rest -->
    <template #widget="w">
      <MyFilePicker v-if="w.type?.kind === 'con' && w.type.id === 'vertex.File'" v-bind="w" />
      <WorkflowWidget v-else v-bind="w" />
    </template>
  </WorkflowWorkbench>
</template>

analysis 重播 validate 每一次改变。将添加接线或类型错误的拖动将被拒绝,并显示原因。连接使用的数据输入或执行输出取代其边缘。节点遵循主机的next UI主题;端口和电线的颜色是 --wf-* CSS变量。 bun run play 打开一个带有§10语言和内存库的游乐场。

语言将它们的节点分组为类别,这些类别为节点库提供部分,并为节点提供标题颜色。标签可以嵌入输入,节点内联显示为一个句子:

defineLanguage({ id: "notes", version: 1, include: [std] })
  .category({ id: "notes.files", label: "Files", color: "teal", icon: "i-lucide-folder" })
  .node({
    type: "notes.rename",
    label: "Rename {file} to {newName}", // inputs named here render inline
    category: "notes.files",
    // …
  });

颜色(TypeDef, CategoryDef,以及节点自己的 color)取一个调色板名称(sky, rose,…)或任何CSS颜色。变量和My Blocks在类别之后 order 0或更小,所以 order: 1 将语言自己的部分放在它们后面,就像在Scratch中一样。

要翻译语言,请在将其交给editor: node之前重写其清单的字符串 label (保持 {input} 占位符)和 description、港口 label年代, exec.labels 对于执行输出,以及类别、类型和功能标签。

为每个项目计算一次输入的节点,例如 std.filter,在子作用域中读取它,其中项是它自己的输出: await ctx.input("keep", { scope: { item, index } }) 重新计算后面的纯节点 keep 对于那个项目。

在画布上,拖动空白区域绘制一个选择框;滚动,两个手指,空格+拖动或中间按钮平移,并⌘/Ctrl+滚动或缩放。快捷方式:⌘Z /⌘⌘Z撤消和重做(除视窗外的每个文档编辑),⌘C /⌘X /⌘V /⌘D复制,剪切,粘贴在光标处并复制,⌘A选择全部,Tab或双击在光标处打开节点搜索。在空白处放置一根电线,打开搜索,过滤到可以接受它的节点,并连接到你选择的那个节点。 右键单击画布、节点、边缘或端口将打开菜单; WorkflowCanvas的 menu 道具可以改变它的物品,而它的 #selection Slot将按钮添加到多节点选择下显示的栏中。

const editor = useWorkflowEditor(vertex.manifest, document, {
  messages: () => ({ undo: t("undo"), selected: (n) => t("selected", { n }) }), // translate the built-in strings
  clipboard: { read: readText, write: writeText }, // default: navigator.clipboard
});

副本以JSON格式(parseClip 读取它们),因此它们粘贴到相同语言的另一个文档中。当剪贴板无法读取(被拒绝的权限)时,粘贴会返回到应用程序中的最后一个副本。

程序从库和选择栏中增长结构。节点库中的变量和我的块创建变量和块(函数);对于Get,单击或拖动变量,对于Set,单击或拖动变量;检查器编辑所选节点后面的声明(块的名称、输入、输出和退出)。一个执行块可能有几个出口,每个Return指定一个出口,因此调用可以像 std.if。选择节点并选择Make block将它们移动到一个名为in their place的新块中(extractFunction in core完成工作),或者Group构建它们;拖拽组的标题会移动其中的内容,画布菜单会添加粘性注释。组和评论存在于 view,所以它们不会改变程序散列。双击一个调用打开它的代码块;面包屑通向后面。

通过编辑器在画布上观看: editor.run 递给你 onTrace, signal 和 beforeStep 展开到运行中,画布显示一个运行条(Pause、Step、Continue、Cancel),并显示正在运行的节点。

editor.stepping.value = stepThrough; // stop before the first node
const { result } = await editor.run((live) =>
  run(vertex, editor.snapshot(), { event: "vertex.onManual", effects, trace: "values", ...live }),
);

花在 beforeStep 在上面提到的能力中 interactive (提示,拾取者)不计入 limits.timeoutMs.

脚本

脚本做
bun run build包 src/core 和 src/vue → dist/ + .d.mts
bun run play维特游乐场(playground/)
bun run test运行Vitest一次(test:watch 观看)
bun run typecheckui:prepare (生成next UI的主题类型),vue-tsc (TypeScript 6.0.3), TypeScript 7 on src/core
bun run lintOxlint
bun run formatOxfmt (format:check 仅供核实)
bun run checkLint + format:check + typecheck + test
bun run changeset记录下一个版本的变更

消费者支持的最小TypeScript是 6.0.3,被钉住的dev typescript,所以 typecheck 与它背道而驰。vue-tsc需要TypeScript API,而7.0没有;TypeScript 7 (typescript7)检查 src/core 没有DOM库。子路径导出是手工列出的 package.json.

释放

运行 bun run changeset 在你的PR中,在合并到 main,发布工作流打开“版本包”PR;合并发布到npm。

许可证

麻省理工学院

版本与发布记录

@type32/workflows 共发布 3 个版本,首次发布于 2026/11/03;过去 12 个月发布了 3 个版本。

版本发布日期距今
0.1.1latest2026/11/03今天
0.1.02026/11/03今天
0.0.0-stage2026/11/03今天

关于 @type32/workflows 的常见问题

@type32/workflows 是什么?

工作流脚本引擎

如何安装 @type32/workflows?

在项目目录运行 npm install @type32/workflows,或使用 pnpm add / yarn add / bun add。

@type32/workflows 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/type32__workflows 是否存在。

@type32/workflows 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@type32/workflows 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.19。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@type32/workflows 可以商用吗?

@type32/workflows 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@type32/workflows 还在维护吗?

最新版本 0.1.1 发布于 2026/11/03(今天);至今共发布 3 个版本,其中过去 12 个月发布了 3 个;首次发布于 2026/11/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。