@aikirun/task 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 +12 -3
- package/dist/index.js +33 -37
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Serializable } from '@aikirun/types/error';
|
|
2
2
|
import { RetryStrategy } from '@aikirun/types/retry';
|
|
3
3
|
import { TaskId } from '@aikirun/types/task';
|
|
4
4
|
import { WorkflowRunContext } from '@aikirun/workflow';
|
|
@@ -22,6 +22,15 @@ type PathFromObjectInternal<T, IncludeArrayKeys extends boolean> = And<[
|
|
|
22
22
|
type ExtractObjectType<T> = T extends object ? T : never;
|
|
23
23
|
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;
|
|
24
24
|
|
|
25
|
+
interface Schema<Data> {
|
|
26
|
+
parse: (data: unknown) => Data;
|
|
27
|
+
}
|
|
28
|
+
interface EventDefinition<Data> {
|
|
29
|
+
_type: Data;
|
|
30
|
+
schema?: Schema<Data>;
|
|
31
|
+
}
|
|
32
|
+
type EventsDefinition = Record<string, EventDefinition<unknown>>;
|
|
33
|
+
|
|
25
34
|
/**
|
|
26
35
|
* Defines a durable task with deterministic execution and automatic retries.
|
|
27
36
|
*
|
|
@@ -65,7 +74,7 @@ type TypeOfValueAtPath<T extends object, Path extends PathFromObject<T>> = Path
|
|
|
65
74
|
* const result = await chargeCard.start(run, { cardId: "123", amount: 9999 });
|
|
66
75
|
* ```
|
|
67
76
|
*/
|
|
68
|
-
declare function task<Input extends
|
|
77
|
+
declare function task<Input extends Serializable = null, Output = void>(params: TaskParams<Input, Output>): Task<Input, Output>;
|
|
69
78
|
interface TaskParams<Input, Output> {
|
|
70
79
|
id: string;
|
|
71
80
|
handler: (input: Input) => Promise<Output>;
|
|
@@ -82,7 +91,7 @@ interface TaskBuilder<Input, Output> {
|
|
|
82
91
|
interface Task<Input, Output> {
|
|
83
92
|
id: TaskId;
|
|
84
93
|
with(): TaskBuilder<Input, Output>;
|
|
85
|
-
start:
|
|
94
|
+
start: (run: WorkflowRunContext<unknown, unknown, EventsDefinition>, ...args: Input extends null ? [] : [Input]) => Promise<Output>;
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
export { type Task, type TaskParams, task };
|
package/dist/index.js
CHANGED
|
@@ -142,6 +142,7 @@ function getRetryParams(attempts, strategy) {
|
|
|
142
142
|
// task.ts
|
|
143
143
|
import { INTERNAL } from "@aikirun/types/symbols";
|
|
144
144
|
import { TaskFailedError } from "@aikirun/types/task";
|
|
145
|
+
import { WorkflowRunSuspendedError } from "@aikirun/types/workflow-run";
|
|
145
146
|
function task(params) {
|
|
146
147
|
return new TaskImpl(params);
|
|
147
148
|
}
|
|
@@ -162,31 +163,31 @@ var TaskImpl = class _TaskImpl {
|
|
|
162
163
|
async start(run, ...args) {
|
|
163
164
|
const handle = run[INTERNAL].handle;
|
|
164
165
|
handle[INTERNAL].assertExecutionAllowed();
|
|
165
|
-
const input = isNonEmptyArray(args) ? args[0] :
|
|
166
|
-
// this cast is okay cos if args is empty, Input must be type null
|
|
167
|
-
null
|
|
168
|
-
);
|
|
166
|
+
const input = isNonEmptyArray(args) ? args[0] : null;
|
|
169
167
|
const path = await this.getPath(input);
|
|
170
168
|
const taskState = handle.run.tasksState[path] ?? { status: "none" };
|
|
171
169
|
if (taskState.status === "completed") {
|
|
172
170
|
return taskState.output;
|
|
173
171
|
}
|
|
172
|
+
if (taskState.status === "failed") {
|
|
173
|
+
throw new TaskFailedError(path, taskState.attempts, taskState.error.message);
|
|
174
|
+
}
|
|
174
175
|
const logger = run.logger.child({
|
|
175
176
|
"aiki.component": "task-execution",
|
|
176
177
|
"aiki.taskPath": path
|
|
177
178
|
});
|
|
178
179
|
let attempts = 0;
|
|
179
180
|
const retryStrategy = this.params.opts?.retry ?? { type: "never" };
|
|
180
|
-
if ("
|
|
181
|
+
if (taskState.status !== "none") {
|
|
181
182
|
this.assertRetryAllowed(path, taskState.attempts, retryStrategy, logger);
|
|
182
|
-
logger.
|
|
183
|
+
logger.debug("Retrying task", {
|
|
183
184
|
"aiki.attempts": taskState.attempts,
|
|
184
185
|
"aiki.taskStatus": taskState.status
|
|
185
186
|
});
|
|
186
187
|
attempts = taskState.attempts;
|
|
187
188
|
}
|
|
188
|
-
if (taskState.status === "
|
|
189
|
-
|
|
189
|
+
if (taskState.status === "awaiting_retry" && handle.run.state.status === "running") {
|
|
190
|
+
throw new WorkflowRunSuspendedError(run.id);
|
|
190
191
|
}
|
|
191
192
|
attempts++;
|
|
192
193
|
logger.info("Starting task", { "aiki.attempts": attempts });
|
|
@@ -199,37 +200,41 @@ var TaskImpl = class _TaskImpl {
|
|
|
199
200
|
async tryExecuteTask(run, input, path, retryStrategy, currentAttempt, logger) {
|
|
200
201
|
let attempts = currentAttempt;
|
|
201
202
|
while (true) {
|
|
202
|
-
const attemptedAt = Date.now();
|
|
203
203
|
try {
|
|
204
204
|
const output = await this.params.handler(input);
|
|
205
205
|
return { output, lastAttempt: attempts };
|
|
206
206
|
} catch (error) {
|
|
207
207
|
const serializableError = createSerializableError(error);
|
|
208
|
-
const taskFailedState = {
|
|
209
|
-
status: "failed",
|
|
210
|
-
reason: serializableError.message,
|
|
211
|
-
attempts,
|
|
212
|
-
attemptedAt,
|
|
213
|
-
error: serializableError
|
|
214
|
-
};
|
|
215
208
|
const retryParams = getRetryParams(attempts, retryStrategy);
|
|
216
209
|
if (!retryParams.retriesLeft) {
|
|
217
|
-
await run[INTERNAL].handle[INTERNAL].transitionTaskState(path, taskFailedState);
|
|
218
210
|
logger.error("Task failed", {
|
|
219
211
|
"aiki.attempts": attempts,
|
|
220
|
-
"aiki.reason":
|
|
212
|
+
"aiki.reason": serializableError.message
|
|
213
|
+
});
|
|
214
|
+
await run[INTERNAL].handle[INTERNAL].transitionTaskState(path, {
|
|
215
|
+
status: "failed",
|
|
216
|
+
attempts,
|
|
217
|
+
error: serializableError
|
|
221
218
|
});
|
|
222
|
-
throw new TaskFailedError(path, attempts,
|
|
219
|
+
throw new TaskFailedError(path, attempts, serializableError.message);
|
|
223
220
|
}
|
|
224
|
-
|
|
225
|
-
await run[INTERNAL].handle[INTERNAL].transitionTaskState(path, { ...taskFailedState, nextAttemptAt });
|
|
226
|
-
logger.debug("Task failed. Retrying", {
|
|
221
|
+
logger.debug("Task failed. It will be retried", {
|
|
227
222
|
"aiki.attempts": attempts,
|
|
228
|
-
"aiki.
|
|
229
|
-
"aiki.reason":
|
|
223
|
+
"aiki.nextAttemptInMs": retryParams.delayMs,
|
|
224
|
+
"aiki.reason": serializableError.message
|
|
230
225
|
});
|
|
231
|
-
|
|
232
|
-
|
|
226
|
+
if (retryParams.delayMs <= run[INTERNAL].options.spinThresholdMs) {
|
|
227
|
+
await delay(retryParams.delayMs);
|
|
228
|
+
attempts++;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
await run[INTERNAL].handle[INTERNAL].transitionTaskState(path, {
|
|
232
|
+
status: "awaiting_retry",
|
|
233
|
+
attempts,
|
|
234
|
+
error: serializableError,
|
|
235
|
+
nextAttemptInMs: retryParams.delayMs
|
|
236
|
+
});
|
|
237
|
+
throw new WorkflowRunSuspendedError(run.id);
|
|
233
238
|
}
|
|
234
239
|
}
|
|
235
240
|
}
|
|
@@ -242,19 +247,10 @@ var TaskImpl = class _TaskImpl {
|
|
|
242
247
|
throw new TaskFailedError(path, attempts, "Task retry not allowed");
|
|
243
248
|
}
|
|
244
249
|
}
|
|
245
|
-
async delayIfNecessary(taskState) {
|
|
246
|
-
if (taskState.nextAttemptAt !== void 0) {
|
|
247
|
-
const now = Date.now();
|
|
248
|
-
const remainingDelay = Math.max(0, taskState.nextAttemptAt - now);
|
|
249
|
-
if (remainingDelay > 0) {
|
|
250
|
-
await delay(remainingDelay);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
250
|
async getPath(input) {
|
|
255
251
|
const inputHash = await sha256(stableStringify(input));
|
|
256
|
-
const
|
|
257
|
-
return
|
|
252
|
+
const path = this.params.opts?.idempotencyKey ? `${this.id}/${inputHash}/${this.params.opts.idempotencyKey}` : `${this.id}/${inputHash}`;
|
|
253
|
+
return path;
|
|
258
254
|
}
|
|
259
255
|
};
|
|
260
256
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikirun/task",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Task SDK for Aiki - define reliable tasks with automatic retries, idempotency, and error handling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"build": "tsup"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@aikirun/types": "0.
|
|
22
|
-
"@aikirun/workflow": "0.
|
|
21
|
+
"@aikirun/types": "0.7.0",
|
|
22
|
+
"@aikirun/workflow": "0.7.0"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|