@iworkflow/core 0.5.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 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`,
@@ -36,6 +38,6 @@ try {
36
38
 
37
39
  Приоритет: `client.start(..., { priority })` (1 — первым, 5 — последним) → `ctx.priority`; шаги и
38
40
  дети его наследуют, `StepOptions.priority` — свой. Срок повтора из отказа: `new Failure(msg, { type,
39
- retryAfterMs })`. Номер попытки в шаге — `stepAttempt()`. Время в процессе — `await ctx.now()`.
41
+ retryAfterMs })`; `benign: true` — штатный отказ, Temporal не пишет его ошибкой. Номер попытки в шаге — `stepAttempt()`. Время в процессе — `await ctx.now()`.
40
42
  `client.signalWithStart(target, args, opts, topic, value)` — сигнал идущему или запуск с сигналом.
41
43
  Контракт обоих движков — `lab/test/contract.test.ts` репозитория.
package/dist/errors.d.ts CHANGED
@@ -8,11 +8,14 @@ export declare class Failure extends Error {
8
8
  readonly nonRetryable: boolean;
9
9
  /** Следующая попытка шага не раньше чем через столько мс (FLOOD_WAIT): вместо паузы из StepOptions. */
10
10
  readonly retryAfterMs?: number;
11
+ /** Штатный отказ (флуд, тихие часы): Temporal не пишет его в журнал ошибкой (category BENIGN). */
12
+ readonly benign: boolean;
11
13
  constructor(message: string, opts: {
12
14
  type: string;
13
15
  details?: unknown;
14
16
  nonRetryable?: boolean;
15
17
  retryAfterMs?: number;
18
+ benign?: boolean;
16
19
  });
17
20
  }
18
21
  /** Отказ без повтора: `throw nonRetryable("нет такой фазы", "UnknownPhase")`. */
@@ -4,7 +4,16 @@ export interface StepRuntime {
4
4
  /** Номер попытки шага, с 1. */
5
5
  attempt(): number;
6
6
  }
7
- export declare function bindStepRuntime(rt: StepRuntime): void;
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;
8
17
  /** Отметить, что шаг жив, и сохранить прогресс. */
9
18
  export declare const heartbeat: (details?: unknown) => Promise<void>;
10
19
  /** Прогресс прошлой попытки этого шага; undefined — первая попытка или прогресса не было. */
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from "./types";
2
- export { bindStepRuntime, heartbeat, lastHeartbeat, stepAttempt, type StepRuntime } from "./heartbeat";
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,12 +1,12 @@
1
1
  // src/heartbeat.ts
2
- var KEY = Symbol.for("@iworkflow/core.step-runtime");
3
- function bindStepRuntime(rt) {
4
- globalThis[KEY] = rt;
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);
@@ -18,12 +18,14 @@ class Failure extends Error {
18
18
  details;
19
19
  nonRetryable;
20
20
  retryAfterMs;
21
+ benign;
21
22
  constructor(message, opts) {
22
23
  super(message);
23
24
  this.name = "Failure";
24
25
  this.type = opts.type;
25
26
  this.details = opts.details;
26
27
  this.nonRetryable = opts.nonRetryable ?? false;
28
+ this.benign = opts.benign ?? false;
27
29
  const after = opts.retryAfterMs;
28
30
  this.retryAfterMs = after !== undefined && Number.isFinite(after) && after >= 0 ? after : undefined;
29
31
  }
@@ -81,12 +83,12 @@ export {
81
83
  DEFAULT_PRIORITY,
82
84
  Failure,
83
85
  FlowError,
84
- bindStepRuntime,
85
86
  checkPriority,
86
87
  exitSignal,
87
88
  heartbeat,
88
89
  isFlowError,
89
90
  lastHeartbeat,
90
91
  nonRetryable,
91
- stepAttempt
92
+ stepAttempt,
93
+ stepScope
92
94
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iworkflow/core",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Engine-neutral durable workflow port: flows, steps, child workflows, signals, queries, patches, heartbeat",
5
5
  "keywords": [
6
6
  "workflow",
package/src/errors.ts CHANGED
@@ -10,15 +10,18 @@ export class Failure extends Error {
10
10
  readonly nonRetryable: boolean;
11
11
  /** Следующая попытка шага не раньше чем через столько мс (FLOOD_WAIT): вместо паузы из StepOptions. */
12
12
  readonly retryAfterMs?: number;
13
+ /** Штатный отказ (флуд, тихие часы): Temporal не пишет его в журнал ошибкой (category BENIGN). */
14
+ readonly benign: boolean;
13
15
  constructor(
14
16
  message: string,
15
- opts: { type: string; details?: unknown; nonRetryable?: boolean; retryAfterMs?: number },
17
+ opts: { type: string; details?: unknown; nonRetryable?: boolean; retryAfterMs?: number; benign?: boolean },
16
18
  ) {
17
19
  super(message);
18
20
  this.name = "Failure";
19
21
  this.type = opts.type;
20
22
  this.details = opts.details;
21
23
  this.nonRetryable = opts.nonRetryable ?? false;
24
+ this.benign = opts.benign ?? false;
22
25
  // Неразобранный срок (NaN, отрицательный) — как не заданный: пауза из StepOptions.
23
26
  const after = opts.retryAfterMs;
24
27
  this.retryAfterMs = after !== undefined && Number.isFinite(after) && after >= 0 ? after : undefined;
package/src/heartbeat.ts CHANGED
@@ -8,16 +8,26 @@ export interface StepRuntime {
8
8
  attempt(): number;
9
9
  }
10
10
 
11
- const KEY = Symbol.for("@iworkflow/core.step-runtime");
12
- type Slot = { [KEY]?: StepRuntime };
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 };
13
19
 
14
- export function bindStepRuntime(rt: StepRuntime): void {
15
- (globalThis as Slot)[KEY] = rt;
20
+ /**
21
+ * Одна область на процесс; её создаёт первый адаптер (AsyncLocalStorage) — core читает песочница
22
+ * Temporal, и node:async_hooks ему импортировать нельзя.
23
+ */
24
+ export function stepScope(make: () => StepScope): StepScope {
25
+ return ((globalThis as Slot)[KEY] ??= make());
16
26
  }
17
27
 
18
28
  function current(): StepRuntime {
19
- const rt = (globalThis as Slot)[KEY];
20
- if (!rt) throw new Error("heartbeat вне шага: движок процесса не запущен в этом процессе");
29
+ const rt = (globalThis as Slot)[KEY]?.current();
30
+ if (!rt) throw new Error("heartbeat вне шага: шаг движка процесса сейчас не идёт");
21
31
  return rt;
22
32
  }
23
33
 
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from "./types";
2
- export { bindStepRuntime, heartbeat, lastHeartbeat, stepAttempt, type StepRuntime } from "./heartbeat";
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";