ts-workflow-engine-lite 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +140 -21
  2. package/README.zh.md +353 -0
  3. package/dist/src/api/docsPage.js +6 -1
  4. package/dist/src/api/docsPage.js.map +1 -1
  5. package/dist/src/cli.js +0 -4
  6. package/dist/src/cli.js.map +1 -1
  7. package/dist/src/engine/InstanceManager.d.ts +1 -0
  8. package/dist/src/engine/InstanceManager.js +4 -0
  9. package/dist/src/engine/InstanceManager.js.map +1 -1
  10. package/dist/src/engine/WorkflowInstanceControl.js +2 -4
  11. package/dist/src/engine/WorkflowInstanceControl.js.map +1 -1
  12. package/dist/src/storage/ArchiveManager.js +1 -0
  13. package/dist/src/storage/ArchiveManager.js.map +1 -1
  14. package/dist/src/storage/LocalFileStorage.d.ts +17 -0
  15. package/dist/src/storage/LocalFileStorage.js +205 -65
  16. package/dist/src/storage/LocalFileStorage.js.map +1 -1
  17. package/dist/src/storage/MemoryStorage.d.ts +29 -0
  18. package/dist/src/storage/MemoryStorage.js +205 -51
  19. package/dist/src/storage/MemoryStorage.js.map +1 -1
  20. package/dist/src/storage/StorageProvider.d.ts +2 -0
  21. package/dist/src/templates/AutoExtract.js +1 -1
  22. package/dist/src/templates/AutoExtract.js.map +1 -1
  23. package/dist/src/utils/concurrency.d.ts +1 -0
  24. package/dist/src/utils/concurrency.js +7 -0
  25. package/dist/src/utils/concurrency.js.map +1 -1
  26. package/package.json +4 -18
  27. package/dist/src/demo/demoWorkflow.d.ts +0 -3
  28. package/dist/src/demo/demoWorkflow.js +0 -82
  29. package/dist/src/demo/demoWorkflow.js.map +0 -1
  30. package/dist/src/demo/exampleWorkflow.d.ts +0 -36
  31. package/dist/src/demo/exampleWorkflow.js +0 -184
  32. package/dist/src/demo/exampleWorkflow.js.map +0 -1
  33. package/dist/src/demo/index.d.ts +0 -2
  34. package/dist/src/demo/index.js +0 -3
  35. package/dist/src/demo/index.js.map +0 -1
package/README.md CHANGED
@@ -32,7 +32,7 @@ and run the checked-in quick start:
32
32
 
33
33
  ```bash
34
34
  pnpm install
35
- pnpm example:quickstart
35
+ pnpm example quickstart
36
36
  ```
37
37
 
38
38
  ## 5-minute TypeScript quick start
@@ -102,11 +102,11 @@ create a second engine sharing that container) without calling
102
102
  ## Run the examples
103
103
 
104
104
  ```bash
105
- pnpm example:quickstart # minimal embedded workflow
106
- pnpm example:order # condition branch and multi-step order workflow
107
- pnpm example:embedded-express # mounting createWorkflowRouter into a host Express app
108
- pnpm example:error-handling # catching WorkflowNotFoundError / InstanceNotFoundError
109
- pnpm dev # event-driven built-in demo
105
+ pnpm example quickstart # minimal embedded workflow
106
+ pnpm example order-processing # condition branch and multi-step order workflow
107
+ pnpm example embedded-express-app # mounting createWorkflowRouter into a host Express app
108
+ pnpm example error-handling # catching WorkflowNotFoundError / InstanceNotFoundError
109
+ pnpm dev # event-driven built-in demo
110
110
  ```
111
111
 
112
112
  ## Run the REST API
@@ -229,6 +229,88 @@ await engine.resumeRunningInstancesFromStorage();
229
229
  must be registered first. Embedded applications otherwise opt into recovery
230
230
  with `resumeRunningInstances: true`.
231
231
 
232
+ #### Restart/recovery checklist
233
+
234
+ Treat recovery as an application startup protocol:
235
+
236
+ 1. Use `storageType: "file"` with a durable, instance-specific
237
+ `storageDirectory`; never share it between engine processes.
238
+ 2. Register every workflow containing function-valued `action` or `rollback`
239
+ handlers before `resumeRunningInstancesFromStorage()`.
240
+ 3. Stop cleanly before backing up or replacing storage. File persistence is
241
+ restart-safe, but it is not a database transaction.
242
+ 4. Make external effects idempotent. A crash can happen after an effect
243
+ succeeds but before its result is persisted, so include instance and node
244
+ attempt identity in downstream idempotency keys.
245
+ 5. Use retries for transient errors, `failureNext` for deliberate recovery,
246
+ `rollback` for compensating effects, and the DLQ for exhausted/manual cases.
247
+ Rollback is Saga-style compensation, not an atomic undo.
248
+ 6. Inspect each `*/corrupt/` directory after startup; quarantined records are
249
+ not restored automatically.
250
+
251
+ Durable `wait` nodes persist their original deadline: after a restart they
252
+ wait only for the remaining time, and fire immediately if it has passed. This
253
+ fits “eventually after N days”, not a deadline that must fire while the process
254
+ is down. For that case, use an external scheduler to publish an `event`.
255
+
256
+ ### Node handbook: semantics and failure behavior
257
+
258
+ The complete field-by-field reference, examples, and common mistakes are in
259
+ [`docs/NODE_REFERENCE.md`](./docs/NODE_REFERENCE.md). The core execution model
260
+ is:
261
+
262
+ | Node family | Success | Failure / recovery |
263
+ | ------------------------------------------------ | ------------------------------------------------- | ------------------------------------------------------------ |
264
+ | `action`, `http`, `sql`, `queue`, `notification` | Persist output, then follow `next` | Retry; then `failureNext`, otherwise instance failure / DLQ |
265
+ | `condition`, `router` | Evaluate and follow the selected branch | Invalid expression or unmatched route fails the node |
266
+ | `transform` | Build an output object from expressions | Expression errors fail the node; prior outputs are unchanged |
267
+ | `wait`, `event`, `approval` | Persist waiting state and resume via `next` | Timeout/cancellation is failure unless explicitly handled |
268
+ | `loop`, `subworkflow`, `join` | Complete aggregate/child work and continue | Body/child failure or missing join branch propagates |
269
+ | `rollback` | Run compensation and follow `rollbackTo` / `next` | Compensation can fail; it is not a transaction boundary |
270
+
271
+ `next` is a batch of successor IDs and may fan out. `failureNext` is a failure
272
+ path, not an implicit rollback. Runtime outputs live in
273
+ `state.nodes[id].output`; do not define `output` yourself. Input schemas are
274
+ checked before execution and output schemas after it returns.
275
+
276
+ ### Expression capability and boundary
277
+
278
+ Expressions are a small evaluator, not arbitrary TypeScript/JavaScript. They
279
+ can read `context` and prior outputs with dot notation, perform arithmetic,
280
+ comparison, and logical operations, and call registered built-ins for math,
281
+ strings, collections, and dates. Custom functions must be registered
282
+ explicitly. Expressions cannot import modules, perform I/O, access unrestricted
283
+ globals, or serve as a replacement for business code. Keep side effects in
284
+ `action` or integration nodes, and expressions deterministic and cheap.
285
+
286
+ Use `${nodeId.output.field}` and `${context.tenantId}`. Prefer identifier-safe
287
+ node IDs: access is dot-based and does not support arbitrary bracket lookup.
288
+ See the [expression reference](./docs/NODE_REFERENCE.md#表达式语法速查) for the
289
+ full operator/function list.
290
+
291
+ ### Multi-tenant usage pattern
292
+
293
+ The engine does not provide database-level tenant isolation, authorization, or
294
+ cross-process coordination. A tenant ID in `context` is only data. Enforce
295
+ tenant scope at the API/service boundary, derive storage and queue namespaces
296
+ from a trusted identity, and pass the tenant ID through every external call
297
+ and idempotency key:
298
+
299
+ ```ts
300
+ const tenantId = authenticatedTenantId; // never trust request.body.tenantId
301
+ const instanceId = await engine.start("order", {
302
+ tenantId,
303
+ orderId,
304
+ idempotencyKey: `${tenantId}:order:${orderId}`,
305
+ });
306
+ ```
307
+
308
+ One engine can serve many tenants if every query/signal is authorized and
309
+ filtered. For stronger blast-radius isolation, run one engine and storage
310
+ directory per tenant or tenant group. The
311
+ [`multi-tenant-workflow.ts`](./examples/multi-tenant-workflow.ts) example shows
312
+ the context convention, not authorization or storage isolation.
313
+
232
314
  ### Archive management
233
315
 
234
316
  Terminal-instance archiving is disabled by default so completed instances stay
@@ -296,7 +378,38 @@ across ordinary process restarts, but it is not a transactional database and
296
378
  the same `STORAGE_DIR` must never be shared by multiple engine processes.
297
379
  Process-local leases, rate limits, idempotency keys, the event bus, and Cron
298
380
  scheduling do not provide distributed coordination. The project also does not
299
- provide clustering, multi-tenancy isolation, GraphQL, or a visual editor.
381
+ provide clustering, database-level multi-tenancy isolation, GraphQL, or a
382
+ visual editor. Multi-tenant conventions are possible at the application
383
+ boundary; isolation is your responsibility.
384
+
385
+ ### When to use this engine—and when to switch
386
+
387
+ Choose this engine when the workflow is embedded in one Node.js service, local
388
+ file persistence is sufficient, the workload is modest, and you want a small
389
+ TypeScript API with HTTP/event integrations, human waits, retries, and explicit
390
+ compensation paths.
391
+
392
+ Choose [Temporal](https://temporal.io/) when you need a distributed workflow
393
+ platform: many workers and services, durable timers while workers are down,
394
+ stronger execution history/replay guarantees, operational visibility, and
395
+ horizontal scaling across hosts. Accept the operational footprint and
396
+ Temporal's workflow/activity programming model.
397
+
398
+ Choose a PostgreSQL-backed engine such as
399
+ [pg-workflows](https://github.com/boazsegev/pg-workflows) when PostgreSQL is
400
+ already the system of record and you want queueing, locking, and workflow state
401
+ to share one transactional database. It is a better fit when multiple
402
+ processes must coordinate through Postgres; it is less attractive when a
403
+ single embedded service and local files are the desired deployment boundary.
404
+
405
+ | Requirement | Best fit |
406
+ | -------------------------------------------------------------- | ------------------------- |
407
+ | Embedded, single-process, low operational overhead | `ts-workflow-engine-lite` |
408
+ | Distributed workers, long-lived timers, workflow-as-a-platform | Temporal |
409
+ | Postgres-native coordination and transactional state | pg-workflows |
410
+
411
+ The key boundary is not “how many node types are available”; it is the
412
+ durability and coordination model your workflow requires.
300
413
 
301
414
  ## Development scripts
302
415
 
@@ -332,20 +445,26 @@ are not installed.
332
445
 
333
446
  ## Examples
334
447
 
335
- | Example | Script | Description |
336
- | --------------------------------------------------------------- | ------------------------------- | ------------------------------------------------ |
337
- | [quickstart.ts](./examples/quickstart.ts) | `pnpm example:quickstart` | Minimal workflow with condition routing |
338
- | [order-processing.ts](./examples/order-processing.ts) | `pnpm example:order` | Multi-step order processing pipeline |
339
- | [embedded-express-app.ts](./examples/embedded-express-app.ts) | `pnpm example:embedded-express` | Embed API in a host Express app |
340
- | [error-handling.ts](./examples/error-handling.ts) | `pnpm example:error-handling` | Named error classes and retry handling |
341
- | [multi-tenant-workflow.ts](./examples/multi-tenant-workflow.ts) | `pnpm example:multi-tenant` | Tenant-isolated workflows with condition routing |
342
- | [approval-pipeline.ts](./examples/approval-pipeline.ts) | `pnpm example:approval` | Long-running approval with timeout |
343
- | [http-orchestration.ts](./examples/http-orchestration.ts) | `pnpm example:http` | HTTP integration with conditional routing |
344
- | [parallel-fan-out.ts](./examples/parallel-fan-out.ts) | `pnpm example:parallel` | Parallel processing with merge |
345
- | [graceful-degradation.ts](./examples/graceful-degradation.ts) | `pnpm example:degradation` | Fallback paths and dead letter queues |
346
- | [headless-engine.ts](./examples/headless-engine.ts) | `pnpm example:headless` | Pure programmatic usage, no API server |
347
- | [worker-pool.ts](./examples/worker-pool.ts) | `pnpm example:worker-pool` | CPU-intensive tasks with worker threads |
348
- | [cron-data-sync.ts](./examples/cron-data-sync.ts) | `pnpm example:cron-sync` | Scheduled data synchronization |
448
+ | Example | Script | Description |
449
+ | ----------------------------------------------------------------- | ------------------------------------- | ------------------------------------------------ |
450
+ | [quickstart.ts](./examples/quickstart.ts) | `pnpm example quickstart` | Minimal workflow with condition routing |
451
+ | [order-processing.ts](./examples/order-processing.ts) | `pnpm example order-processing` | Multi-step order processing pipeline |
452
+ | [embedded-express-app.ts](./examples/embedded-express-app.ts) | `pnpm example embedded-express-app` | Embed API in a host Express app |
453
+ | [error-handling.ts](./examples/error-handling.ts) | `pnpm example error-handling` | Named error classes and retry handling |
454
+ | [multi-tenant-workflow.ts](./examples/multi-tenant-workflow.ts) | `pnpm example multi-tenant-workflow` | Tenant-isolated workflows with condition routing |
455
+ | [approval-pipeline.ts](./examples/approval-pipeline.ts) | `pnpm example approval-pipeline` | Long-running approval with timeout |
456
+ | [http-orchestration.ts](./examples/http-orchestration.ts) | `pnpm example http-orchestration` | HTTP integration with conditional routing |
457
+ | [parallel-fan-out.ts](./examples/parallel-fan-out.ts) | `pnpm example parallel-fan-out` | Parallel processing with merge |
458
+ | [graceful-degradation.ts](./examples/graceful-degradation.ts) | `pnpm example graceful-degradation` | Fallback paths and dead letter queues |
459
+ | [headless-engine.ts](./examples/headless-engine.ts) | `pnpm example headless-engine` | Pure programmatic usage, no API server |
460
+ | [worker-pool.ts](./examples/worker-pool.ts) | `pnpm example worker-pool` | CPU-intensive tasks with worker threads |
461
+ | [cron-data-sync.ts](./examples/cron-data-sync.ts) | `pnpm example cron-data-sync` | Scheduled data synchronization |
462
+ | [demo-onboarding.ts](./examples/demo-onboarding.ts) | `pnpm example demo-onboarding` | Event-driven onboarding demo |
463
+ | [headless-no-express.ts](./examples/headless-no-express.ts) | `pnpm example headless-no-express` | Headless engine without Express installed |
464
+ | [data-processing.ts](./examples/data-processing.ts) | `pnpm example data-processing` | CSV import pipeline with cleaning and validation |
465
+ | [event-timeout-workflow.ts](./examples/event-timeout-workflow.ts) | `pnpm example event-timeout-workflow` | Event waiting, timeout and rollback paths |
466
+ | [instance-control-api.ts](./examples/instance-control-api.ts) | `pnpm example instance-control-api` | Retry / skip / compensate instance control API |
467
+ | [output-injection.ts](./examples/output-injection.ts) | `pnpm example output-injection` | Referencing upstream node output via ${...} |
349
468
 
350
469
  ## License
351
470
 
package/README.zh.md ADDED
@@ -0,0 +1,353 @@
1
+ # ts-workflow-engine-lite
2
+
3
+ 一个用于嵌入式、单进程应用的轻量级 TypeScript 工作流引擎。它包含工作流执行、REST API、事件、Cron 调度、重试和本地文件持久化。本地使用无需依赖 Redis 或数据库。
4
+
5
+ ## 安装
6
+
7
+ 将该库添加到现有的 TypeScript 项目中:
8
+
9
+ ```bash
10
+ pnpm add ts-workflow-engine-lite
11
+ ```
12
+
13
+ 需要 Node.js 18 或更高版本。
14
+
15
+ 该包作为 ES 模块发布。在 JavaScript 或 TypeScript 中使用 ESM 导入:
16
+
17
+ ```js
18
+ import { bootstrap, destroyContainer } from "ts-workflow-engine-lite";
19
+ ```
20
+
21
+ TypeScript 使用者可以使用 `moduleResolution: "NodeNext"` 或 `"Bundler"`。此仓库在其 TypeScript 源码中保留了无扩展名的相对导入;构建步骤仅会将所需的 `.js` 扩展名添加到生成的 `dist` 文件中。
22
+
23
+ 如果直接在此仓库中进行开发,请安装开发依赖并运行内置的快速入门示例:
24
+
25
+ ```bash
26
+ pnpm install
27
+ pnpm example quickstart
28
+ ```
29
+
30
+ ## 5分钟 TypeScript 快速入门
31
+
32
+ 该包的入口点是无副作用的。导入它不会启动服务器、注册进程处理程序或运行演示。
33
+
34
+ ```ts
35
+ import {
36
+ bootstrap,
37
+ destroyContainer,
38
+ type WorkflowDefinition,
39
+ } from "ts-workflow-engine-lite";
40
+
41
+ const workflow: WorkflowDefinition = {
42
+ id: "hello",
43
+ name: "Hello workflow",
44
+ startNode: "greet",
45
+ nodes: {
46
+ greet: {
47
+ id: "greet",
48
+ type: "action",
49
+ action: async (instance) => ({
50
+ message: `Hello, ${instance?.context?.name ?? "world"}!`,
51
+ }),
52
+ next: [],
53
+ },
54
+ },
55
+ };
56
+
57
+ const { engine, container } = await bootstrap({
58
+ skipGracefulShutdown: true,
59
+ logLevel: "WARN",
60
+ });
61
+
62
+ try {
63
+ await engine.register(workflow);
64
+ const instanceId = await engine.start("hello", { name: "Ada" });
65
+ const instance = await engine.waitForCompletion(instanceId);
66
+
67
+ console.log(instance.status); // completed (已完成)
68
+ console.log(instance.state?.nodes?.greet?.output);
69
+ } finally {
70
+ engine.destroy();
71
+ await destroyContainer(container);
72
+ }
73
+ ```
74
+
75
+ `waitForCompletion()` 会在实例状态变为 `completed`(已完成)、`failed`(失败)或 `cancelled`(已取消)时返回。它接受 `timeoutMs`(超时时间)、`pollIntervalMs`(轮询间隔)和一个 `AbortSignal`(中止信号)。
76
+
77
+ ### 我应该使用哪个初始化 API?
78
+
79
+ 该包提供了两种获取运行中 `WorkflowEngineV2` 的方式:
80
+
81
+ | API | 适用场景 |
82
+ | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
83
+ | `bootstrap(options)` | **默认选择。** 一次调用即可配置存储、密钥管理器、可选的线程池和归档功能以及优雅关闭,然后返回 `{ engine, container }`。与 CLI 和上述快速入门中的用法一致。 |
84
+ | `createContainer(options)` + `setContainer(container)` + `createEngine(options)` | 当你需要更精细地控制初始化顺序(例如,在创建容器和引擎之间注册工作流或自定义 `SecretManager`),或者你需要针对自己管理的容器组合多个引擎时使用。`createEngine()` 会从 `setContainer()` 设置的全进程单例中读取其容器——请先调用 `createContainer` 和 `setContainer`,否则会抛出 `"Container not initialized"` 错误。 |
85
+
86
+ `bootstrap()` 内部也会调用 `setContainer(container)`,因此它返回的 `container` 与 `createEngine()` 读取的全进程单例是同一个——你仍然可以在之后再次调用 `createEngine()`(例如,创建共享该容器的第二个引擎),而无需自己调用 `setContainer`。
87
+
88
+ ## 运行示例
89
+
90
+ ```bash
91
+ pnpm example quickstart # 最小化的嵌入式工作流
92
+ pnpm example order-processing # 条件分支和多步骤订单工作流
93
+ pnpm example embedded-express-app # 将 createWorkflowRouter 挂载到宿主的 Express 应用中
94
+ pnpm example error-handling # 捕获 WorkflowNotFoundError / InstanceNotFoundError
95
+ pnpm dev # 事件驱动的内置演示
96
+ ```
97
+
98
+ ## 运行 REST API
99
+
100
+ ```bash
101
+ pnpm dev:api
102
+ ```
103
+
104
+ API 将在 `http://localhost:3345` 启动,并使用本地文件存储:
105
+
106
+ ```bash
107
+ curl http://localhost:3345/workflow-api/v1/workflows
108
+ ```
109
+
110
+ 交互式 OpenAPI 文档位于:
111
+
112
+ ```text
113
+ http://localhost:3345/api-docs
114
+ ```
115
+
116
+ CLI 入口点会在需要时从 `.env.example` 创建 `.env` 文件。嵌入式的 `bootstrap()` API 会读取当前环境变量,但不会自动创建或加载 `.env` 文件。
117
+
118
+ ### 挂载到现有的 Express 应用中
119
+
120
+ 上述的 `startApiServer()` 会创建并启动其独立的 `express()` 应用——当此包需要作为独立的 HTTP 服务运行时使用它。如果要将工作流 API 作为路由挂载到宿主应用现有的 Express 应用中(共享其端口、body 解析器和认证中间件),请使用 `createWorkflowRouter()`:
121
+
122
+ ```ts
123
+ import express from "express";
124
+ import { bootstrap, createWorkflowRouter } from "ts-workflow-engine-lite";
125
+
126
+ const { engine, container } = await bootstrap({ skipGracefulShutdown: true });
127
+
128
+ const app = express();
129
+ app.use(express.json());
130
+ // 如果需要,在此处应用你自己的认证/限流中间件。
131
+ app.use(
132
+ "/workflow-api/v1",
133
+ await createWorkflowRouter(engine, container.storage),
134
+ );
135
+
136
+ app.listen(3000);
137
+ ```
138
+
139
+ 该路由暴露了 `/workflows`、`/instances`、`/events`、`/webhooks`、`/dlq`、`/templates`、`/analytics`、`/functions`、`/health`,以及(除非传入 `{ enableMetrics: false }`)`/metrics`。它假定 `express.json()` 已经执行,并且**不包含** `startApiServer` 的独立服务相关配置——如 helmet/CORS、限流、JWT 认证、欢迎/文档页面或 WebSocket 控制台流。请在挂载此路由之前,在宿主应用实例上自行添加所需的功能。
140
+
141
+ ## 配置
142
+
143
+ 将 `WORKFLOW_ENGINE_LOG_LEVEL` 设置为 `DEBUG`、`INFO`、`WARN` 或 `ERROR`。嵌入式应用可以通过 `bootstrap({ logLevel: "WARN" })` 覆盖它,或者稍后使用 `Logger.setLevel()` 进行更改。
144
+
145
+ ### 存储模式
146
+
147
+ | 模式 | 预期用途 | 重启恢复 |
148
+ | -------- | ------------------------ | -------- |
149
+ | `file` | 默认的单进程运行时 | 支持 |
150
+ | `memory` | 测试和有意为之的临时用途 | 不支持 |
151
+
152
+ 在测试环境之外,默认启用本地文件持久化。运行时状态存储在 `.ts-workflow-engine-data/` 目录下,每条记录使用一个 JSON 文件,并通过原子化的临时文件替换来写入:
153
+
154
+ ```bash
155
+ STORAGE_TYPE=file
156
+ STORAGE_DIR=.ts-workflow-engine-data
157
+ ```
158
+
159
+ 存储目录按记录类型组织:
160
+
161
+ ```text
162
+ .ts-workflow-engine-data/
163
+ ├── instances/
164
+ ├── workflows/
165
+ ├── workflow-metadata/
166
+ ├── workflow-versions/
167
+ ├── waiting/
168
+ ├── metrics/
169
+ ├── events/
170
+ ├── heartbeats/
171
+ └── dlq/
172
+ ```
173
+
174
+ 每个集合还有一个 `corrupt/`(损坏)目录。如果在启动期间无法解析某条记录,它会被移动到该目录,以防止单个损坏的文件阻止引擎启动。请手动检查并恢复被隔离的文件;它们不会被自动加载。
175
+
176
+ ### 重启恢复
177
+
178
+ 基于 JSON/配置的工作流定义会自动恢复。包含 JavaScript 函数或闭包的工作流定义无法被序列化,因此应用代码必须在恢复未完成的实例之前重新注册这些定义:
179
+
180
+ ```ts
181
+ const { engine } = await bootstrap({
182
+ storageType: "file",
183
+ resumeRunningInstances: false,
184
+ });
185
+
186
+ await engine.register(workflowWithFunctions);
187
+ await engine.resumeRunningInstancesFromStorage();
188
+ ```
189
+
190
+ `RESUME_ON_STARTUP=true` 适用于 API 模式。在嵌入式使用时,如果必须先注册基于闭包的工作流,请将其保持禁用状态,或设置 `resumeRunningInstances: false`。否则,嵌入式应用可以通过设置 `resumeRunningInstances: true` 来选择启用恢复功能。
191
+
192
+ #### 重启/恢复检查清单
193
+
194
+ 将恢复过程视为应用程序的启动协议:
195
+
196
+ 1. 使用 `storageType: "file"` 并配置一个持久的、特定于实例的 `storageDirectory`;绝不要在多个引擎进程之间共享它。
197
+ 2. 在调用 `resumeRunningInstancesFromStorage()` 之前,注册所有包含函数类型 `action` 或 `rollback` 处理程序的工作流。
198
+ 3. 在备份或替换存储之前,请干净地停止引擎。文件持久化是重启安全的,但它不是数据库事务。
199
+ 4. 使外部效应具备幂等性。崩溃可能发生在效应成功执行之后但其结果被持久化之前,因此请在下游的幂等键中包含实例和节点尝试的身份标识。
200
+ 5. 使用重试处理瞬时错误,使用 `failureNext` 进行主动恢复,使用 `rollback` 进行效应补偿,并使用 DLQ(死信队列)处理耗尽重试或需要手动干预的情况。Rollback 是 Saga 风格的补偿,而不是原子撤销。
201
+ 6. 启动后检查每个 `*/corrupt/` 目录;被隔离的记录不会自动恢复。
202
+
203
+ 持久化的 `wait`(等待)节点会保留其原始截止时间:重启后,它们只会等待剩余的时间,如果时间已过则立即触发。这适用于“N天后最终执行”的场景,而不适用于必须在进程宕机期间触发的严格截止时间。对于后一种情况,请使用外部调度器来发布 `event`(事件)。
204
+
205
+ ### 节点手册:语义和故障行为
206
+
207
+ 完整的字段级参考、示例和常见错误请参阅 [`docs/NODE_REFERENCE.md`](./docs/NODE_REFERENCE.md)。核心执行模型如下:
208
+
209
+ | 节点族 | 成功 | 失败 / 恢复 |
210
+ | ------------------------------------------------ | ------------------------------------ | ----------------------------------------------------- |
211
+ | `action`, `http`, `sql`, `queue`, `notification` | 持久化输出,然后跟随 `next` | 重试;然后执行 `failureNext`,否则实例失败 / 进入 DLQ |
212
+ | `condition`, `router` | 计算并跟随选定的分支 | 无效的表达式或未匹配的路由会导致节点失败 |
213
+ | `transform` | 从表达式构建输出对象 | 表达式错误会导致节点失败;先前的输出保持不变 |
214
+ | `wait`, `event`, `approval` | 持久化等待状态并通过 `next` 恢复 | 超时/取消即为失败,除非显式处理 |
215
+ | `loop`, `subworkflow`, `join` | 完成聚合/子任务并继续 | 循环体/子任务失败或缺失 join 分支会向上传播失败 |
216
+ | `rollback` | 运行补偿并跟随 `rollbackTo` / `next` | 补偿可能会失败;它不是事务边界 |
217
+
218
+ `next` 是后继 ID 的集合,可以扇出(fan out)。`failureNext` 是失败路径,不是隐式的回滚。运行时输出存放在 `state.nodes[id].output` 中;请勿自行定义 `output`。输入 Schema 在执行前进行检查,输出 Schema 在返回后进行检查。
219
+
220
+ ### 表达式能力和边界
221
+
222
+ 表达式是一个小型的计算器,而不是任意的 TypeScript/JavaScript。它们可以使用点表示法读取 `context` 和先前的输出,执行算术、比较和逻辑运算,并调用注册的内置函数来处理数学、字符串、集合和日期。自定义函数必须显式注册。表达式不能导入模块、执行 I/O、访问不受限制的全局变量,也不能作为业务代码的替代品。请将副作用保留在 `action` 或集成节点中,并保持表达式的确定性和轻量级。
223
+
224
+ 使用 `${nodeId.output.field}` 和 `${context.tenantId}`。首选标识符安全的节点 ID:访问是基于点的,不支持任意的括号查找。完整的操作符/函数列表请参阅[表达式参考](./docs/NODE_REFERENCE.md#表达式语法速查)。
225
+
226
+ ### 多租户使用模式
227
+
228
+ 该引擎不提供数据库级别的租户隔离、授权或跨进程协调。`context` 中的租户 ID 仅仅是数据。请在 API/服务边界强制执行租户范围,从受信任的身份派生存储和队列命名空间,并在每次外部调用和幂等键中传递租户 ID:
229
+
230
+ ```ts
231
+ const tenantId = authenticatedTenantId; // 绝不信任 request.body.tenantId
232
+ const instanceId = await engine.start("order", {
233
+ tenantId,
234
+ orderId,
235
+ idempotencyKey: `${tenantId}:order:${orderId}`,
236
+ });
237
+ ```
238
+
239
+ 如果每个查询/信号都经过授权和过滤,一个引擎可以服务于多个租户。为了实现更强的故障隔离,请为每个租户或租户组运行一个引擎和存储目录。[`multi-tenant-workflow.ts`](./examples/multi-tenant-workflow.ts) 示例展示了 context 的约定,而非授权或存储隔离。
240
+
241
+ ### 归档管理
242
+
243
+ 默认情况下禁用终态实例归档,以便已完成的实例在正常的 TTL 清理之前仍可供查询。若要将终态实例归档为按日期分区的本地 JSON 并将其从热存储中驱逐,请开启此功能:
244
+
245
+ ```bash
246
+ ARCHIVE_ENABLED=true
247
+ ARCHIVE_DIR=./archive
248
+ ARCHIVE_RETENTION_DAYS=90
249
+ ARCHIVE_CLEANUP_INTERVAL_MS=21600000
250
+ ```
251
+
252
+ 归档文件使用 `archive/YYYY-MM-DD/<instanceId>.json` 格式。只有在归档文件成功写入后,实例才会从热存储中移除。过期的日期分区将在启动时和配置的清理间隔按照 `ARCHIVE_RETENTION_DAYS` 进行删除。归档的实例是冷文件:正常的实例查询不会自动返回它们,也不会将它们恢复到热存储中。
253
+
254
+ 等效的嵌入式选项为 `storageType`、`storageDirectory`、`enableArchiving`、`archiveDirectory` 和 `archiveRetentionDays`。
255
+
256
+ ### 备份和恢复
257
+
258
+ 请分别备份处于活动状态的 `STORAGE_DIR` 和启用归档时的 `ARCHIVE_DIR`。为了获得跨集合的一致性备份,请在复制目录前干净地停止引擎。通过在启动前将目录复制回配置的路径来进行恢复。请勿在引擎运行时编辑活动的 JSON 文件。
259
+
260
+ ## 错误处理
261
+
262
+ 针对最高频的查找/并发失败模式,引擎会抛出命名的错误类(从包根目录导出),因此调用者可以使用 `instanceof` 进行检查,而不是匹配 `Error.message` 字符串:
263
+
264
+ | 类 | 抛出时机 |
265
+ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
266
+ | `WorkflowNotFoundError` | `engine.start()` / `engine.dryRun()` / 内部执行引用了未注册的 `workflowId`(可选特定 `version`)。包含 `.workflowId` 和 `.version`。 |
267
+ | `InstanceNotFoundError` | `engine.signal()` / `engine.query()` / `engine.update()` / `waitForCompletion()` 引用了无法解析为已存储实例的 `instanceId`。包含 `.instanceId`。 |
268
+ | `ConcurrencyConflictError` | 乐观并发控制 (CAS) 在持久化实例更新时耗尽了其重试预算——另一个写入者一直在 `instance.version` 的竞争中获胜。包含 `.instanceId` 和 `.attempts`。 |
269
+ | `LockAcquisitionError` | `ConcurrencyControl.withLock()` 无法在其重试预算内获取锁。包含 `.resourceId`。 |
270
+
271
+ 其他失败(超时、无效的状态转换、最大实例数限制、通过 `DataValidationError` 进行的 Schema 验证)仍然会抛出普通的 `Error` 或其现有的错误类——请参阅 [`examples/error-handling.ts`](./examples/error-handling.ts) 以获取可运行的演示。
272
+
273
+ ## 支持的工作流特性
274
+
275
+ - 节点类型:`action`、`wait`、`event`、`rollback`、`subworkflow`、`condition`、`router`、`loop`、`http`、`sql`、`queue`、`notification` 和 `approval`
276
+ - 事件总线、钩子、去重、Cron 调度、重试、心跳和死信队列处理
277
+ - 用于工作流、实例、信号、查询、更新、模板、Webhooks、事件、分析和死信队列的 REST API
278
+ - 包含内置和自定义函数的表达式计算
279
+
280
+ ## 范围和局限性
281
+
282
+ 此项目有意设计为单进程。本地文件存储可以在普通的进程重启期间保护状态,但它不是事务型数据库,并且同一个 `STORAGE_DIR` 绝不能被多个引擎进程共享。进程本地的租约、限流、幂等键、事件总线和 Cron 调度不提供分布式协调。该项目也不提供集群、数据库级别的多租户隔离、GraphQL 或可视化编辑器。多租户约定可以在应用边界实现;隔离是你的责任。
283
+
284
+ ### 何时使用此引擎——以及何时切换
285
+
286
+ 当工作流嵌入在单个 Node.js 服务中,本地文件持久化已足够,工作量适中,并且你需要一个带有 HTTP/事件集成、人工等待、重试和显式补偿路径的小型 TypeScript API 时,请选择此引擎。
287
+
288
+ 当你需要分布式工作流平台时,请选择 [Temporal](https://temporal.io/):众多 Worker 和服务、Worker 宕机时的持久计时器、更强的执行历史/重放保证、运营可见性以及跨主机的水平扩展。请接受其运营负担和 Temporal 的工作流/活动编程模型。
289
+
290
+ 当 PostgreSQL 已经是事实上的记录系统,并且你希望队列、锁定和工作流状态共享同一个事务型数据库时,请选择支持 PostgreSQL 的引擎,例如 [pg-workflows](https://github.com/boazsegev/pg-workflows)。当多个进程必须通过 Postgres 进行协调时,它是更好的选择;当期望的部署边界是单个嵌入式服务和本地文件时,它的吸引力较小。
291
+
292
+ | 需求 | 最佳选择 |
293
+ | --------------------------------------------- | ------------------------- |
294
+ | 嵌入式、单进程、低运营开销 | `ts-workflow-engine-lite` |
295
+ | 分布式 Worker、长生命周期计时器、平台化工作流 | Temporal |
296
+ | Postgres 原生协调和事务状态 | pg-workflows |
297
+
298
+ 关键的界限不在于“有多少种节点类型可用”,而在于你的工作流所需的持久性和协调模型。
299
+
300
+ ## 开发脚本
301
+
302
+ ```bash
303
+ pnpm build # 清理并编译 TypeScript 到 dist/
304
+ pnpm test # 运行 Vitest
305
+ pnpm typecheck # 对源码、测试和示例进行类型检查
306
+ pnpm lint # 运行 oxlint 检查
307
+ pnpm format:check # 验证代码格式
308
+ ```
309
+
310
+ ## 可选的 API 层
311
+
312
+ REST API 是可选的。核心引擎的导入绝不会引入 `express`、`cors`、`helmet` 或任何 HTTP 框架。如果你只需要工作流引擎,请完全跳过这些依赖:
313
+
314
+ ```bash
315
+ # 仅核心(无 REST API)
316
+ pnpm add ts-workflow-engine-lite
317
+ ```
318
+
319
+ 如果你需要 REST API,请安装对等依赖(peer dependencies):
320
+
321
+ ```bash
322
+ # 包含 REST API 的完整安装
323
+ pnpm add ts-workflow-engine-lite express cors helmet morgan ws swagger-ui-dist
324
+ ```
325
+
326
+ 与 API 相关的导出(`startApiServer`、`createWorkflowRouter`)在内部使用动态 `import()`,如果未安装 HTTP 相关的包,将会抛出明确的错误。
327
+
328
+ ## 示例
329
+
330
+ | 示例 | 脚本 | 描述 |
331
+ | ----------------------------------------------------------------- | ------------------------------------- | ----------------------------------- |
332
+ | [quickstart.ts](./examples/quickstart.ts) | `pnpm example quickstart` | 带有条件路由的最小化工作流 |
333
+ | [order-processing.ts](./examples/order-processing.ts) | `pnpm example order-processing` | 多步骤订单处理流水线 |
334
+ | [embedded-express-app.ts](./examples/embedded-express-app.ts) | `pnpm example embedded-express-app` | 将 API 嵌入到宿主 Express 应用中 |
335
+ | [error-handling.ts](./examples/error-handling.ts) | `pnpm example error-handling` | 命名的错误类和重试处理 |
336
+ | [multi-tenant-workflow.ts](./examples/multi-tenant-workflow.ts) | `pnpm example multi-tenant-workflow` | 带有条件路由的租户隔离工作流 |
337
+ | [approval-pipeline.ts](./examples/approval-pipeline.ts) | `pnpm example approval-pipeline` | 带有超时的长时间运行审批 |
338
+ | [http-orchestration.ts](./examples/http-orchestration.ts) | `pnpm example http-orchestration` | 带有条件路由的 HTTP 集成 |
339
+ | [parallel-fan-out.ts](./examples/parallel-fan-out.ts) | `pnpm example parallel-fan-out` | 带有合并的并行处理 |
340
+ | [graceful-degradation.ts](./examples/graceful-degradation.ts) | `pnpm example graceful-degradation` | 降级路径和死信队列 |
341
+ | [headless-engine.ts](./examples/headless-engine.ts) | `pnpm example headless-engine` | 纯编程方式使用,无 API 服务器 |
342
+ | [worker-pool.ts](./examples/worker-pool.ts) | `pnpm example worker-pool` | 使用 Worker 线程处理 CPU 密集型任务 |
343
+ | [cron-data-sync.ts](./examples/cron-data-sync.ts) | `pnpm example cron-data-sync` | 计划内的数据同步 |
344
+ | [demo-onboarding.ts](./examples/demo-onboarding.ts) | `pnpm example demo-onboarding` | 事件驱动的入职演示 |
345
+ | [headless-no-express.ts](./examples/headless-no-express.ts) | `pnpm example headless-no-express` | 未安装 Express 时的无头引擎 |
346
+ | [data-processing.ts](./examples/data-processing.ts) | `pnpm example data-processing` | CSV 导入流水线,含清洗与校验 |
347
+ | [event-timeout-workflow.ts](./examples/event-timeout-workflow.ts) | `pnpm example event-timeout-workflow` | 事件等待、超时与回滚路径 |
348
+ | [instance-control-api.ts](./examples/instance-control-api.ts) | `pnpm example instance-control-api` | 重试 / 跳过 / 补偿的实例控制 API |
349
+ | [output-injection.ts](./examples/output-injection.ts) | `pnpm example output-injection` | 通过 ${...} 引用上游节点输出 |
350
+
351
+ ## 许可证
352
+
353
+ MIT
@@ -302,8 +302,13 @@ ${fnRows}
302
302
 
303
303
  <section id="integration">
304
304
  <h2>持久化与外部集成增强</h2>
305
- <p><code>FSYNC_ON_WRITE=true</code> 时,<code>LocalFileStorage</code> 每次写入会 fsync 临时文件和所在目录,
305
+ <p><code>FSYNC_ON_WRITE=true</code> 时,<code>LocalFileStorage</code> 写入会 fsync 临时文件和所在目录,
306
306
  换取主机级崩溃/断电下的持久性(代价是写入延迟明显增加);默认 <code>false</code>,仅保证原子 rename 后文件本身完整。</p>
307
+ <p>fsync 按集合生效:<code>instances</code>、<code>events</code>、<code>waiting</code>、<code>workflows*</code>、
308
+ <code>dlq</code>、<code>webhooks*</code> 等系统记录与审计历史会 fsync;<code>metrics</code> 与 <code>heartbeats</code>
309
+ <strong>不会</strong>——它们是纯派生的可观测性数据,却占了每节点 3 次写入中的 2 次,fsync 它们会让每个节点执行
310
+ 都为没人需要的持久性买单。代价是主机级崩溃可能丢失最近的 metrics 与 heartbeat,实例状态与事件历史不受影响。
311
+ 需要时可通过 <code>LocalFileStorageOptions.fsyncCollections</code> 显式覆盖。</p>
307
312
  <p><code>createWorkflowRouter()</code> / <code>createWorkflowRouterBundle()</code> 允许宿主 Express 应用将工作流 API
308
313
  挂载为普通 <code>express.Router</code>,无需通过 <code>startApiServer()</code> 独立运行。</p>
309
314
  <p>高频查找/并发错误改用具名错误类导出(<code>WorkflowNotFoundError</code>、<code>InstanceNotFoundError</code>、
@@ -1 +1 @@
1
- {"version":3,"file":"docsPage.js","sourceRoot":"","sources":["../../../src/api/docsPage.ts"],"names":[],"mappings":"AAWA,MAAM,SAAS,GAAmB;IAChC,EAAE,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE;IACtD,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE;IACxC,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE;IACxC,EAAE,EAAE,EAAE,qBAAqB,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE;IAC/D,EAAE,EAAE,EAAE,iBAAiB,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;IACtD,EAAE,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE;IACzD,EAAE,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE;IACtD,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE;IAC7C,EAAE,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE;CACzD,CAAC;AAOF,MAAM,SAAS,GAAc;IAC3B,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,8CAA8C,EAAE;IACzE;QACE,QAAQ,EAAE,SAAS;QACnB,KAAK,EACH,2FAA2F;KAC9F;IACD;QACE,QAAQ,EAAE,UAAU;QACpB,KAAK,EACH,uGAAuG;KAC1G;IACD;QACE,QAAQ,EAAE,QAAQ;QAClB,KAAK,EAAE,uCAAuC;KAC/C;IACD;QACE,QAAQ,EAAE,SAAS;QACnB,KAAK,EACH,2EAA2E;KAC9E;IACD;QACE,QAAQ,EAAE,QAAQ;QAClB,KAAK,EAAE,6DAA6D;KACrE;IACD;QACE,QAAQ,EAAE,SAAS;QACnB,KAAK,EACH,gGAAgG;KACnG;IACD;QACE,QAAQ,EAAE,MAAM;QAChB,KAAK,EACH,6EAA6E;KAChF;CACF,CAAC;AAEF,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAC1B,CAAC,CAAC,EAAE,EAAE,CACJ,iBAAiB,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,UAAU,YAAY,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAC7G,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAC1B,CAAC,CAAC,EAAE,EAAE,CACJ,WAAW,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,kBAAkB,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAC5F,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsJP,MAAM;;;;;;;;;EASN,MAAM;;;;;mCAK2B,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAuG/B,CAAC;AACT,CAAC"}
1
+ {"version":3,"file":"docsPage.js","sourceRoot":"","sources":["../../../src/api/docsPage.ts"],"names":[],"mappings":"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"}
package/dist/src/cli.js CHANGED
@@ -16,10 +16,6 @@ export async function main() {
16
16
  });
17
17
  Logger.info("system", "api", "API server is running");
18
18
  }
19
- else if (process.env.RUN_DEMO !== "false") {
20
- const { runDemo } = await import("./demo/index.js");
21
- await runDemo(engine, container.eventBus);
22
- }
23
19
  Logger.info("system", "init", "Workflow engine started successfully");
24
20
  }
25
21
  if (process.argv[1] &&
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAMA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,OAAO,EAAE,CAAC;IACV,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,6BAA6B,CAAC,CAAC;IAE7D,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,SAAS,EAAE,CAAC;IAEhD,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,MAAM,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;QAI/D,mBAAmB,EAAE,EAAE,qBAAqB,CAAC,KAAK,IAAI,EAAE;YACtD,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,uBAAuB,CAAC,CAAC;YAC3D,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,uBAAuB,CAAC,CAAC;IACxD,CAAC;SAAM,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC5C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;QACjD,MAAM,OAAO,CAAC,MAAwB,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,sCAAsC,CAAC,CAAC;AACxE,CAAC;AAED,IACE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACf,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EACvD,CAAC;IACD,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QAC9B,MAAM,CAAC,KAAK,CACV,QAAQ,EACR,OAAO,EACP,4BAA4B,EAC5B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACrD,CAAC;QACF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAOA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,OAAO,EAAE,CAAC;IACV,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,6BAA6B,CAAC,CAAC;IAE7D,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,SAAS,EAAE,CAAC;IAEhD,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,MAAM,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;QAI/D,mBAAmB,EAAE,EAAE,qBAAqB,CAAC,KAAK,IAAI,EAAE;YACtD,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,uBAAuB,CAAC,CAAC;YAC3D,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,uBAAuB,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,sCAAsC,CAAC,CAAC;AACxE,CAAC;AAED,IACE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACf,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EACvD,CAAC;IACD,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QAC9B,MAAM,CAAC,KAAK,CACV,QAAQ,EACR,OAAO,EACP,4BAA4B,EAC5B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACrD,CAAC;QACF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -11,6 +11,7 @@ export declare class InstanceManager {
11
11
  getInstance(instanceId: string): WorkflowInstance | undefined;
12
12
  private syncSearchIndex;
13
13
  removeInstance(instanceId: string): void;
14
+ replaceInstance(instance: WorkflowInstance): void;
14
15
  updateInstance(instance: WorkflowInstance, maxRetries?: number): Promise<void>;
15
16
  private casBackoff;
16
17
  listInstances(): string[];
@@ -74,6 +74,10 @@ export class InstanceManager {
74
74
  this.instances.delete(instanceId);
75
75
  searchAttributeManager.removeIndex(instanceId);
76
76
  }
77
+ replaceInstance(instance) {
78
+ this.instances.set(instance.instanceId, instance);
79
+ this.syncSearchIndex(instance);
80
+ }
77
81
  async updateInstance(instance, maxRetries = 3) {
78
82
  instance.updatedAt = new Date();
79
83
  this.instances.set(instance.instanceId, instance);