@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 +4 -1
- package/dist/workflow.d.ts +12 -2
- package/dist/workflow.js +22 -3
- package/package.json +2 -2
- package/src/workflow.ts +36 -5
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 })` — соединение при первом вызове.
|
package/dist/workflow.d.ts
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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 = {
|