@iworkflow/core 0.6.1 → 0.8.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 +3 -3
- package/dist/errors.d.ts +5 -0
- package/dist/heartbeat.d.ts +17 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +36 -4
- package/dist/types.d.ts +22 -4
- package/package.json +1 -1
- package/src/errors.ts +27 -0
- package/src/heartbeat.ts +17 -3
- package/src/index.ts +1 -1
- package/src/types.ts +23 -4
package/README.md
CHANGED
|
@@ -33,11 +33,11 @@ try {
|
|
|
33
33
|
}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Ещё: `ctx.startChild` (без
|
|
37
|
-
`ctx.parentId`, `AppDefinition.pools`, `handle.describe()` и `handle.terminate()`.
|
|
36
|
+
Ещё: `ctx.startChild` (без ожидания, `reuseClosedId`), `ChildOptions.timeoutMs` и `onExisting`, `ctx.startedAt`,
|
|
37
|
+
`ctx.parentId`, `AppDefinition.pools` и `maxConcurrentSteps`, `StartOptions.onExisting`, `handle.describe()` и `handle.terminate()`.
|
|
38
38
|
|
|
39
39
|
Приоритет: `client.start(..., { priority })` (1 — первым, 5 — последним) → `ctx.priority`; шаги и
|
|
40
40
|
дети его наследуют, `StepOptions.priority` — свой. Срок повтора из отказа: `new Failure(msg, { type,
|
|
41
|
-
retryAfterMs })`; `benign: true` — штатный отказ, Temporal не пишет его ошибкой. Номер попытки в шаге — `stepAttempt()`. Время в процессе — `await ctx.now()`.
|
|
41
|
+
retryAfterMs })`; `benign: true` — штатный отказ, Temporal не пишет его ошибкой. Номер попытки в шаге — `stepAttempt()`, процесс и запуск — `stepInfo()`, отмена попытки — `stepSignal()`. Журнал без повторов — `ctx.log`. Время в процессе — `await ctx.now()`.
|
|
42
42
|
`client.signalWithStart(target, args, opts, topic, value)` — сигнал идущему или запуск с сигналом.
|
|
43
43
|
Контракт обоих движков — `lab/test/contract.test.ts` репозитория.
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AppDefinition } from "./types";
|
|
1
2
|
/**
|
|
2
3
|
* Отказ, который бросает код шага или процесса. `type` — код для разбора (`FlowError.type`),
|
|
3
4
|
* `nonRetryable` — шаг не повторять. Отказ процесса не повторяется никогда.
|
|
@@ -47,3 +48,7 @@ export declare const isFlowError: (e: unknown) => e is FlowError;
|
|
|
47
48
|
/** Приоритет порта — целое 1…5 (1 — первым); иное отвергается одинаково на обоих движках. */
|
|
48
49
|
export declare function checkPriority(p: number | undefined): number | undefined;
|
|
49
50
|
export declare const DEFAULT_PRIORITY = 3;
|
|
51
|
+
/** Пределы модуля — целые ≥ 1: иначе DBOS молча вешает шаги, а Temporal падает на старте. */
|
|
52
|
+
export declare function checkLimits(def: AppDefinition): void;
|
|
53
|
+
/** attrs журнала — в JSON-данные: BigInt строкой, Error — name, message и свои поля; непереносимый ключ выпадает один. */
|
|
54
|
+
export declare function plainAttrs(attrs: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
|
package/dist/heartbeat.d.ts
CHANGED
|
@@ -3,6 +3,14 @@ export interface StepRuntime {
|
|
|
3
3
|
lastHeartbeat(): Promise<unknown>;
|
|
4
4
|
/** Номер попытки шага, с 1. */
|
|
5
5
|
attempt(): number;
|
|
6
|
+
/** Процесс шага, запуск и шаг в нём. */
|
|
7
|
+
info(): {
|
|
8
|
+
workflowId: string;
|
|
9
|
+
runId: string;
|
|
10
|
+
stepId: string;
|
|
11
|
+
};
|
|
12
|
+
/** Отмена попытки. Temporal — срок и отмена процесса, узнаёт через heartbeat; DBOS — только срок попытки. */
|
|
13
|
+
signal(): AbortSignal;
|
|
6
14
|
}
|
|
7
15
|
/** Область шага: какой рантайм у шага, что идёт сейчас. Самый внутренний шаг — свой, без угадывания движка. */
|
|
8
16
|
export interface StepScope {
|
|
@@ -20,3 +28,12 @@ export declare const heartbeat: (details?: unknown) => Promise<void>;
|
|
|
20
28
|
export declare const lastHeartbeat: <T>() => Promise<T | undefined>;
|
|
21
29
|
/** Номер попытки этого шага, с 1. DBOS считает заново после рестарта процесса, Temporal — нет. */
|
|
22
30
|
export declare const stepAttempt: () => number;
|
|
31
|
+
/** Ключ идемпотентности внешней записи — runId + stepId: id переиспользуется (reuseClosedId), runId нет. */
|
|
32
|
+
export declare const stepInfo: () => {
|
|
33
|
+
workflowId: string;
|
|
34
|
+
runId: string;
|
|
35
|
+
stepId: string;
|
|
36
|
+
attempt: number;
|
|
37
|
+
};
|
|
38
|
+
/** Сигнал отмены попытки: передать в fetch и т. п.; aborted — итог попытки отброшен, не оборачивать в Failure. */
|
|
39
|
+
export declare const stepSignal: () => AbortSignal;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
|
-
export { heartbeat, lastHeartbeat, stepAttempt, stepScope, type StepRuntime, type StepScope } from "./heartbeat";
|
|
2
|
+
export { heartbeat, lastHeartbeat, stepAttempt, stepInfo, stepScope, stepSignal, type StepRuntime, type StepScope } from "./heartbeat";
|
|
3
3
|
export * from "./errors";
|
|
4
4
|
export { exitSignal, type ExitSignal } from "./exit";
|
package/dist/index.js
CHANGED
|
@@ -3,15 +3,20 @@ var KEY = Symbol.for("@iworkflow/core.step-scope");
|
|
|
3
3
|
function stepScope(make) {
|
|
4
4
|
return globalThis[KEY] ??= make();
|
|
5
5
|
}
|
|
6
|
-
function current() {
|
|
6
|
+
function current(name = "heartbeat") {
|
|
7
7
|
const rt = globalThis[KEY]?.current();
|
|
8
8
|
if (!rt)
|
|
9
|
-
throw new Error(
|
|
9
|
+
throw new Error(`${name} вне шага: шаг движка процесса сейчас не идёт`);
|
|
10
10
|
return rt;
|
|
11
11
|
}
|
|
12
12
|
var heartbeat = (details) => current().heartbeat(details);
|
|
13
13
|
var lastHeartbeat = async () => await current().lastHeartbeat() ?? undefined;
|
|
14
|
-
var stepAttempt = () => current().attempt();
|
|
14
|
+
var stepAttempt = () => current("stepAttempt").attempt();
|
|
15
|
+
var stepInfo = () => {
|
|
16
|
+
const rt = current("stepInfo");
|
|
17
|
+
return { ...rt.info(), attempt: rt.attempt() };
|
|
18
|
+
};
|
|
19
|
+
var stepSignal = () => current("stepSignal").signal();
|
|
15
20
|
// src/errors.ts
|
|
16
21
|
class Failure extends Error {
|
|
17
22
|
type;
|
|
@@ -55,6 +60,29 @@ function checkPriority(p) {
|
|
|
55
60
|
return p;
|
|
56
61
|
}
|
|
57
62
|
var DEFAULT_PRIORITY = 3;
|
|
63
|
+
function checkLimits(def) {
|
|
64
|
+
const check = (name, n) => {
|
|
65
|
+
if (n !== undefined && (!Number.isInteger(n) || n < 1))
|
|
66
|
+
throw new Error(`${def.app}: ${name} = ${n}, нужно целое ≥ 1`);
|
|
67
|
+
};
|
|
68
|
+
check("maxConcurrentSteps", def.maxConcurrentSteps);
|
|
69
|
+
for (const [p, n] of Object.entries(def.pools ?? {}))
|
|
70
|
+
check(`pools.${p}`, n);
|
|
71
|
+
}
|
|
72
|
+
function plainAttrs(attrs) {
|
|
73
|
+
if (!attrs)
|
|
74
|
+
return;
|
|
75
|
+
const plain = (_, v) => typeof v === "bigint" ? String(v) : v instanceof Error ? { ...v, name: v.name, message: v.message } : v;
|
|
76
|
+
const out = {};
|
|
77
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
78
|
+
try {
|
|
79
|
+
const json = JSON.stringify(v, plain);
|
|
80
|
+
if (json !== undefined)
|
|
81
|
+
out[k] = JSON.parse(json);
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
58
86
|
// src/exit.ts
|
|
59
87
|
function exitSignal() {
|
|
60
88
|
let d = Promise.withResolvers();
|
|
@@ -83,12 +111,16 @@ export {
|
|
|
83
111
|
DEFAULT_PRIORITY,
|
|
84
112
|
Failure,
|
|
85
113
|
FlowError,
|
|
114
|
+
checkLimits,
|
|
86
115
|
checkPriority,
|
|
87
116
|
exitSignal,
|
|
88
117
|
heartbeat,
|
|
89
118
|
isFlowError,
|
|
90
119
|
lastHeartbeat,
|
|
91
120
|
nonRetryable,
|
|
121
|
+
plainAttrs,
|
|
92
122
|
stepAttempt,
|
|
93
|
-
|
|
123
|
+
stepInfo,
|
|
124
|
+
stepScope,
|
|
125
|
+
stepSignal
|
|
94
126
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -34,6 +34,13 @@ export interface ChildOptions {
|
|
|
34
34
|
*/
|
|
35
35
|
onExisting?: "reject";
|
|
36
36
|
}
|
|
37
|
+
export interface StartChildOptions extends Omit<ChildOptions, "onExisting"> {
|
|
38
|
+
/**
|
|
39
|
+
* Id завершённого процесса — новый запуск под ним (окно сбора на чат). Только Temporal: DBOS id не
|
|
40
|
+
* переиспользует — already-started. Идущий процесс с этим id — already-started всегда.
|
|
41
|
+
*/
|
|
42
|
+
reuseClosedId?: boolean;
|
|
43
|
+
}
|
|
37
44
|
/** Дочерний процесс, запущенный без ожидания. */
|
|
38
45
|
export interface ChildHandle<T> {
|
|
39
46
|
id: string;
|
|
@@ -58,12 +65,14 @@ export interface FlowContext {
|
|
|
58
65
|
* Дочерний процесс без ожидания: живёт и после завершения родителя. Id уже идёт или занят —
|
|
59
66
|
* FlowError reason "already-started".
|
|
60
67
|
*/
|
|
61
|
-
startChild<T>(target: FlowRef, args: unknown[], opts:
|
|
68
|
+
startChild<T>(target: FlowRef, args: unknown[], opts: StartChildOptions): Promise<ChildHandle<T>>;
|
|
62
69
|
sleep(ms: number): Promise<void>;
|
|
63
|
-
/** Ждать сигнал по теме; null — вышло
|
|
70
|
+
/** Ждать сигнал по теме; null — вышло время, при timeoutMs 0 — сигнала нет сейчас (Temporal: таймер 1 мс). Пришедший раньше не теряется. */
|
|
64
71
|
recv<T>(topic: string, timeoutMs: number): Promise<T | null>;
|
|
65
72
|
/** Сигнал другому процессу по id — в том числе в другом модуле. Процесса нет или он завершён — FlowError "not-found". */
|
|
66
73
|
signal(workflowId: string, topic: string, value: unknown): Promise<void>;
|
|
74
|
+
/** Журнал из процесса: Temporal не повторяет строку при переигрывании, DBOS может повторить после рестарта. */
|
|
75
|
+
log(level: "info" | "warn" | "error", message: string, attrs?: Record<string, unknown>): Promise<void>;
|
|
67
76
|
/** Опубликовать значение для query снаружи; читается и после завершения процесса. */
|
|
68
77
|
setState(key: string, value: unknown): Promise<void>;
|
|
69
78
|
/**
|
|
@@ -92,6 +101,8 @@ export interface AppDefinition {
|
|
|
92
101
|
patches?: string[];
|
|
93
102
|
/** Пулы шагов: имя → сколько шагов пула идёт одновременно в этом процессе. */
|
|
94
103
|
pools?: Record<string, number>;
|
|
104
|
+
/** Сколько шагов вне пулов идёт одновременно в этом процессе (бережёт пул БД). Не задано — у DBOS без предела, у Temporal 100. */
|
|
105
|
+
maxConcurrentSteps?: number;
|
|
95
106
|
}
|
|
96
107
|
export type FlowStatus = "running" | "completed" | "failed" | "terminated" | "timed-out";
|
|
97
108
|
export interface FlowDescription {
|
|
@@ -124,16 +135,23 @@ export interface StartOptions {
|
|
|
124
135
|
timeoutMs?: number;
|
|
125
136
|
/** 1 — первым, 5 — последним; шаги процесса наследуют его. */
|
|
126
137
|
priority?: number;
|
|
138
|
+
/** Id уже занят (идёт или завершён; Temporal помнит завершённый в пределах хранения namespace) — FlowError "already-started". */
|
|
139
|
+
onExisting?: "reject";
|
|
140
|
+
/**
|
|
141
|
+
* Отказ, только пока процесс идёт; завершённый id — новый запуск (ночные задачи с постоянным id).
|
|
142
|
+
* Включает onExisting "reject". Только Temporal: DBOS id не переиспользует — already-started.
|
|
143
|
+
*/
|
|
144
|
+
reuseClosedId?: boolean;
|
|
127
145
|
}
|
|
128
146
|
/** Сторона вызывающего вне процесса (HTTP, CLI, тест). */
|
|
129
147
|
export interface EngineClient {
|
|
130
|
-
/** Id уже идёт — handle к
|
|
148
|
+
/** Id уже идёт — handle к нему (с onExisting "reject" — already-started). timeoutMs — срок процесса. */
|
|
131
149
|
start<T>(target: FlowRef, args: unknown[], opts: StartOptions): Promise<FlowHandle<T>>;
|
|
132
150
|
/**
|
|
133
151
|
* Сигнал процессу; нет идущего с этим id — запустить и отдать сигнал ему. Temporal заводит новый
|
|
134
152
|
* запуск и под id завершённого; DBOS id не переиспользует — сигнал уйдёт завершённому и пропадёт.
|
|
135
153
|
*/
|
|
136
|
-
signalWithStart<T>(target: FlowRef, args: unknown[], opts: StartOptions, topic: string, value: unknown): Promise<FlowHandle<T>>;
|
|
154
|
+
signalWithStart<T>(target: FlowRef, args: unknown[], opts: Omit<StartOptions, "onExisting" | "reuseClosedId">, topic: string, value: unknown): Promise<FlowHandle<T>>;
|
|
137
155
|
/** Уже запущенный процесс по id. */
|
|
138
156
|
handle<T>(id: string): FlowHandle<T>;
|
|
139
157
|
close(): Promise<void>;
|
package/package.json
CHANGED
package/src/errors.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Ошибки порта: одинаковые на обоих движках. Без импортов рантайма — их читает песочница Temporal.
|
|
2
|
+
import type { AppDefinition } from "./types";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Отказ, который бросает код шага или процесса. `type` — код для разбора (`FlowError.type`),
|
|
@@ -75,3 +76,29 @@ export function checkPriority(p: number | undefined): number | undefined {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
export const DEFAULT_PRIORITY = 3;
|
|
79
|
+
|
|
80
|
+
/** Пределы модуля — целые ≥ 1: иначе DBOS молча вешает шаги, а Temporal падает на старте. */
|
|
81
|
+
export function checkLimits(def: AppDefinition): void {
|
|
82
|
+
const check = (name: string, n: number | undefined) => {
|
|
83
|
+
if (n !== undefined && (!Number.isInteger(n) || n < 1)) throw new Error(`${def.app}: ${name} = ${n}, нужно целое ≥ 1`);
|
|
84
|
+
};
|
|
85
|
+
check("maxConcurrentSteps", def.maxConcurrentSteps);
|
|
86
|
+
for (const [p, n] of Object.entries(def.pools ?? {})) check(`pools.${p}`, n);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** attrs журнала — в JSON-данные: BigInt строкой, Error — name, message и свои поля; непереносимый ключ выпадает один. */
|
|
90
|
+
export function plainAttrs(attrs: Record<string, unknown> | undefined): Record<string, unknown> | undefined {
|
|
91
|
+
if (!attrs) return undefined;
|
|
92
|
+
const plain = (_: string, v: unknown) =>
|
|
93
|
+
typeof v === "bigint" ? String(v) : v instanceof Error ? { ...v, name: v.name, message: v.message } : v;
|
|
94
|
+
const out: Record<string, unknown> = {};
|
|
95
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
96
|
+
try {
|
|
97
|
+
const json = JSON.stringify(v, plain);
|
|
98
|
+
if (json !== undefined) out[k] = JSON.parse(json);
|
|
99
|
+
} catch {
|
|
100
|
+
// цикл или бросающий геттер — только этот ключ
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
package/src/heartbeat.ts
CHANGED
|
@@ -6,6 +6,10 @@ export interface StepRuntime {
|
|
|
6
6
|
lastHeartbeat(): Promise<unknown>;
|
|
7
7
|
/** Номер попытки шага, с 1. */
|
|
8
8
|
attempt(): number;
|
|
9
|
+
/** Процесс шага, запуск и шаг в нём. */
|
|
10
|
+
info(): { workflowId: string; runId: string; stepId: string };
|
|
11
|
+
/** Отмена попытки. Temporal — срок и отмена процесса, узнаёт через heartbeat; DBOS — только срок попытки. */
|
|
12
|
+
signal(): AbortSignal;
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
/** Область шага: какой рантайм у шага, что идёт сейчас. Самый внутренний шаг — свой, без угадывания движка. */
|
|
@@ -25,9 +29,9 @@ export function stepScope(make: () => StepScope): StepScope {
|
|
|
25
29
|
return ((globalThis as Slot)[KEY] ??= make());
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
function current(): StepRuntime {
|
|
32
|
+
function current(name = "heartbeat"): StepRuntime {
|
|
29
33
|
const rt = (globalThis as Slot)[KEY]?.current();
|
|
30
|
-
if (!rt) throw new Error(
|
|
34
|
+
if (!rt) throw new Error(`${name} вне шага: шаг движка процесса сейчас не идёт`);
|
|
31
35
|
return rt;
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -39,4 +43,14 @@ export const lastHeartbeat = async <T>(): Promise<T | undefined> =>
|
|
|
39
43
|
((await current().lastHeartbeat()) ?? undefined) as T | undefined;
|
|
40
44
|
|
|
41
45
|
/** Номер попытки этого шага, с 1. DBOS считает заново после рестарта процесса, Temporal — нет. */
|
|
42
|
-
export const stepAttempt = (): number => current().attempt();
|
|
46
|
+
export const stepAttempt = (): number => current("stepAttempt").attempt();
|
|
47
|
+
|
|
48
|
+
/** Ключ идемпотентности внешней записи — runId + stepId: id переиспользуется (reuseClosedId), runId нет. */
|
|
49
|
+
// У DBOS runId = workflowId: id не переиспользуется.
|
|
50
|
+
export const stepInfo = (): { workflowId: string; runId: string; stepId: string; attempt: number } => {
|
|
51
|
+
const rt = current("stepInfo");
|
|
52
|
+
return { ...rt.info(), attempt: rt.attempt() };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Сигнал отмены попытки: передать в fetch и т. п.; aborted — итог попытки отброшен, не оборачивать в Failure. */
|
|
56
|
+
export const stepSignal = (): AbortSignal => current("stepSignal").signal();
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
|
-
export { heartbeat, lastHeartbeat, stepAttempt, stepScope, type StepRuntime, type StepScope } from "./heartbeat";
|
|
2
|
+
export { heartbeat, lastHeartbeat, stepAttempt, stepInfo, stepScope, stepSignal, type StepRuntime, type StepScope } from "./heartbeat";
|
|
3
3
|
export * from "./errors";
|
|
4
4
|
export { exitSignal, type ExitSignal } from "./exit";
|
package/src/types.ts
CHANGED
|
@@ -40,6 +40,14 @@ export interface ChildOptions {
|
|
|
40
40
|
onExisting?: "reject";
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export interface StartChildOptions extends Omit<ChildOptions, "onExisting"> {
|
|
44
|
+
/**
|
|
45
|
+
* Id завершённого процесса — новый запуск под ним (окно сбора на чат). Только Temporal: DBOS id не
|
|
46
|
+
* переиспользует — already-started. Идущий процесс с этим id — already-started всегда.
|
|
47
|
+
*/
|
|
48
|
+
reuseClosedId?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
/** Дочерний процесс, запущенный без ожидания. */
|
|
44
52
|
export interface ChildHandle<T> {
|
|
45
53
|
id: string;
|
|
@@ -65,12 +73,14 @@ export interface FlowContext {
|
|
|
65
73
|
* Дочерний процесс без ожидания: живёт и после завершения родителя. Id уже идёт или занят —
|
|
66
74
|
* FlowError reason "already-started".
|
|
67
75
|
*/
|
|
68
|
-
startChild<T>(target: FlowRef, args: unknown[], opts:
|
|
76
|
+
startChild<T>(target: FlowRef, args: unknown[], opts: StartChildOptions): Promise<ChildHandle<T>>;
|
|
69
77
|
sleep(ms: number): Promise<void>;
|
|
70
|
-
/** Ждать сигнал по теме; null — вышло
|
|
78
|
+
/** Ждать сигнал по теме; null — вышло время, при timeoutMs 0 — сигнала нет сейчас (Temporal: таймер 1 мс). Пришедший раньше не теряется. */
|
|
71
79
|
recv<T>(topic: string, timeoutMs: number): Promise<T | null>;
|
|
72
80
|
/** Сигнал другому процессу по id — в том числе в другом модуле. Процесса нет или он завершён — FlowError "not-found". */
|
|
73
81
|
signal(workflowId: string, topic: string, value: unknown): Promise<void>;
|
|
82
|
+
/** Журнал из процесса: Temporal не повторяет строку при переигрывании, DBOS может повторить после рестарта. */
|
|
83
|
+
log(level: "info" | "warn" | "error", message: string, attrs?: Record<string, unknown>): Promise<void>;
|
|
74
84
|
/** Опубликовать значение для query снаружи; читается и после завершения процесса. */
|
|
75
85
|
setState(key: string, value: unknown): Promise<void>;
|
|
76
86
|
/**
|
|
@@ -102,6 +112,8 @@ export interface AppDefinition {
|
|
|
102
112
|
patches?: string[];
|
|
103
113
|
/** Пулы шагов: имя → сколько шагов пула идёт одновременно в этом процессе. */
|
|
104
114
|
pools?: Record<string, number>;
|
|
115
|
+
/** Сколько шагов вне пулов идёт одновременно в этом процессе (бережёт пул БД). Не задано — у DBOS без предела, у Temporal 100. */
|
|
116
|
+
maxConcurrentSteps?: number;
|
|
105
117
|
}
|
|
106
118
|
|
|
107
119
|
export type FlowStatus = "running" | "completed" | "failed" | "terminated" | "timed-out";
|
|
@@ -139,17 +151,24 @@ export interface StartOptions {
|
|
|
139
151
|
timeoutMs?: number;
|
|
140
152
|
/** 1 — первым, 5 — последним; шаги процесса наследуют его. */
|
|
141
153
|
priority?: number;
|
|
154
|
+
/** Id уже занят (идёт или завершён; Temporal помнит завершённый в пределах хранения namespace) — FlowError "already-started". */
|
|
155
|
+
onExisting?: "reject";
|
|
156
|
+
/**
|
|
157
|
+
* Отказ, только пока процесс идёт; завершённый id — новый запуск (ночные задачи с постоянным id).
|
|
158
|
+
* Включает onExisting "reject". Только Temporal: DBOS id не переиспользует — already-started.
|
|
159
|
+
*/
|
|
160
|
+
reuseClosedId?: boolean;
|
|
142
161
|
}
|
|
143
162
|
|
|
144
163
|
/** Сторона вызывающего вне процесса (HTTP, CLI, тест). */
|
|
145
164
|
export interface EngineClient {
|
|
146
|
-
/** Id уже идёт — handle к
|
|
165
|
+
/** Id уже идёт — handle к нему (с onExisting "reject" — already-started). timeoutMs — срок процесса. */
|
|
147
166
|
start<T>(target: FlowRef, args: unknown[], opts: StartOptions): Promise<FlowHandle<T>>;
|
|
148
167
|
/**
|
|
149
168
|
* Сигнал процессу; нет идущего с этим id — запустить и отдать сигнал ему. Temporal заводит новый
|
|
150
169
|
* запуск и под id завершённого; DBOS id не переиспользует — сигнал уйдёт завершённому и пропадёт.
|
|
151
170
|
*/
|
|
152
|
-
signalWithStart<T>(target: FlowRef, args: unknown[], opts: StartOptions, topic: string, value: unknown): Promise<FlowHandle<T>>;
|
|
171
|
+
signalWithStart<T>(target: FlowRef, args: unknown[], opts: Omit<StartOptions, "onExisting" | "reuseClosedId">, topic: string, value: unknown): Promise<FlowHandle<T>>;
|
|
153
172
|
/** Уже запущенный процесс по id. */
|
|
154
173
|
handle<T>(id: string): FlowHandle<T>;
|
|
155
174
|
close(): Promise<void>;
|