diff --git a/docs/docs.json b/docs/docs.json index 2d99921a96..f38bf7d225 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -113,30 +113,32 @@ ] }, { - "group": "Frontend usage", + "group": "Realtime", "pages": [ - "frontend/overview", + "realtime/overview", + "realtime/how-it-works", + "realtime/run-object", + "realtime/auth", + { + "group": "React hooks (frontend)", + "pages": [ + "realtime/react-hooks/overview", + "realtime/react-hooks/triggering", + "realtime/react-hooks/subscribe", + "realtime/react-hooks/streams", + "realtime/react-hooks/swr" + ] + }, { - "group": "React hooks", + "group": "Backend", "pages": [ - "frontend/react-hooks/overview", - "frontend/react-hooks/realtime", - "frontend/react-hooks/triggering" + "realtime/backend/overview", + "realtime/backend/subscribe", + "realtime/backend/streams" ] } ] }, - { - "group": "Realtime API", - "pages": [ - "realtime/overview", - "realtime/streams", - "realtime/react-hooks", - "realtime/subscribe-to-run", - "realtime/subscribe-to-runs-with-tag", - "realtime/subscribe-to-batch" - ] - }, { "group": "CLI", "pages": [ @@ -573,7 +575,47 @@ }, { "source": "/frontend/react-hooks", - "destination": "/frontend/react-hooks/overview" + "destination": "/realtime/react-hooks/overview" + }, + { + "source": "/frontend/overview", + "destination": "/realtime/auth" + }, + { + "source": "/frontend/react-hooks/overview", + "destination": "/realtime/react-hooks/overview" + }, + { + "source": "/frontend/react-hooks/realtime", + "destination": "/realtime/react-hooks/realtime" + }, + { + "source": "/frontend/react-hooks/triggering", + "destination": "/realtime/react-hooks/triggering" + }, + { + "source": "/realtime/backend", + "destination": "/realtime/backend/overview" + }, + { + "source": "/realtime/streams", + "destination": "/realtime/backend/streams" + }, + { + "source": "/realtime/react-hooks", + "destination": "/realtime/react-hooks/overview" + }, + { + "source": "/realtime/subscribe-to-run", + "destination": "/realtime/backend/subscribe" + }, + { + "source": "/realtime/subscribe-to-runs-with-tag", + "destination": "/realtime/backend/subscribe" + }, + { + "source": "/realtime/subscribe-to-batch", + "destination": "/realtime/backend/subscribe" }, { "source": "/management/projects/runs", diff --git a/docs/frontend/overview.mdx b/docs/frontend/overview.mdx deleted file mode 100644 index 6df411bc3a..0000000000 --- a/docs/frontend/overview.mdx +++ /dev/null @@ -1,171 +0,0 @@ ---- -title: Overview & Authentication -sidebarTitle: Overview & Auth -description: Using the Trigger.dev SDK from your frontend application. ---- - -You can use our [React hooks](/frontend/react-hooks) in your frontend application to interact with the Trigger.dev API. This guide will show you how to generate Public Access Tokens that can be used to authenticate your requests. - -## Authentication - -To create a Public Access Token, you can use the `auth.createPublicToken` function in your **backend** code: - -```tsx -import { auth } from "@trigger.dev/sdk/v3"; - -const publicToken = await auth.createPublicToken(); // πŸ‘ˆ this public access token has no permissions, so is pretty useless! -``` - -### Scopes - -By default a Public Access Token has no permissions. You must specify the scopes you need when creating a Public Access Token: - -```ts -import { auth } from "@trigger.dev/sdk/v3"; - -const publicToken = await auth.createPublicToken({ - scopes: { - read: { - runs: true, // ❌ this token can read all runs, possibly useful for debugging/testing - }, - }, -}); -``` - -This will allow the token to read all runs, which is probably not what you want. You can specify only certain runs by passing an array of run IDs: - -```ts -import { auth } from "@trigger.dev/sdk/v3"; - -const publicToken = await auth.createPublicToken({ - scopes: { - read: { - runs: ["run_1234", "run_5678"], // βœ… this token can read only these runs - }, - }, -}); -``` - -You can scope the token to only read certain tasks: - -```ts -import { auth } from "@trigger.dev/sdk/v3"; - -const publicToken = await auth.createPublicToken({ - scopes: { - read: { - tasks: ["my-task-1", "my-task-2"], // πŸ‘ˆ this token can read all runs of these tasks - }, - }, -}); -``` - -Or tags: - -```ts -import { auth } from "@trigger.dev/sdk/v3"; - -const publicToken = await auth.createPublicToken({ - scopes: { - read: { - tags: ["my-tag-1", "my-tag-2"], // πŸ‘ˆ this token can read all runs with these tags - }, - }, -}); -``` - -Or a specific batch of runs: - -```ts -import { auth } from "@trigger.dev/sdk/v3"; - -const publicToken = await auth.createPublicToken({ - scopes: { - read: { - batch: "batch_1234", // πŸ‘ˆ this token can read all runs in this batch - }, - }, -}); -``` - -You can also combine scopes. For example, to read runs with specific tags and for specific tasks: - -```ts -import { auth } from "@trigger.dev/sdk/v3"; - -const publicToken = await auth.createPublicToken({ - scopes: { - read: { - tasks: ["my-task-1", "my-task-2"], - tags: ["my-tag-1", "my-tag-2"], - }, - }, -}); -``` - -### Expiration - -By default, Public Access Token's expire after 15 minutes. You can specify a different expiration time when creating a Public Access Token: - -```ts -import { auth } from "@trigger.dev/sdk/v3"; - -const publicToken = await auth.createPublicToken({ - expirationTime: "1hr", -}); -``` - -- If `expirationTime` is a string, it will be treated as a time span -- If `expirationTime` is a number, it will be treated as a Unix timestamp -- If `expirationTime` is a `Date`, it will be treated as a date - -The format used for a time span is the same as the [jose package](https://github.com/panva/jose), which is a number followed by a unit. Valid units are: "sec", "secs", "second", "seconds", "s", "minute", "minutes", "min", "mins", "m", "hour", "hours", "hr", "hrs", "h", "day", "days", "d", "week", "weeks", "w", "year", "years", "yr", "yrs", and "y". It is not possible to specify months. 365.25 days is used as an alias for a year. If the string is suffixed with "ago", or prefixed with a "-", the resulting time span gets subtracted from the current unix timestamp. A "from now" suffix can also be used for readability when adding to the current unix timestamp. - -## Auto-generated tokens - -When triggering a task from your backend, the `handle` received from the `trigger` function now includes a `publicAccessToken` field. This token can be used to authenticate requests in your frontend application: - -```ts -import { tasks } from "@trigger.dev/sdk/v3"; - -const handle = await tasks.trigger("my-task", { some: "data" }); - -console.log(handle.publicAccessToken); -``` - -By default, tokens returned from the `trigger` function expire after 15 minutes and have a read scope for that specific run. You can customize the expiration of the auto-generated tokens by passing a `publicTokenOptions` object to the `trigger` function: - -```ts -import { tasks } from "@trigger.dev/sdk/v3"; - -const handle = await tasks.trigger( - "my-task", - { some: "data" }, - { - tags: ["my-tag"], - }, - { - publicAccessToken: { - expirationTime: "1hr", - }, - } -); -``` - -You will also get back a Public Access Token when using the `batchTrigger` function: - -```ts -import { tasks } from "@trigger.dev/sdk/v3"; - -const handle = await tasks.batchTrigger("my-task", [ - { payload: { some: "data" } }, - { payload: { some: "data" } }, - { payload: { some: "data" } }, -]); - -console.log(handle.publicAccessToken); -``` - -## Usage - -To learn how to use these Public Access Tokens, see our [React hooks](/frontend/react-hooks) guide. diff --git a/docs/frontend/react-hooks/overview.mdx b/docs/frontend/react-hooks/overview.mdx deleted file mode 100644 index 3dedfcaa2b..0000000000 --- a/docs/frontend/react-hooks/overview.mdx +++ /dev/null @@ -1,337 +0,0 @@ ---- -title: Overview -sidebarTitle: Overview -description: Using the Trigger.dev v3 API from your React application. ---- - -import RealtimeExamplesCards from "/snippets/realtime-examples-cards.mdx"; - -Our react hooks package provides a set of hooks that make it easy to interact with the Trigger.dev API from your React application, using our [frontend API](/frontend/overview). You can use these hooks to fetch runs, and subscribe to real-time updates, and trigger tasks from your frontend application. - -## Installation - -Install the `@trigger.dev/react-hooks` package in your project: - - - -```bash npm -npm add @trigger.dev/react-hooks -``` - -```bash pnpm -pnpm add @trigger.dev/react-hooks -``` - -```bash yarn -yarn install @trigger.dev/react-hooks -``` - - - -## Authentication - -All hooks accept an optional last argument `options` that accepts an `accessToken` param, which should be a valid Public Access Token. Learn more about [generating tokens in the frontend guide](/frontend/overview). - -```tsx -import { useRealtimeRun } from "@trigger.dev/react-hooks"; - -export function MyComponent({ - runId, - publicAccessToken, -}: { - runId: string; - publicAccessToken: string; -}) { - const { run, error } = useRealtimeRun(runId, { - accessToken: publicAccessToken, // This is required - baseURL: "https://your-trigger-dev-instance.com", // optional, only needed if you are self-hosting Trigger.dev - }); - - // ... -} -``` - -Alternatively, you can use our `TriggerAuthContext` provider - -```tsx -import { TriggerAuthContext } from "@trigger.dev/react-hooks"; - -export function SetupTrigger({ publicAccessToken }: { publicAccessToken: string }) { - return ( - - - - ); -} -``` - -Now children components can use the hooks to interact with the Trigger.dev API. If you are self-hosting Trigger.dev, you can provide the `baseURL` to the `TriggerAuthContext` provider. - -```tsx -import { TriggerAuthContext } from "@trigger.dev/react-hooks"; - -export function SetupTrigger({ publicAccessToken }: { publicAccessToken: string }) { - return ( - - - - ); -} -``` - -### Next.js and client components - -If you are using Next.js with the App Router, you have to make sure the component that uses the `TriggerAuthContext` is a client component. So for example, the following code will not work: - -```tsx app/page.tsx -import { TriggerAuthContext } from "@trigger.dev/react-hooks"; - -export default function Page() { - return ( - - - - ); -} -``` - -That's because `Page` is a server component and the `TriggerAuthContext.Provider` uses client-only react code. To fix this, wrap the `TriggerAuthContext.Provider` in a client component: - -```ts components/TriggerProvider.tsx -"use client"; - -import { TriggerAuthContext } from "@trigger.dev/react-hooks"; - -export function TriggerProvider({ - accessToken, - children, -}: { - accessToken: string; - children: React.ReactNode; -}) { - return ( - - {children} - - ); -} -``` - -### Passing the token to the frontend - -Techniques for passing the token to the frontend vary depending on your setup. Here are a few ways to do it for different setups: - -#### Next.js App Router - -If you are using Next.js with the App Router and you are triggering a task from a server action, you can use cookies to store and pass the token to the frontend. - -```tsx actions/trigger.ts -"use server"; - -import { tasks } from "@trigger.dev/sdk/v3"; -import type { exampleTask } from "@/trigger/example"; -import { redirect } from "next/navigation"; -import { cookies } from "next/headers"; - -export async function startRun() { - const handle = await tasks.trigger("example", { foo: "bar" }); - - // Set the auto-generated publicAccessToken in a cookie - cookies().set("publicAccessToken", handle.publicAccessToken); // βœ… this token only has access to read this run - - redirect(`/runs/${handle.id}`); -} -``` - -Then in the `/runs/[id].tsx` page, you can read the token from the cookie and pass it to the `TriggerProvider`. - -```tsx pages/runs/[id].tsx -import { TriggerProvider } from "@/components/TriggerProvider"; - -export default function RunPage({ params }: { params: { id: string } }) { - const publicAccessToken = cookies().get("publicAccessToken"); - - return ( - - - - ); -} -``` - -Instead of a cookie, you could also use a query parameter to pass the token to the frontend: - -```tsx actions/trigger.ts -import { tasks } from "@trigger.dev/sdk/v3"; -import type { exampleTask } from "@/trigger/example"; -import { redirect } from "next/navigation"; -import { cookies } from "next/headers"; - -export async function startRun() { - const handle = await tasks.trigger("example", { foo: "bar" }); - - redirect(`/runs/${handle.id}?publicAccessToken=${handle.publicAccessToken}`); -} -``` - -And then in the `/runs/[id].tsx` page: - -```tsx pages/runs/[id].tsx -import { TriggerProvider } from "@/components/TriggerProvider"; - -export default function RunPage({ - params, - searchParams, -}: { - params: { id: string }; - searchParams: { publicAccessToken: string }; -}) { - return ( - - - - ); -} -``` - -Another alternative would be to use a server-side rendered page to fetch the token and pass it to the frontend: - - - -```tsx pages/runs/[id].tsx -import { TriggerProvider } from "@/components/TriggerProvider"; -import { generatePublicAccessToken } from "@/trigger/auth"; - -export default async function RunPage({ params }: { params: { id: string } }) { - // This will be executed on the server only - const publicAccessToken = await generatePublicAccessToken(params.id); - - return ( - - - - ); -} -``` - -```tsx trigger/auth.ts -import { auth } from "@trigger.dev/sdk/v3"; - -export async function generatePublicAccessToken(runId: string) { - return auth.createPublicToken({ - scopes: { - read: { - runs: [runId], - }, - }, - expirationTime: "1h", - }); -} -``` - - - -## SWR vs Realtime hooks - -We offer two "styles" of hooks: SWR and Realtime. The SWR hooks use the [swr](https://swr.vercel.app/) library to fetch data once and cache it. The Realtime hooks use [Trigger.dev realtime](/realtime) to subscribe to updates in real-time. - - - It can be a little confusing which one to use because [swr](https://swr.vercel.app/) can also be - configured to poll for updates. But because of rate-limits and the way the Trigger.dev API works, - we recommend using the Realtime hooks for most use-cases. - - -## SWR Hooks - -### useRun - -The `useRun` hook allows you to fetch a run by its ID. - -```tsx -"use client"; // This is needed for Next.js App Router or other RSC frameworks - -import { useRun } from "@trigger.dev/react-hooks"; - -export function MyComponent({ runId }: { runId: string }) { - const { run, error, isLoading } = useRun(runId); - - if (isLoading) return
Loading...
; - if (error) return
Error: {error.message}
; - - return
Run: {run.id}
; -} -``` - -The `run` object returned is the same as the [run object](/management/runs/retrieve) returned by the Trigger.dev API. To correctly type the run's payload and output, you can provide the type of your task to the `useRun` hook: - -```tsx -import { useRun } from "@trigger.dev/react-hooks"; -import type { myTask } from "@/trigger/myTask"; - -export function MyComponent({ runId }: { runId: string }) { - const { run, error, isLoading } = useRun(runId, { - refreshInterval: 0, // Disable polling - }); - - if (isLoading) return
Loading...
; - if (error) return
Error: {error.message}
; - - // Now run.payload and run.output are correctly typed - - return
Run: {run.id}
; -} -``` - -### Common options - -You can pass the following options to the all SWR hooks: - - - Revalidate the data when the window regains focus. - - - - Revalidate the data when the browser regains a network connection. - - - - Poll for updates at the specified interval (in milliseconds). Polling is not recommended for most - use-cases. Use the Realtime hooks instead. - - -### Common return values - - - An error object if an error occurred while fetching the data. - - - - A boolean indicating if the data is currently being fetched. - - - - A boolean indicating if the data is currently being revalidated. - - - - A boolean indicating if an error occurred while fetching the data. - - -## Realtime hooks - -See our [Realtime hooks documentation](/frontend/react-hooks/realtime) for more information. - -## Trigger Hooks - -See our [Trigger hooks documentation](/frontend/react-hooks/triggering) for more information. - - diff --git a/docs/frontend/react-hooks/realtime.mdx b/docs/frontend/react-hooks/realtime.mdx deleted file mode 100644 index d1cb260faa..0000000000 --- a/docs/frontend/react-hooks/realtime.mdx +++ /dev/null @@ -1,420 +0,0 @@ ---- -title: Realtime hooks -sidebarTitle: Realtime -description: Get live updates from the Trigger.dev API in your frontend application. ---- - -import RealtimeExamplesCards from "/snippets/realtime-examples-cards.mdx"; - -These hooks allow you to subscribe to runs, batches, and streams using [Trigger.dev realtime](/realtime). Before reading this guide: - -- Read our [Realtime documentation](/realtime) to understand how the Trigger.dev realtime API works. -- Read how to [setup and authenticate](/frontend/overview) using the `@trigger.dev/react-hooks` package. - -## Hooks - -### useRealtimeRun - -The `useRealtimeRun` hook allows you to subscribe to a run by its ID. - -```tsx -"use client"; // This is needed for Next.js App Router or other RSC frameworks - -import { useRealtimeRun } from "@trigger.dev/react-hooks"; - -export function MyComponent({ - runId, - publicAccessToken, -}: { - runId: string; - publicAccessToken: string; -}) { - const { run, error } = useRealtimeRun(runId, { - accessToken: publicAccessToken, - }); - - if (error) return
Error: {error.message}
; - - return
Run: {run.id}
; -} -``` - -To correctly type the run's payload and output, you can provide the type of your task to the `useRealtimeRun` hook: - -```tsx -import { useRealtimeRun } from "@trigger.dev/react-hooks"; -import type { myTask } from "@/trigger/myTask"; - -export function MyComponent({ - runId, - publicAccessToken, -}: { - runId: string; - publicAccessToken: string; -}) { - const { run, error } = useRealtimeRun(runId, { - accessToken: publicAccessToken, - }); - - if (error) return
Error: {error.message}
; - - // Now run.payload and run.output are correctly typed - - return
Run: {run.id}
; -} -``` - -You can supply an `onComplete` callback to the `useRealtimeRun` hook to be called when the run is completed or errored. This is useful if you want to perform some action when the run is completed, like navigating to a different page or showing a notification. - -```tsx -import { useRealtimeRun } from "@trigger.dev/react-hooks"; - -export function MyComponent({ - runId, - publicAccessToken, -}: { - runId: string; - publicAccessToken: string; -}) { - const { run, error } = useRealtimeRun(runId, { - accessToken: publicAccessToken, - onComplete: (run, error) => { - console.log("Run completed", run); - }, - }); - - if (error) return
Error: {error.message}
; - - return
Run: {run.id}
; -} -``` - -See our [Realtime documentation](/realtime) for more information about the type of the run object and more. - -### useRealtimeRunsWithTag - -The `useRealtimeRunsWithTag` hook allows you to subscribe to multiple runs with a specific tag. - -```tsx -"use client"; // This is needed for Next.js App Router or other RSC frameworks - -import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks"; - -export function MyComponent({ tag }: { tag: string }) { - const { runs, error } = useRealtimeRunsWithTag(tag); - - if (error) return
Error: {error.message}
; - - return ( -
- {runs.map((run) => ( -
Run: {run.id}
- ))} -
- ); -} -``` - -To correctly type the runs payload and output, you can provide the type of your task to the `useRealtimeRunsWithTag` hook: - -```tsx -import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks"; -import type { myTask } from "@/trigger/myTask"; - -export function MyComponent({ tag }: { tag: string }) { - const { runs, error } = useRealtimeRunsWithTag(tag); - - if (error) return
Error: {error.message}
; - - // Now runs[i].payload and runs[i].output are correctly typed - - return ( -
- {runs.map((run) => ( -
Run: {run.id}
- ))} -
- ); -} -``` - -If `useRealtimeRunsWithTag` could return multiple different types of tasks, you can pass a union of all the task types to the hook: - -```tsx -import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks"; -import type { myTask1, myTask2 } from "@/trigger/myTasks"; - -export function MyComponent({ tag }: { tag: string }) { - const { runs, error } = useRealtimeRunsWithTag(tag); - - if (error) return
Error: {error.message}
; - - // You can narrow down the type of the run based on the taskIdentifier - for (const run of runs) { - if (run.taskIdentifier === "my-task-1") { - // run is correctly typed as myTask1 - } else if (run.taskIdentifier === "my-task-2") { - // run is correctly typed as myTask2 - } - } - - return ( -
- {runs.map((run) => ( -
Run: {run.id}
- ))} -
- ); -} -``` - -### useRealtimeBatch - -The `useRealtimeBatch` hook allows you to subscribe to a batch of runs by its the batch ID. - -```tsx -"use client"; // This is needed for Next.js App Router or other RSC frameworks - -import { useRealtimeBatch } from "@trigger.dev/react-hooks"; - -export function MyComponent({ batchId }: { batchId: string }) { - const { runs, error } = useRealtimeBatch(batchId); - - if (error) return
Error: {error.message}
; - - return ( -
- {runs.map((run) => ( -
Run: {run.id}
- ))} -
- ); -} -``` - -See our [Realtime documentation](/realtime) for more information. - -### useRealtimeRunWithStreams - -The `useRealtimeRunWithStreams` hook allows you to subscribe to a run by its ID and also receive any streams that are emitted by the task. See our [Realtime documentation](/realtime#streams) for more information about emitting streams from a task. - -```tsx -"use client"; // This is needed for Next.js App Router or other RSC frameworks - -import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks"; - -export function MyComponent({ - runId, - publicAccessToken, -}: { - runId: string; - publicAccessToken: string; -}) { - const { run, streams, error } = useRealtimeRunWithStreams(runId, { - accessToken: publicAccessToken, - }); - - if (error) return
Error: {error.message}
; - - return ( -
-
Run: {run.id}
-
- {Object.keys(streams).map((stream) => ( -
Stream: {stream}
- ))} -
-
- ); -} -``` - -You can provide the type of the streams to the `useRealtimeRunWithStreams` hook: - -```tsx -import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks"; -import type { myTask } from "@/trigger/myTask"; - -type STREAMS = { - openai: string; // this is the type of each "part" of the stream -}; - -export function MyComponent({ - runId, - publicAccessToken, -}: { - runId: string; - publicAccessToken: string; -}) { - const { run, streams, error } = useRealtimeRunWithStreams(runId, { - accessToken: publicAccessToken, - }); - - if (error) return
Error: {error.message}
; - - const text = streams.openai?.map((part) => part).join(""); - - return ( -
-
Run: {run.id}
-
{text}
-
- ); -} -``` - -As you can see above, each stream is an array of the type you provided, keyed by the stream name. If instead of a pure text stream you have a stream of objects, you can provide the type of the object: - -```tsx -import type { TextStreamPart } from "ai"; -import type { myTask } from "@/trigger/myTask"; - -type STREAMS = { openai: TextStreamPart<{}> }; - -export function MyComponent({ - runId, - publicAccessToken, -}: { - runId: string; - publicAccessToken: string; -}) { - const { run, streams, error } = useRealtimeRunWithStreams(runId, { - accessToken: publicAccessToken, - }); - - if (error) return
Error: {error.message}
; - - const text = streams.openai - ?.filter((stream) => stream.type === "text-delta") - ?.map((part) => part.text) - .join(""); - - return ( -
-
Run: {run.id}
-
{text}
-
- ); -} -``` - -## Common options - -### accessToken & baseURL - -You can pass the `accessToken` option to the Realtime hooks to authenticate the subscription. - -```tsx -import { useRealtimeRun } from "@trigger.dev/react-hooks"; - -export function MyComponent({ - runId, - publicAccessToken, -}: { - runId: string; - publicAccessToken: string; -}) { - const { run, error } = useRealtimeRun(runId, { - accessToken: publicAccessToken, - baseURL: "https://my-self-hosted-trigger.com", // Optional if you are using a self-hosted Trigger.dev instance - }); - - if (error) return
Error: {error.message}
; - - return
Run: {run.id}
; -} -``` - -### enabled - -You can pass the `enabled` option to the Realtime hooks to enable or disable the subscription. - -```tsx -import { useRealtimeRun } from "@trigger.dev/react-hooks"; - -export function MyComponent({ - runId, - publicAccessToken, - enabled, -}: { - runId: string; - publicAccessToken: string; - enabled: boolean; -}) { - const { run, error } = useRealtimeRun(runId, { - accessToken: publicAccessToken, - enabled, - }); - - if (error) return
Error: {error.message}
; - - return
Run: {run.id}
; -} -``` - -This allows you to conditionally disable using the hook based on some state. - -### id - -You can pass the `id` option to the Realtime hooks to change the ID of the subscription. - -```tsx -import { useRealtimeRun } from "@trigger.dev/react-hooks"; - -export function MyComponent({ - id, - runId, - publicAccessToken, - enabled, -}: { - id: string; - runId: string; - publicAccessToken: string; - enabled: boolean; -}) { - const { run, error } = useRealtimeRun(runId, { - accessToken: publicAccessToken, - enabled, - id, - }); - - if (error) return
Error: {error.message}
; - - return
Run: {run.id}
; -} -``` - -This allows you to change the ID of the subscription based on some state. Passing in a different ID will unsubscribe from the current subscription and subscribe to the new one (and remove any cached data). - -### experimental_throttleInMs - -The `*withStreams` variants of the Realtime hooks accept an `experimental_throttleInMs` option to throttle the updates from the server. This can be useful if you are getting too many updates and want to reduce the number of updates. - -```tsx -import { useRealtimeRunsWithStreams } from "@trigger.dev/react-hooks"; - -export function MyComponent({ - runId, - publicAccessToken, -}: { - runId: string; - publicAccessToken: string; -}) { - const { runs, error } = useRealtimeRunsWithStreams(tag, { - accessToken: publicAccessToken, - experimental_throttleInMs: 1000, // Throttle updates to once per second - }); - - if (error) return
Error: {error.message}
; - - return ( -
- {runs.map((run) => ( -
Run: {run.id}
- ))} -
- ); -} -``` - - diff --git a/docs/guides/example-projects/batch-llm-evaluator.mdx b/docs/guides/example-projects/batch-llm-evaluator.mdx index c214e80722..5a04331391 100644 --- a/docs/guides/example-projects/batch-llm-evaluator.mdx +++ b/docs/guides/example-projects/batch-llm-evaluator.mdx @@ -39,11 +39,11 @@ This demo is a full stack example that uses the following: - View the Trigger.dev task code in the [src/trigger/batch.ts](https://github.com/triggerdotdev/examples/blob/main/batch-llm-evaluator/src/trigger/batch.ts) file. - The `evaluateModels` task uses the `batch.triggerByTaskAndWait` method to distribute the task to the different LLM models. - It then passes the results through to a `summarizeEvals` task that calculates some dummy "tags" for each LLM response. -- We use a [useRealtimeRunsWithTag](https://trigger.dev/docs/frontend/react-hooks/realtime#userealtimerunswithtag) hook to subscribe to the different evaluation tasks runs in the [src/components/llm-evaluator.tsx](https://github.com/triggerdotdev/examples/blob/main/batch-llm-evaluator/src/components/llm-evaluator.tsx) file. +- We use a [useRealtimeRunsWithTag](/realtime/react-hooks/subscribe#userealtimerunswithtag) hook to subscribe to the different evaluation tasks runs in the [src/components/llm-evaluator.tsx](https://github.com/triggerdotdev/examples/blob/main/batch-llm-evaluator/src/components/llm-evaluator.tsx) file. - We then pass the relevant run down into three different components for the different models: - The `AnthropicEval` component: [src/components/evals/Anthropic.tsx](https://github.com/triggerdotdev/examples/blob/main/batch-llm-evaluator/src/components/evals/Anthropic.tsx) - The `XAIEval` component: [src/components/evals/XAI.tsx](https://github.com/triggerdotdev/examples/blob/main/batch-llm-evaluator/src/components/evals/XAI.tsx) - The `OpenAIEval` component: [src/components/evals/OpenAI.tsx](https://github.com/triggerdotdev/examples/blob/main/batch-llm-evaluator/src/components/evals/OpenAI.tsx) -- Each of these components then uses [useRealtimeRunWithStreams](https://trigger.dev/docs/frontend/react-hooks/realtime#userealtimerunwithstreams) to subscribe to the different LLM responses. +- Each of these components then uses [useRealtimeRunWithStreams](/realtime/react-hooks/streams) to subscribe to the different LLM responses. diff --git a/docs/guides/example-projects/human-in-the-loop-workflow.mdx b/docs/guides/example-projects/human-in-the-loop-workflow.mdx index 36d5bcc66f..dbc0f6f7cd 100644 --- a/docs/guides/example-projects/human-in-the-loop-workflow.mdx +++ b/docs/guides/example-projects/human-in-the-loop-workflow.mdx @@ -79,7 +79,6 @@ await wait.completeToken( - While the workflow in this example is static and does not allow changing the connections between nodes in the UI, it serves as a good baseline for understanding how to build completely custom workflow builders using Trigger.dev and ReactFlow. ## Learn more about Trigger.dev Realtime and waitpoint tokens @@ -87,6 +86,6 @@ While the workflow in this example is static and does not allow changing the con To learn more, take a look at the following resources: - [Trigger.dev Realtime](/realtime) - learn more about how to subscribe to runs and get real-time updates -- [Realtime streaming](/realtime/streams) - learn more about streaming data from your tasks -- [React hooks](/frontend/react-hooks) - learn more about using React hooks to interact with the Trigger.dev API +- [Realtime streaming](/realtime/react-hooks/streams) - learn more about streaming data from your tasks +- [React hooks](/realtime/react-hooks) - learn more about using React hooks to interact with the Trigger.dev API - [Waitpoint tokens](/wait-for-token) - learn about waitpoint tokens in Trigger.dev and human-in-the-loop flows diff --git a/docs/guides/example-projects/vercel-ai-sdk-deep-research.mdx b/docs/guides/example-projects/vercel-ai-sdk-deep-research.mdx index aa0d54687c..33c58925ed 100644 --- a/docs/guides/example-projects/vercel-ai-sdk-deep-research.mdx +++ b/docs/guides/example-projects/vercel-ai-sdk-deep-research.mdx @@ -110,7 +110,7 @@ Level 0 (Initial Query): "AI safety in autonomous vehicles" ### Using Trigger.dev Realtime to trigger and subscribe to the deep research task -We use the [`useRealtimeTaskTrigger`](/frontend/react-hooks/triggering#userealtimetasktrigger) React hook to trigger the `deep-research` task and subscribe to it's updates. +We use the [`useRealtimeTaskTrigger`](/realtime/react-hooks/triggering#userealtimetasktrigger) React hook to trigger the `deep-research` task and subscribe to it's updates. **Frontend (React Hook)**: diff --git a/docs/guides/example-projects/vercel-ai-sdk-image-generator.mdx b/docs/guides/example-projects/vercel-ai-sdk-image-generator.mdx index 15eda0270a..ea13ac92b0 100644 --- a/docs/guides/example-projects/vercel-ai-sdk-image-generator.mdx +++ b/docs/guides/example-projects/vercel-ai-sdk-image-generator.mdx @@ -11,7 +11,7 @@ import RealtimeLearnMore from "/snippets/realtime-learn-more.mdx"; This demo is a full stack example that uses the following: - A [Next.js](https://nextjs.org/) app using [shadcn](https://ui.shadcn.com/) for the UI -- Our 'useRealtimeRun' [React hook](https://trigger.dev/docs/frontend/react-hooks/realtime) to subscribe to the run and show updates on the frontend +- Our 'useRealtimeRun' [React hook](/realtime/react-hooks/subscribe#userealtimerun) to subscribe to the run and show updates on the frontend - The [Vercel AI SDK](https://sdk.vercel.ai/docs/introduction) to [generate images](https://sdk.vercel.ai/docs/ai-sdk-core/image-generation) using OpenAI's DALL-E models ## GitHub repo @@ -36,6 +36,6 @@ This demo is a full stack example that uses the following: ## Relevant code - View the Trigger.dev task code which generates the image using the Vercel AI SDK in [src/trigger/realtime-generate-image.ts](https://github.com/triggerdotdev/examples/tree/main/vercel-ai-sdk-image-generator/src/trigger/realtime-generate-image.ts). -- We use a [useRealtimeRun](https://trigger.dev/docs/frontend/react-hooks/realtime#userealtimerun) hook to subscribe to the run in [src/app/processing/[id]/ProcessingContent.tsx](https://github.com/triggerdotdev/examples/tree/main/vercel-ai-sdk-image-generator/src/app/processing/[id]/ProcessingContent.tsx). +- We use a [useRealtimeRun](/realtime/react-hooks/subscribe#userealtimerun) hook to subscribe to the run in [src/app/processing/[id]/ProcessingContent.tsx](https://github.com/triggerdotdev/examples/tree/main/vercel-ai-sdk-image-generator/src/app/processing/[id]/ProcessingContent.tsx). diff --git a/docs/introduction.mdx b/docs/introduction.mdx index 1ab5baaf7a..0180bf1ef5 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -12,7 +12,11 @@ mode: "center" Explore dozens of examples tasks to use in your own projects - + Learn how to use Trigger.dev with your favorite frameworks @@ -24,7 +28,7 @@ mode: "center" Trigger.dev is an open source background jobs framework that lets you write reliable workflows in plain async code. Run long-running AI tasks, handle complex background jobs, and build AI agents with built-in queuing, automatic retries, and real-time monitoring. No timeouts, elastic scaling, and zero infrastructure management required. -We provide everything you need to build and manage background tasks: a CLI and SDK for writing tasks in your existing codebase, support for both [regular](/tasks/overview) and [scheduled](/tasks/scheduled) tasks, full observability through our dashboard, and a [Realtime API](/realtime) with [React hooks](/frontend/react-hooks#realtime-hooks) for showing task status in your frontend. You can use [Trigger.dev Cloud](https://cloud.trigger.dev) or [self-host](/open-source-self-hosting) on your own infrastructure. +We provide everything you need to build and manage background tasks: a CLI and SDK for writing tasks in your existing codebase, support for both [regular](/tasks/overview) and [scheduled](/tasks/scheduled) tasks, full observability through our dashboard, and a [Realtime API](/realtime) with [React hooks](/realtime/react-hooks#realtime-hooks) for showing task status in your frontend. You can use [Trigger.dev Cloud](https://cloud.trigger.dev) or [self-host](/open-source-self-hosting) on your own infrastructure. ## Learn the concepts @@ -39,7 +43,8 @@ We provide everything you need to build and manage background tasks: a CLI and S Runs are the instances of tasks that are executed. Learn how they work. - API keys are used to authenticate requests to the Trigger.dev API. Learn how to create and use them. + API keys are used to authenticate requests to the Trigger.dev API. Learn how to create and use + them. @@ -52,13 +57,18 @@ We provide everything you need to build and manage background tasks: a CLI and S The Realtime API allows you to trigger tasks and get the status of runs. - + React hooks are a way to show task status in your frontend. Waits are a way to wait for a task to finish before continuing. - + Learn how to handle errors and retries. diff --git a/docs/management/authentication.mdx b/docs/management/authentication.mdx index 2d24c1f354..701a017fcb 100644 --- a/docs/management/authentication.mdx +++ b/docs/management/authentication.mdx @@ -8,7 +8,7 @@ There are two methods of authenticating with the management API: using a secret There is a separate authentication strategy when making requests from your frontend application. - See the [Frontend guide](/frontend/overview) for more information. This guide is for backend usage + See the [Realtime guide](/realtime/overview) for more information. This guide is for backend usage only. @@ -94,4 +94,4 @@ await envvars.upload("proj_1234", "dev", { }, override: true, }); -``` \ No newline at end of file +``` diff --git a/docs/realtime/auth.mdx b/docs/realtime/auth.mdx new file mode 100644 index 0000000000..e574d07e6b --- /dev/null +++ b/docs/realtime/auth.mdx @@ -0,0 +1,204 @@ +--- +title: Realtime authentication +sidebarTitle: Realtime auth +description: Authenticating real-time API requests with Public Access Tokens or Trigger Tokens +--- + +To use the Realtime API, you need to authenticate your requests with Public Access Tokens or Trigger Tokens. These tokens provide secure, scoped access to your runs and can be used in both frontend and backend applications. + +## Token Types + +There are two types of tokens you can use with the Realtime API: + +- **[Public Access Tokens](#public-access-tokens-for-subscribing-to-runs)** - Used to read and subscribe to run data. Can be used in both the frontend and backend. +- **[Trigger Tokens](#trigger-tokens-for-frontend-triggering-only)** - Used to trigger tasks from your frontend. These are more secure, single-use tokens that can only be used in the frontend. + +## Public Access Tokens (for subscribing to runs) + +Use Public Access Tokens to subscribe to runs and receive real-time updates in your frontend or backend. + +### Creating Public Access Tokens + +You can create a Public Access Token using the `auth.createPublicToken` function in your **backend** code: + +```tsx +// Somewhere in your backend code +import { auth } from "@trigger.dev/sdk/v3"; + +const publicToken = await auth.createPublicToken(); // πŸ‘ˆ this public access token has no permissions, so is pretty useless! +``` + +### Scopes + +By default a Public Access Token has no permissions. You must specify the scopes you need when creating a Public Access Token: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; + +const publicToken = await auth.createPublicToken({ + scopes: { + read: { + runs: true, // ❌ this token can read all runs, possibly useful for debugging/testing + }, + }, +}); +``` + +This will allow the token to read all runs, which is probably not what you want. You can specify only certain runs by passing an array of run IDs: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; + +const publicToken = await auth.createPublicToken({ + scopes: { + read: { + runs: ["run_1234", "run_5678"], // βœ… this token can read only these runs + }, + }, +}); +``` + +You can scope the token to only read certain tasks: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; + +const publicToken = await auth.createPublicToken({ + scopes: { + read: { + tasks: ["my-task-1", "my-task-2"], // πŸ‘ˆ this token can read all runs of these tasks + }, + }, +}); +``` + +Or tags: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; + +const publicToken = await auth.createPublicToken({ + scopes: { + read: { + tags: ["my-tag-1", "my-tag-2"], // πŸ‘ˆ this token can read all runs with these tags + }, + }, +}); +``` + +Or a specific batch of runs: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; + +const publicToken = await auth.createPublicToken({ + scopes: { + read: { + batch: "batch_1234", // πŸ‘ˆ this token can read all runs in this batch + }, + }, +}); +``` + +You can also combine scopes. For example, to read runs with specific tags and for specific tasks: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; + +const publicToken = await auth.createPublicToken({ + scopes: { + read: { + tasks: ["my-task-1", "my-task-2"], + tags: ["my-tag-1", "my-tag-2"], + }, + }, +}); +``` + +### Expiration + +By default, Public Access Token's expire after 15 minutes. You can specify a different expiration time when creating a Public Access Token: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; + +const publicToken = await auth.createPublicToken({ + expirationTime: "1hr", +}); +``` + +- If `expirationTime` is a string, it will be treated as a time span +- If `expirationTime` is a number, it will be treated as a Unix timestamp +- If `expirationTime` is a `Date`, it will be treated as a date + +The format used for a time span is the same as the [jose package](https://github.com/panva/jose), which is a number followed by a unit. Valid units are: "sec", "secs", "second", "seconds", "s", "minute", "minutes", "min", "mins", "m", "hour", "hours", "hr", "hrs", "h", "day", "days", "d", "week", "weeks", "w", "year", "years", "yr", "yrs", and "y". It is not possible to specify months. 365.25 days is used as an alias for a year. If the string is suffixed with "ago", or prefixed with a "-", the resulting time span gets subtracted from the current unix timestamp. A "from now" suffix can also be used for readability when adding to the current unix timestamp. + +### Auto-generated tokens + +When you [trigger tasks](/triggering) from your backend, the `handle` received includes a `publicAccessToken` field. This token can be used to authenticate real-time requests in your frontend application. + +By default, auto-generated tokens expire after 15 minutes and have a read scope for the specific run(s) that were triggered. You can customize the expiration by passing a `publicTokenOptions` object to the trigger function. + +See our [triggering documentation](/triggering) for detailed examples of how to trigger tasks and get auto-generated tokens. + +### Subscribing to runs with Public Access Tokens + +Once you have a Public Access Token, you can use it to authenticate requests to the Realtime API in both backend and frontend applications. + +**Backend usage:** See our [backend documentation](/realtime/backend) for examples of what you can do with Realtime in your backend once you have authenticated with a token. + +**Frontend usage:** See our [React hooks documentation](/realtime/react-hooks) for examples of using tokens with frontend components. + +## Trigger Tokens (for frontend triggering only) + +For triggering tasks from your frontend, you need special "trigger" tokens. These tokens can only be used once to trigger a task and are more secure than regular Public Access Tokens. + +### Creating Trigger Tokens + +```ts +import { auth } from "@trigger.dev/sdk/v3"; + +// Somewhere in your backend code +const triggerToken = await auth.createTriggerPublicToken("my-task"); +``` + +### Multiple tasks + +You can pass multiple tasks to create a token that can trigger multiple tasks: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; + +// Somewhere in your backend code +const triggerToken = await auth.createTriggerPublicToken(["my-task-1", "my-task-2"]); +``` + +### Multiple use + +You can also create tokens that can be used multiple times: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; + +// Somewhere in your backend code +const triggerToken = await auth.createTriggerPublicToken("my-task", { + multipleUse: true, // ❌ Use this with caution! +}); +``` + +### Expiration + +These tokens also expire, with the default expiration time being 15 minutes. You can specify a custom expiration time: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; + +// Somewhere in your backend code +const triggerToken = await auth.createTriggerPublicToken("my-task", { + expirationTime: "24hr", +}); +``` + +### Triggering tasks from the frontend with Trigger Tokens + +Check out our [React hooks documentation](/realtime/react-hooks) for examples of how to use Trigger Tokens in your frontend applications. diff --git a/docs/realtime/backend/overview.mdx b/docs/realtime/backend/overview.mdx new file mode 100644 index 0000000000..079004e923 --- /dev/null +++ b/docs/realtime/backend/overview.mdx @@ -0,0 +1,42 @@ +--- +title: Backend overview +sidebarTitle: Overview +description: Using the Trigger.dev realtime API from your backend code +--- + +import RealtimeExamplesCards from "/snippets/realtime-examples-cards.mdx"; + +Use these backend functions to subscribe to runs and streams from your server-side code or other tasks. + +## Overview + +There are three main categories of functionality: + +- **[Subscribe functions](/realtime/backend/subscribe)** - Subscribe to run updates using async iterators +- **[Metadata](/realtime/backend/subscribe#subscribe-to-metadata-updates-from-your-tasks)** - Update and subscribe to run metadata in real-time +- **[Streams](/realtime/backend/streams)** - Emit and consume real-time streaming data from your tasks + +## Authentication + +All backend functions support both server-side and client-side authentication: + +- **Server-side**: Use your API key (automatically handled in tasks) +- **Client-side**: Generate a Public Access Token with appropriate scopes + +See our [authentication guide](/realtime/auth) for detailed information on creating and using tokens. + +## Quick example + +Subscribe to a run: + +```ts +import { runs, tasks } from "@trigger.dev/sdk/v3"; + +// Trigger a task +const handle = await tasks.trigger("my-task", { some: "data" }); + +// Subscribe to real-time updates +for await (const run of runs.subscribeToRun(handle.id)) { + console.log(`Run ${run.id} status: ${run.status}`); +} +``` diff --git a/docs/realtime/streams.mdx b/docs/realtime/backend/streams.mdx similarity index 60% rename from docs/realtime/streams.mdx rename to docs/realtime/backend/streams.mdx index aa74a68e09..7a5ae28d51 100644 --- a/docs/realtime/streams.mdx +++ b/docs/realtime/backend/streams.mdx @@ -1,20 +1,23 @@ --- -title: Realtime streams +title: Streams sidebarTitle: Streams -description: Stream data in realtime from inside your tasks +description: Emit and consume real-time streaming data from your tasks --- -import RealtimeExamplesCards from "/snippets/realtime-examples-cards.mdx"; +The Streams API allows you to stream data from your tasks to the outside world in realtime using the [metadata](/runs/metadata) system. This is particularly useful for streaming LLM outputs or any other real-time data. -The world is going realtime, and so should your tasks. With the Streams API, you can stream data from your tasks to the outside world in realtime. This is useful for a variety of use cases, including AI. + + For frontend applications using React, see our [React hooks streams + documentation](/realtime/react-hooks/streams) for consuming streams in your UI. + -## How it works +## How streams work -The Streams API is a simple API that allows you to send data from your tasks to the outside world in realtime using the [metadata](/runs/metadata) system. You can send any kind of data that is streamed in realtime, but the most common use case is to send streaming output from streaming LLM providers, like OpenAI. +Streams use the metadata system to send data chunks in real-time. You register a stream with a specific key using `metadata.stream`, and then consumers can subscribe to receive those chunks as they're emitted. -## Usage +## Basic streaming example -To use the Streams API, you need to register a stream with a specific key using `metadata.stream`. The following example uses the OpenAI SDK with `stream: true` to stream the output of the LLM model in realtime: +Here's how to stream data from OpenAI in your task: ```ts import { task, metadata } from "@trigger.dev/sdk/v3"; @@ -56,12 +59,9 @@ export const myTask = task({ }); ``` -You can then subscribe to the stream using the `runs.subscribeToRun` method: +## Subscribing to streams from backend - - `runs.subscribeToRun` should be used from your backend or another task. To subscribe to a run from - your frontend, you can use our [React hooks](/frontend/react-hooks). - +You can subscribe to the stream using the `runs.subscribeToRun` method with `.withStreams()`: ```ts import { runs } from "@trigger.dev/sdk/v3"; @@ -86,7 +86,9 @@ async function subscribeToStream(runId: string) { } ``` -You can register and subscribe to multiple streams in the same task. Let's add a stream from the response body of a fetch request: +## Multiple streams + +You can register and subscribe to multiple streams in the same task: ```ts import { task, metadata } from "@trigger.dev/sdk/v3"; @@ -97,7 +99,7 @@ const openai = new OpenAI({ }); export type STREAMS = { - openai: OpenAI.ChatCompletionChunk; // The type of the chunk is determined by the provider + openai: OpenAI.ChatCompletionChunk; fetch: string; // The response body will be an array of strings }; @@ -110,7 +112,7 @@ export const myTask = task({ stream: true, }); - // Register the stream with the key "openai" + // Register the OpenAI stream await metadata.stream("openai", completion); const response = await fetch("https://jsonplaceholder.typicode.com/posts"); @@ -119,7 +121,7 @@ export const myTask = task({ return; } - // Register the stream with the key "fetch" + // Register a fetch response stream // Pipe the response.body through a TextDecoderStream to convert it to a string await metadata.stream("fetch", response.body.pipeThrough(new TextDecoderStream())); }, @@ -133,7 +135,7 @@ export const myTask = task({ task. -And then subscribing to the streams: +Then subscribe to both streams: ```ts import { runs } from "@trigger.dev/sdk/v3"; @@ -141,7 +143,6 @@ import type { myTask, STREAMS } from "./trigger/my-task"; // Somewhere in your backend async function subscribeToStream(runId: string) { - // Use a for-await loop to subscribe to the stream for await (const part of runs.subscribeToRun(runId).withStreams()) { switch (part.type) { case "run": { @@ -163,45 +164,9 @@ async function subscribeToStream(runId: string) { } ``` -## React hooks - -If you're building a frontend application, you can use our React hooks to subscribe to streams. Here's an example of how you can use the `useRealtimeRunWithStreams` hook to subscribe to a stream: - -```tsx -import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks"; -import type { myTask, STREAMS } from "./trigger/my-task"; - -// Somewhere in your React component -function MyComponent({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) { - const { run, streams } = useRealtimeRunWithStreams(runId, { - accessToken: publicAccessToken, - }); - - if (!run) { - return
Loading...
; - } - - return ( -
-

Run ID: {run.id}

-

Streams:

-
    - {Object.entries(streams).map(([key, value]) => ( -
  • - {key}: {JSON.stringify(value)} -
  • - ))} -
-
- ); -} -``` - -Read more about using the React hooks in the [React hooks](/frontend/react-hooks) documentation. - -## Usage with the `ai` SDK +## Using with the AI SDK -The [ai SDK](https://sdk.vercel.ai/docs/introduction) provides a higher-level API for working with AI models. You can use the `ai` SDK with the Streams API by using the `streamText` method: +The [AI SDK](https://sdk.vercel.ai/docs/introduction) provides a higher-level API for working with AI models. You can use it with the Streams API: ```ts import { openai } from "@ai-sdk/openai"; @@ -244,35 +209,9 @@ export const aiStreaming = schemaTask({ }); ``` -And then render the stream in your frontend: +## Using AI SDK with tools -```tsx -import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks"; -import type { aiStreaming, STREAMS } from "./trigger/ai-streaming"; - -function MyComponent({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) { - const { streams } = useRealtimeRunWithStreams(runId, { - accessToken: publicAccessToken, - }); - - if (!streams.openai) { - return
Loading...
; - } - - const text = streams.openai.join(""); // `streams.openai` is an array of strings - - return ( -
-

OpenAI response:

-

{text}

-
- ); -} -``` - -### Using tools and `fullStream` - -When calling `streamText`, you can provide a `tools` object that allows the LLM to use additional tools. You can then access the tool call and results using the `fullStream` method: +When using tools with the AI SDK, you can access tool calls and results using the `fullStream`: ```ts import { openai } from "@ai-sdk/openai"; @@ -306,7 +245,7 @@ export const aiStreamingWithTools = schemaTask({ prompt: z .string() .default( - "Based on the temperature, will I need to wear extra clothes today in San Fransico? Please be detailed." + "Based on the temperature, will I need to wear extra clothes today in San Francisco? Please be detailed." ), }), run: async ({ model, prompt }) => { @@ -338,50 +277,9 @@ export const aiStreamingWithTools = schemaTask({ }); ``` -Now you can get access to the tool call and results in your frontend: +## Using toolTask -```tsx -import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks"; -import type { aiStreamingWithTools, STREAMS } from "./trigger/ai-streaming"; - -function MyComponent({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) { - const { streams } = useRealtimeRunWithStreams(runId, { - accessToken: publicAccessToken, - }); - - if (!streams.openai) { - return
Loading...
; - } - - // streams.openai is an array of TextStreamPart - const toolCall = streams.openai.find( - (stream) => stream.type === "tool-call" && stream.toolName === "getWeather" - ); - const toolResult = streams.openai.find((stream) => stream.type === "tool-result"); - const textDeltas = streams.openai.filter((stream) => stream.type === "text-delta"); - - const text = textDeltas.map((delta) => delta.textDelta).join(""); - const weatherLocation = toolCall ? toolCall.args.location : undefined; - const weather = toolResult ? toolResult.result.temperature : undefined; - - return ( -
-

OpenAI response:

-

{text}

-

Weather:

-

- {weatherLocation - ? `The weather in ${weatherLocation} is ${weather} degrees.` - : "No weather data"} -

-
- ); -} -``` - -### Using `toolTask` - -As you can see above, we defined a tool which will be used in the `aiStreamingWithTools` task. You can also define a Trigger.dev task that can be used as a tool, and will automatically be invoked with `triggerAndWait` when the tool is called. This is done using the `toolTask` function: +You can define a Trigger.dev task that can be used as a tool, and will automatically be invoked with `triggerAndWait` when the tool is called: ```ts import { openai } from "@ai-sdk/openai"; @@ -418,7 +316,7 @@ export const aiStreamingWithTools = schemaTask({ prompt: z .string() .default( - "Based on the temperature, will I need to wear extra clothes today in San Fransico? Please be detailed." + "Based on the temperature, will I need to wear extra clothes today in San Francisco? Please be detailed." ), }), run: async ({ model, prompt }) => { @@ -451,5 +349,3 @@ export const aiStreamingWithTools = schemaTask({ }, }); ``` - - diff --git a/docs/realtime/backend/subscribe.mdx b/docs/realtime/backend/subscribe.mdx new file mode 100644 index 0000000000..973ce1414c --- /dev/null +++ b/docs/realtime/backend/subscribe.mdx @@ -0,0 +1,225 @@ +--- +title: Subscribe functions +sidebarTitle: Subscribe +description: Subscribe to run updates using async iterators +--- + +These functions allow you to subscribe to run updates from your backend code. Each function returns an async iterator that yields run objects as they change. + +## runs.subscribeToRun + +Subscribes to all changes to a specific run. + +```ts Example +import { runs } from "@trigger.dev/sdk/v3"; + +for await (const run of runs.subscribeToRun("run_1234")) { + console.log(run); +} +``` + +This function subscribes to all changes to a run. It returns an async iterator that yields the run object whenever the run is updated. The iterator will complete when the run is finished. + +**Authentication**: This function supports both server-side and client-side authentication. For server-side authentication, use your API key. For client-side authentication, you must generate a public access token with read access to the specific run. See our [authentication guide](/realtime/auth) for details. + +**Response**: The AsyncIterator yields the [run object](/realtime/run-object). + +## runs.subscribeToRunsWithTag + +Subscribes to all changes to runs with a specific tag. + +```ts Example +import { runs } from "@trigger.dev/sdk/v3"; + +for await (const run of runs.subscribeToRunsWithTag("user:1234")) { + console.log(run); +} +``` + +This function subscribes to all changes to runs with a specific tag. It returns an async iterator that yields the run object whenever a run with the specified tag is updated. This iterator will never complete, so you must manually break out of the loop when you no longer want to receive updates. + +**Authentication**: This function supports both server-side and client-side authentication. For server-side authentication, use your API key. For client-side authentication, you must generate a public access token with read access to the specific tag. See our [authentication guide](/realtime/auth) for details. + +**Response**: The AsyncIterator yields the [run object](/realtime/run-object). + +## runs.subscribeToBatch + +Subscribes to all changes for runs in a batch. + +```ts Example +import { runs } from "@trigger.dev/sdk/v3"; + +for await (const run of runs.subscribeToBatch("batch_1234")) { + console.log(run); +} +``` + +This function subscribes to all changes for runs in a batch. It returns an async iterator that yields a run object whenever a run in the batch is updated. The iterator does not complete on its own, you must manually `break` the loop when you want to stop listening for updates. + +**Authentication**: This function supports both server-side and client-side authentication. For server-side authentication, use your API key. For client-side authentication, you must generate a public access token with read access to the specific batch. See our [authentication guide](/realtime/auth) for details. + +**Response**: The AsyncIterator yields the [run object](/realtime/run-object). + +## Type safety + +You can infer the types of the run's payload and output by passing the type of the task to the subscribe functions: + +```ts +import { runs, tasks } from "@trigger.dev/sdk/v3"; +import type { myTask } from "./trigger/my-task"; + +async function myBackend() { + const handle = await tasks.trigger("my-task", { some: "data" }); + + for await (const run of runs.subscribeToRun(handle.id)) { + // run.payload and run.output are now typed + console.log(run.payload.some); + + if (run.output) { + console.log(run.output.some); + } + } +} +``` + +When using `subscribeToRunsWithTag`, you can pass a union of task types: + +```ts +import { runs } from "@trigger.dev/sdk/v3"; +import type { myTask, myOtherTask } from "./trigger/my-task"; + +for await (const run of runs.subscribeToRunsWithTag("my-tag")) { + // Narrow down the type based on the taskIdentifier + switch (run.taskIdentifier) { + case "my-task": { + console.log("Run output:", run.output.foo); // Type-safe + break; + } + case "my-other-task": { + console.log("Run output:", run.output.bar); // Type-safe + break; + } + } +} +``` + +## Subscribe to metadata updates from your tasks + +The metadata API allows you to update custom metadata on runs and receive real-time updates when metadata changes. This is useful for tracking progress, storing intermediate results, or adding custom status information that can be monitored in real-time. + + + For frontend applications using React, see our [React hooks metadata + documentation](/realtime/react-hooks/subscribe#using-metadata-to-show-progress-in-your-ui) for + consuming metadata updates in your UI. + + +When you update metadata from within a task using `metadata.set()`, `metadata.append()`, or other metadata methods, all subscribers to that run will automatically receive the updated run object containing the new metadata. + +This makes metadata perfect for: + +- Progress tracking +- Status updates +- Intermediate results +- Custom notifications + +Use the metadata API within your task to update metadata in real-time. In this basic example task, we're updating the progress of a task as it processes items. + +### How to subscribe to metadata updates + +This example task updates the progress of a task as it processes items. + +```ts +// Your task code +import { task, metadata } from "@trigger.dev/sdk/v3"; + +export const progressTask = task({ + id: "progress-task", + run: async (payload: { items: string[] }) => { + const total = payload.items.length; + + for (let i = 0; i < payload.items.length; i++) { + // Update progress metadata + metadata.set("progress", { + current: i + 1, + total: total, + percentage: Math.round(((i + 1) / total) * 100), + currentItem: payload.items[i], + }); + + // Process the item + await processItem(payload.items[i]); + } + + metadata.set("status", "completed"); + return { processed: total }; + }, +}); + +async function processItem(item: string) { + // Simulate work + await new Promise((resolve) => setTimeout(resolve, 1000)); +} +``` + +We can now subscribe to the runs and receive real-time metadata updates. + +```ts +// Somewhere in your backend code +import { runs } from "@trigger.dev/sdk/v3"; +import type { progressTask } from "./trigger/progress-task"; + +async function monitorProgress(runId: string) { + for await (const run of runs.subscribeToRun(runId)) { + console.log(`Run ${run.id} status: ${run.status}`); + + if (run.metadata?.progress) { + const progress = run.metadata.progress as { + current: number; + total: number; + percentage: number; + currentItem: string; + }; + + console.log(`Progress: ${progress.current}/${progress.total} (${progress.percentage}%)`); + console.log(`Processing: ${progress.currentItem}`); + } + + if (run.metadata?.status === "completed") { + console.log("Task completed!"); + break; + } + } +} +``` + +For more information on how to write tasks that use the metadata API, as well as more examples, see our [run metadata docs](/runs/metadata#more-metadata-task-examples). + +### Type safety + +You can get type safety for your metadata by defining types: + +```ts +import { runs } from "@trigger.dev/sdk/v3"; +import type { progressTask } from "./trigger/progress-task"; + +interface ProgressMetadata { + progress?: { + current: number; + total: number; + percentage: number; + currentItem: string; + }; + status?: "running" | "completed" | "failed"; +} + +async function monitorTypedProgress(runId: string) { + for await (const run of runs.subscribeToRun(runId)) { + const metadata = run.metadata as ProgressMetadata; + + if (metadata?.progress) { + // Now you have full type safety + console.log(`Progress: ${metadata.progress.percentage}%`); + } + } +} +``` diff --git a/docs/realtime/how-it-works.mdx b/docs/realtime/how-it-works.mdx new file mode 100644 index 0000000000..594b9da926 --- /dev/null +++ b/docs/realtime/how-it-works.mdx @@ -0,0 +1,92 @@ +--- +title: How it works +sidebarTitle: How it works +description: Technical details about how the Trigger.dev Realtime API works +--- + +## Architecture + +The Realtime API is built on top of [Electric SQL](https://electric-sql.com/), an open-source PostgreSQL syncing engine. The Trigger.dev API wraps Electric SQL and provides a simple API to subscribe to [runs](/runs) and get real-time updates. + +## Run changes + +You will receive updates whenever a run changes for the following reasons: + +- The run moves to a new state. See our [run lifecycle docs](/runs#the-run-lifecycle) for more information. +- [Run tags](/tags) are added or removed. +- [Run metadata](/runs/metadata) is updated. + +## Run object + +The run object returned by Realtime subscriptions is optimized for streaming updates and differs from the management API's run object. See [the run object](/realtime/run-object) page for the complete schema and field descriptions. + +## Basic usage + +After you trigger a task, you can subscribe to the run using the `runs.subscribeToRun` function. This function returns an async iterator that you can use to get updates on the run status. + +```ts +import { runs, tasks } from "@trigger.dev/sdk/v3"; + +// Somewhere in your backend code +async function myBackend() { + const handle = await tasks.trigger("my-task", { some: "data" }); + + for await (const run of runs.subscribeToRun(handle.id)) { + // This will log the run every time it changes + console.log(run); + } +} +``` + +Every time the run changes, the async iterator will yield the updated run. You can use this to update your UI, log the run status, or take any other action. + +Alternatively, you can subscribe to changes to any run that includes a specific tag (or tags) using the `runs.subscribeToRunsWithTag` function. + +```ts +import { runs } from "@trigger.dev/sdk/v3"; + +// Somewhere in your backend code +for await (const run of runs.subscribeToRunsWithTag("user:1234")) { + // This will log the run every time it changes, for all runs with the tag "user:1234" + console.log(run); +} +``` + +If you've used `batchTrigger` to trigger multiple runs, you can also subscribe to changes to all the runs triggered in the batch using the `runs.subscribeToBatch` function. + +```ts +import { runs } from "@trigger.dev/sdk/v3"; + +// Somewhere in your backend code +for await (const run of runs.subscribeToBatch("batch-id")) { + // This will log the run every time it changes, for all runs in the batch with the ID "batch-id" + console.log(run); +} +``` + +## Run metadata + +The run metadata API gives you the ability to add or update custom metadata on a run, which will cause the run to be updated. This allows you to extend the Realtime API with custom data attached to a run that can be used for various purposes. Some common use cases include: + +- Adding a link to a related resource +- Adding a reference to a user or organization +- Adding a custom status with progress information + +See our [run metadata docs](/runs/metadata) for more on how to write tasks that use the metadata API. + +### Using metadata with Realtime & React hooks + +You can combine run metadata with the Realtime API to bridge the gap between your trigger.dev tasks and your applications in two ways: + +1. Using our [React hooks](/realtime/react-hooks/subscribe#using-metadata) to subscribe to metadata updates and update your UI in real-time. +2. Using our [backend functions](/realtime/backend) to subscribe to metadata updates in your backend. + +## Limits + +The Realtime API in the Trigger.dev Cloud limits the number of concurrent subscriptions, depending on your plan. If you exceed the limit, you will receive an error when trying to subscribe to a run. For more information, see our [pricing page](https://trigger.dev/pricing). + +## Learn more + +- Read our Realtime blog post ["How we built a real-time service that handles 20,000 updates per second](https://trigger.dev/blog/how-we-built-realtime) +- Using Realtime: [React Hooks (frontend)](/realtime/react-hooks) +- Using [Backend (server-side)](/realtime/backend) diff --git a/docs/realtime/overview.mdx b/docs/realtime/overview.mdx index 718181dc16..998a2e6295 100644 --- a/docs/realtime/overview.mdx +++ b/docs/realtime/overview.mdx @@ -1,272 +1,39 @@ --- title: Realtime overview sidebarTitle: Overview -description: Using the Trigger.dev v3 realtime API +description: Using the Trigger.dev Realtime API to trigger and/or subscribe to runs in real-time. --- -import RealtimeExamplesCards from "/snippets/realtime-examples-cards.mdx"; +Trigger.dev Realtime allows you to trigger, subscribe to, and get real-time updates for runs. This is useful for monitoring runs, updating UIs, and building real-time dashboards. -Trigger.dev Realtime is a set of APIs that allow you to subscribe to runs and get real-time updates on the run status. This is useful for monitoring runs, updating UIs, and building realtime dashboards. +You can subscribe to real-time updates for different scopes of runs: -## How it works +- **Specific runs** - Monitor individual run progress by run ID +- **Runs with specific tags** - Track all runs that have certain [tags](/tags) (e.g., all runs tagged with `user:123`) +- **Batch runs** - All runs within a specific batch +- **Trigger + subscribe combos** - Trigger a task and immediately subscribe to its run (frontend only) -The Realtime API is built on top of [Electric SQL](https://electric-sql.com/), an open-source PostgreSQL syncing engine. The Trigger.dev API wraps Electric SQL and provides a simple API to subscribe to [runs](/runs) and get real-time updates. +Once subscribed, you'll receive the complete [run object](/realtime/run-object) with automatic real-time updates whenever the [run changes](/realtime/how-it-works#run-changes), including: -## Walkthrough +- **Status changes** - queued β†’ executing β†’ completed, etc. +- **Metadata updates** - Custom progress tracking, status updates, user data, etc. ([React hooks](/realtime/react-hooks/subscribe#using-metadata-to-show-progress-in-your-ui) | [backend](/realtime/backend/subscribe#subscribe-to-metadata-updates-from-your-tasks)) +- **Tag changes** - When [tags](/tags) are added or removed from the run +- **Stream data** - Real-time data chunks from your tasks, perfect for AI/LLM streaming ([React hooks](/realtime/react-hooks/streams) | [backend](/realtime/backend/streams)) -