@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.
- package/dist/index.d.ts +213 -52
- package/dist/index.js +431 -105
- 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,
|
|
4
|
+
import { WorkflowRun, TerminalWorkflowRunStatus, WorkflowRunState, WorkflowRunId, WorkflowOptions } from '@aikirun/types/workflow-run';
|
|
5
5
|
import { SleepParams, SleepResult } from '@aikirun/types/sleep';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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<
|
|
29
|
-
declare function workflowRunHandle<Input, Output>(client: Client<
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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,
|
|
179
|
+
handle: WorkflowRunHandle<Input, unknown, AppContext, TEventDefinition>;
|
|
180
|
+
options: {
|
|
181
|
+
spinThresholdMs: number;
|
|
182
|
+
};
|
|
77
183
|
};
|
|
78
184
|
}
|
|
79
185
|
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
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
|
|
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,
|
|
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/
|
|
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(
|
|
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(
|
|
265
|
-
this.api = api;
|
|
427
|
+
constructor(client, _run, eventsDefinition, logger) {
|
|
266
428
|
this._run = _run;
|
|
267
429
|
this.logger = logger;
|
|
268
|
-
this
|
|
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
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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:
|
|
323
|
-
|
|
481
|
+
success: false,
|
|
482
|
+
cause: "run_terminated"
|
|
324
483
|
};
|
|
325
484
|
}
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
330
|
-
|
|
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"
|
|
512
|
+
return this.transitionState({ status: "paused" });
|
|
338
513
|
}
|
|
339
514
|
async resume() {
|
|
340
|
-
return this.transitionState({ status: "scheduled",
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
604
|
+
throw new WorkflowRunSuspendedError2(handle.run.id);
|
|
427
605
|
};
|
|
428
606
|
}
|
|
429
607
|
|
|
430
608
|
// workflow.ts
|
|
431
|
-
import { INTERNAL as
|
|
609
|
+
import { INTERNAL as INTERNAL6 } from "@aikirun/types/symbols";
|
|
432
610
|
|
|
433
611
|
// workflow-version.ts
|
|
434
|
-
import { INTERNAL as
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
707
|
+
events;
|
|
708
|
+
[INTERNAL5];
|
|
450
709
|
with() {
|
|
451
710
|
const optsOverrider = objectOverrider(this.params.opts ?? {});
|
|
452
|
-
const createBuilder = (optsBuilder) =>
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
client,
|
|
456
|
-
|
|
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[
|
|
473
|
-
handle[
|
|
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
|
|
794
|
+
await this.assertRetryAllowed(handle, retryStrategy, logger);
|
|
478
795
|
}
|
|
479
796
|
logger.info("Starting workflow");
|
|
480
|
-
await handle[
|
|
797
|
+
await handle[INTERNAL5].transitionState({ status: "running" });
|
|
481
798
|
const output = await this.tryExecuteWorkflow(input, run, context, retryStrategy);
|
|
482
|
-
await handle[
|
|
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
|
|
807
|
+
if (error instanceof WorkflowRunSuspendedError4 || error instanceof WorkflowRunFailedError2) {
|
|
491
808
|
throw error;
|
|
492
809
|
}
|
|
493
|
-
const
|
|
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
|
|
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
|
|
824
|
+
throw new WorkflowRunFailedError2(run.id, attempts);
|
|
507
825
|
}
|
|
508
|
-
const
|
|
509
|
-
|
|
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
|
|
837
|
+
throw new WorkflowRunSuspendedError4(run.id);
|
|
522
838
|
}
|
|
523
839
|
}
|
|
524
840
|
}
|
|
525
|
-
assertRetryAllowed(
|
|
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
|
-
|
|
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
|
|
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,
|
|
871
|
+
createAwaitingRetryState(error, nextAttemptInMs) {
|
|
552
872
|
if (error instanceof TaskFailedError) {
|
|
553
873
|
return {
|
|
554
874
|
status: "awaiting_retry",
|
|
555
875
|
cause: "task",
|
|
556
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
901
|
+
[INTERNAL6];
|
|
579
902
|
workflowVersions = /* @__PURE__ */ new Map();
|
|
580
903
|
constructor(params) {
|
|
581
904
|
this.id = params.id;
|
|
582
|
-
this[
|
|
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
|
-
|
|
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.
|
|
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.
|
|
21
|
+
"@aikirun/types": "0.7.0"
|
|
22
22
|
},
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"access": "public"
|