@usehelical/workflows 0.0.1-alpha.18 → 0.0.1-alpha.19
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 -1
- package/dist/api.d.ts +2 -2
- package/dist/api.js +50 -7
- package/dist/api.js.map +1 -1
- package/dist/chunk-7I6XZ2V3.js +1039 -0
- package/dist/chunk-7I6XZ2V3.js.map +1 -0
- package/dist/external-JXNhdgzR.d.ts +154 -0
- package/dist/index.d.ts +29 -110
- package/dist/index.js +21 -616
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-6L2Y7WUY.js +0 -440
- package/dist/chunk-6L2Y7WUY.js.map +0 -1
- package/dist/state-B6QkOxSb.d.ts +0 -38
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { BaseError, ErrorType, FatalError, MaxRecoveryAttemptsExceededError, MaxRetriesExceededError, OperationTimedOutError, RunCancelledError, RunDeadlineExceededError, RunNotFoundError, RunOutsideOfWorkflowError, RunTimedOutError, SerializationError, UnknownError, WorkflowNotFoundError } from './chunk-
|
|
1
|
+
import { getState2, sendMessage, resumeRun, cancelRun, createRunHandle, queueWorkflow, runWorkflow, waitForRunResult, getRunStatus, withDbRetry, getState, getRun, executeWorkflow, deserialize, MaxRecoveryAttemptsExceededError, RunNotFoundError } from './chunk-7I6XZ2V3.js';
|
|
2
|
+
export { BaseError, ErrorType, FatalError, MaxRecoveryAttemptsExceededError, MaxRetriesExceededError, OperationTimedOutError, RunCancelledError, RunDeadlineExceededError, RunNotFoundError, RunOutsideOfWorkflowError, RunTimedOutError, SerializationError, UnknownError, WorkflowNotFoundError } from './chunk-7I6XZ2V3.js';
|
|
3
3
|
import crypto from 'crypto';
|
|
4
4
|
import { Kysely, PostgresDialect, sql } from 'kysely';
|
|
5
5
|
import { Pool } from 'pg';
|
|
@@ -121,15 +121,6 @@ function splitSubscriptionKey(subscriptionKey) {
|
|
|
121
121
|
return subscriptionKey.split("::");
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
// src/internal/db/queries/get-state.ts
|
|
125
|
-
async function getState(db, runId, key) {
|
|
126
|
-
const result = await db.selectFrom("state").select(["key", "value", "change_id"]).where("run_id", "=", runId).where("key", "=", key).executeTakeFirst();
|
|
127
|
-
if (!result) {
|
|
128
|
-
return void 0;
|
|
129
|
-
}
|
|
130
|
-
return result.value;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
124
|
// src/internal/db/queries/get-state-batch.ts
|
|
134
125
|
async function getStateBatch(db, stateRetrievalRequests) {
|
|
135
126
|
const results = await db.selectFrom("state").select(["key", "value", "change_id", "run_id", "change_id"]).where(
|
|
@@ -271,347 +262,6 @@ function getMessageRetrievalRequests(subscriptionKeys) {
|
|
|
271
262
|
messageType
|
|
272
263
|
}));
|
|
273
264
|
}
|
|
274
|
-
async function recordRunResult(db, runId, result, cancelled) {
|
|
275
|
-
const [{ change_id }] = await db.updateTable("runs").set({
|
|
276
|
-
output: result.result,
|
|
277
|
-
error: result.error,
|
|
278
|
-
status: cancelled ? "cancelled" : result.error ? "error" : "success",
|
|
279
|
-
updated_at: sql`(extract(epoch from now()) * 1000)::bigint`
|
|
280
|
-
}).where("id", "=", runId).returning(["change_id"]).execute();
|
|
281
|
-
return change_id;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// src/internal/execute-workflow.ts
|
|
285
|
-
async function executeWorkflow(ctx, params) {
|
|
286
|
-
const { db, runRegistry } = ctx;
|
|
287
|
-
const { options, runId, runPath, fn, args, operations } = params;
|
|
288
|
-
const abortController = new AbortController();
|
|
289
|
-
const [deadline, deadlineReason] = getDeadlineAndReason({
|
|
290
|
-
timeout: options?.timeout,
|
|
291
|
-
deadline: options?.deadline
|
|
292
|
-
});
|
|
293
|
-
const runStore = createExecutionContext({
|
|
294
|
-
ctx,
|
|
295
|
-
abortSignal: AbortSignal.any(
|
|
296
|
-
[abortController.signal].concat(deadline ? [AbortSignal.timeout(deadline - Date.now())] : [])
|
|
297
|
-
),
|
|
298
|
-
runId,
|
|
299
|
-
runPath,
|
|
300
|
-
operations
|
|
301
|
-
});
|
|
302
|
-
const executionPromise = (async () => {
|
|
303
|
-
try {
|
|
304
|
-
const result = await runWithExecutionContext(runStore, async () => {
|
|
305
|
-
return await runWithTimeout(async () => {
|
|
306
|
-
return await fn(...args);
|
|
307
|
-
}, deadlineReason);
|
|
308
|
-
});
|
|
309
|
-
await recordRunResult(db, runId, { result: result ? serialize(result) : void 0 });
|
|
310
|
-
return result;
|
|
311
|
-
} catch (error) {
|
|
312
|
-
if (error instanceof RunCancelledError) {
|
|
313
|
-
await recordRunResult(db, runId, { error: serializeError(error) }, true);
|
|
314
|
-
} else {
|
|
315
|
-
await recordRunResult(db, runId, { error: serializeError(error) });
|
|
316
|
-
}
|
|
317
|
-
throw error;
|
|
318
|
-
} finally {
|
|
319
|
-
runRegistry.unregisterRun(runId);
|
|
320
|
-
}
|
|
321
|
-
})();
|
|
322
|
-
runRegistry.registerRun(runId, {
|
|
323
|
-
store: runStore,
|
|
324
|
-
promise: executionPromise,
|
|
325
|
-
abortController
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
function getDeadlineAndReason({
|
|
329
|
-
timeout,
|
|
330
|
-
deadline
|
|
331
|
-
}) {
|
|
332
|
-
const now = Date.now();
|
|
333
|
-
const timeoutDeadline = timeout ? now + timeout : void 0;
|
|
334
|
-
if (timeoutDeadline && deadline) {
|
|
335
|
-
return [
|
|
336
|
-
Math.min(timeoutDeadline, deadline),
|
|
337
|
-
timeoutDeadline < deadline ? "timeout" : "deadline"
|
|
338
|
-
];
|
|
339
|
-
} else if (timeoutDeadline) {
|
|
340
|
-
return [timeoutDeadline, "timeout"];
|
|
341
|
-
} else if (deadline) {
|
|
342
|
-
return [deadline, "deadline"];
|
|
343
|
-
}
|
|
344
|
-
return [void 0, void 0];
|
|
345
|
-
}
|
|
346
|
-
async function runWithTimeout(fn, deadlineReason) {
|
|
347
|
-
const { abortSignal } = getExecutionContext();
|
|
348
|
-
const abortPromise = new Promise((_, reject) => {
|
|
349
|
-
abortSignal.throwIfAborted();
|
|
350
|
-
abortSignal.addEventListener(
|
|
351
|
-
"abort",
|
|
352
|
-
() => {
|
|
353
|
-
if (abortSignal.reason?.name === "TimeoutError") {
|
|
354
|
-
reject(new RunTimedOutError());
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
reject(new RunCancelledError());
|
|
358
|
-
},
|
|
359
|
-
{ once: true }
|
|
360
|
-
);
|
|
361
|
-
});
|
|
362
|
-
const callPromise = fn();
|
|
363
|
-
try {
|
|
364
|
-
return await Promise.race([callPromise, abortPromise]);
|
|
365
|
-
} catch (error) {
|
|
366
|
-
if (error instanceof RunTimedOutError) {
|
|
367
|
-
if (deadlineReason === "timeout") {
|
|
368
|
-
throw new RunTimedOutError();
|
|
369
|
-
} else if (deadlineReason === "deadline") {
|
|
370
|
-
throw new RunDeadlineExceededError();
|
|
371
|
-
}
|
|
372
|
-
throw error;
|
|
373
|
-
}
|
|
374
|
-
await callPromise.catch(() => {
|
|
375
|
-
});
|
|
376
|
-
throw error;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// src/internal/db/queries/get-run-status.ts
|
|
381
|
-
async function getRunStatus(db, runId) {
|
|
382
|
-
const run = await db.selectFrom("runs").select("status").where("id", "=", runId).executeTakeFirst();
|
|
383
|
-
if (!run) {
|
|
384
|
-
throw new RunNotFoundError(runId);
|
|
385
|
-
}
|
|
386
|
-
return run.status;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// src/internal/get-run-status.ts
|
|
390
|
-
async function getRunStatus2(ctx, runId) {
|
|
391
|
-
const { db } = ctx;
|
|
392
|
-
if (ctx.type === "runtime") {
|
|
393
|
-
const { runRegistry } = ctx;
|
|
394
|
-
const run = runRegistry.getRun(runId);
|
|
395
|
-
if (run) {
|
|
396
|
-
return deriveRunStatus(run);
|
|
397
|
-
}
|
|
398
|
-
return getRunStatus(db, runId);
|
|
399
|
-
}
|
|
400
|
-
if (ctx.type === "execution") {
|
|
401
|
-
const { operationManager, runRegistry } = ctx;
|
|
402
|
-
const op = operationManager.getOperationResult();
|
|
403
|
-
if (op) {
|
|
404
|
-
returnOrThrowOperationResult(op);
|
|
405
|
-
}
|
|
406
|
-
const status = await executeAndRecordOperation(operationManager, "getRunStatus", async () => {
|
|
407
|
-
const run = runRegistry.getRun(runId);
|
|
408
|
-
if (run) {
|
|
409
|
-
return deriveRunStatus(run);
|
|
410
|
-
}
|
|
411
|
-
return await getRunStatus(db, runId);
|
|
412
|
-
});
|
|
413
|
-
return status;
|
|
414
|
-
}
|
|
415
|
-
return await getRunStatus(db, runId);
|
|
416
|
-
}
|
|
417
|
-
async function deriveRunStatus(runEntry) {
|
|
418
|
-
if (runEntry.store.abortSignal.aborted) {
|
|
419
|
-
return "cancelled";
|
|
420
|
-
}
|
|
421
|
-
const promiseState = runEntry.getPromiseState();
|
|
422
|
-
if (promiseState === "pending") {
|
|
423
|
-
return "pending";
|
|
424
|
-
}
|
|
425
|
-
if (promiseState === "fulfilled") {
|
|
426
|
-
return "success";
|
|
427
|
-
}
|
|
428
|
-
return "error";
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// src/internal/wait-for-run-result.ts
|
|
432
|
-
async function waitForRunResult(ctx, runId) {
|
|
433
|
-
if (ctx.type === "execution") {
|
|
434
|
-
const { operationManager } = ctx;
|
|
435
|
-
const op = operationManager.getOperationResult();
|
|
436
|
-
if (op) {
|
|
437
|
-
returnOrThrowOperationResult(op);
|
|
438
|
-
}
|
|
439
|
-
const result = await executeAndRecordOperation(
|
|
440
|
-
operationManager,
|
|
441
|
-
"waitForRunResult",
|
|
442
|
-
async () => {
|
|
443
|
-
return await getOrSubscribeToRunResult(ctx, runId);
|
|
444
|
-
}
|
|
445
|
-
);
|
|
446
|
-
return result;
|
|
447
|
-
}
|
|
448
|
-
return await getOrSubscribeToRunResult(ctx, runId);
|
|
449
|
-
}
|
|
450
|
-
async function getOrSubscribeToRunResult(ctx, runId) {
|
|
451
|
-
const { db, runEventBus } = ctx;
|
|
452
|
-
const run = await getRun(db, runId);
|
|
453
|
-
if (!run) {
|
|
454
|
-
return {
|
|
455
|
-
error: new RunNotFoundError(runId),
|
|
456
|
-
success: false
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
switch (run.status) {
|
|
460
|
-
case "success":
|
|
461
|
-
return {
|
|
462
|
-
data: run.output ? deserialize(run.output) : void 0,
|
|
463
|
-
success: true
|
|
464
|
-
};
|
|
465
|
-
case "error":
|
|
466
|
-
return {
|
|
467
|
-
error: run.error ? deserializeError(run.error) : new UnknownError(),
|
|
468
|
-
success: false
|
|
469
|
-
};
|
|
470
|
-
case "cancelled":
|
|
471
|
-
return {
|
|
472
|
-
error: new RunCancelledError(),
|
|
473
|
-
success: false
|
|
474
|
-
};
|
|
475
|
-
case "max_recovery_attempts_exceeded":
|
|
476
|
-
return {
|
|
477
|
-
error: run.error ? deserializeError(run.error) : new MaxRecoveryAttemptsExceededError(runId, run.recoveryAttempts),
|
|
478
|
-
success: false
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
return new Promise((resolve, reject) => {
|
|
482
|
-
const unsubscribe = runEventBus.subscribe(runId, "*", async (e) => {
|
|
483
|
-
if (TERMINAL_STATES.includes(e.status)) {
|
|
484
|
-
unsubscribe();
|
|
485
|
-
try {
|
|
486
|
-
const run2 = await getRun(db, runId);
|
|
487
|
-
if (!run2) {
|
|
488
|
-
reject(new RunNotFoundError(runId));
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
switch (run2.status) {
|
|
492
|
-
case "success":
|
|
493
|
-
resolve({
|
|
494
|
-
data: run2.output ? deserialize(run2.output) : void 0,
|
|
495
|
-
success: true
|
|
496
|
-
});
|
|
497
|
-
return;
|
|
498
|
-
case "error":
|
|
499
|
-
resolve({
|
|
500
|
-
error: run2.error ? deserializeError(run2.error) : new UnknownError(),
|
|
501
|
-
success: false
|
|
502
|
-
});
|
|
503
|
-
return;
|
|
504
|
-
case "cancelled":
|
|
505
|
-
resolve({
|
|
506
|
-
error: new RunCancelledError(),
|
|
507
|
-
success: false
|
|
508
|
-
});
|
|
509
|
-
return;
|
|
510
|
-
case "max_recovery_attempts_exceeded":
|
|
511
|
-
resolve({
|
|
512
|
-
error: run2.error ? deserializeError(run2.error) : new MaxRecoveryAttemptsExceededError(runId, run2.recoveryAttempts),
|
|
513
|
-
success: false
|
|
514
|
-
});
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
} catch (error) {
|
|
518
|
-
if (error instanceof RunNotFoundError) {
|
|
519
|
-
reject(error);
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
resolve({
|
|
523
|
-
error,
|
|
524
|
-
success: false
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
});
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// src/internal/run.ts
|
|
533
|
-
function createRunHandle(runtimeContext, id) {
|
|
534
|
-
return {
|
|
535
|
-
id,
|
|
536
|
-
getStatus: () => getRunStatus2(runtimeContext, id),
|
|
537
|
-
waitForResult: () => waitForRunResult(runtimeContext, id)
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
async function insertPendingRun(db, options) {
|
|
541
|
-
const result = await db.insertInto("runs").values({
|
|
542
|
-
id: options.runId,
|
|
543
|
-
path: options.path,
|
|
544
|
-
inputs: options.inputs,
|
|
545
|
-
executor_id: options.executorId,
|
|
546
|
-
workflow_name: options.workflowName,
|
|
547
|
-
status: "pending",
|
|
548
|
-
started_at_epoch_ms: sql`(extract(epoch from now()) * 1000)::bigint`,
|
|
549
|
-
created_at: sql`(extract(epoch from now()) * 1000)::bigint`,
|
|
550
|
-
updated_at: sql`(extract(epoch from now()) * 1000)::bigint`
|
|
551
|
-
}).returning(["id", "path", "change_id"]).executeTakeFirst();
|
|
552
|
-
return {
|
|
553
|
-
runId: result.id,
|
|
554
|
-
path: result.path,
|
|
555
|
-
changeId: result.change_id
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// src/internal/run-workflow.ts
|
|
560
|
-
async function runWorkflow(ctx, workflowName, args = [], options = {}) {
|
|
561
|
-
const { db, executorId, workflowsMap, type } = ctx;
|
|
562
|
-
const workflow = workflowsMap[workflowName];
|
|
563
|
-
if (!workflow) {
|
|
564
|
-
throw new WorkflowNotFoundError(workflowName);
|
|
565
|
-
}
|
|
566
|
-
const newRunId = options.id ?? crypto.randomUUID();
|
|
567
|
-
let newRunPath = [];
|
|
568
|
-
if (type === "execution") {
|
|
569
|
-
const { operationManager, runPath } = ctx;
|
|
570
|
-
const op = operationManager.getOperationResult();
|
|
571
|
-
if (op) {
|
|
572
|
-
if (op.error) {
|
|
573
|
-
throw deserializeError(op.error);
|
|
574
|
-
}
|
|
575
|
-
const newRun2 = deserialize(op.result);
|
|
576
|
-
return createRunHandle(ctx, newRun2.runId);
|
|
577
|
-
}
|
|
578
|
-
const newRun = await executeAndRecordOperation(operationManager, "runWorkflow", async () => {
|
|
579
|
-
const newRun2 = {
|
|
580
|
-
runId: newRunId,
|
|
581
|
-
runPath: [...runPath, newRunId],
|
|
582
|
-
workflowName
|
|
583
|
-
};
|
|
584
|
-
withDbRetry(async () => {
|
|
585
|
-
return await insertPendingRun(db, {
|
|
586
|
-
...newRun2,
|
|
587
|
-
path: newRun2.runPath,
|
|
588
|
-
inputs: serialize(args),
|
|
589
|
-
executorId
|
|
590
|
-
});
|
|
591
|
-
});
|
|
592
|
-
return newRun2;
|
|
593
|
-
});
|
|
594
|
-
newRunPath = newRun.runPath;
|
|
595
|
-
}
|
|
596
|
-
if (type === "runtime") {
|
|
597
|
-
const { path } = await insertPendingRun(db, {
|
|
598
|
-
runId: newRunId,
|
|
599
|
-
path: [newRunId],
|
|
600
|
-
inputs: serialize(args),
|
|
601
|
-
executorId,
|
|
602
|
-
workflowName
|
|
603
|
-
});
|
|
604
|
-
newRunPath = path;
|
|
605
|
-
}
|
|
606
|
-
await executeWorkflow(ctx, {
|
|
607
|
-
runId: newRunId,
|
|
608
|
-
runPath: newRunPath,
|
|
609
|
-
fn: workflow.fn,
|
|
610
|
-
args,
|
|
611
|
-
options
|
|
612
|
-
});
|
|
613
|
-
return createRunHandle(ctx, newRunId);
|
|
614
|
-
}
|
|
615
265
|
|
|
616
266
|
// src/internal/context/run-registry.ts
|
|
617
267
|
var RunRegistry = class {
|
|
@@ -854,136 +504,6 @@ var RunEventBus = class {
|
|
|
854
504
|
this.pollingLoop.stop();
|
|
855
505
|
}
|
|
856
506
|
};
|
|
857
|
-
async function cancelRun(runId, db, options = {}) {
|
|
858
|
-
if (options.cascade) {
|
|
859
|
-
return db.transaction().execute(async (tx) => {
|
|
860
|
-
const result = await tx.updateTable("runs").set({
|
|
861
|
-
status: "cancelled",
|
|
862
|
-
updated_at: sql`(extract(epoch from now()) * 1000)::bigint`
|
|
863
|
-
}).where((eb) => eb.and([eb("id", "=", runId), eb("status", "not in", TERMINAL_STATES)])).returning(["change_id", "path"]).executeTakeFirst();
|
|
864
|
-
if (!result) {
|
|
865
|
-
const exists = await tx.selectFrom("runs").select([]).where("id", "=", runId).executeTakeFirst();
|
|
866
|
-
if (exists) {
|
|
867
|
-
return void 0;
|
|
868
|
-
}
|
|
869
|
-
throw new RunNotFoundError(runId);
|
|
870
|
-
}
|
|
871
|
-
await sql`
|
|
872
|
-
UPDATE runs
|
|
873
|
-
SET
|
|
874
|
-
status = ${"cancelled"},
|
|
875
|
-
updated_at = (extract(epoch from now()) * 1000)::bigint
|
|
876
|
-
WHERE path @> ARRAY[${runId}]::text[]
|
|
877
|
-
AND id != ${runId}
|
|
878
|
-
AND status NOT IN (${"cancelled"}, ${"success"}, ${"error"})
|
|
879
|
-
`.execute(tx);
|
|
880
|
-
return {
|
|
881
|
-
path: result.path,
|
|
882
|
-
changeId: result.change_id
|
|
883
|
-
};
|
|
884
|
-
});
|
|
885
|
-
} else {
|
|
886
|
-
const result = await db.updateTable("runs").set({
|
|
887
|
-
status: "cancelled",
|
|
888
|
-
updated_at: sql`(extract(epoch from now()) * 1000)::bigint`
|
|
889
|
-
}).where((eb) => eb.and([eb("id", "=", runId), eb("status", "not in", TERMINAL_STATES)])).returning(["path", "change_id"]).executeTakeFirst();
|
|
890
|
-
if (!result) {
|
|
891
|
-
throw new RunNotFoundError(runId);
|
|
892
|
-
}
|
|
893
|
-
return {
|
|
894
|
-
path: result.path,
|
|
895
|
-
changeId: result.change_id
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
// src/internal/cancel-run.ts
|
|
901
|
-
async function cancelRun2(ctx, runId, options = {}) {
|
|
902
|
-
if (ctx.type === "execution" || ctx.type === "runtime") {
|
|
903
|
-
const { db, runRegistry } = ctx;
|
|
904
|
-
const run = await cancelRun(runId, db, options);
|
|
905
|
-
if (!run) {
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
|
-
if (options.cascade) {
|
|
909
|
-
for (const pathPart of run.path) {
|
|
910
|
-
const run2 = runRegistry.getRun(pathPart);
|
|
911
|
-
if (run2) {
|
|
912
|
-
run2.abortController.abort();
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
return;
|
|
916
|
-
}
|
|
917
|
-
runRegistry.getRun(runId)?.abortController.abort();
|
|
918
|
-
} else {
|
|
919
|
-
const { db } = ctx;
|
|
920
|
-
const run = await cancelRun(runId, db, options);
|
|
921
|
-
if (!run) {
|
|
922
|
-
return;
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
async function enqueueRun(db, options) {
|
|
927
|
-
const result = await db.insertInto("runs").values({
|
|
928
|
-
id: options.runId,
|
|
929
|
-
path: options.path,
|
|
930
|
-
inputs: options.inputs,
|
|
931
|
-
queue_name: options.queueName,
|
|
932
|
-
queue_partition_key: options.queuePartitionKey,
|
|
933
|
-
queue_deduplication_id: options.deduplicationId,
|
|
934
|
-
workflow_name: options.workflowName,
|
|
935
|
-
status: "queued",
|
|
936
|
-
recovery_attempts: options.recoveryAttempts,
|
|
937
|
-
created_at: sql`(extract(epoch from now()) * 1000)::bigint`,
|
|
938
|
-
updated_at: sql`(extract(epoch from now()) * 1000)::bigint`
|
|
939
|
-
}).onConflict((oc) => oc.columns(["queue_name", "queue_deduplication_id"]).doNothing()).returning(["id", "change_id"]).executeTakeFirst();
|
|
940
|
-
return {
|
|
941
|
-
runId: result?.id,
|
|
942
|
-
changeId: result?.change_id
|
|
943
|
-
};
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
// src/internal/queue-workflow.ts
|
|
947
|
-
async function queueWorkflow(ctx, queueName, workflowName, args, options) {
|
|
948
|
-
const { db } = ctx;
|
|
949
|
-
const newRunId = options?.id ?? crypto.randomUUID();
|
|
950
|
-
if (ctx.type === "execution") {
|
|
951
|
-
const { operationManager, runPath } = ctx;
|
|
952
|
-
const op = operationManager.getOperationResult();
|
|
953
|
-
if (op) {
|
|
954
|
-
if (op.error) {
|
|
955
|
-
throw deserializeError(op.error);
|
|
956
|
-
}
|
|
957
|
-
const newRunId2 = deserialize(op.result);
|
|
958
|
-
return createRunHandle(ctx, newRunId2);
|
|
959
|
-
}
|
|
960
|
-
await executeAndRecordOperation(operationManager, "queueWorkflow", async () => {
|
|
961
|
-
const { runId } = await enqueueRun(db, {
|
|
962
|
-
runId: newRunId,
|
|
963
|
-
path: [...runPath, newRunId],
|
|
964
|
-
inputs: serialize(args),
|
|
965
|
-
workflowName,
|
|
966
|
-
queueName,
|
|
967
|
-
timeout: options?.timeout,
|
|
968
|
-
deadline: options?.deadline
|
|
969
|
-
});
|
|
970
|
-
return runId;
|
|
971
|
-
});
|
|
972
|
-
return createRunHandle(ctx, newRunId);
|
|
973
|
-
}
|
|
974
|
-
await withDbRetry(
|
|
975
|
-
async () => await enqueueRun(db, {
|
|
976
|
-
runId: newRunId,
|
|
977
|
-
path: [newRunId],
|
|
978
|
-
inputs: serialize(args),
|
|
979
|
-
workflowName,
|
|
980
|
-
queueName,
|
|
981
|
-
timeout: options?.timeout,
|
|
982
|
-
deadline: options?.deadline
|
|
983
|
-
})
|
|
984
|
-
);
|
|
985
|
-
return createRunHandle(ctx, newRunId);
|
|
986
|
-
}
|
|
987
507
|
async function dequeueRun(tx, runId, executorId) {
|
|
988
508
|
const result = await tx.updateTable("runs").set({
|
|
989
509
|
status: "pending",
|
|
@@ -1096,8 +616,8 @@ var QueueManager = class {
|
|
|
1096
616
|
pollingLoop;
|
|
1097
617
|
queues;
|
|
1098
618
|
async handlePoll() {
|
|
1099
|
-
for (const
|
|
1100
|
-
await this.dispatch(
|
|
619
|
+
for (const queue of this.queues) {
|
|
620
|
+
await this.dispatch(queue.name, queue);
|
|
1101
621
|
}
|
|
1102
622
|
}
|
|
1103
623
|
async dispatch(queueName, queue) {
|
|
@@ -1211,122 +731,6 @@ function createPgDriver({ connectionString }) {
|
|
|
1211
731
|
db: new Kysely({ dialect: new PostgresDialect({ pool }) })
|
|
1212
732
|
};
|
|
1213
733
|
}
|
|
1214
|
-
var INTERNAL_QUEUE_NAME = "_helical_internal_queue";
|
|
1215
|
-
async function resumeRun(db, runId) {
|
|
1216
|
-
const result = await db.updateTable("runs").set({
|
|
1217
|
-
status: "queued",
|
|
1218
|
-
queue_name: INTERNAL_QUEUE_NAME,
|
|
1219
|
-
deadline_epoch_ms: null,
|
|
1220
|
-
timeout_ms: null,
|
|
1221
|
-
recovery_attempts: 0,
|
|
1222
|
-
started_at_epoch_ms: sql`(extract(epoch from now()) * 1000)::bigint`,
|
|
1223
|
-
updated_at: sql`(extract(epoch from now()) * 1000)::bigint`
|
|
1224
|
-
}).where("id", "=", runId).where("status", "=", "pending").execute();
|
|
1225
|
-
if (!result) {
|
|
1226
|
-
throw new RunNotFoundError(runId);
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
// src/internal/resume-run.ts
|
|
1231
|
-
async function resumeRun2(ctx, runId) {
|
|
1232
|
-
const { db } = ctx;
|
|
1233
|
-
if (ctx.type === "execution") {
|
|
1234
|
-
const { operationManager } = ctx;
|
|
1235
|
-
const op = operationManager.getOperationResult();
|
|
1236
|
-
if (op) {
|
|
1237
|
-
returnOrThrowOperationResult(op);
|
|
1238
|
-
}
|
|
1239
|
-
await executeAndRecordOperation(operationManager, "resumeRun", async () => {
|
|
1240
|
-
await withDbRetry(async () => await resumeRun(db, runId));
|
|
1241
|
-
});
|
|
1242
|
-
} else {
|
|
1243
|
-
await withDbRetry(async () => await resumeRun(db, runId));
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
// src/internal/db/commands/insert-message.ts
|
|
1248
|
-
async function insertMessage(db, options) {
|
|
1249
|
-
return await db.insertInto("messages").values({
|
|
1250
|
-
destination_run_id: options.destinationWorkflowId,
|
|
1251
|
-
type: options.messageType,
|
|
1252
|
-
payload: options.data
|
|
1253
|
-
}).execute();
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
// src/internal/send-message.ts
|
|
1257
|
-
async function sendMessage(ctx, target, name, data) {
|
|
1258
|
-
const { db } = ctx;
|
|
1259
|
-
const destinationWorkflowId = typeof target === "string" ? target : target.id;
|
|
1260
|
-
const messageType = typeof name === "string" ? name : name.name;
|
|
1261
|
-
const serializedData = serialize(data);
|
|
1262
|
-
if (ctx.type === "execution") {
|
|
1263
|
-
const { operationManager } = ctx;
|
|
1264
|
-
const op = operationManager.getOperationResult();
|
|
1265
|
-
if (op) {
|
|
1266
|
-
return returnOrThrowOperationResult(op);
|
|
1267
|
-
}
|
|
1268
|
-
await executeAndRecordOperation(operationManager, "sendMessage", async () => {
|
|
1269
|
-
await insertMessage(db, {
|
|
1270
|
-
destinationWorkflowId,
|
|
1271
|
-
messageType,
|
|
1272
|
-
data: serializedData
|
|
1273
|
-
});
|
|
1274
|
-
});
|
|
1275
|
-
} else {
|
|
1276
|
-
await insertMessage(db, {
|
|
1277
|
-
destinationWorkflowId,
|
|
1278
|
-
messageType,
|
|
1279
|
-
data: serializedData
|
|
1280
|
-
});
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
// src/internal/get-state.ts
|
|
1285
|
-
var StateNotAvailableError = class extends Error {
|
|
1286
|
-
};
|
|
1287
|
-
async function getState2(ctx, target, key) {
|
|
1288
|
-
const { db, stateEventBus } = ctx;
|
|
1289
|
-
const destinationWorkflowId = typeof target === "string" ? target : target.id;
|
|
1290
|
-
const stateKey = typeof key === "string" ? key : key.name;
|
|
1291
|
-
if (ctx.type === "execution") {
|
|
1292
|
-
const { operationManager } = ctx;
|
|
1293
|
-
const op = operationManager.getOperationResult();
|
|
1294
|
-
if (op) {
|
|
1295
|
-
return returnOrThrowOperationResult(op);
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
while (true) {
|
|
1299
|
-
try {
|
|
1300
|
-
return await withDbRetry(async () => {
|
|
1301
|
-
const state = await getState(db, destinationWorkflowId, stateKey);
|
|
1302
|
-
if (!state) {
|
|
1303
|
-
throw new StateNotAvailableError();
|
|
1304
|
-
}
|
|
1305
|
-
if (ctx.type === "execution") {
|
|
1306
|
-
const { operationManager } = ctx;
|
|
1307
|
-
await executeAndRecordOperation(operationManager, "getState", async () => {
|
|
1308
|
-
return state;
|
|
1309
|
-
});
|
|
1310
|
-
}
|
|
1311
|
-
return deserialize(state);
|
|
1312
|
-
});
|
|
1313
|
-
} catch (error) {
|
|
1314
|
-
if (error instanceof StateNotAvailableError) {
|
|
1315
|
-
await waitForStateNotification(stateEventBus, destinationWorkflowId, stateKey);
|
|
1316
|
-
continue;
|
|
1317
|
-
}
|
|
1318
|
-
throw error;
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
async function waitForStateNotification(stateEventBus, runId, key) {
|
|
1323
|
-
return new Promise((resolve) => {
|
|
1324
|
-
const unsubscribe = stateEventBus.subscribe(runId, key, (state) => {
|
|
1325
|
-
unsubscribe();
|
|
1326
|
-
resolve(state);
|
|
1327
|
-
});
|
|
1328
|
-
});
|
|
1329
|
-
}
|
|
1330
734
|
|
|
1331
735
|
// src/main/worker/worker.ts
|
|
1332
736
|
function createWorker(props) {
|
|
@@ -1336,7 +740,13 @@ function createWorker(props) {
|
|
|
1336
740
|
const executorId = props.options.instanceId || crypto.randomUUID();
|
|
1337
741
|
const runRegistry = new RunRegistry();
|
|
1338
742
|
const runEventBus = new RunEventBus(db);
|
|
1339
|
-
const workflowsMap = props.workflows
|
|
743
|
+
const workflowsMap = props.workflows.reduce(
|
|
744
|
+
(acc, workflow) => {
|
|
745
|
+
acc[workflow.name] = workflow;
|
|
746
|
+
return acc;
|
|
747
|
+
},
|
|
748
|
+
{}
|
|
749
|
+
);
|
|
1340
750
|
const runtimeContext = {
|
|
1341
751
|
type: "runtime",
|
|
1342
752
|
db,
|
|
@@ -1345,7 +755,7 @@ function createWorker(props) {
|
|
|
1345
755
|
stateEventBus,
|
|
1346
756
|
runRegistry,
|
|
1347
757
|
workflowsMap,
|
|
1348
|
-
queueRegistry: props.queues ||
|
|
758
|
+
queueRegistry: props.queues || [],
|
|
1349
759
|
runEventBus
|
|
1350
760
|
};
|
|
1351
761
|
const notifySetupPromise = setupPostgresNotify(client, {
|
|
@@ -1357,7 +767,7 @@ function createWorker(props) {
|
|
|
1357
767
|
queueManager.start();
|
|
1358
768
|
recoverPendingRuns(runtimeContext);
|
|
1359
769
|
return {
|
|
1360
|
-
runWorkflow: async (
|
|
770
|
+
runWorkflow: async (workflow, argsOrOptions, options) => {
|
|
1361
771
|
await notifySetupPromise;
|
|
1362
772
|
let args, opts;
|
|
1363
773
|
if (argsOrOptions !== void 0) {
|
|
@@ -1368,9 +778,9 @@ function createWorker(props) {
|
|
|
1368
778
|
opts = argsOrOptions;
|
|
1369
779
|
}
|
|
1370
780
|
}
|
|
1371
|
-
return runWorkflow(runtimeContext,
|
|
781
|
+
return runWorkflow(runtimeContext, workflow.name, args, opts);
|
|
1372
782
|
},
|
|
1373
|
-
queueWorkflow: async (
|
|
783
|
+
queueWorkflow: async (queue, workflow, argsOrOptions, options) => {
|
|
1374
784
|
await notifySetupPromise;
|
|
1375
785
|
let args, opts;
|
|
1376
786
|
if (argsOrOptions !== void 0) {
|
|
@@ -1381,19 +791,14 @@ function createWorker(props) {
|
|
|
1381
791
|
opts = argsOrOptions;
|
|
1382
792
|
}
|
|
1383
793
|
}
|
|
1384
|
-
return queueWorkflow(runtimeContext,
|
|
794
|
+
return queueWorkflow(runtimeContext, queue.name, workflow.name, args, opts);
|
|
1385
795
|
},
|
|
1386
796
|
getRun: async (runId) => {
|
|
1387
797
|
await notifySetupPromise;
|
|
1388
798
|
return createRunHandle(runtimeContext, runId);
|
|
1389
799
|
},
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
return waitForRunResult(runtimeContext, runId);
|
|
1393
|
-
},
|
|
1394
|
-
cancelRun: async (runId) => cancelRun2(runtimeContext, runId),
|
|
1395
|
-
resumeRun: async (runId) => resumeRun2(runtimeContext, runId),
|
|
1396
|
-
getRunStatus: async (runId) => getRunStatus2(runtimeContext, runId),
|
|
800
|
+
cancelRun: async (runId) => cancelRun(runtimeContext, runId),
|
|
801
|
+
resumeRun: async (runId) => resumeRun(runtimeContext, runId),
|
|
1397
802
|
sendMessage: async (target, name, data) => {
|
|
1398
803
|
await notifySetupPromise;
|
|
1399
804
|
return sendMessage(runtimeContext, target, name, data);
|
|
@@ -1437,13 +842,13 @@ function createClient(options) {
|
|
|
1437
842
|
}
|
|
1438
843
|
return queueWorkflow(clientContext, queueName, workflowName, args, opts);
|
|
1439
844
|
},
|
|
1440
|
-
cancelRun: async (runId) =>
|
|
1441
|
-
resumeRun: async (runId) =>
|
|
845
|
+
cancelRun: async (runId) => cancelRun(clientContext, runId),
|
|
846
|
+
resumeRun: async (runId) => resumeRun(clientContext, runId),
|
|
1442
847
|
getRun: async (runId) => {
|
|
1443
848
|
await notifySetupPromise;
|
|
1444
849
|
return createRunHandle(clientContext, runId);
|
|
1445
850
|
},
|
|
1446
|
-
getRunStatus: async (runId) =>
|
|
851
|
+
getRunStatus: async (runId) => getRunStatus(clientContext, runId),
|
|
1447
852
|
waitForRunResult: async (runId) => waitForRunResult(clientContext, runId),
|
|
1448
853
|
sendMessage: async (runId, name, data) => {
|
|
1449
854
|
await notifySetupPromise;
|