@aikirun/task 0.8.0 → 0.9.1
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/README.md +24 -132
- package/dist/index.js +32 -33
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# @aikirun/task
|
|
2
2
|
|
|
3
|
-
Task SDK for Aiki durable execution platform
|
|
4
|
-
handling.
|
|
3
|
+
Task SDK for Aiki durable execution platform.
|
|
5
4
|
|
|
6
5
|
## Installation
|
|
7
6
|
|
|
@@ -11,159 +10,52 @@ npm install @aikirun/task
|
|
|
11
10
|
|
|
12
11
|
## Quick Start
|
|
13
12
|
|
|
14
|
-
### Define a Simple Task
|
|
15
|
-
|
|
16
13
|
```typescript
|
|
17
14
|
import { task } from "@aikirun/task";
|
|
18
15
|
|
|
19
|
-
export const
|
|
20
|
-
name: "send-
|
|
21
|
-
async handler(input: { email: string }) {
|
|
22
|
-
return emailService.
|
|
16
|
+
export const sendEmail = task({
|
|
17
|
+
name: "send-email",
|
|
18
|
+
async handler(input: { email: string; message: string }) {
|
|
19
|
+
return emailService.send(input.email, input.message);
|
|
23
20
|
},
|
|
24
21
|
});
|
|
25
22
|
```
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
export const ringAlarm = task({
|
|
31
|
-
name: "ring-alarm",
|
|
32
|
-
handler(input: { song: string }) {
|
|
33
|
-
return Promise.resolve(audioService.play(input.song));
|
|
34
|
-
},
|
|
35
|
-
opts: {
|
|
36
|
-
retry: {
|
|
37
|
-
type: "fixed",
|
|
38
|
-
maxAttempts: 3,
|
|
39
|
-
delayMs: 1000,
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Execute Task in a Workflow
|
|
24
|
+
Execute in a workflow:
|
|
46
25
|
|
|
47
26
|
```typescript
|
|
48
27
|
import { workflow } from "@aikirun/workflow";
|
|
49
28
|
|
|
50
|
-
export const
|
|
29
|
+
export const notificationWorkflow = workflow({ name: "notifications" });
|
|
51
30
|
|
|
52
|
-
export const
|
|
53
|
-
async handler(run, input) {
|
|
54
|
-
|
|
55
|
-
|
|
31
|
+
export const notificationWorkflowV1 = notificationWorkflow.v("1.0.0", {
|
|
32
|
+
async handler(run, input: { email: string }) {
|
|
33
|
+
await sendEmail.start(run, {
|
|
34
|
+
email: input.email,
|
|
35
|
+
message: "Welcome!",
|
|
36
|
+
});
|
|
37
|
+
return { sent: true };
|
|
56
38
|
},
|
|
57
39
|
});
|
|
58
40
|
```
|
|
59
41
|
|
|
60
42
|
## Features
|
|
61
43
|
|
|
62
|
-
- **
|
|
63
|
-
- **
|
|
64
|
-
- **Reference IDs** - Custom identifiers for
|
|
65
|
-
- **
|
|
66
|
-
- **
|
|
67
|
-
- **Type Safety** - Full TypeScript support with input/output types
|
|
68
|
-
|
|
69
|
-
## Task Configuration
|
|
44
|
+
- **Automatic Retries** - Configurable retry strategies (fixed, exponential, jittered)
|
|
45
|
+
- **Idempotent Execution** - Same input returns cached result
|
|
46
|
+
- **Reference IDs** - Custom identifiers for deduplication
|
|
47
|
+
- **Schema Validation** - Validate input and output at runtime
|
|
48
|
+
- **Type Safety** - Full TypeScript support
|
|
70
49
|
|
|
71
|
-
|
|
72
|
-
interface TaskOptions {
|
|
73
|
-
retry?: RetryStrategy;
|
|
74
|
-
reference?: { id: string; onConflict?: "error" | "return_existing" };
|
|
75
|
-
}
|
|
76
|
-
```
|
|
50
|
+
## Documentation
|
|
77
51
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
#### Never Retry
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
opts: {
|
|
84
|
-
retry: { type: "never" },
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
#### Fixed Delay
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
opts: {
|
|
92
|
-
retry: {
|
|
93
|
-
type: "fixed",
|
|
94
|
-
maxAttempts: 3,
|
|
95
|
-
delayMs: 1000,
|
|
96
|
-
},
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
#### Exponential Backoff
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
opts: {
|
|
104
|
-
retry: {
|
|
105
|
-
type: "exponential",
|
|
106
|
-
maxAttempts: 5,
|
|
107
|
-
baseDelayMs: 1000,
|
|
108
|
-
factor: 2,
|
|
109
|
-
maxDelayMs: 30000,
|
|
110
|
-
},
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
#### Jittered Exponential
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
opts: {
|
|
118
|
-
retry: {
|
|
119
|
-
type: "jittered",
|
|
120
|
-
maxAttempts: 5,
|
|
121
|
-
baseDelayMs: 1000,
|
|
122
|
-
jitterFactor: 0.1,
|
|
123
|
-
maxDelayMs: 30000,
|
|
124
|
-
},
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Execution Context
|
|
129
|
-
|
|
130
|
-
Tasks are executed within a workflow's execution context. Logging happens in the workflow:
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
export const processPayment = task({
|
|
134
|
-
name: "process-payment",
|
|
135
|
-
async handler(input: { amount: number }) {
|
|
136
|
-
return { success: true, transactionId: "tx_123" };
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
export const paymentWorkflowV1 = paymentWorkflow.v("1.0.0", {
|
|
141
|
-
async handler(run, input) {
|
|
142
|
-
run.logger.info("Processing payment", { amount: input.amount });
|
|
143
|
-
const result = await processPayment.start(run, { amount: input.amount });
|
|
144
|
-
run.logger.info("Payment complete", result);
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## Best Practices
|
|
150
|
-
|
|
151
|
-
1. **Make Tasks Idempotent** - Tasks may be retried, so re-running should not cause unintended side effects
|
|
152
|
-
2. **Use Reference IDs** - Use custom reference IDs to prevent duplicate processing
|
|
153
|
-
3. **Use Meaningful Errors** - Help diagnose failures
|
|
154
|
-
4. **Log Information** - Use `run.logger` for debugging
|
|
155
|
-
5. **Keep Tasks Focused** - One responsibility per task
|
|
52
|
+
For comprehensive documentation including retry strategies, schema validation, and best practices, see the [Tasks Guide](https://aiki.run/docs/core-concepts/tasks).
|
|
156
53
|
|
|
157
54
|
## Related Packages
|
|
158
55
|
|
|
159
|
-
- [@aikirun/workflow](https://www.npmjs.com/package/@aikirun/workflow) -
|
|
160
|
-
- [@aikirun/
|
|
161
|
-
- [@aikirun/
|
|
162
|
-
- [@aikirun/types](https://www.npmjs.com/package/@aikirun/types) - Type definitions
|
|
163
|
-
|
|
164
|
-
## Changelog
|
|
165
|
-
|
|
166
|
-
See the [CHANGELOG](https://github.com/aikirun/aiki/blob/main/CHANGELOG.md) for version history.
|
|
56
|
+
- [@aikirun/workflow](https://www.npmjs.com/package/@aikirun/workflow) - Define workflows
|
|
57
|
+
- [@aikirun/client](https://www.npmjs.com/package/@aikirun/client) - Start workflows
|
|
58
|
+
- [@aikirun/worker](https://www.npmjs.com/package/@aikirun/worker) - Execute workflows
|
|
167
59
|
|
|
168
60
|
## License
|
|
169
61
|
|
package/dist/index.js
CHANGED
|
@@ -182,19 +182,7 @@ var TaskImpl = class _TaskImpl {
|
|
|
182
182
|
const handle = run[INTERNAL].handle;
|
|
183
183
|
handle[INTERNAL].assertExecutionAllowed();
|
|
184
184
|
const inputRaw = isNonEmptyArray(args) ? args[0] : void 0;
|
|
185
|
-
|
|
186
|
-
if (this.params.schema?.input) {
|
|
187
|
-
try {
|
|
188
|
-
input = this.params.schema.input.parse(inputRaw);
|
|
189
|
-
} catch (error) {
|
|
190
|
-
await handle[INTERNAL].transitionState({
|
|
191
|
-
status: "failed",
|
|
192
|
-
cause: "self",
|
|
193
|
-
error: createSerializableError(error)
|
|
194
|
-
});
|
|
195
|
-
throw new WorkflowRunFailedError(run.id, handle.run.attempts);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
185
|
+
const input = await this.parse(handle, this.params.schema?.input, inputRaw);
|
|
198
186
|
const inputHash = await hashInput(input);
|
|
199
187
|
const reference = this.params.opts?.reference;
|
|
200
188
|
const path = getTaskPath(this.name, reference?.id ?? inputHash);
|
|
@@ -203,7 +191,7 @@ var TaskImpl = class _TaskImpl {
|
|
|
203
191
|
await this.assertUniqueTaskReferenceId(handle, existingTaskInfo, inputHash, reference, run.logger);
|
|
204
192
|
}
|
|
205
193
|
if (existingTaskInfo?.state.status === "completed") {
|
|
206
|
-
return existingTaskInfo.state.output;
|
|
194
|
+
return this.parse(handle, this.params.schema?.output, existingTaskInfo.state.output);
|
|
207
195
|
}
|
|
208
196
|
if (existingTaskInfo?.state.status === "failed") {
|
|
209
197
|
const { state } = existingTaskInfo;
|
|
@@ -245,7 +233,15 @@ var TaskImpl = class _TaskImpl {
|
|
|
245
233
|
"aiki.taskId": taskId
|
|
246
234
|
});
|
|
247
235
|
logger.info("Task started", { "aiki.attempts": attempts });
|
|
248
|
-
const { output, lastAttempt } = await this.tryExecuteTask(
|
|
236
|
+
const { output, lastAttempt } = await this.tryExecuteTask(
|
|
237
|
+
handle,
|
|
238
|
+
input,
|
|
239
|
+
taskId,
|
|
240
|
+
retryStrategy,
|
|
241
|
+
attempts,
|
|
242
|
+
run[INTERNAL].options.spinThresholdMs,
|
|
243
|
+
logger
|
|
244
|
+
);
|
|
249
245
|
await handle[INTERNAL].transitionTaskState({
|
|
250
246
|
taskId,
|
|
251
247
|
taskState: { status: "completed", attempts: lastAttempt, output }
|
|
@@ -253,24 +249,12 @@ var TaskImpl = class _TaskImpl {
|
|
|
253
249
|
logger.info("Task complete", { "aiki.attempts": lastAttempt });
|
|
254
250
|
return output;
|
|
255
251
|
}
|
|
256
|
-
async tryExecuteTask(
|
|
252
|
+
async tryExecuteTask(handle, input, taskId, retryStrategy, currentAttempt, spinThresholdMs, logger) {
|
|
257
253
|
let attempts = currentAttempt;
|
|
258
254
|
while (true) {
|
|
259
255
|
try {
|
|
260
256
|
const outputRaw = await this.params.handler(input);
|
|
261
|
-
|
|
262
|
-
if (this.params.schema?.output) {
|
|
263
|
-
try {
|
|
264
|
-
output = this.params.schema.output.parse(outputRaw);
|
|
265
|
-
} catch (error) {
|
|
266
|
-
await run[INTERNAL].handle[INTERNAL].transitionState({
|
|
267
|
-
status: "failed",
|
|
268
|
-
cause: "self",
|
|
269
|
-
error: createSerializableError(error)
|
|
270
|
-
});
|
|
271
|
-
throw new WorkflowRunFailedError(run.id, run[INTERNAL].handle.run.attempts);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
257
|
+
const output = await this.parse(handle, this.params.schema?.output, outputRaw);
|
|
274
258
|
return { output, lastAttempt: attempts };
|
|
275
259
|
} catch (error) {
|
|
276
260
|
if (error instanceof WorkflowRunFailedError || error instanceof WorkflowRunSuspendedError || error instanceof WorkflowRunConflictError) {
|
|
@@ -283,7 +267,7 @@ var TaskImpl = class _TaskImpl {
|
|
|
283
267
|
"aiki.attempts": attempts,
|
|
284
268
|
"aiki.reason": serializableError.message
|
|
285
269
|
});
|
|
286
|
-
await
|
|
270
|
+
await handle[INTERNAL].transitionTaskState({
|
|
287
271
|
taskId,
|
|
288
272
|
taskState: { status: "failed", attempts, error: serializableError }
|
|
289
273
|
});
|
|
@@ -294,12 +278,12 @@ var TaskImpl = class _TaskImpl {
|
|
|
294
278
|
"aiki.nextAttemptInMs": retryParams.delayMs,
|
|
295
279
|
"aiki.reason": serializableError.message
|
|
296
280
|
});
|
|
297
|
-
if (retryParams.delayMs <=
|
|
281
|
+
if (retryParams.delayMs <= spinThresholdMs) {
|
|
298
282
|
await delay(retryParams.delayMs);
|
|
299
283
|
attempts++;
|
|
300
284
|
continue;
|
|
301
285
|
}
|
|
302
|
-
await
|
|
286
|
+
await handle[INTERNAL].transitionTaskState({
|
|
303
287
|
taskId,
|
|
304
288
|
taskState: {
|
|
305
289
|
status: "awaiting_retry",
|
|
@@ -308,7 +292,7 @@ var TaskImpl = class _TaskImpl {
|
|
|
308
292
|
nextAttemptInMs: retryParams.delayMs
|
|
309
293
|
}
|
|
310
294
|
});
|
|
311
|
-
throw new WorkflowRunSuspendedError(run.id);
|
|
295
|
+
throw new WorkflowRunSuspendedError(handle.run.id);
|
|
312
296
|
}
|
|
313
297
|
}
|
|
314
298
|
}
|
|
@@ -348,6 +332,21 @@ var TaskImpl = class _TaskImpl {
|
|
|
348
332
|
throw new TaskFailedError(taskId, attempts, "Task retry not allowed");
|
|
349
333
|
}
|
|
350
334
|
}
|
|
335
|
+
async parse(handle, schema, data) {
|
|
336
|
+
if (!schema) {
|
|
337
|
+
return data;
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
return schema.parse(data);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
await handle[INTERNAL].transitionState({
|
|
343
|
+
status: "failed",
|
|
344
|
+
cause: "self",
|
|
345
|
+
error: createSerializableError(error)
|
|
346
|
+
});
|
|
347
|
+
throw new WorkflowRunFailedError(handle.run.id, handle.run.attempts);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
351
350
|
};
|
|
352
351
|
export {
|
|
353
352
|
task
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikirun/task",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
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.9.1",
|
|
22
|
+
"@aikirun/workflow": "0.9.1"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|