@iworkflow/core 0.3.0 → 0.5.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.
package/README.md CHANGED
@@ -33,4 +33,9 @@ try {
33
33
 
34
34
  Ещё: `ctx.startChild` (без ожидания), `ChildOptions.timeoutMs` и `onExisting`, `ctx.startedAt`,
35
35
  `ctx.parentId`, `AppDefinition.pools`, `handle.describe()` и `handle.terminate()`.
36
+
37
+ Приоритет: `client.start(..., { priority })` (1 — первым, 5 — последним) → `ctx.priority`; шаги и
38
+ дети его наследуют, `StepOptions.priority` — свой. Срок повтора из отказа: `new Failure(msg, { type,
39
+ retryAfterMs })`. Номер попытки в шаге — `stepAttempt()`. Время в процессе — `await ctx.now()`.
40
+ `client.signalWithStart(target, args, opts, topic, value)` — сигнал идущему или запуск с сигналом.
36
41
  Контракт обоих движков — `lab/test/contract.test.ts` репозитория.
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Отказ, который бросает код шага или процесса. `type` — код для разбора (`FlowError.type`),
3
+ * `nonRetryable` — шаг не повторять. Отказ процесса не повторяется никогда.
4
+ */
5
+ export declare class Failure extends Error {
6
+ readonly type: string;
7
+ readonly details?: unknown;
8
+ readonly nonRetryable: boolean;
9
+ /** Следующая попытка шага не раньше чем через столько мс (FLOOD_WAIT): вместо паузы из StepOptions. */
10
+ readonly retryAfterMs?: number;
11
+ constructor(message: string, opts: {
12
+ type: string;
13
+ details?: unknown;
14
+ nonRetryable?: boolean;
15
+ retryAfterMs?: number;
16
+ });
17
+ }
18
+ /** Отказ без повтора: `throw nonRetryable("нет такой фазы", "UnknownPhase")`. */
19
+ export declare const nonRetryable: (message: string, type: string, details?: unknown) => Failure;
20
+ /**
21
+ * - failed — шаг или процесс упал; `type` — тип отказа (`Failure.type` или имя класса ошибки).
22
+ * - timeout — вышел срок: попытки шага (`timeoutMs`), шага целиком (`totalTimeoutMs`) или процесса.
23
+ * - terminated — процесс прерван снаружи (`EngineClient.terminate`).
24
+ * - already-started — процесс с этим id уже есть (`startChild`, `onExisting: "reject"`).
25
+ * - not-found — процесса с этим id нет или он завершён (`signal`, `describe`).
26
+ */
27
+ export type FlowErrorReason = "failed" | "timeout" | "terminated" | "already-started" | "not-found";
28
+ /** Что бросает порт: `ctx.step`, `ctx.child`, `ctx.signal`, `handle.result()`, `client.describe`. */
29
+ export declare class FlowError extends Error {
30
+ readonly reason: FlowErrorReason;
31
+ readonly type?: string;
32
+ readonly details?: unknown;
33
+ /** Шаг упал на последней разрешённой попытке, а не остановлен `nonRetryable`. */
34
+ readonly exhausted: boolean;
35
+ constructor(message: string, opts: {
36
+ reason: FlowErrorReason;
37
+ type?: string;
38
+ details?: unknown;
39
+ exhausted?: boolean;
40
+ });
41
+ }
42
+ /** FlowError по признакам, а не по классу: класс теряется при сериализации в DBOS. */
43
+ export declare const isFlowError: (e: unknown) => e is FlowError;
44
+ /** Приоритет порта — целое 1…5 (1 — первым); иное отвергается одинаково на обоих движках. */
45
+ export declare function checkPriority(p: number | undefined): number | undefined;
46
+ export declare const DEFAULT_PRIORITY = 3;
package/dist/exit.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export interface ExitSignal {
2
+ /** Перед start: прошлый цикл завершён — новое обещание, иначе то же. */
3
+ arm(): void;
4
+ resolve(): void;
5
+ reject(e: unknown): void;
6
+ promise(): Promise<void>;
7
+ }
8
+ export declare function exitSignal(): ExitSignal;
@@ -0,0 +1,13 @@
1
+ export interface StepRuntime {
2
+ heartbeat(details?: unknown): Promise<void>;
3
+ lastHeartbeat(): Promise<unknown>;
4
+ /** Номер попытки шага, с 1. */
5
+ attempt(): number;
6
+ }
7
+ export declare function bindStepRuntime(rt: StepRuntime): void;
8
+ /** Отметить, что шаг жив, и сохранить прогресс. */
9
+ export declare const heartbeat: (details?: unknown) => Promise<void>;
10
+ /** Прогресс прошлой попытки этого шага; undefined — первая попытка или прогресса не было. */
11
+ export declare const lastHeartbeat: <T>() => Promise<T | undefined>;
12
+ /** Номер попытки этого шага, с 1. DBOS считает заново после рестарта процесса, Temporal — нет. */
13
+ export declare const stepAttempt: () => number;
@@ -0,0 +1,4 @@
1
+ export * from "./types";
2
+ export { bindStepRuntime, heartbeat, lastHeartbeat, stepAttempt, type StepRuntime } from "./heartbeat";
3
+ export * from "./errors";
4
+ export { exitSignal, type ExitSignal } from "./exit";
package/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ // src/heartbeat.ts
2
+ var KEY = Symbol.for("@iworkflow/core.step-runtime");
3
+ function bindStepRuntime(rt) {
4
+ globalThis[KEY] = rt;
5
+ }
6
+ function current() {
7
+ const rt = globalThis[KEY];
8
+ if (!rt)
9
+ throw new Error("heartbeat вне шага: движок процесса не запущен в этом процессе");
10
+ return rt;
11
+ }
12
+ var heartbeat = (details) => current().heartbeat(details);
13
+ var lastHeartbeat = async () => await current().lastHeartbeat() ?? undefined;
14
+ var stepAttempt = () => current().attempt();
15
+ // src/errors.ts
16
+ class Failure extends Error {
17
+ type;
18
+ details;
19
+ nonRetryable;
20
+ retryAfterMs;
21
+ constructor(message, opts) {
22
+ super(message);
23
+ this.name = "Failure";
24
+ this.type = opts.type;
25
+ this.details = opts.details;
26
+ this.nonRetryable = opts.nonRetryable ?? false;
27
+ const after = opts.retryAfterMs;
28
+ this.retryAfterMs = after !== undefined && Number.isFinite(after) && after >= 0 ? after : undefined;
29
+ }
30
+ }
31
+ var nonRetryable = (message, type, details) => new Failure(message, { type, details, nonRetryable: true });
32
+
33
+ class FlowError extends Error {
34
+ reason;
35
+ type;
36
+ details;
37
+ exhausted;
38
+ constructor(message, opts) {
39
+ super(message);
40
+ this.name = "FlowError";
41
+ this.reason = opts.reason;
42
+ this.type = opts.type;
43
+ this.details = opts.details;
44
+ this.exhausted = opts.exhausted ?? false;
45
+ }
46
+ }
47
+ var isFlowError = (e) => e instanceof FlowError || e instanceof Error && e.name === "FlowError" && typeof e.reason === "string";
48
+ function checkPriority(p) {
49
+ if (p === undefined)
50
+ return;
51
+ if (!Number.isInteger(p) || p < 1 || p > 5)
52
+ throw new Failure(`приоритет ${p}: нужно целое от 1 до 5`, { type: "InvalidPriority", nonRetryable: true });
53
+ return p;
54
+ }
55
+ var DEFAULT_PRIORITY = 3;
56
+ // src/exit.ts
57
+ function exitSignal() {
58
+ let d = Promise.withResolvers();
59
+ let settled = false;
60
+ d.promise.catch(() => {});
61
+ const settle = (f) => {
62
+ if (settled)
63
+ return;
64
+ settled = true;
65
+ f();
66
+ };
67
+ return {
68
+ arm() {
69
+ if (!settled)
70
+ return;
71
+ d = Promise.withResolvers();
72
+ d.promise.catch(() => {});
73
+ settled = false;
74
+ },
75
+ resolve: () => settle(() => d.resolve()),
76
+ reject: (e) => settle(() => d.reject(e)),
77
+ promise: () => d.promise
78
+ };
79
+ }
80
+ export {
81
+ DEFAULT_PRIORITY,
82
+ Failure,
83
+ FlowError,
84
+ bindStepRuntime,
85
+ checkPriority,
86
+ exitSignal,
87
+ heartbeat,
88
+ isFlowError,
89
+ lastHeartbeat,
90
+ nonRetryable,
91
+ stepAttempt
92
+ };
@@ -0,0 +1,141 @@
1
+ export type Activity = (...args: any[]) => Promise<unknown>;
2
+ export interface StepOptions {
3
+ /** Попыток всего, включая первую. По умолчанию 3; Infinity — до totalTimeoutMs или срока процесса. */
4
+ attempts?: number;
5
+ /** Предел одной попытки. По умолчанию 60 с. DBOS не ограничивает: шаг повторяется после рестарта. */
6
+ timeoutMs?: number;
7
+ /** Шаг без heartbeat дольше этого — попытка мёртвой, повтор. Только Temporal; DBOS ловит смерть рестартом. */
8
+ heartbeatTimeoutMs?: number;
9
+ /** Пауза перед вторым повтором. По умолчанию 200 мс. */
10
+ retryIntervalMs?: number;
11
+ /** Множитель паузы для следующих повторов. По умолчанию 1. */
12
+ backoff?: number;
13
+ /** Предел паузы между повторами. */
14
+ maxRetryIntervalMs?: number;
15
+ /**
16
+ * Срок шага со всеми повторами; вышел — FlowError reason "timeout". Temporal режет и идущую
17
+ * попытку, DBOS только не начинает новую: идущую ограничивает timeoutMs.
18
+ */
19
+ totalTimeoutMs?: number;
20
+ /** Типы отказов (`Failure.type` или имя класса ошибки), которые не повторять. */
21
+ nonRetryableTypes?: string[];
22
+ /** Пул шага из AppDefinition.pools — со своим пределом одновременных шагов. */
23
+ pool?: string;
24
+ /** Приоритет шага в очереди пула: 1 — первым, 5 — последним. По умолчанию — приоритет процесса. */
25
+ priority?: number;
26
+ }
27
+ export interface ChildOptions {
28
+ id: string;
29
+ /** Срок процесса; вышел — FlowError reason "timeout". */
30
+ timeoutMs?: number;
31
+ /**
32
+ * Id уже занят (процесс идёт или завершён): "reject" — FlowError reason "already-started".
33
+ * Без него движки расходятся: Temporal запускает завершённый id заново, DBOS отдаёт его результат.
34
+ */
35
+ onExisting?: "reject";
36
+ }
37
+ /** Дочерний процесс, запущенный без ожидания. */
38
+ export interface ChildHandle<T> {
39
+ id: string;
40
+ result(): Promise<T>;
41
+ }
42
+ /** Что видит код процесса. Детерминирован: время, случайность, ввод-вывод — только через step. */
43
+ export interface FlowContext {
44
+ readonly workflowId: string;
45
+ /** Id родительского процесса; undefined — процесс запущен снаружи. */
46
+ readonly parentId: string | undefined;
47
+ /** Время запуска процесса: одно и то же при каждом переигрывании. */
48
+ readonly startedAt: Date;
49
+ /** Приоритет процесса из start: 1 — первым, 5 — последним; не задан — 3. */
50
+ readonly priority: number;
51
+ /** Текущее время, мс: одно и то же при переигрывании. Date.now() в процессе DBOS не детерминирован. */
52
+ now(): Promise<number>;
53
+ /** Шаг упал после повторов — FlowError reason "failed" или "timeout". */
54
+ step<T>(name: string, args: unknown[], opts?: StepOptions): Promise<T>;
55
+ /** Дочерний процесс в другом модуле: его очередь, его процесс, ждём результат. */
56
+ child<T>(target: FlowRef, args: unknown[], opts: ChildOptions): Promise<T>;
57
+ /**
58
+ * Дочерний процесс без ожидания: живёт и после завершения родителя. Id уже идёт или занят —
59
+ * FlowError reason "already-started".
60
+ */
61
+ startChild<T>(target: FlowRef, args: unknown[], opts: Omit<ChildOptions, "onExisting">): Promise<ChildHandle<T>>;
62
+ sleep(ms: number): Promise<void>;
63
+ /** Ждать сигнал по теме; null — вышло время. Сигнал, пришедший раньше, не теряется. */
64
+ recv<T>(topic: string, timeoutMs: number): Promise<T | null>;
65
+ /** Сигнал другому процессу по id — в том числе в другом модуле. Процесса нет или он завершён — FlowError "not-found". */
66
+ signal(workflowId: string, topic: string, value: unknown): Promise<void>;
67
+ /** Опубликовать значение для query снаружи; читается и после завершения процесса. */
68
+ setState(key: string, value: unknown): Promise<void>;
69
+ /**
70
+ * Ветка новой версии кода: true — процесс начат новым кодом, false — старым и переигрывается.
71
+ * Новый код кладётся под `if (await ctx.patched("id"))`, старый остаётся в else.
72
+ */
73
+ patched(id: string): Promise<boolean>;
74
+ /** Старых процессов больше нет: всегда true, старую ветку можно удалить. Следующий шаг — убрать вызов. */
75
+ deprecatePatch(id: string): Promise<void>;
76
+ }
77
+ export type Flow = (ctx: FlowContext, ...args: any[]) => Promise<unknown>;
78
+ /** Адрес процесса: модуль (= очередь и приложение) и имя процесса. */
79
+ export interface FlowRef {
80
+ app: string;
81
+ flow: string;
82
+ }
83
+ /** Модуль в одном процессе bun: свои процессы и свои шаги, своя очередь. */
84
+ export interface AppDefinition {
85
+ app: string;
86
+ flows: Record<string, Flow>;
87
+ activities: Record<string, Activity>;
88
+ /**
89
+ * Патчи этой версии кода — все ids из ctx.patched. Необъявленный id — ошибка на обоих движках:
90
+ * DBOS решает ветку по этому списку (см. @iworkflow/dbos), опечатка молча вела бы в старую.
91
+ */
92
+ patches?: string[];
93
+ /** Пулы шагов: имя → сколько шагов пула идёт одновременно в этом процессе. */
94
+ pools?: Record<string, number>;
95
+ }
96
+ export type FlowStatus = "running" | "completed" | "failed" | "terminated" | "timed-out";
97
+ export interface FlowDescription {
98
+ id: string;
99
+ status: FlowStatus;
100
+ startedAt: Date;
101
+ }
102
+ export interface FlowHandle<T> {
103
+ id: string;
104
+ /** Процесс упал — FlowError: "failed", "timeout" или "terminated". */
105
+ result(): Promise<T>;
106
+ signal(topic: string, value: unknown): Promise<void>;
107
+ /** Последнее значение setState по ключу; undefined — ещё не публиковалось. */
108
+ query<V>(key: string): Promise<V | undefined>;
109
+ /** Нет процесса — FlowError "not-found". */
110
+ describe(): Promise<FlowDescription>;
111
+ /** Прервать процесс без отката; result() бросит FlowError "terminated". */
112
+ terminate(reason?: string): Promise<void>;
113
+ }
114
+ /** Сторона модуля: держит воркер своей очереди. */
115
+ export interface EngineRuntime {
116
+ start(def: AppDefinition): Promise<void>;
117
+ stop(): Promise<void>;
118
+ /** Цикл start…stop: завершается со stop; отклоняется при сбое start и (Temporal) падении воркера. */
119
+ done(): Promise<void>;
120
+ }
121
+ export interface StartOptions {
122
+ id: string;
123
+ /** Срок процесса. */
124
+ timeoutMs?: number;
125
+ /** 1 — первым, 5 — последним; шаги процесса наследуют его. */
126
+ priority?: number;
127
+ }
128
+ /** Сторона вызывающего вне процесса (HTTP, CLI, тест). */
129
+ export interface EngineClient {
130
+ /** Id уже идёт — handle к нему. timeoutMs — срок процесса. */
131
+ start<T>(target: FlowRef, args: unknown[], opts: StartOptions): Promise<FlowHandle<T>>;
132
+ /**
133
+ * Сигнал процессу; нет идущего с этим id — запустить и отдать сигнал ему. Temporal заводит новый
134
+ * запуск и под id завершённого; DBOS id не переиспользует — сигнал уйдёт завершённому и пропадёт.
135
+ */
136
+ signalWithStart<T>(target: FlowRef, args: unknown[], opts: StartOptions, topic: string, value: unknown): Promise<FlowHandle<T>>;
137
+ /** Уже запущенный процесс по id. */
138
+ handle<T>(id: string): FlowHandle<T>;
139
+ close(): Promise<void>;
140
+ }
141
+ export type EngineName = "temporal" | "dbos";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iworkflow/core",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Engine-neutral durable workflow port: flows, steps, child workflows, signals, queries, patches, heartbeat",
5
5
  "keywords": [
6
6
  "workflow",
@@ -15,18 +15,29 @@
15
15
  "directory": "packages/core"
16
16
  },
17
17
  "files": [
18
- "src"
18
+ "src",
19
+ "!src/**/*.test.ts",
20
+ "dist"
19
21
  ],
20
22
  "type": "module",
21
23
  "sideEffects": false,
22
24
  "exports": {
23
- ".": "./src/index.ts"
25
+ ".": {
26
+ "bun": "./src/index.ts",
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ }
24
30
  },
25
31
  "publishConfig": {
26
32
  "access": "public"
27
33
  },
28
34
  "scripts": {
35
+ "build": "rm -rf dist && bun build src/index.ts --outdir dist --target node --format esm --packages external && tsc -p tsconfig.build.json",
29
36
  "type-check": "tsc --noEmit -p tsconfig.json",
30
- "test": "bun test"
37
+ "test": "bun test",
38
+ "prepack": "bun run build"
39
+ },
40
+ "engines": {
41
+ "node": ">=22"
31
42
  }
32
43
  }
package/src/errors.ts CHANGED
@@ -8,12 +8,20 @@ export class Failure extends Error {
8
8
  readonly type: string;
9
9
  readonly details?: unknown;
10
10
  readonly nonRetryable: boolean;
11
- constructor(message: string, opts: { type: string; details?: unknown; nonRetryable?: boolean }) {
11
+ /** Следующая попытка шага не раньше чем через столько мс (FLOOD_WAIT): вместо паузы из StepOptions. */
12
+ readonly retryAfterMs?: number;
13
+ constructor(
14
+ message: string,
15
+ opts: { type: string; details?: unknown; nonRetryable?: boolean; retryAfterMs?: number },
16
+ ) {
12
17
  super(message);
13
18
  this.name = "Failure";
14
19
  this.type = opts.type;
15
20
  this.details = opts.details;
16
21
  this.nonRetryable = opts.nonRetryable ?? false;
22
+ // Неразобранный срок (NaN, отрицательный) — как не заданный: пауза из StepOptions.
23
+ const after = opts.retryAfterMs;
24
+ this.retryAfterMs = after !== undefined && Number.isFinite(after) && after >= 0 ? after : undefined;
17
25
  }
18
26
  }
19
27
 
@@ -53,3 +61,14 @@ export class FlowError extends Error {
53
61
  /** FlowError по признакам, а не по классу: класс теряется при сериализации в DBOS. */
54
62
  export const isFlowError = (e: unknown): e is FlowError =>
55
63
  e instanceof FlowError || (e instanceof Error && e.name === "FlowError" && typeof (e as FlowError).reason === "string");
64
+
65
+ /** Приоритет порта — целое 1…5 (1 — первым); иное отвергается одинаково на обоих движках. */
66
+ export function checkPriority(p: number | undefined): number | undefined {
67
+ if (p === undefined) return undefined;
68
+ // Failure, а не Error: в процессе Temporal Error — вечный повтор задачи процесса, а не отказ.
69
+ if (!Number.isInteger(p) || p < 1 || p > 5)
70
+ throw new Failure(`приоритет ${p}: нужно целое от 1 до 5`, { type: "InvalidPriority", nonRetryable: true });
71
+ return p;
72
+ }
73
+
74
+ export const DEFAULT_PRIORITY = 3;
package/src/exit.ts ADDED
@@ -0,0 +1,32 @@
1
+ // Общее для адаптеров: done() рантайма — одно обещание на цикл start…stop, взятое хоть до start.
2
+
3
+ export interface ExitSignal {
4
+ /** Перед start: прошлый цикл завершён — новое обещание, иначе то же. */
5
+ arm(): void;
6
+ resolve(): void;
7
+ reject(e: unknown): void;
8
+ promise(): Promise<void>;
9
+ }
10
+
11
+ export function exitSignal(): ExitSignal {
12
+ let d = Promise.withResolvers<void>();
13
+ let settled = false;
14
+ // Никто не ждёт done() — отказ воркера не становится unhandled rejection.
15
+ d.promise.catch(() => {});
16
+ const settle = (f: () => void) => {
17
+ if (settled) return;
18
+ settled = true;
19
+ f();
20
+ };
21
+ return {
22
+ arm() {
23
+ if (!settled) return;
24
+ d = Promise.withResolvers<void>();
25
+ d.promise.catch(() => {});
26
+ settled = false;
27
+ },
28
+ resolve: () => settle(() => d.resolve()),
29
+ reject: (e) => settle(() => d.reject(e)),
30
+ promise: () => d.promise,
31
+ };
32
+ }
package/src/heartbeat.ts CHANGED
@@ -4,6 +4,8 @@
4
4
  export interface StepRuntime {
5
5
  heartbeat(details?: unknown): Promise<void>;
6
6
  lastHeartbeat(): Promise<unknown>;
7
+ /** Номер попытки шага, с 1. */
8
+ attempt(): number;
7
9
  }
8
10
 
9
11
  const KEY = Symbol.for("@iworkflow/core.step-runtime");
@@ -25,3 +27,6 @@ export const heartbeat = (details?: unknown): Promise<void> => current().heartbe
25
27
  /** Прогресс прошлой попытки этого шага; undefined — первая попытка или прогресса не было. */
26
28
  export const lastHeartbeat = async <T>(): Promise<T | undefined> =>
27
29
  ((await current().lastHeartbeat()) ?? undefined) as T | undefined;
30
+
31
+ /** Номер попытки этого шага, с 1. DBOS считает заново после рестарта процесса, Temporal — нет. */
32
+ export const stepAttempt = (): number => current().attempt();
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./types";
2
- export { bindStepRuntime, heartbeat, lastHeartbeat, type StepRuntime } from "./heartbeat";
2
+ export { bindStepRuntime, heartbeat, lastHeartbeat, stepAttempt, type StepRuntime } from "./heartbeat";
3
3
  export * from "./errors";
4
+ export { exitSignal, type ExitSignal } from "./exit";
package/src/types.ts CHANGED
@@ -25,6 +25,8 @@ export interface StepOptions {
25
25
  nonRetryableTypes?: string[];
26
26
  /** Пул шага из AppDefinition.pools — со своим пределом одновременных шагов. */
27
27
  pool?: string;
28
+ /** Приоритет шага в очереди пула: 1 — первым, 5 — последним. По умолчанию — приоритет процесса. */
29
+ priority?: number;
28
30
  }
29
31
 
30
32
  export interface ChildOptions {
@@ -51,6 +53,10 @@ export interface FlowContext {
51
53
  readonly parentId: string | undefined;
52
54
  /** Время запуска процесса: одно и то же при каждом переигрывании. */
53
55
  readonly startedAt: Date;
56
+ /** Приоритет процесса из start: 1 — первым, 5 — последним; не задан — 3. */
57
+ readonly priority: number;
58
+ /** Текущее время, мс: одно и то же при переигрывании. Date.now() в процессе DBOS не детерминирован. */
59
+ now(): Promise<number>;
54
60
  /** Шаг упал после повторов — FlowError reason "failed" или "timeout". */
55
61
  step<T>(name: string, args: unknown[], opts?: StepOptions): Promise<T>;
56
62
  /** Дочерний процесс в другом модуле: его очередь, его процесс, ждём результат. */
@@ -123,12 +129,27 @@ export interface FlowHandle<T> {
123
129
  export interface EngineRuntime {
124
130
  start(def: AppDefinition): Promise<void>;
125
131
  stop(): Promise<void>;
132
+ /** Цикл start…stop: завершается со stop; отклоняется при сбое start и (Temporal) падении воркера. */
133
+ done(): Promise<void>;
134
+ }
135
+
136
+ export interface StartOptions {
137
+ id: string;
138
+ /** Срок процесса. */
139
+ timeoutMs?: number;
140
+ /** 1 — первым, 5 — последним; шаги процесса наследуют его. */
141
+ priority?: number;
126
142
  }
127
143
 
128
144
  /** Сторона вызывающего вне процесса (HTTP, CLI, тест). */
129
145
  export interface EngineClient {
130
146
  /** Id уже идёт — handle к нему. timeoutMs — срок процесса. */
131
- start<T>(target: FlowRef, args: unknown[], opts: { id: string; timeoutMs?: number }): Promise<FlowHandle<T>>;
147
+ start<T>(target: FlowRef, args: unknown[], opts: StartOptions): Promise<FlowHandle<T>>;
148
+ /**
149
+ * Сигнал процессу; нет идущего с этим id — запустить и отдать сигнал ему. Temporal заводит новый
150
+ * запуск и под id завершённого; DBOS id не переиспользует — сигнал уйдёт завершённому и пропадёт.
151
+ */
152
+ signalWithStart<T>(target: FlowRef, args: unknown[], opts: StartOptions, topic: string, value: unknown): Promise<FlowHandle<T>>;
132
153
  /** Уже запущенный процесс по id. */
133
154
  handle<T>(id: string): FlowHandle<T>;
134
155
  close(): Promise<void>;