@usehelical/workflows 0.0.1-alpha.13 → 0.0.1-alpha.15

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.
@@ -28,95 +28,29 @@ var QueueNotFoundError = class extends Error {
28
28
  }
29
29
  };
30
30
 
31
- // core/internal/errors.ts
32
- var RunCancelledError = class extends Error {
33
- constructor() {
34
- super("This workflow run has been cancelled");
35
- this.name = "RUN_CANCELLED" /* RUN_CANCELLED */;
36
- }
37
- };
38
- var RunNotFoundError = class extends Error {
39
- constructor(runId) {
40
- super(`Workflow run "${runId}" not found`);
41
- this.name = "RUN_NOT_FOUND" /* RUN_NOT_FOUND */;
42
- }
43
- };
44
- var RunOutsideOfWorkflowError = class extends Error {
45
- constructor() {
46
- super("This function must be called within a workflow");
47
- this.name = "RUN_OUTSIDE_OF_WORKFLOW" /* RUN_OUTSIDE_OF_WORKFLOW */;
48
- }
49
- };
50
- var FatalError = class extends Error {
51
- constructor(message) {
52
- super(message);
53
- this.name = "FATAL_ERROR" /* FATAL_ERROR */;
54
- }
55
- };
56
- var MaxRetriesExceededError = class extends Error {
57
- attemptErrors;
58
- stepName;
59
- maxAttempts;
60
- constructor(stepName, maxAttempts, errors) {
61
- const formattedErrors = errors.map((error, index) => `Attempt ${index + 1}: ${error.message}`).join(". ");
62
- super(`Step "${stepName}" failed after ${maxAttempts + 1} attempts. ${formattedErrors}`);
63
- this.name = "MAX_RETRIES_EXCEEDED" /* MAX_RETRIES_EXCEEDED */;
64
- this.attemptErrors = errors;
65
- this.stepName = stepName;
66
- this.maxAttempts = maxAttempts;
67
- }
68
- };
69
- var ErrorThatShouldNeverHappen = class extends Error {
70
- constructor(message) {
71
- super(message);
72
- this.name = "ERROR_THAT_SHOULD_NEVER_HAPPEN" /* ERROR_THAT_SHOULD_NEVER_HAPPEN */;
73
- }
74
- };
75
- var SerializationError = class extends Error {
76
- constructor(message) {
77
- super(message);
78
- this.name = "SERIALIZATION_ERROR" /* SERIALIZATION_ERROR */;
79
- }
80
- };
81
- var TimeoutError2 = class extends Error {
82
- constructor(message) {
83
- super(message);
84
- this.name = "TIMEOUT" /* TIMEOUT */;
85
- }
86
- };
87
- var DeadlineError = class extends Error {
88
- constructor(message) {
89
- super(message);
90
- this.name = "DEADLINE" /* DEADLINE */;
91
- }
92
- };
93
- function serialize(value) {
94
- try {
95
- return JSON.stringify(value);
96
- } catch (error) {
97
- throw new SerializationError(error.message);
98
- }
99
- }
100
- function deserialize(value) {
101
- try {
102
- return JSON.parse(value);
103
- } catch (error) {
104
- throw new SerializationError(error.message);
105
- }
106
- }
107
- function serializeError(error) {
108
- try {
109
- return JSON.stringify(serializeError$1(error));
110
- } catch (error2) {
111
- throw new SerializationError(error2.message);
112
- }
31
+ // core/workflow.ts
32
+ var TERMINAL_STATES = [
33
+ "success",
34
+ "error",
35
+ "cancelled",
36
+ "max_recovery_attempts_exceeded"
37
+ ];
38
+ function defineWorkflow(fn, options = {}) {
39
+ return () => {
40
+ return {
41
+ fn,
42
+ maxRecoveryAttempts: options.maxRecoveryAttempts
43
+ };
44
+ };
113
45
  }
114
- function deserializeError(serialized) {
115
- try {
116
- return deserializeError$1(JSON.parse(serialized));
117
- } catch (error) {
118
- throw new SerializationError(error.message);
46
+
47
+ // core/internal/repository/get-state.ts
48
+ async function getState(db, runId, key) {
49
+ const result = await db.selectFrom("state").select(["key", "value", "change_id"]).where("run_id", "=", runId).where("key", "=", key).executeTakeFirst();
50
+ if (!result) {
51
+ return void 0;
119
52
  }
53
+ return result.value;
120
54
  }
121
55
 
122
56
  // core/internal/utils/sleep.ts
@@ -234,6 +168,116 @@ async function withDbRetry(fn, options = {}) {
234
168
  }
235
169
  }
236
170
  }
171
+
172
+ // core/internal/errors.ts
173
+ var BaseError = class extends Error {
174
+ reason;
175
+ constructor(message, reason) {
176
+ super(message);
177
+ this.reason = reason;
178
+ }
179
+ };
180
+ var RunTimedOutError = class extends BaseError {
181
+ constructor() {
182
+ super("This workflow run has timed out", "timeout");
183
+ }
184
+ };
185
+ var UnknownError = class extends BaseError {
186
+ constructor(message) {
187
+ super(message || "An unknown error occurred", "unknown");
188
+ }
189
+ };
190
+ var RunDeadlineExceededError = class extends BaseError {
191
+ constructor() {
192
+ super("This workflow run has exceeded its deadline", "deadline");
193
+ }
194
+ };
195
+ var RunCancelledError = class extends BaseError {
196
+ constructor(message) {
197
+ super(message || "This workflow run has been cancelled", "cancel");
198
+ }
199
+ };
200
+ var MaxRecoveryAttemptsExceededError2 = class extends BaseError {
201
+ constructor(message) {
202
+ super(message || "Max recovery attempts exceeded", "max_recovery_attempts_exceeded");
203
+ }
204
+ };
205
+ var OperationTimedOutError = class extends BaseError {
206
+ constructor(operationName) {
207
+ super(`This operation "${operationName}" has timed out`, "timeout");
208
+ }
209
+ };
210
+ var RunNotFoundError = class extends Error {
211
+ constructor(runId) {
212
+ super(`Workflow run "${runId}" not found`);
213
+ this.name = "RUN_NOT_FOUND" /* RUN_NOT_FOUND */;
214
+ }
215
+ };
216
+ var RunOutsideOfWorkflowError = class extends Error {
217
+ constructor() {
218
+ super("This function must be called within a workflow");
219
+ this.name = "RUN_OUTSIDE_OF_WORKFLOW" /* RUN_OUTSIDE_OF_WORKFLOW */;
220
+ }
221
+ };
222
+ var FatalError = class extends Error {
223
+ constructor(message) {
224
+ super(message);
225
+ this.name = "FATAL_ERROR" /* FATAL_ERROR */;
226
+ }
227
+ };
228
+ var MaxRetriesExceededError = class extends Error {
229
+ attemptErrors;
230
+ stepName;
231
+ maxAttempts;
232
+ constructor(stepName, maxAttempts, errors) {
233
+ const formattedErrors = errors.map((error, index) => `Attempt ${index + 1}: ${error.message}`).join(". ");
234
+ super(`Step "${stepName}" failed after ${maxAttempts + 1} attempts. ${formattedErrors}`);
235
+ this.name = "MAX_RETRIES_EXCEEDED" /* MAX_RETRIES_EXCEEDED */;
236
+ this.attemptErrors = errors;
237
+ this.stepName = stepName;
238
+ this.maxAttempts = maxAttempts;
239
+ }
240
+ };
241
+ var ErrorThatShouldNeverHappen = class extends Error {
242
+ constructor(message) {
243
+ super(message);
244
+ this.name = "ERROR_THAT_SHOULD_NEVER_HAPPEN" /* ERROR_THAT_SHOULD_NEVER_HAPPEN */;
245
+ }
246
+ };
247
+ var SerializationError = class extends Error {
248
+ constructor(message) {
249
+ super(message);
250
+ this.name = "SERIALIZATION_ERROR" /* SERIALIZATION_ERROR */;
251
+ }
252
+ };
253
+ function serialize(value) {
254
+ try {
255
+ return JSON.stringify(value);
256
+ } catch (error) {
257
+ throw new SerializationError(error.message);
258
+ }
259
+ }
260
+ function deserialize(value) {
261
+ try {
262
+ return JSON.parse(value);
263
+ } catch (error) {
264
+ throw new SerializationError(error.message);
265
+ }
266
+ }
267
+ function serializeError(error) {
268
+ try {
269
+ return JSON.stringify(serializeError$1(error));
270
+ } catch (error2) {
271
+ throw new SerializationError(error2.message);
272
+ }
273
+ }
274
+ function deserializeError(serialized) {
275
+ try {
276
+ return deserializeError$1(JSON.parse(serialized));
277
+ } catch (error) {
278
+ throw new SerializationError(error.message);
279
+ }
280
+ }
237
281
  function getExecutionContext() {
238
282
  const store = asyncLocalStorage.getStore();
239
283
  if (!store) {
@@ -273,7 +317,7 @@ async function insertOperation(tx, runId, operationName, sequenceId, result, err
273
317
  }).execute();
274
318
  }
275
319
 
276
- // core/internal/operation-manager.ts
320
+ // core/internal/context/operation-manager.ts
277
321
  var OperationManager = class {
278
322
  constructor(db, runId, operations = []) {
279
323
  this.db = db;
@@ -399,53 +443,22 @@ function createExecutionContext({
399
443
  db: ctx.db
400
444
  };
401
445
  }
402
- async function recordRunResult(db, runId, result) {
446
+ async function recordRunResult(db, runId, result, cancelled) {
403
447
  const [{ change_id }] = await db.updateTable("runs").set({
404
448
  output: result.result,
405
449
  error: result.error,
406
- status: result.error ? "error" : "success",
450
+ status: cancelled ? "cancelled" : result.error ? "error" : "success",
407
451
  updated_at: sql`(extract(epoch from now()) * 1000)::bigint`
408
452
  }).where("id", "=", runId).returning(["change_id"]).execute();
409
453
  return change_id;
410
454
  }
411
- async function cancelRun(runId, db) {
412
- return withDbRetry(async () => {
413
- return db.transaction().execute(async (tx) => {
414
- const result = await tx.updateTable("runs").set({
415
- status: "cancelled",
416
- updated_at: sql`(extract(epoch from now()) * 1000)::bigint`
417
- }).where(
418
- (eb) => eb.and([eb("id", "=", runId), eb("status", "not in", ["cancelled", "success", "error"])])
419
- ).returning(["change_id", "path"]).executeTakeFirst();
420
- if (!result) {
421
- const exists = await tx.selectFrom("runs").select([]).where("id", "=", runId).executeTakeFirst();
422
- if (exists) {
423
- return void 0;
424
- }
425
- throw new RunNotFoundError(runId);
426
- }
427
- await sql`
428
- UPDATE runs
429
- SET
430
- status = ${"cancelled"},
431
- updated_at = (extract(epoch from now()) * 1000)::bigint
432
- WHERE path @> ARRAY[${runId}]::text[]
433
- AND id != ${runId}
434
- AND status NOT IN (${"cancelled"}, ${"success"}, ${"error"})
435
- `.execute(tx);
436
- return {
437
- path: result.path
438
- };
439
- });
440
- });
441
- }
442
455
 
443
456
  // core/internal/execute-workflow.ts
444
457
  async function executeWorkflow(ctx, params) {
445
458
  const { db, runRegistry } = ctx;
446
459
  const { options, runId, runPath, fn, args, operations } = params;
447
460
  const abortController = new AbortController();
448
- const [deadline] = getDeadlineAndReason({
461
+ const [deadline, deadlineReason] = getDeadlineAndReason({
449
462
  timeout: options?.timeout,
450
463
  deadline: options?.deadline
451
464
  });
@@ -463,12 +476,16 @@ async function executeWorkflow(ctx, params) {
463
476
  const result = await runWithExecutionContext(runStore, async () => {
464
477
  return await runWithTimeout(async () => {
465
478
  return await fn(...args);
466
- });
479
+ }, deadlineReason);
467
480
  });
468
481
  await recordRunResult(db, runId, { result: result ? serialize(result) : void 0 });
469
482
  return result;
470
483
  } catch (error) {
471
- await recordRunResult(db, runId, { error: serializeError(error) });
484
+ if (error instanceof RunCancelledError) {
485
+ await recordRunResult(db, runId, { error: serializeError(error) }, true);
486
+ } else {
487
+ await recordRunResult(db, runId, { error: serializeError(error) });
488
+ }
472
489
  throw error;
473
490
  } finally {
474
491
  runRegistry.unregisterRun(runId);
@@ -498,15 +515,15 @@ function getDeadlineAndReason({
498
515
  }
499
516
  return [void 0, void 0];
500
517
  }
501
- async function runWithTimeout(fn) {
502
- const { runId, db, abortSignal } = getExecutionContext();
518
+ async function runWithTimeout(fn, deadlineReason) {
519
+ const { abortSignal } = getExecutionContext();
503
520
  const abortPromise = new Promise((_, reject) => {
504
521
  abortSignal.throwIfAborted();
505
522
  abortSignal.addEventListener(
506
523
  "abort",
507
524
  () => {
508
525
  if (abortSignal.reason?.name === "TimeoutError") {
509
- reject(new TimeoutError(`Workflow timed out`));
526
+ reject(new RunTimedOutError());
510
527
  return;
511
528
  }
512
529
  reject(new RunCancelledError());
@@ -518,8 +535,13 @@ async function runWithTimeout(fn) {
518
535
  try {
519
536
  return await Promise.race([callPromise, abortPromise]);
520
537
  } catch (error) {
521
- if (error instanceof TimeoutError || error instanceof DeadlineError) {
522
- await cancelRun(runId, db);
538
+ if (error instanceof RunTimedOutError) {
539
+ if (deadlineReason === "timeout") {
540
+ throw new RunTimedOutError();
541
+ } else if (deadlineReason === "deadline") {
542
+ throw new RunDeadlineExceededError();
543
+ }
544
+ throw error;
523
545
  }
524
546
  await callPromise.catch(() => {
525
547
  });
@@ -527,7 +549,7 @@ async function runWithTimeout(fn) {
527
549
  }
528
550
  }
529
551
 
530
- // core/internal/repository/get-run-status.ts
552
+ // core/internal/db/queries/get-run-status.ts
531
553
  async function getRunStatus(db, runId) {
532
554
  const run = await db.selectFrom("runs").select("status").where("id", "=", runId).executeTakeFirst();
533
555
  if (!run) {
@@ -536,16 +558,16 @@ async function getRunStatus(db, runId) {
536
558
  return run.status;
537
559
  }
538
560
 
539
- // client/get-run-status.ts
561
+ // core/internal/get-run-status.ts
540
562
  async function getRunStatus2(ctx, runId) {
541
563
  const { db, runRegistry } = ctx;
542
564
  const run = runRegistry.getRun(runId);
543
565
  if (run) {
544
- return getRunStatusFromRegistry(run);
566
+ return deriveRunStatus(run);
545
567
  }
546
568
  return getRunStatus(db, runId);
547
569
  }
548
- async function getRunStatusFromRegistry(runEntry) {
570
+ async function deriveRunStatus(runEntry) {
549
571
  if (runEntry.store.abortSignal.aborted) {
550
572
  return "cancelled";
551
573
  }
@@ -559,7 +581,7 @@ async function getRunStatusFromRegistry(runEntry) {
559
581
  return "error";
560
582
  }
561
583
 
562
- // client/wait-for-run-result.ts
584
+ // core/internal/wait-for-run-result.ts
563
585
  async function waitForRunResult(ctx, runId) {
564
586
  const { db, runEventBus } = ctx;
565
587
  const run = await getRun(db, runId);
@@ -569,48 +591,69 @@ async function waitForRunResult(ctx, runId) {
569
591
  success: false
570
592
  };
571
593
  }
572
- if (run.status === "cancelled") {
573
- return {
574
- error: new RunCancelledError(),
575
- success: false
576
- };
577
- }
578
- if (run.status === "error") {
579
- return {
580
- error: run.error ? deserializeError(run.error) : new Error("Unexptected error"),
581
- success: false
582
- };
594
+ switch (run.status) {
595
+ case "success":
596
+ return {
597
+ data: run.output ? deserialize(run.output) : void 0,
598
+ success: true
599
+ };
600
+ case "error":
601
+ return {
602
+ error: run.error ? deserializeError(run.error) : new UnknownError(),
603
+ success: false
604
+ };
605
+ case "cancelled":
606
+ return {
607
+ error: new RunCancelledError(),
608
+ success: false
609
+ };
610
+ case "max_recovery_attempts_exceeded":
611
+ return {
612
+ error: run.error ? deserializeError(run.error) : new MaxRecoveryAttemptsExceededError2(),
613
+ success: false
614
+ };
583
615
  }
584
616
  return new Promise((resolve, reject) => {
585
617
  const unsubscribe = runEventBus.subscribe(runId, "*", async (e) => {
586
- if (e.status === "cancelled") {
587
- unsubscribe();
588
- resolve({
589
- error: new RunCancelledError(),
590
- success: false
591
- });
592
- return;
593
- }
594
- if (e.status === "success" || e.status === "error") {
618
+ if (TERMINAL_STATES.includes(e.status)) {
595
619
  unsubscribe();
596
620
  try {
597
- const completedRun = await getRun(db, runId);
598
- if (!completedRun) {
621
+ const run2 = await getRun(db, runId);
622
+ if (!run2) {
599
623
  reject(new RunNotFoundError(runId));
600
624
  return;
601
625
  }
602
- if (completedRun.status === "error") {
603
- resolve({
604
- error: completedRun.error ? deserializeError(completedRun.error) : new Error("error"),
605
- success: false
606
- });
607
- return;
626
+ switch (run2.status) {
627
+ case "success":
628
+ resolve({
629
+ data: run2.output ? deserialize(run2.output) : void 0,
630
+ success: true
631
+ });
632
+ return;
633
+ case "error":
634
+ resolve({
635
+ error: run2.error ? deserializeError(run2.error) : new UnknownError(),
636
+ success: false
637
+ });
638
+ return;
639
+ case "cancelled":
640
+ resolve({
641
+ error: new RunCancelledError(),
642
+ success: false
643
+ });
644
+ return;
645
+ case "max_recovery_attempts_exceeded":
646
+ resolve({
647
+ error: run2.error ? deserializeError(run2.error) : new MaxRecoveryAttemptsExceededError2(),
648
+ success: false
649
+ });
650
+ return;
608
651
  }
609
- resolve({
610
- data: completedRun.output ? deserialize(completedRun.output) : void 0,
611
- success: true
612
- });
613
652
  } catch (error) {
653
+ if (error instanceof RunNotFoundError) {
654
+ reject(error);
655
+ return;
656
+ }
614
657
  resolve({
615
658
  error,
616
659
  success: false
@@ -647,7 +690,70 @@ async function insertPendingRun(db, options) {
647
690
  changeId: result.change_id
648
691
  };
649
692
  }
693
+ async function enqueueRun(db, options) {
694
+ const result = await db.insertInto("runs").values({
695
+ id: options.runId,
696
+ path: options.path,
697
+ inputs: options.inputs,
698
+ queue_name: options.queueName,
699
+ queue_partition_key: options.queuePartitionKey,
700
+ queue_deduplication_id: options.deduplicationId,
701
+ executor_id: options.executorId,
702
+ workflow_name: options.workflowName,
703
+ status: "queued",
704
+ recovery_attempts: options.recoveryAttempts,
705
+ created_at: sql`(extract(epoch from now()) * 1000)::bigint`,
706
+ updated_at: sql`(extract(epoch from now()) * 1000)::bigint`
707
+ }).onConflict((oc) => oc.columns(["queue_name", "queue_deduplication_id"]).doNothing()).returning(["id", "change_id"]).executeTakeFirst();
708
+ return {
709
+ runId: result?.id,
710
+ changeId: result?.change_id
711
+ };
712
+ }
713
+
714
+ // core/internal/repository/insert-message.ts
715
+ async function insertMessage(db, options) {
716
+ return await db.insertInto("messages").values({
717
+ destination_run_id: options.destinationWorkflowId,
718
+ type: options.messageType,
719
+ payload: options.data
720
+ }).execute();
721
+ }
722
+
723
+ // core/internal/get-state.ts
724
+ var StateNotAvailableError = class extends Error {
725
+ };
726
+ async function getState2(ctx, target, key) {
727
+ const { db, stateEventBus } = ctx;
728
+ const destinationWorkflowId = typeof target === "string" ? target : target.id;
729
+ const stateKey = typeof key === "string" ? key : key.name;
730
+ while (true) {
731
+ try {
732
+ return await withDbRetry(async () => {
733
+ const state = await getState(db, destinationWorkflowId, stateKey);
734
+ if (!state) {
735
+ throw new StateNotAvailableError();
736
+ }
737
+ return deserialize(state);
738
+ });
739
+ } catch (error) {
740
+ if (error instanceof StateNotAvailableError) {
741
+ await waitForStateNotification(stateEventBus, destinationWorkflowId, stateKey);
742
+ continue;
743
+ }
744
+ throw error;
745
+ }
746
+ }
747
+ }
748
+ async function waitForStateNotification(stateEventBus, runId, key) {
749
+ return new Promise((resolve) => {
750
+ const unsubscribe = stateEventBus.subscribe(runId, key, (state) => {
751
+ unsubscribe();
752
+ resolve(state);
753
+ });
754
+ });
755
+ }
650
756
 
651
- export { ErrorThatShouldNeverHappen, FatalError, MaxRecoveryAttemptsExceededError, MaxRetriesExceededError, QueueNotFoundError, RunCancelledError, RunNotFoundError, TimeoutError2 as TimeoutError, TimeoutError as TimeoutError2, WorkflowNotFoundError, cancelRun, createRunHandle, deserialize, deserializeError, executeAndRecordOperation, executeWorkflow, getExecutionContext, getRun, getRunStatus2 as getRunStatus, insertPendingRun, returnOrThrowOperationResult, serialize, serializeError, sleep, waitForRunResult, withDbRetry };
652
- //# sourceMappingURL=chunk-SQ7WUJCB.js.map
653
- //# sourceMappingURL=chunk-SQ7WUJCB.js.map
757
+ export { ErrorThatShouldNeverHappen, FatalError, MaxRecoveryAttemptsExceededError, MaxRetriesExceededError, OperationTimedOutError, QueueNotFoundError, RunCancelledError, RunNotFoundError, StateNotAvailableError, TERMINAL_STATES, TimeoutError, WorkflowNotFoundError, createRunHandle, defineWorkflow, deserialize, deserializeError, enqueueRun, executeAndRecordOperation, executeWorkflow, getExecutionContext, getRun, getRunStatus2 as getRunStatus, getState, getState2, insertMessage, insertPendingRun, returnOrThrowOperationResult, serialize, serializeError, sleep, waitForRunResult, waitForStateNotification, withDbRetry };
758
+ //# sourceMappingURL=chunk-TZSPUZQ3.js.map
759
+ //# sourceMappingURL=chunk-TZSPUZQ3.js.map