@iworkflow/core

中文文档
0.15.1发布于 2026/11/01MITTypeScriptES Module零依赖

Engine-neutral durable workflow port: flows, steps, child workflows, signals, queries, patches, heartbeat, workflow contracts

原始使用文档

@iworkflow/core

Порт движка долгих процессов: код процесса пишется один раз и исполняется Temporal (@iworkflow/temporal) или DBOS (@iworkflow/dbos). Зависимостей нет; контракту процесса нужен @sinclair/typebox.

import type { Flow } from "@iworkflow/core";

export const outreach: Flow = async (ctx, input: { id: string }) => {
  const draft = await ctx.step<string>("compose", [input]);
  const profile = await ctx.child({ app: "iprofile", flow: "enrich" }, [input], { id: `${ctx.workflowId}-p` });
  const reply = await ctx.recv<string>("reply", 86_400_000);
  if (await ctx.patched("send-twice")) await ctx.step("remind", [input]);
  await ctx.setState("status", { reply });
  return { draft, profile, reply };
};

Шаг с прогрессом: heartbeat(details) и lastHeartbeat() из этого пакета внутри функции шага. Рантайм шага ставит адаптер на время шага (stepScope): в одном процессе могут жить оба движка. @iworkflow/core и адаптеры — одной версии: область шага общая через Symbol.for.

Отказы одинаковы на обоих движках. Шаг бросает nonRetryable(message, type, details) или new Failure(message, { type }); порт бросает FlowError с reason (failed, timeout, terminated, already-started, not-found), type, details и exhausted:

try {
  await ctx.step("send", [input], { attempts: 20, retryIntervalMs: 120_000, backoff: 1.5, pool: "paced" });
} catch (e) {
  if (e instanceof FlowError && e.type === "QuietHours") await ctx.sleep(e.details as number);
  else throw e;
}

Ещё: ctx.startChild (без ожидания), ChildOptions.timeoutMs (id не переиспользуется: занятый — already-started), ctx.startedAt, ctx.parentId, AppDefinition.pools и maxConcurrentSteps, StartOptions.onExisting, handle.describe() и handle.terminate(), client.list({ idPrefix, flow, status, since, limit }) — процессы движка, свежие первыми (без своего реестра запусков).

Приоритет: client.start(..., { priority }) (1 — первым, 5 — последним) → ctx.priority; шаги и дети его наследуют, StepOptions.priority — свой. Срок повтора из отказа: new Failure(msg, { type, retryAfterMs }); benign: true — штатный отказ, Temporal не пишет его ошибкой. Номер попытки в шаге — stepAttempt(), процесс и запуск — stepInfo(), отмена попытки — stepSignal(). Журнал без повторов — ctx.log. Время в процессе — await ctx.now(). client.signalWithStart(target, args, opts, topic, value) — сигнал идущему или запуск с сигналом. Контракт обоих движков — lab/test/contract.test.ts репозитория.

Контракт процесса

Владелец процесса объявляет контракт схемами TypeBox (@sinclair/typebox 0.34, как в Elysia) в своём пакете; соседи импортируют его оттуда, а не копируют типы.

import { Type as T } from "@sinclair/typebox";
import { childContract, defineWorkflowContract, startContract } from "@iworkflow/core/contract";

export const organizationOnboarding = defineWorkflowContract({
  name: "organizationOnboarding",
  taskQueue: "iprofile",
  workflowId: "iprofile-org-{code}",            // поля — обязательные поля входа
  input: T.Object({ code: T.String(), linkedBy: T.String() }),
  outcomes: { created: T.Object({ ok: T.Literal(true), organizationId: T.String() }),
              refused: T.Object({ ok: T.Literal(false), reason: T.String() }) },
  states: { checking: { next: ["provisioning", "refused"] }, provisioning: { next: ["created"] } },
  signals: {}, queries: {},                        // тема/ключ → { description, schema }
});

const h = await startContract(client, organizationOnboarding, input);   // id из шаблона, исход типизирован
const out = await childContract(ctx, organizationOnboarding, input);     // из кода процесса

Ошибки согласованности (поле id не во входе, недостижимый исход, состояние без пути к исходу) — при импорте. workflowId(input), startContract и childContract на пустом или неверном поле отдают Failure с типом InvalidWorkflowId (nonRetryable): в процессе это отказ, а не вечный повтор задачи. Отказ по делу (bad-code) родитель получает, если сам проверит вход до запуска или поймает isFailure(e) && e.type === INVALID_WORKFLOW_ID. Манифест (JSON Schema входа, исходов, сигналов и query), страница markdown с диаграммой Mermaid и проверка дрейфа:

bunx iworkflow-contracts src/temporal/contract.ts --out docs/contracts          # записать (нужен bun: грузит .ts)
bunx iworkflow-contracts src/temporal/contract.ts --out docs/contracts --check  # в CI: падает на расхождении

Один вызов на каталог со всеми модулями: лишние файлы с меткой генератора запись удаляет, файлы без метки не трогает.

版本与发布记录

@iworkflow/core 共发布 18 个版本,首次发布于 2026/10/21;过去 12 个月发布了 18 个版本。

版本发布日期距今
0.15.1latest2026/11/012 天前
0.15.02026/11/012 天前
0.14.02026/10/303 天前
0.13.02026/10/304 天前
0.12.02026/10/304 天前
0.11.02026/10/304 天前

关于 @iworkflow/core 的常见问题

@iworkflow/core 是什么?

引擎中立的持久工作流端口:流、步骤、子工作流、信号、查询、补丁、心跳、工作流契约

如何安装 @iworkflow/core?

在项目目录运行 npm install @iworkflow/core,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 iworkflow-contracts,可以用 npx iworkflow-contracts 直接运行。

@iworkflow/core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@iworkflow/core 支持 ESM 还是 CommonJS?

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

@iworkflow/core 需要什么版本的 Node.js?

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

@iworkflow/core 可以商用吗?

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

@iworkflow/core 还在维护吗?

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