Skip to content

Commit 7c36a1a

Browse files
authored
v3: Adding SDK functions for triggering tasks in a typesafe way (#1177)
* v3: Adding SDK functions for triggering tasks in a typesafe way, without importing task file * Add type usages
1 parent 225effb commit 7c36a1a

File tree

12 files changed

+622
-138
lines changed

12 files changed

+622
-138
lines changed

.changeset/sweet-ducks-remember.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
---
4+
5+
v3: Adding SDK functions for triggering tasks in a typesafe way, without importing task file

apps/webapp/app/v3/tracer.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ function getTracer() {
117117
const samplingRate = 1.0 / Math.max(parseInt(env.INTERNAL_OTEL_TRACE_SAMPLING_RATE, 10), 1);
118118

119119
const provider = new NodeTracerProvider({
120-
forceFlushTimeoutMillis: 500,
120+
forceFlushTimeoutMillis: 5000,
121121
resource: new Resource({
122122
[SEMRESATTRS_SERVICE_NAME]: env.SERVICE_NAME,
123123
}),

docs/v3/triggering.mdx

Lines changed: 233 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,37 @@ title: "Triggering"
33
description: "Tasks need to be triggered to run."
44
---
55

6-
There are currently four ways you can trigger any task from your own code:
6+
There are currently six ways you can trigger tasks:
77

88
| Function | Where does this work? | What it does |
99
| -------------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
1010
| `yourTask.trigger()` | Anywhere | Triggers a task and gets a handle you can use to monitor and manage the run. It does not wait for the result. |
1111
| `yourTask.batchTrigger()` | Anywhere | Triggers a task multiple times and gets a handle you can use to monitor and manage the runs. It does not wait for the results. |
1212
| `yourTask.triggerAndWait()` | Inside a task | Triggers a task and then waits until it's complete. You get the result data to continue with. |
1313
| `yourTask.batchTriggerAndWait()` | Inside a task | Triggers a task multiple times in parallel and then waits until they're all complete. You get the resulting data to continue with. |
14+
| `tasks.trigger()` | Outside of a task | Triggers a task and gets a handle you can use to fetch and manage the run. |
15+
| `tasks.batchTrigger()` | Outside of a task | Triggers a task multiple times and gets a handle you can use to fetch and manage the runs. |
1416

1517
Additionally, [scheduled tasks](/v3/tasks-scheduled) get automatically triggered on their schedule and [webhooks](/v3/tasks-webhooks) when receiving a webhook.
1618

1719
## Scheduled tasks
1820

1921
You should attach one or more schedules to your `schedules.task()` to trigger it on a recurring schedule. [Read the scheduled tasks docs](/v3/tasks-scheduled).
2022

21-
## From outside of a task
23+
## Authentication
2224

23-
You can trigger any task from your backend code, using either `trigger()` or `batchTrigger()`.
24-
25-
<Note>
26-
Do not trigger tasks directly from your frontend. If you do, you will leak your private
27-
Trigger.dev API key to the world.
28-
</Note>
29-
30-
You can use Next.js Server Actions but [you need to be careful with bundling](#next-js-server-actions).
25+
When you trigger a task from your backend code, you need to set the `TRIGGER_SECRET_KEY` environment variable. You can find the value on the API keys page in the Trigger.dev dashboard. [More info on API keys](/v3/apikeys).
3126

32-
### Authentication
27+
## Task instance methods
3328

34-
When you trigger a task from your backend code, you need to set the `TRIGGER_SECRET_KEY` environment variable. You can find the value on the API keys page in the Trigger.dev dashboard. [More info on API keys](/v3/apikeys).
29+
Task instance methods are available on the `Task` object you receive when you define a task. They can be called from your backend code or from inside another task.
3530

36-
### trigger()
31+
### Task.trigger()
3732

3833
Triggers a single run of a task with the payload you pass in, and any options you specify. It does NOT wait for the result, you cannot do that from outside a task.
3934

35+
If called from within a task, you can use the `AndWait` version to pause execution until the triggered run is complete.
36+
4037
<CodeGroup>
4138

4239
```ts Next.js API route
@@ -74,11 +71,24 @@ export async function action({ request, params }: ActionFunctionArgs) {
7471
}
7572
```
7673

74+
```ts /trigger/my-task.ts
75+
import { myOtherTask } from "~/trigger/my-other-task";
76+
77+
export const myTask = task({
78+
id: "my-task",
79+
run: async (payload: string) => {
80+
const handle = await myOtherTask.trigger("some data");
81+
82+
//...do other stuff
83+
},
84+
});
85+
```
86+
7787
</CodeGroup>
7888

79-
### batchTrigger()
89+
### Task.batchTrigger()
8090

81-
Triggers multiples runs of a task with the payloads you pass in, and any options you specify. It does NOT wait for the results, you cannot do that from outside a task.
91+
Triggers multiples runs of a task with the payloads you pass in, and any options you specify.
8292

8393
<CodeGroup>
8494

@@ -121,33 +131,6 @@ export async function action({ request, params }: ActionFunctionArgs) {
121131
}
122132
```
123133

124-
</CodeGroup>
125-
126-
## From inside a task
127-
128-
You can trigger tasks from other tasks using `trigger()` or `batchTrigger()`. You can also trigger and wait for the result of triggered tasks using `triggerAndWait()` and `batchTriggerAndWait()`. This is a powerful way to build complex tasks.
129-
130-
### trigger()
131-
132-
This works the same as from outside a task. You call it and you get a handle back, but it does not wait for the result.
133-
134-
```ts /trigger/my-task.ts
135-
import { myOtherTask } from "~/trigger/my-other-task";
136-
137-
export const myTask = task({
138-
id: "my-task",
139-
run: async (payload: string) => {
140-
const handle = await myOtherTask.trigger("some data");
141-
142-
//...do other stuff
143-
},
144-
});
145-
```
146-
147-
### batchTrigger()
148-
149-
This works the same as from outside a task. You call it and you get a handle back, but it does not wait for the results.
150-
151134
```ts /trigger/my-task.ts
152135
import { myOtherTask } from "~/trigger/my-other-task";
153136

@@ -161,7 +144,9 @@ export const myTask = task({
161144
});
162145
```
163146

164-
### triggerAndWait()
147+
</CodeGroup>
148+
149+
### Task.triggerAndWait()
165150

166151
This is where it gets interesting. You can trigger a task and then wait for the result. This is useful when you need to call a different task and then use the result to continue with your task.
167152

@@ -219,7 +204,7 @@ export const parentTask = task({
219204
});
220205
```
221206

222-
### batchTriggerAndWait()
207+
### Task.batchTriggerAndWait()
223208

224209
You can batch trigger a task and wait for all the results. This is useful for the fan-out pattern, where you need to call a task multiple times and then wait for all the results to continue with your task.
225210

@@ -284,11 +269,213 @@ export const batchParentTask = task({
284269
});
285270
```
286271

272+
## SDK functions
273+
274+
You can trigger any task from your backend code using the `tasks.trigger()` or `tasks.batchTrigger()` SDK functions.
275+
276+
<Note>
277+
Do not trigger tasks directly from your frontend. If you do, you will leak your private
278+
Trigger.dev API key.
279+
</Note>
280+
281+
You can use Next.js Server Actions but [you need to be careful with bundling](#next-js-server-actions).
282+
283+
### tasks.trigger()
284+
285+
Triggers a single run of a task with the payload you pass in, and any options you specify, without needing to import the task.
286+
287+
<Note>
288+
Why would you use this instead of the `Task.trigger()` instance method? Tasks can import
289+
dependencies/modules that you might not want included in your application code or cause problems
290+
with building.
291+
</Note>
292+
293+
<CodeGroup>
294+
295+
```ts Next.js API route
296+
import { tasks } from "@trigger.dev/sdk/v3";
297+
import type { emailSequence } from "~/trigger/emails";
298+
// 👆 **type-only** import
299+
300+
//app/email/route.ts
301+
export async function POST(request: Request) {
302+
//get the JSON from the request
303+
const data = await request.json();
304+
305+
// Pass the task type to `trigger()` as a generic argument, giving you full type checking
306+
const handle = await tasks.trigger<typeof emailSequence>("email-sequence", {
307+
to: data.email,
308+
name: data.name,
309+
});
310+
311+
//return a success response with the handle
312+
return Response.json(handle);
313+
}
314+
```
315+
316+
```ts Remix
317+
import { tasks } from "@trigger.dev/sdk/v3";
318+
319+
export async function action({ request, params }: ActionFunctionArgs) {
320+
if (request.method.toUpperCase() !== "POST") {
321+
return json("Method Not Allowed", { status: 405 });
322+
}
323+
324+
//get the JSON from the request
325+
const data = await request.json();
326+
327+
// The generic argument is optional, but recommended for full type checking
328+
const handle = await tasks.trigger("email-sequence", {
329+
to: data.email,
330+
name: data.name,
331+
});
332+
333+
//return a success response with the handle
334+
return json(handle);
335+
}
336+
```
337+
338+
</CodeGroup>
339+
340+
<Tip>
341+
By importing the task with the type modifier, the import of `"~/trigger/emails"` is a type-only
342+
import. This means that the task code is not included in your application at build time.
343+
</Tip>
344+
345+
### tasks.batchTrigger()
346+
347+
Triggers multiples runs of a task with the payloads you pass in, and any options you specify, without needing to import the task.
348+
349+
<CodeGroup>
350+
351+
```ts Next.js API route
352+
import { tasks } from "@trigger.dev/sdk/v3";
353+
import type { emailSequence } from "~/trigger/emails";
354+
// 👆 **type-only** import
355+
356+
//app/email/route.ts
357+
export async function POST(request: Request) {
358+
//get the JSON from the request
359+
const data = await request.json();
360+
361+
// Pass the task type to `batchTrigger()` as a generic argument, giving you full type checking
362+
const batchHandle = await tasks.batchTrigger<typeof emailSequence>(
363+
"email-sequence",
364+
data.users.map((u) => ({ payload: { to: u.email, name: u.name } }))
365+
);
366+
367+
//return a success response with the handle
368+
return Response.json(batchHandle);
369+
}
370+
```
371+
372+
</CodeGroup>
373+
374+
### tasks.triggerAndPoll()
375+
376+
Triggers a single run of a task with the payload you pass in, and any options you specify, and then polls the run until it's complete.
377+
378+
<CodeGroup>
379+
380+
```ts Next.js API route
381+
import { tasks } from "@trigger.dev/sdk/v3";
382+
import type { emailSequence } from "~/trigger/emails";
383+
384+
//app/email/route.ts
385+
export async function POST(request: Request) {
386+
//get the JSON from the request
387+
const data = await request.json();
388+
389+
// Pass the task type to `triggerAndPoll()` as a generic argument, giving you full type checking
390+
const result = await tasks.triggerAndPoll<typeof emailSequence>(
391+
"email-sequence",
392+
{
393+
to: data.email,
394+
name: data.name,
395+
},
396+
{ pollIntervalMs: 5000 }
397+
);
398+
399+
//return a success response with the result
400+
return Response.json(result);
401+
}
402+
```
403+
404+
</CodeGroup>
405+
406+
<Note>
407+
The above code is just a demonstration of the API and is not recommended to use in an API route
408+
this way as it will block the request until the task is complete.
409+
</Note>
410+
411+
### runs.retrieve()
412+
413+
You can retrieve a run by its handle using the `runs.retrieve()` function.
414+
415+
<CodeGroup>
416+
417+
```ts Next.js API route
418+
import { tasks, runs } from "@trigger.dev/sdk/v3";
419+
import type { emailSequence } from "~/trigger/emails";
420+
// 👆 **type-only** import
421+
422+
//app/email/route.ts
423+
export async function POST(request: Request) {
424+
//get the JSON from the request
425+
const data = await request.json();
426+
427+
// Pass the task type to `trigger()` as a generic argument, giving you full type checking
428+
const handle = await tasks.trigger<typeof emailSequence>("email-sequence", {
429+
to: data.email,
430+
name: data.name,
431+
});
432+
433+
const run = await runs.retrieve(handle);
434+
435+
// run.output will be correctly typed as the return value of the task
436+
return Response.json(run.output);
437+
}
438+
```
439+
440+
</CodeGroup>
441+
442+
### runs.poll()
443+
444+
You can poll a run by its handle using the `runs.poll()` function.
445+
446+
<CodeGroup>
447+
448+
```ts Next.js API route
449+
import { tasks, runs } from "@trigger.dev/sdk/v3";
450+
import type { emailSequence } from "~/trigger/emails";
451+
// 👆 **type-only** import
452+
453+
//app/email/route.ts
454+
export async function POST(request: Request) {
455+
//get the JSON from the request
456+
const data = await request.json();
457+
458+
// Pass the task type to `trigger()` as a generic argument, giving you full type checking
459+
const handle = await tasks.trigger<typeof emailSequence>("email-sequence", {
460+
to: data.email,
461+
name: data.name,
462+
});
463+
464+
// Poll the run until it's complete
465+
const run = await runs.poll(handle, { pollIntervalMs: 5000 });
466+
467+
// run.output will be correctly typed as the return value of the task
468+
return Response.json(run.output);
469+
}
470+
```
471+
472+
</CodeGroup>
473+
287474
## Next.js Server Actions
288475

289476
Server Actions allow you to call your backend code without creating API routes. This is very useful for triggering tasks but you need to be careful you don't accidentally bundle the Trigger.dev SDK into your frontend code.
290477

291-
If you see an error like this then you've bundled `@trigger.dev/sdk` into your frontend code:
478+
If you see an error like this then you've bundled `@trigger.dev/sdk/v3` into your frontend code:
292479

293480
```bash
294481
Module build failed: UnhandledSchemeError: Reading from "node:crypto" is not handled by plugins (Unhandled scheme).
@@ -297,7 +484,7 @@ Webpack supports "data:" and "file:" URIs by default.
297484
You may need an additional plugin to handle "node:" URIs.
298485
```
299486

300-
When you use server actions that use `@trigger.dev/sdk`:
487+
When you use server actions that use `@trigger.dev/sdk/v3`:
301488

302489
- The file can't have any React components in it.
303490
- The file should have `"use server"` on the first line.

0 commit comments

Comments
 (0)