v3: Delayed runs and run ttl expiration (#1193)

* v3: Trigger delayed runs and reschedule them

* Create a `@trigger.dev/core/v3/schemas` export

* fixed the `@trigger.dev/core/v3/schemas` export

* Small docs tweak

* Add ttl option when triggering tasks, expire runs after ttl

Dev runs expire in 10m by default
This commit is contained in:
Eric Allam
2024-07-01 12:01:35 +01:00
committed by GitHub
parent 76a5c6204f
commit 0e77e7ef7d
34 changed files with 930 additions and 90 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"@trigger.dev/sdk": patch
"@trigger.dev/core": patch
---
v3: Trigger delayed runs and reschedule them
@@ -3,10 +3,12 @@ import {
BoltSlashIcon,
BugAntIcon,
CheckCircleIcon,
ClockIcon,
FireIcon,
NoSymbolIcon,
PauseCircleIcon,
RectangleStackIcon,
TrashIcon,
XCircleIcon,
} from "@heroicons/react/20/solid";
import { TaskRunStatus } from "@trigger.dev/database";
@@ -16,6 +18,7 @@ import { Spinner } from "~/components/primitives/Spinner";
import { cn } from "~/utils/cn";
export const allTaskRunStatuses = [
"DELAYED",
"WAITING_FOR_DEPLOY",
"PENDING",
"EXECUTING",
@@ -28,10 +31,12 @@ export const allTaskRunStatuses = [
"PAUSED",
"INTERRUPTED",
"SYSTEM_FAILURE",
"EXPIRED",
] as const satisfies Readonly<Array<TaskRunStatus>>;
export const filterableTaskRunStatuses = [
"WAITING_FOR_DEPLOY",
"DELAYED",
"PENDING",
"EXECUTING",
"RETRYING_AFTER_FAILURE",
@@ -42,9 +47,11 @@ export const filterableTaskRunStatuses = [
"CRASHED",
"INTERRUPTED",
"SYSTEM_FAILURE",
"EXPIRED",
] as const satisfies Readonly<Array<TaskRunStatus>>;
const taskRunStatusDescriptions: Record<TaskRunStatus, string> = {
DELAYED: "Task has been delayed and is waiting to be executed",
PENDING: "Task is waiting to be executed",
WAITING_FOR_DEPLOY: "Task needs to be deployed first to start executing",
EXECUTING: "Task is currently being executed",
@@ -57,9 +64,10 @@ const taskRunStatusDescriptions: Record<TaskRunStatus, string> = {
SYSTEM_FAILURE: "Task has failed due to a system failure",
PAUSED: "Task has been paused by the user",
CRASHED: "Task has crashed and won't be retried",
EXPIRED: "Task has surpassed its ttl and won't be executed",
};
export const QUEUED_STATUSES: TaskRunStatus[] = ["PENDING", "WAITING_FOR_DEPLOY"];
export const QUEUED_STATUSES: TaskRunStatus[] = ["PENDING", "WAITING_FOR_DEPLOY", "DELAYED"];
export const RUNNING_STATUSES: TaskRunStatus[] = [
"EXECUTING",
@@ -74,6 +82,7 @@ export const FINISHED_STATUSES: TaskRunStatus[] = [
"INTERRUPTED",
"SYSTEM_FAILURE",
"CRASHED",
"EXPIRED",
];
export function descriptionForTaskRunStatus(status: TaskRunStatus): string {
@@ -109,6 +118,8 @@ export function TaskRunStatusIcon({
className: string;
}) {
switch (status) {
case "DELAYED":
return <ClockIcon className={cn(runStatusClassNameColor(status), className)} />;
case "PENDING":
return <RectangleStackIcon className={cn(runStatusClassNameColor(status), className)} />;
case "WAITING_FOR_DEPLOY":
@@ -133,6 +144,8 @@ export function TaskRunStatusIcon({
return <BugAntIcon className={cn(runStatusClassNameColor(status), className)} />;
case "CRASHED":
return <FireIcon className={cn(runStatusClassNameColor(status), className)} />;
case "EXPIRED":
return <TrashIcon className={cn(runStatusClassNameColor(status), className)} />;
default: {
assertNever(status);
@@ -143,6 +156,7 @@ export function TaskRunStatusIcon({
export function runStatusClassNameColor(status: TaskRunStatus): string {
switch (status) {
case "PENDING":
case "DELAYED":
return "text-charcoal-500";
case "WAITING_FOR_DEPLOY":
return "text-amber-500";
@@ -154,6 +168,7 @@ export function runStatusClassNameColor(status: TaskRunStatus): string {
case "PAUSED":
return "text-amber-300";
case "CANCELED":
case "EXPIRED":
return "text-charcoal-500";
case "INTERRUPTED":
return "text-error";
@@ -173,6 +188,8 @@ export function runStatusClassNameColor(status: TaskRunStatus): string {
export function runStatusTitle(status: TaskRunStatus): string {
switch (status) {
case "DELAYED":
return "Delayed";
case "PENDING":
return "Queued";
case "WAITING_FOR_DEPLOY":
@@ -197,6 +214,8 @@ export function runStatusTitle(status: TaskRunStatus): string {
return "System failure";
case "CRASHED":
return "Crashed";
case "EXPIRED":
return "Expired";
default: {
assertNever(status);
}
@@ -118,6 +118,8 @@ export function TaskRunsTable({
<TableHeaderCell>Duration</TableHeaderCell>
<TableHeaderCell>Test</TableHeaderCell>
<TableHeaderCell>Created at</TableHeaderCell>
<TableHeaderCell>Delayed until</TableHeaderCell>
<TableHeaderCell>TTL</TableHeaderCell>
<TableHeaderCell>
<span className="sr-only">Go to page</span>
</TableHeaderCell>
@@ -187,6 +189,10 @@ export function TaskRunsTable({
<TableCell to={path}>
{run.createdAt ? <DateTime date={run.createdAt} /> : ""}
</TableCell>
<TableCell to={path}>
{run.delayUntil ? <DateTime date={run.delayUntil} /> : ""}
</TableCell>
<TableCell to={path}>{run.ttl ?? ""}</TableCell>
<RunActionsCell run={run} path={path} />
</TableRow>
);
+2
View File
@@ -40,6 +40,8 @@ export const TaskRunStatus = {
COMPLETED_WITH_ERRORS: "COMPLETED_WITH_ERRORS",
SYSTEM_FAILURE: "SYSTEM_FAILURE",
CRASHED: "CRASHED",
DELAYED: "DELAYED",
EXPIRED: "EXPIRED",
} as const satisfies Record<TaskRunStatusType, TaskRunStatusType>;
export const JobRunStatus = {
+2
View File
@@ -118,6 +118,7 @@ export function batchTaskRunItemStatusForRunStatus(
case TaskRunStatus.COMPLETED_WITH_ERRORS:
case TaskRunStatus.SYSTEM_FAILURE:
case TaskRunStatus.CRASHED:
case TaskRunStatus.EXPIRED:
return BatchTaskRunItemStatus.FAILED;
case TaskRunStatus.PENDING:
case TaskRunStatus.WAITING_FOR_DEPLOY:
@@ -125,6 +126,7 @@ export function batchTaskRunItemStatusForRunStatus(
case TaskRunStatus.RETRYING_AFTER_FAILURE:
case TaskRunStatus.EXECUTING:
case TaskRunStatus.PAUSED:
case TaskRunStatus.DELAYED:
return BatchTaskRunItemStatus.PENDING;
default:
assertNever(status);
@@ -111,11 +111,14 @@ export class ApiRetrieveRunPresenter extends BasePresenter {
finishedAt: ApiRetrieveRunPresenter.isStatusFinished(apiStatus)
? taskRun.updatedAt
: undefined,
delayedUntil: taskRun.delayUntil ?? undefined,
payload: $payload,
payloadPresignedUrl: $payloadPresignedUrl,
output: $output,
outputPresignedUrl: $outputPresignedUrl,
isTest: taskRun.isTest,
ttl: taskRun.ttl ?? undefined,
expiredAt: taskRun.expiredAt ?? undefined,
schedule: taskRun.schedule
? {
id: taskRun.schedule.friendlyId,
@@ -171,6 +174,9 @@ export class ApiRetrieveRunPresenter extends BasePresenter {
static apiStatusFromRunStatus(status: TaskRunStatus): RunStatus {
switch (status) {
case "DELAYED": {
return "DELAYED";
}
case "WAITING_FOR_DEPLOY": {
return "WAITING_FOR_DEPLOY";
}
@@ -205,6 +211,9 @@ export class ApiRetrieveRunPresenter extends BasePresenter {
case "COMPLETED_WITH_ERRORS": {
return "FAILED";
}
case "EXPIRED": {
return "EXPIRED";
}
default: {
assertNever(status);
}
@@ -212,7 +221,7 @@ export class ApiRetrieveRunPresenter extends BasePresenter {
}
static apiBooleanHelpersFromRunStatus(status: RunStatus) {
const isQueued = status === "QUEUED" || status === "WAITING_FOR_DEPLOY";
const isQueued = status === "QUEUED" || status === "WAITING_FOR_DEPLOY" || status === "DELAYED";
const isExecuting = status === "EXECUTING" || status === "REATTEMPTING" || status === "FROZEN";
const isCompleted =
status === "COMPLETED" ||
@@ -209,7 +209,10 @@ export class ApiRunListPresenter extends BasePresenter {
updatedAt: new Date(run.updatedAt),
startedAt: run.startedAt ? new Date(run.startedAt) : undefined,
finishedAt: run.finishedAt ? new Date(run.finishedAt) : undefined,
delayedUntil: run.delayUntil ? new Date(run.delayUntil) : undefined,
isTest: run.isTest,
ttl: run.ttl ?? undefined,
expiredAt: run.expiredAt ? new Date(run.expiredAt) : undefined,
env: {
id: run.environment.id,
name: run.environment.slug,
@@ -233,6 +236,8 @@ export class ApiRunListPresenter extends BasePresenter {
static apiStatusToRunStatuses(status: RunStatus): TaskRunStatus[] | TaskRunStatus {
switch (status) {
case "DELAYED":
return "DELAYED";
case "WAITING_FOR_DEPLOY": {
return "WAITING_FOR_DEPLOY";
}
@@ -266,6 +271,9 @@ export class ApiRunListPresenter extends BasePresenter {
case "FAILED": {
return "COMPLETED_WITH_ERRORS";
}
case "EXPIRED": {
return "EXPIRED";
}
default: {
assertNever(status);
}
@@ -158,10 +158,13 @@ export class RunListPresenter extends BasePresenter {
createdAt: Date;
startedAt: Date | null;
lockedAt: Date | null;
delayUntil: Date | null;
updatedAt: Date;
isTest: boolean;
spanId: string;
idempotencyKey: string | null;
ttl: string | null;
expiredAt: Date | null;
}[]
>`
SELECT
@@ -174,11 +177,14 @@ export class RunListPresenter extends BasePresenter {
tr.status AS status,
tr."createdAt" AS "createdAt",
tr."startedAt" AS "startedAt",
tr."delayUntil" AS "delayUntil",
tr."lockedAt" AS "lockedAt",
tr."updatedAt" AS "updatedAt",
tr."isTest" AS "isTest",
tr."spanId" AS "spanId",
tr."idempotencyKey" AS "idempotencyKey"
tr."idempotencyKey" AS "idempotencyKey",
tr."ttl" AS "ttl",
tr."expiredAt" AS "expiredAt"
FROM
${sqlDatabaseSchema}."TaskRun" tr
LEFT JOIN
@@ -283,6 +289,7 @@ export class RunListPresenter extends BasePresenter {
createdAt: run.createdAt.toISOString(),
updatedAt: run.updatedAt.toISOString(),
startedAt: startedAt ? startedAt.toISOString() : undefined,
delayUntil: run.delayUntil ? run.delayUntil.toISOString() : undefined,
hasFinished,
finishedAt: hasFinished ? run.updatedAt.toISOString() : undefined,
isTest: run.isTest,
@@ -294,6 +301,8 @@ export class RunListPresenter extends BasePresenter {
isCancellable: isCancellableRunStatus(run.status),
environment: displayableEnvironment(environment, userId),
idempotencyKey: run.idempotencyKey ? run.idempotencyKey : undefined,
ttl: run.ttl ? run.ttl : undefined,
expiredAt: run.expiredAt ? run.expiredAt.toISOString() : undefined,
};
}),
pagination: {
@@ -0,0 +1,85 @@
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { RescheduleRunRequestBody } from "@trigger.dev/core/v3/schemas";
import { z } from "zod";
import { prisma } from "~/db.server";
import { ApiRetrieveRunPresenter } from "~/presenters/v3/ApiRetrieveRunPresenter.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
import { ServiceValidationError } from "~/v3/services/baseService.server";
import { RescheduleTaskRunService } from "~/v3/services/rescheduleTaskRun.server";
const ParamsSchema = z.object({
runParam: z.string(),
});
export async function action({ request, params }: ActionFunctionArgs) {
// Ensure this is a POST request
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
}
// Authenticate the request
const authenticationResult = await authenticateApiRequest(request);
if (!authenticationResult) {
return json({ error: "Invalid or missing API Key" }, { status: 401 });
}
const parsed = ParamsSchema.safeParse(params);
if (!parsed.success) {
return json({ error: "Invalid or missing run ID" }, { status: 400 });
}
const { runParam } = parsed.data;
const taskRun = await prisma.taskRun.findUnique({
where: {
friendlyId: runParam,
runtimeEnvironmentId: authenticationResult.environment.id,
},
});
if (!taskRun) {
return json({ error: "Run not found" }, { status: 404 });
}
const anyBody = await request.json();
const body = RescheduleRunRequestBody.safeParse(anyBody);
if (!body.success) {
return json({ error: "Invalid request body" }, { status: 400 });
}
const service = new RescheduleTaskRunService();
try {
const updatedRun = await service.call(taskRun, body.data);
if (!updatedRun) {
return json({ error: "An unknown error occurred" }, { status: 500 });
}
const presenter = new ApiRetrieveRunPresenter();
const result = await presenter.call(
updatedRun.friendlyId,
authenticationResult.environment,
true
);
if (!result) {
return json({ error: "Run not found" }, { status: 404 });
}
return json(result);
} catch (error) {
if (error instanceof ServiceValidationError) {
return json({ error: error.message }, { status: 400 });
} else if (error instanceof Error) {
return json({ error: error.message }, { status: 500 });
} else {
return json({ error: "An unknown error occurred" }, { status: 500 });
}
}
}
@@ -33,6 +33,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
const taskRun = await prisma.taskRun.findUnique({
where: {
friendlyId: runParam,
runtimeEnvironmentId: authenticationResult.environment.id,
},
});
+26
View File
@@ -47,6 +47,8 @@ import { ResumeTaskService } from "./tasks/resumeTask.server";
import { RequeueV2Message } from "~/v3/marqs/requeueV2Message.server";
import { MarqsConcurrencyMonitor } from "~/v3/marqs/concurrencyMonitor.server";
import { reportUsageEvent } from "~/v3/openMeter.server";
import { EnqueueDelayedRunService } from "~/v3/services/enqueueDelayedRun.server";
import { ExpireEnqueuedRunService } from "~/v3/services/expireEnqueuedRun.server";
const workerCatalog = {
indexEndpoint: z.object({
@@ -177,6 +179,12 @@ const workerCatalog = {
}),
additionalData: z.record(z.any()).optional(),
}),
"v3.enqueueDelayedRun": z.object({
runId: z.string(),
}),
"v3.expireRun": z.object({
runId: z.string(),
}),
};
const executionWorkerCatalog = {
@@ -672,6 +680,24 @@ function getWorkerQueue() {
});
},
},
"v3.enqueueDelayedRun": {
priority: 0,
maxAttempts: 8,
handler: async (payload, job) => {
const service = new EnqueueDelayedRunService();
return await service.call(payload.runId);
},
},
"v3.expireRun": {
priority: 0,
maxAttempts: 8,
handler: async (payload, job) => {
const service = new ExpireEnqueuedRunService();
return await service.call(payload.runId);
},
},
},
});
}
@@ -48,6 +48,7 @@ export class RequeueTaskRunService extends BaseService {
break;
}
case "DELAYED":
case "WAITING_FOR_DEPLOY": {
logger.debug("[RequeueTaskRunService] Removing task run from queue", { taskRun });
@@ -68,6 +69,7 @@ export class RequeueTaskRunService extends BaseService {
case "CRASHED":
case "COMPLETED_WITH_ERRORS":
case "COMPLETED_SUCCESSFULLY":
case "EXPIRED":
case "CANCELED": {
logger.debug("[RequeueTaskRunService] Task run is completed", { taskRun });
@@ -0,0 +1,72 @@
import { logger } from "~/services/logger.server";
import { marqs } from "~/v3/marqs/index.server";
import { BaseService } from "./baseService.server";
import { parseNaturalLanguageDuration } from "./triggerTask.server";
import { workerQueue } from "~/services/worker.server";
import { $transaction } from "~/db.server";
export class EnqueueDelayedRunService extends BaseService {
public async call(runId: string) {
const run = await this._prisma.taskRun.findUnique({
where: {
id: runId,
},
include: {
runtimeEnvironment: {
include: {
organization: true,
project: true,
},
},
},
});
if (!run) {
logger.debug("Could not find delayed run to enqueue", {
runId,
});
return;
}
if (run.status !== "DELAYED") {
logger.debug("Delayed run cannot be enqueued because it's not in DELAYED status", {
run,
});
return;
}
await $transaction(this._prisma, async (tx) => {
await tx.taskRun.update({
where: {
id: run.id,
},
data: {
status: "PENDING",
queuedAt: new Date(),
},
});
if (run.ttl) {
const expireAt = parseNaturalLanguageDuration(run.ttl);
if (expireAt) {
await workerQueue.enqueue(
"v3.expireRun",
{ runId: run.id },
{ tx, runAt: expireAt, jobKey: `v3.expireRun.${run.id}` }
);
}
}
});
await marqs?.enqueueMessage(
run.runtimeEnvironment,
run.queue,
run.id,
{ type: "EXECUTE", taskIdentifier: run.taskIdentifier },
run.concurrencyKey ?? undefined
);
}
}
@@ -0,0 +1,53 @@
import { logger } from "~/services/logger.server";
import { marqs } from "~/v3/marqs/index.server";
import { BaseService } from "./baseService.server";
export class ExpireEnqueuedRunService extends BaseService {
public async call(runId: string) {
const run = await this._prisma.taskRun.findUnique({
where: {
id: runId,
},
include: {
runtimeEnvironment: {
include: {
organization: true,
project: true,
},
},
},
});
if (!run) {
logger.debug("Could not find enqueued run to expire", {
runId,
});
return;
}
if (run.status !== "PENDING") {
logger.debug("Run cannot be expired because it's not in PENDING status", {
run,
});
return;
}
logger.debug("Expiring enqueued run", {
run,
});
await this._prisma.taskRun.update({
where: {
id: run.id,
},
data: {
status: "EXPIRED",
expiredAt: new Date(),
},
});
await marqs?.acknowledgeMessage(run.id);
}
}
@@ -0,0 +1,39 @@
import { TaskRun } from "@trigger.dev/database";
import { BaseService, ServiceValidationError } from "./baseService.server";
import { RescheduleRunRequestBody } from "@trigger.dev/core/v3";
import { parseDelay } from "./triggerTask.server";
import { $transaction } from "~/db.server";
import { workerQueue } from "~/services/worker.server";
export class RescheduleTaskRunService extends BaseService {
public async call(taskRun: TaskRun, body: RescheduleRunRequestBody) {
if (taskRun.status !== "DELAYED") {
throw new ServiceValidationError("Cannot reschedule a run that is not delayed");
}
const delay = await parseDelay(body.delay);
if (!delay) {
throw new ServiceValidationError(`Invalid delay: ${body.delay}`);
}
return await $transaction(this._prisma, async (tx) => {
const updatedRun = await tx.taskRun.update({
where: {
id: taskRun.id,
},
data: {
delayUntil: delay,
},
});
await workerQueue.enqueue(
"v3.enqueueDelayedRun",
{ runId: taskRun.id },
{ tx, runAt: delay, jobKey: `v3.enqueueDelayedRun.${taskRun.id}` }
);
return updatedRun;
});
}
}
+152 -18
View File
@@ -4,15 +4,16 @@ import {
TriggerTaskRequestBody,
packetRequiresOffloading,
} from "@trigger.dev/core/v3";
import { env } from "~/env.server";
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { autoIncrementCounter } from "~/services/autoIncrementCounter.server";
import { workerQueue } from "~/services/worker.server";
import { marqs, sanitizeQueueName } from "~/v3/marqs/index.server";
import { eventRepository } from "../eventRepository.server";
import { generateFriendlyId } from "../friendlyIdentifiers";
import { uploadToObjectStore } from "../r2.server";
import { startActiveSpan } from "../tracer.server";
import { BaseService } from "./baseService.server";
import { env } from "~/env.server";
export type TriggerTaskServiceOptions = {
idempotencyKey?: string;
@@ -35,6 +36,12 @@ export class TriggerTaskService extends BaseService {
span.setAttribute("taskId", taskId);
const idempotencyKey = options.idempotencyKey ?? body.options?.idempotencyKey;
const delayUntil = await parseDelay(body.options?.delay);
const ttl =
typeof body.options?.ttl === "number"
? stringifyDuration(body.options?.ttl)
: body.options?.ttl ?? (environment.type === "DEVELOPMENT" ? "10m" : undefined);
const existingRun = idempotencyKey
? await this._prisma.taskRun.findUnique({
@@ -49,9 +56,19 @@ export class TriggerTaskService extends BaseService {
if (existingRun && existingRun.taskIdentifier === taskId) {
span.setAttribute("runId", existingRun.friendlyId);
return existingRun;
}
const runFriendlyId = generateFriendlyId("run");
const payloadPacket = await this.#handlePayloadPacket(
body.payload,
body.options?.payloadType ?? "application/json",
runFriendlyId,
environment
);
return await eventRepository.traceEvent(
taskId,
{
@@ -76,15 +93,6 @@ export class TriggerTaskService extends BaseService {
immediate: true,
},
async (event, traceContext) => {
const runFriendlyId = generateFriendlyId("run");
const payloadPacket = await this.#handlePayloadPacket(
body.payload,
body.options?.payloadType ?? "application/json",
runFriendlyId,
environment
);
const run = await autoIncrementCounter.incrementInTransaction(
`v3-run:${environment.id}:${taskId}`,
async (num, tx) => {
@@ -112,7 +120,7 @@ export class TriggerTaskService extends BaseService {
const taskRun = await tx.taskRun.create({
data: {
status: "PENDING",
status: delayUntil ? "DELAYED" : "PENDING",
number: num,
friendlyId: runFriendlyId,
runtimeEnvironmentId: environment.id,
@@ -129,6 +137,9 @@ export class TriggerTaskService extends BaseService {
concurrencyKey: body.options?.concurrencyKey,
queue: queueName,
isTest: body.options?.test ?? false,
delayUntil,
queuedAt: delayUntil ? undefined : new Date(),
ttl,
},
});
@@ -215,6 +226,26 @@ export class TriggerTaskService extends BaseService {
}
}
if (taskRun.delayUntil) {
await workerQueue.enqueue(
"v3.enqueueDelayedRun",
{ runId: taskRun.id },
{ tx, runAt: delayUntil, jobKey: `v3.enqueueDelayedRun.${taskRun.id}` }
);
}
if (!taskRun.delayUntil && taskRun.ttl) {
const expireAt = parseNaturalLanguageDuration(taskRun.ttl);
if (expireAt) {
await workerQueue.enqueue(
"v3.expireRun",
{ runId: taskRun.id },
{ tx, runAt: expireAt, jobKey: `v3.expireRun.${taskRun.id}` }
);
}
}
return taskRun;
},
async (_, tx) => {
@@ -238,13 +269,15 @@ export class TriggerTaskService extends BaseService {
}
// We need to enqueue the task run into the appropriate queue. This is done after the tx completes to prevent a race condition where the task run hasn't been created yet by the time we dequeue.
await marqs?.enqueueMessage(
environment,
run.queue,
run.id,
{ type: "EXECUTE", taskIdentifier: taskId },
body.options?.concurrencyKey
);
if (run.status === "PENDING") {
await marqs?.enqueueMessage(
environment,
run.queue,
run.id,
{ type: "EXECUTE", taskIdentifier: taskId },
body.options?.concurrencyKey
);
}
return run;
}
@@ -297,3 +330,104 @@ export class TriggerTaskService extends BaseService {
return { dataType: payloadType };
}
}
export async function parseDelay(value?: string | Date): Promise<Date | undefined> {
if (!value) {
return;
}
if (value instanceof Date) {
return value;
}
try {
const date = new Date(value);
// Check if the date is valid
if (isNaN(date.getTime())) {
return parseNaturalLanguageDuration(value);
}
if (date.getTime() <= Date.now()) {
return;
}
return date;
} catch (error) {
return parseNaturalLanguageDuration(value);
}
}
export function parseNaturalLanguageDuration(duration: string): Date | undefined {
const regexPattern = /^(\d+w)?(\d+d)?(\d+h)?(\d+m)?(\d+s)?$/;
const result: Date = new Date();
let hasMatch = false;
const elements = duration.match(regexPattern);
if (elements) {
if (elements[1]) {
const weeks = Number(elements[1].slice(0, -1));
if (weeks >= 0) {
result.setDate(result.getDate() + 7 * weeks);
hasMatch = true;
}
}
if (elements[2]) {
const days = Number(elements[2].slice(0, -1));
if (days >= 0) {
result.setDate(result.getDate() + days);
hasMatch = true;
}
}
if (elements[3]) {
const hours = Number(elements[3].slice(0, -1));
if (hours >= 0) {
result.setHours(result.getHours() + hours);
hasMatch = true;
}
}
if (elements[4]) {
const minutes = Number(elements[4].slice(0, -1));
if (minutes >= 0) {
result.setMinutes(result.getMinutes() + minutes);
hasMatch = true;
}
}
if (elements[5]) {
const seconds = Number(elements[5].slice(0, -1));
if (seconds >= 0) {
result.setSeconds(result.getSeconds() + seconds);
hasMatch = true;
}
}
}
if (hasMatch) {
return result;
}
return undefined;
}
function stringifyDuration(seconds: number): string | undefined {
if (seconds <= 0) {
return;
}
const units = {
w: Math.floor(seconds / 604800),
d: Math.floor((seconds % 604800) / 86400),
h: Math.floor((seconds % 86400) / 3600),
m: Math.floor((seconds % 3600) / 60),
s: Math.floor(seconds % 60),
};
// Filter the units having non-zero values and join them
const result: string = Object.entries(units)
.filter(([unit, val]) => val != 0)
.map(([unit, val]) => `${val}${unit}`)
.join("");
return result;
}
+2
View File
@@ -1,6 +1,7 @@
import type { TaskRunAttemptStatus, TaskRunStatus } from "@trigger.dev/database";
export const CANCELLABLE_RUN_STATUSES: TaskRunStatus[] = [
"DELAYED",
"PENDING",
"WAITING_FOR_DEPLOY",
"EXECUTING",
@@ -38,6 +39,7 @@ export const FINAL_RUN_STATUSES: TaskRunStatus[] = [
"COMPLETED_WITH_ERRORS",
"INTERRUPTED",
"SYSTEM_FAILURE",
"EXPIRED",
];
export const FINAL_ATTEMPT_STATUSES: TaskRunAttemptStatus[] = ["CANCELED", "COMPLETED", "FAILED"];
Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

+79 -24
View File
@@ -1,8 +1,14 @@
{
"$schema": "https://mintlify.com/schema.json",
"name": "Trigger.dev",
"openapi": ["/openapi.yml", "/v3-openapi.yaml"],
"versions": ["v3", "v2"],
"openapi": [
"/openapi.yml",
"/v3-openapi.yaml"
],
"versions": [
"v3",
"v2"
],
"api": {
"playground": {
"mode": "simple"
@@ -29,7 +35,6 @@
}
},
"theme": "quill",
"modeToggle": {
"default": "dark",
"isHidden": true
@@ -49,7 +54,6 @@
"url": "https://discord.gg/kA47vcd8P6"
}
],
"redirects": [
{
"source": "/documentation/quickstart",
@@ -72,7 +76,9 @@
{
"group": "",
"version": "v3",
"pages": ["v3/introduction"]
"pages": [
"v3/introduction"
]
},
{
"group": "Getting Started",
@@ -95,7 +101,10 @@
"v3/apikeys",
{
"group": "Task types",
"pages": ["v3/tasks-regular", "v3/tasks-scheduled"]
"pages": [
"v3/tasks-regular",
"v3/tasks-scheduled"
]
},
"v3/trigger-config"
]
@@ -103,7 +112,10 @@
{
"group": "Development",
"version": "v3",
"pages": ["v3/cli-dev", "v3/run-tests"]
"pages": [
"v3/cli-dev",
"v3/run-tests"
]
},
{
"group": "Deployment",
@@ -114,7 +126,9 @@
"v3/github-actions",
{
"group": "Deployment integrations",
"pages": ["v3/vercel-integration"]
"pages": [
"v3/vercel-integration"
]
}
]
},
@@ -150,7 +164,10 @@
"v3/management/overview",
{
"group": "Tasks API",
"pages": ["v3/management/tasks/trigger", "v3/management/tasks/batch-trigger"]
"pages": [
"v3/management/tasks/trigger",
"v3/management/tasks/batch-trigger"
]
},
{
"group": "Runs API",
@@ -158,7 +175,8 @@
"v3/management/runs/list",
"v3/management/runs/retrieve",
"v3/management/runs/replay",
"v3/management/runs/cancel"
"v3/management/runs/cancel",
"v3/management/runs/reschedule"
]
},
{
@@ -187,14 +205,20 @@
},
{
"group": "Projects API",
"pages": ["v3/management/projects/runs"]
"pages": [
"v3/management/projects/runs"
]
}
]
},
{
"group": "Open source",
"version": "v3",
"pages": ["v3/github-repo", "v3/open-source-self-hosting", "v3/open-source-contributing"]
"pages": [
"v3/github-repo",
"v3/open-source-self-hosting",
"v3/open-source-contributing"
]
},
{
"group": "Troubleshooting",
@@ -210,7 +234,11 @@
{
"group": "Help",
"version": "v3",
"pages": ["v3/community", "v3/help-slack", "v3/help-email"]
"pages": [
"v3/community",
"v3/help-slack",
"v3/help-email"
]
},
{
"group": "Getting Started",
@@ -402,7 +430,10 @@
"pages": [
{
"group": "Airtable",
"pages": ["integrations/apis/airtable", "integrations/apis/airtable-tasks"]
"pages": [
"integrations/apis/airtable",
"integrations/apis/airtable-tasks"
]
},
{
"group": "GitHub",
@@ -428,16 +459,25 @@
},
{
"group": "Plain",
"pages": ["integrations/apis/plain", "integrations/apis/plain-tasks"]
"pages": [
"integrations/apis/plain",
"integrations/apis/plain-tasks"
]
},
"integrations/apis/replicate",
{
"group": "SendGrid",
"pages": ["integrations/apis/sendgrid", "integrations/apis/sendgrid-tasks"]
"pages": [
"integrations/apis/sendgrid",
"integrations/apis/sendgrid-tasks"
]
},
{
"group": "Resend",
"pages": ["integrations/apis/resend", "integrations/apis/resend-tasks"]
"pages": [
"integrations/apis/resend",
"integrations/apis/resend-tasks"
]
},
{
"group": "Shopify",
@@ -449,7 +489,10 @@
},
{
"group": "Slack",
"pages": ["integrations/apis/slack", "integrations/apis/slack-tasks"]
"pages": [
"integrations/apis/slack",
"integrations/apis/slack-tasks"
]
},
"integrations/apis/stripe",
{
@@ -475,7 +518,9 @@
"sdk/triggerclient/constructor",
{
"group": "Instance properties",
"pages": ["sdk/triggerclient/store"]
"pages": [
"sdk/triggerclient/store"
]
},
{
"group": "Instance methods",
@@ -538,7 +583,10 @@
"sdk/dynamictrigger/constructor",
{
"group": "Instance methods",
"pages": ["sdk/dynamictrigger/register", "sdk/dynamictrigger/unregister"]
"pages": [
"sdk/dynamictrigger/register",
"sdk/dynamictrigger/unregister"
]
}
]
},
@@ -549,7 +597,10 @@
"sdk/dynamicschedule/constructor",
{
"group": "Instance methods",
"pages": ["sdk/dynamicschedule/register", "sdk/dynamicschedule/unregister"]
"pages": [
"sdk/dynamicschedule/register",
"sdk/dynamicschedule/unregister"
]
}
]
},
@@ -562,7 +613,9 @@
{
"group": "HTTP Reference",
"version": "v2",
"pages": ["sdk/api-reference/events/create-an-event"]
"pages": [
"sdk/api-reference/events/create-an-event"
]
},
{
"group": "React SDK",
@@ -578,7 +631,9 @@
{
"group": "Overview",
"version": "v2",
"pages": ["examples/introduction"]
"pages": [
"examples/introduction"
]
}
],
"footerSocials": {
@@ -586,4 +641,4 @@
"github": "https://github.com/triggerdotdev",
"linkedin": "https://www.linkedin.com/company/triggerdotdev"
}
}
}
+83
View File
@@ -269,6 +269,7 @@ paths:
"/api/v1/timezones":
get:
security: []
operationId: get_timezones_v1
summary: Get all supported timezones
description: Get all supported timezones that schedule tasks support.
@@ -424,6 +425,71 @@ paths:
await runs.cancel("run_1234");
"/api/v1/runs/{runId}/reschedule":
parameters:
- $ref: "#/components/parameters/runId"
post:
operationId: reschedule_run_v1
summary: Rescheduled a delayed run
description: Updates a delayed run with a new delay. Only valid when the run is in the DELAYED state.
requestBody:
required: true
content:
application/json:
schema:
"$ref": "#/components/schemas/RescheduleRunRequestBody"
responses:
"200":
description: Successful request
content:
application/json:
schema:
"$ref": "#/components/schemas/RetrieveRunResponse"
"400":
description: Invalid request
content:
application/json:
schema:
type: object
properties:
error:
type: string
enum:
- Invalid or missing run ID
- Failed to create new run
"401":
description: Unauthorized request
content:
application/json:
schema:
type: object
properties:
error:
type: string
enum:
- Invalid or Missing API key
"404":
description: Resource not found
content:
application/json:
schema:
type: object
properties:
error:
type: string
enum:
- Run not found
tags:
- runs
security:
- secretKey: []
x-codeSamples:
- lang: typescript
source: |-
import { runs } from "@trigger.dev/sdk/v3";
const handle = await runs.reschedule("run_1234", { delay: new Date("2024-06-29T20:45:56.340Z") });
"/api/v3/runs/{runId}":
parameters:
- $ref: "#/components/parameters/runId"
@@ -1681,6 +1747,18 @@ components:
type: string
example: slack_123456
required: ["name", "value"]
RescheduleRunRequestBody:
type: object
properties:
delay:
oneOf:
- type: string
description: The duration to delay the run by. The duration should be in the format of `1d`, `6h`, `10m`, `11s`, etc.
example: 1hr
- type: string
format: date-time
description: The Date to delay the run until, e.g. `new Date()` or `"2024-06-25T15:45:26Z"`
example: 2024-06-25T15:45:26Z
RetrieveRunResponse:
type: object
required:
@@ -1699,6 +1777,7 @@ components:
type: string
description: The status of the run
enum:
- DELAYED
- WAITING_FOR_DEPLOY
- QUEUED
- EXECUTING
@@ -1756,6 +1835,10 @@ components:
type: string
format: date-time
description: The time the run finished
delayedUntil:
type: string
format: date-time
description: If the run was triggered with a delay, this will be the time the run will be enqueued to execute
schedule:
type: object
description: The schedule that triggered the run. Will be omitted if the run was not triggered by a schedule
+4
View File
@@ -0,0 +1,4 @@
---
title: "Reschedule run"
openapi: "v3-openapi POST /api/v1/runs/{runId}/reschedule"
---
+91
View File
@@ -619,3 +619,94 @@ export const myTask = task({
### Batch Triggering
When using `batchTrigger` or `batchTriggerAndWait`, the total size of all payloads cannot exceed 10MB. This means if you are doing a batch of 100 runs, each payload should be less than 100KB.
## Delayed runs
When you want to trigger a task now, but have it run at a later time, you can use the `delay` option:
```ts
// Delay the task run by 1 hour
await myTask.trigger({ some: "data" }, { delay: "1h" });
// Delay the task run by 88 seconds
await myTask.trigger({ some: "data" }, { delay: "88s" });
// Delay the task run by 1 hour and 52 minutes and 18 seconds
await myTask.trigger({ some: "data" }, { delay: "1h52m18s" });
// Delay until a specific time
await myTask.trigger({ some: "data" }, { delay: "2024-12-01T00:00:00" });
// Delay using a Date object
await myTask.trigger({ some: "data" }, { delay: new Date(Date.now() + 1000 * 60 * 60) });
```
Runs that are delayed and have not been enqueued yet will display in the dashboard with a "Delayed" status:
![Delayed run in the dashboard](/images/v3/delayed-runs.png)
<Note>
Delayed runs will be enqueued at the time specified, and will run as soon as possible after that
time, just as a normally triggered run would.
</Note>
You can cancel a delayed run using the `runs.cancel` SDK function:
```ts
import { runs } from "@trigger.dev/sdk/v3";
await runs.cancel("run_1234");
```
You can also reschedule a delayed run using the `runs.reschedule` SDK function:
```ts
import { runs } from "@trigger.dev/sdk/v3";
// The delay option here takes the same format as the trigger delay option
await runs.reschedule("run_1234", { delay: "1h" });
```
The `delay` option is also available when using `batchTrigger`:
```ts
await myTask.batchTrigger([{ payload: { some: "data" }, options: { delay: "1h" } }]);
```
## TTL
You can set a TTL (time to live) when triggering a task, which will automatically expire the run if it hasn't started within the specified time. This is useful for ensuring that a run doesn't get stuck in the queue for too long.
<Note>
All runs in development have a default `ttl` of 10 minutes. You can disable this by setting the
`ttl` option.
</Note>
```ts
import { myTask } from "./trigger/myTasks";
// Expire the run if it hasn't started within 1 hour
await myTask.trigger({ some: "data" }, { ttl: "1h" });
// If you specify a number, it will be treated as seconds
await myTask.trigger({ some: "data" }, { ttl: 3600 }); // 1 hour
```
When a run is expired, it will be marked as "Expired" in the dashboard:
![Expired runs in the dashboard](/images/v3/expired-runs.png)
### Delayed runs and TTL
When you use both `delay` and `ttl`, the TTL will start counting down from the time the run is enqueued, not from the time the run is triggered.
So for example, when using the following code:
```ts
await myTask.trigger({ some: "data" }, { delay: "10m", ttl: "1h" });
```
The timeline would look like this:
1. The run is created at 12:00:00
2. The run is enqueued at 12:10:00
3. The TTL starts counting down from 12:10:00
4. If the run hasn't started by 13:10:00, it will be expired
For this reason, the `ttl` option only accepts durations and not absolute timestamps.
+8
View File
@@ -117,6 +117,14 @@
"require": "./dist/v3/workers/index.js",
"types": "./dist/v3/workers/index.d.ts"
},
"./v3/schemas": {
"import": {
"types": "./dist/v3/schemas/index.d.mts",
"default": "./dist/v3/schemas/index.mjs"
},
"require": "./dist/v3/schemas/index.js",
"types": "./dist/v3/schemas/index.d.ts"
},
"./package.json": "./package.json"
},
"typesVersions": {
+14
View File
@@ -15,6 +15,7 @@ import {
ListRunResponseItem,
ListScheduleOptions,
ReplayRunResponse,
RescheduleRunRequestBody,
RetrieveRunResponse,
ScheduleObject,
TaskRunExecutionResult,
@@ -247,6 +248,19 @@ export class ApiClient {
);
}
rescheduleRun(runId: string, body: RescheduleRunRequestBody) {
return zodfetch(
RetrieveRunResponse,
`${this.baseUrl}/api/v1/runs/${runId}/reschedule`,
{
method: "POST",
headers: this.#getHeaders(false),
body: JSON.stringify(body),
},
zodFetchOptions
);
}
createSchedule(options: CreateScheduleOptions) {
return zodfetch(ScheduleObject, `${this.baseUrl}/api/v1/schedules`, {
method: "POST",
+15
View File
@@ -68,6 +68,8 @@ export const TriggerTaskRequestBody = z.object({
idempotencyKey: z.string().optional(),
test: z.boolean().optional(),
payloadType: z.string().optional(),
delay: z.string().or(z.coerce.date()).optional(),
ttl: z.string().or(z.number().nonnegative().int()).optional(),
})
.optional(),
});
@@ -107,6 +109,12 @@ export const GetBatchResponseBody = z.object({
export type GetBatchResponseBody = z.infer<typeof GetBatchResponseBody>;
export const RescheduleRunRequestBody = z.object({
delay: z.string().or(z.coerce.date()),
});
export type RescheduleRunRequestBody = z.infer<typeof RescheduleRunRequestBody>;
export const GetEnvironmentVariablesResponseBody = z.object({
variables: z.record(z.string()),
});
@@ -377,6 +385,10 @@ export const RunStatus = z.enum([
"INTERRUPTED",
/// Task has failed to complete, due to an error in the system
"SYSTEM_FAILURE",
/// Task has been scheduled to run at a specific time
"DELAYED",
/// Task has expired and won't be executed
"EXPIRED",
]);
export type RunStatus = z.infer<typeof RunStatus>;
@@ -426,6 +438,9 @@ const CommonRunFields = {
updatedAt: z.coerce.date(),
startedAt: z.coerce.date().optional(),
finishedAt: z.coerce.date().optional(),
delayedUntil: z.coerce.date().optional(),
ttl: z.string().optional(),
expiredAt: z.coerce.date().optional(),
};
export const RetrieveRunResponse = z.object({
+1
View File
@@ -17,5 +17,6 @@ export default defineConfig({
"./src/v3/prod/index.ts",
"./src/v3/workers/index.ts",
"./src/v3/zodfetch.ts",
"./src/v3/schemas/index.ts",
],
});
@@ -0,0 +1,6 @@
-- AlterEnum
ALTER TYPE "TaskRunStatus" ADD VALUE 'DELAYED';
-- AlterTable
ALTER TABLE "TaskRun" ADD COLUMN "delayUntil" TIMESTAMP(3),
ADD COLUMN "queuedAt" TIMESTAMP(3);
@@ -0,0 +1,6 @@
-- AlterEnum
ALTER TYPE "TaskRunStatus" ADD VALUE 'EXPIRED';
-- AlterTable
ALTER TABLE "TaskRun" ADD COLUMN "expiredAt" TIMESTAMP(3),
ADD COLUMN "ttl" TEXT;
+10
View File
@@ -1660,6 +1660,11 @@ model TaskRun {
concurrencyKey String?
delayUntil DateTime?
queuedAt DateTime?
ttl String?
expiredAt DateTime?
batchItems BatchTaskRunItem[]
dependency TaskRunDependency?
CheckpointRestoreEvent CheckpointRestoreEvent[]
@@ -1684,6 +1689,8 @@ model TaskRun {
}
enum TaskRunStatus {
/// Task has been scheduled to run in the future
DELAYED
/// Task is waiting to be executed by a worker
PENDING
@@ -1719,6 +1726,9 @@ enum TaskRunStatus {
/// Task has crashed and won't be retried, most likely the worker ran out of resources, e.g. memory or storage
CRASHED
// Task reached the ttl without being executed
EXPIRED
}
model TaskRunDependency {
+19 -1
View File
@@ -1,4 +1,8 @@
import type { ListProjectRunsQueryParams, ListRunsQueryParams } from "@trigger.dev/core/v3";
import type {
ListProjectRunsQueryParams,
ListRunsQueryParams,
RescheduleRunRequestBody,
} from "@trigger.dev/core/v3";
import {
ApiPromise,
CanceledRunResponse,
@@ -21,6 +25,7 @@ export const runs = {
cancel: cancelRun,
retrieve: retrieveRun,
list: listRuns,
reschedule: rescheduleRun,
poll,
};
@@ -84,6 +89,19 @@ function cancelRun(runId: string): ApiPromise<CanceledRunResponse> {
return apiClient.cancelRun(runId);
}
function rescheduleRun(
runId: string,
body: RescheduleRunRequestBody
): ApiPromise<RetrieveRunResponse> {
const apiClient = apiClientManager.client;
if (!apiClient) {
throw apiClientMissingError();
}
return apiClient.rescheduleRun(runId, body);
}
export type PollOptions = { pollIntervalMs?: number };
async function poll<TRunHandle extends RunHandle<any> | string>(
+41 -2
View File
@@ -154,6 +154,7 @@ export type TaskOptions<
| "large-1x"
| "large-2x";
};
/** This gets called when a task is triggered. It's where you put the code you want to execute.
*
* @param payload - The payload that is passed to your task when it's triggered. This must be JSON serializable.
@@ -363,10 +364,36 @@ export type TaskIdentifier<TTask extends AnyTask> = TTask extends Task<infer TId
export type TaskRunOptions = {
idempotencyKey?: string;
maxAttempts?: number;
startAt?: Date;
startAfter?: number;
queue?: TaskRunConcurrencyOptions;
concurrencyKey?: string;
/**
* The delay before the task is executed. This can be a string like "1h" or a Date object.
*
* @example
* "1h" - 1 hour
* "30d" - 30 days
* "15m" - 15 minutes
* "2w" - 2 weeks
* "60s" - 60 seconds
* new Date("2025-01-01T00:00:00Z")
*/
delay?: string | Date;
/**
* Set a time-to-live for this run. If the run is not executed within this time, it will be removed from the queue and never execute.
*
* @example
*
* ```ts
* await myTask.trigger({ foo: "bar" }, { ttl: "1h" });
* await myTask.trigger({ foo: "bar" }, { ttl: 60 * 60 }); // 1 hour
* ```
*
* The minimum value is 1 second. Setting the `ttl` to `0` will disable the TTL and the run will never expire.
*
* **Note:** Runs in development have a default `ttl` of 10 minutes. You can override this by setting the `ttl` option.
*/
ttl?: string | number;
};
type TaskRunConcurrencyOptions = Queue;
@@ -413,6 +440,8 @@ export function createTask<
test: taskContext.ctx?.run.isTest,
payloadType: payloadPacket.dataType,
idempotencyKey: options?.idempotencyKey,
delay: options?.delay,
ttl: options?.ttl,
},
},
{ spanParentAsLink: true }
@@ -474,6 +503,8 @@ export function createTask<
test: taskContext.ctx?.run.isTest,
payloadType: payloadPacket.dataType,
idempotencyKey: item.options?.idempotencyKey,
delay: item.options?.delay,
ttl: item.options?.ttl,
},
};
})
@@ -547,6 +578,8 @@ export function createTask<
test: taskContext.ctx?.run.isTest,
payloadType: payloadPacket.dataType,
idempotencyKey: options?.idempotencyKey,
delay: options?.delay,
ttl: options?.ttl,
},
});
@@ -631,6 +664,8 @@ export function createTask<
test: taskContext.ctx?.run.isTest,
payloadType: payloadPacket.dataType,
idempotencyKey: item.options?.idempotencyKey,
delay: item.options?.delay,
ttl: item.options?.ttl,
},
};
})
@@ -792,6 +827,8 @@ export async function trigger<TTask extends AnyTask>(
test: taskContext.ctx?.run.isTest,
payloadType: payloadPacket.dataType,
idempotencyKey: options?.idempotencyKey,
delay: options?.delay,
ttl: options?.ttl,
},
});
@@ -846,6 +883,8 @@ export async function batchTrigger<TTask extends AnyTask>(
test: taskContext.ctx?.run.isTest,
payloadType: payloadPacket.dataType,
idempotencyKey: item.options?.idempotencyKey,
delay: item.options?.delay,
ttl: item.options?.ttl,
},
};
})
+56 -41
View File
@@ -8,59 +8,74 @@ type createJsonHeroDocIdentifier = TaskIdentifier<typeof createJsonHeroDoc>; //
type createJsonHeroDocHandle = TaskOutputHandle<typeof createJsonHeroDoc>; // retrieves the handle of the task
async function main() {
const anyHandle = await tasks.trigger("create-jsonhero-doc", {
title: "Hello World",
content: {
message: "Hello, World!",
const anyHandle = await tasks.trigger(
"create-jsonhero-doc",
{
title: "Hello World",
content: {
message: "Hello, World!",
},
},
});
{
delay: "1m",
ttl: "1m",
}
);
const anyRun = await runs.retrieve(anyHandle);
console.log(`Run ${anyHandle.id} completed with output:`, anyRun.output);
console.log(`Run ${anyHandle.id} status: ${anyRun.status}, ttl: ${anyRun.ttl}`);
const handle = await tasks.trigger<typeof createJsonHeroDoc>("create-jsonhero-doc", {
title: "Hello World",
content: {
message: "Hello, World!",
},
});
await new Promise((resolve) => setTimeout(resolve, 121000)); // wait for 2 minutes
console.log(handle);
const expiredRun = await runs.retrieve(anyRun.id);
const completedRun = await runs.poll(handle, { pollIntervalMs: 100 });
console.log(
`Run ${anyHandle.id} status: ${expiredRun.status}, expired at: ${expiredRun.expiredAt}`
);
console.log(`Run ${handle.id} completed with output:`, completedRun.output);
// const handle = await tasks.trigger<typeof createJsonHeroDoc>("create-jsonhero-doc", {
// title: "Hello World",
// content: {
// message: "Hello, World!",
// },
// });
const run = await tasks.triggerAndPoll<typeof createJsonHeroDoc>("create-jsonhero-doc", {
title: "Hello World",
content: {
message: "Hello, World!",
},
});
// console.log(handle);
console.log(`Run ${run.id} completed with output: `, run.output);
// const completedRun = await runs.poll(handle, { pollIntervalMs: 100 });
const batchHandle = await tasks.batchTrigger<typeof createJsonHeroDoc>("create-jsonhero-doc", [
{
payload: {
title: "Hello World",
content: {
message: "Hello, World!",
},
},
},
{
payload: {
title: "Hello World 2",
content: {
message: "Hello, World 2!",
},
},
},
]);
// console.log(`Run ${handle.id} completed with output:`, completedRun.output);
const run2 = await runs.retrieve(batchHandle.runs[0]);
// const run = await tasks.triggerAndPoll<typeof createJsonHeroDoc>("create-jsonhero-doc", {
// title: "Hello World",
// content: {
// message: "Hello, World!",
// },
// });
// console.log(`Run ${run.id} completed with output: `, run.output);
// const batchHandle = await tasks.batchTrigger<typeof createJsonHeroDoc>("create-jsonhero-doc", [
// {
// payload: {
// title: "Hello World",
// content: {
// message: "Hello, World!",
// },
// },
// },
// {
// payload: {
// title: "Hello World 2",
// content: {
// message: "Hello, World 2!",
// },
// },
// },
// ]);
// const run2 = await runs.retrieve(batchHandle.runs[0]);
}
main().catch(console.error);
+1 -1
View File
@@ -239,5 +239,5 @@ async function doTriggerUnfriendlyTaskId() {
// doListRuns().catch(console.error);
// doScheduleLists().catch(console.error);
// doSchedules().catch(console.error);
doEnvVars().catch(console.error);
// doEnvVars().catch(console.error);
// doTriggerUnfriendlyTaskId().catch(console.error);