@iworkflow/core 0.1.0 → 0.3.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 +16 -0
- package/package.json +1 -1
- package/src/errors.ts +55 -0
- package/src/index.ts +1 -0
- package/src/types.ts +62 -4
package/README.md
CHANGED
|
@@ -17,4 +17,20 @@ export const outreach: Flow = async (ctx, input: { id: string }) => {
|
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Шаг с прогрессом: `heartbeat(details)` и `lastHeartbeat()` из этого пакета внутри функции шага.
|
|
20
|
+
|
|
21
|
+
Отказы одинаковы на обоих движках. Шаг бросает `nonRetryable(message, type, details)` или
|
|
22
|
+
`new Failure(message, { type })`; порт бросает `FlowError` с `reason` (`failed`, `timeout`,
|
|
23
|
+
`terminated`, `already-started`, `not-found`), `type`, `details` и `exhausted`:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
try {
|
|
27
|
+
await ctx.step("send", [input], { attempts: 20, retryIntervalMs: 120_000, backoff: 1.5, pool: "paced" });
|
|
28
|
+
} catch (e) {
|
|
29
|
+
if (e instanceof FlowError && e.type === "QuietHours") await ctx.sleep(e.details as number);
|
|
30
|
+
else throw e;
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Ещё: `ctx.startChild` (без ожидания), `ChildOptions.timeoutMs` и `onExisting`, `ctx.startedAt`,
|
|
35
|
+
`ctx.parentId`, `AppDefinition.pools`, `handle.describe()` и `handle.terminate()`.
|
|
20
36
|
Контракт обоих движков — `lab/test/contract.test.ts` репозитория.
|
package/package.json
CHANGED
package/src/errors.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Ошибки порта: одинаковые на обоих движках. Без импортов рантайма — их читает песочница Temporal.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Отказ, который бросает код шага или процесса. `type` — код для разбора (`FlowError.type`),
|
|
5
|
+
* `nonRetryable` — шаг не повторять. Отказ процесса не повторяется никогда.
|
|
6
|
+
*/
|
|
7
|
+
export class Failure extends Error {
|
|
8
|
+
readonly type: string;
|
|
9
|
+
readonly details?: unknown;
|
|
10
|
+
readonly nonRetryable: boolean;
|
|
11
|
+
constructor(message: string, opts: { type: string; details?: unknown; nonRetryable?: boolean }) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "Failure";
|
|
14
|
+
this.type = opts.type;
|
|
15
|
+
this.details = opts.details;
|
|
16
|
+
this.nonRetryable = opts.nonRetryable ?? false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Отказ без повтора: `throw nonRetryable("нет такой фазы", "UnknownPhase")`. */
|
|
21
|
+
export const nonRetryable = (message: string, type: string, details?: unknown) =>
|
|
22
|
+
new Failure(message, { type, details, nonRetryable: true });
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* - failed — шаг или процесс упал; `type` — тип отказа (`Failure.type` или имя класса ошибки).
|
|
26
|
+
* - timeout — вышел срок: попытки шага (`timeoutMs`), шага целиком (`totalTimeoutMs`) или процесса.
|
|
27
|
+
* - terminated — процесс прерван снаружи (`EngineClient.terminate`).
|
|
28
|
+
* - already-started — процесс с этим id уже есть (`startChild`, `onExisting: "reject"`).
|
|
29
|
+
* - not-found — процесса с этим id нет или он завершён (`signal`, `describe`).
|
|
30
|
+
*/
|
|
31
|
+
export type FlowErrorReason = "failed" | "timeout" | "terminated" | "already-started" | "not-found";
|
|
32
|
+
|
|
33
|
+
/** Что бросает порт: `ctx.step`, `ctx.child`, `ctx.signal`, `handle.result()`, `client.describe`. */
|
|
34
|
+
export class FlowError extends Error {
|
|
35
|
+
readonly reason: FlowErrorReason;
|
|
36
|
+
readonly type?: string;
|
|
37
|
+
readonly details?: unknown;
|
|
38
|
+
/** Шаг упал на последней разрешённой попытке, а не остановлен `nonRetryable`. */
|
|
39
|
+
readonly exhausted: boolean;
|
|
40
|
+
constructor(
|
|
41
|
+
message: string,
|
|
42
|
+
opts: { reason: FlowErrorReason; type?: string; details?: unknown; exhausted?: boolean },
|
|
43
|
+
) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = "FlowError";
|
|
46
|
+
this.reason = opts.reason;
|
|
47
|
+
this.type = opts.type;
|
|
48
|
+
this.details = opts.details;
|
|
49
|
+
this.exhausted = opts.exhausted ?? false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** FlowError по признакам, а не по классу: класс теряется при сериализации в DBOS. */
|
|
54
|
+
export const isFlowError = (e: unknown): e is FlowError =>
|
|
55
|
+
e instanceof FlowError || (e instanceof Error && e.name === "FlowError" && typeof (e as FlowError).reason === "string");
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -4,24 +4,66 @@
|
|
|
4
4
|
export type Activity = (...args: any[]) => Promise<unknown>;
|
|
5
5
|
|
|
6
6
|
export interface StepOptions {
|
|
7
|
-
/** Попыток всего, включая первую. По умолчанию 3
|
|
7
|
+
/** Попыток всего, включая первую. По умолчанию 3; Infinity — до totalTimeoutMs или срока процесса. */
|
|
8
8
|
attempts?: number;
|
|
9
9
|
/** Предел одной попытки. По умолчанию 60 с. DBOS не ограничивает: шаг повторяется после рестарта. */
|
|
10
10
|
timeoutMs?: number;
|
|
11
11
|
/** Шаг без heartbeat дольше этого — попытка мёртвой, повтор. Только Temporal; DBOS ловит смерть рестартом. */
|
|
12
12
|
heartbeatTimeoutMs?: number;
|
|
13
|
+
/** Пауза перед вторым повтором. По умолчанию 200 мс. */
|
|
14
|
+
retryIntervalMs?: number;
|
|
15
|
+
/** Множитель паузы для следующих повторов. По умолчанию 1. */
|
|
16
|
+
backoff?: number;
|
|
17
|
+
/** Предел паузы между повторами. */
|
|
18
|
+
maxRetryIntervalMs?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Срок шага со всеми повторами; вышел — FlowError reason "timeout". Temporal режет и идущую
|
|
21
|
+
* попытку, DBOS только не начинает новую: идущую ограничивает timeoutMs.
|
|
22
|
+
*/
|
|
23
|
+
totalTimeoutMs?: number;
|
|
24
|
+
/** Типы отказов (`Failure.type` или имя класса ошибки), которые не повторять. */
|
|
25
|
+
nonRetryableTypes?: string[];
|
|
26
|
+
/** Пул шага из AppDefinition.pools — со своим пределом одновременных шагов. */
|
|
27
|
+
pool?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ChildOptions {
|
|
31
|
+
id: string;
|
|
32
|
+
/** Срок процесса; вышел — FlowError reason "timeout". */
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Id уже занят (процесс идёт или завершён): "reject" — FlowError reason "already-started".
|
|
36
|
+
* Без него движки расходятся: Temporal запускает завершённый id заново, DBOS отдаёт его результат.
|
|
37
|
+
*/
|
|
38
|
+
onExisting?: "reject";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Дочерний процесс, запущенный без ожидания. */
|
|
42
|
+
export interface ChildHandle<T> {
|
|
43
|
+
id: string;
|
|
44
|
+
result(): Promise<T>;
|
|
13
45
|
}
|
|
14
46
|
|
|
15
47
|
/** Что видит код процесса. Детерминирован: время, случайность, ввод-вывод — только через step. */
|
|
16
48
|
export interface FlowContext {
|
|
17
49
|
readonly workflowId: string;
|
|
50
|
+
/** Id родительского процесса; undefined — процесс запущен снаружи. */
|
|
51
|
+
readonly parentId: string | undefined;
|
|
52
|
+
/** Время запуска процесса: одно и то же при каждом переигрывании. */
|
|
53
|
+
readonly startedAt: Date;
|
|
54
|
+
/** Шаг упал после повторов — FlowError reason "failed" или "timeout". */
|
|
18
55
|
step<T>(name: string, args: unknown[], opts?: StepOptions): Promise<T>;
|
|
19
56
|
/** Дочерний процесс в другом модуле: его очередь, его процесс, ждём результат. */
|
|
20
|
-
child<T>(target: FlowRef, args: unknown[], opts:
|
|
57
|
+
child<T>(target: FlowRef, args: unknown[], opts: ChildOptions): Promise<T>;
|
|
58
|
+
/**
|
|
59
|
+
* Дочерний процесс без ожидания: живёт и после завершения родителя. Id уже идёт или занят —
|
|
60
|
+
* FlowError reason "already-started".
|
|
61
|
+
*/
|
|
62
|
+
startChild<T>(target: FlowRef, args: unknown[], opts: Omit<ChildOptions, "onExisting">): Promise<ChildHandle<T>>;
|
|
21
63
|
sleep(ms: number): Promise<void>;
|
|
22
64
|
/** Ждать сигнал по теме; null — вышло время. Сигнал, пришедший раньше, не теряется. */
|
|
23
65
|
recv<T>(topic: string, timeoutMs: number): Promise<T | null>;
|
|
24
|
-
/** Сигнал другому процессу по id — в том числе в другом модуле. */
|
|
66
|
+
/** Сигнал другому процессу по id — в том числе в другом модуле. Процесса нет или он завершён — FlowError "not-found". */
|
|
25
67
|
signal(workflowId: string, topic: string, value: unknown): Promise<void>;
|
|
26
68
|
/** Опубликовать значение для query снаружи; читается и после завершения процесса. */
|
|
27
69
|
setState(key: string, value: unknown): Promise<void>;
|
|
@@ -52,14 +94,29 @@ export interface AppDefinition {
|
|
|
52
94
|
* DBOS решает ветку по этому списку (см. @iworkflow/dbos), опечатка молча вела бы в старую.
|
|
53
95
|
*/
|
|
54
96
|
patches?: string[];
|
|
97
|
+
/** Пулы шагов: имя → сколько шагов пула идёт одновременно в этом процессе. */
|
|
98
|
+
pools?: Record<string, number>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type FlowStatus = "running" | "completed" | "failed" | "terminated" | "timed-out";
|
|
102
|
+
|
|
103
|
+
export interface FlowDescription {
|
|
104
|
+
id: string;
|
|
105
|
+
status: FlowStatus;
|
|
106
|
+
startedAt: Date;
|
|
55
107
|
}
|
|
56
108
|
|
|
57
109
|
export interface FlowHandle<T> {
|
|
58
110
|
id: string;
|
|
111
|
+
/** Процесс упал — FlowError: "failed", "timeout" или "terminated". */
|
|
59
112
|
result(): Promise<T>;
|
|
60
113
|
signal(topic: string, value: unknown): Promise<void>;
|
|
61
114
|
/** Последнее значение setState по ключу; undefined — ещё не публиковалось. */
|
|
62
115
|
query<V>(key: string): Promise<V | undefined>;
|
|
116
|
+
/** Нет процесса — FlowError "not-found". */
|
|
117
|
+
describe(): Promise<FlowDescription>;
|
|
118
|
+
/** Прервать процесс без отката; result() бросит FlowError "terminated". */
|
|
119
|
+
terminate(reason?: string): Promise<void>;
|
|
63
120
|
}
|
|
64
121
|
|
|
65
122
|
/** Сторона модуля: держит воркер своей очереди. */
|
|
@@ -70,7 +127,8 @@ export interface EngineRuntime {
|
|
|
70
127
|
|
|
71
128
|
/** Сторона вызывающего вне процесса (HTTP, CLI, тест). */
|
|
72
129
|
export interface EngineClient {
|
|
73
|
-
|
|
130
|
+
/** Id уже идёт — handle к нему. timeoutMs — срок процесса. */
|
|
131
|
+
start<T>(target: FlowRef, args: unknown[], opts: { id: string; timeoutMs?: number }): Promise<FlowHandle<T>>;
|
|
74
132
|
/** Уже запущенный процесс по id. */
|
|
75
133
|
handle<T>(id: string): FlowHandle<T>;
|
|
76
134
|
close(): Promise<void>;
|