Upgrading to v4
What’s new in v4, how to upgrade, and breaking changes.
What’s new in v4?
Read our blog post for an overview of the new features.
Wait tokens
In addition to waiting for a specific duration, or waiting for a child task to complete, you can now create and wait for a token to be completed, giving you more flexibility and the ability to wait for arbitrary conditions. For example, you can send the token to a Slack channel, and only complete the token when the user has clicked an “Approve” button.
To wait for a token, you need to first create one using the wait.createToken
function:
Wait tokens are completed with a payload that you can specify when you complete the token:
You can wait for the token using the token ID:
Wait idempotency
You can now pass an idempotency key to any wait function, allowing you to skip waits if the same idempotency key is used again. This can be useful if you want to skip waits when retrying a task, for example:
idempotencyKeyTTL
allows you to specify how long the idempotency key should be valid for. The default is 30 days.
Priority
You can now specify a priority when triggering a task. This allows you to prioritize certain tasks over others, and is useful if you want to ensure that certain tasks are executed before others.
The priority value is a time duration in seconds, which offsets the timestamp of the run in the queue. If you specify a priority of 10
, the run will win over runs with a priority of 0
that were triggered within the last 10 seconds. A more concrete example:
In this case, the second run will be executed first, because it’s priority moved it 1 second ahead of the first run.
We purposefully chose to use a time duration as the priority value instead of specifying priority levels, because priority levels can cause “level starvation” where lower priority runs are never executed because there are always higher priority runs in the queue.
Global lifecycle hooks
We’ve added a new way to register global lifecycle hooks that are executed for all runs, regardless of the task. Previously, this was only possible in the trigger.config.ts
file, but now you can register them anywhere in your codebase:
init.ts
If you create a init.ts
file at the root of your trigger directory, it will be automatically loaded when a task is executed. This is useful if you want to register global lifecycle hooks, or initialize a database connection, etc.
onWait and onResume
We’ve added two new lifecycle hooks that allow you to run code when a run is paused or resumed because of a wait:
onComplete
We’ve added a new lifecycle hook that is executed when a run completes, regardless of whether it succeeded or failed:
Improved middleware and locals
Our task middleware system is now much more useful. Previously it only ran “around” the run
function, but now we’ve hoisted it to the top level and it now runs before/after all the other hooks.
We’ve also added a new locals
API that allows you to share data between middleware and hooks.
Now in your tasks run
function and all your hooks (global or task specific) you can access the database client using getDb()
:
Hidden tasks
Previously, you were required to export the task from a file in your trigger directory to be able to execute it. We’ve changed the way tasks are indexed and this requirement has been removed. So you can now just define a task without exporting it, and everything will still work:
You can use this to define “hidden” tasks that should only ever be triggered by other tasks in the same file:
Or you can create a package of reusable tasks that can be imported and used in your tasks, without having to re-export them:
useWaitToken
We’ve added a new useWaitToken
react hook that allows you to complete a wait token from a React component, using a Public Access Token.
Now you can use the useWaitToken
hook in your frontend code:
ai.tool
We’ve added a new ai.tool
function that allows you to create an AI tool from an existing schemaTask
to use with the Vercel AI SDK:
You can also pass the experimental_toToolResultContent
option to the ai.tool
function to customize the content of the tool result:
You can also now get access to the current tool execution options inside the task run function using the ai.currentToolOptions()
function:
See the AI SDK tool execution options docs for more details on the tool execution options.
ai.tool
is compatible with schemaTask
’s defined with Zod and ArkType schemas, or any schemas
that implement a .toJsonSchema()
function.
How to migrate to v4
First read the deprecations, breaking changes, and known issues sections below.
We recommend the following steps to migrate to v4:
- Install the v4 package.
- Run the
trigger dev
CLI command and test your tasks locally, fixing any breaking changes. - Deploy to the staging environment and test your tasks in staging, fixing any breaking changes. (this step is optional, but highly recommended)
- Once you’ve verified that v4 is working as expected, you should deploy your application backend with the updated v4 package.
- Once you’ve deployed your application backend, you should deploy your tasks to the production environment.
Note that between steps 4 and 5, runs triggered with the v4 package will continue using v3, and only new runs triggered after step 5 is complete will use v4.
Once v4 is activated in your environment, there will be a period of time where old runs will continue to execute using v3, while new runs will use v4. Because these engines use completely different underlying queues and concurrency models, it’s possible you may have up to double the amount of concurrently executing runs. Once the runs drain from the old run engine, the concurrency will return to normal.
Installation
To opt-in to using v4, you will need to update your dependencies to the latest version of the v4-beta
tag.
@trigger.dev/*
packages. You’ll also need to use the v4-beta
version of the trigger.dev
CLI package:
Known issues
During the beta we will be tracking issues and releasing regular fixes.
There are no known issues at the moment.
Deprecations
We’ve deprecated the following APIs:
@trigger.dev/sdk/v3
We’ve deprecated the @trigger.dev/sdk/v3
import path and moved to a new path:
handleError
and init
We’ve renamed the handleError
hook to catchError
to better reflect that it can catch and react to errors. handleError
will be removed in a future version.
init
was previously used to initialize data used in the run function:
This has now been deprecated in favor of the locals
API and middleware. See the Improved middleware and locals section for more details.
toolTask
We’ve deprecated the toolTask
function, which created both a Trigger.dev task and a tool compatible with the Vercel AI SDK:
We’ve replaced the toolTask
function with the ai.tool
function, which creates an AI tool from an existing schemaTask
. See the ai.tool section for more details.
Breaking changes
Queue changes
Previously, it was possible to specify a queue name of a queue that did not exist, along with a concurrency limit. The queue would then be created “on-demand” with the specified concurrency limit. If the queue did exist, the concurrency limit of the queue would be updated to the specified value:
This is no longer possible, and queues must now be defined ahead of time using the queue
function:
Now when you trigger a task, you can only specify the queue by name:
Or you can set the queue on the task:
Now you can trigger these tasks without having to specify the queue name in the trigger options:
Releasing concurrency on waits
We’ve changed the default behavior on how concurrency is released when a run is paused or resumed because of a wait. Previously, the concurrency would be released immediately when the run was first paused, no matter the settings on the queue.
Now we will no longer release concurrency on a queue that has a specified concurrencyLimit
when a run is paused. You can go back to the previous behavior by setting the releaseConcurrencyOnWaitpoint
option to true
on the queue:
You can also now control whether concurrency is released when performing a wait:
The new default behavior allows you to ensure that you can control the number of executing & waiting runs on a queue, and guarantee runs will resume once they are meant to be resumed.
If you do choose to release concurrency on waits, be aware that it’s possible a resume is delayed if the concurrency that was released is not available at the time the wait completes. In this case, the run will go back into the queue and will resume once concurrency becomes available.
This new behavior effects all the wait functions:
- Wait for duration (e.g.
wait.for({ seconds: 10 })
) - Wait for a child task to complete (e.g.
myTask.triggerAndWait()
,myTask.batchTriggerAndWait([...])
) - Wait for a token to complete (e.g.
wait.forToken(tokenId)
)
Lifecycle hooks
We’ve changed the function signatures of the lifecycle hooks to be more consistent and easier to use, by unifying all the parameters into a single object that can be destructured.
Previously, hooks received a payload as the first argument and then an additional object as the second argument:
Now, all the parameters are passed in a single object:
This is true for all the lifecycle hooks:
Context changes
We’ve made a few small changes to the ctx
object:
ctx.attempt.id
andctx.attempt.status
have been removed.ctx.attempt.number
is still available.ctx.task.exportName
has been removed (since we no longer require tasks to be exported to be triggered).