chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:32:57 +08:00
commit cd420f9332
4811 changed files with 884702 additions and 0 deletions
+159
View File
@@ -0,0 +1,159 @@
---
title: Input Streams
sidebarTitle: Input Streams
description: Send data into running tasks from your backend code
---
The Input Streams API allows you to send data into running Trigger.dev tasks from your backend code. This enables bidirectional communication — while [output streams](/realtime/backend/streams) let you read data from tasks, input streams let you push data into them.
<Note>
To learn how to receive input stream data inside your tasks, see [Input
Streams](/tasks/streams#input-streams) in the Streams doc.
</Note>
<Tip>
Input streams are keyed by `runId` — they're correct for sending data to a specific live run. If you need a bidirectional channel that survives run boundaries (e.g. a chat that resumes tomorrow, an agent coordinated across many runs), look at [`chat.agent`](/ai-chat/overview): it's built on a durable Session row that owns its runs and exposes the same consumer-side API (`on` / `once` / `wait` / `waitWithIdleTimeout`) on its `.in` channel.
</Tip>
## Sending data to a running task
### Using defined input streams (Recommended)
The recommended approach is to use [defined input streams](/tasks/streams#defining-input-streams) for full type safety:
```ts
import { cancelSignal, approval } from "./trigger/streams";
// Cancel a running AI stream
await cancelSignal.send(runId, { reason: "User clicked stop" });
// Approve a draft
await approval.send(runId, { approved: true, reviewer: "alice@example.com" });
```
The `.send()` method is fully typed — the data parameter must match the generic type you defined on the input stream.
<Note>
`.send()` works the same regardless of how the task is listening — whether it uses `.wait()`
(suspending), `.once()` (non-suspending), or `.on()` (continuous). The sender doesn't need to know
how the task is consuming the data. See [Input Streams](/tasks/streams#input-streams) for details on each
receiving method.
</Note>
## Practical examples
### Cancel from a Next.js API route
```ts app/api/cancel/route.ts
import { cancelStream } from "@/trigger/streams";
export async function POST(req: Request) {
const { runId } = await req.json();
await cancelStream.send(runId, { reason: "User clicked stop" });
return Response.json({ cancelled: true });
}
```
### Approval workflow API
```ts app/api/approve/route.ts
import { approval } from "@/trigger/streams";
export async function POST(req: Request) {
const { runId, approved, reviewer } = await req.json();
await approval.send(runId, {
approved,
reviewer,
});
return Response.json({ success: true });
}
```
### Remix action handler
```ts app/routes/api.approve.ts
import { json, type ActionFunctionArgs } from "@remix-run/node";
import { approval } from "~/trigger/streams";
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const runId = formData.get("runId") as string;
const approved = formData.get("approved") === "true";
const reviewer = formData.get("reviewer") as string;
await approval.send(runId, { approved, reviewer });
return json({ success: true });
}
```
### Express handler
```ts
import express from "express";
import { cancelSignal } from "./trigger/streams";
const app = express();
app.use(express.json());
app.post("/api/cancel", async (req, res) => {
const { runId, reason } = req.body;
await cancelSignal.send(runId, { reason });
res.json({ cancelled: true });
});
```
### Sending from another task
You can send input stream data from one task to another running task:
```ts
import { task } from "@trigger.dev/sdk";
import { approval } from "./streams";
export const reviewerTask = task({
id: "auto-reviewer",
run: async (payload: { targetRunId: string }) => {
// Perform automated review logic...
const isApproved = await performReview();
// Send approval to the waiting task
await approval.send(payload.targetRunId, {
approved: isApproved,
reviewer: "auto-reviewer",
});
},
});
```
## Error handling
The `.send()` method will throw if:
- The run has already completed, failed, or been canceled
- The payload exceeds the 1MB size limit
- The run ID is invalid
```ts
import { cancelSignal } from "./trigger/streams";
try {
await cancelSignal.send(runId, { reason: "User clicked stop" });
} catch (error) {
console.error("Failed to send:", error);
// Handle the error — the run may have already completed
}
```
## Important notes
- Maximum payload size per `.send()` call is **1MB**
- You cannot send data to a completed, failed, or canceled run
- Data sent before a listener is registered inside the task is **buffered** and delivered when a listener attaches
- Input streams require the current streams implementation (v2 is the default in SDK 4.1.0+). See [Streams](/tasks/streams) for details.
+45
View File
@@ -0,0 +1,45 @@
---
title: "Subscribe to tasks from your backend"
sidebarTitle: Overview
description: "Subscribe to run progress, stream AI output, and react to task status changes from your backend code or other tasks."
---
import RealtimeExamplesCards from "/snippets/realtime-examples-cards.mdx";
**Subscribe to runs from your server-side code or other tasks using async iterators.** Get status updates, metadata changes, and streamed data without polling.
## What's available
| Category | What it does | Guide |
|---|---|---|
| **Run updates** | Subscribe to run status, metadata, and tag changes | [Run updates](/realtime/backend/subscribe) |
| **Streaming** | Read AI output, file chunks, or any continuous data from tasks | [Streaming](/realtime/backend/streams) |
<Note>
To learn how to emit streams from your tasks, see [Streaming data from tasks](/tasks/streams).
</Note>
## 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";
// 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}`);
}
```
+418
View File
@@ -0,0 +1,418 @@
---
title: "Stream data to your backend (AI, files)"
sidebarTitle: "Streaming"
description: "Read AI/LLM output, file chunks, and other streaming data from your Trigger.dev tasks in backend code."
---
**Read streaming data from your tasks in backend code.** Consume AI completions as they generate, process file chunks, or handle any continuous data your tasks produce.
<Note>
To emit streams from your tasks, see [Streaming data from tasks](/tasks/streams). For React components, see [Streaming in React](/realtime/react-hooks/streams).
</Note>
<Tip>
Run-scoped streams are the right primitive for ephemeral I/O that lives inside a single run's lifetime. For durable, long-lived channels that outlive a run, see [`chat.agent`](/ai-chat/overview): it's built on a Session row that owns the chat's runs and exposes bidirectional `.in` / `.out` channels addressed by a durable id.
</Tip>
## Reading streams
### Using defined streams (Recommended)
The recommended approach is to use [defined streams](/tasks/streams#defining-typed-streams-recommended) for full type safety:
```ts
import { streams } from "@trigger.dev/sdk";
import { aiStream } from "./trigger/streams";
async function consumeStream(runId: string) {
// Read from the defined stream
const stream = await aiStream.read(runId);
let fullText = "";
for await (const chunk of stream) {
console.log("Received chunk:", chunk); // chunk is typed!
fullText += chunk;
}
console.log("Final text:", fullText);
}
```
### Direct stream reading
If you prefer not to use defined streams, you can read directly by specifying the stream key:
```ts
import { streams } from "@trigger.dev/sdk";
async function consumeStream(runId: string) {
// Read from a stream by key
const stream = await streams.read<string>(runId, "ai-output");
for await (const chunk of stream) {
console.log("Received chunk:", chunk);
}
}
```
### Reading from the default stream
Every run has a default stream, so you can omit the stream key:
```ts
import { streams } from "@trigger.dev/sdk";
async function consumeDefaultStream(runId: string) {
// Read from the default stream
const stream = await streams.read<string>(runId);
for await (const chunk of stream) {
console.log("Received chunk:", chunk);
}
}
```
## Stream options
The `read()` method accepts several options for controlling stream behavior:
### Timeout
Set a timeout to stop reading if no data is received within a specified time:
```ts
import { streams } from "@trigger.dev/sdk";
import { aiStream } from "./trigger/streams";
async function consumeWithTimeout(runId: string) {
const stream = await aiStream.read(runId, {
timeoutInSeconds: 120, // Wait up to 2 minutes for data
});
try {
for await (const chunk of stream) {
console.log("Received chunk:", chunk);
}
} catch (error) {
if (error.name === "TimeoutError") {
console.log("Stream timed out");
}
}
}
```
### Start index
Resume reading from a specific chunk index (useful for reconnection scenarios):
```ts
import { streams } from "@trigger.dev/sdk";
import { aiStream } from "./trigger/streams";
async function resumeStream(runId: string, lastChunkIndex: number) {
// Start reading from the chunk after the last one we received
const stream = await aiStream.read(runId, {
startIndex: lastChunkIndex + 1,
});
for await (const chunk of stream) {
console.log("Received chunk:", chunk);
}
}
```
### Abort signal
Use an `AbortSignal` to cancel stream reading:
```ts
import { streams } from "@trigger.dev/sdk";
import { aiStream } from "./trigger/streams";
async function consumeWithCancellation(runId: string) {
const controller = new AbortController();
// Cancel after 30 seconds
setTimeout(() => controller.abort(), 30000);
const stream = await aiStream.read(runId, {
signal: controller.signal,
});
try {
for await (const chunk of stream) {
console.log("Received chunk:", chunk);
// Optionally abort based on content
if (chunk.includes("STOP")) {
controller.abort();
}
}
} catch (error) {
if (error.name === "AbortError") {
console.log("Stream was cancelled");
}
}
}
```
### Combining options
You can combine multiple options:
```ts
import { streams } from "@trigger.dev/sdk";
import { aiStream } from "./trigger/streams";
async function advancedStreamConsumption(runId: string) {
const controller = new AbortController();
const stream = await aiStream.read(runId, {
timeoutInSeconds: 300, // 5 minute timeout
startIndex: 0, // Start from the beginning
signal: controller.signal, // Allow cancellation
});
try {
for await (const chunk of stream) {
console.log("Received chunk:", chunk);
}
} catch (error) {
if (error.name === "AbortError") {
console.log("Stream was cancelled");
} else if (error.name === "TimeoutError") {
console.log("Stream timed out");
} else {
console.error("Stream error:", error);
}
}
}
```
## Practical examples
### Reading AI streaming responses
Here's a complete example of consuming an AI stream from your backend:
```ts
import { streams } from "@trigger.dev/sdk";
import { aiStream } from "./trigger/streams";
async function consumeAIStream(runId: string) {
const stream = await aiStream.read(runId, {
timeoutInSeconds: 300, // AI responses can take time
});
let fullResponse = "";
const chunks: string[] = [];
for await (const chunk of stream) {
chunks.push(chunk);
fullResponse += chunk;
// Process each chunk as it arrives
console.log("Chunk received:", chunk);
// Could send to websocket, SSE, etc.
// await sendToClient(chunk);
}
console.log("Stream complete!");
console.log("Total chunks:", chunks.length);
console.log("Full response:", fullResponse);
return { fullResponse, chunks };
}
```
### Reading multiple streams
If a task emits multiple streams, you can read them concurrently or sequentially:
```ts
import { streams } from "@trigger.dev/sdk";
import { aiStream, progressStream } from "./trigger/streams";
async function consumeMultipleStreams(runId: string) {
// Read streams concurrently
const [aiData, progressData] = await Promise.all([
consumeStream(aiStream, runId),
consumeStream(progressStream, runId),
]);
return { aiData, progressData };
}
async function consumeStream<T>(
streamDef: { read: (runId: string) => Promise<AsyncIterableStream<T>> },
runId: string
): Promise<T[]> {
const stream = await streamDef.read(runId);
const chunks: T[] = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
return chunks;
}
```
### Piping streams to HTTP responses
You can pipe streams directly to HTTP responses for server-sent events (SSE):
```ts
import { streams } from "@trigger.dev/sdk";
import { aiStream } from "./trigger/streams";
import type { NextRequest } from "next/server";
export async function GET(request: NextRequest) {
const runId = request.nextUrl.searchParams.get("runId");
if (!runId) {
return new Response("Missing runId", { status: 400 });
}
const stream = await aiStream.read(runId, {
timeoutInSeconds: 300,
});
// Create a readable stream for SSE
const encoder = new TextEncoder();
const readableStream = new ReadableStream({
async start(controller) {
try {
for await (const chunk of stream) {
// Format as SSE
const data = `data: ${JSON.stringify({ chunk })}\n\n`;
controller.enqueue(encoder.encode(data));
}
controller.close();
} catch (error) {
controller.error(error);
}
},
});
return new Response(readableStream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
},
});
}
```
### Implementing retry logic
Handle transient errors with retry logic:
```ts
import { streams } from "@trigger.dev/sdk";
import { aiStream } from "./trigger/streams";
async function consumeStreamWithRetry(
runId: string,
maxRetries = 3
): Promise<string[]> {
let lastChunkIndex = 0;
const allChunks: string[] = [];
let attempt = 0;
while (attempt < maxRetries) {
try {
const stream = await aiStream.read(runId, {
startIndex: lastChunkIndex,
timeoutInSeconds: 120,
});
for await (const chunk of stream) {
allChunks.push(chunk);
lastChunkIndex++;
}
// Success! Break out of retry loop
break;
} catch (error) {
attempt++;
if (attempt >= maxRetries) {
throw new Error(`Failed after ${maxRetries} attempts: ${error.message}`);
}
console.log(`Retry attempt ${attempt} after error:`, error.message);
// Wait before retrying (exponential backoff)
await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
}
}
return allChunks;
}
```
### Processing streams in chunks
Process streams in batches for efficiency:
```ts
import { streams } from "@trigger.dev/sdk";
import { aiStream } from "./trigger/streams";
async function processStreamInBatches(runId: string, batchSize = 10) {
const stream = await aiStream.read(runId);
let batch: string[] = [];
for await (const chunk of stream) {
batch.push(chunk);
if (batch.length >= batchSize) {
// Process the batch
await processBatch(batch);
batch = [];
}
}
// Process remaining chunks
if (batch.length > 0) {
await processBatch(batch);
}
}
async function processBatch(chunks: string[]) {
console.log(`Processing batch of ${chunks.length} chunks`);
// Do something with the batch
// e.g., save to database, send to queue, etc.
}
```
## Using with `runs.subscribeToRun()`
For more advanced use cases where you need both the run status and streams, you can use the `runs.subscribeToRun()` method with `.withStreams()`:
```ts
import { runs } from "@trigger.dev/sdk";
import type { myTask } from "./trigger/myTask";
async function subscribeToRunAndStreams(runId: string) {
for await (const update of runs.subscribeToRun<typeof myTask>(runId).withStreams()) {
switch (update.type) {
case "run":
console.log("Run update:", update.run.status);
break;
case "default":
console.log("Stream chunk:", update.chunk);
break;
}
}
}
```
<Note>
For most use cases, we recommend using `streams.read()` with defined streams for better type safety and clearer code. Use `runs.subscribeToRun().withStreams()` only when you need to track both run status and stream data simultaneously.
</Note>
+225
View File
@@ -0,0 +1,225 @@
---
title: "Run updates (backend)"
sidebarTitle: "Run updates"
description: "Subscribe to run status changes, metadata updates, and tag changes from your backend code using async iterators."
---
**Subscribe to runs from your backend and get updates whenever status, metadata, or tags change.** Each function returns an async iterator that yields the run object on every change.
## runs.subscribeToRun
Subscribes to all changes to a specific run.
```ts Example
import { runs } from "@trigger.dev/sdk";
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";
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";
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";
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<typeof myTask>(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";
import type { myTask, myOtherTask } from "./trigger/my-task";
for await (const run of runs.subscribeToRunsWithTag<typeof myTask | typeof myOtherTask>("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.
<Note>
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.
</Note>
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";
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";
import type { progressTask } from "./trigger/progress-task";
async function monitorProgress(runId: string) {
for await (const run of runs.subscribeToRun<typeof progressTask>(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";
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<typeof progressTask>(runId)) {
const metadata = run.metadata as ProgressMetadata;
if (metadata?.progress) {
// Now you have full type safety
console.log(`Progress: ${metadata.progress.percentage}%`);
}
}
}
```