@iworkflow/temporal 0.6.1 → 0.8.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/dist/index.js +35 -4
- package/dist/workflow.js +6 -2
- package/package.json +2 -2
- package/src/index.ts +39 -4
- package/src/workflow.ts +10 -3
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Context } from "@temporalio/activity";
|
|
|
4
4
|
import { NativeConnection, Worker } from "@temporalio/worker";
|
|
5
5
|
import {
|
|
6
6
|
stepScope,
|
|
7
|
+
checkLimits,
|
|
7
8
|
checkPriority,
|
|
8
9
|
DEFAULT_PRIORITY,
|
|
9
10
|
exitSignal,
|
|
@@ -140,7 +141,15 @@ var wrap = (h, ready = async () => {}) => ({
|
|
|
140
141
|
var TEMPORAL_STEP = {
|
|
141
142
|
heartbeat: async (details) => Context.current().heartbeat(details),
|
|
142
143
|
lastHeartbeat: async () => Context.current().info.heartbeatDetails,
|
|
143
|
-
attempt: () => Context.current().info.attempt
|
|
144
|
+
attempt: () => Context.current().info.attempt,
|
|
145
|
+
info: () => {
|
|
146
|
+
const { workflowExecution, activityId } = Context.current().info;
|
|
147
|
+
const { workflowId, runId } = workflowExecution ?? {};
|
|
148
|
+
if (!workflowId || !runId)
|
|
149
|
+
throw new Error("stepInfo: шаг вне процесса Temporal");
|
|
150
|
+
return { workflowId, runId, stepId: activityId };
|
|
151
|
+
},
|
|
152
|
+
signal: () => Context.current().cancellationSignal
|
|
144
153
|
};
|
|
145
154
|
var temporalStep = (fn) => stepScope(alsScope).run(TEMPORAL_STEP, fn);
|
|
146
155
|
function temporalActivities(activities) {
|
|
@@ -167,8 +176,14 @@ function temporalRuntime(workflowsPath, opts = {}) {
|
|
|
167
176
|
async start(def) {
|
|
168
177
|
if (starting)
|
|
169
178
|
throw new Error("воркер уже запущен");
|
|
170
|
-
starting = true;
|
|
171
179
|
exit.arm();
|
|
180
|
+
try {
|
|
181
|
+
checkLimits(def);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
exit.reject(e);
|
|
184
|
+
throw e;
|
|
185
|
+
}
|
|
186
|
+
starting = true;
|
|
172
187
|
stopping = false;
|
|
173
188
|
let c;
|
|
174
189
|
try {
|
|
@@ -183,7 +198,15 @@ function temporalRuntime(workflowsPath, opts = {}) {
|
|
|
183
198
|
const activities = temporalActivities(def.activities);
|
|
184
199
|
const base = { connection: c, namespace: namespace(opts), ...opts.plugins ? { plugins: opts.plugins } : {} };
|
|
185
200
|
const code = opts.workflowBundlePath ? { workflowBundle: { codePath: opts.workflowBundlePath } } : { workflowsPath };
|
|
186
|
-
workers = [
|
|
201
|
+
workers = [
|
|
202
|
+
await Worker.create({
|
|
203
|
+
...base,
|
|
204
|
+
taskQueue: def.app,
|
|
205
|
+
...code,
|
|
206
|
+
activities,
|
|
207
|
+
...def.maxConcurrentSteps ? { maxConcurrentActivityTaskExecutions: def.maxConcurrentSteps } : {}
|
|
208
|
+
})
|
|
209
|
+
];
|
|
187
210
|
for (const [pool, limit] of Object.entries(def.pools ?? {}))
|
|
188
211
|
workers.push(await Worker.create({
|
|
189
212
|
...base,
|
|
@@ -267,7 +290,15 @@ async function temporalClient(opts = {}) {
|
|
|
267
290
|
return {
|
|
268
291
|
async start(target, args, o) {
|
|
269
292
|
await ready();
|
|
270
|
-
const h = await client.workflow.start(target.flow, {
|
|
293
|
+
const h = await client.workflow.start(target.flow, {
|
|
294
|
+
...startOptions(target, args, o),
|
|
295
|
+
...o.onExisting === "reject" || o.reuseClosedId ? {
|
|
296
|
+
workflowIdConflictPolicy: "FAIL",
|
|
297
|
+
workflowIdReusePolicy: o.reuseClosedId ? "ALLOW_DUPLICATE" : "REJECT_DUPLICATE"
|
|
298
|
+
} : { workflowIdConflictPolicy: "USE_EXISTING" }
|
|
299
|
+
}).catch((e) => {
|
|
300
|
+
throw fromTemporal(e);
|
|
301
|
+
});
|
|
271
302
|
return wrap(h, ready);
|
|
272
303
|
},
|
|
273
304
|
async signalWithStart(target, args, o, topic, value) {
|
package/dist/workflow.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
deprecatePatch,
|
|
7
7
|
executeChild,
|
|
8
8
|
getExternalWorkflowHandle,
|
|
9
|
+
log,
|
|
9
10
|
ParentClosePolicy,
|
|
10
11
|
patched,
|
|
11
12
|
proxyActivities,
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
TemporalFailure,
|
|
16
17
|
workflowInfo
|
|
17
18
|
} from "@temporalio/workflow";
|
|
18
|
-
import { checkPriority, DEFAULT_PRIORITY, FlowError as FlowError2 } from "@iworkflow/core";
|
|
19
|
+
import { checkPriority, DEFAULT_PRIORITY, FlowError as FlowError2, plainAttrs } from "@iworkflow/core";
|
|
19
20
|
|
|
20
21
|
// src/failure.ts
|
|
21
22
|
import {
|
|
@@ -126,8 +127,10 @@ function temporalFlow(flow, patches = []) {
|
|
|
126
127
|
step: (name, stepArgs, opts) => activitiesWith(opts)[name](...stepArgs).catch(rethrow),
|
|
127
128
|
child: (target, childArgs, opts) => executeChild(target.flow, childOptions(target.app, childArgs, opts)).catch(rethrow),
|
|
128
129
|
async startChild(target, childArgs, opts) {
|
|
130
|
+
const { reuseClosedId, ...rest } = opts;
|
|
129
131
|
const h = await startChild(target.flow, {
|
|
130
|
-
...childOptions(target.app, childArgs, { ...
|
|
132
|
+
...childOptions(target.app, childArgs, { ...rest, onExisting: "reject" }),
|
|
133
|
+
...reuseClosedId ? { workflowIdReusePolicy: "ALLOW_DUPLICATE" } : {},
|
|
131
134
|
parentClosePolicy: ParentClosePolicy.ABANDON
|
|
132
135
|
}).catch(rethrow);
|
|
133
136
|
return { id: opts.id, result: () => h.result().catch(rethrow) };
|
|
@@ -142,6 +145,7 @@ function temporalFlow(flow, patches = []) {
|
|
|
142
145
|
throw e;
|
|
143
146
|
throw new FlowError2(`процесса ${workflowId} нет или он завершён`, { reason: "not-found" });
|
|
144
147
|
}),
|
|
148
|
+
log: async (level, message, attrs) => log[level](message, plainAttrs(attrs)),
|
|
145
149
|
async setState(key, value) {
|
|
146
150
|
state.set(key, value);
|
|
147
151
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iworkflow/temporal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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.8.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@temporalio/activity": "^1.24.0",
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { NativeConnection, Worker } from "@temporalio/worker";
|
|
|
5
5
|
import {
|
|
6
6
|
stepScope,
|
|
7
7
|
type StepRuntime,
|
|
8
|
+
checkLimits,
|
|
8
9
|
checkPriority,
|
|
9
10
|
DEFAULT_PRIORITY,
|
|
10
11
|
exitSignal,
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
type StartOptions,
|
|
20
21
|
} from "@iworkflow/core";
|
|
21
22
|
import { address, connectOptions, namespace, renewApiKey, type TemporalOptions } from "./connection";
|
|
22
|
-
import { cause, toTemporal } from "./failure";
|
|
23
|
+
import { cause, fromTemporal, toTemporal } from "./failure";
|
|
23
24
|
import { alsScope } from "./scope";
|
|
24
25
|
|
|
25
26
|
export type { TemporalOptions };
|
|
@@ -77,6 +78,14 @@ const TEMPORAL_STEP: StepRuntime = {
|
|
|
77
78
|
heartbeat: async (details) => Context.current().heartbeat(details),
|
|
78
79
|
lastHeartbeat: async () => Context.current().info.heartbeatDetails,
|
|
79
80
|
attempt: () => Context.current().info.attempt,
|
|
81
|
+
info: () => {
|
|
82
|
+
// Пустой id как ключ идемпотентности склеил бы записи разных процессов — отказ.
|
|
83
|
+
const { workflowExecution, activityId } = Context.current().info;
|
|
84
|
+
const { workflowId, runId } = workflowExecution ?? {};
|
|
85
|
+
if (!workflowId || !runId) throw new Error("stepInfo: шаг вне процесса Temporal");
|
|
86
|
+
return { workflowId, runId, stepId: activityId };
|
|
87
|
+
},
|
|
88
|
+
signal: () => Context.current().cancellationSignal,
|
|
80
89
|
};
|
|
81
90
|
|
|
82
91
|
/** Один вызов шага в рантайме Temporal без обёртки отказов — для тестов в MockActivityEnvironment. */
|
|
@@ -120,8 +129,14 @@ export function temporalRuntime(
|
|
|
120
129
|
return {
|
|
121
130
|
async start(def: AppDefinition) {
|
|
122
131
|
if (starting) throw new Error("воркер уже запущен");
|
|
123
|
-
starting = true;
|
|
124
132
|
exit.arm();
|
|
133
|
+
try {
|
|
134
|
+
checkLimits(def);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
exit.reject(e);
|
|
137
|
+
throw e;
|
|
138
|
+
}
|
|
139
|
+
starting = true;
|
|
125
140
|
stopping = false;
|
|
126
141
|
let c: NativeConnection;
|
|
127
142
|
try {
|
|
@@ -137,7 +152,15 @@ export function temporalRuntime(
|
|
|
137
152
|
const activities = temporalActivities(def.activities);
|
|
138
153
|
const base = { connection: c, namespace: namespace(opts), ...(opts.plugins ? { plugins: opts.plugins } : {}) };
|
|
139
154
|
const code = opts.workflowBundlePath ? { workflowBundle: { codePath: opts.workflowBundlePath } } : { workflowsPath };
|
|
140
|
-
workers = [
|
|
155
|
+
workers = [
|
|
156
|
+
await Worker.create({
|
|
157
|
+
...base,
|
|
158
|
+
taskQueue: def.app,
|
|
159
|
+
...code,
|
|
160
|
+
activities,
|
|
161
|
+
...(def.maxConcurrentSteps ? { maxConcurrentActivityTaskExecutions: def.maxConcurrentSteps } : {}),
|
|
162
|
+
}),
|
|
163
|
+
];
|
|
141
164
|
// Пул — своя очередь с пределом одновременных шагов в этом процессе.
|
|
142
165
|
for (const [pool, limit] of Object.entries(def.pools ?? {}))
|
|
143
166
|
workers.push(
|
|
@@ -232,7 +255,19 @@ export async function temporalClient(opts: TemporalOptions & { lazy?: boolean }
|
|
|
232
255
|
return {
|
|
233
256
|
async start(target, args, o) {
|
|
234
257
|
await ready();
|
|
235
|
-
const h = await client.workflow
|
|
258
|
+
const h = await client.workflow
|
|
259
|
+
.start(target.flow, {
|
|
260
|
+
...startOptions(target, args, o),
|
|
261
|
+
...(o.onExisting === "reject" || o.reuseClosedId
|
|
262
|
+
? {
|
|
263
|
+
workflowIdConflictPolicy: "FAIL" as const,
|
|
264
|
+
workflowIdReusePolicy: o.reuseClosedId ? ("ALLOW_DUPLICATE" as const) : ("REJECT_DUPLICATE" as const),
|
|
265
|
+
}
|
|
266
|
+
: { workflowIdConflictPolicy: "USE_EXISTING" as const }),
|
|
267
|
+
})
|
|
268
|
+
.catch((e) => {
|
|
269
|
+
throw fromTemporal(e);
|
|
270
|
+
});
|
|
236
271
|
return wrap(h, ready);
|
|
237
272
|
},
|
|
238
273
|
async signalWithStart(target, args, o, topic, value) {
|
package/src/workflow.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
deprecatePatch,
|
|
9
9
|
executeChild,
|
|
10
10
|
getExternalWorkflowHandle,
|
|
11
|
+
log,
|
|
11
12
|
ParentClosePolicy,
|
|
12
13
|
patched,
|
|
13
14
|
proxyActivities,
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
workflowInfo,
|
|
19
20
|
} from "@temporalio/workflow";
|
|
20
21
|
|
|
21
|
-
import { checkPriority, DEFAULT_PRIORITY, FlowError, type ChildOptions, type Flow, type FlowContext, type StepOptions } from "@iworkflow/core";
|
|
22
|
+
import { checkPriority, DEFAULT_PRIORITY, FlowError, plainAttrs, type ChildOptions, type Flow, type FlowContext, type StepOptions } from "@iworkflow/core";
|
|
22
23
|
import { fromTemporal, toTemporal } from "./failure";
|
|
23
24
|
|
|
24
25
|
export const SIGNAL = "iwf.signal";
|
|
@@ -94,15 +95,18 @@ export function temporalFlow(flow: Flow, patches: string[] = []) {
|
|
|
94
95
|
child: (target, childArgs, opts) =>
|
|
95
96
|
executeChild(target.flow, childOptions(target.app, childArgs, opts)).catch(rethrow) as Promise<never>,
|
|
96
97
|
async startChild(target, childArgs, opts) {
|
|
97
|
-
// REJECT_DUPLICATE: завершённый id тоже
|
|
98
|
+
// REJECT_DUPLICATE: завершённый id тоже занят, как у DBOS; reuseClosedId — ALLOW_DUPLICATE, новый запуск.
|
|
99
|
+
const { reuseClosedId, ...rest } = opts;
|
|
98
100
|
const h = await startChild(target.flow, {
|
|
99
|
-
...childOptions(target.app, childArgs, { ...
|
|
101
|
+
...childOptions(target.app, childArgs, { ...rest, onExisting: "reject" }),
|
|
102
|
+
...(reuseClosedId ? { workflowIdReusePolicy: "ALLOW_DUPLICATE" as const } : {}),
|
|
100
103
|
parentClosePolicy: ParentClosePolicy.ABANDON,
|
|
101
104
|
}).catch(rethrow);
|
|
102
105
|
return { id: opts.id, result: () => h.result().catch(rethrow) as Promise<never> };
|
|
103
106
|
},
|
|
104
107
|
sleep: (ms) => sleep(ms),
|
|
105
108
|
async recv<T>(topic: string, timeoutMs: number) {
|
|
109
|
+
// 0 и меньше — таймер 1 мс (так было и до 0.8): отдаёт активацию, цикл опроса не виснет.
|
|
106
110
|
const ready = await condition(() => (inbox.get(topic)?.length ?? 0) > 0, timeoutMs);
|
|
107
111
|
return ready ? (inbox.get(topic)!.shift() as T) : null;
|
|
108
112
|
},
|
|
@@ -113,6 +117,9 @@ export function temporalFlow(flow: Flow, patches: string[] = []) {
|
|
|
113
117
|
if (!(e instanceof TemporalFailure)) throw e;
|
|
114
118
|
throw new FlowError(`процесса ${workflowId} нет или он завершён`, { reason: "not-found" });
|
|
115
119
|
}),
|
|
120
|
+
// log песочницы молчит при переигрывании.
|
|
121
|
+
// Неклонируемые attrs (функция, BigInt) уронили бы активацию при отправке из песочницы.
|
|
122
|
+
log: async (level, message, attrs) => log[level](message, plainAttrs(attrs)),
|
|
116
123
|
async setState(key, value) {
|
|
117
124
|
state.set(key, value);
|
|
118
125
|
},
|