@aikirun/workflow 0.5.3 → 0.7.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +213 -52
  2. package/dist/index.js +431 -105
  3. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -1,10 +1,13 @@
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
- import { WorkflowRun, WorkflowRunStatus, WorkflowRunStateCompleted, WorkflowRunStateInComplete, WorkflowRunState, WorkflowRunId, WorkflowOptions } from '@aikirun/types/workflow-run';
4
+ import { WorkflowRun, TerminalWorkflowRunStatus, 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';
7
- import { SerializableInput } from '@aikirun/types/error';
6
+ import { EventSendOptions, EventWaitOptions, EventWaitState } from '@aikirun/types/event';
7
+ import { DurationObject } from '@aikirun/types/duration';
8
+ import { TaskPath } from '@aikirun/types/task';
9
+ import { WorkflowRunStateRequest, TaskStateRequest } from '@aikirun/types/workflow-run-api';
10
+ import { Serializable } from '@aikirun/types/error';
8
11
 
9
12
  type NonEmptyArray<T> = [T, ...T[]];
10
13
 
@@ -25,88 +28,246 @@ 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, AppContext, TEventsDefinition extends EventsDefinition>(client: Client<AppContext>, id: WorkflowRunId, eventsDefinition?: TEventsDefinition, logger?: Logger): Promise<WorkflowRunHandle<Input, Output, AppContext, TEventsDefinition>>;
32
+ declare function workflowRunHandle<Input, Output, AppContext, TEventsDefinition extends EventsDefinition>(client: Client<AppContext>, run: WorkflowRun<Input, Output>, eventsDefinition?: TEventsDefinition, logger?: Logger): Promise<WorkflowRunHandle<Input, Output, AppContext, TEventsDefinition>>;
33
+ interface WorkflowRunHandle<Input, Output, AppContext, TEventsDefinition extends EventsDefinition = EventsDefinition> {
31
34
  run: Readonly<WorkflowRun<Input, Output>>;
35
+ events: EventSenders<TEventsDefinition>;
32
36
  refresh: () => Promise<void>;
33
- wait<S extends WorkflowRunStatus>(condition: {
34
- type: "status";
35
- status: S;
36
- }, options: WorkflowRunWaitOptions): Promise<{
37
- success: false;
38
- cause: "timeout" | "aborted";
39
- } | {
40
- success: true;
41
- state: S extends "completed" ? WorkflowRunStateCompleted<Output> : WorkflowRunStateInComplete;
42
- }>;
43
- wait(condition: {
44
- type: "event";
45
- event: string;
46
- }, options: WorkflowRunWaitOptions): Promise<{
47
- success: false;
48
- cause: "timeout" | "aborted";
49
- } | {
50
- success: true;
51
- state: WorkflowRunState<Output>;
52
- }>;
37
+ /**
38
+ * Waits for the child workflow run to reach a terminal status by polling.
39
+ *
40
+ * Returns a result object:
41
+ * - `{ success: true, state }` - workflow reached the expected status
42
+ * - `{ success: false, cause }` - workflow did not reach status
43
+ *
44
+ * Possible failure causes:
45
+ * - `"run_terminated"` - workflow reached a terminal state other than expected
46
+ * - `"timeout"` - timeout elapsed (only when timeout option provided)
47
+ * - `"aborted"` - abort signal triggered (only when abortSignal option provided)
48
+ *
49
+ * @param status - The target status to wait for
50
+ * @param options - Optional configuration for polling interval, timeout, and abort signal
51
+ *
52
+ * @example
53
+ * // Wait indefinitely until completed or the workflow reaches another terminal state
54
+ * const result = await handle.waitForStatus("completed");
55
+ * if (result.success) {
56
+ * console.log(result.state.output);
57
+ * } else {
58
+ * console.log(`Workflow terminated: ${result.cause}`);
59
+ * }
60
+ *
61
+ * @example
62
+ * // Wait with a timeout
63
+ * const result = await handle.waitForStatus("completed", {
64
+ * timeout: { seconds: 30 }
65
+ * });
66
+ * if (result.success) {
67
+ * console.log(result.state.output);
68
+ * } else if (result.cause === "timeout") {
69
+ * console.log("Timed out waiting for completion");
70
+ * }
71
+ *
72
+ * @example
73
+ * // Wait with an abort signal
74
+ * const controller = new AbortController();
75
+ * const result = await handle.waitForStatus("completed", {
76
+ * abortSignal: controller.signal
77
+ * });
78
+ * if (!result.success) {
79
+ * console.log(`Wait ended: ${result.cause}`);
80
+ * }
81
+ */
82
+ waitForStatus<Status extends TerminalWorkflowRunStatus>(status: Status, options?: WorkflowRunWaitOptions<false, false>): Promise<WorkflowRunWaitResult<Status, Output, false, false>>;
83
+ waitForStatus<Status extends TerminalWorkflowRunStatus>(status: Status, options: WorkflowRunWaitOptions<true, false>): Promise<WorkflowRunWaitResult<Status, Output, true, false>>;
84
+ waitForStatus<Status extends TerminalWorkflowRunStatus>(status: Status, options: WorkflowRunWaitOptions<false, true>): Promise<WorkflowRunWaitResult<Status, Output, false, true>>;
85
+ waitForStatus<Status extends TerminalWorkflowRunStatus>(status: Status, options: WorkflowRunWaitOptions<true, true>): Promise<WorkflowRunWaitResult<Status, Output, true, true>>;
53
86
  cancel: (reason?: string) => Promise<void>;
54
87
  pause: () => Promise<void>;
55
88
  resume: () => Promise<void>;
89
+ awake: () => Promise<void>;
56
90
  [INTERNAL]: {
57
- transitionState: (state: WorkflowRunState<Output>) => Promise<void>;
58
- transitionTaskState: (taskPath: TaskPath, taskState: TaskState<unknown>) => Promise<void>;
91
+ client: Client<AppContext>;
92
+ transitionState: (state: WorkflowRunStateRequest) => Promise<void>;
93
+ transitionTaskState: (taskPath: TaskPath, taskState: TaskStateRequest) => Promise<void>;
59
94
  assertExecutionAllowed: () => void;
60
95
  };
61
96
  }
62
- interface WorkflowRunWaitOptions {
63
- maxDurationMs: number;
64
- pollIntervalMs?: number;
65
- abortSignal?: AbortSignal;
97
+ interface WorkflowRunWaitOptions<Timed extends boolean, Abortable extends boolean> {
98
+ interval?: DurationObject;
99
+ timeout?: Timed extends true ? DurationObject : never;
100
+ abortSignal?: Abortable extends true ? AbortSignal : never;
66
101
  }
102
+ type WorkflowRunWaitResultSuccess<Status extends TerminalWorkflowRunStatus, Output> = Extract<WorkflowRunState<Output>, {
103
+ status: Status;
104
+ }>;
105
+ type WorkflowRunWaitResult<Status extends TerminalWorkflowRunStatus, Output, Timed extends boolean, Abortable extends boolean> = {
106
+ success: false;
107
+ cause: "run_terminated" | (Timed extends true ? "timeout" : never) | (Abortable extends true ? "aborted" : never);
108
+ } | {
109
+ success: true;
110
+ state: WorkflowRunWaitResultSuccess<Status, Output>;
111
+ };
67
112
 
68
- interface WorkflowRunContext<Input, Output> {
113
+ /**
114
+ * Defines an event type that can be sent to and waited for by workflows.
115
+ *
116
+ * Events are type-first with optional runtime schema validation.
117
+ *
118
+ * @template Data - Type of event data (must be JSON serializable)
119
+ * @param params - Optional event configuration
120
+ * @param opts.schema - Optional schema for runtime validation
121
+ * @returns EventDefinition for use in workflows
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * // Type-only event (no runtime validation)
126
+ * const approved = event<{ by: string }>();
127
+ *
128
+ * // Event with runtime validation
129
+ * const rejected = event<{ by: string; reason: string }>({
130
+ * schema: z.object({ by: z.string(), reason: z.string() })
131
+ * });
132
+ * ```
133
+ */
134
+ declare function event(): EventDefinition<undefined>;
135
+ declare function event<Data>(params?: EventParams<Data>): EventDefinition<Data>;
136
+ interface EventParams<Data> {
137
+ schema?: Schema<Data>;
138
+ }
139
+ interface Schema<Data> {
140
+ parse: (data: unknown) => Data;
141
+ }
142
+ interface EventDefinition<Data> {
143
+ _type: Data;
144
+ schema?: Schema<Data>;
145
+ }
146
+ type EventsDefinition = Record<string, EventDefinition<unknown>>;
147
+ type EventData<TEventDefinition> = TEventDefinition extends EventDefinition<infer Data> ? Data : never;
148
+ type EventWaiters<TEventsDefinition extends EventsDefinition> = {
149
+ [K in keyof TEventsDefinition]: EventWaiter<EventData<TEventsDefinition[K]>>;
150
+ };
151
+ interface EventWaiter<Data> {
152
+ wait(options?: EventWaitOptions<false>): Promise<EventWaitState<Data, false>>;
153
+ wait(options: EventWaitOptions<true>): Promise<EventWaitState<Data, true>>;
154
+ }
155
+ type EventSenders<TEventsDefinition extends EventsDefinition> = {
156
+ [K in keyof TEventsDefinition]: EventSender<EventData<TEventsDefinition[K]>>;
157
+ };
158
+ interface EventSender<Data> {
159
+ send: (...args: Data extends undefined ? [data?: Data, options?: EventSendOptions] : [data: Data, options?: EventSendOptions]) => Promise<void>;
160
+ }
161
+ type EventMulticasters<TEventsDefinition extends EventsDefinition> = {
162
+ [K in keyof TEventsDefinition]: EventMulticaster<EventData<TEventsDefinition[K]>>;
163
+ };
164
+ interface EventMulticaster<Data> {
165
+ send: <AppContext>(client: Client<AppContext>, runId: string | string[], data: Data, options?: EventSendOptions) => Promise<void>;
166
+ }
167
+ declare function createEventWaiters<TEventsDefinition extends EventsDefinition>(handle: WorkflowRunHandle<unknown, unknown, unknown, TEventsDefinition>, eventsDefinition: TEventsDefinition, logger: Logger): EventWaiters<TEventsDefinition>;
168
+ declare function createEventSenders<TEventsDefinition extends EventsDefinition>(api: ApiClient, workflowRunId: string, eventsDefinition: TEventsDefinition, logger: Logger, onSend: (run: WorkflowRun<unknown, unknown>) => void): EventSenders<TEventsDefinition>;
169
+
170
+ interface WorkflowRunContext<Input, AppContext, TEventDefinition extends EventsDefinition> {
69
171
  id: WorkflowRunId;
70
172
  workflowId: WorkflowId;
71
173
  workflowVersionId: WorkflowVersionId;
72
174
  options: WorkflowOptions;
73
175
  logger: Logger;
74
176
  sleep: (params: SleepParams) => Promise<SleepResult>;
177
+ events: EventWaiters<TEventDefinition>;
75
178
  [INTERNAL]: {
76
- handle: WorkflowRunHandle<Input, Output>;
179
+ handle: WorkflowRunHandle<Input, unknown, AppContext, TEventDefinition>;
180
+ options: {
181
+ spinThresholdMs: number;
182
+ };
77
183
  };
78
184
  }
79
185
 
80
- interface WorkflowVersionParams<Input, Output, AppContext> {
81
- handler: (input: Input, run: Readonly<WorkflowRunContext<Input, Output>>, context: AppContext) => Promise<Output>;
186
+ type ChildWorkflowRunHandle<Input, Output, AppContext, TEventsDefinition extends EventsDefinition = EventsDefinition> = Omit<WorkflowRunHandle<Input, Output, AppContext, TEventsDefinition>, "waitForStatus"> & {
187
+ /**
188
+ * Waits for the child workflow run to reach a terminal status.
189
+ *
190
+ * This method suspends the parent workflow until the child reaches the expected terminal status
191
+ * or the optional timeout elapses.
192
+ *
193
+ * When the parent resumes, the result is deterministically replayed from stored wait results.
194
+ *
195
+ * Returns a result object:
196
+ * - `{ success: true, state }` - child reached the expected status
197
+ * - `{ success: false, cause }` - child did not reach status
198
+ *
199
+ * Possible failure causes:
200
+ * - `"run_terminated"` - child reached a different terminal state than expected
201
+ * - `"timeout"` - timeout elapsed (only when timeout option provided)
202
+ *
203
+ * @param status - The target terminal status to wait for
204
+ * @param options - Optional configuration with timeout
205
+ *
206
+ * @example
207
+ * // Wait indefinitely for child to complete
208
+ * const result = await childHandle.waitForStatus("completed");
209
+ * if (result.success) {
210
+ * console.log(result.state.output);
211
+ * } else {
212
+ * console.log(`Child terminated: ${result.cause}`);
213
+ * }
214
+ *
215
+ * @example
216
+ * // Wait with a timeout
217
+ * const result = await childHandle.waitForStatus("completed", {
218
+ * timeout: { minutes: 5 }
219
+ * });
220
+ * if (!result.success && result.cause === "timeout") {
221
+ * console.log("Child workflow took too long");
222
+ * }
223
+ */
224
+ waitForStatus<Status extends TerminalWorkflowRunStatus>(status: Status, options?: ChildWorkflowRunWaitOptions<false>): Promise<WorkflowRunWaitResult<Status, Output, false, false>>;
225
+ waitForStatus<Status extends TerminalWorkflowRunStatus>(status: Status, options: ChildWorkflowRunWaitOptions<true>): Promise<WorkflowRunWaitResult<Status, Output, true, false>>;
226
+ };
227
+ interface ChildWorkflowRunWaitOptions<Timed extends boolean> {
228
+ timeout?: Timed extends true ? DurationObject : never;
229
+ }
230
+
231
+ interface WorkflowVersionParams<Input, Output, AppContext, TEventsDefinition extends EventsDefinition> {
232
+ handler: (input: Input, run: Readonly<WorkflowRunContext<Input, AppContext, TEventsDefinition>>, context: AppContext) => Promise<Output>;
233
+ events?: TEventsDefinition;
82
234
  opts?: WorkflowOptions;
83
235
  }
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"];
236
+ interface WorkflowBuilder<Input, Output, AppContext, TEventsDefinition extends EventsDefinition> {
237
+ opt<Path extends PathFromObject<WorkflowOptions>>(path: Path, value: TypeOfValueAtPath<WorkflowOptions, Path>): WorkflowBuilder<Input, Output, AppContext, TEventsDefinition>;
238
+ start: WorkflowVersion<Input, Output, AppContext, TEventsDefinition>["start"];
239
+ startAsChild: WorkflowVersion<Input, Output, AppContext, TEventsDefinition>["startAsChild"];
87
240
  }
88
- interface WorkflowVersion<Input, Output, AppContext> {
241
+ interface WorkflowVersion<Input, Output, AppContext, TEventsDefinition extends EventsDefinition = EventsDefinition> {
89
242
  id: WorkflowId;
90
243
  versionId: WorkflowVersionId;
91
- with(): WorkflowBuilder<Input, Output, AppContext>;
92
- start: (client: Client<AppContext>, ...args: Input extends null ? [] : [Input]) => Promise<WorkflowRunHandle<Input, Output>>;
244
+ events: EventMulticasters<TEventsDefinition>;
245
+ with(): WorkflowBuilder<Input, Output, AppContext, TEventsDefinition>;
246
+ start: (client: Client<AppContext>, ...args: Input extends null ? [] : [Input]) => Promise<WorkflowRunHandle<Input, Output, AppContext, TEventsDefinition>>;
247
+ startAsChild: <ParentInput, ParentEventsDefinition extends EventsDefinition>(parentRun: WorkflowRunContext<ParentInput, AppContext, ParentEventsDefinition>, ...args: Input extends null ? [] : [Input]) => Promise<ChildWorkflowRunHandle<Input, Output, AppContext, TEventsDefinition>>;
248
+ getHandle: (client: Client<AppContext>, runId: WorkflowRunId) => Promise<WorkflowRunHandle<Input, Output, AppContext, TEventsDefinition>>;
93
249
  [INTERNAL]: {
94
- handler: (input: Input, run: WorkflowRunContext<Input, Output>, context: AppContext) => Promise<void>;
250
+ eventsDefinition: TEventsDefinition;
251
+ handler: (input: Input, run: WorkflowRunContext<Input, AppContext, TEventsDefinition>, context: AppContext) => Promise<void>;
95
252
  };
96
253
  }
97
- declare class WorkflowVersionImpl<Input, Output, AppContext> implements WorkflowVersion<Input, Output, AppContext> {
254
+ declare class WorkflowVersionImpl<Input, Output, AppContext, TEventsDefinition extends EventsDefinition> implements WorkflowVersion<Input, Output, AppContext, TEventsDefinition> {
98
255
  readonly id: WorkflowId;
99
256
  readonly versionId: WorkflowVersionId;
100
257
  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>>;
258
+ readonly events: EventMulticasters<TEventsDefinition>;
259
+ readonly [INTERNAL]: WorkflowVersion<Input, Output, AppContext, TEventsDefinition>[typeof INTERNAL];
260
+ constructor(id: WorkflowId, versionId: WorkflowVersionId, params: WorkflowVersionParams<Input, Output, AppContext, TEventsDefinition>);
261
+ with(): WorkflowBuilder<Input, Output, AppContext, TEventsDefinition>;
262
+ start(client: Client<AppContext>, ...args: Input extends null ? [] : [Input]): Promise<WorkflowRunHandle<Input, Output, AppContext, TEventsDefinition>>;
263
+ startAsChild<ParentInput>(parentRun: WorkflowRunContext<ParentInput, AppContext, EventsDefinition>, ...args: Input extends null ? [] : [Input]): Promise<ChildWorkflowRunHandle<Input, Output, AppContext, TEventsDefinition>>;
264
+ getHandle(client: Client<AppContext>, runId: WorkflowRunId): Promise<WorkflowRunHandle<Input, Output, AppContext, TEventsDefinition>>;
105
265
  private handler;
106
266
  private tryExecuteWorkflow;
107
267
  private assertRetryAllowed;
108
268
  private createFailedState;
109
269
  private createAwaitingRetryState;
270
+ private getPath;
110
271
  }
111
272
 
112
273
  declare function workflowRegistry(): WorkflowRegistry;
@@ -124,7 +285,7 @@ interface WorkflowRegistry {
124
285
  interface SleeperOptions {
125
286
  spinThresholdMs: number;
126
287
  }
127
- declare function createWorkflowRunSleeper(workflowRunHandle: WorkflowRunHandle<unknown, unknown>, logger: Logger, options: SleeperOptions): (params: SleepParams) => Promise<SleepResult>;
288
+ declare function createSleeper(handle: WorkflowRunHandle<unknown, unknown, unknown>, logger: Logger, options: SleeperOptions): (params: SleepParams) => Promise<SleepResult>;
128
289
 
129
290
  /**
130
291
  * Defines a durable workflow with versioning and multiple task execution.
@@ -179,11 +340,11 @@ interface WorkflowParams {
179
340
  }
180
341
  interface Workflow {
181
342
  id: WorkflowId;
182
- v: <Input extends SerializableInput = null, Output = void, AppContext = null>(versionId: string, params: WorkflowVersionParams<Input, Output, AppContext>) => WorkflowVersion<Input, Output, AppContext>;
343
+ v: <Input extends Serializable, Output extends Serializable, AppContext = null, TEventsDefinition extends EventsDefinition = Record<string, never>>(versionId: string, params: WorkflowVersionParams<Input, Output, AppContext, TEventsDefinition>) => WorkflowVersion<Input, Output, AppContext, TEventsDefinition>;
183
344
  [INTERNAL]: {
184
345
  getAllVersions: () => WorkflowVersion<unknown, unknown, unknown>[];
185
346
  getVersion: (versionId: WorkflowVersionId) => WorkflowVersion<unknown, unknown, unknown> | undefined;
186
347
  };
187
348
  }
188
349
 
189
- export { type Workflow, type WorkflowParams, type WorkflowRegistry, type WorkflowRunContext, type WorkflowRunHandle, type WorkflowRunWaitOptions, type WorkflowVersion, WorkflowVersionImpl, type WorkflowVersionParams, createWorkflowRunSleeper, workflow, workflowRegistry, workflowRunHandle };
350
+ export { type EventMulticaster, type EventMulticasters, 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
@@ -77,6 +77,14 @@ function delay(ms, options) {
77
77
  });
78
78
  }
79
79
 
80
+ // ../../lib/crypto/hash.ts
81
+ async function sha256(input) {
82
+ const data = new TextEncoder().encode(input);
83
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
84
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
85
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
86
+ }
87
+
80
88
  // ../../lib/duration/convert.ts
81
89
  var MS_PER_SECOND = 1e3;
82
90
  var MS_PER_MINUTE = 60 * MS_PER_SECOND;
@@ -136,6 +144,25 @@ function createSerializableError(error) {
136
144
  };
137
145
  }
138
146
 
147
+ // ../../lib/json/stable-stringify.ts
148
+ function stableStringify(value) {
149
+ if (value === null || value === void 0) {
150
+ return JSON.stringify(value);
151
+ }
152
+ if (typeof value !== "object") {
153
+ return JSON.stringify(value);
154
+ }
155
+ if (Array.isArray(value)) {
156
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
157
+ }
158
+ const keys = Object.keys(value).sort();
159
+ const pairs = keys.map((key) => {
160
+ const val = value[key];
161
+ return `${JSON.stringify(key)}:${stableStringify(val)}`;
162
+ });
163
+ return `{${pairs.join(",")}}`;
164
+ }
165
+
139
166
  // ../../lib/object/overrider.ts
140
167
  function set(obj, path, value) {
141
168
  const keys = path.split(".");
@@ -251,27 +278,169 @@ function getRetryParams(attempts, strategy) {
251
278
  }
252
279
  }
253
280
 
254
- // run/run-handle.ts
281
+ // run/event.ts
255
282
  import { INTERNAL } from "@aikirun/types/symbols";
256
283
  import {
284
+ WorkflowRunFailedError,
285
+ WorkflowRunSuspendedError
286
+ } from "@aikirun/types/workflow-run";
287
+ function event(params) {
288
+ return {
289
+ _type: void 0,
290
+ schema: params?.schema
291
+ };
292
+ }
293
+ function createEventWaiters(handle, eventsDefinition, logger) {
294
+ const waiters = {};
295
+ for (const [eventId, eventDefinition] of Object.entries(eventsDefinition)) {
296
+ const waiter = createEventWaiter(
297
+ handle,
298
+ eventId,
299
+ eventDefinition.schema,
300
+ logger.child({ "aiki.eventId": eventId })
301
+ );
302
+ waiters[eventId] = waiter;
303
+ }
304
+ return waiters;
305
+ }
306
+ function createEventWaiter(handle, eventId, schema, logger) {
307
+ let nextEventIndex = 0;
308
+ async function wait(options) {
309
+ await handle.refresh();
310
+ const events = handle.run.eventsQueue[eventId]?.events ?? [];
311
+ const event2 = events[nextEventIndex];
312
+ if (event2) {
313
+ nextEventIndex++;
314
+ if (event2.status === "timeout") {
315
+ logger.debug("Timed out waiting for event");
316
+ return { timeout: true };
317
+ }
318
+ let data;
319
+ try {
320
+ data = schema ? schema.parse(event2.data) : event2.data;
321
+ } catch (error) {
322
+ logger.error("Invalid event data", { data: event2.data, error });
323
+ const serializableError = createSerializableError(error);
324
+ await handle[INTERNAL].transitionState({
325
+ status: "failed",
326
+ cause: "self",
327
+ error: serializableError
328
+ });
329
+ throw new WorkflowRunFailedError(handle.run.id, handle.run.attempts);
330
+ }
331
+ logger.debug("Event received");
332
+ return { timeout: false, data };
333
+ }
334
+ const timeoutInMs = options?.timeout && toMilliseconds(options.timeout);
335
+ logger.info("Waiting for event", {
336
+ ...timeoutInMs !== void 0 ? { "aiki.timeoutInMs": timeoutInMs } : {}
337
+ });
338
+ await handle[INTERNAL].transitionState({
339
+ status: "awaiting_event",
340
+ eventId,
341
+ timeoutInMs
342
+ });
343
+ throw new WorkflowRunSuspendedError(handle.run.id);
344
+ }
345
+ return { wait };
346
+ }
347
+ function createEventSenders(api, workflowRunId, eventsDefinition, logger, onSend) {
348
+ const senders = {};
349
+ for (const [eventId, eventDefinition] of Object.entries(eventsDefinition)) {
350
+ const sender = createEventSender(
351
+ api,
352
+ workflowRunId,
353
+ eventId,
354
+ eventDefinition.schema,
355
+ logger.child({ "aiki.eventId": eventId }),
356
+ onSend
357
+ );
358
+ senders[eventId] = sender;
359
+ }
360
+ return senders;
361
+ }
362
+ function createEventSender(api, workflowRunId, eventId, schema, logger, onSend) {
363
+ return {
364
+ async send(...args) {
365
+ const [data, options] = args;
366
+ if (schema) {
367
+ schema.parse(data);
368
+ }
369
+ logger.debug("Sending event", {
370
+ ...options?.idempotencyKey ? { "aiki.idempotencyKey": options.idempotencyKey } : {}
371
+ });
372
+ const { run } = await api.workflowRun.sendEventV1({
373
+ id: workflowRunId,
374
+ eventId,
375
+ data,
376
+ options
377
+ });
378
+ onSend(run);
379
+ }
380
+ };
381
+ }
382
+ function createEventMulticasters(eventsDefinition) {
383
+ const senders = {};
384
+ for (const [eventId, eventDefinition] of Object.entries(eventsDefinition)) {
385
+ const sender = createEventMulticaster(eventId, eventDefinition.schema);
386
+ senders[eventId] = sender;
387
+ }
388
+ return senders;
389
+ }
390
+ function createEventMulticaster(eventId, schema) {
391
+ return {
392
+ async send(client, runId, data, options) {
393
+ if (schema) {
394
+ schema.parse(data);
395
+ }
396
+ const runIds = Array.isArray(runId) ? runId : [runId];
397
+ await client.api.workflowRun.multicastEventV1({
398
+ ids: runIds,
399
+ eventId,
400
+ data,
401
+ options
402
+ });
403
+ }
404
+ };
405
+ }
406
+
407
+ // run/handle.ts
408
+ import { INTERNAL as INTERNAL2 } from "@aikirun/types/symbols";
409
+ import {
410
+ isTerminalWorkflowRunStatus,
257
411
  WorkflowRunNotExecutableError
258
412
  } from "@aikirun/types/workflow-run";
259
- async function workflowRunHandle(client, runOrId, logger) {
413
+ async function workflowRunHandle(client, runOrId, eventsDefinition, logger) {
260
414
  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 }));
415
+ return new WorkflowRunHandleImpl(
416
+ client,
417
+ run,
418
+ eventsDefinition ?? {},
419
+ logger ?? client.logger.child({
420
+ "aiki.workflowId": run.workflowId,
421
+ "aiki.workflowVersionId": run.workflowVersionId,
422
+ "aiki.workflowRunId": run.id
423
+ })
424
+ );
262
425
  }
263
426
  var WorkflowRunHandleImpl = class {
264
- constructor(api, _run, logger) {
265
- this.api = api;
427
+ constructor(client, _run, eventsDefinition, logger) {
266
428
  this._run = _run;
267
429
  this.logger = logger;
268
- this[INTERNAL] = {
430
+ this.api = client.api;
431
+ this.events = createEventSenders(client.api, this._run.id, eventsDefinition, this.logger, (run) => {
432
+ this._run = run;
433
+ });
434
+ this[INTERNAL2] = {
435
+ client,
269
436
  transitionState: this.transitionState.bind(this),
270
437
  transitionTaskState: this.transitionTaskState.bind(this),
271
438
  assertExecutionAllowed: this.assertExecutionAllowed.bind(this)
272
439
  };
273
440
  }
274
- [INTERNAL];
441
+ api;
442
+ events;
443
+ [INTERNAL2];
275
444
  get run() {
276
445
  return this._run;
277
446
  }
@@ -279,68 +448,77 @@ var WorkflowRunHandleImpl = class {
279
448
  const { run: currentRun } = await this.api.workflowRun.getByIdV1({ id: this.run.id });
280
449
  this._run = currentRun;
281
450
  }
282
- async wait(condition, options) {
283
- if (options.abortSignal?.aborted) {
284
- throw new Error("Wait operation aborted");
285
- }
286
- const delayMs = options.pollIntervalMs ?? 1e3;
287
- const maxAttempts = Math.ceil(options.maxDurationMs / delayMs);
288
- switch (condition.type) {
289
- case "status": {
290
- if (options.abortSignal !== void 0) {
291
- const maybeResult2 = await withRetry(
292
- async () => {
293
- await this.refresh();
294
- return this.run.state;
295
- },
296
- { type: "fixed", maxAttempts, delayMs },
297
- {
298
- abortSignal: options.abortSignal,
299
- shouldRetryOnResult: (state) => Promise.resolve(state.status !== condition.status)
300
- }
301
- ).run();
302
- if (maybeResult2.state === "timeout" || maybeResult2.state === "aborted") {
303
- return { success: false, cause: maybeResult2.state };
304
- }
305
- return {
306
- success: true,
307
- state: maybeResult2.result
308
- };
309
- }
310
- const maybeResult = await withRetry(
311
- async () => {
312
- await this.refresh();
313
- return this.run.state;
314
- },
315
- { type: "fixed", maxAttempts, delayMs },
316
- { shouldRetryOnResult: (state) => Promise.resolve(state.status !== condition.status) }
317
- ).run();
318
- if (maybeResult.state === "timeout") {
319
- return { success: false, cause: maybeResult.state };
320
- }
451
+ // TODO: instead checking the current state, use the transition history
452
+ // because it is possible for a workflow to flash though a state
453
+ // and the handle will never know that the workflow hit that state
454
+ async waitForStatus(status, options) {
455
+ return this.waitForStatusByPolling(status, options);
456
+ }
457
+ async waitForStatusByPolling(expectedStatus, options) {
458
+ if (options?.abortSignal?.aborted) {
459
+ return {
460
+ success: false,
461
+ cause: "aborted"
462
+ };
463
+ }
464
+ const delayMs = options?.interval ? toMilliseconds(options.interval) : 1e3;
465
+ const maxAttempts = options?.timeout ? Math.ceil(toMilliseconds(options.timeout) / delayMs) : Number.POSITIVE_INFINITY;
466
+ const retryStrategy = { type: "fixed", maxAttempts, delayMs };
467
+ const loadState = async () => {
468
+ await this.refresh();
469
+ return this.run.state;
470
+ };
471
+ const isNeitherExpectedNorTerminal = (state) => Promise.resolve(state.status !== expectedStatus && !isTerminalWorkflowRunStatus(state.status));
472
+ if (!Number.isFinite(maxAttempts) && !options?.abortSignal) {
473
+ const maybeResult2 = await withRetry(loadState, retryStrategy, {
474
+ shouldRetryOnResult: isNeitherExpectedNorTerminal
475
+ }).run();
476
+ if (maybeResult2.state === "timeout") {
477
+ throw new Error("Something's wrong, this should've never timed out");
478
+ }
479
+ if (await isNeitherExpectedNorTerminal(maybeResult2.result)) {
321
480
  return {
322
- success: true,
323
- state: maybeResult.result
481
+ success: false,
482
+ cause: "run_terminated"
324
483
  };
325
484
  }
326
- case "event": {
327
- throw new Error("Event-based waiting is not yet implemented");
485
+ return {
486
+ success: true,
487
+ state: maybeResult2.result
488
+ };
489
+ }
490
+ const maybeResult = options?.abortSignal ? await withRetry(loadState, retryStrategy, {
491
+ abortSignal: options.abortSignal,
492
+ shouldRetryOnResult: isNeitherExpectedNorTerminal
493
+ }).run() : await withRetry(loadState, retryStrategy, { shouldRetryOnResult: isNeitherExpectedNorTerminal }).run();
494
+ if (maybeResult.state === "completed") {
495
+ if (await isNeitherExpectedNorTerminal(maybeResult.result)) {
496
+ return {
497
+ success: false,
498
+ cause: "run_terminated"
499
+ };
328
500
  }
329
- default:
330
- return condition;
501
+ return {
502
+ success: true,
503
+ state: maybeResult.result
504
+ };
331
505
  }
506
+ return { success: false, cause: maybeResult.state };
332
507
  }
333
508
  async cancel(reason) {
334
509
  return this.transitionState({ status: "cancelled", reason });
335
510
  }
336
511
  async pause() {
337
- return this.transitionState({ status: "paused", pausedAt: Date.now() });
512
+ return this.transitionState({ status: "paused" });
338
513
  }
339
514
  async resume() {
340
- return this.transitionState({ status: "scheduled", scheduledAt: Date.now(), reason: "resume" });
515
+ return this.transitionState({ status: "scheduled", scheduledInMs: 0, reason: "resume" });
516
+ }
517
+ async awake() {
518
+ return this.transitionState({ status: "scheduled", scheduledInMs: 0, reason: "awake" });
341
519
  }
342
520
  async transitionState(targetState) {
343
- if (targetState.status === "scheduled" && (targetState.reason === "new" || targetState.reason === "resume") || targetState.status === "paused" || targetState.status === "cancelled") {
521
+ if (targetState.status === "scheduled" && (targetState.reason === "new" || targetState.reason === "resume" || targetState.reason === "awake") || targetState.status === "paused" || targetState.status === "cancelled") {
344
522
  const { run: run2 } = await this.api.workflowRun.transitionStateV1({
345
523
  type: "pessimistic",
346
524
  id: this.run.id,
@@ -375,11 +553,11 @@ var WorkflowRunHandleImpl = class {
375
553
  };
376
554
 
377
555
  // run/sleeper.ts
378
- import { INTERNAL as INTERNAL2 } from "@aikirun/types/symbols";
379
- import { WorkflowRunSuspendedError } from "@aikirun/types/workflow-run";
556
+ import { INTERNAL as INTERNAL3 } from "@aikirun/types/symbols";
557
+ import { WorkflowRunSuspendedError as WorkflowRunSuspendedError2 } from "@aikirun/types/workflow-run";
380
558
  var MAX_SLEEP_YEARS = 10;
381
559
  var MAX_SLEEP_MS = MAX_SLEEP_YEARS * 365 * 24 * 60 * 60 * 1e3;
382
- function createWorkflowRunSleeper(workflowRunHandle2, logger, options) {
560
+ function createSleeper(handle, logger, options) {
383
561
  return async (params) => {
384
562
  const { id: sleepId, ...durationFields } = params;
385
563
  const durationMs = toMilliseconds(durationFields);
@@ -387,7 +565,7 @@ function createWorkflowRunSleeper(workflowRunHandle2, logger, options) {
387
565
  throw new Error(`Sleep duration ${durationMs}ms exceeds maximum of ${MAX_SLEEP_YEARS} years`);
388
566
  }
389
567
  const sleepPath = `${sleepId}/${durationMs}`;
390
- const sleepState = workflowRunHandle2.run.sleepsState[sleepPath] ?? { status: "none" };
568
+ const sleepState = handle.run.sleepsState[sleepPath] ?? { status: "none" };
391
569
  if (sleepState.status === "completed") {
392
570
  logger.debug("Sleep completed", {
393
571
  "aiki.sleepId": sleepId,
@@ -407,7 +585,7 @@ function createWorkflowRunSleeper(workflowRunHandle2, logger, options) {
407
585
  "aiki.sleepId": sleepId,
408
586
  "aiki.durationMs": durationMs
409
587
  });
410
- throw new WorkflowRunSuspendedError(workflowRunHandle2.run.id);
588
+ throw new WorkflowRunSuspendedError2(handle.run.id);
411
589
  }
412
590
  sleepState;
413
591
  if (durationMs <= options.spinThresholdMs) {
@@ -418,44 +596,131 @@ function createWorkflowRunSleeper(workflowRunHandle2, logger, options) {
418
596
  await delay(durationMs);
419
597
  return { cancelled: false };
420
598
  }
421
- await workflowRunHandle2[INTERNAL2].transitionState({ status: "sleeping", sleepPath, durationMs });
599
+ await handle[INTERNAL3].transitionState({ status: "sleeping", sleepPath, durationMs });
422
600
  logger.info("Workflow going to sleep", {
423
601
  "aiki.sleepId": sleepId,
424
602
  "aiki.durationMs": durationMs
425
603
  });
426
- throw new WorkflowRunSuspendedError(workflowRunHandle2.run.id);
604
+ throw new WorkflowRunSuspendedError2(handle.run.id);
427
605
  };
428
606
  }
429
607
 
430
608
  // workflow.ts
431
- import { INTERNAL as INTERNAL4 } from "@aikirun/types/symbols";
609
+ import { INTERNAL as INTERNAL6 } from "@aikirun/types/symbols";
432
610
 
433
611
  // workflow-version.ts
434
- import { INTERNAL as INTERNAL3 } from "@aikirun/types/symbols";
612
+ import { INTERNAL as INTERNAL5 } from "@aikirun/types/symbols";
435
613
  import { TaskFailedError } from "@aikirun/types/task";
436
614
  import {
437
- WorkflowRunFailedError,
438
- WorkflowRunSuspendedError as WorkflowRunSuspendedError2
615
+ WorkflowRunFailedError as WorkflowRunFailedError2,
616
+ WorkflowRunSuspendedError as WorkflowRunSuspendedError4
617
+ } from "@aikirun/types/workflow-run";
618
+
619
+ // run/handle-child.ts
620
+ import { INTERNAL as INTERNAL4 } from "@aikirun/types/symbols";
621
+ import {
622
+ isTerminalWorkflowRunStatus as isTerminalWorkflowRunStatus2,
623
+ WorkflowRunSuspendedError as WorkflowRunSuspendedError3
439
624
  } from "@aikirun/types/workflow-run";
625
+ async function childWorkflowRunHandle(client, path, run, parentRun, logger, eventsDefinition) {
626
+ const handle = await workflowRunHandle(client, run, eventsDefinition, logger);
627
+ return {
628
+ run: handle.run,
629
+ events: handle.events,
630
+ refresh: handle.refresh.bind(handle),
631
+ waitForStatus: createStatusWaiter(path, handle, parentRun, logger),
632
+ cancel: handle.cancel.bind(handle),
633
+ pause: handle.pause.bind(handle),
634
+ resume: handle.resume.bind(handle),
635
+ awake: handle.awake.bind(handle),
636
+ [INTERNAL4]: handle[INTERNAL4]
637
+ };
638
+ }
639
+ function createStatusWaiter(path, handle, parentRun, logger) {
640
+ let nextWaitIndex = 0;
641
+ async function waitForStatus(expectedStatus, options) {
642
+ const parentRunHandle = parentRun[INTERNAL4].handle;
643
+ const waitResults = parentRunHandle.run.childWorkflowRuns[path]?.statusWaitResults ?? [];
644
+ const waitResult = waitResults[nextWaitIndex];
645
+ if (waitResult) {
646
+ nextWaitIndex++;
647
+ if (waitResult.status === "timeout") {
648
+ logger.debug("Timed out waiting for child workflow status", { "aiki.expectedStatus": expectedStatus });
649
+ return {
650
+ success: false,
651
+ cause: "timeout"
652
+ };
653
+ }
654
+ if (waitResult.childWorkflowRunState.status === expectedStatus) {
655
+ return {
656
+ success: true,
657
+ state: waitResult.childWorkflowRunState
658
+ };
659
+ }
660
+ if (isTerminalWorkflowRunStatus2(waitResult.childWorkflowRunState.status)) {
661
+ logger.debug("Child workflow run reached termnial state");
662
+ return {
663
+ success: false,
664
+ cause: "run_terminated"
665
+ };
666
+ }
667
+ }
668
+ const { state } = handle.run;
669
+ if (state.status === expectedStatus) {
670
+ return {
671
+ success: true,
672
+ state
673
+ };
674
+ }
675
+ if (isTerminalWorkflowRunStatus2(state.status)) {
676
+ logger.debug("Child workflow run reached termnial state");
677
+ return {
678
+ success: false,
679
+ cause: "run_terminated"
680
+ };
681
+ }
682
+ const timeoutInMs = options?.timeout && toMilliseconds(options.timeout);
683
+ await parentRunHandle[INTERNAL4].transitionState({
684
+ status: "awaiting_child_workflow",
685
+ childWorkflowRunPath: path,
686
+ childWorkflowRunStatus: expectedStatus,
687
+ timeoutInMs
688
+ });
689
+ throw new WorkflowRunSuspendedError3(parentRun.id);
690
+ }
691
+ return waitForStatus;
692
+ }
693
+
694
+ // workflow-version.ts
440
695
  var WorkflowVersionImpl = class _WorkflowVersionImpl {
441
696
  constructor(id, versionId, params) {
442
697
  this.id = id;
443
698
  this.versionId = versionId;
444
699
  this.params = params;
445
- this[INTERNAL3] = {
700
+ const eventsDefinition = this.params.events ?? {};
701
+ this.events = createEventMulticasters(eventsDefinition);
702
+ this[INTERNAL5] = {
703
+ eventsDefinition,
446
704
  handler: this.handler.bind(this)
447
705
  };
448
706
  }
449
- [INTERNAL3];
707
+ events;
708
+ [INTERNAL5];
450
709
  with() {
451
710
  const optsOverrider = objectOverrider(this.params.opts ?? {});
452
- const createBuilder = (optsBuilder) => ({
453
- opt: (path, value) => createBuilder(optsBuilder.with(path, value)),
454
- start: (client, ...args) => new _WorkflowVersionImpl(this.id, this.versionId, { ...this.params, opts: optsBuilder.build() }).start(
455
- client,
456
- ...args
457
- )
458
- });
711
+ const createBuilder = (optsBuilder) => {
712
+ return {
713
+ opt: (path, value) => createBuilder(optsBuilder.with(path, value)),
714
+ start: (client, ...args) => new _WorkflowVersionImpl(this.id, this.versionId, {
715
+ ...this.params,
716
+ opts: optsBuilder.build()
717
+ }).start(client, ...args),
718
+ startAsChild: (parentRun, ...args) => new _WorkflowVersionImpl(this.id, this.versionId, {
719
+ ...this.params,
720
+ opts: optsBuilder.build()
721
+ }).startAsChild(parentRun, ...args)
722
+ };
723
+ };
459
724
  return createBuilder(optsOverrider());
460
725
  }
461
726
  async start(client, ...args) {
@@ -465,21 +730,73 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
465
730
  input: isNonEmptyArray(args) ? args[0] : null,
466
731
  options: this.params.opts
467
732
  });
468
- return workflowRunHandle(client, run);
733
+ return workflowRunHandle(client, run, this[INTERNAL5].eventsDefinition);
734
+ }
735
+ async startAsChild(parentRun, ...args) {
736
+ const parentRunHandle = parentRun[INTERNAL5].handle;
737
+ parentRunHandle[INTERNAL5].assertExecutionAllowed();
738
+ const { client } = parentRunHandle[INTERNAL5];
739
+ const input = isNonEmptyArray(args) ? args[0] : null;
740
+ const childRunPath = await this.getPath(input);
741
+ const existingChildRunId = parentRunHandle.run.childWorkflowRuns[childRunPath]?.id;
742
+ if (existingChildRunId) {
743
+ const { run: existingChildRun } = await client.api.workflowRun.getByIdV1({ id: existingChildRunId });
744
+ const logger2 = parentRun.logger.child({
745
+ "aiki.childWorkflowId": existingChildRun.workflowId,
746
+ "aiki.childWorkflowVersionId": existingChildRun.workflowVersionId,
747
+ "aiki.childWorkflowRunId": existingChildRun.id
748
+ });
749
+ return childWorkflowRunHandle(
750
+ client,
751
+ childRunPath,
752
+ existingChildRun,
753
+ parentRun,
754
+ logger2,
755
+ this[INTERNAL5].eventsDefinition
756
+ );
757
+ }
758
+ const { run: newChildRun } = await client.api.workflowRun.createV1({
759
+ workflowId: this.id,
760
+ workflowVersionId: this.versionId,
761
+ input,
762
+ path: childRunPath,
763
+ parentWorkflowRunId: parentRun.id,
764
+ options: {
765
+ ...this.params.opts,
766
+ idempotencyKey: childRunPath
767
+ }
768
+ });
769
+ parentRunHandle.run.childWorkflowRuns[childRunPath] = { id: newChildRun.id, statusWaitResults: [] };
770
+ const logger = parentRun.logger.child({
771
+ "aiki.childWorkflowId": newChildRun.workflowId,
772
+ "aiki.childWorkflowVersionId": newChildRun.workflowVersionId,
773
+ "aiki.childWorkflowRunId": newChildRun.id
774
+ });
775
+ return childWorkflowRunHandle(
776
+ client,
777
+ childRunPath,
778
+ newChildRun,
779
+ parentRun,
780
+ logger,
781
+ this[INTERNAL5].eventsDefinition
782
+ );
783
+ }
784
+ async getHandle(client, runId) {
785
+ return workflowRunHandle(client, runId, this[INTERNAL5].eventsDefinition);
469
786
  }
470
787
  async handler(input, run, context) {
471
788
  const { logger } = run;
472
- const { handle } = run[INTERNAL3];
473
- handle[INTERNAL3].assertExecutionAllowed();
789
+ const { handle } = run[INTERNAL5];
790
+ handle[INTERNAL5].assertExecutionAllowed();
474
791
  const retryStrategy = this.params.opts?.retry ?? { type: "never" };
475
792
  const state = handle.run.state;
476
793
  if (state.status === "queued" && state.reason === "retry") {
477
- this.assertRetryAllowed(handle.run.id, handle.run.attempts, retryStrategy, logger);
794
+ await this.assertRetryAllowed(handle, retryStrategy, logger);
478
795
  }
479
796
  logger.info("Starting workflow");
480
- await handle[INTERNAL3].transitionState({ status: "running" });
797
+ await handle[INTERNAL5].transitionState({ status: "running" });
481
798
  const output = await this.tryExecuteWorkflow(input, run, context, retryStrategy);
482
- await handle[INTERNAL3].transitionState({ status: "completed", output });
799
+ await handle[INTERNAL5].transitionState({ status: "completed", output });
483
800
  logger.info("Workflow complete");
484
801
  }
485
802
  async tryExecuteWorkflow(input, run, context, retryStrategy) {
@@ -487,14 +804,15 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
487
804
  try {
488
805
  return await this.params.handler(input, run, context);
489
806
  } catch (error) {
490
- if (error instanceof WorkflowRunSuspendedError2) {
807
+ if (error instanceof WorkflowRunSuspendedError4 || error instanceof WorkflowRunFailedError2) {
491
808
  throw error;
492
809
  }
493
- const attempts = run[INTERNAL3].handle.run.attempts;
810
+ const { handle } = run[INTERNAL5];
811
+ const attempts = handle.run.attempts;
494
812
  const retryParams = getRetryParams(attempts, retryStrategy);
495
813
  if (!retryParams.retriesLeft) {
496
814
  const failedState = this.createFailedState(error);
497
- await run[INTERNAL3].handle[INTERNAL3].transitionState(failedState);
815
+ await handle[INTERNAL5].transitionState(failedState);
498
816
  const logMeta2 = {};
499
817
  for (const [key, value] of Object.entries(failedState)) {
500
818
  logMeta2[`aiki.${key}`] = value;
@@ -503,32 +821,36 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
503
821
  "aiki.attempts": attempts,
504
822
  ...logMeta2
505
823
  });
506
- throw new WorkflowRunFailedError(run.id, attempts, failedState.reason, failedState.cause);
824
+ throw new WorkflowRunFailedError2(run.id, attempts);
507
825
  }
508
- const nextAttemptAt = Date.now() + retryParams.delayMs;
509
- const awaitingRetryState = this.createAwaitingRetryState(error, nextAttemptAt);
510
- await run[INTERNAL3].handle[INTERNAL3].transitionState(awaitingRetryState);
826
+ const awaitingRetryState = this.createAwaitingRetryState(error, retryParams.delayMs);
827
+ await handle[INTERNAL5].transitionState(awaitingRetryState);
511
828
  const logMeta = {};
512
829
  for (const [key, value] of Object.entries(awaitingRetryState)) {
513
830
  logMeta[`aiki.${key}`] = value;
514
831
  }
515
832
  run.logger.info("Workflow failed. Awaiting retry", {
516
833
  "aiki.attempts": attempts,
517
- "aiki.nextAttemptAt": nextAttemptAt,
518
834
  "aiki.delayMs": retryParams.delayMs,
519
835
  ...logMeta
520
836
  });
521
- throw new WorkflowRunSuspendedError2(run.id);
837
+ throw new WorkflowRunSuspendedError4(run.id);
522
838
  }
523
839
  }
524
840
  }
525
- assertRetryAllowed(id, attempts, retryStrategy, logger) {
841
+ async assertRetryAllowed(handle, retryStrategy, logger) {
842
+ const { id, attempts } = handle.run;
526
843
  const retryParams = getRetryParams(attempts, retryStrategy);
527
844
  if (!retryParams.retriesLeft) {
528
- logger.error("Workflow retry not allowed", {
529
- "aiki.attempts": attempts
845
+ logger.error("Workflow retry not allowed", { "aiki.attempts": attempts });
846
+ const error = new WorkflowRunFailedError2(id, attempts);
847
+ const serializableError = createSerializableError(error);
848
+ await handle[INTERNAL5].transitionState({
849
+ status: "failed",
850
+ cause: "self",
851
+ error: serializableError
530
852
  });
531
- throw new WorkflowRunFailedError(id, attempts, "Workflow retry not allowed");
853
+ throw error;
532
854
  }
533
855
  }
534
856
  createFailedState(error) {
@@ -536,25 +858,22 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
536
858
  return {
537
859
  status: "failed",
538
860
  cause: "task",
539
- taskPath: error.taskPath,
540
- reason: error.reason
861
+ taskPath: error.taskPath
541
862
  };
542
863
  }
543
864
  const serializableError = createSerializableError(error);
544
865
  return {
545
866
  status: "failed",
546
867
  cause: "self",
547
- reason: serializableError.message,
548
868
  error: serializableError
549
869
  };
550
870
  }
551
- createAwaitingRetryState(error, nextAttemptAt) {
871
+ createAwaitingRetryState(error, nextAttemptInMs) {
552
872
  if (error instanceof TaskFailedError) {
553
873
  return {
554
874
  status: "awaiting_retry",
555
875
  cause: "task",
556
- reason: error.reason,
557
- nextAttemptAt,
876
+ nextAttemptInMs,
558
877
  taskPath: error.taskPath
559
878
  };
560
879
  }
@@ -562,11 +881,15 @@ var WorkflowVersionImpl = class _WorkflowVersionImpl {
562
881
  return {
563
882
  status: "awaiting_retry",
564
883
  cause: "self",
565
- reason: serializableError.message,
566
- nextAttemptAt,
884
+ nextAttemptInMs,
567
885
  error: serializableError
568
886
  };
569
887
  }
888
+ async getPath(input) {
889
+ const inputHash = await sha256(stableStringify(input));
890
+ const path = this.params.opts?.idempotencyKey ? `${this.id}/${this.versionId}/${inputHash}/${this.params.opts.idempotencyKey}` : `${this.id}/${this.versionId}/${inputHash}`;
891
+ return path;
892
+ }
570
893
  };
571
894
 
572
895
  // workflow.ts
@@ -575,11 +898,11 @@ function workflow(params) {
575
898
  }
576
899
  var WorkflowImpl = class {
577
900
  id;
578
- [INTERNAL4];
901
+ [INTERNAL6];
579
902
  workflowVersions = /* @__PURE__ */ new Map();
580
903
  constructor(params) {
581
904
  this.id = params.id;
582
- this[INTERNAL4] = {
905
+ this[INTERNAL6] = {
583
906
  getAllVersions: this.getAllVersions.bind(this),
584
907
  getVersion: this.getVersion.bind(this)
585
908
  };
@@ -604,7 +927,10 @@ var WorkflowImpl = class {
604
927
  };
605
928
  export {
606
929
  WorkflowVersionImpl,
607
- createWorkflowRunSleeper,
930
+ createEventSenders,
931
+ createEventWaiters,
932
+ createSleeper,
933
+ event,
608
934
  workflow,
609
935
  workflowRegistry,
610
936
  workflowRunHandle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikirun/workflow",
3
- "version": "0.5.3",
3
+ "version": "0.7.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.7.0"
22
22
  },
23
23
  "publishConfig": {
24
24
  "access": "public"