@iworkflow/core 0.4.0 → 0.6.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 +7 -0
- package/dist/errors.d.ts +9 -0
- package/dist/heartbeat.d.ts +14 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +24 -7
- package/dist/types.d.ts +19 -4
- package/package.json +1 -1
- package/src/errors.ts +23 -1
- package/src/heartbeat.ts +21 -6
- package/src/index.ts +1 -1
- package/src/types.ts +20 -1
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@ export const outreach: Flow = async (ctx, input: { id: string }) => {
|
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Шаг с прогрессом: `heartbeat(details)` и `lastHeartbeat()` из этого пакета внутри функции шага.
|
|
20
|
+
Рантайм шага ставит адаптер на время шага (`stepScope`): в одном процессе могут жить оба движка.
|
|
21
|
+
`@iworkflow/core` и адаптеры — одной версии: область шага общая через `Symbol.for`.
|
|
20
22
|
|
|
21
23
|
Отказы одинаковы на обоих движках. Шаг бросает `nonRetryable(message, type, details)` или
|
|
22
24
|
`new Failure(message, { type })`; порт бросает `FlowError` с `reason` (`failed`, `timeout`,
|
|
@@ -33,4 +35,9 @@ try {
|
|
|
33
35
|
|
|
34
36
|
Ещё: `ctx.startChild` (без ожидания), `ChildOptions.timeoutMs` и `onExisting`, `ctx.startedAt`,
|
|
35
37
|
`ctx.parentId`, `AppDefinition.pools`, `handle.describe()` и `handle.terminate()`.
|
|
38
|
+
|
|
39
|
+
Приоритет: `client.start(..., { priority })` (1 — первым, 5 — последним) → `ctx.priority`; шаги и
|
|
40
|
+
дети его наследуют, `StepOptions.priority` — свой. Срок повтора из отказа: `new Failure(msg, { type,
|
|
41
|
+
retryAfterMs })`; `benign: true` — штатный отказ, Temporal не пишет его ошибкой. Номер попытки в шаге — `stepAttempt()`. Время в процессе — `await ctx.now()`.
|
|
42
|
+
`client.signalWithStart(target, args, opts, topic, value)` — сигнал идущему или запуск с сигналом.
|
|
36
43
|
Контракт обоих движков — `lab/test/contract.test.ts` репозитория.
|
package/dist/errors.d.ts
CHANGED
|
@@ -6,10 +6,16 @@ export declare class Failure extends Error {
|
|
|
6
6
|
readonly type: string;
|
|
7
7
|
readonly details?: unknown;
|
|
8
8
|
readonly nonRetryable: boolean;
|
|
9
|
+
/** Следующая попытка шага не раньше чем через столько мс (FLOOD_WAIT): вместо паузы из StepOptions. */
|
|
10
|
+
readonly retryAfterMs?: number;
|
|
11
|
+
/** Штатный отказ (флуд, тихие часы): Temporal не пишет его в журнал ошибкой (category BENIGN). */
|
|
12
|
+
readonly benign: boolean;
|
|
9
13
|
constructor(message: string, opts: {
|
|
10
14
|
type: string;
|
|
11
15
|
details?: unknown;
|
|
12
16
|
nonRetryable?: boolean;
|
|
17
|
+
retryAfterMs?: number;
|
|
18
|
+
benign?: boolean;
|
|
13
19
|
});
|
|
14
20
|
}
|
|
15
21
|
/** Отказ без повтора: `throw nonRetryable("нет такой фазы", "UnknownPhase")`. */
|
|
@@ -38,3 +44,6 @@ export declare class FlowError extends Error {
|
|
|
38
44
|
}
|
|
39
45
|
/** FlowError по признакам, а не по классу: класс теряется при сериализации в DBOS. */
|
|
40
46
|
export declare const isFlowError: (e: unknown) => e is FlowError;
|
|
47
|
+
/** Приоритет порта — целое 1…5 (1 — первым); иное отвергается одинаково на обоих движках. */
|
|
48
|
+
export declare function checkPriority(p: number | undefined): number | undefined;
|
|
49
|
+
export declare const DEFAULT_PRIORITY = 3;
|
package/dist/heartbeat.d.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
export interface StepRuntime {
|
|
2
2
|
heartbeat(details?: unknown): Promise<void>;
|
|
3
3
|
lastHeartbeat(): Promise<unknown>;
|
|
4
|
+
/** Номер попытки шага, с 1. */
|
|
5
|
+
attempt(): number;
|
|
4
6
|
}
|
|
5
|
-
|
|
7
|
+
/** Область шага: какой рантайм у шага, что идёт сейчас. Самый внутренний шаг — свой, без угадывания движка. */
|
|
8
|
+
export interface StepScope {
|
|
9
|
+
run<T>(rt: StepRuntime, fn: () => T): T;
|
|
10
|
+
current(): StepRuntime | undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Одна область на процесс; её создаёт первый адаптер (AsyncLocalStorage) — core читает песочница
|
|
14
|
+
* Temporal, и node:async_hooks ему импортировать нельзя.
|
|
15
|
+
*/
|
|
16
|
+
export declare function stepScope(make: () => StepScope): StepScope;
|
|
6
17
|
/** Отметить, что шаг жив, и сохранить прогресс. */
|
|
7
18
|
export declare const heartbeat: (details?: unknown) => Promise<void>;
|
|
8
19
|
/** Прогресс прошлой попытки этого шага; undefined — первая попытка или прогресса не было. */
|
|
9
20
|
export declare const lastHeartbeat: <T>() => Promise<T | undefined>;
|
|
21
|
+
/** Номер попытки этого шага, с 1. DBOS считает заново после рестарта процесса, Temporal — нет. */
|
|
22
|
+
export declare const stepAttempt: () => number;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
|
-
export {
|
|
2
|
+
export { heartbeat, lastHeartbeat, stepAttempt, stepScope, type StepRuntime, type StepScope } from "./heartbeat";
|
|
3
3
|
export * from "./errors";
|
|
4
4
|
export { exitSignal, type ExitSignal } from "./exit";
|
package/dist/index.js
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
// src/heartbeat.ts
|
|
2
|
-
var KEY = Symbol.for("@iworkflow/core.step-
|
|
3
|
-
function
|
|
4
|
-
globalThis[KEY]
|
|
2
|
+
var KEY = Symbol.for("@iworkflow/core.step-scope");
|
|
3
|
+
function stepScope(make) {
|
|
4
|
+
return globalThis[KEY] ??= make();
|
|
5
5
|
}
|
|
6
6
|
function current() {
|
|
7
|
-
const rt = globalThis[KEY];
|
|
7
|
+
const rt = globalThis[KEY]?.current();
|
|
8
8
|
if (!rt)
|
|
9
|
-
throw new Error("heartbeat вне шага:
|
|
9
|
+
throw new Error("heartbeat вне шага: шаг движка процесса сейчас не идёт");
|
|
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
15
|
// src/errors.ts
|
|
15
16
|
class Failure extends Error {
|
|
16
17
|
type;
|
|
17
18
|
details;
|
|
18
19
|
nonRetryable;
|
|
20
|
+
retryAfterMs;
|
|
21
|
+
benign;
|
|
19
22
|
constructor(message, opts) {
|
|
20
23
|
super(message);
|
|
21
24
|
this.name = "Failure";
|
|
22
25
|
this.type = opts.type;
|
|
23
26
|
this.details = opts.details;
|
|
24
27
|
this.nonRetryable = opts.nonRetryable ?? false;
|
|
28
|
+
this.benign = opts.benign ?? false;
|
|
29
|
+
const after = opts.retryAfterMs;
|
|
30
|
+
this.retryAfterMs = after !== undefined && Number.isFinite(after) && after >= 0 ? after : undefined;
|
|
25
31
|
}
|
|
26
32
|
}
|
|
27
33
|
var nonRetryable = (message, type, details) => new Failure(message, { type, details, nonRetryable: true });
|
|
@@ -41,6 +47,14 @@ class FlowError extends Error {
|
|
|
41
47
|
}
|
|
42
48
|
}
|
|
43
49
|
var isFlowError = (e) => e instanceof FlowError || e instanceof Error && e.name === "FlowError" && typeof e.reason === "string";
|
|
50
|
+
function checkPriority(p) {
|
|
51
|
+
if (p === undefined)
|
|
52
|
+
return;
|
|
53
|
+
if (!Number.isInteger(p) || p < 1 || p > 5)
|
|
54
|
+
throw new Failure(`приоритет ${p}: нужно целое от 1 до 5`, { type: "InvalidPriority", nonRetryable: true });
|
|
55
|
+
return p;
|
|
56
|
+
}
|
|
57
|
+
var DEFAULT_PRIORITY = 3;
|
|
44
58
|
// src/exit.ts
|
|
45
59
|
function exitSignal() {
|
|
46
60
|
let d = Promise.withResolvers();
|
|
@@ -66,12 +80,15 @@ function exitSignal() {
|
|
|
66
80
|
};
|
|
67
81
|
}
|
|
68
82
|
export {
|
|
83
|
+
DEFAULT_PRIORITY,
|
|
69
84
|
Failure,
|
|
70
85
|
FlowError,
|
|
71
|
-
|
|
86
|
+
checkPriority,
|
|
72
87
|
exitSignal,
|
|
73
88
|
heartbeat,
|
|
74
89
|
isFlowError,
|
|
75
90
|
lastHeartbeat,
|
|
76
|
-
nonRetryable
|
|
91
|
+
nonRetryable,
|
|
92
|
+
stepAttempt,
|
|
93
|
+
stepScope
|
|
77
94
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface StepOptions {
|
|
|
21
21
|
nonRetryableTypes?: string[];
|
|
22
22
|
/** Пул шага из AppDefinition.pools — со своим пределом одновременных шагов. */
|
|
23
23
|
pool?: string;
|
|
24
|
+
/** Приоритет шага в очереди пула: 1 — первым, 5 — последним. По умолчанию — приоритет процесса. */
|
|
25
|
+
priority?: number;
|
|
24
26
|
}
|
|
25
27
|
export interface ChildOptions {
|
|
26
28
|
id: string;
|
|
@@ -44,6 +46,10 @@ export interface FlowContext {
|
|
|
44
46
|
readonly parentId: string | undefined;
|
|
45
47
|
/** Время запуска процесса: одно и то же при каждом переигрывании. */
|
|
46
48
|
readonly startedAt: Date;
|
|
49
|
+
/** Приоритет процесса из start: 1 — первым, 5 — последним; не задан — 3. */
|
|
50
|
+
readonly priority: number;
|
|
51
|
+
/** Текущее время, мс: одно и то же при переигрывании. Date.now() в процессе DBOS не детерминирован. */
|
|
52
|
+
now(): Promise<number>;
|
|
47
53
|
/** Шаг упал после повторов — FlowError reason "failed" или "timeout". */
|
|
48
54
|
step<T>(name: string, args: unknown[], opts?: StepOptions): Promise<T>;
|
|
49
55
|
/** Дочерний процесс в другом модуле: его очередь, его процесс, ждём результат. */
|
|
@@ -112,13 +118,22 @@ export interface EngineRuntime {
|
|
|
112
118
|
/** Цикл start…stop: завершается со stop; отклоняется при сбое start и (Temporal) падении воркера. */
|
|
113
119
|
done(): Promise<void>;
|
|
114
120
|
}
|
|
121
|
+
export interface StartOptions {
|
|
122
|
+
id: string;
|
|
123
|
+
/** Срок процесса. */
|
|
124
|
+
timeoutMs?: number;
|
|
125
|
+
/** 1 — первым, 5 — последним; шаги процесса наследуют его. */
|
|
126
|
+
priority?: number;
|
|
127
|
+
}
|
|
115
128
|
/** Сторона вызывающего вне процесса (HTTP, CLI, тест). */
|
|
116
129
|
export interface EngineClient {
|
|
117
130
|
/** Id уже идёт — handle к нему. timeoutMs — срок процесса. */
|
|
118
|
-
start<T>(target: FlowRef, args: unknown[], opts:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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>>;
|
|
122
137
|
/** Уже запущенный процесс по id. */
|
|
123
138
|
handle<T>(id: string): FlowHandle<T>;
|
|
124
139
|
close(): Promise<void>;
|
package/package.json
CHANGED
package/src/errors.ts
CHANGED
|
@@ -8,12 +8,23 @@ export class Failure extends Error {
|
|
|
8
8
|
readonly type: string;
|
|
9
9
|
readonly details?: unknown;
|
|
10
10
|
readonly nonRetryable: boolean;
|
|
11
|
-
|
|
11
|
+
/** Следующая попытка шага не раньше чем через столько мс (FLOOD_WAIT): вместо паузы из StepOptions. */
|
|
12
|
+
readonly retryAfterMs?: number;
|
|
13
|
+
/** Штатный отказ (флуд, тихие часы): Temporal не пишет его в журнал ошибкой (category BENIGN). */
|
|
14
|
+
readonly benign: boolean;
|
|
15
|
+
constructor(
|
|
16
|
+
message: string,
|
|
17
|
+
opts: { type: string; details?: unknown; nonRetryable?: boolean; retryAfterMs?: number; benign?: boolean },
|
|
18
|
+
) {
|
|
12
19
|
super(message);
|
|
13
20
|
this.name = "Failure";
|
|
14
21
|
this.type = opts.type;
|
|
15
22
|
this.details = opts.details;
|
|
16
23
|
this.nonRetryable = opts.nonRetryable ?? false;
|
|
24
|
+
this.benign = opts.benign ?? false;
|
|
25
|
+
// Неразобранный срок (NaN, отрицательный) — как не заданный: пауза из StepOptions.
|
|
26
|
+
const after = opts.retryAfterMs;
|
|
27
|
+
this.retryAfterMs = after !== undefined && Number.isFinite(after) && after >= 0 ? after : undefined;
|
|
17
28
|
}
|
|
18
29
|
}
|
|
19
30
|
|
|
@@ -53,3 +64,14 @@ export class FlowError extends Error {
|
|
|
53
64
|
/** FlowError по признакам, а не по классу: класс теряется при сериализации в DBOS. */
|
|
54
65
|
export const isFlowError = (e: unknown): e is FlowError =>
|
|
55
66
|
e instanceof FlowError || (e instanceof Error && e.name === "FlowError" && typeof (e as FlowError).reason === "string");
|
|
67
|
+
|
|
68
|
+
/** Приоритет порта — целое 1…5 (1 — первым); иное отвергается одинаково на обоих движках. */
|
|
69
|
+
export function checkPriority(p: number | undefined): number | undefined {
|
|
70
|
+
if (p === undefined) return undefined;
|
|
71
|
+
// Failure, а не Error: в процессе Temporal Error — вечный повтор задачи процесса, а не отказ.
|
|
72
|
+
if (!Number.isInteger(p) || p < 1 || p > 5)
|
|
73
|
+
throw new Failure(`приоритет ${p}: нужно целое от 1 до 5`, { type: "InvalidPriority", nonRetryable: true });
|
|
74
|
+
return p;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const DEFAULT_PRIORITY = 3;
|
package/src/heartbeat.ts
CHANGED
|
@@ -4,18 +4,30 @@
|
|
|
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
|
-
|
|
10
|
-
|
|
11
|
+
/** Область шага: какой рантайм у шага, что идёт сейчас. Самый внутренний шаг — свой, без угадывания движка. */
|
|
12
|
+
export interface StepScope {
|
|
13
|
+
run<T>(rt: StepRuntime, fn: () => T): T;
|
|
14
|
+
current(): StepRuntime | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const KEY = Symbol.for("@iworkflow/core.step-scope");
|
|
18
|
+
type Slot = { [KEY]?: StepScope };
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Одна область на процесс; её создаёт первый адаптер (AsyncLocalStorage) — core читает песочница
|
|
22
|
+
* Temporal, и node:async_hooks ему импортировать нельзя.
|
|
23
|
+
*/
|
|
24
|
+
export function stepScope(make: () => StepScope): StepScope {
|
|
25
|
+
return ((globalThis as Slot)[KEY] ??= make());
|
|
14
26
|
}
|
|
15
27
|
|
|
16
28
|
function current(): StepRuntime {
|
|
17
|
-
const rt = (globalThis as Slot)[KEY];
|
|
18
|
-
if (!rt) throw new Error("heartbeat вне шага:
|
|
29
|
+
const rt = (globalThis as Slot)[KEY]?.current();
|
|
30
|
+
if (!rt) throw new Error("heartbeat вне шага: шаг движка процесса сейчас не идёт");
|
|
19
31
|
return rt;
|
|
20
32
|
}
|
|
21
33
|
|
|
@@ -25,3 +37,6 @@ export const heartbeat = (details?: unknown): Promise<void> => current().heartbe
|
|
|
25
37
|
/** Прогресс прошлой попытки этого шага; undefined — первая попытка или прогресса не было. */
|
|
26
38
|
export const lastHeartbeat = async <T>(): Promise<T | undefined> =>
|
|
27
39
|
((await current().lastHeartbeat()) ?? undefined) as T | undefined;
|
|
40
|
+
|
|
41
|
+
/** Номер попытки этого шага, с 1. DBOS считает заново после рестарта процесса, Temporal — нет. */
|
|
42
|
+
export const stepAttempt = (): number => current().attempt();
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
|
-
export {
|
|
2
|
+
export { heartbeat, lastHeartbeat, stepAttempt, stepScope, type StepRuntime, type StepScope } from "./heartbeat";
|
|
3
3
|
export * from "./errors";
|
|
4
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
|
/** Дочерний процесс в другом модуле: его очередь, его процесс, ждём результат. */
|
|
@@ -127,10 +133,23 @@ export interface EngineRuntime {
|
|
|
127
133
|
done(): Promise<void>;
|
|
128
134
|
}
|
|
129
135
|
|
|
136
|
+
export interface StartOptions {
|
|
137
|
+
id: string;
|
|
138
|
+
/** Срок процесса. */
|
|
139
|
+
timeoutMs?: number;
|
|
140
|
+
/** 1 — первым, 5 — последним; шаги процесса наследуют его. */
|
|
141
|
+
priority?: number;
|
|
142
|
+
}
|
|
143
|
+
|
|
130
144
|
/** Сторона вызывающего вне процесса (HTTP, CLI, тест). */
|
|
131
145
|
export interface EngineClient {
|
|
132
146
|
/** Id уже идёт — handle к нему. timeoutMs — срок процесса. */
|
|
133
|
-
start<T>(target: FlowRef, args: unknown[], opts:
|
|
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>>;
|
|
134
153
|
/** Уже запущенный процесс по id. */
|
|
135
154
|
handle<T>(id: string): FlowHandle<T>;
|
|
136
155
|
close(): Promise<void>;
|