diff --git a/.changeset/config.json b/.changeset/config.json index 64bfe8e522..5e8716336b 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,11 +1,6 @@ { "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", - "changelog": [ - "@remix-run/changelog-github", - { - "repo": "triggerdotdev/trigger.dev" - } - ], + "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [ [ diff --git a/.changeset/kind-penguins-try.md b/.changeset/kind-penguins-try.md new file mode 100644 index 0000000000..763013956e --- /dev/null +++ b/.changeset/kind-penguins-try.md @@ -0,0 +1,7 @@ +--- +"@trigger.dev/sdk": patch +"@trigger.dev/react": patch +"@trigger.dev/core": patch +--- + +You can create statuses in your Jobs that can then be read using React hooks diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts new file mode 100644 index 0000000000..e547534231 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts @@ -0,0 +1,151 @@ +import type { ActionArgs } from "@remix-run/server-runtime"; +import { json } from "@remix-run/server-runtime"; +import { TaskStatus } from "@trigger.dev/database"; +import { + RunTaskBodyOutput, + RunTaskBodyOutputSchema, + ServerTask, + StatusHistory, + StatusHistorySchema, + StatusUpdate, + StatusUpdateData, + StatusUpdateSchema, + StatusUpdateState, +} from "@trigger.dev/core"; +import { z } from "zod"; +import { $transaction, PrismaClient, prisma } from "~/db.server"; +import { taskWithAttemptsToServerTask } from "~/models/task.server"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; +import { logger } from "~/services/logger.server"; +import { ulid } from "~/services/ulid.server"; +import { workerQueue } from "~/services/worker.server"; +import { JobRunStatusRecordSchema } from "@trigger.dev/core"; + +const ParamsSchema = z.object({ + runId: z.string(), + id: z.string(), +}); + +export async function action({ request, params }: ActionArgs) { + // Ensure this is a POST request + if (request.method.toUpperCase() !== "PUT") { + return { status: 405, body: "Method Not Allowed" }; + } + + // Next authenticate the request + const authenticationResult = await authenticateApiRequest(request); + + if (!authenticationResult) { + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const { runId, id } = ParamsSchema.parse(params); + + // Now parse the request body + const anyBody = await request.json(); + + logger.debug("SetStatusService.call() request body", { + body: anyBody, + runId, + id, + }); + + const body = StatusUpdateSchema.safeParse(anyBody); + + if (!body.success) { + return json({ error: "Invalid request body" }, { status: 400 }); + } + + const service = new SetStatusService(); + + try { + const statusRecord = await service.call(runId, id, body.data); + + logger.debug("SetStatusService.call() response body", { + runId, + id, + statusRecord, + }); + + if (!statusRecord) { + return json({ error: "Something went wrong" }, { status: 500 }); + } + + const status = JobRunStatusRecordSchema.parse({ + ...statusRecord, + state: statusRecord.state ?? undefined, + history: statusRecord.history ?? undefined, + data: statusRecord.data ?? undefined, + }); + + return json(status); + } catch (error) { + if (error instanceof Error) { + return json({ error: error.message }, { status: 400 }); + } + + return json({ error: "Something went wrong" }, { status: 500 }); + } +} + +export class SetStatusService { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call(runId: string, id: string, status: StatusUpdate) { + const statusRecord = await $transaction(this.#prismaClient, async (tx) => { + const existingStatus = await tx.jobRunStatusRecord.findUnique({ + where: { + runId_key: { + runId, + key: id, + }, + }, + }); + + const history: StatusHistory = []; + const historyResult = StatusHistorySchema.safeParse(existingStatus?.history); + if (historyResult.success) { + history.push(...historyResult.data); + } + if (existingStatus) { + history.push({ + label: existingStatus.label, + state: (existingStatus.state ?? undefined) as StatusUpdateState, + data: (existingStatus.data ?? undefined) as StatusUpdateData, + }); + } + + const updatedStatus = await tx.jobRunStatusRecord.upsert({ + where: { + runId_key: { + runId, + key: id, + }, + }, + create: { + key: id, + runId, + //this shouldn't ever use the id in reality, as the SDK makess it compulsory on the first call + label: status.label ?? id, + state: status.state, + data: status.data as any, + history: [], + }, + update: { + label: status.label, + state: status.state, + data: status.data as any, + history: history as any[], + }, + }); + + return updatedStatus; + }); + + return statusRecord; + } +} diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts new file mode 100644 index 0000000000..f7e6351de2 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.ts @@ -0,0 +1,82 @@ +import type { LoaderArgs } from "@remix-run/server-runtime"; +import { json } from "@remix-run/server-runtime"; +import { JobRunStatusRecordSchema } from "@trigger.dev/core"; +import { z } from "zod"; +import { prisma } from "~/db.server"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; +import { logger } from "~/services/logger.server"; +import { apiCors } from "~/utils/apiCors"; + +const ParamsSchema = z.object({ + runId: z.string(), +}); + +const RecordsSchema = z.array(JobRunStatusRecordSchema); + +export async function loader({ request, params }: LoaderArgs) { + if (request.method.toUpperCase() === "OPTIONS") { + return apiCors(request, json({})); + } + + // Next authenticate the request + const authenticationResult = await authenticateApiRequest(request, { allowPublicKey: true }); + + if (!authenticationResult) { + return apiCors(request, json({ error: "Invalid or Missing API key" }, { status: 401 })); + } + + const { runId } = ParamsSchema.parse(params); + + logger.debug("Get run statuses", { + runId, + }); + + try { + const run = await prisma.jobRun.findUnique({ + where: { + id: runId, + }, + select: { + id: true, + status: true, + output: true, + statuses: { + orderBy: { + createdAt: "asc", + }, + }, + }, + }); + + if (!run) { + return apiCors(request, json({ error: `No run found for id ${runId}` }, { status: 404 })); + } + + const parsedStatuses = RecordsSchema.parse( + run.statuses.map((s) => ({ + ...s, + state: s.state ?? undefined, + data: s.data ?? undefined, + history: s.history ?? undefined, + })) + ); + + return apiCors( + request, + json({ + run: { + id: run.id, + status: run.status, + output: run.output, + }, + statuses: parsedStatuses, + }) + ); + } catch (error) { + if (error instanceof Error) { + return apiCors(request, json({ error: error.message }, { status: 400 })); + } + + return apiCors(request, json({ error: "Something went wrong" }, { status: 500 })); + } +} diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.ts b/apps/webapp/app/routes/api.v1.runs.$runId.ts index 973d71f787..e1273654f8 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.ts @@ -1,9 +1,8 @@ import type { LoaderArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; -import { cors } from "remix-utils"; import { z } from "zod"; import { prisma } from "~/db.server"; -import { authenticateApiRequest, getApiKeyFromRequest } from "~/services/apiAuth.server"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; import { apiCors } from "~/utils/apiCors"; import { taskListToTree } from "~/utils/taskListToTree"; @@ -93,6 +92,9 @@ export async function loader({ request, params }: LoaderArgs) { } : undefined, }, + statuses: { + select: { key: true, label: true, state: true, data: true, history: true }, + }, }, }); @@ -122,6 +124,12 @@ export async function loader({ request, params }: LoaderArgs) { const { parentId, ...rest } = task; return { ...rest }; }), + statuses: jobRun.statuses.map((s) => ({ + ...s, + state: s.state ?? undefined, + data: s.data ?? undefined, + history: s.history ?? undefined, + })), nextCursor: nextTask ? nextTask.id : undefined, }) ); diff --git a/docs/_snippets/how-to-get-run-id.mdx b/docs/_snippets/how-to-get-run-id.mdx new file mode 100644 index 0000000000..8d5d372431 --- /dev/null +++ b/docs/_snippets/how-to-get-run-id.mdx @@ -0,0 +1,5 @@ + + You can call [client.getRuns()](/sdk/triggerclient/instancemethods/getruns) with a Job id to get a + list of the most recent Runs for that Job. You can then pass that run id to your frontend to use + in the hook. + diff --git a/docs/_snippets/react-hook-types.mdx b/docs/_snippets/react-hook-types.mdx new file mode 100644 index 0000000000..03608f73d1 --- /dev/null +++ b/docs/_snippets/react-hook-types.mdx @@ -0,0 +1,24 @@ +## The two types of Run progress you can use + +1. Automatic updates of Run and Task progress (no extra Job code required) +2. Explicitly created and updated `statuses` (more flexible and powerful) + +### Automatic updates + +These require no changes inside your Job code. You can receive: + +- Info about an event you sent, including the Runs it triggered. +- The overall status of the Run (in progress, success and fail statuses). +- Metadata like start and completed times. +- The Run output (what is returned or an error that failed the Job) +- Information about the Tasks that have completed/failed/are running. + +### Explicit `statuses` + +You can create `statuses` in your Job code. This gives you fine grained control over what you want to expose. + +It allows you to: + +- Show exactly what you want in your UI (with as many statuses as you want). +- Pass arbitrary data to your UI, which you can use to render elements. +- Update existing elements in your UI as the progress of the run continues. diff --git a/docs/documentation/guides/react-hooks-automatic.mdx b/docs/documentation/guides/react-hooks-automatic.mdx new file mode 100644 index 0000000000..d097442556 --- /dev/null +++ b/docs/documentation/guides/react-hooks-automatic.mdx @@ -0,0 +1,140 @@ +--- +title: "Automatic React hooks" +description: "These allow you to show Run and Task progress without adding extra code to your Jobs." +--- + +## The data you can receive + +- Info about an event you sent, including the Runs it triggered. +- The overall status of the Run (in progress, success and fail statuses). +- Metadata like start and completed times. +- The Run output (what is returned or an error that failed the Job) +- Information about the Tasks that have completed/failed/are running. + +## The hooks + +- [useEventDetails](/sdk/react/useeventdetails): get the details of a specific event +- [useRunDetails](/sdk/react/userundetails): get the details of a specific Run +- [useEventRunDetails](/sdk/react/useeventrundetails): get the details of a Run triggered from a specific event + +All of these hooks will automatically refresh your components as the state of your Runs or events change. + +#### useEventDetails + +The `useEventDetails` hook will get the details of a specific event. You can use this to show the status of a specific event. + + + +This component will show the details of an event and the overall status of Runs that were triggered by the event: + +```tsx +import { useEventDetails } from "@trigger.dev/react"; + +export default function EventDetails({ eventId }: { eventId: string }) { + const { data, error } = useEventDetails(eventId); + + if (error) { + return
Error: {error.message}
; + } + + if (!data) { + return
Loading...
; + } + + return ( +
+

{data.name}

+

Runs: {data.runs?.length}

+
+ {data.runs?.map((run) => ( +
+

+ Run {run.id}: {run.status} +

+
+ ))} +
+
+ ); +} +``` + +#### useRunDetails + +The `useRunDetails` hook will get the details of a specific Run. You can use this to show the status of a specific Run. + + + +This component will show the details of a Run and the status of each task in the Run: + +```tsx +import { useRunDetails } from "@trigger.dev/react"; + +export default function RunDetails({ runId }: { runId: string }) { + const { data, error } = useRunDetails(runId); + + if (error) { + return
Error: {error.message}
; + } + + if (!data) { + return
Loading...
; + } + + return ( +
+

Run {data.id}

+

Status: {data.status}

+
+ {data.tasks?.map((task) => ( +
+

+ Task {task.id}: {task.status} +

+
+ ))} +
+
+ ); +} +``` + +#### useEventRunDetails + +The `useEventRunDetails` hook will get the details of a specific Run that was triggered by a specific event. You can use this to show the status of a specific Run. + + + +This component will show the details of a Run and the status of each task in the Run: + +```tsx +import { useEventRunDetails } from "@trigger.dev/react"; + +export default function EventRunDetails({ eventId }: { eventId: string }) { + const { data, error } = useEventRunDetails(eventId); + + if (error) { + return
Error: {error.message}
; + } + + if (!data) { + return
Loading...
; + } + + return ( +
+

Run {data.id}

+

Status: {data.status}

+
+ {data.tasks?.map((task) => ( +
+

+ Task {task.id}: {task.status} +

+
+ ))} +
+
+ ); +} +``` diff --git a/docs/documentation/guides/react-hooks-statuses.mdx b/docs/documentation/guides/react-hooks-statuses.mdx new file mode 100644 index 0000000000..eb0bb08f34 --- /dev/null +++ b/docs/documentation/guides/react-hooks-statuses.mdx @@ -0,0 +1,192 @@ +--- +title: "Explicit status hooks" +description: "How to add `statuses` to your Job code and then subscribe using the hooks" +--- + +You can create `statuses` in your Job code. This gives you fine grained control over what you want to expose. + +It allows you to: + +- Show exactly what you want in your UI (with as many statuses as you want). +- Pass arbitrary data to your UI, which you can use to render elements. +- Update existing elements in your UI as the progress of the run continues. + +## In your Job code + +You should create a "status", which you can then update throughout the Job if you'd like to. You can create more than one status. Each of these will come through to the hook with the latest status and the history for each. + +```ts jobs/yourjob.ts +//your job +client.defineJob({ + id: "meme-generator", + name: "Generate memes", + version: "0.1.1", + trigger: eventTrigger({ + name: "generate-memes", + }), + run: async (payload, io, ctx) => { + //create a status "generating-memes" + //you give it the starting state. Only label is required + const generatingMemes = await io.createStatus("generating-memes", { + //the label is compulsory on this first call + label: "Generating memes", + //state is optional + state: "loading", + //data is an optional object. the values can be any type that is JSON serializable + data: { + progress: 0.1, + }, + }); + + //...do stuff, like generate memes + + //update the generatingMemes status. + //anything set here will override the previous values, but you'll be able to view the full history with hooks + await generatingMemes.update("middle-generation", { + //label isn't specified so will remain the same + //state will be updated to "success" + state: "success", + //set data, this overrides the previous value + data: { + progress: 1, + urls: [ + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnZoMndsdWh0MmhvY2kyaDF6YjZjZzg1ZGsxdnhhYm13a3Q1Y3lkbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + ], + }, + }); + }, +}); +``` + +In this case we created just a single status `generating-memes` and then updated it. It's worth noting that you can create as many statuses as you'd like in a single Job and you can update them as many times as you want. + +This allows you to fine-grained control over how you report progress and output data from your Job. + +## Using the React hooks + +There are two hooks you can use in your UI to display the Run statuses. + +- **useEventRunStatuses**: get the statuses of a run that was triggered by a specified event +- **useRunStatuses**: get the statuses of the specified run + +## useEventRunStatuses + +The `useEventRunStatuses` hook will give you the statuses and overview data of **the first run** that is triggered by an event. + + + +This component will show the details of a Run and the status of each task in the Run: + +```ts +import { useEventRunStatuses } from "@trigger.dev/react"; + +export function EventRunData({ id }: { id: string }) { + const { fetchStatus, error, statuses, run } = useEventRunStatuses(id); + + if (fetchStatus === "loading") { + return

Loading...

; + } + + if (fetchStatus === "error") { + return ( +
+

{error.name}

+

{error.message}

+
+ ); + } + + return ( + <> + //you receive the overall status of the run, e.g. SUCCESS, FAIL +
Run status: {run.status}
+
+ {statuses.map((status) => { + switch (status.key) { + case "generating-memes": { + const urls = status.data?.urls as string[] | undefined; + return ( +
+ // Will display: "Generating memes: loading" +

+ {status.label}: {status.state} +

+ //will render the memes as images + {urls?.map((url) => )} +
+ ); + } + } + })} +
+ //this is what's returned from the run function + {run.output && ( + +
{JSON.stringify(run.output, null, 2)}
+
+ )} + + ); +} +``` + +## useRunStatuses + +The `useRunStatuses` hook will give you the statuses and overview data of a specific Run. + + + +This component will show the details of a Run and the status of each task in the Run: + +```ts +import { useRunStatuses } from "@trigger.dev/react"; + +export function RunData({ id }: { id: string }) { + const { fetchStatus, error, statuses, run } = useRunStatuses(id); + + if (fetchStatus === "loading") { + return

Loading...

; + } + + if (fetchStatus === "error") { + return ( +
+

{error.name}

+

{error.message}

+
+ ); + } + + return ( + <> + //you receive the overall status of the run, e.g. SUCCESS, FAIL +
Run status: {run.status}
+
+ {statuses.map((status) => { + switch (status.key) { + case "generating-memes": { + const urls = status.data?.urls as string[] | undefined; + return ( +
+ // Will display: "Generating memes: loading" +

+ {status.label}: {status.state} +

+ //will render the memes as images + {urls?.map((url) => )} +
+ ); + } + } + })} +
+ //this is what's returned from the run function + {run.output && ( + +
{JSON.stringify(run.output, null, 2)}
+
+ )} + + ); +} +``` diff --git a/docs/documentation/guides/react-hooks.mdx b/docs/documentation/guides/react-hooks.mdx index 5df366aa87..cebb7e7a0d 100644 --- a/docs/documentation/guides/react-hooks.mdx +++ b/docs/documentation/guides/react-hooks.mdx @@ -1,5 +1,5 @@ --- -title: "React hooks" +title: "Overview" description: "How to show the live status of Job Runs in your React app" --- @@ -9,207 +9,96 @@ You can display the live progress of a Job Run to your users, including the stat -## Steps +## Setting up your project for hooks This guide assumes that your project is already setup and you have a Job running. If not, you should follow the [quick start guide](/documentation/quickstart) first. -### 1. Install the package + + + Add the `@trigger.dev/react` package to your project: -Add the `@trigger.dev/react` package to your project: + - + ```bash npm + npm install @trigger.dev/react@latest + ``` -```bash npm -npm install @trigger.dev/react@latest -``` + ```bash pnpm + pnpm install @trigger.dev/react@latest + ``` -```bash pnpm -pnpm install @trigger.dev/react@latest -``` + ```bash yarn + yarn add @trigger.dev/react@latest + ``` -```bash yarn -yarn add @trigger.dev/react@latest -``` + - + + + In the Trigger.dev dashboard you should go to your Project and then the "Environments & API Keys" page. -### 2. Get your public API key + ![Get your public API Key](/images/environments-public-apikey.png) -In the Trigger.dev dashboard you should go to your Project and then the "Environments & API Keys" page. + You should copy the `PUBLIC` API key for the dev environment. -![Get your public API Key](/images/environments-public-apikey.png) + + A public API key is a key that can be used in the browser. It can only be used to read certain + data from the API and can not write data. This means that it can be used to get the status of a + Job Run, but not to start a new Job Run. + -You should copy the `PUBLIC` API key for the dev environment. + + + Add the `NEXT_PUBLIC_TRIGGER_API_KEY` environment variable to your project. This will be used by the `TriggerProvider` component to connect to the Trigger API. - - A public API key is a key that can be used in the browser. It can only be used to read certain - data from the API and can not write data. This means that it can be used to get the status of a - Job Run, but not to start a new Job Run. - + ```sh .env.local + #... + TRIGGER_API_KEY=[your_private_api_key] + NEXT_PUBLIC_TRIGGER_API_KEY=[your_public_api_key] + #... + ``` -### 3. Add the env var to your project + Your private API key should already be in there. -Add the `NEXT_PUBLIC_TRIGGER_API_KEY` environment variable to your project. This will be used by the `TriggerProvider` component to connect to the Trigger API. + -```sh .env.local -#... -TRIGGER_API_KEY=[your_private_api_key] -NEXT_PUBLIC_TRIGGER_API_KEY=[your_public_api_key] -#... -``` + + The [TriggerProvider](/sdk/react/triggerprovider) component is a React Context Provider that will make the Trigger API client available to all child components. -Your private API key should already be in there. + Generally you'll want to add this to the root of your app, so that it's available everywhere. However, you can add it lower in the hierarchy but it must be above any of the hooks. -### 4. Add the `TriggerProvider` component + ```tsx app/layout.tsx + export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + + ); + } + ``` -The [TriggerProvider](/sdk/react/triggerprovider) component is a React Context Provider that will make the Trigger API client available to all child components. + + -Generally you'll want to add this to the root of your app, so that it's available everywhere. However, you can add it lower in the hierarchy but it must be above any of the hooks. +## Two ways to report Run progress -```tsx app/layout.tsx -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - - {children} - - - - ); -} -``` +**Automatic progress** – without writing additional code in your Job you can get updates on the overall run status and individual tasks inside the run. -### 5. Add hooks to your components +**Explicit status** – add code to your Job that reports the status of the things you're doing. This gives you full flexibility for displaying progress in your UI. -There are three hooks that you can use to show statuses: - -- [useEventDetails](/sdk/react/useeventdetails): get the details of a specific event -- [useRunDetails](/sdk/react/userundetails): get the details of a specific Run -- [useEventRunDetails](/sdk/react/useeventrundetails): get the details of a Run triggered from a specific event - -All of these hooks will automatically refresh your components as the state of your Runs or events change. - -#### 5.a useEventDetails - -The `useEventDetails` hook will get the details of a specific event. You can use this to show the status of a specific event. - - - -This component will show the details of an event and the overall status of Runs that were triggered by the event: - -```tsx -import { useEventDetails } from "@trigger.dev/react"; - -export default function EventDetails({ eventId }: { eventId: string }) { - const { data, error } = useEventDetails(eventId); - - if (error) { - return
Error: {error.message}
; - } - - if (!data) { - return
Loading...
; - } - - return ( -
-

{data.name}

-

Runs: {data.runs?.length}

-
- {data.runs?.map((run) => ( -
-

- Run {run.id}: {run.status} -

-
- ))} -
-
- ); -} -``` - -#### 5.b useRunDetails - -The `useRunDetails` hook will get the details of a specific Run. You can use this to show the status of a specific Run. - - - You can call [client.getRuns()](/sdk/triggerclient/instancemethods/getruns) with a Job id to get a - list of the most recent Runs for that Job. You can then pass that run id to your frontend to use - in the hook. - - -This component will show the details of a Run and the status of each task in the Run: - -```tsx -import { useRunDetails } from "@trigger.dev/react"; - -export default function RunDetails({ runId }: { runId: string }) { - const { data, error } = useRunDetails(runId); - - if (error) { - return
Error: {error.message}
; - } - - if (!data) { - return
Loading...
; - } - - return ( -
-

Run {data.id}

-

Status: {data.status}

-
- {data.tasks?.map((task) => ( -
-

- Task {task.id}: {task.status} -

-
- ))} -
-
- ); -} -``` - -#### 5.c useEventRunDetails - -The `useEventRunDetails` hook will get the details of a specific Run that was triggered by a specific event. You can use this to show the status of a specific Run. - - - -This component will show the details of a Run and the status of each task in the Run: - -```tsx -import { useEventRunDetails } from "@trigger.dev/react"; - -export default function EventRunDetails({ eventId }: { eventId: string }) { - const { data, error } = useEventRunDetails(eventId); - - if (error) { - return
Error: {error.message}
; - } - - if (!data) { - return
Loading...
; - } - - return ( -
-

Run {data.id}

-

Status: {data.status}

-
- {data.tasks?.map((task) => ( -
-

- Task {task.id}: {task.status} -

-
- ))} -
-
- ); -} -``` + + + Receive coarse updates without writing additional Job code + + + Add statuses to your Job code for fine-grained UI updates + + diff --git a/docs/mint.json b/docs/mint.json index f877bcf3d6..f8f2b7cf85 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -171,7 +171,14 @@ "documentation/guides/using-integrations-oauth" ] }, - "documentation/guides/react-hooks", + { + "group": "React hooks", + "pages": [ + "documentation/guides/react-hooks", + "documentation/guides/react-hooks-automatic", + "documentation/guides/react-hooks-statuses" + ] + }, { "group": "Deployment", "pages": [ diff --git a/packages/core/src/schemas/api.ts b/packages/core/src/schemas/api.ts index 112c33191a..b59f23be76 100644 --- a/packages/core/src/schemas/api.ts +++ b/packages/core/src/schemas/api.ts @@ -15,6 +15,8 @@ import { } from "./schedules"; import { CachedTaskSchema, TaskSchema } from "./tasks"; import { EventSpecificationSchema, TriggerMetadataSchema } from "./triggers"; +import { RunStatusSchema } from "./runs"; +import { JobRunStatusRecordSchema } from "./statuses"; export const UpdateTriggerSourceBodyV1Schema = z.object({ registeredEvents: z.array(z.string()), @@ -729,3 +731,9 @@ export const CreateExternalConnectionBodySchema = z.object({ }); export type CreateExternalConnectionBody = z.infer; + +export const GetRunStatusesSchema = z.object({ + run: z.object({ id: z.string(), status: RunStatusSchema, output: z.any().optional() }), + statuses: z.array(JobRunStatusRecordSchema), +}); +export type GetRunStatuses = z.infer; diff --git a/packages/core/src/schemas/index.ts b/packages/core/src/schemas/index.ts index 3ad2573181..09f3a15c05 100644 --- a/packages/core/src/schemas/index.ts +++ b/packages/core/src/schemas/index.ts @@ -12,3 +12,4 @@ export * from "./fetch"; export * from "./events"; export * from "./runs"; export * from "./addMissingVersionField"; +export * from "./statuses"; diff --git a/packages/core/src/schemas/runs.ts b/packages/core/src/schemas/runs.ts index 31f9bde8b1..e22b964535 100644 --- a/packages/core/src/schemas/runs.ts +++ b/packages/core/src/schemas/runs.ts @@ -1,5 +1,6 @@ import { ZodObject, z } from "zod"; import { TaskStatusSchema } from "./tasks"; +import { JobRunStatusRecordSchema } from "./statuses"; export const RunStatusSchema = z.union([ z.literal("PENDING"), @@ -78,6 +79,8 @@ export const GetRunSchema = RunSchema.extend({ output: z.any().optional(), /** The tasks from the run */ tasks: z.array(RunTaskWithSubtasksSchema), + /** Any status updates that were published from the run */ + statuses: z.array(JobRunStatusRecordSchema).default([]), /** If there are more tasks, you can use this to get them */ nextCursor: z.string().optional(), }); diff --git a/packages/core/src/schemas/statuses.ts b/packages/core/src/schemas/statuses.ts new file mode 100644 index 0000000000..2216161e16 --- /dev/null +++ b/packages/core/src/schemas/statuses.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import { SerializableJsonSchema } from "./json"; +import { RunStatusSchema } from "./runs"; + +export const StatusUpdateStateSchema = z.union([ + z.literal("loading"), + z.literal("success"), + z.literal("failure"), +]); +export type StatusUpdateState = z.infer; + +const StatusUpdateDataSchema = z.record(SerializableJsonSchema); +export type StatusUpdateData = z.infer; + +export const StatusUpdateSchema = z.object({ + label: z.string().optional(), + state: StatusUpdateStateSchema.optional(), + data: StatusUpdateDataSchema.optional(), +}); +export type StatusUpdate = z.infer; + +const InitalStatusUpdateSchema = StatusUpdateSchema.required({ label: true }); +export type InitialStatusUpdate = z.infer; + +export const StatusHistorySchema = z.array(StatusUpdateSchema); +export type StatusHistory = z.infer; + +export const JobRunStatusRecordSchema = InitalStatusUpdateSchema.extend({ + key: z.string(), + history: StatusHistorySchema, +}); diff --git a/packages/database/prisma/migrations/20230919141017_added_job_run_status_record_table/migration.sql b/packages/database/prisma/migrations/20230919141017_added_job_run_status_record_table/migration.sql new file mode 100644 index 0000000000..dd1eb9b312 --- /dev/null +++ b/packages/database/prisma/migrations/20230919141017_added_job_run_status_record_table/migration.sql @@ -0,0 +1,20 @@ +-- CreateTable +CREATE TABLE "JobRunStatusRecord" ( + "id" TEXT NOT NULL, + "key" TEXT NOT NULL, + "runId" TEXT NOT NULL, + "label" TEXT NOT NULL, + "state" TEXT, + "data" JSONB, + "history" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "JobRunStatusRecord_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "JobRunStatusRecord_runId_key_key" ON "JobRunStatusRecord"("runId", "key"); + +-- AddForeignKey +ALTER TABLE "JobRunStatusRecord" ADD CONSTRAINT "JobRunStatusRecord_runId_fkey" FOREIGN KEY ("runId") REFERENCES "JobRun"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index 2878bc638c..8e36acef9a 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -716,6 +716,7 @@ model JobRun { runConnections RunConnection[] missingConnections MissingConnection[] executions JobRunExecution[] + statuses JobRunStatusRecord[] } enum JobRunStatus { @@ -850,6 +851,25 @@ enum TaskAttemptStatus { ERRORED } +model JobRunStatusRecord { + id String @id @default(cuid()) + key String + + run JobRun @relation(fields: [runId], references: [id], onDelete: Cascade, onUpdate: Cascade) + runId String + + label String + state String? + data Json? + + history Json? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([runId, key]) +} + model SecretReference { id String @id @default(cuid()) key String @unique diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 46718c7f49..47223c19b4 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,3 +1,4 @@ export * from "./TriggerProvider"; export * from "./events"; export * from "./runs"; +export * from "./statuses"; diff --git a/packages/react/src/statuses.ts b/packages/react/src/statuses.ts new file mode 100644 index 0000000000..ec722d6640 --- /dev/null +++ b/packages/react/src/statuses.ts @@ -0,0 +1,108 @@ +"use client"; + +import { UseQueryResult, useQuery } from "@tanstack/react-query"; +import { + GetRun, + GetRunOptions, + GetRunSchema, + GetRunStatuses, + GetRunStatusesSchema, + urlWithSearchParams, +} from "@trigger.dev/core"; +import { useTriggerProvider } from "./TriggerProvider"; +import { zodfetch } from "./fetch"; +import { useEventDetails } from "./events"; +import { runResolvedStatuses } from "./runs"; + +const defaultRefreshInterval = 1000; + +export type RunStatusesOptions = { + /** How often you want to refresh, the default is 1000. Min is 500 */ + refreshIntervalMs?: number; +}; + +export type UseRunStatusesResult = + | { + fetchStatus: "loading"; + error: undefined; + statuses: undefined; + run: undefined; + } + | { + fetchStatus: "error"; + error: Error; + statuses: undefined; + run: undefined; + } + | ({ + fetchStatus: "success"; + error: undefined; + } & GetRunStatuses); + +export function useRunStatuses( + runId: string | undefined, + options?: RunStatusesOptions +): UseRunStatusesResult { + const { apiUrl, publicApiKey, queryClient } = useTriggerProvider(); + + const queryResult = useQuery( + { + queryKey: [`triggerdotdev-run-${runId}`], + queryFn: async () => { + return await zodfetch(GetRunStatusesSchema, `${apiUrl}/api/v1/runs/${runId}/statuses`, { + method: "GET", + headers: { + Authorization: `Bearer ${publicApiKey}`, + }, + }); + }, + enabled: !!runId, + refetchInterval: (data) => { + if (data?.run.status && runResolvedStatuses.includes(data.run.status)) { + return false; + } + if (options?.refreshIntervalMs !== undefined) { + return Math.max(options.refreshIntervalMs, 500); + } + + return defaultRefreshInterval; + }, + }, + queryClient + ); + + switch (queryResult.status) { + case "pending": { + return { + fetchStatus: "loading", + error: undefined, + statuses: undefined, + run: undefined, + }; + } + case "error": { + return { + fetchStatus: "error", + error: queryResult.error, + statuses: undefined, + run: undefined, + }; + } + case "success": { + return { + fetchStatus: "success", + error: undefined, + run: queryResult.data.run, + statuses: queryResult.data.statuses, + }; + } + } +} + +export function useEventRunStatuses( + eventId: string | undefined, + options?: RunStatusesOptions +): UseRunStatusesResult { + const event = useEventDetails(eventId); + return useRunStatuses(event.data?.runs[0]?.id, options); +} diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index 887ec9b5b4..4ab3ef959a 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -4,6 +4,7 @@ "jsx": "react", "moduleResolution": "node", "esModuleInterop": true, + "strict": true, "lib": ["es2015", "dom"], "paths": { "@trigger.dev/core": ["../core/src/index"], diff --git a/packages/trigger-sdk/src/apiClient.ts b/packages/trigger-sdk/src/apiClient.ts index 394284d1fa..f7a4bcb399 100644 --- a/packages/trigger-sdk/src/apiClient.ts +++ b/packages/trigger-sdk/src/apiClient.ts @@ -24,6 +24,9 @@ import { urlWithSearchParams, UpdateTriggerSourceBodyV2, RegisterTriggerBodyV2, + GetRunStatusesSchema, + JobRunStatusRecordSchema, + StatusUpdate, } from "@trigger.dev/core"; import fetch, { type RequestInit } from "node-fetch"; @@ -198,6 +201,28 @@ export class ApiClient { }); } + async updateStatus(runId: string, id: string, status: StatusUpdate) { + const apiKey = await this.#apiKey(); + + this.#logger.debug("Update status", { + id, + status, + }); + + return await zodfetch( + JobRunStatusRecordSchema, + `${this.#apiUrl}/api/v1/runs/${runId}/statuses/${id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify(status), + } + ); + } + async updateSource( client: string, key: string, @@ -359,6 +384,21 @@ export class ApiClient { ); } + async getRunStatuses(runId: string) { + const apiKey = await this.#apiKey(); + + this.#logger.debug("Getting Run statuses", { + runId, + }); + + return await zodfetch(GetRunStatusesSchema, `${this.#apiUrl}/api/v1/runs/${runId}/statuses`, { + method: "GET", + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + } + async getRuns(jobSlug: string, options?: GetRunsOptions) { const apiKey = await this.#apiKey(); diff --git a/packages/trigger-sdk/src/io.ts b/packages/trigger-sdk/src/io.ts index 1e3574e0d1..5e0101528f 100644 --- a/packages/trigger-sdk/src/io.ts +++ b/packages/trigger-sdk/src/io.ts @@ -5,13 +5,13 @@ import { ErrorWithStackSchema, FetchRequestInit, FetchRetryOptions, + InitialStatusUpdate, IntervalOptions, LogLevel, Logger, RunTaskOptions, SendEvent, SendEventOptions, - SerializableJson, SerializableJsonSchema, ServerTask, UpdateTriggerSourceBodyV2, @@ -25,13 +25,14 @@ import { RetryWithTaskError, isTriggerError, } from "./errors"; -import { createIOWithIntegrations } from "./ioWithIntegrations"; import { calculateRetryAt } from "./retry"; import { TriggerClient } from "./triggerClient"; import { DynamicTrigger } from "./triggers/dynamic"; import { ExternalSource, ExternalSourceParams } from "./triggers/externalSource"; import { DynamicSchedule } from "./triggers/scheduled"; import { EventSpecification, TaskLogger, TriggerContext } from "./types"; +import { IntegrationTaskKey } from "./integrations"; +import { TriggerStatus } from "./status"; export type IOTask = ServerTask; @@ -88,6 +89,16 @@ export class IO { this._context = options.context; } + /** @internal */ + get runId() { + return this._id; + } + + /** @internal */ + get triggerClient() { + return this._triggerClient; + } + /** Used to send log messages to the [Run log](https://trigger.dev/docs/documentation/guides/viewing-runs). */ get logger() { return new IOLogger(async (level, message, data) => { @@ -150,6 +161,48 @@ export class IO { }); } + /** `io.createStatus()` allows you to set a status with associated data during the Run. Statuses can be used by your UI using the react package + * @param key Should be a stable and unique key inside the `run()`. See [resumability](https://trigger.dev/docs/documentation/concepts/resumability) for more information. + * @param initialStatus The initial status you want this status to have. You can update it during the rub using the returned object. + * @returns a TriggerStatus object that you can call `update()` on, to update the status. + * @example + * ```ts + * client.defineJob( + //... + run: async (payload, io, ctx) => { + const generatingImages = await io.createStatus("generating-images", { + label: "Generating Images", + state: "loading", + data: { + progress: 0.1, + }, + }); + + //...do stuff + + await generatingImages.update("completed-generation", { + label: "Generated images", + state: "success", + data: { + progress: 1.0, + urls: ["http://..."] + }, + }); + + //... + }); + * ``` + */ + async createStatus( + key: IntegrationTaskKey, + initialStatus: InitialStatusUpdate + ): Promise { + const id = typeof key === "string" ? key : key.join("-"); + const status = new TriggerStatus(id, this); + await status.update(key, initialStatus); + return status; + } + /** `io.backgroundFetch()` fetches data from a URL that can take longer that the serverless timeout. The actual `fetch` request is performed on the Trigger.dev platform, and the response is sent back to you. * @param key Should be a stable and unique key inside the `run()`. See [resumability](https://trigger.dev/docs/documentation/concepts/resumability) for more information. * @param url The URL to fetch from. diff --git a/packages/trigger-sdk/src/status.ts b/packages/trigger-sdk/src/status.ts new file mode 100644 index 0000000000..ff249604a5 --- /dev/null +++ b/packages/trigger-sdk/src/status.ts @@ -0,0 +1,44 @@ +import { DisplayProperty, StatusUpdate } from "@trigger.dev/core/schemas"; +import { IntegrationTaskKey } from "./integrations"; +import { IO } from "./io"; +import { TriggerClient } from "./triggerClient"; + +export class TriggerStatus { + constructor( + private id: string, + private io: IO + ) {} + + async update(key: IntegrationTaskKey, status: StatusUpdate) { + const properties: DisplayProperty[] = []; + + if (status.label) { + properties.push({ + label: "Label", + text: status.label, + }); + } + + if (status.state) { + properties.push({ + label: "State", + text: status.state, + }); + } + + return await this.io.runTask( + key, + async (task) => { + return await this.io.triggerClient.updateStatus(this.io.runId, this.id, status); + }, + { + name: status.label ?? `Status update`, + icon: "clock", + params: { + ...status, + }, + properties, + } + ); + } +} diff --git a/packages/trigger-sdk/src/triggerClient.ts b/packages/trigger-sdk/src/triggerClient.ts index 6c2ffaf3df..d193c53d3f 100644 --- a/packages/trigger-sdk/src/triggerClient.ts +++ b/packages/trigger-sdk/src/triggerClient.ts @@ -26,6 +26,7 @@ import { SendEvent, SendEventOptions, SourceMetadataV2, + StatusUpdate, } from "@trigger.dev/core"; import { ApiClient } from "./apiClient"; import { CanceledWithTaskError, ResumeWithTaskError, RetryWithTaskError } from "./errors"; @@ -567,6 +568,10 @@ export class TriggerClient { return this.#client.cancelEvent(eventId); } + async updateStatus(runId: string, id: string, status: StatusUpdate) { + return this.#client.updateStatus(runId, id, status); + } + async registerSchedule(id: string, key: string, schedule: ScheduleMetadata) { return this.#client.registerSchedule(this.id, id, key, schedule); } @@ -587,6 +592,10 @@ export class TriggerClient { return this.#client.getRuns(jobSlug, options); } + async getRunStatuses(runId: string) { + return this.#client.getRunStatuses(runId); + } + authorized( apiKey?: string | null ): "authorized" | "unauthorized" | "missing-client" | "missing-header" { diff --git a/packages/trigger-sdk/tsconfig.json b/packages/trigger-sdk/tsconfig.json index e5b64c11cd..7b13d85e2b 100644 --- a/packages/trigger-sdk/tsconfig.json +++ b/packages/trigger-sdk/tsconfig.json @@ -10,7 +10,8 @@ }, "lib": ["DOM", "DOM.Iterable"], "declaration": false, - "declarationMap": false + "declarationMap": false, + "stripInternal": true }, "exclude": ["node_modules"] } diff --git a/references/job-catalog/package.json b/references/job-catalog/package.json index 37e18f34be..7d77bf5952 100644 --- a/references/job-catalog/package.json +++ b/references/job-catalog/package.json @@ -21,6 +21,7 @@ "dynamic-schedule": "nodemon --watch src/dynamic-schedule.ts -r tsconfig-paths/register -r dotenv/config src/dynamic-schedule.ts", "dynamic-triggers": "nodemon --watch src/dynamic-triggers.ts -r tsconfig-paths/register -r dotenv/config src/dynamic-triggers.ts", "background-fetch": "nodemon --watch src/background-fetch.ts -r tsconfig-paths/register -r dotenv/config src/background-fetch.ts", + "status": "nodemon --watch src/status.ts -r tsconfig-paths/register -r dotenv/config src/status.ts", "dev:trigger": "trigger-cli dev --port 8080" }, "dependencies": { @@ -52,4 +53,4 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^3.14.1" } -} \ No newline at end of file +} diff --git a/references/job-catalog/src/status.ts b/references/job-catalog/src/status.ts new file mode 100644 index 0000000000..bf70a63c5f --- /dev/null +++ b/references/job-catalog/src/status.ts @@ -0,0 +1,61 @@ +import { createExpressServer } from "@trigger.dev/express"; +import { TriggerClient, eventTrigger } from "@trigger.dev/sdk"; +import { z } from "zod"; + +export const client = new TriggerClient({ + id: "job-catalog", + apiKey: process.env["TRIGGER_API_KEY"], + apiUrl: process.env["TRIGGER_API_URL"], + verbose: false, + ioLogLocalEnabled: true, +}); + +client.defineJob({ + id: "status", + name: "Status: updating status", + version: "0.0.2", + trigger: eventTrigger({ + name: "status", + }), + run: async (payload, io, ctx) => { + const generatingText = await io.createStatus("generating-images", { + label: "Generating Images", + state: "loading", + data: { + progress: 0.1, + }, + }); + + await io.wait("wait", 2); + + //...do stuff + await generatingText.update("middle-generation", { + data: { + progress: 0.5, + urls: ["http://www."], + }, + }); + + await io.wait("wait-again", 2); + + //...do stuff + await generatingText.update("completed-generation", { + label: "Generated images", + state: "success", + data: { + progress: 1.0, + urls: ["http://www.", "http://www.", "http://www."], + }, + }); + + await io.runTask("get statuses", async () => { + return await client.getRunStatuses(ctx.run.id); + }); + + await io.runTask("get run", async () => { + return await client.getRun(ctx.run.id); + }); + }, +}); + +createExpressServer(client); diff --git a/references/nextjs-reference/src/app/api/trigger/route.ts b/references/nextjs-reference/src/app/api/trigger/route.ts index f72f543aed..acc3ef2863 100644 --- a/references/nextjs-reference/src/app/api/trigger/route.ts +++ b/references/nextjs-reference/src/app/api/trigger/route.ts @@ -1,19 +1,6 @@ import { createAppRoute } from "@trigger.dev/nextjs"; import { client } from "@/trigger"; -import "@/jobs/events"; -import "@/jobs/general"; -import "@/jobs/github"; -import "@/jobs/logging"; -import "@/jobs/openai"; -import "@/jobs/plain"; -import "@/jobs/resend"; -import "@/jobs/schedules"; -import "@/jobs/slack"; -import "@/jobs/typeform"; -import "@/jobs/edgeCases"; import "@/jobs/hooks"; -import "@/jobs/supabase"; -import "@/jobs/stripe"; export const { POST, dynamic } = createAppRoute(client); diff --git a/references/nextjs-reference/src/components/RunDetails.tsx b/references/nextjs-reference/src/components/RunDetails.tsx index d6a83dbf1f..a0f85bf444 100644 --- a/references/nextjs-reference/src/components/RunDetails.tsx +++ b/references/nextjs-reference/src/components/RunDetails.tsx @@ -1,89 +1,51 @@ "use client"; -import styles from "@/styles/Home.module.css"; -import { useEventDetails, useEventRunDetails, useRunDetails } from "@trigger.dev/react"; - -export function EventData({ id }: { id: string }) { - const { isLoading, data, error } = useEventDetails(id); - - return ( - <> -

Event

- {isLoading ? ( -

Loading

- ) : error ? ( - JSON.stringify(error, null, 2) - ) : data ? ( -

- Event ID: {data.id} -

- ) : ( -

- )} - - ); -} - -export function RunData({ id }: { id: string }) { - const { isLoading, isError, data, error } = useRunDetails(id); - - if (isLoading) { - return

Loading...

; - } - - if (isError) { - return

Error

; - } - - if (!data) { - return

Loading...

; - } - - return ( - <> -
Run status: {data.status}
-
- {data.tasks?.map((task) => ( -
-

{task.name}

-

Status: {task.status}

-
- ))} -
- - ); -} +import { useEventRunStatuses } from "@trigger.dev/react"; export function EventRunData({ id }: { id: string }) { - const { isLoading, isError, data, error } = useEventRunDetails(id); + const { fetchStatus, error, statuses, run } = useEventRunStatuses(id); - if (isLoading) { + if (fetchStatus === "loading") { return

Loading...

; } - if (isError) { - return

Error

; - } - - if (!data) { - return

Loading...

; + if (fetchStatus === "error") { + return ( +
+

{error.name}

+

{error.message}

+
+ ); } return ( <> -
Run status: {data.status}
+
Run status: {run.status}
- {data.tasks?.map((task) => ( -
-

{task.displayKey ?? task.name}

-

{task.icon}

-

Status: {task.status}

-
- ))} + {statuses.map((status) => { + switch (status.key) { + case "getting-input-data": { + return ( +
+

{status.label}

+

Status: {status.state}

+
+ ); + } + case "generating-memes": { + const urls = status.data?.urls as string[] | undefined; + return ( +
+ {urls?.map((url) => )} +
+ ); + } + } + })}
- {data.output && ( + {run.output && ( -
{JSON.stringify(data.output, null, 2)}
+
{JSON.stringify(run.output, null, 2)}
)} diff --git a/references/nextjs-reference/src/jobs/edgeCases.ts b/references/nextjs-reference/src/jobs/edgeCases.ts deleted file mode 100644 index cd7c60b23e..0000000000 --- a/references/nextjs-reference/src/jobs/edgeCases.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { client } from "@/trigger"; -import { Job, eventTrigger } from "@trigger.dev/sdk"; -import { z } from "zod"; - -client.defineJob({ - id: "test-long-running-cpu", - name: "Test long running CPU", - version: "0.0.1", - trigger: eventTrigger({ - name: "test.cpu", - schema: z.object({ - iterations: z.number(), - sleepDuration: z.number(), - }), - }), - run: async (payload, io, ctx) => { - console.log(`Running run ${ctx.run.id} at ${new Date().toISOString()}`); - - for (let i = 0; i < payload.iterations ?? 1; i++) { - await new Promise((resolve) => setTimeout(resolve, payload.sleepDuration ?? 1000)); - } - - console.log(`Finishing run ${ctx.run.id} at ${new Date().toISOString()}`); - }, -}); diff --git a/references/nextjs-reference/src/jobs/events.ts b/references/nextjs-reference/src/jobs/events.ts deleted file mode 100644 index 4ef4071864..0000000000 --- a/references/nextjs-reference/src/jobs/events.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { client } from "@/trigger"; -import { Job, eventTrigger } from "@trigger.dev/sdk"; -import { z } from "zod"; - -client.defineJob({ - id: "test-event-trigger-1", - name: "Test Event Trigger 1", - version: "0.0.1", - logLevel: "debug", - trigger: eventTrigger({ - name: "test-event-trigger-1", - schema: z.object({ - name: z.string(), - payload: z.any(), - }), - }), - run: async (payload, io, ctx) => { - await io.sendEvent( - "send", - { - name: payload.name, - payload: payload.payload, - timestamp: new Date(), - }, - { deliverAt: new Date(Date.now() + 1000 * 30) } - ); - }, -}); - -client.defineJob({ - id: "test-event-trigger-2", - name: "Test Event Trigger 2", - version: "0.0.1", - logLevel: "debug", - trigger: eventTrigger({ - name: "test-event-trigger-2", - }), - run: async (payload, io, ctx) => { - for (let index = 0; index < 100; index++) { - await io.sendEvent(`send-${index}`, { - name: "test-event-trigger-1", - payload: { name: "whatever", payload: { index } }, - }); - } - }, -}); - -client.defineJob({ - id: "test-multiple-events", - name: "Test Multiple Events", - version: "0.0.1", - logLevel: "debug", - trigger: eventTrigger({ - name: ["test.event.1", "test.event.2"], - examples: [{ id: "test", name: "Test", payload: { name: "test" } }], - }), - run: async (payload, io, ctx) => { - await io.logger.log(`Triggered by the ${ctx.event.name} event`, { ctx }); - }, -}); diff --git a/references/nextjs-reference/src/jobs/general.ts b/references/nextjs-reference/src/jobs/general.ts deleted file mode 100644 index a35786bf9f..0000000000 --- a/references/nextjs-reference/src/jobs/general.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { client, github, githubUser, openai, slack } from "@/trigger"; -import { events } from "@trigger.dev/github"; -import { - Job, - cronTrigger, - eventTrigger, - intervalTrigger, - isTriggerError, - missingConnectionNotification, - missingConnectionResolvedNotification, -} from "@trigger.dev/sdk"; -import { z } from "zod"; - -const enabled = true; - -client.defineJob({ - id: "on-missing-auth-connection", - name: "On missing auth connection", - version: "0.1.1", - enabled, - trigger: missingConnectionNotification([githubUser]), - integrations: { - slack, - }, - run: async (payload, io, ctx) => { - switch (payload.type) { - case "DEVELOPER": { - return await io.slack.postMessage("message", { - text: `Missing developer connection: ${JSON.stringify(payload)}`, - channel: "C04GWUTDC3W", - }); - } - case "EXTERNAL": { - return await io.slack.postMessage("message", { - text: `Missing external connection: account: ${JSON.stringify( - payload.account - )}, payload: ${JSON.stringify(payload)}`, - channel: "C04GWUTDC3W", - }); - } - } - }, -}); - -client.defineJob({ - id: "on-missing-auth-connection-resolved", - name: "On missing auth connection-resolved", - version: "0.1.1", - enabled, - trigger: missingConnectionResolvedNotification([githubUser]), - integrations: { - slack, - }, - run: async (payload, io, ctx) => { - switch (payload.type) { - case "DEVELOPER": { - return await io.slack.postMessage("message", { - text: `Missing developer connection resolved: ${JSON.stringify(payload)}`, - channel: "C04GWUTDC3W", - }); - } - case "EXTERNAL": { - return await io.slack.postMessage("message", { - text: `Missing external connection resolved: ${JSON.stringify(payload)}`, - channel: "C04GWUTDC3W", - }); - } - } - }, -}); - -client.defineJob({ - id: "get-user-repo", - name: "Get User Repo", - version: "0.1.1", - enabled, - trigger: eventTrigger({ - name: "get.repo", - schema: z.object({ - owner: z.string(), - repo: z.string(), - }), - }), - integrations: { - github: githubUser, - }, - run: async (payload, io, ctx) => { - return await io.github.getRepo("get.repo", payload); - }, -}); - -client.defineJob({ - id: "event-1", - name: "Run when the foo.bar event happens", - version: "0.0.1", - enabled: true, - trigger: eventTrigger({ - name: "foo.bar", - }), - run: async (payload, io, ctx) => { - await io.try( - async () => { - return await io.runTask( - "task-1", - async (task) => { - if (task.attempts > 2) { - return { - bar: "foo", - }; - } - - throw new Error(`Task failed on ${task.attempts} attempt(s)`); - }, - { name: "task-1", retry: { limit: 3 } } - ); - }, - async (error) => { - // These should never be reached - await io.wait("wait-after-error", 5); - - await io.logger.error("This is a log error message", { - payload, - error, - }); - - return { - foo: "bar", - }; - } - ); - - try { - await io.runTask( - "task-2", - async (task) => { - throw new Error(`Task failed on ${task.attempts} attempt(s)`); - }, - { name: "task-2", retry: { limit: 5 } } - ); - } catch (error) { - if (isTriggerError(error)) { - throw error; - } - - await io.wait("wait-after-error", 5); - - await io.logger.error("This is a log error message", { - payload, - error, - }); - } - - return { - payload, - }; - }, -}); - -client.defineJob({ - id: "scheduled-job-1", - name: "Scheduled Job 1", - version: "0.1.1", - enabled: true, - trigger: intervalTrigger({ - seconds: 60, - }), - run: async (payload, io, ctx) => { - await io.wait("wait", 5); // wait for 5 seconds - - await io.logger.info("This is a log info message", { - payload, - }); - - await io.sendEvent("send-event", { - name: "custom.event", - payload, - context: ctx, - }); - - await io.runTask("level 1", async () => { - await io.runTask("level 2", async () => { - await io.runTask("level 3", async () => { - await io.runTask("level 4", async () => { - await io.runTask("level 5", async () => {}); - }); - }); - }); - }); - - await io.wait("5 minutes", 5 * 60); - - await io.runTask("Fingers crossed", async () => { - throw new Error("You messed up buddy!"); - }); - }, -}); - -client.defineJob({ - id: "scheduled-job-2", - name: "Scheduled Job 2", - version: "0.1.1", - enabled, - trigger: cronTrigger({ - cron: "*/5 * * * *", // every 5 minutes - }), - run: async (payload, io, ctx) => { - await io.wait("wait", 5); // wait for 5 seconds - await io.logger.info("This is a log info message", { - payload, - ctx, - }); - - return { - message: "Hello from scheduled job 1", - }; - }, -}); - -client.defineJob({ - id: "test-io-functions", - name: "Test IO functions", - version: "0.1.1", - enabled, - trigger: eventTrigger({ - name: "test.io", - }), - run: async (payload, io, ctx) => { - await io.wait("wait", 5); // wait for 5 seconds - await io.logger.info("This is a log info message", { - payload, - }); - await io.sendEvent("send-event", { - name: "custom.event", - payload, - context: ctx, - }); - }, -}); - -client.defineJob({ - id: "alert-on-new-github-issues-3", - name: "Alert on new GitHub issues", - version: "0.1.1", - enabled, - integrations: { - slack, - }, - trigger: github.triggers.repo({ - event: events.onIssueOpened, - owner: "ericallam", - repo: "basic-starter-12k", - }), - run: async (payload, io, ctx) => { - await io.runTask("slow task", async () => { - await new Promise((resolve) => setTimeout(resolve, 5000)); - }); - - await io.logger.info("This is a simple log info message"); - - await io.wait("wait", 5); // wait for 5 seconds - - const response = await io.slack.postMessage("Slack 📝", { - text: `New Issue opened: ${payload.issue.html_url}`, - channel: "C04GWUTDC3W", - }); - - return response; - }, -}); diff --git a/references/nextjs-reference/src/jobs/github.ts b/references/nextjs-reference/src/jobs/github.ts deleted file mode 100644 index c4aa833b4e..0000000000 --- a/references/nextjs-reference/src/jobs/github.ts +++ /dev/null @@ -1,371 +0,0 @@ -import { client, github, slack } from "@/trigger"; -import { Github } from "@trigger.dev/github"; -import { events } from "@trigger.dev/github"; -import { Job } from "@trigger.dev/sdk"; - -const githubApiKey = new Github({ - id: "github-api-key", - token: process.env["GITHUB_API_KEY"]!, -}); - -client.defineJob({ - id: "github-integration-on-issue", - name: "GitHub Integration - On Issue", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onIssue, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-issue-opened", - name: "GitHub Integration - On Issue Opened", - version: "0.1.0", - integrations: { github: githubApiKey }, - trigger: githubApiKey.triggers.repo({ - event: events.onIssueOpened, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.github.addIssueAssignees("add assignee", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - issueNumber: payload.issue.number, - assignees: ["matt-aitken"], - }); - - await io.github.addIssueLabels("add label", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - issueNumber: payload.issue.number, - labels: ["bug"], - }); - - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "new-github-issue-reminder", - name: "New GitHub issue reminder", - version: "0.1.0", - integrations: { github, slack }, - trigger: github.triggers.repo({ - event: events.onIssueOpened, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - //delay for 24 hours (or 60 seconds in development) - const delayDuration = ctx.environment.type === "DEVELOPMENT" ? 60 : 60 * 60 * 24; - await io.wait("wait 24 hours", delayDuration); - - const issue = await io.github.getIssue("get issue", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - issueNumber: payload.issue.number, - }); - - //if the issue has had no activity - if (issue.updated_at === payload.issue.updated_at) { - await io.slack.postMessage("Slack reminder", { - text: `New issue needs attention: <${issue.html_url}|${issue.title}>`, - channel: "C04GWUTDC3W", - }); - - //assign it to someone, in this case… me - await io.github.addIssueAssignees("add assignee", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - issueNumber: payload.issue.number, - assignees: ["ericallam"], - }); - } - }, -}); - -client.defineJob({ - id: "github-integration-on-issue-assigned", - name: "GitHub Integration - On Issue assigned", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onIssueAssigned, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-issue-commented", - name: "GitHub Integration - On Issue commented", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onIssueComment, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "star-slack-notification", - name: "New Star Slack Notification", - version: "0.1.0", - integrations: { slack }, - trigger: githubApiKey.triggers.repo({ - event: events.onNewStar, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - const response = await io.slack.postMessage("Slack star", { - text: `${payload.sender.login} starred ${payload.repository.full_name}.\nTotal: ${payload.repository.stargazers_count}⭐️`, - channel: "C04GWUTDC3W", - }); - }, -}); - -client.defineJob({ - id: "github-integration-on-new-star", - name: "GitHub Integration - On New Star", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onNewStar, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-new-repo", - name: "GitHub Integration - On New Repository", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onNewRepository, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-new-branch-or-tag", - name: "GitHub Integration - On New Branch or Tag", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onNewBranchOrTag, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-new-branch", - name: "GitHub Integration - On New Branch", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onNewBranch, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-push", - name: "GitHub Integration - On Push", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onPush, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-pull-request", - name: "GitHub Integration - On Pull Request", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onPullRequest, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-pull-request-review", - name: "GitHub Integration - On Pull Request Review", - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onPullRequestReview, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-on-pull-request-merge-commit", - name: "GitHub Integration - on Pull Request Merge Commit", - version: "0.1.0", - integrations: { github }, - trigger: githubApiKey.triggers.repo({ - event: events.onPullRequest, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - - if (payload.pull_request.merged && payload.pull_request.merge_commit_sha) { - const commit = await io.github.getCommit("get merge commit", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - commitSHA: payload.pull_request.merge_commit_sha, - }); - await io.logger.info("Merge Commit Details", commit); - } - - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-get-tree", - name: "GitHub Integration - Get Tree", - version: "0.1.0", - integrations: { github }, - trigger: githubApiKey.triggers.repo({ - event: events.onPullRequest, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - - if (payload.pull_request.merged && payload.pull_request.merge_commit_sha) { - const tree = await io.github.getTree("get merge commit", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - treeSHA: payload.pull_request.merge_commit_sha, - }); - await io.logger.info("Tree ", tree); - } - - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-get-reference", - name: "GitHub Integration - Get Reference", - integrations: { github }, - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onNewBranch, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - - const ref = await io.github.getReference("Get reference", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - ref: payload.ref, - }); - - await io.logger.info("Reference ", ref); - - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-list-matching-references", - name: "GitHub Integration - List Matching References", - integrations: { github }, - version: "0.1.0", - trigger: githubApiKey.triggers.repo({ - event: events.onNewBranch, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - - const ref = await io.github.listMatchingReferences("List Matching References", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - ref: payload.ref, - }); - - await io.logger.info("Reference ", ref); - - return { payload, ctx }; - }, -}); - -client.defineJob({ - id: "github-integration-get-tag", - name: "GitHub Integration - Get Tag", - version: "0.1.0", - integrations: { github }, - trigger: githubApiKey.triggers.repo({ - event: events.onNewBranchOrTag, - owner: "triggerdotdev", - repo: "empty", - }), - run: async (payload, io, ctx) => { - await io.logger.info("This is a simple log info message"); - if (payload.ref_type === "tag") { - const tag = io.github.getTag("Get Tag", { - owner: payload.repository.owner.login, - repo: payload.repository.name, - tagSHA: payload.ref, - }); - await io.logger.info("Tag ", tag); - } - return { payload, ctx }; - }, -}); diff --git a/references/nextjs-reference/src/jobs/hooks.ts b/references/nextjs-reference/src/jobs/hooks.ts index f7a9fd425d..09ed9412ce 100644 --- a/references/nextjs-reference/src/jobs/hooks.ts +++ b/references/nextjs-reference/src/jobs/hooks.ts @@ -9,14 +9,64 @@ client.defineJob({ name: "test-event", }), run: async (payload, io, ctx) => { - await io.logger.log("This Job is triggered from a button in the frontend"); - await io.wait("wait", 5); - await io.logger.log("It runs for a while to test the React hooks"); - await io.wait("wait 2", 5); - await io.logger.log("This is the end of the job"); - - return { - myMessage: "This is the output of the job", - }; + const gettingInputData = await io.createStatus("getting-input-data", { + label: "Getting input data", + // state: "loading", + }); + + await io.wait("wait-input", 2); + + await gettingInputData.update("input-data-complete", { + label: "Input data complete", + state: "success", + }); + + const generatingMemes = await io.createStatus("generating-memes", { + label: "Generating memes", + state: "loading", + data: { + progress: 0.1, + }, + }); + + await io.wait("wait", 2); + + //...do stuff + await generatingMemes.update("middle-generation", { + data: { + progress: 0.3, + urls: [ + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnZoMndsdWh0MmhvY2kyaDF6YjZjZzg1ZGsxdnhhYm13a3Q1Y3lkbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + ], + }, + }); + + await io.wait("wait-again", 4); + + //...do stuff + await generatingMemes.update("generating-more-memes", { + data: { + progress: 0.6, + urls: [ + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnZoMndsdWh0MmhvY2kyaDF6YjZjZzg1ZGsxdnhhYm13a3Q1Y3lkbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbXdhNGhjaXVoZzFrMWJ0dmYyM2ZuOTIxN2J3aWYwY3J1OHI4eW13cCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/scZPhLqaVOM1qG4lT9/giphy.gif", + ], + }, + }); + + await io.wait("wait-again", 4); + + await generatingMemes.update("completed-generation", { + label: "Generated memes", + state: "success", + data: { + progress: 1.0, + urls: [ + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnZoMndsdWh0MmhvY2kyaDF6YjZjZzg1ZGsxdnhhYm13a3Q1Y3lkbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbXdhNGhjaXVoZzFrMWJ0dmYyM2ZuOTIxN2J3aWYwY3J1OHI4eW13cCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/scZPhLqaVOM1qG4lT9/giphy.gif", + "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExdHJhdXJ2Nnl6YnR3bXZuejZ3Y3Q5a2w3Mng2ZXZmMmJjeWdtZWhibCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/yYSSBtDgbbRzq/giphy-downsized.gif", + ], + }, + }); }, }); diff --git a/references/nextjs-reference/src/jobs/logging.ts b/references/nextjs-reference/src/jobs/logging.ts deleted file mode 100644 index 3ebd97c5b8..0000000000 --- a/references/nextjs-reference/src/jobs/logging.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { client } from "@/trigger"; -import { Job, eventTrigger } from "@trigger.dev/sdk"; - -client.defineJob({ - id: "test-logging", - name: "Test logging", - version: "0.0.1", - logLevel: "debug", - trigger: eventTrigger({ - name: "test.logging", - }), - run: async (payload, io, ctx) => { - await io.logger.log("Hello log level", { payload }); - await io.logger.error("Hello error level", { payload }); - await io.logger.warn("Hello warn level", { payload }); - await io.logger.info("Hello info level", { payload }); - await io.logger.debug("Hello debug level", { payload }); - }, -}); diff --git a/references/nextjs-reference/src/jobs/openai.ts b/references/nextjs-reference/src/jobs/openai.ts deleted file mode 100644 index cd3219fa5d..0000000000 --- a/references/nextjs-reference/src/jobs/openai.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { client } from "@/trigger"; -import { OpenAI } from "@trigger.dev/openai"; -import { Job, eventTrigger } from "@trigger.dev/sdk"; -import { z } from "zod"; - -const openai = new OpenAI({ - id: "openai", - apiKey: process.env["OPENAI_API_KEY"]!, -}); - -client.defineJob({ - id: "openai-tasks", - name: "OpenAI Tasks", - version: "0.0.1", - trigger: eventTrigger({ - name: "openai.tasks", - schema: z.object({}), - }), - integrations: { - openai, - }, - run: async (payload, io, ctx) => { - const models = await io.openai.listModels("list-models"); - - if (models.data.length > 0) { - await io.openai.retrieveModel("get-model", { - model: models.data[0].id, - }); - } - - await io.openai.backgroundCreateChatCompletion("background-chat-completion", { - model: "gpt-3.5-turbo", - messages: [ - { - role: "user", - content: "Create a good programming joke about background jobs", - }, - ], - }); - - await io.openai.createChatCompletion("chat-completion", { - model: "gpt-3.5-turbo", - messages: [ - { - role: "user", - content: "Create a good programming joke about background jobs", - }, - ], - }); - - await io.openai.backgroundCreateCompletion("background-completion", { - model: "text-davinci-003", - prompt: "Create a good programming joke about Tasks", - }); - - await io.openai.createCompletion("completion", { - model: "text-davinci-003", - prompt: "Create a good programming joke about Tasks", - }); - - await io.openai.createEdit("edit", { - model: "text-davinci-edit-001", - input: "Thsi is ridddled with erors", - instruction: "Fix the spelling errors", - }); - - await io.openai.createEmbedding("embedding", { - model: "text-embedding-ada-002", - input: "The food was delicious and the waiter...", - }); - }, -}); - -client.defineJob({ - id: "openai-images", - name: "OpenAI Images", - version: "0.0.1", - trigger: eventTrigger({ - name: "openai.images", - schema: z.object({}), - }), - integrations: { - openai, - }, - run: async (payload, io, ctx) => { - await io.openai.createImage("image", { - prompt: "A hedgehog wearing a party hat", - n: 2, - size: "256x256", - response_format: "url", - }); - }, -}); - -client.defineJob({ - id: "openai-files", - name: "OpenAI Files", - version: "0.0.1", - trigger: eventTrigger({ - name: "openai.files", - schema: z.object({}), - }), - integrations: { - openai, - }, - run: async (payload, io, ctx) => { - // jsonl string - await io.openai.createFile("file-string", { - file: `{ "prompt": "Tell me a joke", "completion": "Something funny" }\n{ "prompt": "Tell me another joke", "completion": "Something also funny" }`, - fileName: "cool-file.jsonl", - purpose: "fine-tune", - }); - - // fine tune file - const fineTuneFile = await io.openai.createFineTuneFile("file-fine-tune", { - fileName: "fine-tune.jsonl", - examples: [ - { - prompt: "Tell me a joke", - completion: "Why did the chicken cross the road? No one knows", - }, - { - prompt: "Tell me another joke", - completion: "Why did the chicken cross the road? To get to the other side", - }, - ], - }); - - const model = await io.openai.createFineTune("fine-tune", { - model: "davinci", - training_file: fineTuneFile.id, - }); - - const fineTunes = await io.openai.listFineTunes("list-fine-tunes"); - - const fineTune = await io.openai.retrieveFineTune("get-fine-tune", { - fineTuneId: model.id, - }); - - const events = await io.openai.listFineTuneEvents("list-fine-tune-events", { - fineTuneId: model.id, - }); - - const cancelFineTune = await io.openai.cancelFineTune("cancel-fine-tune", { - fineTuneId: model.id, - }); - - const files = await io.openai.listFiles("list-files"); - await io.logger.info("files", files); - - //this will fail because the fine tune didn't complete - await io.logger.info("This next task will fail because the model never completed"); - const deleteFineTune = await io.openai.deleteFineTune("delete-fine-tune", { - fineTunedModelId: model.id, - }); - }, -}); diff --git a/references/nextjs-reference/src/jobs/plain.ts b/references/nextjs-reference/src/jobs/plain.ts deleted file mode 100644 index 7eb51b1f8d..0000000000 --- a/references/nextjs-reference/src/jobs/plain.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { client } from "@/trigger"; -import { - ComponentDividerSpacingSize, - ComponentTextColor, - ComponentTextSize, - Plain, -} from "@trigger.dev/plain"; -import { Job, eventTrigger } from "@trigger.dev/sdk"; - -export const plain = new Plain({ - id: "plain-1", - apiKey: process.env["PLAIN_API_KEY"]!, -}); - -client.defineJob({ - id: "plain-playground", - name: "Plain Playground", - version: "0.1.1", - integrations: { - plain, - }, - trigger: eventTrigger({ - name: "plain.playground", - }), - run: async (payload, io, ctx) => { - const { customer } = await io.plain.upsertCustomer("upsert-customer", { - identifier: { - emailAddress: "eric@trigger.dev", - }, - onCreate: { - email: { - email: "eric@trigger.dev", - isVerified: true, - }, - fullName: "Eric Allam", - externalId: "123", - }, - onUpdate: { - fullName: { - value: "Eric Allam", - }, - externalId: { - value: "123", - }, - }, - }); - - const result = await io.plain.runTask("create-issue", async (client) => - client.createIssue({ - customerId: "abcdefghij", - issueTypeId: "123456", - }) - ); - - const foundCustomer = await io.plain.getCustomerById("get-customer", { - customerId: customer.id, - }); - - const timelineEntry = await io.plain.upsertCustomTimelineEntry("upsert-timeline-entry", { - customerId: customer.id, - title: "My timeline entry", - components: [ - { - componentText: { - text: `This is a nice title`, - }, - }, - { - componentDivider: { - dividerSpacingSize: ComponentDividerSpacingSize.M, - }, - }, - { - componentText: { - textSize: ComponentTextSize.S, - textColor: ComponentTextColor.Muted, - text: "External id", - }, - }, - { - componentText: { - text: foundCustomer?.externalId ?? "", - }, - }, - ], - }); - }, -}); diff --git a/references/nextjs-reference/src/jobs/schedules.ts b/references/nextjs-reference/src/jobs/schedules.ts deleted file mode 100644 index d5dc061ebf..0000000000 --- a/references/nextjs-reference/src/jobs/schedules.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { client } from "@/trigger"; -import { Job, cronTrigger } from "@trigger.dev/sdk"; - -client.defineJob({ - id: "test-cron-schedule-5", - name: "Test Cron Schedule 5", - version: "0.0.1", - logLevel: "debug", - trigger: cronTrigger({ - cron: "*/1 * * * *", - }), - run: async (payload, io, ctx) => { - await io.logger.debug("Hello cron schedule 2a", { - payload, - payload2: payload, - }); - }, -}); diff --git a/references/nextjs-reference/src/jobs/slack.ts b/references/nextjs-reference/src/jobs/slack.ts deleted file mode 100644 index 4df5200607..0000000000 --- a/references/nextjs-reference/src/jobs/slack.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { client } from "@/trigger"; -import { Slack } from "@trigger.dev/slack"; -import { Job, cronTrigger, eventTrigger } from "@trigger.dev/sdk"; - -const db = { - getKpiSummary: async (date: Date) => { - return { - revenue: 23_269, - orders: 1_234, - }; - }, -}; - -export const slack = new Slack({ id: "slack-6" }); -export const slackMissing = new Slack({ id: "slack-7" }); - -client.defineJob({ - id: "slack-kpi-summary", - name: "Slack kpi summary", - version: "0.1.1", - integrations: { - slack, - }, - trigger: cronTrigger({ - cron: "0 9 * * *", // 9am every day (UTC) - }), - run: async (payload, io, ctx) => { - const { revenue } = await db.getKpiSummary(payload.ts); - const response = await io.slack.postMessage("Slack 📝", { - text: `Yesterday's revenue was $${revenue}`, - channel: "C04GWUTDC3W", - }); - - return response; - }, -}); - -client.defineJob({ - id: "slack-auto-join", - name: "Slack Auto Join", - version: "0.1.1", - integrations: { - slack, - }, - trigger: eventTrigger({ - name: "slack.auto_join", - }), - run: async (payload, io, ctx) => { - const response = await io.slack.postMessage("Slack 📝", { - channel: "C05G130TH4G", - text: "Welcome to the team, Eric!", - blocks: [ - { - type: "section", - text: { - type: "mrkdwn", - text: `Welcome to the team, Eric!`, - }, - }, - { - type: "section", - text: { - type: "mrkdwn", - text: `I'm here to help you get started with Trigger!`, - }, - }, - ], - }); - - return response; - }, -}); - -client.defineJob({ - id: "slack-missing-integration", - name: "Slack with missing integration", - version: "0.1.1", - integrations: { - slack: slackMissing, - }, - trigger: eventTrigger({ - name: "missing.integration", - }), - run: async (payload, io, ctx) => { - const response = await io.slack.postMessage("message", { - text: `There's no Slack connection, or is there?`, - channel: "C04GWUTDC3W", - }); - - return response; - }, -}); diff --git a/references/nextjs-reference/src/jobs/stripe.ts b/references/nextjs-reference/src/jobs/stripe.ts deleted file mode 100644 index df834b82a3..0000000000 --- a/references/nextjs-reference/src/jobs/stripe.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { Stripe } from "@trigger.dev/stripe"; -import { client } from "@/trigger"; -import { eventTrigger } from "@trigger.dev/sdk"; -import { z } from "zod"; - -const stripe = new Stripe({ - id: "stripe", - apiKey: process.env["STRIPE_API_KEY"]!, -}); - -client.defineJob({ - id: "stripe-example-1", - name: "Stripe Example 1", - version: "0.1.0", - trigger: eventTrigger({ - name: "stripe.example", - schema: z.object({ - customerId: z.string(), - source: z.string(), - }), - }), - integrations: { - stripe, - }, - run: async (payload, io, ctx) => { - await io.stripe.createCharge("create-charge", { - amount: 100, - currency: "usd", - source: payload.source, - customer: payload.customerId, - }); - }, -}); - -client.defineJob({ - id: "stripe-example-1", - name: "Stripe Example 1", - version: "0.1.0", - trigger: eventTrigger({ - name: "stripe.example", - schema: z.object({ - customerId: z.string(), - source: z.string(), - }), - }), - integrations: { - stripe, - }, - run: async (payload, io, ctx) => { - await io.stripe.createCharge("create-charge", { - amount: 100, - currency: "usd", - source: payload.source, - customer: payload.customerId, - }); - }, -}); - -client.defineJob({ - id: "stripe-create-customer", - name: "Stripe Create Customer", - version: "0.1.0", - trigger: eventTrigger({ - name: "stripe.new.customer", - schema: z.object({ - email: z.string(), - name: z.string(), - }), - }), - integrations: { - stripe, - }, - run: async (payload, io, ctx) => { - await io.stripe.createCustomer("create-customer", { - email: payload.email, - name: payload.name, - }); - }, -}); - -client.defineJob({ - id: "stripe-update-customer", - name: "Stripe Update Customer", - version: "0.1.0", - trigger: eventTrigger({ - name: "stripe.update.customer", - schema: z.object({ - customerId: z.string(), - name: z.string(), - }), - }), - integrations: { - stripe, - }, - run: async (payload, io, ctx) => { - await io.stripe.updateCustomer("update-customer", { - id: payload.customerId, - name: payload.name, - }); - }, -}); - -client.defineJob({ - id: "stripe-retrieve-subscription", - name: "Stripe Retrieve Subscription", - version: "0.1.0", - trigger: eventTrigger({ - name: "stripe.retrieve.subscription", - schema: z.object({ - id: z.string(), - }), - }), - integrations: { - stripe, - }, - run: async (payload, io, ctx) => { - const subscription = await io.stripe.retrieveSubscription("get", { - id: payload.id, - expand: ["customer"], - }); - }, -}); - -client.defineJob({ - id: "stripe-on-price", - name: "Stripe On Price", - version: "0.1.0", - trigger: stripe.onPrice({ events: ["price.created", "price.updated"] }), - run: async (payload, io, ctx) => { - if (ctx.event.name === "price.created") { - await io.logger.info("price created!", { ctx }); - } else { - await io.logger.info("price updated!", { ctx }); - } - }, -}); - -client.defineJob({ - id: "stripe-on-price-created", - name: "Stripe On Price Created", - version: "0.1.0", - trigger: stripe.onPriceCreated(), - run: async (payload, io, ctx) => { - await io.logger.info("ctx", { ctx }); - }, -}); - -client.defineJob({ - id: "stripe-on-subscription-created", - name: "Stripe On Subscription Created", - version: "0.1.0", - trigger: stripe.onCustomerSubscriptionCreated({ - filter: { - currency: ["usd"], - }, - }), - run: async (payload, io, ctx) => { - await io.logger.info("ctx", { ctx }); - }, -}); diff --git a/references/nextjs-reference/src/jobs/supabase.ts b/references/nextjs-reference/src/jobs/supabase.ts deleted file mode 100644 index 0b93b3c37a..0000000000 --- a/references/nextjs-reference/src/jobs/supabase.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { Database } from "@/supabase.types"; -import { client } from "@/trigger"; -import { - type IntegrationIO, - Job, - eventTrigger, - type JobPayload, - type JobIO, - type TriggerPayload, - type IOWithIntegrations, -} from "@trigger.dev/sdk"; -import { SupabaseManagement, Supabase } from "@trigger.dev/supabase"; -import { z } from "zod"; - -const supabase = new SupabaseManagement({ - id: "supabase", -}); - -const db = supabase.db(process.env["SUPABASE_ID"]!); - -const dbNoTypes = supabase.db(process.env["SUPABASE_ID"]!); - -const supabaseManagementKey = new SupabaseManagement({ - id: "supabase-management-key", - apiKey: process.env["SUPABASE_API_KEY"]!, -}); - -const dbKey = supabase.db(process.env["SUPABASE_ID"]!); - -const supabaseDB = new Supabase({ - id: "supabase-db", - supabaseUrl: `https://${process.env["SUPABASE_ID"]}.supabase.co`, - supabaseKey: process.env["SUPABASE_KEY"]!, -}); - -async function doPlaygroundStuff(io: IntegrationIO, ref: string) { - await io.getPGConfig("get-pg-config", { - ref, - }); -} - -new Job(client, { - id: "supabase-playground", - name: "Supabase Playground", - version: "0.1.1", - trigger: eventTrigger({ - name: "supabase.playground", - }), - integrations: { - supabase, - supabaseDB, - supabaseManagementKey, - }, - run: async (payload, io, ctx) => { - await io.supabaseManagementKey.getPGConfig("get-pg-config", { - ref: payload.ref, - }); - - await io.supabase.getOrganizations("get-orgs"); - await io.supabase.getProjects("get-projects"); - - await io.supabase.listFunctions("list-functions", { - ref: payload.ref, - }); - - await io.supabase.runQuery("run-query", { - ref: payload.ref, - query: "SELECT * FROM users", - }); - - await io.supabase.getTypescriptTypes("get-typescript-types", { - ref: payload.ref, - }); - - const users = await io.supabaseDB.runTask( - "fetch-users", - async (db) => { - const { data, error } = await db.from("users").select("*"); - - if (error) throw error; - - return data; - }, - { name: "Fetch Users" } - ); - - const newUser = await io.supabaseDB.runTask( - "create-user", - async (db) => { - return await db - .from("users") - .insert({ - first_name: "John", - last_name: "Doe", - email_address: "john@trigger.dev", - }) - .select(); - }, - { name: "New Users" } - ); - }, -}); - -const createTodoJob = new Job(client, { - id: "supabase-create-todo", - name: "Supabase Create Todo", - version: "0.1.1", - trigger: eventTrigger({ - name: "supabase.create-todo", - schema: z.object({ - contents: z.string(), - user_id: z.number(), - }), - }), - integrations: { - supabaseDB, - }, - run: async (payload, io, ctx) => { - const newTodo = await io.supabaseDB.runTask( - "create-todo", - async (db) => { - const { data, error } = await db - .from("todos") - .insert({ - contents: payload.contents, - user_id: payload.user_id, - is_complete: false, - }) - .select(); - - if (error) throw error; - - return data; - }, - { - name: "Create Todo", - properties: [{ label: "Contents", text: payload.contents }], - } - ); - }, -}); - -type CreateTodo = JobPayload; -type CreateTodoIO = JobIO; - -async function runCreateTodo(payload: CreateTodo, io: CreateTodoIO) { - const newTodo = await io.supabaseDB.runTask( - "create-todo", - async (db) => { - const { data, error } = await db - .from("todos") - .insert({ - contents: payload.contents, - user_id: payload.user_id, - is_complete: false, - }) - .select(); - - if (error) throw error; - - return data; - }, - { - name: "Create Todo", - properties: [{ label: "Contents", text: payload.contents }], - } - ); -} - -const createProjectTrigger = eventTrigger({ - name: "supabase.create", - schema: z.object({ - name: z.string(), - organization_id: z.string(), - plan: z.enum(["free", "pro"]), - region: z.enum(["us-east-1", "us-west-1"]), - password: z.string(), - }), -}); - -async function doRun( - payload: TriggerPayload, - io: IOWithIntegrations<{ supabase: typeof supabase }> -) { - await io.supabase.createProject("create-project", { - name: payload.name, - organization_id: payload.organization_id, - plan: payload.plan, - region: payload.region, - kps_enabled: true, - db_pass: payload.password, - }); -} - -new Job(client, { - id: "supabase-create-project", - name: "Supabase Create Project", - version: "0.1.1", - trigger: createProjectTrigger, - integrations: { - supabase, - }, - run: async (payload, io, ctx) => { - await io.supabase.createProject("create-project", { - name: payload.name, - organization_id: payload.organization_id, - plan: payload.plan, - region: payload.region, - kps_enabled: true, - db_pass: payload.password, - }); - }, -}); - -new Job(client, { - id: "supabase-on-user-insert", - name: "Supabase On User Insert", - version: "0.1.1", - trigger: db.onInserted({ - table: "users", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-user-insert-2", - name: "Supabase On User Insert 2", - version: "0.1.1", - trigger: db.onInserted({ - table: "users", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-user-email-changed", - name: "Supabase On User Email Changed", - version: "0.1.1", - trigger: db.onUpdated({ - table: "users", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-user-deleted", - name: "Supabase On User Deleted", - version: "0.1.1", - trigger: db.onDeleted({ - table: "users", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-todo-created", - name: "Supabase On TODO created", - version: "0.1.1", - trigger: dbKey.onInserted({ - table: "todos", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-todo-created", - name: "Supabase On TODO created", - version: "0.1.1", - trigger: dbKey.onInserted({ - table: "todos", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-todo-completed", - name: "Supabase On TODO completed", - version: "0.1.1", - trigger: dbKey.onUpdated({ - table: "todos", - filter: { - old_record: { - is_complete: [false], - }, - record: { - is_complete: [true], - }, - }, - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => { - await io.logger.log("Todo Completed", { payload }); - }, -}); - -new Job(client, { - id: "supabase-on-tweet-created-or-deleted", - name: "Supabase On Tweet Created or Deleted", - version: "0.1.1", - trigger: dbKey.onDeleted({ - schema: "public_2", - table: "tweets", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); - -new Job(client, { - id: "supabase-on-todo-created-no-types", - name: "Supabase On TODO created", - version: "0.1.1", - trigger: dbNoTypes.onInserted({ - table: "todos", - }), - integrations: { - supabase, - }, - run: async (payload, io, ctx) => {}, -}); diff --git a/references/nextjs-reference/src/jobs/typeform.ts b/references/nextjs-reference/src/jobs/typeform.ts deleted file mode 100644 index a2b03e0821..0000000000 --- a/references/nextjs-reference/src/jobs/typeform.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { client } from "@/trigger"; -import { Typeform, events } from "@trigger.dev/typeform"; -import { Job, eventTrigger } from "@trigger.dev/sdk"; -import { DynamicTrigger } from "@trigger.dev/sdk"; -import { z } from "zod"; - -export const typeform = new Typeform({ - id: "typeform-1", - token: process.env["TYPEFORM_API_KEY"]!, -}); - -client.defineJob({ - id: "typeform-playground", - name: "Typeform Playground", - version: "0.1.1", - integrations: { - typeform, - }, - trigger: eventTrigger({ - name: "typeform.playground", - schema: z.object({ - formId: z.string().optional(), - }), - }), - run: async (payload, io, ctx) => { - await io.typeform.listForms("list-forms"); - - if (payload.formId) { - const form = await io.typeform.getForm("get-form", { - uid: payload.formId, - }); - - const listResponses = await io.typeform.listResponses("list-responses", { - uid: payload.formId, - pageSize: 50, - }); - - const allResponses = await io.typeform.getAllResponses("get-all-responses", { - uid: payload.formId, - }); - } - - await io.typeform.runTask( - "create-form", - async (client) => { - return client.forms.create({ - data: { - title: "My Form", - fields: [ - { - title: "What is your name?", - type: "short_text", - ref: "name", - }, - { - title: "What is your email?", - type: "email", - ref: "email", - }, - ], - }, - }); - }, - { name: "Create Form" } - ); - }, -}); - -client.defineJob({ - id: "typeform-webhook-2", - name: "Typeform Webhook 2", - version: "0.1.1", - trigger: typeform.onFormResponse({ - uid: "QQnotGJM", - tag: "tag4", - }), - run: async (payload, io, ctx) => {}, -}); - -const dynamicTrigger = new DynamicTrigger(client, { - id: "typeform-dynamic-trigger", - source: typeform.source, - event: events.onFormResponse, -}); diff --git a/references/nextjs-reference/src/trigger.ts b/references/nextjs-reference/src/trigger.ts index 86184b71e2..e52fb84d8f 100644 --- a/references/nextjs-reference/src/trigger.ts +++ b/references/nextjs-reference/src/trigger.ts @@ -19,11 +19,6 @@ export const client = new TriggerClient({ ioLogLocalEnabled: true, }); -export const openai = new OpenAI({ - id: "openai", - apiKey: process.env["OPENAI_API_KEY"]!, -}); - export const github = new Github({ id: "github", octokitRequest: { fetch }, diff --git a/references/nextjs-reference/tsconfig.json b/references/nextjs-reference/tsconfig.json index 28ad34a2cf..acd8c76025 100644 --- a/references/nextjs-reference/tsconfig.json +++ b/references/nextjs-reference/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "ES2015", - "lib": [ - "dom", - "dom.iterable", - "ES2015" - ], + "lib": ["dom", "dom.iterable", "ES2015"], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -19,87 +15,35 @@ "jsx": "preserve", "incremental": true, "paths": { - "@/*": [ - "./src/*" - ], - "@trigger.dev/sdk": [ - "../../packages/trigger-sdk/src/index" - ], - "@trigger.dev/sdk/*": [ - "../../packages/trigger-sdk/src/*" - ], - "@trigger.dev/nextjs": [ - "../../packages/nextjs/src/index" - ], - "@trigger.dev/nextjs/*": [ - "../../packages/nextjs/src/*" - ], - "@trigger.dev/core": [ - "../../packages/core/src/index" - ], - "@trigger.dev/core/*": [ - "../../packages/core/src/*" - ], - "@trigger.dev/integration-kit": [ - "../../packages/integration-kit/src/index" - ], - "@trigger.dev/integration-kit/*": [ - "../../packages/integration-kit/src/*" - ], - "@trigger.dev/github": [ - "../../integrations/github/src/index" - ], - "@trigger.dev/github/*": [ - "../../integrations/github/src/*" - ], - "@trigger.dev/slack": [ - "../../integrations/slack/src/index" - ], - "@trigger.dev/slack/*": [ - "../../integrations/slack/src/*" - ], - "@trigger.dev/openai": [ - "../../integrations/openai/src/index" - ], - "@trigger.dev/openai/*": [ - "../../integrations/openai/src/*" - ], - "@trigger.dev/resend": [ - "../../integrations/resend/src/index" - ], - "@trigger.dev/resend/*": [ - "../../integrations/resend/src/*" - ], - "@trigger.dev/typeform": [ - "../../integrations/typeform/src/index" - ], - "@trigger.dev/typeform/*": [ - "../../integrations/typeform/src/*" - ], - "@trigger.dev/plain": [ - "../../integrations/plain/src/index" - ], - "@trigger.dev/plain/*": [ - "../../integrations/plain/src/*" - ], - "@trigger.dev/supabase": [ - "../../integrations/supabase/src/index" - ], - "@trigger.dev/supabase/*": [ - "../../integrations/supabase/src/*" - ], - "@trigger.dev/stripe": [ - "../../integrations/stripe/src/index" - ], - "@trigger.dev/stripe/*": [ - "../../integrations/stripe/src/*" - ], - "@trigger.dev/sendgrid": [ - "../../integrations/sendgrid/src/index" - ], - "@trigger.dev/sendgrid/*": [ - "../../integrations/sendgrid/src/*" - ] + "@/*": ["./src/*"], + "@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"], + "@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"], + "@trigger.dev/react": ["../../packages/react/src/index"], + "@trigger.dev/react/*": ["../../packages/react/src/*"], + "@trigger.dev/nextjs": ["../../packages/nextjs/src/index"], + "@trigger.dev/nextjs/*": ["../../packages/nextjs/src/*"], + "@trigger.dev/core": ["../../packages/core/src/index"], + "@trigger.dev/core/*": ["../../packages/core/src/*"], + "@trigger.dev/integration-kit": ["../../packages/integration-kit/src/index"], + "@trigger.dev/integration-kit/*": ["../../packages/integration-kit/src/*"], + "@trigger.dev/github": ["../../integrations/github/src/index"], + "@trigger.dev/github/*": ["../../integrations/github/src/*"], + "@trigger.dev/slack": ["../../integrations/slack/src/index"], + "@trigger.dev/slack/*": ["../../integrations/slack/src/*"], + "@trigger.dev/openai": ["../../integrations/openai/src/index"], + "@trigger.dev/openai/*": ["../../integrations/openai/src/*"], + "@trigger.dev/resend": ["../../integrations/resend/src/index"], + "@trigger.dev/resend/*": ["../../integrations/resend/src/*"], + "@trigger.dev/typeform": ["../../integrations/typeform/src/index"], + "@trigger.dev/typeform/*": ["../../integrations/typeform/src/*"], + "@trigger.dev/plain": ["../../integrations/plain/src/index"], + "@trigger.dev/plain/*": ["../../integrations/plain/src/*"], + "@trigger.dev/supabase": ["../../integrations/supabase/src/index"], + "@trigger.dev/supabase/*": ["../../integrations/supabase/src/*"], + "@trigger.dev/stripe": ["../../integrations/stripe/src/index"], + "@trigger.dev/stripe/*": ["../../integrations/stripe/src/*"], + "@trigger.dev/sendgrid": ["../../integrations/sendgrid/src/index"], + "@trigger.dev/sendgrid/*": ["../../integrations/sendgrid/src/*"] }, "plugins": [ { @@ -107,13 +51,6 @@ } ] }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts" - ], - "exclude": [ - "node_modules" - ] -} \ No newline at end of file + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +}