@iworkflow/temporal 0.6.0 → 0.7.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 +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +27 -6
- package/dist/workflow.js +3 -1
- package/package.json +3 -2
- package/src/index.ts +33 -6
- package/src/workflow.ts +4 -2
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ Temporal для `@iworkflow/core`. Peer: `@temporalio/{client,common,worker,work
|
|
|
11
11
|
- `temporalActivities(activities)` — шаги порта для своего `Worker.create` (слив старой очереди, тестовый
|
|
12
12
|
воркер): `Failure` → `ApplicationFailure`, работают `heartbeat` и `stepAttempt`. Шаги без неё
|
|
13
13
|
в своём воркере `heartbeat`/`stepAttempt` не видят — с 0.6.0 рантайм шага ставится на время шага.
|
|
14
|
+
- `temporalStep(fn)` — тест шага без воркера: `new MockActivityEnvironment().run(() => temporalStep(() => acts.send(x)))`;
|
|
15
|
+
`acts` — сырые шаги (не результат `temporalActivities`), отказ остаётся `Failure`; `@temporalio/testing` ставит сам модуль.
|
|
14
16
|
- `@iworkflow/temporal/workflow` — только для входа песочницы модуля:
|
|
15
17
|
`export const outreach = temporalFlow(flows.outreach, ["send-twice"])`.
|
|
16
18
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type Activity, type EngineClient, type EngineRuntime } from "@iworkflow/core";
|
|
2
2
|
import { type TemporalOptions } from "./connection";
|
|
3
3
|
export type { TemporalOptions };
|
|
4
|
+
/** Один вызов шага в рантайме Temporal без обёртки отказов — для тестов в MockActivityEnvironment. */
|
|
5
|
+
export declare const temporalStep: <T>(fn: () => T) => T;
|
|
4
6
|
/**
|
|
5
7
|
* Шаги порта для своего Worker.create (слив старой очереди, тестовый воркер): Failure доезжает
|
|
6
8
|
* как ApplicationFailure, heartbeat и stepAttempt работают. temporalRuntime делает это сам.
|
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,
|
|
@@ -142,13 +143,13 @@ var TEMPORAL_STEP = {
|
|
|
142
143
|
lastHeartbeat: async () => Context.current().info.heartbeatDetails,
|
|
143
144
|
attempt: () => Context.current().info.attempt
|
|
144
145
|
};
|
|
146
|
+
var temporalStep = (fn) => stepScope(alsScope).run(TEMPORAL_STEP, fn);
|
|
145
147
|
function temporalActivities(activities) {
|
|
146
|
-
const scope = stepScope(alsScope);
|
|
147
148
|
return Object.fromEntries(Object.entries(activities).map(([name, fn]) => [
|
|
148
149
|
name,
|
|
149
150
|
async (...args) => {
|
|
150
151
|
try {
|
|
151
|
-
return await
|
|
152
|
+
return await temporalStep(() => fn(...args));
|
|
152
153
|
} catch (e) {
|
|
153
154
|
throw toTemporal(e, false);
|
|
154
155
|
}
|
|
@@ -167,8 +168,14 @@ function temporalRuntime(workflowsPath, opts = {}) {
|
|
|
167
168
|
async start(def) {
|
|
168
169
|
if (starting)
|
|
169
170
|
throw new Error("воркер уже запущен");
|
|
170
|
-
starting = true;
|
|
171
171
|
exit.arm();
|
|
172
|
+
try {
|
|
173
|
+
checkLimits(def);
|
|
174
|
+
} catch (e) {
|
|
175
|
+
exit.reject(e);
|
|
176
|
+
throw e;
|
|
177
|
+
}
|
|
178
|
+
starting = true;
|
|
172
179
|
stopping = false;
|
|
173
180
|
let c;
|
|
174
181
|
try {
|
|
@@ -183,7 +190,15 @@ function temporalRuntime(workflowsPath, opts = {}) {
|
|
|
183
190
|
const activities = temporalActivities(def.activities);
|
|
184
191
|
const base = { connection: c, namespace: namespace(opts), ...opts.plugins ? { plugins: opts.plugins } : {} };
|
|
185
192
|
const code = opts.workflowBundlePath ? { workflowBundle: { codePath: opts.workflowBundlePath } } : { workflowsPath };
|
|
186
|
-
workers = [
|
|
193
|
+
workers = [
|
|
194
|
+
await Worker.create({
|
|
195
|
+
...base,
|
|
196
|
+
taskQueue: def.app,
|
|
197
|
+
...code,
|
|
198
|
+
activities,
|
|
199
|
+
...def.maxConcurrentSteps ? { maxConcurrentActivityTaskExecutions: def.maxConcurrentSteps } : {}
|
|
200
|
+
})
|
|
201
|
+
];
|
|
187
202
|
for (const [pool, limit] of Object.entries(def.pools ?? {}))
|
|
188
203
|
workers.push(await Worker.create({
|
|
189
204
|
...base,
|
|
@@ -267,7 +282,12 @@ async function temporalClient(opts = {}) {
|
|
|
267
282
|
return {
|
|
268
283
|
async start(target, args, o) {
|
|
269
284
|
await ready();
|
|
270
|
-
const h = await client.workflow.start(target.flow, {
|
|
285
|
+
const h = await client.workflow.start(target.flow, {
|
|
286
|
+
...startOptions(target, args, o),
|
|
287
|
+
...o.onExisting === "reject" ? { workflowIdConflictPolicy: "FAIL", workflowIdReusePolicy: "REJECT_DUPLICATE" } : { workflowIdConflictPolicy: "USE_EXISTING" }
|
|
288
|
+
}).catch((e) => {
|
|
289
|
+
throw fromTemporal(e);
|
|
290
|
+
});
|
|
271
291
|
return wrap(h, ready);
|
|
272
292
|
},
|
|
273
293
|
async signalWithStart(target, args, o, topic, value) {
|
|
@@ -286,5 +306,6 @@ async function temporalClient(opts = {}) {
|
|
|
286
306
|
export {
|
|
287
307
|
temporalActivities,
|
|
288
308
|
temporalClient,
|
|
289
|
-
temporalRuntime
|
|
309
|
+
temporalRuntime,
|
|
310
|
+
temporalStep
|
|
290
311
|
};
|
package/dist/workflow.js
CHANGED
|
@@ -126,8 +126,10 @@ function temporalFlow(flow, patches = []) {
|
|
|
126
126
|
step: (name, stepArgs, opts) => activitiesWith(opts)[name](...stepArgs).catch(rethrow),
|
|
127
127
|
child: (target, childArgs, opts) => executeChild(target.flow, childOptions(target.app, childArgs, opts)).catch(rethrow),
|
|
128
128
|
async startChild(target, childArgs, opts) {
|
|
129
|
+
const { reuseClosedId, ...rest } = opts;
|
|
129
130
|
const h = await startChild(target.flow, {
|
|
130
|
-
...childOptions(target.app, childArgs, { ...
|
|
131
|
+
...childOptions(target.app, childArgs, { ...rest, onExisting: "reject" }),
|
|
132
|
+
...reuseClosedId ? { workflowIdReusePolicy: "ALLOW_DUPLICATE" } : {},
|
|
131
133
|
parentClosePolicy: ParentClosePolicy.ABANDON
|
|
132
134
|
}).catch(rethrow);
|
|
133
135
|
return { id: opts.id, result: () => h.result().catch(rethrow) };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iworkflow/temporal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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.7.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@temporalio/activity": "^1.24.0",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"@temporalio/activity": "^1.24.0",
|
|
55
55
|
"@temporalio/client": "^1.24.0",
|
|
56
56
|
"@temporalio/common": "^1.24.0",
|
|
57
|
+
"@temporalio/testing": "^1.24.0",
|
|
57
58
|
"@temporalio/worker": "^1.24.0",
|
|
58
59
|
"@temporalio/workflow": "^1.24.0"
|
|
59
60
|
},
|
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 };
|
|
@@ -79,18 +80,21 @@ const TEMPORAL_STEP: StepRuntime = {
|
|
|
79
80
|
attempt: () => Context.current().info.attempt,
|
|
80
81
|
};
|
|
81
82
|
|
|
83
|
+
/** Один вызов шага в рантайме Temporal без обёртки отказов — для тестов в MockActivityEnvironment. */
|
|
84
|
+
// Область берётся при вызове, а не при импорте: пакет sideEffects: false.
|
|
85
|
+
export const temporalStep = <T>(fn: () => T): T => stepScope(alsScope).run(TEMPORAL_STEP, fn);
|
|
86
|
+
|
|
82
87
|
/**
|
|
83
88
|
* Шаги порта для своего Worker.create (слив старой очереди, тестовый воркер): Failure доезжает
|
|
84
89
|
* как ApplicationFailure, heartbeat и stepAttempt работают. temporalRuntime делает это сам.
|
|
85
90
|
*/
|
|
86
91
|
export function temporalActivities<T extends Record<string, Activity>>(activities: T): T {
|
|
87
|
-
const scope = stepScope(alsScope);
|
|
88
92
|
return Object.fromEntries(
|
|
89
93
|
Object.entries(activities).map(([name, fn]) => [
|
|
90
94
|
name,
|
|
91
95
|
async (...args: unknown[]) => {
|
|
92
96
|
try {
|
|
93
|
-
return await
|
|
97
|
+
return await temporalStep(() => fn(...args));
|
|
94
98
|
} catch (e) {
|
|
95
99
|
throw toTemporal(e, false);
|
|
96
100
|
}
|
|
@@ -117,8 +121,14 @@ export function temporalRuntime(
|
|
|
117
121
|
return {
|
|
118
122
|
async start(def: AppDefinition) {
|
|
119
123
|
if (starting) throw new Error("воркер уже запущен");
|
|
120
|
-
starting = true;
|
|
121
124
|
exit.arm();
|
|
125
|
+
try {
|
|
126
|
+
checkLimits(def);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
exit.reject(e);
|
|
129
|
+
throw e;
|
|
130
|
+
}
|
|
131
|
+
starting = true;
|
|
122
132
|
stopping = false;
|
|
123
133
|
let c: NativeConnection;
|
|
124
134
|
try {
|
|
@@ -134,7 +144,15 @@ export function temporalRuntime(
|
|
|
134
144
|
const activities = temporalActivities(def.activities);
|
|
135
145
|
const base = { connection: c, namespace: namespace(opts), ...(opts.plugins ? { plugins: opts.plugins } : {}) };
|
|
136
146
|
const code = opts.workflowBundlePath ? { workflowBundle: { codePath: opts.workflowBundlePath } } : { workflowsPath };
|
|
137
|
-
workers = [
|
|
147
|
+
workers = [
|
|
148
|
+
await Worker.create({
|
|
149
|
+
...base,
|
|
150
|
+
taskQueue: def.app,
|
|
151
|
+
...code,
|
|
152
|
+
activities,
|
|
153
|
+
...(def.maxConcurrentSteps ? { maxConcurrentActivityTaskExecutions: def.maxConcurrentSteps } : {}),
|
|
154
|
+
}),
|
|
155
|
+
];
|
|
138
156
|
// Пул — своя очередь с пределом одновременных шагов в этом процессе.
|
|
139
157
|
for (const [pool, limit] of Object.entries(def.pools ?? {}))
|
|
140
158
|
workers.push(
|
|
@@ -229,7 +247,16 @@ export async function temporalClient(opts: TemporalOptions & { lazy?: boolean }
|
|
|
229
247
|
return {
|
|
230
248
|
async start(target, args, o) {
|
|
231
249
|
await ready();
|
|
232
|
-
const h = await client.workflow
|
|
250
|
+
const h = await client.workflow
|
|
251
|
+
.start(target.flow, {
|
|
252
|
+
...startOptions(target, args, o),
|
|
253
|
+
...(o.onExisting === "reject"
|
|
254
|
+
? { workflowIdConflictPolicy: "FAIL" as const, workflowIdReusePolicy: "REJECT_DUPLICATE" as const }
|
|
255
|
+
: { workflowIdConflictPolicy: "USE_EXISTING" as const }),
|
|
256
|
+
})
|
|
257
|
+
.catch((e) => {
|
|
258
|
+
throw fromTemporal(e);
|
|
259
|
+
});
|
|
233
260
|
return wrap(h, ready);
|
|
234
261
|
},
|
|
235
262
|
async signalWithStart(target, args, o, topic, value) {
|
package/src/workflow.ts
CHANGED
|
@@ -94,9 +94,11 @@ export function temporalFlow(flow: Flow, patches: string[] = []) {
|
|
|
94
94
|
child: (target, childArgs, opts) =>
|
|
95
95
|
executeChild(target.flow, childOptions(target.app, childArgs, opts)).catch(rethrow) as Promise<never>,
|
|
96
96
|
async startChild(target, childArgs, opts) {
|
|
97
|
-
// REJECT_DUPLICATE: завершённый id тоже
|
|
97
|
+
// REJECT_DUPLICATE: завершённый id тоже занят, как у DBOS; reuseClosedId — ALLOW_DUPLICATE, новый запуск.
|
|
98
|
+
const { reuseClosedId, ...rest } = opts;
|
|
98
99
|
const h = await startChild(target.flow, {
|
|
99
|
-
...childOptions(target.app, childArgs, { ...
|
|
100
|
+
...childOptions(target.app, childArgs, { ...rest, onExisting: "reject" }),
|
|
101
|
+
...(reuseClosedId ? { workflowIdReusePolicy: "ALLOW_DUPLICATE" as const } : {}),
|
|
100
102
|
parentClosePolicy: ParentClosePolicy.ABANDON,
|
|
101
103
|
}).catch(rethrow);
|
|
102
104
|
return { id: opts.id, result: () => h.result().catch(rethrow) as Promise<never> };
|