@aikirun/workflow 0.6.0 → 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/README.md +29 -24
- package/dist/index.d.ts +188 -77
- package/dist/index.js +596 -198
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,15 +3,15 @@ function workflowRegistry() {
|
|
|
3
3
|
return new WorkflowRegistryImpl();
|
|
4
4
|
}
|
|
5
5
|
var WorkflowRegistryImpl = class {
|
|
6
|
-
|
|
6
|
+
workflowsByName = /* @__PURE__ */ new Map();
|
|
7
7
|
add(workflow2) {
|
|
8
|
-
const workflows = this.
|
|
8
|
+
const workflows = this.workflowsByName.get(workflow2.name);
|
|
9
9
|
if (!workflows) {
|
|
10
|
-
this.
|
|
10
|
+
this.workflowsByName.set(workflow2.name, /* @__PURE__ */ new Map([[workflow2.versionId, workflow2]]));
|
|
11
11
|
return this;
|
|
12
12
|
}
|
|
13
13
|
if (workflows.has(workflow2.versionId)) {
|
|
14
|
-
throw new Error(`Workflow "${workflow2.
|
|
14
|
+
throw new Error(`Workflow "${workflow2.name}/${workflow2.versionId}" is already registered`);
|
|
15
15
|
}
|
|
16
16
|
workflows.set(workflow2.versionId, workflow2);
|
|
17
17
|
return this;
|
|
@@ -23,7 +23,7 @@ var WorkflowRegistryImpl = class {
|
|
|
23
23
|
return this;
|
|
24
24
|
}
|
|
25
25
|
remove(workflow2) {
|
|
26
|
-
const workflowVersinos = this.
|
|
26
|
+
const workflowVersinos = this.workflowsByName.get(workflow2.name);
|
|
27
27
|
if (workflowVersinos) {
|
|
28
28
|
workflowVersinos.delete(workflow2.versionId);
|
|
29
29
|
}
|
|
@@ -36,20 +36,20 @@ var WorkflowRegistryImpl = class {
|
|
|
36
36
|
return this;
|
|
37
37
|
}
|
|
38
38
|
removeAll() {
|
|
39
|
-
this.
|
|
39
|
+
this.workflowsByName.clear();
|
|
40
40
|
return this;
|
|
41
41
|
}
|
|
42
42
|
getAll() {
|
|
43
43
|
const workflows = [];
|
|
44
|
-
for (const workflowVersions of this.
|
|
44
|
+
for (const workflowVersions of this.workflowsByName.values()) {
|
|
45
45
|
for (const workflow2 of workflowVersions.values()) {
|
|
46
46
|
workflows.push(workflow2);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
return workflows;
|
|
50
50
|
}
|
|
51
|
-
get(
|
|
52
|
-
return this.
|
|
51
|
+
get(name, versionId) {
|
|
52
|
+
return this.workflowsByName.get(name)?.get(versionId);
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
|
|
@@ -77,6 +77,42 @@ function delay(ms, options) {
|
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// ../../lib/json/stable-stringify.ts
|
|
81
|
+
function stableStringify(value) {
|
|
82
|
+
return stringifyValue(value);
|
|
83
|
+
}
|
|
84
|
+
function stringifyValue(value) {
|
|
85
|
+
if (value === null || value === void 0) {
|
|
86
|
+
return "null";
|
|
87
|
+
}
|
|
88
|
+
if (typeof value !== "object") {
|
|
89
|
+
return JSON.stringify(value);
|
|
90
|
+
}
|
|
91
|
+
if (Array.isArray(value)) {
|
|
92
|
+
return `[${value.map(stringifyValue).join(",")}]`;
|
|
93
|
+
}
|
|
94
|
+
const keys = Object.keys(value).sort();
|
|
95
|
+
const pairs = [];
|
|
96
|
+
for (const key of keys) {
|
|
97
|
+
const keyValue = value[key];
|
|
98
|
+
if (keyValue !== void 0) {
|
|
99
|
+
pairs.push(`${JSON.stringify(key)}:${stringifyValue(keyValue)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return `{${pairs.join(",")}}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ../../lib/crypto/hash.ts
|
|
106
|
+
async function sha256(input) {
|
|
107
|
+
const data = new TextEncoder().encode(input);
|
|
108
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
109
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
110
|
+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
111
|
+
}
|
|
112
|
+
async function hashInput(input) {
|
|
113
|
+
return sha256(stableStringify({ input }));
|
|
114
|
+
}
|
|
115
|
+
|
|
80
116
|
// ../../lib/duration/convert.ts
|
|
81
117
|
var MS_PER_SECOND = 1e3;
|
|
82
118
|
var MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
@@ -254,6 +290,7 @@ function getRetryParams(attempts, strategy) {
|
|
|
254
290
|
// run/event.ts
|
|
255
291
|
import { INTERNAL } from "@aikirun/types/symbols";
|
|
256
292
|
import {
|
|
293
|
+
WorkflowRunConflictError,
|
|
257
294
|
WorkflowRunFailedError,
|
|
258
295
|
WorkflowRunSuspendedError
|
|
259
296
|
} from "@aikirun/types/workflow-run";
|
|
@@ -265,23 +302,24 @@ function event(params) {
|
|
|
265
302
|
}
|
|
266
303
|
function createEventWaiters(handle, eventsDefinition, logger) {
|
|
267
304
|
const waiters = {};
|
|
268
|
-
for (const [
|
|
305
|
+
for (const [eventName, eventDefinition] of Object.entries(eventsDefinition)) {
|
|
269
306
|
const waiter = createEventWaiter(
|
|
270
307
|
handle,
|
|
271
|
-
|
|
308
|
+
eventName,
|
|
272
309
|
eventDefinition.schema,
|
|
273
|
-
logger.child({ "aiki.
|
|
310
|
+
logger.child({ "aiki.eventName": eventName })
|
|
274
311
|
);
|
|
275
|
-
waiters[
|
|
312
|
+
waiters[eventName] = waiter;
|
|
276
313
|
}
|
|
277
314
|
return waiters;
|
|
278
315
|
}
|
|
279
|
-
function createEventWaiter(handle,
|
|
316
|
+
function createEventWaiter(handle, eventName, schema, logger) {
|
|
280
317
|
let nextEventIndex = 0;
|
|
281
318
|
async function wait(options) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
319
|
+
await handle.refresh();
|
|
320
|
+
const events = handle.run.eventsQueue[eventName]?.events ?? [];
|
|
321
|
+
const event2 = events[nextEventIndex];
|
|
322
|
+
if (event2) {
|
|
285
323
|
nextEventIndex++;
|
|
286
324
|
if (event2.status === "timeout") {
|
|
287
325
|
logger.debug("Timed out waiting for event");
|
|
@@ -292,11 +330,10 @@ function createEventWaiter(handle, eventId, schema, logger) {
|
|
|
292
330
|
data = schema ? schema.parse(event2.data) : event2.data;
|
|
293
331
|
} catch (error) {
|
|
294
332
|
logger.error("Invalid event data", { data: event2.data, error });
|
|
295
|
-
const serializableError = createSerializableError(error);
|
|
296
333
|
await handle[INTERNAL].transitionState({
|
|
297
334
|
status: "failed",
|
|
298
335
|
cause: "self",
|
|
299
|
-
error:
|
|
336
|
+
error: createSerializableError(error)
|
|
300
337
|
});
|
|
301
338
|
throw new WorkflowRunFailedError(handle.run.id, handle.run.attempts);
|
|
302
339
|
}
|
|
@@ -304,81 +341,160 @@ function createEventWaiter(handle, eventId, schema, logger) {
|
|
|
304
341
|
return { timeout: false, data };
|
|
305
342
|
}
|
|
306
343
|
const timeoutInMs = options?.timeout && toMilliseconds(options.timeout);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
344
|
+
try {
|
|
345
|
+
await handle[INTERNAL].transitionState({
|
|
346
|
+
status: "awaiting_event",
|
|
347
|
+
eventName,
|
|
348
|
+
timeoutInMs
|
|
349
|
+
});
|
|
350
|
+
logger.info("Waiting for event", {
|
|
351
|
+
...timeoutInMs !== void 0 ? { "aiki.timeoutInMs": timeoutInMs } : {}
|
|
352
|
+
});
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (error instanceof WorkflowRunConflictError) {
|
|
355
|
+
throw new WorkflowRunSuspendedError(handle.run.id);
|
|
356
|
+
}
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
315
359
|
throw new WorkflowRunSuspendedError(handle.run.id);
|
|
316
360
|
}
|
|
317
361
|
return { wait };
|
|
318
362
|
}
|
|
319
363
|
function createEventSenders(api, workflowRunId, eventsDefinition, logger, onSend) {
|
|
320
364
|
const senders = {};
|
|
321
|
-
for (const [
|
|
365
|
+
for (const [eventName, eventDefinition] of Object.entries(eventsDefinition)) {
|
|
322
366
|
const sender = createEventSender(
|
|
323
367
|
api,
|
|
324
368
|
workflowRunId,
|
|
325
|
-
|
|
369
|
+
eventName,
|
|
326
370
|
eventDefinition.schema,
|
|
327
|
-
logger.child({ "aiki.
|
|
371
|
+
logger.child({ "aiki.eventName": eventName }),
|
|
328
372
|
onSend
|
|
329
373
|
);
|
|
330
|
-
senders[
|
|
374
|
+
senders[eventName] = sender;
|
|
331
375
|
}
|
|
332
376
|
return senders;
|
|
333
377
|
}
|
|
334
|
-
function createEventSender(api, workflowRunId,
|
|
378
|
+
function createEventSender(api, workflowRunId, eventName, schema, logger, onSend, options) {
|
|
379
|
+
const optsOverrider = objectOverrider(options ?? {});
|
|
380
|
+
const createBuilder = (optsBuilder) => ({
|
|
381
|
+
opt: (path, value) => createBuilder(optsBuilder.with(path, value)),
|
|
382
|
+
send: (...args) => createEventSender(api, workflowRunId, eventName, schema, logger, onSend, optsBuilder.build()).send(...args)
|
|
383
|
+
});
|
|
384
|
+
async function send(...args) {
|
|
385
|
+
const data = isNonEmptyArray(args) ? args[0] : void 0;
|
|
386
|
+
if (schema) {
|
|
387
|
+
schema.parse(data);
|
|
388
|
+
}
|
|
389
|
+
const { run } = await api.workflowRun.sendEventV1({
|
|
390
|
+
id: workflowRunId,
|
|
391
|
+
eventName,
|
|
392
|
+
data,
|
|
393
|
+
options
|
|
394
|
+
});
|
|
395
|
+
onSend(run);
|
|
396
|
+
logger.info("Sent event to workflow", {
|
|
397
|
+
...options?.reference ? { "aiki.referenceId": options.reference.id } : {}
|
|
398
|
+
});
|
|
399
|
+
}
|
|
335
400
|
return {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
401
|
+
with: () => createBuilder(optsOverrider()),
|
|
402
|
+
send
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function createEventMulticasters(workflowName, workflowVersionId, eventsDefinition) {
|
|
406
|
+
const senders = {};
|
|
407
|
+
for (const [eventName, eventDefinition] of Object.entries(eventsDefinition)) {
|
|
408
|
+
const sender = createEventMulticaster(
|
|
409
|
+
workflowName,
|
|
410
|
+
workflowVersionId,
|
|
411
|
+
eventName,
|
|
412
|
+
eventDefinition.schema
|
|
413
|
+
);
|
|
414
|
+
senders[eventName] = sender;
|
|
415
|
+
}
|
|
416
|
+
return senders;
|
|
417
|
+
}
|
|
418
|
+
function createEventMulticaster(workflowName, workflowVersionId, eventName, schema, options) {
|
|
419
|
+
const optsOverrider = objectOverrider(options ?? {});
|
|
420
|
+
const createBuilder = (optsBuilder) => ({
|
|
421
|
+
opt: (path, value) => createBuilder(optsBuilder.with(path, value)),
|
|
422
|
+
send: (client, runId, ...args) => createEventMulticaster(workflowName, workflowVersionId, eventName, schema, optsBuilder.build()).send(
|
|
423
|
+
client,
|
|
424
|
+
runId,
|
|
425
|
+
...args
|
|
426
|
+
)
|
|
427
|
+
});
|
|
428
|
+
async function send(client, runId, ...args) {
|
|
429
|
+
const data = isNonEmptyArray(args) ? args[0] : void 0;
|
|
430
|
+
if (schema) {
|
|
431
|
+
schema.parse(data);
|
|
350
432
|
}
|
|
433
|
+
const runIds = Array.isArray(runId) ? runId : [runId];
|
|
434
|
+
if (!isNonEmptyArray(runIds)) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const logger = client.logger.child({
|
|
438
|
+
"aiki.workflowName": workflowName,
|
|
439
|
+
"aiki.workflowVersionId": workflowVersionId,
|
|
440
|
+
"aiki.eventName": eventName
|
|
441
|
+
});
|
|
442
|
+
await client.api.workflowRun.multicastEventV1({
|
|
443
|
+
ids: runIds,
|
|
444
|
+
eventName,
|
|
445
|
+
data,
|
|
446
|
+
options
|
|
447
|
+
});
|
|
448
|
+
logger.info("Multicasted event to workflows", {
|
|
449
|
+
"aiki.workflowName": workflowName,
|
|
450
|
+
"aiki.workflowVersionId": workflowVersionId,
|
|
451
|
+
"aiki.workflowRunIds": runIds,
|
|
452
|
+
"aiki.eventName": eventName,
|
|
453
|
+
...options?.reference ? { "aiki.referenceId": options.reference.id } : {}
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
with: () => createBuilder(optsOverrider()),
|
|
458
|
+
send
|
|
351
459
|
};
|
|
352
460
|
}
|
|
353
461
|
|
|
354
462
|
// run/handle.ts
|
|
355
463
|
import { INTERNAL as INTERNAL2 } from "@aikirun/types/symbols";
|
|
356
464
|
import {
|
|
465
|
+
isTerminalWorkflowRunStatus,
|
|
466
|
+
WorkflowRunConflictError as WorkflowRunConflictError2,
|
|
357
467
|
WorkflowRunNotExecutableError
|
|
358
468
|
} from "@aikirun/types/workflow-run";
|
|
359
469
|
async function workflowRunHandle(client, runOrId, eventsDefinition, logger) {
|
|
360
470
|
const run = typeof runOrId !== "string" ? runOrId : (await client.api.workflowRun.getByIdV1({ id: runOrId })).run;
|
|
361
471
|
return new WorkflowRunHandleImpl(
|
|
362
|
-
client
|
|
472
|
+
client,
|
|
363
473
|
run,
|
|
364
474
|
eventsDefinition ?? {},
|
|
365
|
-
logger ?? client.logger.child({
|
|
475
|
+
logger ?? client.logger.child({
|
|
476
|
+
"aiki.workflowName": run.name,
|
|
477
|
+
"aiki.workflowVersionId": run.versionId,
|
|
478
|
+
"aiki.workflowRunId": run.id
|
|
479
|
+
})
|
|
366
480
|
);
|
|
367
481
|
}
|
|
368
482
|
var WorkflowRunHandleImpl = class {
|
|
369
|
-
constructor(
|
|
370
|
-
this.api = api;
|
|
483
|
+
constructor(client, _run, eventsDefinition, logger) {
|
|
371
484
|
this._run = _run;
|
|
372
485
|
this.logger = logger;
|
|
373
|
-
this.
|
|
486
|
+
this.api = client.api;
|
|
487
|
+
this.events = createEventSenders(client.api, this._run.id, eventsDefinition, this.logger, (run) => {
|
|
374
488
|
this._run = run;
|
|
375
489
|
});
|
|
376
490
|
this[INTERNAL2] = {
|
|
491
|
+
client,
|
|
377
492
|
transitionState: this.transitionState.bind(this),
|
|
378
493
|
transitionTaskState: this.transitionTaskState.bind(this),
|
|
379
494
|
assertExecutionAllowed: this.assertExecutionAllowed.bind(this)
|
|
380
495
|
};
|
|
381
496
|
}
|
|
497
|
+
api;
|
|
382
498
|
events;
|
|
383
499
|
[INTERNAL2];
|
|
384
500
|
get run() {
|
|
@@ -388,95 +504,119 @@ var WorkflowRunHandleImpl = class {
|
|
|
388
504
|
const { run: currentRun } = await this.api.workflowRun.getByIdV1({ id: this.run.id });
|
|
389
505
|
this._run = currentRun;
|
|
390
506
|
}
|
|
391
|
-
// TODO: instead
|
|
392
|
-
// because it is
|
|
507
|
+
// TODO: instead checking the current state, use the transition history
|
|
508
|
+
// because it is possible for a workflow to flash though a state
|
|
393
509
|
// and the handle will never know that the workflow hit that state
|
|
394
|
-
async
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
const maybeResult = await withRetry(
|
|
423
|
-
async () => {
|
|
424
|
-
await this.refresh();
|
|
425
|
-
return this.run.state;
|
|
426
|
-
},
|
|
427
|
-
{ type: "fixed", maxAttempts, delayMs },
|
|
428
|
-
{ shouldRetryOnResult: (state) => Promise.resolve(state.status !== condition.status) }
|
|
429
|
-
).run();
|
|
430
|
-
if (maybeResult.state === "timeout") {
|
|
431
|
-
return { success: false, cause: maybeResult.state };
|
|
432
|
-
}
|
|
510
|
+
async waitForStatus(status, options) {
|
|
511
|
+
return this.waitForStatusByPolling(status, options);
|
|
512
|
+
}
|
|
513
|
+
async waitForStatusByPolling(expectedStatus, options) {
|
|
514
|
+
if (options?.abortSignal?.aborted) {
|
|
515
|
+
return {
|
|
516
|
+
success: false,
|
|
517
|
+
cause: "aborted"
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
const delayMs = options?.interval ? toMilliseconds(options.interval) : 1e3;
|
|
521
|
+
const maxAttempts = options?.timeout ? Math.ceil(toMilliseconds(options.timeout) / delayMs) : Number.POSITIVE_INFINITY;
|
|
522
|
+
const retryStrategy = { type: "fixed", maxAttempts, delayMs };
|
|
523
|
+
const loadState = async () => {
|
|
524
|
+
await this.refresh();
|
|
525
|
+
return this.run.state;
|
|
526
|
+
};
|
|
527
|
+
const isNeitherExpectedNorTerminal = (state) => Promise.resolve(state.status !== expectedStatus && !isTerminalWorkflowRunStatus(state.status));
|
|
528
|
+
if (!Number.isFinite(maxAttempts) && !options?.abortSignal) {
|
|
529
|
+
const maybeResult2 = await withRetry(loadState, retryStrategy, {
|
|
530
|
+
shouldRetryOnResult: isNeitherExpectedNorTerminal
|
|
531
|
+
}).run();
|
|
532
|
+
if (maybeResult2.state === "timeout") {
|
|
533
|
+
throw new Error("Something's wrong, this should've never timed out");
|
|
534
|
+
}
|
|
535
|
+
if (await isNeitherExpectedNorTerminal(maybeResult2.result)) {
|
|
433
536
|
return {
|
|
434
|
-
success:
|
|
435
|
-
|
|
537
|
+
success: false,
|
|
538
|
+
cause: "run_terminated"
|
|
436
539
|
};
|
|
437
540
|
}
|
|
438
|
-
|
|
439
|
-
|
|
541
|
+
return {
|
|
542
|
+
success: true,
|
|
543
|
+
state: maybeResult2.result
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
const maybeResult = options?.abortSignal ? await withRetry(loadState, retryStrategy, {
|
|
547
|
+
abortSignal: options.abortSignal,
|
|
548
|
+
shouldRetryOnResult: isNeitherExpectedNorTerminal
|
|
549
|
+
}).run() : await withRetry(loadState, retryStrategy, { shouldRetryOnResult: isNeitherExpectedNorTerminal }).run();
|
|
550
|
+
if (maybeResult.state === "completed") {
|
|
551
|
+
if (await isNeitherExpectedNorTerminal(maybeResult.result)) {
|
|
552
|
+
return {
|
|
553
|
+
success: false,
|
|
554
|
+
cause: "run_terminated"
|
|
555
|
+
};
|
|
440
556
|
}
|
|
441
|
-
|
|
442
|
-
|
|
557
|
+
return {
|
|
558
|
+
success: true,
|
|
559
|
+
state: maybeResult.result
|
|
560
|
+
};
|
|
443
561
|
}
|
|
562
|
+
return { success: false, cause: maybeResult.state };
|
|
444
563
|
}
|
|
445
564
|
async cancel(reason) {
|
|
446
|
-
|
|
565
|
+
await this.transitionState({ status: "cancelled", reason });
|
|
566
|
+
this.logger.info("Workflow cancelled");
|
|
447
567
|
}
|
|
448
568
|
async pause() {
|
|
449
|
-
|
|
569
|
+
await this.transitionState({ status: "paused" });
|
|
570
|
+
this.logger.info("Workflow paused");
|
|
450
571
|
}
|
|
451
572
|
async resume() {
|
|
452
|
-
|
|
573
|
+
await this.transitionState({ status: "scheduled", scheduledInMs: 0, reason: "resume" });
|
|
574
|
+
this.logger.info("Workflow resumed");
|
|
575
|
+
}
|
|
576
|
+
async awake() {
|
|
577
|
+
await this.transitionState({ status: "scheduled", scheduledInMs: 0, reason: "awake_early" });
|
|
578
|
+
this.logger.info("Workflow awoken");
|
|
453
579
|
}
|
|
454
580
|
async transitionState(targetState) {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
581
|
+
try {
|
|
582
|
+
if (targetState.status === "scheduled" && (targetState.reason === "new" || targetState.reason === "resume" || targetState.reason === "awake_early") || targetState.status === "paused" || targetState.status === "cancelled") {
|
|
583
|
+
const { run: run2 } = await this.api.workflowRun.transitionStateV1({
|
|
584
|
+
type: "pessimistic",
|
|
585
|
+
id: this.run.id,
|
|
586
|
+
state: targetState
|
|
587
|
+
});
|
|
588
|
+
this._run = run2;
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const { run } = await this.api.workflowRun.transitionStateV1({
|
|
592
|
+
type: "optimistic",
|
|
458
593
|
id: this.run.id,
|
|
459
|
-
state: targetState
|
|
594
|
+
state: targetState,
|
|
595
|
+
expectedRevision: this.run.revision
|
|
460
596
|
});
|
|
461
|
-
this._run =
|
|
462
|
-
|
|
597
|
+
this._run = run;
|
|
598
|
+
} catch (error) {
|
|
599
|
+
if (isConflictError(error)) {
|
|
600
|
+
throw new WorkflowRunConflictError2(this.run.id);
|
|
601
|
+
}
|
|
602
|
+
throw error;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
async transitionTaskState(request) {
|
|
606
|
+
try {
|
|
607
|
+
const { run, taskId } = await this.api.workflowRun.transitionTaskStateV1({
|
|
608
|
+
...request,
|
|
609
|
+
id: this.run.id,
|
|
610
|
+
expectedRevision: this.run.revision
|
|
611
|
+
});
|
|
612
|
+
this._run = run;
|
|
613
|
+
return { taskId };
|
|
614
|
+
} catch (error) {
|
|
615
|
+
if (isConflictError(error)) {
|
|
616
|
+
throw new WorkflowRunConflictError2(this.run.id);
|
|
617
|
+
}
|
|
618
|
+
throw error;
|
|
463
619
|
}
|
|
464
|
-
const { run } = await this.api.workflowRun.transitionStateV1({
|
|
465
|
-
type: "optimistic",
|
|
466
|
-
id: this.run.id,
|
|
467
|
-
state: targetState,
|
|
468
|
-
expectedRevision: this.run.revision
|
|
469
|
-
});
|
|
470
|
-
this._run = run;
|
|
471
|
-
}
|
|
472
|
-
async transitionTaskState(taskPath, taskState) {
|
|
473
|
-
const { run } = await this.api.workflowRun.transitionTaskStateV1({
|
|
474
|
-
id: this.run.id,
|
|
475
|
-
taskPath,
|
|
476
|
-
taskState,
|
|
477
|
-
expectedRevision: this.run.revision
|
|
478
|
-
});
|
|
479
|
-
this._run = run;
|
|
480
620
|
}
|
|
481
621
|
assertExecutionAllowed() {
|
|
482
622
|
const status = this.run.state.status;
|
|
@@ -485,133 +625,394 @@ var WorkflowRunHandleImpl = class {
|
|
|
485
625
|
}
|
|
486
626
|
}
|
|
487
627
|
};
|
|
628
|
+
function isConflictError(error) {
|
|
629
|
+
return error != null && typeof error === "object" && "code" in error && error.code === "CONFLICT";
|
|
630
|
+
}
|
|
488
631
|
|
|
489
632
|
// run/sleeper.ts
|
|
490
633
|
import { INTERNAL as INTERNAL3 } from "@aikirun/types/symbols";
|
|
491
|
-
import { WorkflowRunSuspendedError as WorkflowRunSuspendedError2 } from "@aikirun/types/workflow-run";
|
|
634
|
+
import { WorkflowRunConflictError as WorkflowRunConflictError3, WorkflowRunSuspendedError as WorkflowRunSuspendedError2 } from "@aikirun/types/workflow-run";
|
|
492
635
|
var MAX_SLEEP_YEARS = 10;
|
|
493
636
|
var MAX_SLEEP_MS = MAX_SLEEP_YEARS * 365 * 24 * 60 * 60 * 1e3;
|
|
494
|
-
function createSleeper(handle, logger
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const
|
|
637
|
+
function createSleeper(handle, logger) {
|
|
638
|
+
const nextSleepIndexByName = {};
|
|
639
|
+
return async (name, duration) => {
|
|
640
|
+
const sleepName = name;
|
|
641
|
+
let durationMs = toMilliseconds(duration);
|
|
498
642
|
if (durationMs > MAX_SLEEP_MS) {
|
|
499
643
|
throw new Error(`Sleep duration ${durationMs}ms exceeds maximum of ${MAX_SLEEP_YEARS} years`);
|
|
500
644
|
}
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
645
|
+
const nextSleepIndex = nextSleepIndexByName[sleepName] ?? 0;
|
|
646
|
+
const sleepQueue = handle.run.sleepsQueue[sleepName] ?? { sleeps: [] };
|
|
647
|
+
const sleepState = sleepQueue.sleeps[nextSleepIndex];
|
|
648
|
+
if (!sleepState) {
|
|
649
|
+
try {
|
|
650
|
+
await handle[INTERNAL3].transitionState({ status: "sleeping", sleepName, durationMs });
|
|
651
|
+
logger.info("Sleeping", {
|
|
652
|
+
"aiki.sleepName": sleepName,
|
|
653
|
+
"aiki.durationMs": durationMs
|
|
654
|
+
});
|
|
655
|
+
} catch (error) {
|
|
656
|
+
if (error instanceof WorkflowRunConflictError3) {
|
|
657
|
+
throw new WorkflowRunSuspendedError2(handle.run.id);
|
|
658
|
+
}
|
|
659
|
+
throw error;
|
|
660
|
+
}
|
|
661
|
+
throw new WorkflowRunSuspendedError2(handle.run.id);
|
|
662
|
+
}
|
|
663
|
+
if (sleepState.status === "sleeping") {
|
|
664
|
+
logger.debug("Already sleeping", {
|
|
665
|
+
"aiki.sleepName": sleepName,
|
|
666
|
+
"aiki.awakeAt": sleepState.awakeAt
|
|
507
667
|
});
|
|
508
|
-
|
|
668
|
+
throw new WorkflowRunSuspendedError2(handle.run.id);
|
|
509
669
|
}
|
|
670
|
+
sleepState.status;
|
|
671
|
+
nextSleepIndexByName[sleepName] = nextSleepIndex + 1;
|
|
510
672
|
if (sleepState.status === "cancelled") {
|
|
511
673
|
logger.debug("Sleep cancelled", {
|
|
512
|
-
"aiki.
|
|
513
|
-
"aiki.
|
|
674
|
+
"aiki.sleepName": sleepName,
|
|
675
|
+
"aiki.cancelledAt": sleepState.cancelledAt
|
|
514
676
|
});
|
|
515
677
|
return { cancelled: true };
|
|
516
678
|
}
|
|
517
|
-
if (
|
|
518
|
-
logger.debug("
|
|
519
|
-
"aiki.
|
|
520
|
-
"aiki.durationMs": durationMs
|
|
679
|
+
if (durationMs === sleepState.durationMs) {
|
|
680
|
+
logger.debug("Sleep completed", {
|
|
681
|
+
"aiki.sleepName": sleepName,
|
|
682
|
+
"aiki.durationMs": durationMs,
|
|
683
|
+
"aiki.completedAt": sleepState.completedAt
|
|
521
684
|
});
|
|
522
|
-
|
|
685
|
+
return { cancelled: false };
|
|
523
686
|
}
|
|
524
|
-
sleepState
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
"aiki.
|
|
528
|
-
"aiki.
|
|
687
|
+
if (durationMs > sleepState.durationMs) {
|
|
688
|
+
logger.warn("Higher sleep duration encountered during replay. Sleeping for remaining duration", {
|
|
689
|
+
"aiki.sleepName": sleepName,
|
|
690
|
+
"aiki.historicDurationMs": sleepState.durationMs,
|
|
691
|
+
"aiki.latestDurationMs": durationMs
|
|
692
|
+
});
|
|
693
|
+
durationMs -= sleepState.durationMs;
|
|
694
|
+
} else {
|
|
695
|
+
logger.warn("Lower sleep duration encountered during replay. Already slept enough", {
|
|
696
|
+
"aiki.sleepName": sleepName,
|
|
697
|
+
"aiki.historicDurationMs": sleepState.durationMs,
|
|
698
|
+
"aiki.latestDurationMs": durationMs
|
|
529
699
|
});
|
|
530
|
-
await delay(durationMs);
|
|
531
700
|
return { cancelled: false };
|
|
532
701
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
702
|
+
try {
|
|
703
|
+
await handle[INTERNAL3].transitionState({ status: "sleeping", sleepName, durationMs });
|
|
704
|
+
logger.info("Sleeping", {
|
|
705
|
+
"aiki.sleepName": sleepName,
|
|
706
|
+
"aiki.durationMs": durationMs
|
|
707
|
+
});
|
|
708
|
+
} catch (error) {
|
|
709
|
+
if (error instanceof WorkflowRunConflictError3) {
|
|
710
|
+
throw new WorkflowRunSuspendedError2(handle.run.id);
|
|
711
|
+
}
|
|
712
|
+
throw error;
|
|
713
|
+
}
|
|
538
714
|
throw new WorkflowRunSuspendedError2(handle.run.id);
|
|
539
715
|
};
|
|
540
716
|
}
|
|
541
717
|
|
|
542
718
|
// workflow.ts
|
|
543
|
-
import { INTERNAL as
|
|
719
|
+
import { INTERNAL as INTERNAL6 } from "@aikirun/types/symbols";
|
|
720
|
+
|
|
721
|
+
// ../../lib/path/index.ts
|
|
722
|
+
function getWorkflowRunPath(name, versionId, referenceId) {
|
|
723
|
+
return `${name}/${versionId}/${referenceId}`;
|
|
724
|
+
}
|
|
544
725
|
|
|
545
726
|
// workflow-version.ts
|
|
546
|
-
import { INTERNAL as
|
|
727
|
+
import { INTERNAL as INTERNAL5 } from "@aikirun/types/symbols";
|
|
547
728
|
import { TaskFailedError } from "@aikirun/types/task";
|
|
548
729
|
import {
|
|
730
|
+
WorkflowRunConflictError as WorkflowRunConflictError5,
|
|
549
731
|
WorkflowRunFailedError as WorkflowRunFailedError2,
|
|
732
|
+
WorkflowRunSuspendedError as WorkflowRunSuspendedError4
|
|
733
|
+
} from "@aikirun/types/workflow-run";
|
|
734
|
+
|
|
735
|
+
// run/handle-child.ts
|
|
736
|
+
import { INTERNAL as INTERNAL4 } from "@aikirun/types/symbols";
|
|
737
|
+
import {
|
|
738
|
+
isTerminalWorkflowRunStatus as isTerminalWorkflowRunStatus2,
|
|
739
|
+
WorkflowRunConflictError as WorkflowRunConflictError4,
|
|
550
740
|
WorkflowRunSuspendedError as WorkflowRunSuspendedError3
|
|
551
741
|
} from "@aikirun/types/workflow-run";
|
|
742
|
+
async function childWorkflowRunHandle(client, run, parentRun, logger, eventsDefinition) {
|
|
743
|
+
const handle = await workflowRunHandle(client, run, eventsDefinition, logger);
|
|
744
|
+
return {
|
|
745
|
+
run: handle.run,
|
|
746
|
+
events: handle.events,
|
|
747
|
+
refresh: handle.refresh.bind(handle),
|
|
748
|
+
waitForStatus: createStatusWaiter(handle, parentRun, logger),
|
|
749
|
+
cancel: handle.cancel.bind(handle),
|
|
750
|
+
pause: handle.pause.bind(handle),
|
|
751
|
+
resume: handle.resume.bind(handle),
|
|
752
|
+
awake: handle.awake.bind(handle),
|
|
753
|
+
[INTERNAL4]: handle[INTERNAL4]
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function createStatusWaiter(handle, parentRun, logger) {
|
|
757
|
+
let nextWaitIndex = 0;
|
|
758
|
+
async function waitForStatus(expectedStatus, options) {
|
|
759
|
+
const parentRunHandle = parentRun[INTERNAL4].handle;
|
|
760
|
+
const waitResults = parentRunHandle.run.childWorkflowRuns[handle.run.path]?.statusWaitResults ?? [];
|
|
761
|
+
const waitResult = waitResults[nextWaitIndex];
|
|
762
|
+
if (waitResult) {
|
|
763
|
+
nextWaitIndex++;
|
|
764
|
+
if (waitResult.status === "timeout") {
|
|
765
|
+
logger.debug("Timed out waiting for child workflow status", {
|
|
766
|
+
"aiki.childWorkflowExpectedStatus": expectedStatus
|
|
767
|
+
});
|
|
768
|
+
return {
|
|
769
|
+
success: false,
|
|
770
|
+
cause: "timeout"
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
if (waitResult.childWorkflowRunState.status === expectedStatus) {
|
|
774
|
+
return {
|
|
775
|
+
success: true,
|
|
776
|
+
state: waitResult.childWorkflowRunState
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
if (isTerminalWorkflowRunStatus2(waitResult.childWorkflowRunState.status)) {
|
|
780
|
+
logger.debug("Child workflow run reached termnial state", {
|
|
781
|
+
"aiki.childWorkflowTerminalStatus": waitResult.childWorkflowRunState.status
|
|
782
|
+
});
|
|
783
|
+
return {
|
|
784
|
+
success: false,
|
|
785
|
+
cause: "run_terminated"
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
const { state } = handle.run;
|
|
790
|
+
if (state.status === expectedStatus) {
|
|
791
|
+
return {
|
|
792
|
+
success: true,
|
|
793
|
+
state
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
if (isTerminalWorkflowRunStatus2(state.status)) {
|
|
797
|
+
logger.debug("Child workflow run reached termnial state", {
|
|
798
|
+
"aiki.childWorkflowTerminalStatus": state.status
|
|
799
|
+
});
|
|
800
|
+
return {
|
|
801
|
+
success: false,
|
|
802
|
+
cause: "run_terminated"
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
const timeoutInMs = options?.timeout && toMilliseconds(options.timeout);
|
|
806
|
+
try {
|
|
807
|
+
await parentRunHandle[INTERNAL4].transitionState({
|
|
808
|
+
status: "awaiting_child_workflow",
|
|
809
|
+
childWorkflowRunId: handle.run.id,
|
|
810
|
+
childWorkflowRunStatus: expectedStatus,
|
|
811
|
+
timeoutInMs
|
|
812
|
+
});
|
|
813
|
+
logger.info("Waiting for child Workflow", {
|
|
814
|
+
"aiki.childWorkflowExpectedStatus": expectedStatus,
|
|
815
|
+
...timeoutInMs !== void 0 ? { "aiki.timeoutInMs": timeoutInMs } : {}
|
|
816
|
+
});
|
|
817
|
+
} catch (error) {
|
|
818
|
+
if (error instanceof WorkflowRunConflictError4) {
|
|
819
|
+
throw new WorkflowRunSuspendedError3(parentRun.id);
|
|
820
|
+
}
|
|
821
|
+
throw error;
|
|
822
|
+
}
|
|
823
|
+
throw new WorkflowRunSuspendedError3(parentRun.id);
|
|
824
|
+
}
|
|
825
|
+
return waitForStatus;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// workflow-version.ts
|
|
552
829
|
var WorkflowVersionImpl = class _WorkflowVersionImpl {
|
|
553
|
-
constructor(
|
|
554
|
-
this.
|
|
830
|
+
constructor(name, versionId, params) {
|
|
831
|
+
this.name = name;
|
|
555
832
|
this.versionId = versionId;
|
|
556
833
|
this.params = params;
|
|
557
|
-
|
|
558
|
-
|
|
834
|
+
const eventsDefinition = this.params.events ?? {};
|
|
835
|
+
this.events = createEventMulticasters(this.name, this.versionId, eventsDefinition);
|
|
836
|
+
this[INTERNAL5] = {
|
|
837
|
+
eventsDefinition,
|
|
559
838
|
handler: this.handler.bind(this)
|
|
560
839
|
};
|
|
561
840
|
}
|
|
562
|
-
|
|
841
|
+
events;
|
|
842
|
+
[INTERNAL5];
|
|
563
843
|
with() {
|
|
564
844
|
const optsOverrider = objectOverrider(this.params.opts ?? {});
|
|
565
|
-
const createBuilder = (optsBuilder) =>
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
client,
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
845
|
+
const createBuilder = (optsBuilder) => {
|
|
846
|
+
return {
|
|
847
|
+
opt: (path, value) => createBuilder(optsBuilder.with(path, value)),
|
|
848
|
+
start: (client, ...args) => new _WorkflowVersionImpl(this.name, this.versionId, {
|
|
849
|
+
...this.params,
|
|
850
|
+
opts: optsBuilder.build()
|
|
851
|
+
}).start(client, ...args),
|
|
852
|
+
startAsChild: (parentRun, ...args) => new _WorkflowVersionImpl(this.name, this.versionId, {
|
|
853
|
+
...this.params,
|
|
854
|
+
opts: optsBuilder.build()
|
|
855
|
+
}).startAsChild(parentRun, ...args)
|
|
856
|
+
};
|
|
857
|
+
};
|
|
572
858
|
return createBuilder(optsOverrider());
|
|
573
859
|
}
|
|
574
860
|
async start(client, ...args) {
|
|
861
|
+
const inputRaw = isNonEmptyArray(args) ? args[0] : void 0;
|
|
862
|
+
const input = this.params.schema?.input ? this.params.schema.input.parse(inputRaw) : inputRaw;
|
|
575
863
|
const { run } = await client.api.workflowRun.createV1({
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
input
|
|
864
|
+
name: this.name,
|
|
865
|
+
versionId: this.versionId,
|
|
866
|
+
input,
|
|
867
|
+
options: this.params.opts
|
|
868
|
+
});
|
|
869
|
+
client.logger.info("Created workflow", {
|
|
870
|
+
"aiki.workflowName": this.name,
|
|
871
|
+
"aiki.workflowVersionId": this.versionId,
|
|
872
|
+
"aiki.workflowRunId": run.id
|
|
873
|
+
});
|
|
874
|
+
return workflowRunHandle(client, run, this[INTERNAL5].eventsDefinition);
|
|
875
|
+
}
|
|
876
|
+
async startAsChild(parentRun, ...args) {
|
|
877
|
+
const parentRunHandle = parentRun[INTERNAL5].handle;
|
|
878
|
+
parentRunHandle[INTERNAL5].assertExecutionAllowed();
|
|
879
|
+
const { client } = parentRunHandle[INTERNAL5];
|
|
880
|
+
const inputRaw = isNonEmptyArray(args) ? args[0] : void 0;
|
|
881
|
+
let input = inputRaw;
|
|
882
|
+
if (this.params.schema?.input) {
|
|
883
|
+
try {
|
|
884
|
+
input = this.params.schema.input.parse(inputRaw);
|
|
885
|
+
} catch (error) {
|
|
886
|
+
await parentRunHandle[INTERNAL5].transitionState({
|
|
887
|
+
status: "failed",
|
|
888
|
+
cause: "self",
|
|
889
|
+
error: createSerializableError(error)
|
|
890
|
+
});
|
|
891
|
+
throw new WorkflowRunFailedError2(parentRun.id, parentRunHandle.run.attempts);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
const inputHash = await hashInput(input);
|
|
895
|
+
const reference = this.params.opts?.reference;
|
|
896
|
+
const path = getWorkflowRunPath(this.name, this.versionId, reference?.id ?? inputHash);
|
|
897
|
+
const existingRunInfo = parentRunHandle.run.childWorkflowRuns[path];
|
|
898
|
+
if (existingRunInfo) {
|
|
899
|
+
await this.assertUniqueChildRunReferenceId(
|
|
900
|
+
parentRunHandle,
|
|
901
|
+
existingRunInfo,
|
|
902
|
+
inputHash,
|
|
903
|
+
reference,
|
|
904
|
+
parentRun.logger
|
|
905
|
+
);
|
|
906
|
+
const { run: existingRun } = await client.api.workflowRun.getByIdV1({ id: existingRunInfo.id });
|
|
907
|
+
const logger2 = parentRun.logger.child({
|
|
908
|
+
"aiki.childWorkflowName": existingRun.name,
|
|
909
|
+
"aiki.childWorkflowVersionId": existingRun.versionId,
|
|
910
|
+
"aiki.childWorkflowRunId": existingRun.id
|
|
911
|
+
});
|
|
912
|
+
return childWorkflowRunHandle(
|
|
913
|
+
client,
|
|
914
|
+
existingRun,
|
|
915
|
+
parentRun,
|
|
916
|
+
logger2,
|
|
917
|
+
this[INTERNAL5].eventsDefinition
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
const { run: newRun } = await client.api.workflowRun.createV1({
|
|
921
|
+
name: this.name,
|
|
922
|
+
versionId: this.versionId,
|
|
923
|
+
input,
|
|
924
|
+
parentWorkflowRunId: parentRun.id,
|
|
579
925
|
options: this.params.opts
|
|
580
926
|
});
|
|
581
|
-
|
|
927
|
+
parentRunHandle.run.childWorkflowRuns[path] = {
|
|
928
|
+
id: newRun.id,
|
|
929
|
+
inputHash,
|
|
930
|
+
statusWaitResults: []
|
|
931
|
+
};
|
|
932
|
+
const logger = parentRun.logger.child({
|
|
933
|
+
"aiki.childWorkflowName": newRun.name,
|
|
934
|
+
"aiki.childWorkflowVersionId": newRun.versionId,
|
|
935
|
+
"aiki.childWorkflowRunId": newRun.id
|
|
936
|
+
});
|
|
937
|
+
logger.info("Created child workflow");
|
|
938
|
+
return childWorkflowRunHandle(
|
|
939
|
+
client,
|
|
940
|
+
newRun,
|
|
941
|
+
parentRun,
|
|
942
|
+
logger,
|
|
943
|
+
this[INTERNAL5].eventsDefinition
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
async assertUniqueChildRunReferenceId(parentRunHandle, existingRunInfo, inputHash, reference, logger) {
|
|
947
|
+
if (existingRunInfo.inputHash !== inputHash && reference) {
|
|
948
|
+
const onConflict = reference.onConflict ?? "error";
|
|
949
|
+
if (onConflict !== "error") {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
logger.error("Reference ID already used by another child workflow", {
|
|
953
|
+
"aiki.referenceId": reference.id,
|
|
954
|
+
"aiki.existingChildWorkflowRunId": existingRunInfo.id
|
|
955
|
+
});
|
|
956
|
+
const error = new WorkflowRunFailedError2(
|
|
957
|
+
parentRunHandle.run.id,
|
|
958
|
+
parentRunHandle.run.attempts,
|
|
959
|
+
`Reference ID "${reference.id}" already used by another child workflow run ${existingRunInfo.id}`
|
|
960
|
+
);
|
|
961
|
+
await parentRunHandle[INTERNAL5].transitionState({
|
|
962
|
+
status: "failed",
|
|
963
|
+
cause: "self",
|
|
964
|
+
error: createSerializableError(error)
|
|
965
|
+
});
|
|
966
|
+
throw error;
|
|
967
|
+
}
|
|
582
968
|
}
|
|
583
969
|
async getHandle(client, runId) {
|
|
584
|
-
return workflowRunHandle(client, runId, this[
|
|
970
|
+
return workflowRunHandle(client, runId, this[INTERNAL5].eventsDefinition);
|
|
585
971
|
}
|
|
586
|
-
async handler(
|
|
972
|
+
async handler(run, input, context) {
|
|
587
973
|
const { logger } = run;
|
|
588
|
-
const { handle } = run[
|
|
589
|
-
handle[
|
|
974
|
+
const { handle } = run[INTERNAL5];
|
|
975
|
+
handle[INTERNAL5].assertExecutionAllowed();
|
|
590
976
|
const retryStrategy = this.params.opts?.retry ?? { type: "never" };
|
|
591
977
|
const state = handle.run.state;
|
|
592
978
|
if (state.status === "queued" && state.reason === "retry") {
|
|
593
979
|
await this.assertRetryAllowed(handle, retryStrategy, logger);
|
|
594
980
|
}
|
|
595
981
|
logger.info("Starting workflow");
|
|
596
|
-
await handle[
|
|
982
|
+
await handle[INTERNAL5].transitionState({ status: "running" });
|
|
597
983
|
const output = await this.tryExecuteWorkflow(input, run, context, retryStrategy);
|
|
598
|
-
await handle[
|
|
984
|
+
await handle[INTERNAL5].transitionState({ status: "completed", output });
|
|
599
985
|
logger.info("Workflow complete");
|
|
600
986
|
}
|
|
601
987
|
async tryExecuteWorkflow(input, run, context, retryStrategy) {
|
|
602
988
|
while (true) {
|
|
603
989
|
try {
|
|
604
|
-
|
|
990
|
+
const outputRaw = await this.params.handler(run, input, context);
|
|
991
|
+
let output = outputRaw;
|
|
992
|
+
if (this.params.schema?.output) {
|
|
993
|
+
try {
|
|
994
|
+
output = this.params.schema.output.parse(outputRaw);
|
|
995
|
+
} catch (error) {
|
|
996
|
+
const { handle } = run[INTERNAL5];
|
|
997
|
+
await handle[INTERNAL5].transitionState({
|
|
998
|
+
status: "failed",
|
|
999
|
+
cause: "self",
|
|
1000
|
+
error: createSerializableError(error)
|
|
1001
|
+
});
|
|
1002
|
+
throw new WorkflowRunFailedError2(run.id, handle.run.attempts);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return output;
|
|
605
1006
|
} catch (error) {
|
|
606
|
-
if (error instanceof
|
|
1007
|
+
if (error instanceof WorkflowRunSuspendedError4 || error instanceof WorkflowRunFailedError2 || error instanceof WorkflowRunConflictError5) {
|
|
607
1008
|
throw error;
|
|
608
1009
|
}
|
|
609
|
-
const { handle } = run[
|
|
1010
|
+
const { handle } = run[INTERNAL5];
|
|
610
1011
|
const attempts = handle.run.attempts;
|
|
611
1012
|
const retryParams = getRetryParams(attempts, retryStrategy);
|
|
612
1013
|
if (!retryParams.retriesLeft) {
|
|
613
1014
|
const failedState = this.createFailedState(error);
|
|
614
|
-
await handle[
|
|
1015
|
+
await handle[INTERNAL5].transitionState(failedState);
|
|
615
1016
|
const logMeta2 = {};
|
|
616
1017
|
for (const [key, value] of Object.entries(failedState)) {
|
|
617
1018
|
logMeta2[`aiki.${key}`] = value;
|
|
@@ -623,7 +1024,7 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
|
|
|
623
1024
|
throw new WorkflowRunFailedError2(run.id, attempts);
|
|
624
1025
|
}
|
|
625
1026
|
const awaitingRetryState = this.createAwaitingRetryState(error, retryParams.delayMs);
|
|
626
|
-
await handle[
|
|
1027
|
+
await handle[INTERNAL5].transitionState(awaitingRetryState);
|
|
627
1028
|
const logMeta = {};
|
|
628
1029
|
for (const [key, value] of Object.entries(awaitingRetryState)) {
|
|
629
1030
|
logMeta[`aiki.${key}`] = value;
|
|
@@ -633,7 +1034,7 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
|
|
|
633
1034
|
"aiki.delayMs": retryParams.delayMs,
|
|
634
1035
|
...logMeta
|
|
635
1036
|
});
|
|
636
|
-
throw new
|
|
1037
|
+
throw new WorkflowRunSuspendedError4(run.id);
|
|
637
1038
|
}
|
|
638
1039
|
}
|
|
639
1040
|
}
|
|
@@ -643,11 +1044,10 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
|
|
|
643
1044
|
if (!retryParams.retriesLeft) {
|
|
644
1045
|
logger.error("Workflow retry not allowed", { "aiki.attempts": attempts });
|
|
645
1046
|
const error = new WorkflowRunFailedError2(id, attempts);
|
|
646
|
-
|
|
647
|
-
await handle[INTERNAL4].transitionState({
|
|
1047
|
+
await handle[INTERNAL5].transitionState({
|
|
648
1048
|
status: "failed",
|
|
649
1049
|
cause: "self",
|
|
650
|
-
error:
|
|
1050
|
+
error: createSerializableError(error)
|
|
651
1051
|
});
|
|
652
1052
|
throw error;
|
|
653
1053
|
}
|
|
@@ -657,14 +1057,13 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
|
|
|
657
1057
|
return {
|
|
658
1058
|
status: "failed",
|
|
659
1059
|
cause: "task",
|
|
660
|
-
|
|
1060
|
+
taskId: error.taskId
|
|
661
1061
|
};
|
|
662
1062
|
}
|
|
663
|
-
const serializableError = createSerializableError(error);
|
|
664
1063
|
return {
|
|
665
1064
|
status: "failed",
|
|
666
1065
|
cause: "self",
|
|
667
|
-
error:
|
|
1066
|
+
error: createSerializableError(error)
|
|
668
1067
|
};
|
|
669
1068
|
}
|
|
670
1069
|
createAwaitingRetryState(error, nextAttemptInMs) {
|
|
@@ -673,15 +1072,14 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
|
|
|
673
1072
|
status: "awaiting_retry",
|
|
674
1073
|
cause: "task",
|
|
675
1074
|
nextAttemptInMs,
|
|
676
|
-
|
|
1075
|
+
taskId: error.taskId
|
|
677
1076
|
};
|
|
678
1077
|
}
|
|
679
|
-
const serializableError = createSerializableError(error);
|
|
680
1078
|
return {
|
|
681
1079
|
status: "awaiting_retry",
|
|
682
1080
|
cause: "self",
|
|
683
1081
|
nextAttemptInMs,
|
|
684
|
-
error:
|
|
1082
|
+
error: createSerializableError(error)
|
|
685
1083
|
};
|
|
686
1084
|
}
|
|
687
1085
|
};
|
|
@@ -691,21 +1089,21 @@ function workflow(params) {
|
|
|
691
1089
|
return new WorkflowImpl(params);
|
|
692
1090
|
}
|
|
693
1091
|
var WorkflowImpl = class {
|
|
694
|
-
|
|
695
|
-
[
|
|
1092
|
+
name;
|
|
1093
|
+
[INTERNAL6];
|
|
696
1094
|
workflowVersions = /* @__PURE__ */ new Map();
|
|
697
1095
|
constructor(params) {
|
|
698
|
-
this.
|
|
699
|
-
this[
|
|
1096
|
+
this.name = params.name;
|
|
1097
|
+
this[INTERNAL6] = {
|
|
700
1098
|
getAllVersions: this.getAllVersions.bind(this),
|
|
701
1099
|
getVersion: this.getVersion.bind(this)
|
|
702
1100
|
};
|
|
703
1101
|
}
|
|
704
1102
|
v(versionId, params) {
|
|
705
1103
|
if (this.workflowVersions.has(versionId)) {
|
|
706
|
-
throw new Error(`Workflow "${this.
|
|
1104
|
+
throw new Error(`Workflow "${this.name}/${versionId}" already exists`);
|
|
707
1105
|
}
|
|
708
|
-
const workflowVersion = new WorkflowVersionImpl(this.
|
|
1106
|
+
const workflowVersion = new WorkflowVersionImpl(this.name, versionId, params);
|
|
709
1107
|
this.workflowVersions.set(
|
|
710
1108
|
versionId,
|
|
711
1109
|
workflowVersion
|