@aikirun/workflow 0.5.3 → 0.6.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/dist/index.d.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  import { WorkflowId, WorkflowVersionId } from '@aikirun/types/workflow';
2
- import { Client, Logger } from '@aikirun/types/client';
2
+ import { Client, Logger, ApiClient } from '@aikirun/types/client';
3
3
  import { INTERNAL } from '@aikirun/types/symbols';
4
4
  import { WorkflowRun, WorkflowRunStatus, WorkflowRunStateCompleted, WorkflowRunStateInComplete, WorkflowRunState, WorkflowRunId, WorkflowOptions } from '@aikirun/types/workflow-run';
5
5
  import { SleepParams, SleepResult } from '@aikirun/types/sleep';
6
- import { TaskPath, TaskState } from '@aikirun/types/task';
6
+ import { DurationObject } from '@aikirun/types/duration';
7
+ import { EventSendOptions, EventWaitOptions, EventWaitState } from '@aikirun/types/event';
8
+ import { TaskPath } from '@aikirun/types/task';
9
+ import { WorkflowRunStateRequest, TaskStateRequest } from '@aikirun/types/workflow-run-api';
7
10
  import { SerializableInput } from '@aikirun/types/error';
8
11
 
9
12
  type NonEmptyArray<T> = [T, ...T[]];
@@ -25,10 +28,11 @@ type PathFromObjectInternal<T, IncludeArrayKeys extends boolean> = And<[
25
28
  type ExtractObjectType<T> = T extends object ? T : never;
26
29
  type TypeOfValueAtPath<T extends object, Path extends PathFromObject<T>> = Path extends keyof T ? T[Path] : Path extends `${infer First}.${infer Rest}` ? First extends keyof T ? undefined extends T[First] ? Rest extends PathFromObject<ExtractObjectType<T[First]>> ? TypeOfValueAtPath<ExtractObjectType<T[First]>, Rest> | undefined : never : Rest extends PathFromObject<ExtractObjectType<T[First]>> ? TypeOfValueAtPath<ExtractObjectType<T[First]>, Rest> : never : never : never;
27
30
 
28
- declare function workflowRunHandle<Input, Output>(client: Client<unknown>, id: WorkflowRunId): Promise<WorkflowRunHandle<Input, Output>>;
29
- declare function workflowRunHandle<Input, Output>(client: Client<unknown>, run: WorkflowRun<Input, Output>, logger?: Logger): Promise<WorkflowRunHandle<Input, Output>>;
30
- interface WorkflowRunHandle<Input, Output> {
31
+ declare function workflowRunHandle<Input, Output, TEventsDefinition extends EventsDefinition>(client: Client, id: WorkflowRunId, eventsDefinition?: TEventsDefinition, logger?: Logger): Promise<WorkflowRunHandle<Input, Output, TEventsDefinition>>;
32
+ declare function workflowRunHandle<Input, Output, TEventsDefinition extends EventsDefinition>(client: Client, run: WorkflowRun<Input, Output>, eventsDefinition?: TEventsDefinition, logger?: Logger): Promise<WorkflowRunHandle<Input, Output, TEventsDefinition>>;
33
+ interface WorkflowRunHandle<Input, Output, TEventsDefinition extends EventsDefinition = EventsDefinition> {
31
34
  run: Readonly<WorkflowRun<Input, Output>>;
35
+ events: EventSenders<TEventsDefinition>;
32
36
  refresh: () => Promise<void>;
33
37
  wait<S extends WorkflowRunStatus>(condition: {
34
38
  type: "status";
@@ -54,8 +58,8 @@ interface WorkflowRunHandle<Input, Output> {
54
58
  pause: () => Promise<void>;
55
59
  resume: () => Promise<void>;
56
60
  [INTERNAL]: {
57
- transitionState: (state: WorkflowRunState<Output>) => Promise<void>;
58
- transitionTaskState: (taskPath: TaskPath, taskState: TaskState<unknown>) => Promise<void>;
61
+ transitionState: (state: WorkflowRunStateRequest) => Promise<void>;
62
+ transitionTaskState: (taskPath: TaskPath, taskState: TaskStateRequest) => Promise<void>;
59
63
  assertExecutionAllowed: () => void;
60
64
  };
61
65
  }
@@ -65,43 +69,101 @@ interface WorkflowRunWaitOptions {
65
69
  abortSignal?: AbortSignal;
66
70
  }
67
71
 
68
- interface WorkflowRunContext<Input, Output> {
72
+ /**
73
+ * Defines an event type that can be sent to and waited for by workflows.
74
+ *
75
+ * Events are type-first with optional runtime schema validation.
76
+ *
77
+ * @template Data - Type of event data (must be JSON serializable)
78
+ * @param params - Optional event configuration
79
+ * @param opts.schema - Optional schema for runtime validation
80
+ * @returns EventDefinition for use in workflows
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * // Type-only event (no runtime validation)
85
+ * const approved = event<{ by: string }>();
86
+ *
87
+ * // Event with runtime validation
88
+ * const rejected = event<{ by: string; reason: string }>({
89
+ * schema: z.object({ by: z.string(), reason: z.string() })
90
+ * });
91
+ * ```
92
+ */
93
+ declare function event<Data>(params?: EventParams<Data>): EventDefinition<Data>;
94
+ interface EventParams<Data> {
95
+ schema?: Schema<Data>;
96
+ }
97
+ interface Schema<Data> {
98
+ parse: (data: unknown) => Data;
99
+ }
100
+ interface EventDefinition<Data> {
101
+ _type: Data;
102
+ schema?: Schema<Data>;
103
+ }
104
+ type EventsDefinition = Record<string, EventDefinition<unknown>>;
105
+ type EventData<TEventDefinition> = TEventDefinition extends EventDefinition<infer Data> ? Data : never;
106
+ type EventWaiters<TEventsDefinition extends EventsDefinition> = {
107
+ [K in keyof TEventsDefinition]: EventWaiter<EventData<TEventsDefinition[K]>>;
108
+ };
109
+ interface EventWaiter<Data> {
110
+ wait(options?: EventWaitOptions<undefined>): Promise<EventWaitState<Data, false>>;
111
+ wait(options: EventWaitOptions<DurationObject>): Promise<EventWaitState<Data, true>>;
112
+ }
113
+ type EventSenders<TEventsDefinition extends EventsDefinition> = {
114
+ [K in keyof TEventsDefinition]: EventSender<EventData<TEventsDefinition[K]>>;
115
+ };
116
+ interface EventSender<Data> {
117
+ send: (data: Data, options?: EventSendOptions) => Promise<void>;
118
+ }
119
+ declare function createEventWaiters<TEventsDefinition extends EventsDefinition>(handle: WorkflowRunHandle<unknown, unknown, TEventsDefinition>, eventsDefinition: TEventsDefinition, logger: Logger): EventWaiters<TEventsDefinition>;
120
+ declare function createEventSenders<TEventsDefinition extends EventsDefinition>(api: ApiClient, workflowRunId: string, eventsDefinition: TEventsDefinition, logger: Logger, onSend: (run: WorkflowRun<unknown, unknown>) => void): EventSenders<TEventsDefinition>;
121
+
122
+ interface WorkflowRunContext<Input, Output, TEventDefinition extends EventsDefinition> {
69
123
  id: WorkflowRunId;
70
124
  workflowId: WorkflowId;
71
125
  workflowVersionId: WorkflowVersionId;
72
126
  options: WorkflowOptions;
73
127
  logger: Logger;
74
128
  sleep: (params: SleepParams) => Promise<SleepResult>;
129
+ events: EventWaiters<TEventDefinition>;
75
130
  [INTERNAL]: {
76
- handle: WorkflowRunHandle<Input, Output>;
131
+ handle: WorkflowRunHandle<Input, Output, TEventDefinition>;
132
+ options: {
133
+ spinThresholdMs: number;
134
+ };
77
135
  };
78
136
  }
79
137
 
80
- interface WorkflowVersionParams<Input, Output, AppContext> {
81
- handler: (input: Input, run: Readonly<WorkflowRunContext<Input, Output>>, context: AppContext) => Promise<Output>;
138
+ interface WorkflowVersionParams<Input, Output, AppContext, TEventsDefinition extends EventsDefinition> {
139
+ events?: TEventsDefinition;
82
140
  opts?: WorkflowOptions;
141
+ handler: (input: Input, run: Readonly<WorkflowRunContext<Input, Output, TEventsDefinition>>, context: AppContext) => Promise<Output>;
83
142
  }
84
- interface WorkflowBuilder<Input, Output, AppContext> {
85
- opt<Path extends PathFromObject<WorkflowOptions>>(path: Path, value: TypeOfValueAtPath<WorkflowOptions, Path>): WorkflowBuilder<Input, Output, AppContext>;
86
- start: WorkflowVersion<Input, Output, AppContext>["start"];
143
+ interface WorkflowBuilder<Input, Output, AppContext, TEventsDefinition extends EventsDefinition> {
144
+ opt<Path extends PathFromObject<WorkflowOptions>>(path: Path, value: TypeOfValueAtPath<WorkflowOptions, Path>): WorkflowBuilder<Input, Output, AppContext, TEventsDefinition>;
145
+ start: WorkflowVersion<Input, Output, AppContext, TEventsDefinition>["start"];
87
146
  }
88
- interface WorkflowVersion<Input, Output, AppContext> {
147
+ interface WorkflowVersion<Input, Output, AppContext, TEventsDefinition extends EventsDefinition = EventsDefinition> {
89
148
  id: WorkflowId;
90
149
  versionId: WorkflowVersionId;
91
- with(): WorkflowBuilder<Input, Output, AppContext>;
92
- start: (client: Client<AppContext>, ...args: Input extends null ? [] : [Input]) => Promise<WorkflowRunHandle<Input, Output>>;
150
+ with(): WorkflowBuilder<Input, Output, AppContext, TEventsDefinition>;
151
+ start: (client: Client<AppContext>, ...args: Input extends null ? [] : [Input]) => Promise<WorkflowRunHandle<Input, Output, TEventsDefinition>>;
152
+ getHandle: (client: Client<AppContext>, runId: WorkflowRunId) => Promise<WorkflowRunHandle<Input, Output, TEventsDefinition>>;
93
153
  [INTERNAL]: {
94
- handler: (input: Input, run: WorkflowRunContext<Input, Output>, context: AppContext) => Promise<void>;
154
+ eventsDefinition: TEventsDefinition;
155
+ handler: (input: Input, run: WorkflowRunContext<Input, Output, TEventsDefinition>, context: AppContext) => Promise<void>;
95
156
  };
96
157
  }
97
- declare class WorkflowVersionImpl<Input, Output, AppContext> implements WorkflowVersion<Input, Output, AppContext> {
158
+ declare class WorkflowVersionImpl<Input, Output, AppContext, TEventsDefinition extends EventsDefinition> implements WorkflowVersion<Input, Output, AppContext, TEventsDefinition> {
98
159
  readonly id: WorkflowId;
99
160
  readonly versionId: WorkflowVersionId;
100
161
  private readonly params;
101
- readonly [INTERNAL]: WorkflowVersion<Input, Output, AppContext>[typeof INTERNAL];
102
- constructor(id: WorkflowId, versionId: WorkflowVersionId, params: WorkflowVersionParams<Input, Output, AppContext>);
103
- with(): WorkflowBuilder<Input, Output, AppContext>;
104
- start(client: Client<AppContext>, ...args: Input extends null ? [] : [Input]): Promise<WorkflowRunHandle<Input, Output>>;
162
+ readonly [INTERNAL]: WorkflowVersion<Input, Output, AppContext, TEventsDefinition>[typeof INTERNAL];
163
+ constructor(id: WorkflowId, versionId: WorkflowVersionId, params: WorkflowVersionParams<Input, Output, AppContext, TEventsDefinition>);
164
+ with(): WorkflowBuilder<Input, Output, AppContext, TEventsDefinition>;
165
+ start(client: Client<AppContext>, ...args: Input extends null ? [] : [Input]): Promise<WorkflowRunHandle<Input, Output, TEventsDefinition>>;
166
+ getHandle(client: Client<AppContext>, runId: WorkflowRunId): Promise<WorkflowRunHandle<Input, Output, TEventsDefinition>>;
105
167
  private handler;
106
168
  private tryExecuteWorkflow;
107
169
  private assertRetryAllowed;
@@ -124,7 +186,7 @@ interface WorkflowRegistry {
124
186
  interface SleeperOptions {
125
187
  spinThresholdMs: number;
126
188
  }
127
- declare function createWorkflowRunSleeper(workflowRunHandle: WorkflowRunHandle<unknown, unknown>, logger: Logger, options: SleeperOptions): (params: SleepParams) => Promise<SleepResult>;
189
+ declare function createSleeper(handle: WorkflowRunHandle<unknown, unknown>, logger: Logger, options: SleeperOptions): (params: SleepParams) => Promise<SleepResult>;
128
190
 
129
191
  /**
130
192
  * Defines a durable workflow with versioning and multiple task execution.
@@ -179,11 +241,11 @@ interface WorkflowParams {
179
241
  }
180
242
  interface Workflow {
181
243
  id: WorkflowId;
182
- v: <Input extends SerializableInput = null, Output = void, AppContext = null>(versionId: string, params: WorkflowVersionParams<Input, Output, AppContext>) => WorkflowVersion<Input, Output, AppContext>;
244
+ v: <Input extends SerializableInput = null, Output = void, AppContext = null, TEventsDefinition extends EventsDefinition = Record<string, never>>(versionId: string, params: WorkflowVersionParams<Input, Output, AppContext, TEventsDefinition>) => WorkflowVersion<Input, Output, AppContext, TEventsDefinition>;
183
245
  [INTERNAL]: {
184
246
  getAllVersions: () => WorkflowVersion<unknown, unknown, unknown>[];
185
247
  getVersion: (versionId: WorkflowVersionId) => WorkflowVersion<unknown, unknown, unknown> | undefined;
186
248
  };
187
249
  }
188
250
 
189
- export { type Workflow, type WorkflowParams, type WorkflowRegistry, type WorkflowRunContext, type WorkflowRunHandle, type WorkflowRunWaitOptions, type WorkflowVersion, WorkflowVersionImpl, type WorkflowVersionParams, createWorkflowRunSleeper, workflow, workflowRegistry, workflowRunHandle };
251
+ export { type EventDefinition, type EventSender, type EventSenders, type EventWaiter, type EventWaiters, type Workflow, type WorkflowParams, type WorkflowRegistry, type WorkflowRunContext, type WorkflowRunHandle, type WorkflowRunWaitOptions, type WorkflowVersion, WorkflowVersionImpl, type WorkflowVersionParams, createEventSenders, createEventWaiters, createSleeper, event, workflow, workflowRegistry, workflowRunHandle };
package/dist/index.js CHANGED
@@ -251,27 +251,136 @@ function getRetryParams(attempts, strategy) {
251
251
  }
252
252
  }
253
253
 
254
- // run/run-handle.ts
254
+ // run/event.ts
255
255
  import { INTERNAL } from "@aikirun/types/symbols";
256
+ import {
257
+ WorkflowRunFailedError,
258
+ WorkflowRunSuspendedError
259
+ } from "@aikirun/types/workflow-run";
260
+ function event(params) {
261
+ return {
262
+ _type: void 0,
263
+ schema: params?.schema
264
+ };
265
+ }
266
+ function createEventWaiters(handle, eventsDefinition, logger) {
267
+ const waiters = {};
268
+ for (const [eventId, eventDefinition] of Object.entries(eventsDefinition)) {
269
+ const waiter = createEventWaiter(
270
+ handle,
271
+ eventId,
272
+ eventDefinition.schema,
273
+ logger.child({ "aiki.eventId": eventId })
274
+ );
275
+ waiters[eventId] = waiter;
276
+ }
277
+ return waiters;
278
+ }
279
+ function createEventWaiter(handle, eventId, schema, logger) {
280
+ let nextEventIndex = 0;
281
+ async function wait(options) {
282
+ const events = handle.run.eventsQueue[eventId]?.events ?? [];
283
+ if (nextEventIndex < events.length) {
284
+ const event2 = events[nextEventIndex];
285
+ nextEventIndex++;
286
+ if (event2.status === "timeout") {
287
+ logger.debug("Timed out waiting for event");
288
+ return { timeout: true };
289
+ }
290
+ let data;
291
+ try {
292
+ data = schema ? schema.parse(event2.data) : event2.data;
293
+ } catch (error) {
294
+ logger.error("Invalid event data", { data: event2.data, error });
295
+ const serializableError = createSerializableError(error);
296
+ await handle[INTERNAL].transitionState({
297
+ status: "failed",
298
+ cause: "self",
299
+ error: serializableError
300
+ });
301
+ throw new WorkflowRunFailedError(handle.run.id, handle.run.attempts);
302
+ }
303
+ logger.debug("Event received");
304
+ return { timeout: false, data };
305
+ }
306
+ const timeoutInMs = options?.timeout && toMilliseconds(options.timeout);
307
+ logger.info("Waiting for event", {
308
+ ...timeoutInMs !== void 0 ? { "aiki.timeoutInMs": timeoutInMs } : {}
309
+ });
310
+ await handle[INTERNAL].transitionState({
311
+ status: "awaiting_event",
312
+ eventId,
313
+ timeoutInMs
314
+ });
315
+ throw new WorkflowRunSuspendedError(handle.run.id);
316
+ }
317
+ return { wait };
318
+ }
319
+ function createEventSenders(api, workflowRunId, eventsDefinition, logger, onSend) {
320
+ const senders = {};
321
+ for (const [eventId, eventDefinition] of Object.entries(eventsDefinition)) {
322
+ const sender = createEventSender(
323
+ api,
324
+ workflowRunId,
325
+ eventId,
326
+ eventDefinition.schema,
327
+ logger.child({ "aiki.eventId": eventId }),
328
+ onSend
329
+ );
330
+ senders[eventId] = sender;
331
+ }
332
+ return senders;
333
+ }
334
+ function createEventSender(api, workflowRunId, eventId, schema, logger, onSend) {
335
+ return {
336
+ async send(data, options) {
337
+ if (schema) {
338
+ schema.parse(data);
339
+ }
340
+ logger.debug("Sending event", {
341
+ ...options?.idempotencyKey ? { "aiki.idempotencyKey": options.idempotencyKey } : {}
342
+ });
343
+ const { run } = await api.workflowRun.sendEventV1({
344
+ id: workflowRunId,
345
+ eventId,
346
+ data,
347
+ options
348
+ });
349
+ onSend(run);
350
+ }
351
+ };
352
+ }
353
+
354
+ // run/handle.ts
355
+ import { INTERNAL as INTERNAL2 } from "@aikirun/types/symbols";
256
356
  import {
257
357
  WorkflowRunNotExecutableError
258
358
  } from "@aikirun/types/workflow-run";
259
- async function workflowRunHandle(client, runOrId, logger) {
359
+ async function workflowRunHandle(client, runOrId, eventsDefinition, logger) {
260
360
  const run = typeof runOrId !== "string" ? runOrId : (await client.api.workflowRun.getByIdV1({ id: runOrId })).run;
261
- return new WorkflowRunHandleImpl(client.api, run, logger ?? client.logger.child({ "aiki.workflowRunId": run.id }));
361
+ return new WorkflowRunHandleImpl(
362
+ client.api,
363
+ run,
364
+ eventsDefinition ?? {},
365
+ logger ?? client.logger.child({ "aiki.workflowRunId": run.id })
366
+ );
262
367
  }
263
368
  var WorkflowRunHandleImpl = class {
264
- constructor(api, _run, logger) {
369
+ constructor(api, _run, eventsDefinition, logger) {
265
370
  this.api = api;
266
371
  this._run = _run;
267
372
  this.logger = logger;
268
- this[INTERNAL] = {
373
+ this.events = createEventSenders(this.api, this._run.id, eventsDefinition, this.logger, (run) => {
374
+ this._run = run;
375
+ });
376
+ this[INTERNAL2] = {
269
377
  transitionState: this.transitionState.bind(this),
270
378
  transitionTaskState: this.transitionTaskState.bind(this),
271
379
  assertExecutionAllowed: this.assertExecutionAllowed.bind(this)
272
380
  };
273
381
  }
274
- [INTERNAL];
382
+ events;
383
+ [INTERNAL2];
275
384
  get run() {
276
385
  return this._run;
277
386
  }
@@ -279,6 +388,9 @@ var WorkflowRunHandleImpl = class {
279
388
  const { run: currentRun } = await this.api.workflowRun.getByIdV1({ id: this.run.id });
280
389
  this._run = currentRun;
281
390
  }
391
+ // TODO: instead polling the current state, use the transition history
392
+ // because it is entirely possible for a workflow to flash though a state
393
+ // and the handle will never know that the workflow hit that state
282
394
  async wait(condition, options) {
283
395
  if (options.abortSignal?.aborted) {
284
396
  throw new Error("Wait operation aborted");
@@ -334,10 +446,10 @@ var WorkflowRunHandleImpl = class {
334
446
  return this.transitionState({ status: "cancelled", reason });
335
447
  }
336
448
  async pause() {
337
- return this.transitionState({ status: "paused", pausedAt: Date.now() });
449
+ return this.transitionState({ status: "paused" });
338
450
  }
339
451
  async resume() {
340
- return this.transitionState({ status: "scheduled", scheduledAt: Date.now(), reason: "resume" });
452
+ return this.transitionState({ status: "scheduled", scheduledInMs: 0, reason: "resume" });
341
453
  }
342
454
  async transitionState(targetState) {
343
455
  if (targetState.status === "scheduled" && (targetState.reason === "new" || targetState.reason === "resume") || targetState.status === "paused" || targetState.status === "cancelled") {
@@ -375,11 +487,11 @@ var WorkflowRunHandleImpl = class {
375
487
  };
376
488
 
377
489
  // run/sleeper.ts
378
- import { INTERNAL as INTERNAL2 } from "@aikirun/types/symbols";
379
- import { WorkflowRunSuspendedError } from "@aikirun/types/workflow-run";
490
+ import { INTERNAL as INTERNAL3 } from "@aikirun/types/symbols";
491
+ import { WorkflowRunSuspendedError as WorkflowRunSuspendedError2 } from "@aikirun/types/workflow-run";
380
492
  var MAX_SLEEP_YEARS = 10;
381
493
  var MAX_SLEEP_MS = MAX_SLEEP_YEARS * 365 * 24 * 60 * 60 * 1e3;
382
- function createWorkflowRunSleeper(workflowRunHandle2, logger, options) {
494
+ function createSleeper(handle, logger, options) {
383
495
  return async (params) => {
384
496
  const { id: sleepId, ...durationFields } = params;
385
497
  const durationMs = toMilliseconds(durationFields);
@@ -387,7 +499,7 @@ function createWorkflowRunSleeper(workflowRunHandle2, logger, options) {
387
499
  throw new Error(`Sleep duration ${durationMs}ms exceeds maximum of ${MAX_SLEEP_YEARS} years`);
388
500
  }
389
501
  const sleepPath = `${sleepId}/${durationMs}`;
390
- const sleepState = workflowRunHandle2.run.sleepsState[sleepPath] ?? { status: "none" };
502
+ const sleepState = handle.run.sleepsState[sleepPath] ?? { status: "none" };
391
503
  if (sleepState.status === "completed") {
392
504
  logger.debug("Sleep completed", {
393
505
  "aiki.sleepId": sleepId,
@@ -407,7 +519,7 @@ function createWorkflowRunSleeper(workflowRunHandle2, logger, options) {
407
519
  "aiki.sleepId": sleepId,
408
520
  "aiki.durationMs": durationMs
409
521
  });
410
- throw new WorkflowRunSuspendedError(workflowRunHandle2.run.id);
522
+ throw new WorkflowRunSuspendedError2(handle.run.id);
411
523
  }
412
524
  sleepState;
413
525
  if (durationMs <= options.spinThresholdMs) {
@@ -418,35 +530,36 @@ function createWorkflowRunSleeper(workflowRunHandle2, logger, options) {
418
530
  await delay(durationMs);
419
531
  return { cancelled: false };
420
532
  }
421
- await workflowRunHandle2[INTERNAL2].transitionState({ status: "sleeping", sleepPath, durationMs });
533
+ await handle[INTERNAL3].transitionState({ status: "sleeping", sleepPath, durationMs });
422
534
  logger.info("Workflow going to sleep", {
423
535
  "aiki.sleepId": sleepId,
424
536
  "aiki.durationMs": durationMs
425
537
  });
426
- throw new WorkflowRunSuspendedError(workflowRunHandle2.run.id);
538
+ throw new WorkflowRunSuspendedError2(handle.run.id);
427
539
  };
428
540
  }
429
541
 
430
542
  // workflow.ts
431
- import { INTERNAL as INTERNAL4 } from "@aikirun/types/symbols";
543
+ import { INTERNAL as INTERNAL5 } from "@aikirun/types/symbols";
432
544
 
433
545
  // workflow-version.ts
434
- import { INTERNAL as INTERNAL3 } from "@aikirun/types/symbols";
546
+ import { INTERNAL as INTERNAL4 } from "@aikirun/types/symbols";
435
547
  import { TaskFailedError } from "@aikirun/types/task";
436
548
  import {
437
- WorkflowRunFailedError,
438
- WorkflowRunSuspendedError as WorkflowRunSuspendedError2
549
+ WorkflowRunFailedError as WorkflowRunFailedError2,
550
+ WorkflowRunSuspendedError as WorkflowRunSuspendedError3
439
551
  } from "@aikirun/types/workflow-run";
440
552
  var WorkflowVersionImpl = class _WorkflowVersionImpl {
441
553
  constructor(id, versionId, params) {
442
554
  this.id = id;
443
555
  this.versionId = versionId;
444
556
  this.params = params;
445
- this[INTERNAL3] = {
557
+ this[INTERNAL4] = {
558
+ eventsDefinition: this.params.events ?? {},
446
559
  handler: this.handler.bind(this)
447
560
  };
448
561
  }
449
- [INTERNAL3];
562
+ [INTERNAL4];
450
563
  with() {
451
564
  const optsOverrider = objectOverrider(this.params.opts ?? {});
452
565
  const createBuilder = (optsBuilder) => ({
@@ -465,21 +578,24 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
465
578
  input: isNonEmptyArray(args) ? args[0] : null,
466
579
  options: this.params.opts
467
580
  });
468
- return workflowRunHandle(client, run);
581
+ return workflowRunHandle(client, run, this[INTERNAL4].eventsDefinition);
582
+ }
583
+ async getHandle(client, runId) {
584
+ return workflowRunHandle(client, runId, this[INTERNAL4].eventsDefinition);
469
585
  }
470
586
  async handler(input, run, context) {
471
587
  const { logger } = run;
472
- const { handle } = run[INTERNAL3];
473
- handle[INTERNAL3].assertExecutionAllowed();
588
+ const { handle } = run[INTERNAL4];
589
+ handle[INTERNAL4].assertExecutionAllowed();
474
590
  const retryStrategy = this.params.opts?.retry ?? { type: "never" };
475
591
  const state = handle.run.state;
476
592
  if (state.status === "queued" && state.reason === "retry") {
477
- this.assertRetryAllowed(handle.run.id, handle.run.attempts, retryStrategy, logger);
593
+ await this.assertRetryAllowed(handle, retryStrategy, logger);
478
594
  }
479
595
  logger.info("Starting workflow");
480
- await handle[INTERNAL3].transitionState({ status: "running" });
596
+ await handle[INTERNAL4].transitionState({ status: "running" });
481
597
  const output = await this.tryExecuteWorkflow(input, run, context, retryStrategy);
482
- await handle[INTERNAL3].transitionState({ status: "completed", output });
598
+ await handle[INTERNAL4].transitionState({ status: "completed", output });
483
599
  logger.info("Workflow complete");
484
600
  }
485
601
  async tryExecuteWorkflow(input, run, context, retryStrategy) {
@@ -487,14 +603,15 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
487
603
  try {
488
604
  return await this.params.handler(input, run, context);
489
605
  } catch (error) {
490
- if (error instanceof WorkflowRunSuspendedError2) {
606
+ if (error instanceof WorkflowRunSuspendedError3) {
491
607
  throw error;
492
608
  }
493
- const attempts = run[INTERNAL3].handle.run.attempts;
609
+ const { handle } = run[INTERNAL4];
610
+ const attempts = handle.run.attempts;
494
611
  const retryParams = getRetryParams(attempts, retryStrategy);
495
612
  if (!retryParams.retriesLeft) {
496
613
  const failedState = this.createFailedState(error);
497
- await run[INTERNAL3].handle[INTERNAL3].transitionState(failedState);
614
+ await handle[INTERNAL4].transitionState(failedState);
498
615
  const logMeta2 = {};
499
616
  for (const [key, value] of Object.entries(failedState)) {
500
617
  logMeta2[`aiki.${key}`] = value;
@@ -503,32 +620,36 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
503
620
  "aiki.attempts": attempts,
504
621
  ...logMeta2
505
622
  });
506
- throw new WorkflowRunFailedError(run.id, attempts, failedState.reason, failedState.cause);
623
+ throw new WorkflowRunFailedError2(run.id, attempts);
507
624
  }
508
- const nextAttemptAt = Date.now() + retryParams.delayMs;
509
- const awaitingRetryState = this.createAwaitingRetryState(error, nextAttemptAt);
510
- await run[INTERNAL3].handle[INTERNAL3].transitionState(awaitingRetryState);
625
+ const awaitingRetryState = this.createAwaitingRetryState(error, retryParams.delayMs);
626
+ await handle[INTERNAL4].transitionState(awaitingRetryState);
511
627
  const logMeta = {};
512
628
  for (const [key, value] of Object.entries(awaitingRetryState)) {
513
629
  logMeta[`aiki.${key}`] = value;
514
630
  }
515
631
  run.logger.info("Workflow failed. Awaiting retry", {
516
632
  "aiki.attempts": attempts,
517
- "aiki.nextAttemptAt": nextAttemptAt,
518
633
  "aiki.delayMs": retryParams.delayMs,
519
634
  ...logMeta
520
635
  });
521
- throw new WorkflowRunSuspendedError2(run.id);
636
+ throw new WorkflowRunSuspendedError3(run.id);
522
637
  }
523
638
  }
524
639
  }
525
- assertRetryAllowed(id, attempts, retryStrategy, logger) {
640
+ async assertRetryAllowed(handle, retryStrategy, logger) {
641
+ const { id, attempts } = handle.run;
526
642
  const retryParams = getRetryParams(attempts, retryStrategy);
527
643
  if (!retryParams.retriesLeft) {
528
- logger.error("Workflow retry not allowed", {
529
- "aiki.attempts": attempts
644
+ logger.error("Workflow retry not allowed", { "aiki.attempts": attempts });
645
+ const error = new WorkflowRunFailedError2(id, attempts);
646
+ const serializableError = createSerializableError(error);
647
+ await handle[INTERNAL4].transitionState({
648
+ status: "failed",
649
+ cause: "self",
650
+ error: serializableError
530
651
  });
531
- throw new WorkflowRunFailedError(id, attempts, "Workflow retry not allowed");
652
+ throw error;
532
653
  }
533
654
  }
534
655
  createFailedState(error) {
@@ -536,25 +657,22 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
536
657
  return {
537
658
  status: "failed",
538
659
  cause: "task",
539
- taskPath: error.taskPath,
540
- reason: error.reason
660
+ taskPath: error.taskPath
541
661
  };
542
662
  }
543
663
  const serializableError = createSerializableError(error);
544
664
  return {
545
665
  status: "failed",
546
666
  cause: "self",
547
- reason: serializableError.message,
548
667
  error: serializableError
549
668
  };
550
669
  }
551
- createAwaitingRetryState(error, nextAttemptAt) {
670
+ createAwaitingRetryState(error, nextAttemptInMs) {
552
671
  if (error instanceof TaskFailedError) {
553
672
  return {
554
673
  status: "awaiting_retry",
555
674
  cause: "task",
556
- reason: error.reason,
557
- nextAttemptAt,
675
+ nextAttemptInMs,
558
676
  taskPath: error.taskPath
559
677
  };
560
678
  }
@@ -562,8 +680,7 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
562
680
  return {
563
681
  status: "awaiting_retry",
564
682
  cause: "self",
565
- reason: serializableError.message,
566
- nextAttemptAt,
683
+ nextAttemptInMs,
567
684
  error: serializableError
568
685
  };
569
686
  }
@@ -575,11 +692,11 @@ function workflow(params) {
575
692
  }
576
693
  var WorkflowImpl = class {
577
694
  id;
578
- [INTERNAL4];
695
+ [INTERNAL5];
579
696
  workflowVersions = /* @__PURE__ */ new Map();
580
697
  constructor(params) {
581
698
  this.id = params.id;
582
- this[INTERNAL4] = {
699
+ this[INTERNAL5] = {
583
700
  getAllVersions: this.getAllVersions.bind(this),
584
701
  getVersion: this.getVersion.bind(this)
585
702
  };
@@ -604,7 +721,10 @@ var WorkflowImpl = class {
604
721
  };
605
722
  export {
606
723
  WorkflowVersionImpl,
607
- createWorkflowRunSleeper,
724
+ createEventSenders,
725
+ createEventWaiters,
726
+ createSleeper,
727
+ event,
608
728
  workflow,
609
729
  workflowRegistry,
610
730
  workflowRunHandle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikirun/workflow",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "Workflow SDK for Aiki - define durable workflows with tasks, sleeps, waits, and event handling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,7 +18,7 @@
18
18
  "build": "tsup"
19
19
  },
20
20
  "dependencies": {
21
- "@aikirun/types": "0.5.3"
21
+ "@aikirun/types": "0.6.0"
22
22
  },
23
23
  "publishConfig": {
24
24
  "access": "public"