@aikirun/workflow 0.23.1 → 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 +588 -260
- 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(
|
|
@@ -707,10 +664,10 @@ function createReplayManifest(run) {
|
|
|
707
664
|
if (nextIndex >= taskCount) {
|
|
708
665
|
return void 0;
|
|
709
666
|
}
|
|
710
|
-
const
|
|
667
|
+
const task2 = taskQueues[address].tasks[nextIndex];
|
|
711
668
|
nextTaskIndexByAddress[address] = nextIndex + 1;
|
|
712
669
|
consumedEntries++;
|
|
713
|
-
return
|
|
670
|
+
return task2;
|
|
714
671
|
},
|
|
715
672
|
consumeNextChildWorkflowRun(address) {
|
|
716
673
|
const childWorkflowRunCount = childWorkflowRunCountByAddress[address] ?? 0;
|
|
@@ -750,10 +707,7 @@ function createReplayManifest(run) {
|
|
|
750
707
|
|
|
751
708
|
// run/sleeper.ts
|
|
752
709
|
import { INTERNAL as INTERNAL3 } from "@aikirun/types/symbols";
|
|
753
|
-
import {
|
|
754
|
-
WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError3,
|
|
755
|
-
WorkflowRunSuspendedError as WorkflowRunSuspendedError2
|
|
756
|
-
} from "@aikirun/types/workflow-run";
|
|
710
|
+
import { WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError3, WorkflowRunSuspendedError as WorkflowRunSuspendedError2 } from "@aikirun/types/workflow-run-error";
|
|
757
711
|
var MAX_SLEEP_YEARS = 10;
|
|
758
712
|
var MAX_SLEEP_MS = MAX_SLEEP_YEARS * 365 * 24 * 60 * 60 * 1e3;
|
|
759
713
|
function createSleeper(handle, logger) {
|
|
@@ -832,9 +786,64 @@ function createSleeper(handle, logger) {
|
|
|
832
786
|
};
|
|
833
787
|
}
|
|
834
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
|
+
|
|
835
844
|
// schedule.ts
|
|
836
845
|
function schedule(params) {
|
|
837
|
-
async function
|
|
846
|
+
async function activateWithOptions(client, workflow2, options, ...args) {
|
|
838
847
|
const input = args[0];
|
|
839
848
|
let scheduleSpec;
|
|
840
849
|
if (params.type === "interval") {
|
|
@@ -873,52 +882,338 @@ function schedule(params) {
|
|
|
873
882
|
}
|
|
874
883
|
};
|
|
875
884
|
}
|
|
876
|
-
function createBuilder(
|
|
885
|
+
function createBuilder(optionsBuilder) {
|
|
877
886
|
return {
|
|
878
|
-
opt: (path, value) => createBuilder(
|
|
887
|
+
opt: (path, value) => createBuilder(optionsBuilder.with(path, value)),
|
|
879
888
|
async activate(client, workflow2, ...args) {
|
|
880
|
-
return
|
|
889
|
+
return activateWithOptions(client, workflow2, optionsBuilder.build(), ...args);
|
|
881
890
|
}
|
|
882
891
|
};
|
|
883
892
|
}
|
|
884
893
|
return {
|
|
885
894
|
...params,
|
|
886
895
|
with() {
|
|
887
|
-
const
|
|
888
|
-
return createBuilder(
|
|
896
|
+
const optionsOverrider = objectOverrider({});
|
|
897
|
+
return createBuilder(optionsOverrider());
|
|
889
898
|
},
|
|
890
899
|
async activate(client, workflow2, ...args) {
|
|
891
|
-
return
|
|
900
|
+
return activateWithOptions(client, workflow2, {}, ...args);
|
|
892
901
|
}
|
|
893
902
|
};
|
|
894
903
|
}
|
|
895
904
|
|
|
896
|
-
//
|
|
897
|
-
import {
|
|
905
|
+
// system/cancel-child-runs.ts
|
|
906
|
+
import { NON_TERMINAL_WORKFLOW_RUN_STATUSES } from "@aikirun/types/workflow-run";
|
|
898
907
|
|
|
899
908
|
// ../../lib/address/index.ts
|
|
909
|
+
function getTaskAddress(name, inputHash) {
|
|
910
|
+
return `${name}:${inputHash}`;
|
|
911
|
+
}
|
|
900
912
|
function getWorkflowRunAddress(name, versionId, referenceId) {
|
|
901
913
|
return `${name}:${versionId}:${referenceId}`;
|
|
902
914
|
}
|
|
903
915
|
|
|
904
|
-
//
|
|
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
|
|
905
969
|
import { INTERNAL as INTERNAL5 } from "@aikirun/types/symbols";
|
|
906
|
-
import { TaskFailedError } from "@aikirun/types/task";
|
|
907
|
-
import { SchemaValidationError as SchemaValidationError2 } from "@aikirun/types/validator";
|
|
970
|
+
import { TaskFailedError } from "@aikirun/types/task-error";
|
|
908
971
|
import {
|
|
909
|
-
NonDeterminismError,
|
|
910
|
-
WorkflowRunFailedError as
|
|
972
|
+
NonDeterminismError as NonDeterminismError2,
|
|
973
|
+
WorkflowRunFailedError as WorkflowRunFailedError3,
|
|
911
974
|
WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError5,
|
|
912
975
|
WorkflowRunSuspendedError as WorkflowRunSuspendedError4
|
|
913
|
-
} 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";
|
|
914
1210
|
|
|
915
1211
|
// run/handle-child.ts
|
|
916
|
-
import { INTERNAL as
|
|
1212
|
+
import { INTERNAL as INTERNAL6 } from "@aikirun/types/symbols";
|
|
917
1213
|
import {
|
|
918
|
-
isTerminalWorkflowRunStatus
|
|
919
|
-
WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError4,
|
|
920
|
-
WorkflowRunSuspendedError as WorkflowRunSuspendedError3
|
|
1214
|
+
isTerminalWorkflowRunStatus
|
|
921
1215
|
} from "@aikirun/types/workflow-run";
|
|
1216
|
+
import { WorkflowRunRevisionConflictError as WorkflowRunRevisionConflictError6, WorkflowRunSuspendedError as WorkflowRunSuspendedError5 } from "@aikirun/types/workflow-run-error";
|
|
922
1217
|
async function childWorkflowRunHandle(client, run, parentRun, childWorkflowRunWaitQueues, logger, eventsDefinition) {
|
|
923
1218
|
const handle = await workflowRunHandle(client, run, eventsDefinition, logger);
|
|
924
1219
|
return {
|
|
@@ -930,7 +1225,7 @@ async function childWorkflowRunHandle(client, run, parentRun, childWorkflowRunWa
|
|
|
930
1225
|
pause: handle.pause.bind(handle),
|
|
931
1226
|
resume: handle.resume.bind(handle),
|
|
932
1227
|
awake: handle.awake.bind(handle),
|
|
933
|
-
[
|
|
1228
|
+
[INTERNAL6]: handle[INTERNAL6]
|
|
934
1229
|
};
|
|
935
1230
|
}
|
|
936
1231
|
function createStatusWaiter(handle, parentRun, childWorkflowRunWaitQueues, logger) {
|
|
@@ -940,7 +1235,7 @@ function createStatusWaiter(handle, parentRun, childWorkflowRunWaitQueues, logge
|
|
|
940
1235
|
failed: 0
|
|
941
1236
|
};
|
|
942
1237
|
async function waitForStatus(expectedStatus, options) {
|
|
943
|
-
const parentRunHandle = parentRun[
|
|
1238
|
+
const parentRunHandle = parentRun[INTERNAL6].handle;
|
|
944
1239
|
const nextIndex = nextIndexByStatus[expectedStatus];
|
|
945
1240
|
const { run } = handle;
|
|
946
1241
|
const childWorkflowRunWaits = childWorkflowRunWaitQueues[expectedStatus].childWorkflowRunWaits;
|
|
@@ -976,7 +1271,7 @@ function createStatusWaiter(handle, parentRun, childWorkflowRunWaitQueues, logge
|
|
|
976
1271
|
}
|
|
977
1272
|
const timeoutInMs = options?.timeout && toMilliseconds(options.timeout);
|
|
978
1273
|
try {
|
|
979
|
-
await parentRunHandle[
|
|
1274
|
+
await parentRunHandle[INTERNAL6].transitionState({
|
|
980
1275
|
status: "awaiting_child_workflow",
|
|
981
1276
|
childWorkflowRunId: run.id,
|
|
982
1277
|
childWorkflowRunStatus: expectedStatus,
|
|
@@ -987,12 +1282,12 @@ function createStatusWaiter(handle, parentRun, childWorkflowRunWaitQueues, logge
|
|
|
987
1282
|
...timeoutInMs !== void 0 ? { "aiki.timeoutInMs": timeoutInMs } : {}
|
|
988
1283
|
});
|
|
989
1284
|
} catch (error) {
|
|
990
|
-
if (error instanceof
|
|
991
|
-
throw new
|
|
1285
|
+
if (error instanceof WorkflowRunRevisionConflictError6) {
|
|
1286
|
+
throw new WorkflowRunSuspendedError5(parentRun.id);
|
|
992
1287
|
}
|
|
993
1288
|
throw error;
|
|
994
1289
|
}
|
|
995
|
-
throw new
|
|
1290
|
+
throw new WorkflowRunSuspendedError5(parentRun.id);
|
|
996
1291
|
}
|
|
997
1292
|
return waitForStatus;
|
|
998
1293
|
}
|
|
@@ -1005,22 +1300,22 @@ var WorkflowVersionImpl = class {
|
|
|
1005
1300
|
this.params = params;
|
|
1006
1301
|
const eventsDefinition = this.params.events ?? {};
|
|
1007
1302
|
this.events = createEventMulticasters(this.name, this.versionId, eventsDefinition);
|
|
1008
|
-
this[
|
|
1303
|
+
this[INTERNAL7] = {
|
|
1009
1304
|
eventsDefinition,
|
|
1010
1305
|
handler: this.handler.bind(this)
|
|
1011
1306
|
};
|
|
1012
1307
|
}
|
|
1013
1308
|
events;
|
|
1014
|
-
[
|
|
1309
|
+
[INTERNAL7];
|
|
1015
1310
|
with() {
|
|
1016
|
-
const
|
|
1017
|
-
const
|
|
1018
|
-
return new WorkflowBuilderImpl(this,
|
|
1311
|
+
const startOptions = this.params.options ?? {};
|
|
1312
|
+
const startOptionsOverrider = objectOverrider(startOptions);
|
|
1313
|
+
return new WorkflowBuilderImpl(this, startOptionsOverrider());
|
|
1019
1314
|
}
|
|
1020
1315
|
async start(client, ...args) {
|
|
1021
|
-
return this.
|
|
1316
|
+
return this.startWithOptions(client, this.params.options ?? {}, ...args);
|
|
1022
1317
|
}
|
|
1023
|
-
async
|
|
1318
|
+
async startWithOptions(client, startOptions, ...args) {
|
|
1024
1319
|
let input = args[0];
|
|
1025
1320
|
const schema = this.params.schema?.input;
|
|
1026
1321
|
if (schema) {
|
|
@@ -1036,28 +1331,28 @@ var WorkflowVersionImpl = class {
|
|
|
1036
1331
|
name: this.name,
|
|
1037
1332
|
versionId: this.versionId,
|
|
1038
1333
|
input,
|
|
1039
|
-
options:
|
|
1334
|
+
options: startOptions
|
|
1040
1335
|
});
|
|
1041
1336
|
client.logger.info("Created workflow", {
|
|
1042
1337
|
"aiki.workflowName": this.name,
|
|
1043
1338
|
"aiki.workflowVersionId": this.versionId,
|
|
1044
1339
|
"aiki.workflowRunId": id
|
|
1045
1340
|
});
|
|
1046
|
-
return workflowRunHandle(client, id, this[
|
|
1341
|
+
return workflowRunHandle(client, id, this[INTERNAL7].eventsDefinition);
|
|
1047
1342
|
}
|
|
1048
1343
|
async startAsChild(parentRun, ...args) {
|
|
1049
|
-
return this.
|
|
1344
|
+
return this.startAsChildWithOptions(parentRun, this.params.options ?? {}, ...args);
|
|
1050
1345
|
}
|
|
1051
|
-
async
|
|
1052
|
-
const parentRunHandle = parentRun[
|
|
1053
|
-
parentRunHandle[
|
|
1054
|
-
const { client } = parentRunHandle[
|
|
1346
|
+
async startAsChildWithOptions(parentRun, startOptions, ...args) {
|
|
1347
|
+
const parentRunHandle = parentRun[INTERNAL7].handle;
|
|
1348
|
+
parentRunHandle[INTERNAL7].assertExecutionAllowed();
|
|
1349
|
+
const { client } = parentRunHandle[INTERNAL7];
|
|
1055
1350
|
const inputRaw = args[0];
|
|
1056
1351
|
const input = await this.parse(parentRunHandle, this.params.schema?.input, inputRaw, parentRun.logger);
|
|
1057
1352
|
const inputHash = await hashInput(input);
|
|
1058
|
-
const referenceId =
|
|
1353
|
+
const referenceId = startOptions.reference?.id;
|
|
1059
1354
|
const address = getWorkflowRunAddress(this.name, this.versionId, referenceId ?? inputHash);
|
|
1060
|
-
const replayManifest = parentRun[
|
|
1355
|
+
const replayManifest = parentRun[INTERNAL7].replayManifest;
|
|
1061
1356
|
if (replayManifest.hasUnconsumedEntries()) {
|
|
1062
1357
|
const existingRunInfo = replayManifest.consumeNextChildWorkflowRun(address);
|
|
1063
1358
|
if (existingRunInfo) {
|
|
@@ -1076,7 +1371,7 @@ var WorkflowVersionImpl = class {
|
|
|
1076
1371
|
parentRun,
|
|
1077
1372
|
existingRunInfo.childWorkflowRunWaitQueues,
|
|
1078
1373
|
logger2,
|
|
1079
|
-
this[
|
|
1374
|
+
this[INTERNAL7].eventsDefinition
|
|
1080
1375
|
);
|
|
1081
1376
|
}
|
|
1082
1377
|
await this.throwNonDeterminismError(parentRun, parentRunHandle, inputHash, referenceId, replayManifest);
|
|
@@ -1087,7 +1382,7 @@ var WorkflowVersionImpl = class {
|
|
|
1087
1382
|
versionId: this.versionId,
|
|
1088
1383
|
input,
|
|
1089
1384
|
parentWorkflowRunId: parentRun.id,
|
|
1090
|
-
options: shard === void 0 ?
|
|
1385
|
+
options: shard === void 0 ? startOptions : { ...startOptions, shard }
|
|
1091
1386
|
});
|
|
1092
1387
|
const { run: newRun } = await client.api.workflowRun.getByIdV1({ id: newRunId });
|
|
1093
1388
|
const logger = parentRun.logger.child({
|
|
@@ -1106,7 +1401,7 @@ var WorkflowVersionImpl = class {
|
|
|
1106
1401
|
failed: { childWorkflowRunWaits: [] }
|
|
1107
1402
|
},
|
|
1108
1403
|
logger,
|
|
1109
|
-
this[
|
|
1404
|
+
this[INTERNAL7].eventsDefinition
|
|
1110
1405
|
);
|
|
1111
1406
|
}
|
|
1112
1407
|
async throwNonDeterminismError(parentRun, parentRunHandle, inputHash, referenceId, manifest) {
|
|
@@ -1120,8 +1415,8 @@ var WorkflowVersionImpl = class {
|
|
|
1120
1415
|
logMeta["aiki.referenceId"] = referenceId;
|
|
1121
1416
|
}
|
|
1122
1417
|
parentRun.logger.error("Replay divergence", logMeta);
|
|
1123
|
-
const error = new
|
|
1124
|
-
await parentRunHandle[
|
|
1418
|
+
const error = new NonDeterminismError3(parentRun.id, parentRunHandle.run.attempts, unconsumedManifestEntries);
|
|
1419
|
+
await parentRunHandle[INTERNAL7].transitionState({
|
|
1125
1420
|
status: "failed",
|
|
1126
1421
|
cause: "self",
|
|
1127
1422
|
error: createSerializableError(error)
|
|
@@ -1129,7 +1424,7 @@ var WorkflowVersionImpl = class {
|
|
|
1129
1424
|
throw error;
|
|
1130
1425
|
}
|
|
1131
1426
|
async getHandleById(client, runId) {
|
|
1132
|
-
return workflowRunHandle(client, runId, this[
|
|
1427
|
+
return workflowRunHandle(client, runId, this[INTERNAL7].eventsDefinition);
|
|
1133
1428
|
}
|
|
1134
1429
|
async getHandleByReferenceId(client, referenceId) {
|
|
1135
1430
|
const { run } = await client.api.workflowRun.getByReferenceIdV1({
|
|
@@ -1137,39 +1432,39 @@ var WorkflowVersionImpl = class {
|
|
|
1137
1432
|
versionId: this.versionId,
|
|
1138
1433
|
referenceId
|
|
1139
1434
|
});
|
|
1140
|
-
return workflowRunHandle(client, run, this[
|
|
1435
|
+
return workflowRunHandle(client, run, this[INTERNAL7].eventsDefinition);
|
|
1141
1436
|
}
|
|
1142
1437
|
async handler(run, input, context) {
|
|
1143
1438
|
const { logger } = run;
|
|
1144
|
-
const { handle } = run[
|
|
1145
|
-
handle[
|
|
1146
|
-
const retryStrategy = this.params.
|
|
1439
|
+
const { handle } = run[INTERNAL7];
|
|
1440
|
+
handle[INTERNAL7].assertExecutionAllowed();
|
|
1441
|
+
const retryStrategy = this.params.options?.retry ?? { type: "never" };
|
|
1147
1442
|
const state = handle.run.state;
|
|
1148
1443
|
if (state.status === "queued" && state.reason === "retry") {
|
|
1149
1444
|
await this.assertRetryAllowed(handle, retryStrategy, logger);
|
|
1150
1445
|
}
|
|
1151
1446
|
logger.info("Starting workflow");
|
|
1152
|
-
await handle[
|
|
1447
|
+
await handle[INTERNAL7].transitionState({ status: "running" });
|
|
1153
1448
|
const output = await this.tryExecuteWorkflow(input, run, context, retryStrategy);
|
|
1154
|
-
await handle[
|
|
1449
|
+
await handle[INTERNAL7].transitionState({ status: "completed", output });
|
|
1155
1450
|
logger.info("Workflow complete");
|
|
1156
1451
|
}
|
|
1157
1452
|
async tryExecuteWorkflow(input, run, context, retryStrategy) {
|
|
1158
|
-
const { handle } = run[
|
|
1453
|
+
const { handle } = run[INTERNAL7];
|
|
1159
1454
|
while (true) {
|
|
1160
1455
|
try {
|
|
1161
1456
|
const outputRaw = await this.params.handler(run, input, context);
|
|
1162
1457
|
const output = await this.parse(handle, this.params.schema?.output, outputRaw, run.logger);
|
|
1163
1458
|
return output;
|
|
1164
1459
|
} catch (error) {
|
|
1165
|
-
if (error instanceof
|
|
1460
|
+
if (error instanceof WorkflowRunSuspendedError6 || error instanceof WorkflowRunFailedError4 || error instanceof WorkflowRunRevisionConflictError7 || error instanceof NonDeterminismError3) {
|
|
1166
1461
|
throw error;
|
|
1167
1462
|
}
|
|
1168
1463
|
const attempts = handle.run.attempts;
|
|
1169
1464
|
const retryParams = getRetryParams(attempts, retryStrategy);
|
|
1170
1465
|
if (!retryParams.retriesLeft) {
|
|
1171
1466
|
const failedState = this.createFailedState(error);
|
|
1172
|
-
await handle[
|
|
1467
|
+
await handle[INTERNAL7].transitionState(failedState);
|
|
1173
1468
|
const logMeta2 = {};
|
|
1174
1469
|
for (const [key, value] of Object.entries(failedState)) {
|
|
1175
1470
|
logMeta2[`aiki.${key}`] = value;
|
|
@@ -1178,10 +1473,10 @@ var WorkflowVersionImpl = class {
|
|
|
1178
1473
|
"aiki.attempts": attempts,
|
|
1179
1474
|
...logMeta2
|
|
1180
1475
|
});
|
|
1181
|
-
throw new
|
|
1476
|
+
throw new WorkflowRunFailedError4(run.id, attempts);
|
|
1182
1477
|
}
|
|
1183
1478
|
const awaitingRetryState = this.createAwaitingRetryState(error, retryParams.delayMs);
|
|
1184
|
-
await handle[
|
|
1479
|
+
await handle[INTERNAL7].transitionState(awaitingRetryState);
|
|
1185
1480
|
const logMeta = {};
|
|
1186
1481
|
for (const [key, value] of Object.entries(awaitingRetryState)) {
|
|
1187
1482
|
logMeta[`aiki.${key}`] = value;
|
|
@@ -1190,7 +1485,7 @@ var WorkflowVersionImpl = class {
|
|
|
1190
1485
|
"aiki.attempts": attempts,
|
|
1191
1486
|
...logMeta
|
|
1192
1487
|
});
|
|
1193
|
-
throw new
|
|
1488
|
+
throw new WorkflowRunSuspendedError6(run.id);
|
|
1194
1489
|
}
|
|
1195
1490
|
}
|
|
1196
1491
|
}
|
|
@@ -1199,8 +1494,8 @@ var WorkflowVersionImpl = class {
|
|
|
1199
1494
|
const retryParams = getRetryParams(attempts, retryStrategy);
|
|
1200
1495
|
if (!retryParams.retriesLeft) {
|
|
1201
1496
|
logger.error("Workflow retry not allowed", { "aiki.attempts": attempts });
|
|
1202
|
-
const error = new
|
|
1203
|
-
await handle[
|
|
1497
|
+
const error = new WorkflowRunFailedError4(id, attempts);
|
|
1498
|
+
await handle[INTERNAL7].transitionState({
|
|
1204
1499
|
status: "failed",
|
|
1205
1500
|
cause: "self",
|
|
1206
1501
|
error: createSerializableError(error)
|
|
@@ -1218,7 +1513,7 @@ var WorkflowVersionImpl = class {
|
|
|
1218
1513
|
return schemaValidationResult.value;
|
|
1219
1514
|
}
|
|
1220
1515
|
logger.error("Invalid workflow data", { "aiki.issues": schemaValidationResult.issues });
|
|
1221
|
-
await handle[
|
|
1516
|
+
await handle[INTERNAL7].transitionState({
|
|
1222
1517
|
status: "failed",
|
|
1223
1518
|
cause: "self",
|
|
1224
1519
|
error: {
|
|
@@ -1226,10 +1521,10 @@ var WorkflowVersionImpl = class {
|
|
|
1226
1521
|
message: JSON.stringify(schemaValidationResult.issues)
|
|
1227
1522
|
}
|
|
1228
1523
|
});
|
|
1229
|
-
throw new
|
|
1524
|
+
throw new WorkflowRunFailedError4(handle.run.id, handle.run.attempts);
|
|
1230
1525
|
}
|
|
1231
1526
|
createFailedState(error) {
|
|
1232
|
-
if (error instanceof
|
|
1527
|
+
if (error instanceof TaskFailedError2) {
|
|
1233
1528
|
return {
|
|
1234
1529
|
status: "failed",
|
|
1235
1530
|
cause: "task",
|
|
@@ -1243,7 +1538,7 @@ var WorkflowVersionImpl = class {
|
|
|
1243
1538
|
};
|
|
1244
1539
|
}
|
|
1245
1540
|
createAwaitingRetryState(error, nextAttemptInMs) {
|
|
1246
|
-
if (error instanceof
|
|
1541
|
+
if (error instanceof TaskFailedError2) {
|
|
1247
1542
|
return {
|
|
1248
1543
|
status: "awaiting_retry",
|
|
1249
1544
|
cause: "task",
|
|
@@ -1260,18 +1555,18 @@ var WorkflowVersionImpl = class {
|
|
|
1260
1555
|
}
|
|
1261
1556
|
};
|
|
1262
1557
|
var WorkflowBuilderImpl = class _WorkflowBuilderImpl {
|
|
1263
|
-
constructor(workflow2,
|
|
1558
|
+
constructor(workflow2, startOptionsBuilder) {
|
|
1264
1559
|
this.workflow = workflow2;
|
|
1265
|
-
this.
|
|
1560
|
+
this.startOptionsBuilder = startOptionsBuilder;
|
|
1266
1561
|
}
|
|
1267
1562
|
opt(path, value) {
|
|
1268
|
-
return new _WorkflowBuilderImpl(this.workflow, this.
|
|
1563
|
+
return new _WorkflowBuilderImpl(this.workflow, this.startOptionsBuilder.with(path, value));
|
|
1269
1564
|
}
|
|
1270
1565
|
start(client, ...args) {
|
|
1271
|
-
return this.workflow.
|
|
1566
|
+
return this.workflow.startWithOptions(client, this.startOptionsBuilder.build(), ...args);
|
|
1272
1567
|
}
|
|
1273
1568
|
startAsChild(parentRun, ...args) {
|
|
1274
|
-
return this.workflow.
|
|
1569
|
+
return this.workflow.startAsChildWithOptions(parentRun, this.startOptionsBuilder.build(), ...args);
|
|
1275
1570
|
}
|
|
1276
1571
|
};
|
|
1277
1572
|
|
|
@@ -1281,11 +1576,11 @@ function workflow(params) {
|
|
|
1281
1576
|
}
|
|
1282
1577
|
var WorkflowImpl = class {
|
|
1283
1578
|
name;
|
|
1284
|
-
[
|
|
1579
|
+
[INTERNAL8];
|
|
1285
1580
|
workflowVersions = /* @__PURE__ */ new Map();
|
|
1286
1581
|
constructor(params) {
|
|
1287
1582
|
this.name = params.name;
|
|
1288
|
-
this[
|
|
1583
|
+
this[INTERNAL8] = {
|
|
1289
1584
|
getAllVersions: this.getAllVersions.bind(this),
|
|
1290
1585
|
getVersion: this.getVersion.bind(this)
|
|
1291
1586
|
};
|
|
@@ -1295,10 +1590,7 @@ var WorkflowImpl = class {
|
|
|
1295
1590
|
throw new Error(`Workflow "${this.name}:${versionId}" already exists`);
|
|
1296
1591
|
}
|
|
1297
1592
|
const workflowVersion = new WorkflowVersionImpl(this.name, versionId, params);
|
|
1298
|
-
this.workflowVersions.set(
|
|
1299
|
-
versionId,
|
|
1300
|
-
workflowVersion
|
|
1301
|
-
);
|
|
1593
|
+
this.workflowVersions.set(versionId, workflowVersion);
|
|
1302
1594
|
return workflowVersion;
|
|
1303
1595
|
}
|
|
1304
1596
|
getAllVersions() {
|
|
@@ -1308,13 +1600,49 @@ var WorkflowImpl = class {
|
|
|
1308
1600
|
return this.workflowVersions.get(versionId);
|
|
1309
1601
|
}
|
|
1310
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
|
+
}
|
|
1311
1638
|
export {
|
|
1312
|
-
WorkflowVersionImpl,
|
|
1313
1639
|
createEventSenders,
|
|
1314
1640
|
createEventWaiters,
|
|
1315
1641
|
createReplayManifest,
|
|
1316
1642
|
createSleeper,
|
|
1317
1643
|
event,
|
|
1644
|
+
executeWorkflowRun,
|
|
1645
|
+
getSystemWorkflows,
|
|
1318
1646
|
schedule,
|
|
1319
1647
|
workflow,
|
|
1320
1648
|
workflowRegistry,
|