Skip to content

Commit 424238a

Browse files
committed
feat: add global and task-level TTL settings
Support TTL defaults at both the task definition level and globally in trigger.config.ts, with per-trigger overrides still taking precedence. Precedence order: per-trigger TTL > task-level TTL > dev default (10m) https://claude.ai/code/session_01Swj2TA2crHC29m1ynWwEUN
1 parent 436f20e commit 424238a

File tree

11 files changed

+111
-5
lines changed

11 files changed

+111
-5
lines changed

.changeset/quiet-dogs-fly.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
"@trigger.dev/core": patch
4+
"trigger.dev": patch
5+
---
6+
7+
Add support for setting TTL (time-to-live) defaults at the task level and globally in trigger.config.ts, with per-trigger overrides still taking precedence

apps/webapp/app/runEngine/services/triggerTask.server.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,30 @@ export class RunEngineTriggerTaskService {
190190
}
191191
}
192192

193-
const ttl =
194-
typeof body.options?.ttl === "number"
195-
? stringifyDuration(body.options?.ttl)
196-
: body.options?.ttl ?? (environment.type === "DEVELOPMENT" ? "10m" : undefined);
193+
// Resolve TTL with precedence: per-trigger > task-level > dev default
194+
let ttl: string | undefined;
195+
196+
if (body.options?.ttl !== undefined) {
197+
// Per-trigger TTL takes highest priority
198+
ttl =
199+
typeof body.options.ttl === "number"
200+
? stringifyDuration(body.options.ttl)
201+
: body.options.ttl;
202+
} else {
203+
// Look up task-level TTL default from BackgroundWorkerTask
204+
const taskDefaults = await this.prisma.backgroundWorkerTask.findFirst({
205+
where: {
206+
slug: taskId,
207+
projectId: environment.projectId,
208+
runtimeEnvironmentId: environment.id,
209+
},
210+
select: { ttl: true },
211+
orderBy: { createdAt: "desc" },
212+
});
213+
214+
ttl =
215+
taskDefaults?.ttl ?? (environment.type === "DEVELOPMENT" ? "10m" : undefined);
216+
}
197217

198218
// Get parent run if specified
199219
const parentRun = body.options?.parentRunId

apps/webapp/app/v3/services/createBackgroundWorker.server.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
QueueManifest,
66
TaskResource,
77
} from "@trigger.dev/core/v3";
8-
import { BackgroundWorkerId } from "@trigger.dev/core/v3/isomorphic";
8+
import { BackgroundWorkerId, stringifyDuration } from "@trigger.dev/core/v3/isomorphic";
99
import type { BackgroundWorker, TaskQueue, TaskQueueType } from "@trigger.dev/database";
1010
import cronstrue from "cronstrue";
1111
import { Prisma, PrismaClientOrTransaction } from "~/db.server";
@@ -280,6 +280,8 @@ async function createWorkerTask(
280280
triggerSource: task.triggerSource === "schedule" ? "SCHEDULED" : "STANDARD",
281281
fileId: tasksToBackgroundFiles?.get(task.id) ?? null,
282282
maxDurationInSeconds: task.maxDuration ? clampMaxDuration(task.maxDuration) : null,
283+
ttl:
284+
typeof task.ttl === "number" ? stringifyDuration(task.ttl) ?? null : task.ttl ?? null,
283285
queueId: queue.id,
284286
payloadSchema: task.payloadSchema as any,
285287
},
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "public"."BackgroundWorkerTask" ADD COLUMN IF NOT EXISTS "ttl" TEXT;

internal-packages/database/prisma/schema.prisma

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,8 @@ model BackgroundWorkerTask {
569569
570570
maxDurationInSeconds Int?
571571
572+
ttl String?
573+
572574
triggerSource TaskTriggerSource @default(STANDARD)
573575
574576
payloadSchema Json?

packages/cli-v3/src/entryPoints/dev-index-worker.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,20 @@ if (typeof config.maxDuration === "number") {
131131
});
132132
}
133133

134+
// If the config has a TTL, we need to apply it to all tasks that don't have a TTL
135+
if (config.ttl !== undefined) {
136+
tasks = tasks.map((task) => {
137+
if (task.ttl === undefined) {
138+
return {
139+
...task,
140+
ttl: config.ttl,
141+
} satisfies TaskManifest;
142+
}
143+
144+
return task;
145+
});
146+
}
147+
134148
// If the config has a machine preset, we need to apply it to all tasks that don't have a machine preset
135149
if (typeof config.machine === "string") {
136150
tasks = tasks.map((task) => {

packages/cli-v3/src/entryPoints/managed-index-worker.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,20 @@ if (typeof config.maxDuration === "number") {
131131
});
132132
}
133133

134+
// If the config has a TTL, we need to apply it to all tasks that don't have a TTL
135+
if (config.ttl !== undefined) {
136+
tasks = tasks.map((task) => {
137+
if (task.ttl === undefined) {
138+
return {
139+
...task,
140+
ttl: config.ttl,
141+
} satisfies TaskManifest;
142+
}
143+
144+
return task;
145+
});
146+
}
147+
134148
// If the config has a machine preset, we need to apply it to all tasks that don't have a machine preset
135149
if (typeof config.machine === "string") {
136150
tasks = tasks.map((task) => {

packages/core/src/v3/config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,25 @@ export type TriggerConfig = {
176176
*/
177177
maxDuration: number;
178178

179+
/**
180+
* Set a default time-to-live (TTL) for all task runs in the project. If a run is not executed within this time, it will be removed from the queue and never execute.
181+
*
182+
* This can be a string like "1h" (1 hour), "30m" (30 minutes), "1d" (1 day), or a number of seconds.
183+
*
184+
* You can override this on a per-task basis by setting the `ttl` option on the task definition, or per-trigger by setting the `ttl` option when triggering.
185+
*
186+
* @example
187+
*
188+
* ```ts
189+
* export default defineConfig({
190+
* project: "my-project",
191+
* maxDuration: 3600,
192+
* ttl: "1h",
193+
* });
194+
* ```
195+
*/
196+
ttl?: string | number;
197+
179198
/**
180199
* Enable console logging while running the dev CLI. This will print out logs from console.log, console.warn, and console.error. By default all logs are sent to the trigger.dev backend, and not logged to the console.
181200
*/

packages/core/src/v3/schemas/schemas.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ const taskMetadata = {
189189
triggerSource: z.string().optional(),
190190
schedule: ScheduleMetadata.optional(),
191191
maxDuration: z.number().optional(),
192+
ttl: z.string().or(z.number().nonnegative()).optional(),
192193
payloadSchema: z.unknown().optional(),
193194
};
194195

packages/core/src/v3/types/tasks.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,29 @@ type CommonTaskOptions<
277277
*/
278278
maxDuration?: number;
279279

280+
/**
281+
* Set a default time-to-live for runs of this task. If the run is not executed within this time, it will be removed from the queue and never execute.
282+
*
283+
* This can be a string like "1h" (1 hour), "30m" (30 minutes), "1d" (1 day), or a number of seconds.
284+
*
285+
* If omitted it will use the value in your `trigger.config.ts` file, if set.
286+
*
287+
* You can override this on a per-trigger basis by setting the `ttl` option when triggering the task.
288+
*
289+
* @example
290+
*
291+
* ```ts
292+
* export const myTask = task({
293+
* id: "my-task",
294+
* ttl: "10m",
295+
* run: async (payload) => {
296+
* //...
297+
* },
298+
* });
299+
* ```
300+
*/
301+
ttl?: string | number;
302+
280303
/** This gets called when a task is triggered. It's where you put the code you want to execute.
281304
*
282305
* @param payload - The payload that is passed to your task when it's triggered. This must be JSON serializable.

0 commit comments

Comments
 (0)