@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 CHANGED
@@ -1,4 +1,4 @@
1
- import { SerializableInput } from '@aikirun/types/error';
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 SerializableInput = null, Output = void>(params: TaskParams<Input, Output>): Task<Input, Output>;
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: <WorkflowInput, WorkflowOutput>(run: WorkflowRunContext<WorkflowInput, WorkflowOutput>, ...args: Input extends null ? [] : [Input]) => Promise<Output>;
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 ("attempts" in taskState) {
181
+ if (taskState.status !== "none") {
181
182
  this.assertRetryAllowed(path, taskState.attempts, retryStrategy, logger);
182
- logger.warn("Retrying task", {
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 === "failed") {
189
- await this.delayIfNecessary(taskState);
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": taskFailedState.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, taskFailedState.reason);
219
+ throw new TaskFailedError(path, attempts, serializableError.message);
223
220
  }
224
- const nextAttemptAt = Date.now() + retryParams.delayMs;
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.nextAttemptAt": nextAttemptAt,
229
- "aiki.reason": taskFailedState.reason
223
+ "aiki.nextAttemptInMs": retryParams.delayMs,
224
+ "aiki.reason": serializableError.message
230
225
  });
231
- await delay(retryParams.delayMs);
232
- attempts++;
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 taskPath = this.params.opts?.idempotencyKey ? `${this.id}/${inputHash}/${this.params.opts.idempotencyKey}` : `${this.id}/${inputHash}`;
257
- return taskPath;
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.5.3",
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.5.3",
22
- "@aikirun/workflow": "0.5.3"
21
+ "@aikirun/types": "0.7.0",
22
+ "@aikirun/workflow": "0.7.0"
23
23
  },
24
24
  "publishConfig": {
25
25
  "access": "public"