@aikirun/workflow 0.23.0 → 0.24.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 +1 -2
- package/dist/index.d.ts +48 -53
- package/dist/index.js +591 -262
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -58,64 +58,6 @@ function isNonEmptyArray(value) {
|
|
|
58
58
|
return value !== void 0 && value.length > 0;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
// ../../lib/async/delay.ts
|
|
62
|
-
function delay(ms, options) {
|
|
63
|
-
const abortSignal = options?.abortSignal;
|
|
64
|
-
if (abortSignal?.aborted) {
|
|
65
|
-
return Promise.reject(abortSignal.reason);
|
|
66
|
-
}
|
|
67
|
-
return new Promise((resolve, reject) => {
|
|
68
|
-
const abort = () => {
|
|
69
|
-
clearTimeout(timeout);
|
|
70
|
-
reject(abortSignal?.reason);
|
|
71
|
-
};
|
|
72
|
-
const timeout = setTimeout(() => {
|
|
73
|
-
abortSignal?.removeEventListener("abort", abort);
|
|
74
|
-
resolve();
|
|
75
|
-
}, ms);
|
|
76
|
-
abortSignal?.addEventListener("abort", abort, { once: true });
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ../../lib/crypto/hash.ts
|
|
81
|
-
import { createHash } from "crypto";
|
|
82
|
-
|
|
83
|
-
// ../../lib/json/stable-stringify.ts
|
|
84
|
-
function stableStringify(value) {
|
|
85
|
-
return stringifyValue(value);
|
|
86
|
-
}
|
|
87
|
-
function stringifyValue(value) {
|
|
88
|
-
if (value === null || value === void 0) {
|
|
89
|
-
return "null";
|
|
90
|
-
}
|
|
91
|
-
if (typeof value !== "object") {
|
|
92
|
-
return JSON.stringify(value);
|
|
93
|
-
}
|
|
94
|
-
if (Array.isArray(value)) {
|
|
95
|
-
return `[${value.map(stringifyValue).join(",")}]`;
|
|
96
|
-
}
|
|
97
|
-
const keys = Object.keys(value).sort();
|
|
98
|
-
const pairs = [];
|
|
99
|
-
for (const key of keys) {
|
|
100
|
-
const keyValue = value[key];
|
|
101
|
-
if (keyValue !== void 0) {
|
|
102
|
-
pairs.push(`${JSON.stringify(key)}:${stringifyValue(keyValue)}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return `{${pairs.join(",")}}`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ../../lib/crypto/hash.ts
|
|
109
|
-
async function sha256(input) {
|
|
110
|
-
const data = new TextEncoder().encode(input);
|
|
111
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
112
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
113
|
-
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
114
|
-
}
|
|
115
|
-
async function hashInput(input) {
|
|
116
|
-
return sha256(stableStringify({ input }));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
61
|
// ../../lib/duration/convert.ts
|
|
120
62
|
var MS_PER_SECOND = 1e3;
|
|
121
63
|
var MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
@@ -162,19 +104,6 @@ function assertIsPositiveNumber(value, field) {
|
|
|
162
104
|
}
|
|
163
105
|
}
|
|
164
106
|
|
|
165
|
-
// ../../lib/error/serializable.ts
|
|
166
|
-
function createSerializableError(error) {
|
|
167
|
-
return error instanceof Error ? {
|
|
168
|
-
message: error.message,
|
|
169
|
-
name: error.name,
|
|
170
|
-
stack: error.stack,
|
|
171
|
-
cause: error.cause ? createSerializableError(error.cause) : void 0
|
|
172
|
-
} : {
|
|
173
|
-
message: String(error),
|
|
174
|
-
name: "UnknownError"
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
107
|
// ../../lib/object/overrider.ts
|
|
179
108
|
function set(obj, path, value) {
|
|
180
109
|
const keys = path.split(".");
|
|
@@ -205,92 +134,6 @@ var objectOverrider = (defaultObj) => (obj) => {
|
|
|
205
134
|
return createBuilder([]);
|
|
206
135
|
};
|
|
207
136
|
|
|
208
|
-
// ../../lib/retry/strategy.ts
|
|
209
|
-
function withRetry(fn, strategy, options) {
|
|
210
|
-
return {
|
|
211
|
-
run: async (...args) => {
|
|
212
|
-
let attempts = 0;
|
|
213
|
-
while (true) {
|
|
214
|
-
if (options?.abortSignal?.aborted) {
|
|
215
|
-
return {
|
|
216
|
-
state: "aborted",
|
|
217
|
-
reason: options.abortSignal.reason
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
attempts++;
|
|
221
|
-
let result;
|
|
222
|
-
try {
|
|
223
|
-
result = await fn(...args);
|
|
224
|
-
if (options?.shouldRetryOnResult === void 0 || !await options.shouldRetryOnResult(result)) {
|
|
225
|
-
return {
|
|
226
|
-
state: "completed",
|
|
227
|
-
result,
|
|
228
|
-
attempts
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
} catch (err) {
|
|
232
|
-
if (options?.shouldNotRetryOnError !== void 0 && await options.shouldNotRetryOnError(err)) {
|
|
233
|
-
throw err;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
const retryParams = getRetryParams(attempts, strategy);
|
|
237
|
-
if (!retryParams.retriesLeft) {
|
|
238
|
-
return {
|
|
239
|
-
state: "timeout"
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
await delay(retryParams.delayMs, { abortSignal: options?.abortSignal });
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
function getRetryParams(attempts, strategy) {
|
|
248
|
-
const strategyType = strategy.type;
|
|
249
|
-
switch (strategyType) {
|
|
250
|
-
case "never":
|
|
251
|
-
return {
|
|
252
|
-
retriesLeft: false
|
|
253
|
-
};
|
|
254
|
-
case "fixed":
|
|
255
|
-
if (attempts >= strategy.maxAttempts) {
|
|
256
|
-
return {
|
|
257
|
-
retriesLeft: false
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
return {
|
|
261
|
-
retriesLeft: true,
|
|
262
|
-
delayMs: strategy.delayMs
|
|
263
|
-
};
|
|
264
|
-
case "exponential": {
|
|
265
|
-
if (attempts >= strategy.maxAttempts) {
|
|
266
|
-
return {
|
|
267
|
-
retriesLeft: false
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
const delayMs = strategy.baseDelayMs * (strategy.factor ?? 2) ** (attempts - 1);
|
|
271
|
-
return {
|
|
272
|
-
retriesLeft: true,
|
|
273
|
-
delayMs: Math.min(delayMs, strategy.maxDelayMs ?? Number.POSITIVE_INFINITY)
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
case "jittered": {
|
|
277
|
-
if (attempts >= strategy.maxAttempts) {
|
|
278
|
-
return {
|
|
279
|
-
retriesLeft: false
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
const base = strategy.baseDelayMs * (strategy.jitterFactor ?? 2) ** (attempts - 1);
|
|
283
|
-
const delayMs = Math.random() * base;
|
|
284
|
-
return {
|
|
285
|
-
retriesLeft: true,
|
|
286
|
-
delayMs: Math.min(delayMs, strategy.maxDelayMs ?? Number.POSITIVE_INFINITY)
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
default:
|
|
290
|
-
return strategyType;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
137
|
// run/event.ts
|
|
295
138
|
import { INTERNAL } from "@aikirun/types/symbols";
|
|
296
139
|
import { SchemaValidationError } from "@aikirun/types/validator";
|
|
@@ -298,7 +141,7 @@ import {
|
|
|
298
141
|
WorkflowRunFailedError,
|
|
299
142
|
WorkflowRunRevisionConflictError,
|
|
300
143
|
WorkflowRunSuspendedError
|
|
301
|
-
} from "@aikirun/types/workflow-run";
|
|
144
|
+
} from "@aikirun/types/workflow-run-error";
|
|
302
145
|
function event(params) {
|
|
303
146
|
return {
|
|
304
147
|
// biome-ignore lint/style/useNamingConvention: phantom type marker
|
|
@@ -388,10 +231,10 @@ function createEventSenders(api, workflowRunId, eventsDefinition, logger) {
|
|
|
388
231
|
return senders;
|
|
389
232
|
}
|
|
390
233
|
function createEventSender(api, workflowRunId, eventName, schema, logger, options) {
|
|
391
|
-
const
|
|
392
|
-
const createBuilder = (
|
|
393
|
-
opt: (path, value) => createBuilder(
|
|
394
|
-
send: (...args) => createEventSender(api, workflowRunId, eventName, schema, logger,
|
|
234
|
+
const optionsOverrider = objectOverrider(options ?? {});
|
|
235
|
+
const createBuilder = (optionsBuilder) => ({
|
|
236
|
+
opt: (path, value) => createBuilder(optionsBuilder.with(path, value)),
|
|
237
|
+
send: (...args) => createEventSender(api, workflowRunId, eventName, schema, logger, optionsBuilder.build()).send(...args)
|
|
395
238
|
});
|
|
396
239
|
async function send(...args) {
|
|
397
240
|
let data = args[0];
|
|
@@ -415,7 +258,7 @@ function createEventSender(api, workflowRunId, eventName, schema, logger, option
|
|
|
415
258
|
});
|
|
416
259
|
}
|
|
417
260
|
return {
|
|
418
|
-
with: () => createBuilder(
|
|
261
|
+
with: () => createBuilder(optionsOverrider()),
|
|
419
262
|
send
|
|
420
263
|
};
|
|
421
264
|
}
|
|
@@ -433,19 +276,21 @@ function createEventMulticasters(workflowName, workflowVersionId, eventsDefiniti
|
|
|
433
276
|
return senders;
|
|
434
277
|
}
|
|
435
278
|
function createEventMulticaster(workflowName, workflowVersionId, eventName, schema, options) {
|
|
436
|
-
const
|
|
437
|
-
const createBuilder = (
|
|
438
|
-
opt: (path, value) => createBuilder(
|
|
439
|
-
send: (client, runId, ...args) => createEventMulticaster(workflowName, workflowVersionId, eventName, schema,
|
|
279
|
+
const optionsOverrider = objectOverrider(options ?? {});
|
|
280
|
+
const createBuilder = (optionsBuilder) => ({
|
|
281
|
+
opt: (path, value) => createBuilder(optionsBuilder.with(path, value)),
|
|
282
|
+
send: (client, runId, ...args) => createEventMulticaster(workflowName, workflowVersionId, eventName, schema, optionsBuilder.build()).send(
|
|
440
283
|
client,
|
|
441
284
|
runId,
|
|
442
285
|
...args
|
|
443
286
|
),
|
|
444
|
-
sendByReferenceId: (client, referenceId, ...args) => createEventMulticaster(
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
287
|
+
sendByReferenceId: (client, referenceId, ...args) => createEventMulticaster(
|
|
288
|
+
workflowName,
|
|
289
|
+
workflowVersionId,
|
|
290
|
+
eventName,
|
|
291
|
+
schema,
|
|
292
|
+
optionsBuilder.build()
|
|
293
|
+
).sendByReferenceId(client, referenceId, ...args)
|
|
449
294
|
});
|
|
450
295
|
async function send(client, runId, ...args) {
|
|
451
296
|
let data = args[0];
|
|
@@ -520,18 +365,130 @@ function createEventMulticaster(workflowName, workflowVersionId, eventName, sche
|
|
|
520
365
|
});
|
|
521
366
|
}
|
|
522
367
|
return {
|
|
523
|
-
with: () => createBuilder(
|
|
368
|
+
with: () => createBuilder(optionsOverrider()),
|
|
524
369
|
send,
|
|
525
370
|
sendByReferenceId
|
|
526
371
|
};
|
|
527
372
|
}
|
|
528
373
|
|
|
374
|
+
// run/execute.ts
|
|
375
|
+
import { INTERNAL as INTERNAL4 } from "@aikirun/types/symbols";
|
|
376
|
+
import {
|
|
377
|
+
NonDeterminismError,
|
|
378
|
+
WorkflowRunFailedError as WorkflowRunFailedError2,
|
|
379
|
+
WorkflowRunNotExecutableError as WorkflowRunNotExecutableError2,
|
|
380
|
+
WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError4,
|
|
381
|
+
WorkflowRunSuspendedError as WorkflowRunSuspendedError3
|
|
382
|
+
} from "@aikirun/types/workflow-run-error";
|
|
383
|
+
|
|
384
|
+
// ../../lib/async/delay.ts
|
|
385
|
+
function delay(ms, options) {
|
|
386
|
+
const abortSignal = options?.abortSignal;
|
|
387
|
+
if (abortSignal?.aborted) {
|
|
388
|
+
return Promise.reject(abortSignal.reason);
|
|
389
|
+
}
|
|
390
|
+
return new Promise((resolve, reject) => {
|
|
391
|
+
const abort = () => {
|
|
392
|
+
clearTimeout(timeout);
|
|
393
|
+
reject(abortSignal?.reason);
|
|
394
|
+
};
|
|
395
|
+
const timeout = setTimeout(() => {
|
|
396
|
+
abortSignal?.removeEventListener("abort", abort);
|
|
397
|
+
resolve();
|
|
398
|
+
}, ms);
|
|
399
|
+
abortSignal?.addEventListener("abort", abort, { once: true });
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ../../lib/retry/strategy.ts
|
|
404
|
+
function withRetry(fn, strategy, options) {
|
|
405
|
+
return {
|
|
406
|
+
run: async (...args) => {
|
|
407
|
+
let attempts = 0;
|
|
408
|
+
while (true) {
|
|
409
|
+
if (options?.abortSignal?.aborted) {
|
|
410
|
+
return {
|
|
411
|
+
state: "aborted",
|
|
412
|
+
reason: options.abortSignal.reason
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
attempts++;
|
|
416
|
+
let result;
|
|
417
|
+
try {
|
|
418
|
+
result = await fn(...args);
|
|
419
|
+
if (options?.shouldRetryOnResult === void 0 || !await options.shouldRetryOnResult(result)) {
|
|
420
|
+
return {
|
|
421
|
+
state: "completed",
|
|
422
|
+
result,
|
|
423
|
+
attempts
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
} catch (err) {
|
|
427
|
+
if (options?.shouldNotRetryOnError !== void 0 && await options.shouldNotRetryOnError(err)) {
|
|
428
|
+
throw err;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const retryParams = getRetryParams(attempts, strategy);
|
|
432
|
+
if (!retryParams.retriesLeft) {
|
|
433
|
+
return {
|
|
434
|
+
state: "timeout"
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
await delay(retryParams.delayMs, { abortSignal: options?.abortSignal });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function getRetryParams(attempts, strategy) {
|
|
443
|
+
const strategyType = strategy.type;
|
|
444
|
+
switch (strategyType) {
|
|
445
|
+
case "never":
|
|
446
|
+
return {
|
|
447
|
+
retriesLeft: false
|
|
448
|
+
};
|
|
449
|
+
case "fixed":
|
|
450
|
+
if (attempts >= strategy.maxAttempts) {
|
|
451
|
+
return {
|
|
452
|
+
retriesLeft: false
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
retriesLeft: true,
|
|
457
|
+
delayMs: strategy.delayMs
|
|
458
|
+
};
|
|
459
|
+
case "exponential": {
|
|
460
|
+
if (attempts >= strategy.maxAttempts) {
|
|
461
|
+
return {
|
|
462
|
+
retriesLeft: false
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const delayMs = strategy.baseDelayMs * (strategy.factor ?? 2) ** (attempts - 1);
|
|
466
|
+
return {
|
|
467
|
+
retriesLeft: true,
|
|
468
|
+
delayMs: Math.min(delayMs, strategy.maxDelayMs ?? Number.POSITIVE_INFINITY)
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
case "jittered": {
|
|
472
|
+
if (attempts >= strategy.maxAttempts) {
|
|
473
|
+
return {
|
|
474
|
+
retriesLeft: false
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
const base = strategy.baseDelayMs * (strategy.jitterFactor ?? 2) ** (attempts - 1);
|
|
478
|
+
const delayMs = Math.random() * base;
|
|
479
|
+
return {
|
|
480
|
+
retriesLeft: true,
|
|
481
|
+
delayMs: Math.min(delayMs, strategy.maxDelayMs ?? Number.POSITIVE_INFINITY)
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
default:
|
|
485
|
+
return strategyType;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
529
489
|
// run/handle.ts
|
|
530
490
|
import { INTERNAL as INTERNAL2 } from "@aikirun/types/symbols";
|
|
531
|
-
import {
|
|
532
|
-
WorkflowRunNotExecutableError,
|
|
533
|
-
WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError2
|
|
534
|
-
} from "@aikirun/types/workflow-run";
|
|
491
|
+
import { WorkflowRunNotExecutableError, WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError2 } from "@aikirun/types/workflow-run-error";
|
|
535
492
|
async function workflowRunHandle(client, runOrId, eventsDefinition, logger) {
|
|
536
493
|
const run = typeof runOrId !== "string" ? runOrId : (await client.api.workflowRun.getByIdV1({ id: runOrId })).run;
|
|
537
494
|
return new WorkflowRunHandleImpl(
|
|
@@ -581,12 +538,13 @@ var WorkflowRunHandleImpl = class {
|
|
|
581
538
|
const delayMs = options?.interval ? toMilliseconds(options.interval) : 1e3;
|
|
582
539
|
const maxAttempts = options?.timeout ? Math.ceil(toMilliseconds(options.timeout) / delayMs) : Number.POSITIVE_INFINITY;
|
|
583
540
|
const retryStrategy = { type: "fixed", maxAttempts, delayMs };
|
|
584
|
-
|
|
541
|
+
let afterStateTransitionId = this._run.stateTransitionId;
|
|
585
542
|
const hasTerminated = async () => {
|
|
586
|
-
const { terminated } = await this.api.workflowRun.hasTerminatedV1({
|
|
543
|
+
const { terminated, latestStateTransitionId } = await this.api.workflowRun.hasTerminatedV1({
|
|
587
544
|
id: this._run.id,
|
|
588
545
|
afterStateTransitionId
|
|
589
546
|
});
|
|
547
|
+
afterStateTransitionId = latestStateTransitionId;
|
|
590
548
|
return terminated;
|
|
591
549
|
};
|
|
592
550
|
const shouldRetryOnResult = async (terminated) => !terminated;
|
|
@@ -706,10 +664,10 @@ function createReplayManifest(run) {
|
|
|
706
664
|
if (nextIndex >= taskCount) {
|
|
707
665
|
return void 0;
|
|
708
666
|
}
|
|
709
|
-
const
|
|
667
|
+
const task2 = taskQueues[address].tasks[nextIndex];
|
|
710
668
|
nextTaskIndexByAddress[address] = nextIndex + 1;
|
|
711
669
|
consumedEntries++;
|
|
712
|
-
return
|
|
670
|
+
return task2;
|
|
713
671
|
},
|
|
714
672
|
consumeNextChildWorkflowRun(address) {
|
|
715
673
|
const childWorkflowRunCount = childWorkflowRunCountByAddress[address] ?? 0;
|
|
@@ -749,10 +707,7 @@ function createReplayManifest(run) {
|
|
|
749
707
|
|
|
750
708
|
// run/sleeper.ts
|
|
751
709
|
import { INTERNAL as INTERNAL3 } from "@aikirun/types/symbols";
|
|
752
|
-
import {
|
|
753
|
-
WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError3,
|
|
754
|
-
WorkflowRunSuspendedError as WorkflowRunSuspendedError2
|
|
755
|
-
} from "@aikirun/types/workflow-run";
|
|
710
|
+
import { WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError3, WorkflowRunSuspendedError as WorkflowRunSuspendedError2 } from "@aikirun/types/workflow-run-error";
|
|
756
711
|
var MAX_SLEEP_YEARS = 10;
|
|
757
712
|
var MAX_SLEEP_MS = MAX_SLEEP_YEARS * 365 * 24 * 60 * 60 * 1e3;
|
|
758
713
|
function createSleeper(handle, logger) {
|
|
@@ -831,9 +786,64 @@ function createSleeper(handle, logger) {
|
|
|
831
786
|
};
|
|
832
787
|
}
|
|
833
788
|
|
|
789
|
+
// run/execute.ts
|
|
790
|
+
async function executeWorkflowRun(params) {
|
|
791
|
+
const { client, workflowRun, workflowVersion, logger, options, heartbeat } = params;
|
|
792
|
+
let heartbeatInterval;
|
|
793
|
+
try {
|
|
794
|
+
if (heartbeat) {
|
|
795
|
+
heartbeatInterval = setInterval(async () => {
|
|
796
|
+
try {
|
|
797
|
+
await heartbeat();
|
|
798
|
+
logger.debug("Heartbeat sent");
|
|
799
|
+
} catch (error) {
|
|
800
|
+
logger.warn("Failed to send heartbeat", {
|
|
801
|
+
"aiki.error": error instanceof Error ? error.message : String(error)
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
}, options.heartbeatIntervalMs);
|
|
805
|
+
}
|
|
806
|
+
const eventsDefinition = workflowVersion[INTERNAL4].eventsDefinition;
|
|
807
|
+
const handle = await workflowRunHandle(client, workflowRun, eventsDefinition, logger);
|
|
808
|
+
const appContext = client[INTERNAL4].createContext ? client[INTERNAL4].createContext(workflowRun) : null;
|
|
809
|
+
await workflowVersion[INTERNAL4].handler(
|
|
810
|
+
{
|
|
811
|
+
id: workflowRun.id,
|
|
812
|
+
name: workflowRun.name,
|
|
813
|
+
versionId: workflowRun.versionId,
|
|
814
|
+
options: workflowRun.options ?? {},
|
|
815
|
+
logger,
|
|
816
|
+
sleep: createSleeper(handle, logger),
|
|
817
|
+
events: createEventWaiters(handle, eventsDefinition, logger),
|
|
818
|
+
[INTERNAL4]: {
|
|
819
|
+
handle,
|
|
820
|
+
replayManifest: createReplayManifest(workflowRun),
|
|
821
|
+
options: { spinThresholdMs: options.spinThresholdMs }
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
workflowRun.input,
|
|
825
|
+
appContext instanceof Promise ? await appContext : appContext
|
|
826
|
+
);
|
|
827
|
+
return true;
|
|
828
|
+
} catch (error) {
|
|
829
|
+
if (error instanceof WorkflowRunNotExecutableError2 || error instanceof WorkflowRunSuspendedError3 || error instanceof WorkflowRunFailedError2 || error instanceof WorkflowRunRevisionConflictError4 || error instanceof NonDeterminismError) {
|
|
830
|
+
return true;
|
|
831
|
+
}
|
|
832
|
+
logger.error("Unexpected error during workflow execution", {
|
|
833
|
+
"aiki.error": error instanceof Error ? error.message : String(error),
|
|
834
|
+
"aiki.stack": error instanceof Error ? error.stack : void 0
|
|
835
|
+
});
|
|
836
|
+
return false;
|
|
837
|
+
} finally {
|
|
838
|
+
if (heartbeatInterval) {
|
|
839
|
+
clearInterval(heartbeatInterval);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
834
844
|
// schedule.ts
|
|
835
845
|
function schedule(params) {
|
|
836
|
-
async function
|
|
846
|
+
async function activateWithOptions(client, workflow2, options, ...args) {
|
|
837
847
|
const input = args[0];
|
|
838
848
|
let scheduleSpec;
|
|
839
849
|
if (params.type === "interval") {
|
|
@@ -872,52 +882,338 @@ function schedule(params) {
|
|
|
872
882
|
}
|
|
873
883
|
};
|
|
874
884
|
}
|
|
875
|
-
function createBuilder(
|
|
885
|
+
function createBuilder(optionsBuilder) {
|
|
876
886
|
return {
|
|
877
|
-
opt: (path, value) => createBuilder(
|
|
887
|
+
opt: (path, value) => createBuilder(optionsBuilder.with(path, value)),
|
|
878
888
|
async activate(client, workflow2, ...args) {
|
|
879
|
-
return
|
|
889
|
+
return activateWithOptions(client, workflow2, optionsBuilder.build(), ...args);
|
|
880
890
|
}
|
|
881
891
|
};
|
|
882
892
|
}
|
|
883
893
|
return {
|
|
884
894
|
...params,
|
|
885
895
|
with() {
|
|
886
|
-
const
|
|
887
|
-
return createBuilder(
|
|
896
|
+
const optionsOverrider = objectOverrider({});
|
|
897
|
+
return createBuilder(optionsOverrider());
|
|
888
898
|
},
|
|
889
899
|
async activate(client, workflow2, ...args) {
|
|
890
|
-
return
|
|
900
|
+
return activateWithOptions(client, workflow2, {}, ...args);
|
|
891
901
|
}
|
|
892
902
|
};
|
|
893
903
|
}
|
|
894
904
|
|
|
895
|
-
//
|
|
896
|
-
import {
|
|
905
|
+
// system/cancel-child-runs.ts
|
|
906
|
+
import { NON_TERMINAL_WORKFLOW_RUN_STATUSES } from "@aikirun/types/workflow-run";
|
|
897
907
|
|
|
898
908
|
// ../../lib/address/index.ts
|
|
909
|
+
function getTaskAddress(name, inputHash) {
|
|
910
|
+
return `${name}:${inputHash}`;
|
|
911
|
+
}
|
|
899
912
|
function getWorkflowRunAddress(name, versionId, referenceId) {
|
|
900
913
|
return `${name}:${versionId}:${referenceId}`;
|
|
901
914
|
}
|
|
902
915
|
|
|
903
|
-
//
|
|
916
|
+
// ../../lib/crypto/hash.ts
|
|
917
|
+
import { createHash } from "crypto";
|
|
918
|
+
|
|
919
|
+
// ../../lib/json/stable-stringify.ts
|
|
920
|
+
function stableStringify(value) {
|
|
921
|
+
return stringifyValue(value);
|
|
922
|
+
}
|
|
923
|
+
function stringifyValue(value) {
|
|
924
|
+
if (value === null || value === void 0) {
|
|
925
|
+
return "null";
|
|
926
|
+
}
|
|
927
|
+
if (typeof value !== "object") {
|
|
928
|
+
return JSON.stringify(value);
|
|
929
|
+
}
|
|
930
|
+
if (Array.isArray(value)) {
|
|
931
|
+
return `[${value.map(stringifyValue).join(",")}]`;
|
|
932
|
+
}
|
|
933
|
+
const keys = Object.keys(value).sort();
|
|
934
|
+
const pairs = [];
|
|
935
|
+
for (const key of keys) {
|
|
936
|
+
const keyValue = value[key];
|
|
937
|
+
if (keyValue !== void 0) {
|
|
938
|
+
pairs.push(`${JSON.stringify(key)}:${stringifyValue(keyValue)}`);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return `{${pairs.join(",")}}`;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// ../../lib/crypto/hash.ts
|
|
945
|
+
async function sha256(input) {
|
|
946
|
+
const data = new TextEncoder().encode(input);
|
|
947
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
948
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
949
|
+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
950
|
+
}
|
|
951
|
+
async function hashInput(input) {
|
|
952
|
+
return sha256(stableStringify({ input }));
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// ../../lib/error/serializable.ts
|
|
956
|
+
function createSerializableError(error) {
|
|
957
|
+
return error instanceof Error ? {
|
|
958
|
+
message: error.message,
|
|
959
|
+
name: error.name,
|
|
960
|
+
stack: error.stack,
|
|
961
|
+
cause: error.cause ? createSerializableError(error.cause) : void 0
|
|
962
|
+
} : {
|
|
963
|
+
message: String(error),
|
|
964
|
+
name: "UnknownError"
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// ../task/task.ts
|
|
904
969
|
import { INTERNAL as INTERNAL5 } from "@aikirun/types/symbols";
|
|
905
|
-
import { TaskFailedError } from "@aikirun/types/task";
|
|
906
|
-
import { SchemaValidationError as SchemaValidationError2 } from "@aikirun/types/validator";
|
|
970
|
+
import { TaskFailedError } from "@aikirun/types/task-error";
|
|
907
971
|
import {
|
|
908
|
-
NonDeterminismError,
|
|
909
|
-
WorkflowRunFailedError as
|
|
972
|
+
NonDeterminismError as NonDeterminismError2,
|
|
973
|
+
WorkflowRunFailedError as WorkflowRunFailedError3,
|
|
910
974
|
WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError5,
|
|
911
975
|
WorkflowRunSuspendedError as WorkflowRunSuspendedError4
|
|
912
|
-
} from "@aikirun/types/workflow-run";
|
|
976
|
+
} from "@aikirun/types/workflow-run-error";
|
|
977
|
+
function task(params) {
|
|
978
|
+
return new TaskImpl(params);
|
|
979
|
+
}
|
|
980
|
+
var TaskImpl = class {
|
|
981
|
+
constructor(params) {
|
|
982
|
+
this.params = params;
|
|
983
|
+
this.name = params.name;
|
|
984
|
+
}
|
|
985
|
+
name;
|
|
986
|
+
with() {
|
|
987
|
+
const startOptions = this.params.options ?? {};
|
|
988
|
+
const startOptionsOverrider = objectOverrider(startOptions);
|
|
989
|
+
return new TaskBuilderImpl(this, startOptionsOverrider());
|
|
990
|
+
}
|
|
991
|
+
async start(run, ...args) {
|
|
992
|
+
return this.startWithOptions(run, this.params.options ?? {}, ...args);
|
|
993
|
+
}
|
|
994
|
+
async startWithOptions(run, startOptions, ...args) {
|
|
995
|
+
const handle = run[INTERNAL5].handle;
|
|
996
|
+
handle[INTERNAL5].assertExecutionAllowed();
|
|
997
|
+
const inputRaw = args[0];
|
|
998
|
+
const input = await this.parse(handle, this.params.schema?.input, inputRaw, run.logger);
|
|
999
|
+
const inputHash = await hashInput(input);
|
|
1000
|
+
const address = getTaskAddress(this.name, inputHash);
|
|
1001
|
+
const replayManifest = run[INTERNAL5].replayManifest;
|
|
1002
|
+
if (replayManifest.hasUnconsumedEntries()) {
|
|
1003
|
+
const existingTaskInfo = replayManifest.consumeNextTask(address);
|
|
1004
|
+
if (existingTaskInfo) {
|
|
1005
|
+
return this.getExistingTaskResult(run, handle, startOptions, input, existingTaskInfo);
|
|
1006
|
+
}
|
|
1007
|
+
await this.throwNonDeterminismError(run, handle, inputHash, replayManifest.getUnconsumedEntries());
|
|
1008
|
+
}
|
|
1009
|
+
const attempts = 1;
|
|
1010
|
+
const retryStrategy = startOptions.retry ?? { type: "never" };
|
|
1011
|
+
const taskInfo = await handle[INTERNAL5].transitionTaskState({
|
|
1012
|
+
type: "create",
|
|
1013
|
+
taskName: this.name,
|
|
1014
|
+
options: startOptions,
|
|
1015
|
+
taskState: { status: "running", attempts, input }
|
|
1016
|
+
});
|
|
1017
|
+
const logger = run.logger.child({
|
|
1018
|
+
"aiki.taskName": this.name,
|
|
1019
|
+
"aiki.taskId": taskInfo.id
|
|
1020
|
+
});
|
|
1021
|
+
logger.info("Task started", { "aiki.attempts": attempts });
|
|
1022
|
+
const { output, lastAttempt } = await this.tryExecuteTask(
|
|
1023
|
+
handle,
|
|
1024
|
+
input,
|
|
1025
|
+
taskInfo.id,
|
|
1026
|
+
retryStrategy,
|
|
1027
|
+
attempts,
|
|
1028
|
+
run[INTERNAL5].options.spinThresholdMs,
|
|
1029
|
+
logger
|
|
1030
|
+
);
|
|
1031
|
+
await handle[INTERNAL5].transitionTaskState({
|
|
1032
|
+
taskId: taskInfo.id,
|
|
1033
|
+
taskState: { status: "completed", attempts: lastAttempt, output }
|
|
1034
|
+
});
|
|
1035
|
+
logger.info("Task complete", { "aiki.attempts": lastAttempt });
|
|
1036
|
+
return output;
|
|
1037
|
+
}
|
|
1038
|
+
async getExistingTaskResult(run, handle, startOptions, input, existingTaskInfo) {
|
|
1039
|
+
const existingTaskState = existingTaskInfo.state;
|
|
1040
|
+
if (existingTaskState.status === "completed") {
|
|
1041
|
+
return this.parse(handle, this.params.schema?.output, existingTaskState.output, run.logger);
|
|
1042
|
+
}
|
|
1043
|
+
if (existingTaskState.status === "failed") {
|
|
1044
|
+
throw new TaskFailedError(
|
|
1045
|
+
existingTaskInfo.id,
|
|
1046
|
+
existingTaskState.attempts,
|
|
1047
|
+
existingTaskState.error.message
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
existingTaskState.status;
|
|
1051
|
+
const attempts = existingTaskState.attempts;
|
|
1052
|
+
const retryStrategy = startOptions.retry ?? { type: "never" };
|
|
1053
|
+
this.assertRetryAllowed(existingTaskInfo.id, attempts, retryStrategy, run.logger);
|
|
1054
|
+
run.logger.debug("Retrying task", {
|
|
1055
|
+
"aiki.taskName": this.name,
|
|
1056
|
+
"aiki.taskId": existingTaskInfo.id,
|
|
1057
|
+
"aiki.attempts": attempts,
|
|
1058
|
+
"aiki.taskStatus": existingTaskState.status
|
|
1059
|
+
});
|
|
1060
|
+
return this.retryAndExecute(run, handle, input, existingTaskInfo.id, startOptions, retryStrategy, attempts);
|
|
1061
|
+
}
|
|
1062
|
+
async throwNonDeterminismError(run, handle, inputHash, unconsumedManifestEntries) {
|
|
1063
|
+
run.logger.error("Replay divergence", {
|
|
1064
|
+
"aiki.taskName": this.name,
|
|
1065
|
+
"aiki.inputHash": inputHash,
|
|
1066
|
+
"aiki.unconsumedManifestEntries": unconsumedManifestEntries
|
|
1067
|
+
});
|
|
1068
|
+
const error = new NonDeterminismError2(run.id, handle.run.attempts, unconsumedManifestEntries);
|
|
1069
|
+
await handle[INTERNAL5].transitionState({
|
|
1070
|
+
status: "failed",
|
|
1071
|
+
cause: "self",
|
|
1072
|
+
error: createSerializableError(error)
|
|
1073
|
+
});
|
|
1074
|
+
throw error;
|
|
1075
|
+
}
|
|
1076
|
+
async retryAndExecute(run, handle, input, taskId, startOptions, retryStrategy, previousAttempts) {
|
|
1077
|
+
const attempts = previousAttempts + 1;
|
|
1078
|
+
const taskInfo = await handle[INTERNAL5].transitionTaskState({
|
|
1079
|
+
type: "retry",
|
|
1080
|
+
taskId,
|
|
1081
|
+
options: startOptions,
|
|
1082
|
+
taskState: { status: "running", attempts, input }
|
|
1083
|
+
});
|
|
1084
|
+
const logger = run.logger.child({
|
|
1085
|
+
"aiki.taskName": this.name,
|
|
1086
|
+
"aiki.taskId": taskInfo.id
|
|
1087
|
+
});
|
|
1088
|
+
logger.info("Task started", { "aiki.attempts": attempts });
|
|
1089
|
+
const { output, lastAttempt } = await this.tryExecuteTask(
|
|
1090
|
+
handle,
|
|
1091
|
+
input,
|
|
1092
|
+
taskInfo.id,
|
|
1093
|
+
retryStrategy,
|
|
1094
|
+
attempts,
|
|
1095
|
+
run[INTERNAL5].options.spinThresholdMs,
|
|
1096
|
+
logger
|
|
1097
|
+
);
|
|
1098
|
+
await handle[INTERNAL5].transitionTaskState({
|
|
1099
|
+
taskId: taskInfo.id,
|
|
1100
|
+
taskState: { status: "completed", attempts: lastAttempt, output }
|
|
1101
|
+
});
|
|
1102
|
+
logger.info("Task complete", { "aiki.attempts": lastAttempt });
|
|
1103
|
+
return output;
|
|
1104
|
+
}
|
|
1105
|
+
async tryExecuteTask(handle, input, taskId, retryStrategy, currentAttempt, spinThresholdMs, logger) {
|
|
1106
|
+
let attempts = currentAttempt;
|
|
1107
|
+
while (true) {
|
|
1108
|
+
try {
|
|
1109
|
+
const outputRaw = await this.params.handler(input);
|
|
1110
|
+
const output = await this.parse(handle, this.params.schema?.output, outputRaw, logger);
|
|
1111
|
+
return { output, lastAttempt: attempts };
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
if (error instanceof WorkflowRunSuspendedError4 || error instanceof WorkflowRunFailedError3 || error instanceof WorkflowRunRevisionConflictError5) {
|
|
1114
|
+
throw error;
|
|
1115
|
+
}
|
|
1116
|
+
const serializableError = createSerializableError(error);
|
|
1117
|
+
const retryParams = getRetryParams(attempts, retryStrategy);
|
|
1118
|
+
if (!retryParams.retriesLeft) {
|
|
1119
|
+
logger.error("Task failed", {
|
|
1120
|
+
"aiki.attempts": attempts,
|
|
1121
|
+
"aiki.reason": serializableError.message
|
|
1122
|
+
});
|
|
1123
|
+
await handle[INTERNAL5].transitionTaskState({
|
|
1124
|
+
taskId,
|
|
1125
|
+
taskState: { status: "failed", attempts, error: serializableError }
|
|
1126
|
+
});
|
|
1127
|
+
throw new TaskFailedError(taskId, attempts, serializableError.message);
|
|
1128
|
+
}
|
|
1129
|
+
logger.debug("Task failed. It will be retried", {
|
|
1130
|
+
"aiki.attempts": attempts,
|
|
1131
|
+
"aiki.nextAttemptInMs": retryParams.delayMs,
|
|
1132
|
+
"aiki.reason": serializableError.message
|
|
1133
|
+
});
|
|
1134
|
+
if (retryParams.delayMs <= spinThresholdMs) {
|
|
1135
|
+
await delay(retryParams.delayMs);
|
|
1136
|
+
attempts++;
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
await handle[INTERNAL5].transitionTaskState({
|
|
1140
|
+
taskId,
|
|
1141
|
+
taskState: {
|
|
1142
|
+
status: "awaiting_retry",
|
|
1143
|
+
attempts,
|
|
1144
|
+
error: serializableError,
|
|
1145
|
+
nextAttemptInMs: retryParams.delayMs
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
throw new WorkflowRunSuspendedError4(handle.run.id);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
assertRetryAllowed(taskId, attempts, retryStrategy, logger) {
|
|
1153
|
+
const retryParams = getRetryParams(attempts, retryStrategy);
|
|
1154
|
+
if (!retryParams.retriesLeft) {
|
|
1155
|
+
logger.error("Task retry not allowed", {
|
|
1156
|
+
"aiki.taskName": this.name,
|
|
1157
|
+
"aiki.taskId": taskId,
|
|
1158
|
+
"aiki.attempts": attempts
|
|
1159
|
+
});
|
|
1160
|
+
throw new TaskFailedError(taskId, attempts, "Task retry not allowed");
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
async parse(handle, schema, data, logger) {
|
|
1164
|
+
if (!schema) {
|
|
1165
|
+
return data;
|
|
1166
|
+
}
|
|
1167
|
+
const schemaValidation = schema["~standard"].validate(data);
|
|
1168
|
+
const schemaValidationResult = schemaValidation instanceof Promise ? await schemaValidation : schemaValidation;
|
|
1169
|
+
if (!schemaValidationResult.issues) {
|
|
1170
|
+
return schemaValidationResult.value;
|
|
1171
|
+
}
|
|
1172
|
+
logger.error("Invalid task data", { "aiki.issues": schemaValidationResult.issues });
|
|
1173
|
+
await handle[INTERNAL5].transitionState({
|
|
1174
|
+
status: "failed",
|
|
1175
|
+
cause: "self",
|
|
1176
|
+
error: {
|
|
1177
|
+
name: "SchemaValidationError",
|
|
1178
|
+
message: JSON.stringify(schemaValidationResult.issues)
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
throw new WorkflowRunFailedError3(handle.run.id, handle.run.attempts);
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
var TaskBuilderImpl = class _TaskBuilderImpl {
|
|
1185
|
+
constructor(task2, startOptionsBuilder) {
|
|
1186
|
+
this.task = task2;
|
|
1187
|
+
this.startOptionsBuilder = startOptionsBuilder;
|
|
1188
|
+
}
|
|
1189
|
+
opt(path, value) {
|
|
1190
|
+
return new _TaskBuilderImpl(this.task, this.startOptionsBuilder.with(path, value));
|
|
1191
|
+
}
|
|
1192
|
+
start(run, ...args) {
|
|
1193
|
+
return this.task.startWithOptions(run, this.startOptionsBuilder.build(), ...args);
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
|
|
1197
|
+
// workflow.ts
|
|
1198
|
+
import { INTERNAL as INTERNAL8 } from "@aikirun/types/symbols";
|
|
1199
|
+
|
|
1200
|
+
// workflow-version.ts
|
|
1201
|
+
import { INTERNAL as INTERNAL7 } from "@aikirun/types/symbols";
|
|
1202
|
+
import { TaskFailedError as TaskFailedError2 } from "@aikirun/types/task-error";
|
|
1203
|
+
import { SchemaValidationError as SchemaValidationError2 } from "@aikirun/types/validator";
|
|
1204
|
+
import {
|
|
1205
|
+
NonDeterminismError as NonDeterminismError3,
|
|
1206
|
+
WorkflowRunFailedError as WorkflowRunFailedError4,
|
|
1207
|
+
WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError7,
|
|
1208
|
+
WorkflowRunSuspendedError as WorkflowRunSuspendedError6
|
|
1209
|
+
} from "@aikirun/types/workflow-run-error";
|
|
913
1210
|
|
|
914
1211
|
// run/handle-child.ts
|
|
915
|
-
import { INTERNAL as
|
|
1212
|
+
import { INTERNAL as INTERNAL6 } from "@aikirun/types/symbols";
|
|
916
1213
|
import {
|
|
917
|
-
isTerminalWorkflowRunStatus
|
|
918
|
-
WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError4,
|
|
919
|
-
WorkflowRunSuspendedError as WorkflowRunSuspendedError3
|
|
1214
|
+
isTerminalWorkflowRunStatus
|
|
920
1215
|
} from "@aikirun/types/workflow-run";
|
|
1216
|
+
import { WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError6, WorkflowRunSuspendedError as WorkflowRunSuspendedError5 } from "@aikirun/types/workflow-run-error";
|
|
921
1217
|
async function childWorkflowRunHandle(client, run, parentRun, childWorkflowRunWaitQueues, logger, eventsDefinition) {
|
|
922
1218
|
const handle = await workflowRunHandle(client, run, eventsDefinition, logger);
|
|
923
1219
|
return {
|
|
@@ -929,7 +1225,7 @@ async function childWorkflowRunHandle(client, run, parentRun, childWorkflowRunWa
|
|
|
929
1225
|
pause: handle.pause.bind(handle),
|
|
930
1226
|
resume: handle.resume.bind(handle),
|
|
931
1227
|
awake: handle.awake.bind(handle),
|
|
932
|
-
[
|
|
1228
|
+
[INTERNAL6]: handle[INTERNAL6]
|
|
933
1229
|
};
|
|
934
1230
|
}
|
|
935
1231
|
function createStatusWaiter(handle, parentRun, childWorkflowRunWaitQueues, logger) {
|
|
@@ -939,7 +1235,7 @@ function createStatusWaiter(handle, parentRun, childWorkflowRunWaitQueues, logge
|
|
|
939
1235
|
failed: 0
|
|
940
1236
|
};
|
|
941
1237
|
async function waitForStatus(expectedStatus, options) {
|
|
942
|
-
const parentRunHandle = parentRun[
|
|
1238
|
+
const parentRunHandle = parentRun[INTERNAL6].handle;
|
|
943
1239
|
const nextIndex = nextIndexByStatus[expectedStatus];
|
|
944
1240
|
const { run } = handle;
|
|
945
1241
|
const childWorkflowRunWaits = childWorkflowRunWaitQueues[expectedStatus].childWorkflowRunWaits;
|
|
@@ -975,7 +1271,7 @@ function createStatusWaiter(handle, parentRun, childWorkflowRunWaitQueues, logge
|
|
|
975
1271
|
}
|
|
976
1272
|
const timeoutInMs = options?.timeout && toMilliseconds(options.timeout);
|
|
977
1273
|
try {
|
|
978
|
-
await parentRunHandle[
|
|
1274
|
+
await parentRunHandle[INTERNAL6].transitionState({
|
|
979
1275
|
status: "awaiting_child_workflow",
|
|
980
1276
|
childWorkflowRunId: run.id,
|
|
981
1277
|
childWorkflowRunStatus: expectedStatus,
|
|
@@ -986,12 +1282,12 @@ function createStatusWaiter(handle, parentRun, childWorkflowRunWaitQueues, logge
|
|
|
986
1282
|
...timeoutInMs !== void 0 ? { "aiki.timeoutInMs": timeoutInMs } : {}
|
|
987
1283
|
});
|
|
988
1284
|
} catch (error) {
|
|
989
|
-
if (error instanceof
|
|
990
|
-
throw new
|
|
1285
|
+
if (error instanceof WorkflowRunRevisionConflictError6) {
|
|
1286
|
+
throw new WorkflowRunSuspendedError5(parentRun.id);
|
|
991
1287
|
}
|
|
992
1288
|
throw error;
|
|
993
1289
|
}
|
|
994
|
-
throw new
|
|
1290
|
+
throw new WorkflowRunSuspendedError5(parentRun.id);
|
|
995
1291
|
}
|
|
996
1292
|
return waitForStatus;
|
|
997
1293
|
}
|
|
@@ -1004,22 +1300,22 @@ var WorkflowVersionImpl = class {
|
|
|
1004
1300
|
this.params = params;
|
|
1005
1301
|
const eventsDefinition = this.params.events ?? {};
|
|
1006
1302
|
this.events = createEventMulticasters(this.name, this.versionId, eventsDefinition);
|
|
1007
|
-
this[
|
|
1303
|
+
this[INTERNAL7] = {
|
|
1008
1304
|
eventsDefinition,
|
|
1009
1305
|
handler: this.handler.bind(this)
|
|
1010
1306
|
};
|
|
1011
1307
|
}
|
|
1012
1308
|
events;
|
|
1013
|
-
[
|
|
1309
|
+
[INTERNAL7];
|
|
1014
1310
|
with() {
|
|
1015
|
-
const
|
|
1016
|
-
const
|
|
1017
|
-
return new WorkflowBuilderImpl(this,
|
|
1311
|
+
const startOptions = this.params.options ?? {};
|
|
1312
|
+
const startOptionsOverrider = objectOverrider(startOptions);
|
|
1313
|
+
return new WorkflowBuilderImpl(this, startOptionsOverrider());
|
|
1018
1314
|
}
|
|
1019
1315
|
async start(client, ...args) {
|
|
1020
|
-
return this.
|
|
1316
|
+
return this.startWithOptions(client, this.params.options ?? {}, ...args);
|
|
1021
1317
|
}
|
|
1022
|
-
async
|
|
1318
|
+
async startWithOptions(client, startOptions, ...args) {
|
|
1023
1319
|
let input = args[0];
|
|
1024
1320
|
const schema = this.params.schema?.input;
|
|
1025
1321
|
if (schema) {
|
|
@@ -1035,28 +1331,28 @@ var WorkflowVersionImpl = class {
|
|
|
1035
1331
|
name: this.name,
|
|
1036
1332
|
versionId: this.versionId,
|
|
1037
1333
|
input,
|
|
1038
|
-
options:
|
|
1334
|
+
options: startOptions
|
|
1039
1335
|
});
|
|
1040
1336
|
client.logger.info("Created workflow", {
|
|
1041
1337
|
"aiki.workflowName": this.name,
|
|
1042
1338
|
"aiki.workflowVersionId": this.versionId,
|
|
1043
1339
|
"aiki.workflowRunId": id
|
|
1044
1340
|
});
|
|
1045
|
-
return workflowRunHandle(client, id, this[
|
|
1341
|
+
return workflowRunHandle(client, id, this[INTERNAL7].eventsDefinition);
|
|
1046
1342
|
}
|
|
1047
1343
|
async startAsChild(parentRun, ...args) {
|
|
1048
|
-
return this.
|
|
1344
|
+
return this.startAsChildWithOptions(parentRun, this.params.options ?? {}, ...args);
|
|
1049
1345
|
}
|
|
1050
|
-
async
|
|
1051
|
-
const parentRunHandle = parentRun[
|
|
1052
|
-
parentRunHandle[
|
|
1053
|
-
const { client } = parentRunHandle[
|
|
1346
|
+
async startAsChildWithOptions(parentRun, startOptions, ...args) {
|
|
1347
|
+
const parentRunHandle = parentRun[INTERNAL7].handle;
|
|
1348
|
+
parentRunHandle[INTERNAL7].assertExecutionAllowed();
|
|
1349
|
+
const { client } = parentRunHandle[INTERNAL7];
|
|
1054
1350
|
const inputRaw = args[0];
|
|
1055
1351
|
const input = await this.parse(parentRunHandle, this.params.schema?.input, inputRaw, parentRun.logger);
|
|
1056
1352
|
const inputHash = await hashInput(input);
|
|
1057
|
-
const referenceId =
|
|
1353
|
+
const referenceId = startOptions.reference?.id;
|
|
1058
1354
|
const address = getWorkflowRunAddress(this.name, this.versionId, referenceId ?? inputHash);
|
|
1059
|
-
const replayManifest = parentRun[
|
|
1355
|
+
const replayManifest = parentRun[INTERNAL7].replayManifest;
|
|
1060
1356
|
if (replayManifest.hasUnconsumedEntries()) {
|
|
1061
1357
|
const existingRunInfo = replayManifest.consumeNextChildWorkflowRun(address);
|
|
1062
1358
|
if (existingRunInfo) {
|
|
@@ -1075,7 +1371,7 @@ var WorkflowVersionImpl = class {
|
|
|
1075
1371
|
parentRun,
|
|
1076
1372
|
existingRunInfo.childWorkflowRunWaitQueues,
|
|
1077
1373
|
logger2,
|
|
1078
|
-
this[
|
|
1374
|
+
this[INTERNAL7].eventsDefinition
|
|
1079
1375
|
);
|
|
1080
1376
|
}
|
|
1081
1377
|
await this.throwNonDeterminismError(parentRun, parentRunHandle, inputHash, referenceId, replayManifest);
|
|
@@ -1086,7 +1382,7 @@ var WorkflowVersionImpl = class {
|
|
|
1086
1382
|
versionId: this.versionId,
|
|
1087
1383
|
input,
|
|
1088
1384
|
parentWorkflowRunId: parentRun.id,
|
|
1089
|
-
options: shard === void 0 ?
|
|
1385
|
+
options: shard === void 0 ? startOptions : { ...startOptions, shard }
|
|
1090
1386
|
});
|
|
1091
1387
|
const { run: newRun } = await client.api.workflowRun.getByIdV1({ id: newRunId });
|
|
1092
1388
|
const logger = parentRun.logger.child({
|
|
@@ -1105,7 +1401,7 @@ var WorkflowVersionImpl = class {
|
|
|
1105
1401
|
failed: { childWorkflowRunWaits: [] }
|
|
1106
1402
|
},
|
|
1107
1403
|
logger,
|
|
1108
|
-
this[
|
|
1404
|
+
this[INTERNAL7].eventsDefinition
|
|
1109
1405
|
);
|
|
1110
1406
|
}
|
|
1111
1407
|
async throwNonDeterminismError(parentRun, parentRunHandle, inputHash, referenceId, manifest) {
|
|
@@ -1119,8 +1415,8 @@ var WorkflowVersionImpl = class {
|
|
|
1119
1415
|
logMeta["aiki.referenceId"] = referenceId;
|
|
1120
1416
|
}
|
|
1121
1417
|
parentRun.logger.error("Replay divergence", logMeta);
|
|
1122
|
-
const error = new
|
|
1123
|
-
await parentRunHandle[
|
|
1418
|
+
const error = new NonDeterminismError3(parentRun.id, parentRunHandle.run.attempts, unconsumedManifestEntries);
|
|
1419
|
+
await parentRunHandle[INTERNAL7].transitionState({
|
|
1124
1420
|
status: "failed",
|
|
1125
1421
|
cause: "self",
|
|
1126
1422
|
error: createSerializableError(error)
|
|
@@ -1128,7 +1424,7 @@ var WorkflowVersionImpl = class {
|
|
|
1128
1424
|
throw error;
|
|
1129
1425
|
}
|
|
1130
1426
|
async getHandleById(client, runId) {
|
|
1131
|
-
return workflowRunHandle(client, runId, this[
|
|
1427
|
+
return workflowRunHandle(client, runId, this[INTERNAL7].eventsDefinition);
|
|
1132
1428
|
}
|
|
1133
1429
|
async getHandleByReferenceId(client, referenceId) {
|
|
1134
1430
|
const { run } = await client.api.workflowRun.getByReferenceIdV1({
|
|
@@ -1136,39 +1432,39 @@ var WorkflowVersionImpl = class {
|
|
|
1136
1432
|
versionId: this.versionId,
|
|
1137
1433
|
referenceId
|
|
1138
1434
|
});
|
|
1139
|
-
return workflowRunHandle(client, run, this[
|
|
1435
|
+
return workflowRunHandle(client, run, this[INTERNAL7].eventsDefinition);
|
|
1140
1436
|
}
|
|
1141
1437
|
async handler(run, input, context) {
|
|
1142
1438
|
const { logger } = run;
|
|
1143
|
-
const { handle } = run[
|
|
1144
|
-
handle[
|
|
1145
|
-
const retryStrategy = this.params.
|
|
1439
|
+
const { handle } = run[INTERNAL7];
|
|
1440
|
+
handle[INTERNAL7].assertExecutionAllowed();
|
|
1441
|
+
const retryStrategy = this.params.options?.retry ?? { type: "never" };
|
|
1146
1442
|
const state = handle.run.state;
|
|
1147
1443
|
if (state.status === "queued" && state.reason === "retry") {
|
|
1148
1444
|
await this.assertRetryAllowed(handle, retryStrategy, logger);
|
|
1149
1445
|
}
|
|
1150
1446
|
logger.info("Starting workflow");
|
|
1151
|
-
await handle[
|
|
1447
|
+
await handle[INTERNAL7].transitionState({ status: "running" });
|
|
1152
1448
|
const output = await this.tryExecuteWorkflow(input, run, context, retryStrategy);
|
|
1153
|
-
await handle[
|
|
1449
|
+
await handle[INTERNAL7].transitionState({ status: "completed", output });
|
|
1154
1450
|
logger.info("Workflow complete");
|
|
1155
1451
|
}
|
|
1156
1452
|
async tryExecuteWorkflow(input, run, context, retryStrategy) {
|
|
1157
|
-
const { handle } = run[
|
|
1453
|
+
const { handle } = run[INTERNAL7];
|
|
1158
1454
|
while (true) {
|
|
1159
1455
|
try {
|
|
1160
1456
|
const outputRaw = await this.params.handler(run, input, context);
|
|
1161
1457
|
const output = await this.parse(handle, this.params.schema?.output, outputRaw, run.logger);
|
|
1162
1458
|
return output;
|
|
1163
1459
|
} catch (error) {
|
|
1164
|
-
if (error instanceof
|
|
1460
|
+
if (error instanceof WorkflowRunSuspendedError6 || error instanceof WorkflowRunFailedError4 || error instanceof WorkflowRunRevisionConflictError7 || error instanceof NonDeterminismError3) {
|
|
1165
1461
|
throw error;
|
|
1166
1462
|
}
|
|
1167
1463
|
const attempts = handle.run.attempts;
|
|
1168
1464
|
const retryParams = getRetryParams(attempts, retryStrategy);
|
|
1169
1465
|
if (!retryParams.retriesLeft) {
|
|
1170
1466
|
const failedState = this.createFailedState(error);
|
|
1171
|
-
await handle[
|
|
1467
|
+
await handle[INTERNAL7].transitionState(failedState);
|
|
1172
1468
|
const logMeta2 = {};
|
|
1173
1469
|
for (const [key, value] of Object.entries(failedState)) {
|
|
1174
1470
|
logMeta2[`aiki.${key}`] = value;
|
|
@@ -1177,10 +1473,10 @@ var WorkflowVersionImpl = class {
|
|
|
1177
1473
|
"aiki.attempts": attempts,
|
|
1178
1474
|
...logMeta2
|
|
1179
1475
|
});
|
|
1180
|
-
throw new
|
|
1476
|
+
throw new WorkflowRunFailedError4(run.id, attempts);
|
|
1181
1477
|
}
|
|
1182
1478
|
const awaitingRetryState = this.createAwaitingRetryState(error, retryParams.delayMs);
|
|
1183
|
-
await handle[
|
|
1479
|
+
await handle[INTERNAL7].transitionState(awaitingRetryState);
|
|
1184
1480
|
const logMeta = {};
|
|
1185
1481
|
for (const [key, value] of Object.entries(awaitingRetryState)) {
|
|
1186
1482
|
logMeta[`aiki.${key}`] = value;
|
|
@@ -1189,7 +1485,7 @@ var WorkflowVersionImpl = class {
|
|
|
1189
1485
|
"aiki.attempts": attempts,
|
|
1190
1486
|
...logMeta
|
|
1191
1487
|
});
|
|
1192
|
-
throw new
|
|
1488
|
+
throw new WorkflowRunSuspendedError6(run.id);
|
|
1193
1489
|
}
|
|
1194
1490
|
}
|
|
1195
1491
|
}
|
|
@@ -1198,8 +1494,8 @@ var WorkflowVersionImpl = class {
|
|
|
1198
1494
|
const retryParams = getRetryParams(attempts, retryStrategy);
|
|
1199
1495
|
if (!retryParams.retriesLeft) {
|
|
1200
1496
|
logger.error("Workflow retry not allowed", { "aiki.attempts": attempts });
|
|
1201
|
-
const error = new
|
|
1202
|
-
await handle[
|
|
1497
|
+
const error = new WorkflowRunFailedError4(id, attempts);
|
|
1498
|
+
await handle[INTERNAL7].transitionState({
|
|
1203
1499
|
status: "failed",
|
|
1204
1500
|
cause: "self",
|
|
1205
1501
|
error: createSerializableError(error)
|
|
@@ -1217,7 +1513,7 @@ var WorkflowVersionImpl = class {
|
|
|
1217
1513
|
return schemaValidationResult.value;
|
|
1218
1514
|
}
|
|
1219
1515
|
logger.error("Invalid workflow data", { "aiki.issues": schemaValidationResult.issues });
|
|
1220
|
-
await handle[
|
|
1516
|
+
await handle[INTERNAL7].transitionState({
|
|
1221
1517
|
status: "failed",
|
|
1222
1518
|
cause: "self",
|
|
1223
1519
|
error: {
|
|
@@ -1225,10 +1521,10 @@ var WorkflowVersionImpl = class {
|
|
|
1225
1521
|
message: JSON.stringify(schemaValidationResult.issues)
|
|
1226
1522
|
}
|
|
1227
1523
|
});
|
|
1228
|
-
throw new
|
|
1524
|
+
throw new WorkflowRunFailedError4(handle.run.id, handle.run.attempts);
|
|
1229
1525
|
}
|
|
1230
1526
|
createFailedState(error) {
|
|
1231
|
-
if (error instanceof
|
|
1527
|
+
if (error instanceof TaskFailedError2) {
|
|
1232
1528
|
return {
|
|
1233
1529
|
status: "failed",
|
|
1234
1530
|
cause: "task",
|
|
@@ -1242,7 +1538,7 @@ var WorkflowVersionImpl = class {
|
|
|
1242
1538
|
};
|
|
1243
1539
|
}
|
|
1244
1540
|
createAwaitingRetryState(error, nextAttemptInMs) {
|
|
1245
|
-
if (error instanceof
|
|
1541
|
+
if (error instanceof TaskFailedError2) {
|
|
1246
1542
|
return {
|
|
1247
1543
|
status: "awaiting_retry",
|
|
1248
1544
|
cause: "task",
|
|
@@ -1259,18 +1555,18 @@ var WorkflowVersionImpl = class {
|
|
|
1259
1555
|
}
|
|
1260
1556
|
};
|
|
1261
1557
|
var WorkflowBuilderImpl = class _WorkflowBuilderImpl {
|
|
1262
|
-
constructor(workflow2,
|
|
1558
|
+
constructor(workflow2, startOptionsBuilder) {
|
|
1263
1559
|
this.workflow = workflow2;
|
|
1264
|
-
this.
|
|
1560
|
+
this.startOptionsBuilder = startOptionsBuilder;
|
|
1265
1561
|
}
|
|
1266
1562
|
opt(path, value) {
|
|
1267
|
-
return new _WorkflowBuilderImpl(this.workflow, this.
|
|
1563
|
+
return new _WorkflowBuilderImpl(this.workflow, this.startOptionsBuilder.with(path, value));
|
|
1268
1564
|
}
|
|
1269
1565
|
start(client, ...args) {
|
|
1270
|
-
return this.workflow.
|
|
1566
|
+
return this.workflow.startWithOptions(client, this.startOptionsBuilder.build(), ...args);
|
|
1271
1567
|
}
|
|
1272
1568
|
startAsChild(parentRun, ...args) {
|
|
1273
|
-
return this.workflow.
|
|
1569
|
+
return this.workflow.startAsChildWithOptions(parentRun, this.startOptionsBuilder.build(), ...args);
|
|
1274
1570
|
}
|
|
1275
1571
|
};
|
|
1276
1572
|
|
|
@@ -1280,11 +1576,11 @@ function workflow(params) {
|
|
|
1280
1576
|
}
|
|
1281
1577
|
var WorkflowImpl = class {
|
|
1282
1578
|
name;
|
|
1283
|
-
[
|
|
1579
|
+
[INTERNAL8];
|
|
1284
1580
|
workflowVersions = /* @__PURE__ */ new Map();
|
|
1285
1581
|
constructor(params) {
|
|
1286
1582
|
this.name = params.name;
|
|
1287
|
-
this[
|
|
1583
|
+
this[INTERNAL8] = {
|
|
1288
1584
|
getAllVersions: this.getAllVersions.bind(this),
|
|
1289
1585
|
getVersion: this.getVersion.bind(this)
|
|
1290
1586
|
};
|
|
@@ -1294,10 +1590,7 @@ var WorkflowImpl = class {
|
|
|
1294
1590
|
throw new Error(`Workflow "${this.name}:${versionId}" already exists`);
|
|
1295
1591
|
}
|
|
1296
1592
|
const workflowVersion = new WorkflowVersionImpl(this.name, versionId, params);
|
|
1297
|
-
this.workflowVersions.set(
|
|
1298
|
-
versionId,
|
|
1299
|
-
workflowVersion
|
|
1300
|
-
);
|
|
1593
|
+
this.workflowVersions.set(versionId, workflowVersion);
|
|
1301
1594
|
return workflowVersion;
|
|
1302
1595
|
}
|
|
1303
1596
|
getAllVersions() {
|
|
@@ -1307,13 +1600,49 @@ var WorkflowImpl = class {
|
|
|
1307
1600
|
return this.workflowVersions.get(versionId);
|
|
1308
1601
|
}
|
|
1309
1602
|
};
|
|
1603
|
+
|
|
1604
|
+
// system/cancel-child-runs.ts
|
|
1605
|
+
var createCancelChildRunsV1 = (api) => {
|
|
1606
|
+
const listNonTerminalChildRuns = task({
|
|
1607
|
+
name: "aiki:list-non-terminal-child-runs",
|
|
1608
|
+
async handler(parentRunId) {
|
|
1609
|
+
const { runs } = await api.workflowRun.listChildRunsV1({
|
|
1610
|
+
parentRunId,
|
|
1611
|
+
status: NON_TERMINAL_WORKFLOW_RUN_STATUSES
|
|
1612
|
+
});
|
|
1613
|
+
return runs.map((r) => r.id);
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1616
|
+
const cancelRuns = task({
|
|
1617
|
+
name: "aiki:cancel-runs",
|
|
1618
|
+
async handler(runIds) {
|
|
1619
|
+
const { cancelledIds } = await api.workflowRun.cancelByIdsV1({ ids: runIds });
|
|
1620
|
+
return cancelledIds;
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1623
|
+
return workflow({ name: "aiki:cancel-child-runs" }).v("1.0.0", {
|
|
1624
|
+
async handler(run, parentRunId) {
|
|
1625
|
+
const childRunIds = await listNonTerminalChildRuns.start(run, parentRunId);
|
|
1626
|
+
if (!isNonEmptyArray(childRunIds)) {
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
await cancelRuns.start(run, childRunIds);
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
};
|
|
1633
|
+
|
|
1634
|
+
// system/index.ts
|
|
1635
|
+
function getSystemWorkflows(api) {
|
|
1636
|
+
return [createCancelChildRunsV1(api)];
|
|
1637
|
+
}
|
|
1310
1638
|
export {
|
|
1311
|
-
WorkflowVersionImpl,
|
|
1312
1639
|
createEventSenders,
|
|
1313
1640
|
createEventWaiters,
|
|
1314
1641
|
createReplayManifest,
|
|
1315
1642
|
createSleeper,
|
|
1316
1643
|
event,
|
|
1644
|
+
executeWorkflowRun,
|
|
1645
|
+
getSystemWorkflows,
|
|
1317
1646
|
schedule,
|
|
1318
1647
|
workflow,
|
|
1319
1648
|
workflowRegistry,
|