Skip to content

Commit a94a11f

Browse files
authored
feat(sdk): replace onStart lifecycle hook with onStartAttempt (#2515)
* fix(sdk): prevent uncaught errors thrown onSuccess, onComplete, and onFailure hooks to fail attempts & in some cases runs * Add onStartAttempt hook and deprecate onSuccess * Add onStartAttempt hook and deprecate onStart hook * Fix onStartAttempt overload types * Update lifecycle functions diagram
1 parent f116e93 commit a94a11f

File tree

15 files changed

+650
-114
lines changed

15 files changed

+650
-114
lines changed

.changeset/tiny-carrots-rest.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
"@trigger.dev/sdk": minor
3+
---
4+
5+
Prevent uncaught errors in the `onSuccess`, `onComplete`, and `onFailure` lifecycle hooks from failing attempts/runs.
6+
7+
Deprecated the `onStart` lifecycle hook (which only fires before the `run` function on the first attempt). Replaced with `onStartAttempt` that fires before the run function on every attempt:
8+
9+
```ts
10+
export const taskWithOnStartAttempt = task({
11+
id: "task-with-on-start-attempt",
12+
onStartAttempt: async ({ payload, ctx }) => {
13+
//...
14+
},
15+
run: async (payload: any, { ctx }) => {
16+
//...
17+
},
18+
});
19+
20+
// Default a global lifecycle hook using tasks
21+
tasks.onStartAttempt(({ ctx, payload, task }) => {
22+
console.log(
23+
`Run ${ctx.run.id} started on task ${task} attempt ${ctx.run.attempt.number}`,
24+
ctx.run
25+
);
26+
});
27+
```
28+
29+
If you want to execute code before just the first attempt, you can use the `onStartAttempt` function and check `ctx.run.attempt.number === 1`:
30+
31+
```ts /trigger/on-start-attempt.ts
32+
export const taskWithOnStartAttempt = task({
33+
id: "task-with-on-start-attempt",
34+
onStartAttempt: async ({ payload, ctx }) => {
35+
if (ctx.run.attempt.number === 1) {
36+
console.log("Run started on attempt 1", ctx.run);
37+
}
38+
},
39+
});
40+
```
41+

apps/webapp/app/components/runs/v3/RunIcon.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export function RunIcon({ name, className, spanName }: TaskIconProps) {
9898
return <RunFunctionIcon className={cn(className, "text-text-dimmed")} />;
9999
case "task-hook-init":
100100
case "task-hook-onStart":
101+
case "task-hook-onStartAttempt":
101102
case "task-hook-onSuccess":
102103
case "task-hook-onWait":
103104
case "task-hook-onResume":
-12.2 KB
Loading

docs/tasks/overview.mdx

Lines changed: 174 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -174,63 +174,14 @@ tasks.onStart(({ ctx, payload, task }) => {
174174

175175
![Lifecycle functions](/images/lifecycle-functions.png)
176176

177-
### `init` function
178-
179-
This function is called before a run attempt:
180-
181-
```ts /trigger/init.ts
182-
export const taskWithInit = task({
183-
id: "task-with-init",
184-
init: async ({ payload, ctx }) => {
185-
//...
186-
},
187-
run: async (payload: any, { ctx }) => {
188-
//...
189-
},
190-
});
191-
```
192-
193-
You can also return data from the `init` function that will be available in the params of the `run`, `cleanup`, `onSuccess`, and `onFailure` functions.
194-
195-
```ts /trigger/init-return.ts
196-
export const taskWithInitReturn = task({
197-
id: "task-with-init-return",
198-
init: async ({ payload, ctx }) => {
199-
return { someData: "someValue" };
200-
},
201-
run: async (payload: any, { ctx, init }) => {
202-
console.log(init.someData); // "someValue"
203-
},
204-
});
205-
```
206-
207-
<Info>Errors thrown in the `init` function are ignored.</Info>
208-
209-
### `cleanup` function
210-
211-
This function is called after the `run` function is executed, regardless of whether the run was successful or not. It's useful for cleaning up resources, logging, or other side effects.
212-
213-
```ts /trigger/cleanup.ts
214-
export const taskWithCleanup = task({
215-
id: "task-with-cleanup",
216-
cleanup: async ({ payload, ctx }) => {
217-
//...
218-
},
219-
run: async (payload: any, { ctx }) => {
220-
//...
221-
},
222-
});
223-
```
224-
225-
<Info>Errors thrown in the `cleanup` function will fail the attempt.</Info>
226-
227177
### `middleware` and `locals` functions
228178

229179
Our task middleware system runs at the top level, executing before and after all lifecycle hooks. This allows you to wrap the entire task execution lifecycle with custom logic.
230180

231181
<Info>
232182
An error thrown in `middleware` is just like an uncaught error in the run function: it will
233-
propagate through to `catchError()` function and then will fail the attempt (causing a retry).
183+
propagate through to `catchError()` function and then will fail the attempt (either causing a
184+
retry or failing the run).
234185
</Info>
235186

236187
The `locals` API allows you to share data between middleware and hooks.
@@ -296,14 +247,16 @@ export const myTask = task({
296247
});
297248
```
298249

299-
### `onStart` function
250+
### `onStartAttempt` function
251+
252+
<Info>The `onStartAttempt` function was introduced in v4.1.0</Info>
300253

301-
When a task run starts, the `onStart` function is called. It's useful for sending notifications, logging, and other side effects. This function will only be called one per run (not per retry). If you want to run code before each retry, use the `init` function.
254+
Before a task run attempt starts, the `onStartAttempt` function is called. It's useful for sending notifications, logging, and other side effects.
302255

303256
```ts /trigger/on-start.ts
304-
export const taskWithOnStart = task({
305-
id: "task-with-on-start",
306-
onStart: async ({ payload, ctx }) => {
257+
export const taskWithOnStartAttempt = task({
258+
id: "task-with-on-start-attempt",
259+
onStartAttempt: async ({ payload, ctx }) => {
307260
//...
308261
},
309262
run: async (payload: any, { ctx }) => {
@@ -312,20 +265,33 @@ export const taskWithOnStart = task({
312265
});
313266
```
314267

315-
You can also define an `onStart` function in your `trigger.config.ts` file to get notified when any task starts.
268+
You can also define a global `onStartAttempt` function using `tasks.onStartAttempt()`.
316269

317-
```ts trigger.config.ts
318-
import { defineConfig } from "@trigger.dev/sdk";
270+
```ts init.ts
271+
import { tasks } from "@trigger.dev/sdk";
319272

320-
export default defineConfig({
321-
project: "proj_1234",
322-
onStart: async ({ payload, ctx }) => {
323-
console.log("Task started", ctx.task.id);
324-
},
273+
tasks.onStartAttempt(({ ctx, payload, task }) => {
274+
console.log(
275+
`Run ${ctx.run.id} started on task ${task} attempt ${ctx.run.attempt.number}`,
276+
ctx.run
277+
);
325278
});
326279
```
327280

328-
<Info>Errors thrown in the `onStart` function are ignored.</Info>
281+
<Info>Errors thrown in the `onStartAttempt` function will cause the attempt to fail.</Info>
282+
283+
If you want to execute code before just the first attempt, you can use the `onStartAttempt` function and check `ctx.run.attempt.number === 1`:
284+
285+
```ts /trigger/on-start-attempt.ts
286+
export const taskWithOnStartAttempt = task({
287+
id: "task-with-on-start-attempt",
288+
onStartAttempt: async ({ payload, ctx }) => {
289+
if (ctx.run.attempt.number === 1) {
290+
console.log("Run started on attempt 1", ctx.run);
291+
}
292+
},
293+
});
294+
```
329295

330296
### `onWait` and `onResume` functions
331297

@@ -350,6 +316,20 @@ export const myTask = task({
350316
});
351317
```
352318

319+
You can also define global `onWait` and `onResume` functions using `tasks.onWait()` and `tasks.onResume()`:
320+
321+
```ts init.ts
322+
import { tasks } from "@trigger.dev/sdk";
323+
324+
tasks.onWait(({ ctx, payload, wait, task }) => {
325+
console.log("Run paused", ctx.run, wait);
326+
});
327+
328+
tasks.onResume(({ ctx, payload, wait, task }) => {
329+
console.log("Run resumed", ctx.run, wait);
330+
});
331+
```
332+
353333
### `onSuccess` function
354334

355335
When a task run succeeds, the `onSuccess` function is called. It's useful for sending notifications, logging, syncing state to your database, or other side effects.
@@ -366,20 +346,20 @@ export const taskWithOnSuccess = task({
366346
});
367347
```
368348

369-
You can also define an `onSuccess` function in your `trigger.config.ts` file to get notified when any task succeeds.
349+
You can also define a global `onSuccess` function using `tasks.onSuccess()`.
370350

371-
```ts trigger.config.ts
372-
import { defineConfig } from "@trigger.dev/sdk";
351+
```ts init.ts
352+
import { tasks } from "@trigger.dev/sdk";
373353

374-
export default defineConfig({
375-
project: "proj_1234",
376-
onSuccess: async ({ payload, output, ctx }) => {
377-
console.log("Task succeeded", ctx.task.id);
378-
},
354+
tasks.onSuccess(({ ctx, payload, output }) => {
355+
console.log("Task succeeded", ctx.task.id);
379356
});
380357
```
381358

382-
<Info>Errors thrown in the `onSuccess` function are ignored.</Info>
359+
<Info>
360+
Errors thrown in the `onSuccess` function will be ignored, but you will still be able to see them
361+
in the dashboard.
362+
</Info>
383363

384364
### `onComplete` function
385365

@@ -397,6 +377,21 @@ export const taskWithOnComplete = task({
397377
});
398378
```
399379
380+
You can also define a global `onComplete` function using `tasks.onComplete()`.
381+
382+
```ts init.ts
383+
import { tasks } from "@trigger.dev/sdk";
384+
385+
tasks.onComplete(({ ctx, payload, output }) => {
386+
console.log("Task completed", ctx.task.id);
387+
});
388+
```
389+
390+
<Info>
391+
Errors thrown in the `onComplete` function will be ignored, but you will still be able to see them
392+
in the dashboard.
393+
</Info>
394+
400395
### `onFailure` function
401396
402397
When a task run fails, the `onFailure` function is called. It's useful for sending notifications, logging, or other side effects. It will only be executed once the task run has exhausted all its retries.
@@ -413,20 +408,20 @@ export const taskWithOnFailure = task({
413408
});
414409
```
415410
416-
You can also define an `onFailure` function in your `trigger.config.ts` file to get notified when any task fails.
411+
You can also define a global `onFailure` function using `tasks.onFailure()`.
417412
418-
```ts trigger.config.ts
419-
import { defineConfig } from "@trigger.dev/sdk";
413+
```ts init.ts
414+
import { tasks } from "@trigger.dev/sdk";
420415

421-
export default defineConfig({
422-
project: "proj_1234",
423-
onFailure: async ({ payload, error, ctx }) => {
424-
console.log("Task failed", ctx.task.id);
425-
},
416+
tasks.onFailure(({ ctx, payload, error }) => {
417+
console.log("Task failed", ctx.task.id);
426418
});
427419
```
428420
429-
<Info>Errors thrown in the `onFailure` function are ignored.</Info>
421+
<Info>
422+
Errors thrown in the `onFailure` function will be ignored, but you will still be able to see them
423+
in the dashboard.
424+
</Info>
430425
431426
<Note>
432427
`onFailure` doesn’t fire for some of the run statuses like `Crashed`, `System failures`, and
@@ -441,7 +436,7 @@ Read more about `catchError` in our [Errors and Retrying guide](/errors-retrying
441436
442437
<Info>Uncaught errors will throw a special internal error of the type `HANDLE_ERROR_ERROR`.</Info>
443438
444-
### onCancel
439+
### `onCancel` function
445440
446441
You can define an `onCancel` hook that is called when a run is cancelled. This is useful if you want to clean up any resources that were allocated for the run.
447442
@@ -540,6 +535,101 @@ export const cancelExampleTask = task({
540535
point the process will be killed.
541536
</Note>
542537
538+
### `onStart` function (deprecated)
539+
540+
<Info>The `onStart` function was deprecated in v4.1.0. Use `onStartAttempt` instead.</Info>
541+
542+
When a task run starts, the `onStart` function is called. It's useful for sending notifications, logging, and other side effects.
543+
544+
<Warning>
545+
This function will only be called once per run (not per attempt). If you want to run code before
546+
each attempt, use a middleware function or the `onStartAttempt` function.
547+
</Warning>
548+
549+
```ts /trigger/on-start.ts
550+
export const taskWithOnStart = task({
551+
id: "task-with-on-start",
552+
onStart: async ({ payload, ctx }) => {
553+
//...
554+
},
555+
run: async (payload: any, { ctx }) => {
556+
//...
557+
},
558+
});
559+
```
560+
561+
You can also define a global `onStart` function using `tasks.onStart()`.
562+
563+
```ts init.ts
564+
import { tasks } from "@trigger.dev/sdk";
565+
566+
tasks.onStart(({ ctx, payload, task }) => {
567+
console.log(`Run ${ctx.run.id} started on task ${task}`, ctx.run);
568+
});
569+
```
570+
571+
<Info>Errors thrown in the `onStart` function will cause the attempt to fail.</Info>
572+
573+
### `init` function (deprecated)
574+
575+
<Warning>
576+
The `init` hook is deprecated and will be removed in the future. Use
577+
[middleware](/tasks/overview#middleware-and-locals-functions) instead.
578+
</Warning>
579+
580+
This function is called before a run attempt:
581+
582+
```ts /trigger/init.ts
583+
export const taskWithInit = task({
584+
id: "task-with-init",
585+
init: async ({ payload, ctx }) => {
586+
//...
587+
},
588+
run: async (payload: any, { ctx }) => {
589+
//...
590+
},
591+
});
592+
```
593+
594+
You can also return data from the `init` function that will be available in the params of the `run`, `cleanup`, `onSuccess`, and `onFailure` functions.
595+
596+
```ts /trigger/init-return.ts
597+
export const taskWithInitReturn = task({
598+
id: "task-with-init-return",
599+
init: async ({ payload, ctx }) => {
600+
return { someData: "someValue" };
601+
},
602+
run: async (payload: any, { ctx, init }) => {
603+
console.log(init.someData); // "someValue"
604+
},
605+
});
606+
```
607+
608+
<Info>Errors thrown in the `init` function will cause the attempt to fail.</Info>
609+
610+
### `cleanup` function (deprecated)
611+
612+
<Warning>
613+
The `cleanup` hook is deprecated and will be removed in the future. Use
614+
[middleware](/tasks/overview#middleware-and-locals-functions) instead.
615+
</Warning>
616+
617+
This function is called after the `run` function is executed, regardless of whether the run was successful or not. It's useful for cleaning up resources, logging, or other side effects.
618+
619+
```ts /trigger/cleanup.ts
620+
export const taskWithCleanup = task({
621+
id: "task-with-cleanup",
622+
cleanup: async ({ payload, ctx }) => {
623+
//...
624+
},
625+
run: async (payload: any, { ctx }) => {
626+
//...
627+
},
628+
});
629+
```
630+
631+
<Info>Errors thrown in the `cleanup` function will cause the attempt to fail.</Info>
632+
543633
## Next steps
544634
545635
<CardGroup>

0 commit comments

Comments
 (0)