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