@iworkflow/temporal 0.8.0 → 0.9.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
@@ -14,7 +14,10 @@ Temporal для `@iworkflow/core`. Peer: `@temporalio/{client,common,worker,work
14
14
  - `temporalStep(fn)` — тест шага без воркера: `new MockActivityEnvironment().run(() => temporalStep(() => acts.send(x)))`;
15
15
  `acts` — сырые шаги (не результат `temporalActivities`), отказ остаётся `Failure`; `@temporalio/testing` ставит сам модуль.
16
16
  - `@iworkflow/temporal/workflow` — только для входа песочницы модуля:
17
- `export const outreach = temporalFlow(flows.outreach, ["send-twice"])`.
17
+ `export const outreach = temporalFlow(flows.outreach, ["send-twice"])`. Внешний контракт по именам Temporal (соседи
18
+ шлют сигнал и query без порта): `temporalFlow(flow, { patches, signals: ["interviewFinished"], queries: ["link"] })`.
19
+ Новое имя в `signals` у процесса, который уже идёт, меняет переигрывание (сигнал, лежавший без обработчика, дойдёт до recv) —
20
+ добавлять вместе с первым кодом процесса или когда идущих нет.
18
21
 
19
22
  `opts` (`TemporalOptions`): `address`, `namespace`, `tls`, `apiKey` — источник токена, меняется каждые `apiKeyRenewMs`;
20
23
  `plugins` — плагины SDK воркерам и клиенту; `temporalClient({ lazy: true })` — соединение при первом вызове.
@@ -3,5 +3,15 @@ export declare const SIGNAL = "iwf.signal";
3
3
  export declare const QUERY = "iwf.query";
4
4
  /** Очередь пула шагов модуля; воркер пула поднимает temporalRuntime. */
5
5
  export declare const poolQueue: (app: string, pool: string) => string;
6
- /** patches — как AppDefinition.patches: необъявленный id — ошибка, как у DBOS. */
7
- export declare function temporalFlow(flow: Flow, patches?: string[]): (...args: unknown[]) => Promise<unknown>;
6
+ export interface TemporalFlowOptions {
7
+ /** Как AppDefinition.patches: необъявленный id — ошибка, как у DBOS. */
8
+ patches?: string[];
9
+ /** Сигналы Temporal по имени (внешний контракт): тема X, значение recv — signalValue(аргументы). */
10
+ signals?: string[];
11
+ /** Query Temporal по имени: query X — setState(X); не публиковалось — null. */
12
+ queries?: string[];
13
+ }
14
+ /** Аргументы именованного сигнала — значение recv: нет или null — true (не спутать с null срока), несколько — массив. */
15
+ export declare const signalValue: (a: unknown[]) => unknown;
16
+ /** Второй аргумент — patches списком или TemporalFlowOptions. */
17
+ export declare function temporalFlow(flow: Flow, opts?: string[] | TemporalFlowOptions): (...args: unknown[]) => Promise<unknown>;
package/dist/workflow.js CHANGED
@@ -107,16 +107,34 @@ var childOptions = (app, args, opts) => ({
107
107
  var rethrow = (e) => {
108
108
  throw fromTemporal(e);
109
109
  };
110
- function temporalFlow(flow, patches = []) {
110
+ var checkName = (name, seen) => {
111
+ if (!name.trim() || name !== name.trim() || name.startsWith("iwf.") || name.startsWith("__") || seen.has(name))
112
+ throw new Error(`имя "${name}" пустое, повторено или занято портом/Temporal`);
113
+ seen.add(name);
114
+ return name;
115
+ };
116
+ var signalValue = (a) => a.length === 0 || a.length === 1 && a[0] == null ? true : a.length === 1 ? a[0] : a;
117
+ function temporalFlow(flow, opts = []) {
118
+ const { patches = [], signals = [], queries = [] } = Array.isArray(opts) ? { patches: opts } : opts;
119
+ const [sigSeen, qSeen] = [new Set, new Set];
120
+ const named = {
121
+ signals: signals.map((n) => [n, defineSignal(checkName(n, sigSeen))]),
122
+ queries: queries.map((n) => [n, defineQuery(checkName(n, qSeen))])
123
+ };
111
124
  return async (...args) => {
112
125
  const inbox = new Map;
113
126
  const state = new Map;
114
- setHandler(signalDef, ({ topic, value }) => {
127
+ const push = (topic, value) => {
115
128
  const q = inbox.get(topic) ?? [];
116
129
  q.push(value);
117
130
  inbox.set(topic, q);
118
- });
131
+ };
132
+ setHandler(signalDef, ({ topic, value }) => push(topic, value));
119
133
  setHandler(queryDef, (key) => state.get(key));
134
+ for (const [name, def] of named.signals)
135
+ setHandler(def, (...a) => push(name, signalValue(a)));
136
+ for (const [name, def] of named.queries)
137
+ setHandler(def, () => state.get(name) ?? null);
120
138
  const info = workflowInfo();
121
139
  const ctx = {
122
140
  workflowId: info.workflowId,
@@ -167,5 +185,6 @@ export {
167
185
  QUERY,
168
186
  SIGNAL,
169
187
  poolQueue,
188
+ signalValue,
170
189
  temporalFlow
171
190
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iworkflow/temporal",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Temporal adapter for @iworkflow/core",
5
5
  "keywords": [
6
6
  "workflow",
@@ -41,7 +41,7 @@
41
41
  "prepack": "bun run build"
42
42
  },
43
43
  "dependencies": {
44
- "@iworkflow/core": "^0.8.0"
44
+ "@iworkflow/core": "^0.9.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@temporalio/activity": "^1.24.0",
package/src/workflow.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // Обёртка процесса для песочницы Temporal: FlowContext поверх proxyActivities и executeChild.
2
- // Сигналы и query — по одному обработчику на всё: тема и ключ едут в данных.
2
+ // Сигналы и query порта — по одному обработчику (тема и ключ в данных); внешний контракт — по именам (signals/queries).
3
3
  // Импортируется только из входа песочницы модуля: `@iworkflow/temporal/workflow`.
4
4
  import {
5
5
  condition,
@@ -70,18 +70,49 @@ const rethrow = (e: unknown): never => {
70
70
  throw fromTemporal(e);
71
71
  };
72
72
 
73
- /** patches — как AppDefinition.patches: необъявленный id — ошибка, как у DBOS. */
74
- export function temporalFlow(flow: Flow, patches: string[] = []) {
73
+ export interface TemporalFlowOptions {
74
+ /** Как AppDefinition.patches: необъявленный id — ошибка, как у DBOS. */
75
+ patches?: string[];
76
+ /** Сигналы Temporal по имени (внешний контракт): тема X, значение recv — signalValue(аргументы). */
77
+ signals?: string[];
78
+ /** Query Temporal по имени: query X — setState(X); не публиковалось — null. */
79
+ queries?: string[];
80
+ }
81
+
82
+ /** iwf.* — имена порта, __* — служебные Temporal: заняли бы чужой обработчик — отказ при объявлении. */
83
+ const checkName = (name: string, seen: Set<string>) => {
84
+ if (!name.trim() || name !== name.trim() || name.startsWith("iwf.") || name.startsWith("__") || seen.has(name))
85
+ throw new Error(`имя "${name}" пустое, повторено или занято портом/Temporal`);
86
+ seen.add(name);
87
+ return name;
88
+ };
89
+
90
+ /** Аргументы именованного сигнала — значение recv: нет или null — true (не спутать с null срока), несколько — массив. */
91
+ export const signalValue = (a: unknown[]): unknown => (a.length === 0 || (a.length === 1 && a[0] == null) ? true : a.length === 1 ? a[0] : a);
92
+
93
+ /** Второй аргумент — patches списком или TemporalFlowOptions. */
94
+ export function temporalFlow(flow: Flow, opts: string[] | TemporalFlowOptions = []) {
95
+ const { patches = [], signals = [], queries = [] } = Array.isArray(opts) ? { patches: opts } : opts;
96
+ // Имена сигналов и query — разные пространства Temporal: повтор ищется в каждом своём.
97
+ const [sigSeen, qSeen] = [new Set<string>(), new Set<string>()];
98
+ const named = {
99
+ signals: signals.map((n) => [n, defineSignal<unknown[]>(checkName(n, sigSeen))] as const),
100
+ queries: queries.map((n) => [n, defineQuery<unknown>(checkName(n, qSeen))] as const),
101
+ };
75
102
  return async (...args: unknown[]) => {
76
103
  // Буфер по темам: сигнал до recv ждёт в очереди, как в DBOS.
77
104
  const inbox = new Map<string, unknown[]>();
78
105
  const state = new Map<string, unknown>();
79
- setHandler(signalDef, ({ topic, value }) => {
106
+ const push = (topic: string, value: unknown) => {
80
107
  const q = inbox.get(topic) ?? [];
81
108
  q.push(value);
82
109
  inbox.set(topic, q);
83
- });
110
+ };
111
+ setHandler(signalDef, ({ topic, value }) => push(topic, value));
84
112
  setHandler(queryDef, (key) => state.get(key));
113
+ for (const [name, def] of named.signals)
114
+ setHandler(def, (...a: unknown[]) => push(name, signalValue(a)));
115
+ for (const [name, def] of named.queries) setHandler(def, () => state.get(name) ?? null);
85
116
 
86
117
  const info = workflowInfo();
87
118
  const ctx: FlowContext = {