@aikirun/task 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 CHANGED
@@ -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
  *
@@ -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: <WorkflowInput, WorkflowOutput>(run: WorkflowRunContext<WorkflowInput, WorkflowOutput, 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
  }
@@ -171,22 +172,25 @@ var TaskImpl = class _TaskImpl {
171
172
  if (taskState.status === "completed") {
172
173
  return taskState.output;
173
174
  }
175
+ if (taskState.status === "failed") {
176
+ throw new TaskFailedError(path, taskState.attempts, taskState.error.message);
177
+ }
174
178
  const logger = run.logger.child({
175
179
  "aiki.component": "task-execution",
176
180
  "aiki.taskPath": path
177
181
  });
178
182
  let attempts = 0;
179
183
  const retryStrategy = this.params.opts?.retry ?? { type: "never" };
180
- if ("attempts" in taskState) {
184
+ if (taskState.status !== "none") {
181
185
  this.assertRetryAllowed(path, taskState.attempts, retryStrategy, logger);
182
- logger.warn("Retrying task", {
186
+ logger.debug("Retrying task", {
183
187
  "aiki.attempts": taskState.attempts,
184
188
  "aiki.taskStatus": taskState.status
185
189
  });
186
190
  attempts = taskState.attempts;
187
191
  }
188
- if (taskState.status === "failed") {
189
- await this.delayIfNecessary(taskState);
192
+ if (taskState.status === "awaiting_retry" && handle.run.state.status === "running") {
193
+ throw new WorkflowRunSuspendedError(run.id);
190
194
  }
191
195
  attempts++;
192
196
  logger.info("Starting task", { "aiki.attempts": attempts });
@@ -199,37 +203,41 @@ var TaskImpl = class _TaskImpl {
199
203
  async tryExecuteTask(run, input, path, retryStrategy, currentAttempt, logger) {
200
204
  let attempts = currentAttempt;
201
205
  while (true) {
202
- const attemptedAt = Date.now();
203
206
  try {
204
207
  const output = await this.params.handler(input);
205
208
  return { output, lastAttempt: attempts };
206
209
  } catch (error) {
207
210
  const serializableError = createSerializableError(error);
208
- const taskFailedState = {
209
- status: "failed",
210
- reason: serializableError.message,
211
- attempts,
212
- attemptedAt,
213
- error: serializableError
214
- };
215
211
  const retryParams = getRetryParams(attempts, retryStrategy);
216
212
  if (!retryParams.retriesLeft) {
217
- await run[INTERNAL].handle[INTERNAL].transitionTaskState(path, taskFailedState);
218
213
  logger.error("Task failed", {
219
214
  "aiki.attempts": attempts,
220
- "aiki.reason": taskFailedState.reason
215
+ "aiki.reason": serializableError.message
216
+ });
217
+ await run[INTERNAL].handle[INTERNAL].transitionTaskState(path, {
218
+ status: "failed",
219
+ attempts,
220
+ error: serializableError
221
221
  });
222
- throw new TaskFailedError(path, attempts, taskFailedState.reason);
222
+ throw new TaskFailedError(path, attempts, serializableError.message);
223
223
  }
224
- const nextAttemptAt = Date.now() + retryParams.delayMs;
225
- await run[INTERNAL].handle[INTERNAL].transitionTaskState(path, { ...taskFailedState, nextAttemptAt });
226
- logger.debug("Task failed. Retrying", {
224
+ logger.debug("Task failed. It will be retried", {
227
225
  "aiki.attempts": attempts,
228
- "aiki.nextAttemptAt": nextAttemptAt,
229
- "aiki.reason": taskFailedState.reason
226
+ "aiki.nextAttemptInMs": retryParams.delayMs,
227
+ "aiki.reason": serializableError.message
230
228
  });
231
- await delay(retryParams.delayMs);
232
- attempts++;
229
+ if (retryParams.delayMs <= run[INTERNAL].options.spinThresholdMs) {
230
+ await delay(retryParams.delayMs);
231
+ attempts++;
232
+ continue;
233
+ }
234
+ await run[INTERNAL].handle[INTERNAL].transitionTaskState(path, {
235
+ status: "awaiting_retry",
236
+ attempts,
237
+ error: serializableError,
238
+ nextAttemptInMs: retryParams.delayMs
239
+ });
240
+ throw new WorkflowRunSuspendedError(run.id);
233
241
  }
234
242
  }
235
243
  }
@@ -242,15 +250,6 @@ var TaskImpl = class _TaskImpl {
242
250
  throw new TaskFailedError(path, attempts, "Task retry not allowed");
243
251
  }
244
252
  }
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
253
  async getPath(input) {
255
254
  const inputHash = await sha256(stableStringify(input));
256
255
  const taskPath = this.params.opts?.idempotencyKey ? `${this.id}/${inputHash}/${this.params.opts.idempotencyKey}` : `${this.id}/${inputHash}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikirun/task",
3
- "version": "0.5.3",
3
+ "version": "0.6.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.6.0",
22
+ "@aikirun/workflow": "0.6.0"
23
23
  },
24
24
  "publishConfig": {
25
25
  "access": "public"