Changes from v2 to v3

The main difference is that things in v3 are far simpler. That’s because in v3 your code is deployed to our servers (unless you self-host) which are long-running.
  1. No timeouts.
  2. No io.runTask() (and no cacheKeys).
  3. Just use official SDKs, not integrations.
  4. tasks are the new primitive, not jobs.

Convert your v2 job using an AI prompt

The prompt in the accordion below gives good results when using Anthropic Claude 3.5 Sonnet. You’ll need a relatively large token limit.
Don’t forget to paste your own v2 code in a markdown codeblock at the bottom of the prompt before running it.
I would like you to help me convert from Trigger.dev v2 to Trigger.dev v3. The important differences:
  1. The syntax for creating “background jobs” has changed. In v2 it looked like this:
import { eventTrigger } from "@trigger.dev/sdk";
import { client } from "@/trigger";
import { db } from "@/lib/db";
client.defineJob({
  enabled: true,
  id: "my-job-id",
  name: "My job name",
  version: "0.0.1",
  // This is triggered by an event using eventTrigger. You can also trigger Jobs with webhooks, on schedules, and more: https://trigger.dev/docs/documentation/concepts/triggers/introduction
  trigger: eventTrigger({
    name: "theevent.name",
    schema: z.object({
      phoneNumber: z.string(),
      verified: z.boolean(),
    }),
  }),
  run: async (payload, io) => {
    
    //everything needed to be wrapped in io.runTask in v2, to make it possible for long-running code to work
    const result = await io.runTask("get-stuff-from-db", async () => {
      const socials = await db.query.Socials.findMany({
        where: eq(Socials.service, "tiktok"),
      });
      return socials;
    });
    
    io.logger.info("Completed fetch successfully");
  },
});
In v3 it looks like this:
import { task } from "@trigger.dev/sdk";
import { db } from "@/lib/db";
export const getCreatorVideosFromTikTok = task({
  id: "my-job-id",
  run: async (payload: { phoneNumber: string, verified: boolean }) => {
    //in v3 there are no timeouts, so you can just use the code as is, no need to wrap in `io.runTask`
    const socials = await db.query.Socials.findMany({
      where: eq(Socials.service, "tiktok"),
    });
    
    //use `logger` instead of `io.logger`
    logger.info("Completed fetch successfully");
  },
});
Notice that the schema on v2 eventTrigger defines the payload type. In v3 that needs to be done on the TypeScript type of the run payload param. 2. v2 had integrations with some APIs. Any package that isn’t @trigger.dev/sdk can be replaced with an official SDK. The syntax may need to be adapted. For example: v2:
import { OpenAI } from "@trigger.dev/openai";
const openai = new OpenAI({
  id: "openai",
  apiKey: process.env.OPENAI_API_KEY!,
});
client.defineJob({
  id: "openai-job",
  name: "OpenAI Job",
  version: "1.0.0",
  trigger: invokeTrigger(),
  integrations: {
    openai, // Add the OpenAI client as an integration
  },
  run: async (payload, io, ctx) => {
    // Now you can access it through the io object
    const completion = await io.openai.chat.completions.create("completion", {
      model: "gpt-3.5-turbo",
      messages: [
        {
          role: "user",
          content: "Create a good programming joke about background jobs",
        },
      ],
    });
  },
});
Would become in v3:
import OpenAI from "openai";
const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});
export const openaiJob = task({
  id: "openai-job",
  run: async (payload) => {
    const completion = await openai.chat.completions.create(
      {
        model: "gpt-3.5-turbo",
        messages: [
          {
            role: "user",
            content: "Create a good programming joke about background jobs",
          },
        ],
    });
  },
});
So don’t use the @trigger.dev/openai package in v3, use the official OpenAI SDK. Bear in mind that the syntax for the latest official SDK will probably be different from the @trigger.dev integration SDK. You will need to adapt the code accordingly. 3. The most critical difference is that inside the run function you do NOT need to wrap everything in io.runTask. So anything inside there can be extracted out and be used in the main body of the function without wrapping it. 4. The import for task in v3 is import { task } from "@trigger.dev/sdk"; 5. You can trigger jobs from other jobs. In v2 this was typically done by either calling io.sendEvent() or by calling yourOtherTask.invoke(). In v3 you call .trigger() on the other task, there are no events in v3. v2:
export const parentJob = client.defineJob({
  id: "parent-job",
  run: async (payload, io) => {
    //send event
    await client.sendEvent({
      name: "user.created",
      payload: { name: "John Doe", email: "[email protected]", paidPlan: true },
    });
    
    //invoke
    await exampleJob.invoke({ foo: "bar" }, {
      idempotencyKey: `some_string_here_${
        payload.someValue
      }_${new Date().toDateString()}`,
    });
  },
});
v3:
export const parentJob = task({
  id: "parent-job",
  run: async (payload) => {
    //trigger
    await userCreated.trigger({ name: "John Doe", email: "[email protected]", paidPlan: true });
    
    //trigger, you can pass in an idempotency key
    await exampleJob.trigger({ foo: "bar" }, {
      idempotencyKey: `some_string_here_${
        payload.someValue
      }_${new Date().toDateString()}`,
    });
  }
});
Can you help me convert the following code from v2 to v3? Please include the full converted code in the answer, do not truncate it anywhere.

OpenAI example comparison

This is a (very contrived) example that does a long OpenAI API call (>10s), stores the result in a database, waits for 5 mins, and then returns the result.

v2

First, the old v2 code, which uses the OpenAI integration. Comments inline:
v2 OpenAI task
import { client } from "~/trigger";
import { eventTrigger } from "@trigger.dev/sdk";

//1. A Trigger.dev integration for OpenAI
import { OpenAI } from "@trigger.dev/openai";
const openai = new OpenAI({
  id: "openai",
  apiKey: process.env["OPENAI_API_KEY"]!,
});

//2. Use the client to define a "Job"
client.defineJob({
  id: "openai-tasks",
  name: "OpenAI Tasks",
  version: "0.0.1",
  trigger: eventTrigger({
    name: "openai.tasks",
    schema: z.object({
      prompt: z.string(),
    }),
  }),
  //3. integrations are added and come through to `io` in the run fn
  integrations: {
    openai,
  },
  run: async (payload, io, ctx) => {
    //4. You use `io` to get the integration
    //5. Also note that "backgroundCreate" was needed for OpenAI
    //   to do work that lasted longer than your serverless timeout
    const chatCompletion = await io.openai.chat.completions.backgroundCreate(
      //6. You needed to add "cacheKeys" to any "task"
      "background-chat-completion",
      {
        messages: [{ role: "user", content: payload.prompt }],
        model: "gpt-3.5-turbo",
      }
    );

    const result = chatCompletion.choices[0]?.message.content;
    if (!result) {
      //7. throwing an error at the top-level in v2 failed the task immediately
      throw new Error("No result from OpenAI");
    }

    //8. io.runTask needed to be used to prevent work from happening twice
    const dbRow = await io.runTask("store-in-db", async (task) => {
      //9. Custom logic can be put here
      //   Anything returned must be JSON-serializable, so no Date objects etc.
      return saveToDb(result);
    });

    //10. Wait for 5 minutes.
    //    You need a cacheKey and the 2nd param is a number
    await io.wait("wait some time", 60 * 5);

    //11. Anything returned must be JSON-serializable, so no Date objects etc.
    return result;
  },
});

v3

In v3 we eliminate a lot of code mainly because we don’t need tricks to try avoid timeouts. Here’s the equivalent v3 code:
v3 OpenAI task
import { logger, task, wait } from "@trigger.dev/sdk";

//1. Official OpenAI SDK
import OpenAI from "openai";
const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

//2. Jobs don't exist now, use "task"
export const openaiTask = task({
  id: "openai-task",
  //3. Retries happen if a task throws an error that isn't caught
  //   The default settings are in your trigger.config.ts (used if not overriden here)
  retry: {
    maxAttempts: 3,
  },
  run: async (payload: { prompt: string }) => {
    //4. Use the official SDK
    //5. No timeouts, so this can take a long time
    const chatCompletion = await openai.chat.completions.create({
      messages: [{ role: "user", content: payload.prompt }],
      model: "gpt-3.5-turbo",
    });

    const result = chatCompletion.choices[0]?.message.content;
    if (!result) {
      //6. throwing an error at the top-level will retry the task (if retries are enabled)
      throw new Error("No result from OpenAI");
    }

    //7. No need to use runTask, just call the function
    const dbRow = await saveToDb(result);

    //8. You can provide seconds, minutes, hours etc.
    //   You don't need cacheKeys in v3
    await wait.for({ minutes: 5 });

    //9. You can return anything that's serializable using SuperJSON
    //   That includes undefined, Date, bigint, RegExp, Set, Map, Error and URL.
    return result;
  },
});

Triggering tasks comparison

v2

In v2 there were different trigger types and triggering each type was slightly different.
v2 triggering
async function yourBackendFunction() {
  //1. for `eventTrigger` you use `client.sendEvent`
  const event = await client.sendEvent({
    name: "openai.tasks",
    payload: { prompt: "Create a good programming joke about background jobs" },
  });

  //2. for `invokeTrigger` you'd call `invoke` on the job
  const { id } = await invocableJob.invoke({
    prompt: "What is the meaning of life?",
  });
}

v3

We’ve unified triggering in v3. You use trigger() or batchTrigger() which you can do on any type of task. Including scheduled, webhooks, etc if you want.
v3 triggering
async function yourBackendFunction() {
  //call `trigger()` on any task
  const handle = await openaiTask.trigger({
    prompt: "Tell me a programming joke",
  });
}

Upgrading your project

  1. Make sure to upgrade all of your trigger.dev packages to v3 first.
npx @trigger.dev/cli@latest update --to 3.0.0
  1. Follow the v3 quick start to get started with v3. Our new CLI will take care of the rest.

Using v2 together with v3

You can use v2 and v3 in the same codebase. This can be useful where you already have v2 jobs or where we don’t support features you need (yet).
We do not support calling v3 tasks from v2 jobs or vice versa.