From d17fe9d97a99f19efdc5249138558db802cd624f Mon Sep 17 00:00:00 2001 From: D-K-P <8297864+D-K-P@users.noreply.github.com> Date: Fri, 25 Jul 2025 14:03:00 +0100 Subject: [PATCH 01/27] Reordered react hooks + frontend sections --- docs/docs.json | 79 +++- .../overview.mdx => realtime/auth.mdx} | 92 ++++- docs/realtime/backend/metadata.mdx | 225 +++++++++++ docs/realtime/backend/overview.mdx | 63 ++++ docs/realtime/{ => backend}/streams.mdx | 160 ++------ docs/realtime/backend/subscribe.mdx | 116 ++++++ docs/realtime/how-it-works.mdx | 91 +++++ docs/realtime/overview.mdx | 282 ++------------ docs/realtime/react-hooks.mdx | 7 - docs/realtime/react-hooks/metadata.mdx | 357 ++++++++++++++++++ .../react-hooks/overview.mdx | 35 +- .../react-hooks/realtime.mdx | 141 +------ docs/realtime/react-hooks/streams.mdx | 225 +++++++++++ .../react-hooks/triggering.mdx | 28 +- docs/realtime/run-object.mdx | 142 +++++++ docs/realtime/subscribe-to-batch.mdx | 49 --- docs/realtime/subscribe-to-run.mdx | 49 --- docs/realtime/subscribe-to-runs-with-tag.mdx | 49 --- 18 files changed, 1458 insertions(+), 732 deletions(-) rename docs/{frontend/overview.mdx => realtime/auth.mdx} (64%) create mode 100644 docs/realtime/backend/metadata.mdx create mode 100644 docs/realtime/backend/overview.mdx rename docs/realtime/{ => backend}/streams.mdx (60%) create mode 100644 docs/realtime/backend/subscribe.mdx create mode 100644 docs/realtime/how-it-works.mdx delete mode 100644 docs/realtime/react-hooks.mdx create mode 100644 docs/realtime/react-hooks/metadata.mdx rename docs/{frontend => realtime}/react-hooks/overview.mdx (88%) rename docs/{frontend => realtime}/react-hooks/realtime.mdx (65%) create mode 100644 docs/realtime/react-hooks/streams.mdx rename docs/{frontend => realtime}/react-hooks/triggering.mdx (92%) create mode 100644 docs/realtime/run-object.mdx delete mode 100644 docs/realtime/subscribe-to-batch.mdx delete mode 100644 docs/realtime/subscribe-to-run.mdx delete mode 100644 docs/realtime/subscribe-to-runs-with-tag.mdx diff --git a/docs/docs.json b/docs/docs.json index b88d903a93..12b1565064 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -112,30 +112,33 @@ ] }, { - "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/realtime", + "realtime/react-hooks/metadata", + "realtime/react-hooks/streams", + "realtime/react-hooks/triggering" + ] + }, { - "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/metadata", + "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": [ @@ -570,7 +573,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/realtime/auth.mdx similarity index 64% rename from docs/frontend/overview.mdx rename to docs/realtime/auth.mdx index 6df411bc3a..dd6af13ca8 100644 --- a/docs/frontend/overview.mdx +++ b/docs/realtime/auth.mdx @@ -1,14 +1,14 @@ --- -title: Overview & Authentication -sidebarTitle: Overview & Auth -description: Using the Trigger.dev SDK from your frontend application. +title: Authentication +sidebarTitle: Authentication +description: Authenticating real-time API requests with Public Access Tokens --- -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. +To use the Realtime API, you need to authenticate your requests with Public Access Tokens. These tokens provide secure, scoped access to your runs and can be used in both frontend and backend applications. -## Authentication +## Creating Public Access Tokens -To create a Public Access Token, you can use the `auth.createPublicToken` function in your **backend** code: +You can create a Public Access Token using the `auth.createPublicToken` function in your **backend** code: ```tsx import { auth } from "@trigger.dev/sdk/v3"; @@ -16,7 +16,7 @@ import { auth } from "@trigger.dev/sdk/v3"; const publicToken = await auth.createPublicToken(); // πŸ‘ˆ this public access token has no permissions, so is pretty useless! ``` -### Scopes +## Scopes By default a Public Access Token has no permissions. You must specify the scopes you need when creating a Public Access Token: @@ -103,7 +103,7 @@ const publicToken = await auth.createPublicToken({ }); ``` -### Expiration +## Expiration By default, Public Access Token's expire after 15 minutes. You can specify a different expiration time when creating a Public Access Token: @@ -166,6 +166,78 @@ const handle = await tasks.batchTrigger("my-task", [ console.log(handle.publicAccessToken); ``` -## Usage +## Trigger tokens -To learn how to use these Public Access Tokens, see our [React hooks](/frontend/react-hooks) guide. +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. + +```ts +import { auth } from "@trigger.dev/sdk/v3"; + +// Somewhere in your backend code +const triggerToken = await auth.createTriggerPublicToken("my-task"); +``` + +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", +}); +``` + +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"]); +``` + +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! +}); +``` + +## Using tokens + +Once you have a Public Access Token, you can use it to authenticate requests to the Realtime API: + +### Backend usage + +```ts +import { runs } from "@trigger.dev/sdk/v3"; + +// Using the token with backend functions +for await (const run of runs.subscribeToRun("run_1234", { + accessToken: publicToken, +})) { + console.log(run); +} +``` + +### Frontend usage + +```tsx +import { useRealtimeRun } from "@trigger.dev/react-hooks"; + +export function MyComponent({ runId, publicAccessToken }) { + const { run, error } = useRealtimeRun(runId, { + accessToken: publicAccessToken, + }); + + if (error) return
Error: {error.message}
; + return
Run: {run?.id}
; +} +``` + +See our [React hooks documentation](/realtime/react-hooks) for more details on using hooks in your frontend application. diff --git a/docs/realtime/backend/metadata.mdx b/docs/realtime/backend/metadata.mdx new file mode 100644 index 0000000000..7c35d6f4bf --- /dev/null +++ b/docs/realtime/backend/metadata.mdx @@ -0,0 +1,225 @@ +--- +title: Metadata +sidebarTitle: Metadata +description: Update and subscribe to run metadata in real-time from your backend +--- + +import RealtimeExamplesCards from "/snippets/realtime-examples-cards.mdx"; + +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/metadata) for consuming metadata updates in your UI. + + +## How metadata works with realtime + +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 + +## Updating metadata from tasks + +Use the metadata API within your task to update metadata in real-time: + +```ts +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)); +} +``` + +## Subscribing to metadata updates + +Subscribe to runs and receive real-time metadata updates: + +```ts +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; + } + } +} +``` + +## Common metadata patterns + +### Progress tracking + +Track progress with percentage and current step: + +```ts +import { task, metadata } from "@trigger.dev/sdk/v3"; + +export const batchProcessingTask = task({ + id: "batch-processing", + run: async (payload: { records: any[] }) => { + for (let i = 0; i < payload.records.length; i++) { + const record = payload.records[i]; + + // Update progress + metadata.set("progress", { + step: i + 1, + total: payload.records.length, + percentage: Math.round(((i + 1) / payload.records.length) * 100), + currentRecord: record.id, + }); + + await processRecord(record); + } + }, +}); +``` + +### Status updates with logs + +Append log entries while maintaining status: + +```ts +import { task, metadata } from "@trigger.dev/sdk/v3"; + +export const deploymentTask = task({ + id: "deployment", + run: async (payload: { version: string }) => { + metadata.set("status", "initializing"); + metadata.append("logs", "Starting deployment..."); + + // Step 1 + metadata.set("status", "building"); + metadata.append("logs", "Building application..."); + await buildApplication(); + + // Step 2 + metadata.set("status", "deploying"); + metadata.append("logs", "Deploying to production..."); + await deployToProduction(); + + // Step 3 + metadata.set("status", "verifying"); + metadata.append("logs", "Running health checks..."); + await runHealthChecks(); + + metadata.set("status", "completed"); + metadata.append("logs", "Deployment successful!"); + }, +}); +``` + +### User context and notifications + +Store user information and notification preferences: + +```ts +import { task, metadata } from "@trigger.dev/sdk/v3"; + +export const userTask = task({ + id: "user-task", + run: async (payload: { userId: string; action: string }) => { + // Set user context in metadata + metadata.set("user", { + id: payload.userId, + action: payload.action, + startedAt: new Date().toISOString(), + }); + + // Update status for user notifications + metadata.set("notification", { + type: "info", + message: `Starting ${payload.action} for user ${payload.userId}`, + }); + + await performUserAction(payload); + + metadata.set("notification", { + type: "success", + message: `${payload.action} completed successfully`, + }); + }, +}); +``` + +## 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}%`); + } + } +} +``` + +## Authentication + +Backend metadata subscriptions support 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, task, or tag. See our [authentication guide](/realtime/auth) for details. + +{" "} diff --git a/docs/realtime/backend/overview.mdx b/docs/realtime/backend/overview.mdx new file mode 100644 index 0000000000..931493b4c4 --- /dev/null +++ b/docs/realtime/backend/overview.mdx @@ -0,0 +1,63 @@ +--- +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 + +The backend API provides three main categories of functionality: + +- **[Subscribe functions](/realtime/backend/subscribe)** - Subscribe to run updates using async iterators +- **[Metadata](/realtime/backend/metadata)** - Update and subscribe to run metadata in real-time +- **[Streams](/realtime/backend/streams)** - Emit and consume real-time streaming data from your tasks + +## 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}`); +} +``` + +Stream data from a task: + +```ts +import { task, metadata } from "@trigger.dev/sdk/v3"; + +export const myTask = task({ + id: "my-task", + run: async (payload) => { + // Stream real-time data + const stream = await metadata.stream("openai", openaiCompletion); + + // Process the stream + for await (const chunk of stream) { + console.log("Streaming chunk:", chunk); + } + }, +}); +``` + +## 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. + +{" "} 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..a291b3ee8b 100644 --- a/docs/realtime/streams.mdx +++ b/docs/realtime/backend/streams.mdx @@ -1,20 +1,25 @@ --- -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 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. +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. -## How it works + + For frontend applications using React, see our [React hooks streams + documentation](/realtime/react-hooks/streams) for consuming streams in your UI. + + +## 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 +61,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 +88,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 +101,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 +114,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 +123,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 +137,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 +145,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 +166,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. +## Using with the AI SDK -## Usage 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 +211,9 @@ export const aiStreaming = schemaTask({ }); ``` -And then render the stream in your frontend: - -```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` +## Using AI SDK with tools -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 +247,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 +279,9 @@ export const aiStreamingWithTools = schemaTask({ }); ``` -Now you can get access to the tool call and results in your frontend: - -```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` +## 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 +318,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 }) => { @@ -452,4 +352,4 @@ export const aiStreamingWithTools = schemaTask({ }); ``` - +{" "} diff --git a/docs/realtime/backend/subscribe.mdx b/docs/realtime/backend/subscribe.mdx new file mode 100644 index 0000000000..5f9ae34440 --- /dev/null +++ b/docs/realtime/backend/subscribe.mdx @@ -0,0 +1,116 @@ +--- +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; + } + } +} +``` diff --git a/docs/realtime/how-it-works.mdx b/docs/realtime/how-it-works.mdx new file mode 100644 index 0000000000..ed839eee25 --- /dev/null +++ b/docs/realtime/how-it-works.mdx @@ -0,0 +1,91 @@ +--- +title: How it works +sidebarTitle: How it works +description: Technical details about the Trigger.dev Realtime API +--- + +## 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 reference](/realtime/run-object) for the complete schema and field descriptions. + +## Type-safety + +You can infer the types of the run's payload and output by passing the type of the task to the `subscribeToRun` function. This will give you type-safe access to the run's payload and output. + +```ts +import { runs, tasks } from "@trigger.dev/sdk/v3"; +import type { myTask } from "./trigger/my-task"; + +// 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.payload.some); + + if (run.output) { + // This will log the output if it exists + console.log(run.output.some); + } + } +} +``` + +When using `subscribeToRunsWithTag`, you can pass a union of task types for all the possible tasks that can have the tag. + +```ts +import { runs } from "@trigger.dev/sdk/v3"; +import type { myTask, myOtherTask } from "./trigger/my-task"; + +// Somewhere in your backend code +for await (const run of runs.subscribeToRunsWithTag("my-tag")) { + // You can narrow down the type based on the taskIdentifier + switch (run.taskIdentifier) { + case "my-task": { + console.log("Run output:", run.output.foo); // This will be type-safe + break; + } + case "my-other-task": { + console.log("Run output:", run.output.bar); // This will be type-safe + break; + } + } +} +``` + +## 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 use this feature. + +### Using w/Realtime & React hooks + +We suggest combining run metadata with the realtime API and our [React hooks](/realtime/react-hooks) to bridge the gap between your trigger.dev tasks and your UI. This allows you to update your UI in real-time based on changes to the run metadata. As a simple example, you could add a custom status to a run with a progress value, and update your UI based on that progress. + +We have a full demo app repo available [here](https://github.com/triggerdotdev/nextjs-realtime-simple-demo) + +## 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). + +## Known issues + +There is currently a known issue where the realtime API does not work if subscribing to a run that has a large payload or large output and are stored in object store instead of the database. We are working on a fix for this issue: https://github.com/triggerdotdev/trigger.dev/issues/1451. As a workaround you'll need to keep payloads and outputs below 128KB when using the realtime API. diff --git a/docs/realtime/overview.mdx b/docs/realtime/overview.mdx index 718181dc16..6c9cbb34f9 100644 --- a/docs/realtime/overview.mdx +++ b/docs/realtime/overview.mdx @@ -1,272 +1,70 @@ --- title: Realtime overview sidebarTitle: Overview -description: Using the Trigger.dev v3 realtime API +description: Using the Trigger.dev Realtime API to subscribe to runs in real-time. --- import RealtimeExamplesCards from "/snippets/realtime-examples-cards.mdx"; -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. +Trigger.dev Realtime allows you subscribe to runs and get real-time updates as they execute. This is useful for monitoring runs, updating UIs, and building real-time dashboards. -## How it works +## What you can subscribe to -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. +You can subscribe to real-time updates for: -## Walkthrough +- **Specific runs** - Monitor individual run progress +- **Runs with specific tags** - Track runs by category or user +- **Task runs** - All runs for specific tasks +- **Batch runs** - All runs in a batch -