chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
# Trigger.dev Realtime (v4)
|
||||
|
||||
**Real-time monitoring and updates for runs**
|
||||
|
||||
## Core Concepts
|
||||
|
||||
Realtime allows you to:
|
||||
|
||||
- Subscribe to run status changes, metadata updates, and streams
|
||||
- Build real-time dashboards and UI updates
|
||||
- Monitor task progress from frontend and backend
|
||||
|
||||
## Authentication
|
||||
|
||||
### Public Access Tokens
|
||||
|
||||
```ts
|
||||
import { auth } from "@trigger.dev/sdk";
|
||||
|
||||
// Read-only token for specific runs
|
||||
const publicToken = await auth.createPublicToken({
|
||||
scopes: {
|
||||
read: {
|
||||
runs: ["run_123", "run_456"],
|
||||
tasks: ["my-task-1", "my-task-2"],
|
||||
},
|
||||
},
|
||||
expirationTime: "1h", // Default: 15 minutes
|
||||
});
|
||||
```
|
||||
|
||||
### Trigger Tokens (Frontend only)
|
||||
|
||||
```ts
|
||||
// Single-use token for triggering tasks
|
||||
const triggerToken = await auth.createTriggerPublicToken("my-task", {
|
||||
expirationTime: "30m",
|
||||
});
|
||||
```
|
||||
|
||||
## Backend Usage
|
||||
|
||||
### Subscribe to Runs
|
||||
|
||||
```ts
|
||||
import { runs, tasks } from "@trigger.dev/sdk";
|
||||
|
||||
// Trigger and subscribe
|
||||
const handle = await tasks.trigger("my-task", { data: "value" });
|
||||
|
||||
// Subscribe to specific run
|
||||
for await (const run of runs.subscribeToRun<typeof myTask>(handle.id)) {
|
||||
console.log(`Status: ${run.status}, Progress: ${run.metadata?.progress}`);
|
||||
if (run.status === "COMPLETED") break;
|
||||
}
|
||||
|
||||
// Subscribe to runs with tag
|
||||
for await (const run of runs.subscribeToRunsWithTag("user-123")) {
|
||||
console.log(`Tagged run ${run.id}: ${run.status}`);
|
||||
}
|
||||
|
||||
// Subscribe to batch
|
||||
for await (const run of runs.subscribeToBatch(batchId)) {
|
||||
console.log(`Batch run ${run.id}: ${run.status}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Streams
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk";
|
||||
|
||||
// Task that streams data
|
||||
export type STREAMS = {
|
||||
openai: OpenAI.ChatCompletionChunk;
|
||||
};
|
||||
|
||||
export const streamingTask = task({
|
||||
id: "streaming-task",
|
||||
run: async (payload) => {
|
||||
const completion = await openai.chat.completions.create({
|
||||
model: "gpt-4",
|
||||
messages: [{ role: "user", content: payload.prompt }],
|
||||
stream: true,
|
||||
});
|
||||
|
||||
// Register stream
|
||||
const stream = await metadata.stream("openai", completion);
|
||||
|
||||
let text = "";
|
||||
for await (const chunk of stream) {
|
||||
text += chunk.choices[0]?.delta?.content || "";
|
||||
}
|
||||
|
||||
return { text };
|
||||
},
|
||||
});
|
||||
|
||||
// Subscribe to streams
|
||||
for await (const part of runs.subscribeToRun(runId).withStreams<STREAMS>()) {
|
||||
switch (part.type) {
|
||||
case "run":
|
||||
console.log("Run update:", part.run.status);
|
||||
break;
|
||||
case "openai":
|
||||
console.log("Stream chunk:", part.chunk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## React Frontend Usage
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
npm add @trigger.dev/react-hooks
|
||||
```
|
||||
|
||||
### Triggering Tasks
|
||||
|
||||
```tsx
|
||||
"use client";
|
||||
import { useTaskTrigger, useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
|
||||
import type { myTask } from "../trigger/tasks";
|
||||
|
||||
function TriggerComponent({ accessToken }: { accessToken: string }) {
|
||||
// Basic trigger
|
||||
const { submit, handle, isLoading } = useTaskTrigger<typeof myTask>("my-task", {
|
||||
accessToken,
|
||||
});
|
||||
|
||||
// Trigger with realtime updates
|
||||
const {
|
||||
submit: realtimeSubmit,
|
||||
run,
|
||||
isLoading: isRealtimeLoading,
|
||||
} = useRealtimeTaskTrigger<typeof myTask>("my-task", { accessToken });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => submit({ data: "value" })} disabled={isLoading}>
|
||||
Trigger Task
|
||||
</button>
|
||||
|
||||
<button onClick={() => realtimeSubmit({ data: "realtime" })} disabled={isRealtimeLoading}>
|
||||
Trigger with Realtime
|
||||
</button>
|
||||
|
||||
{run && <div>Status: {run.status}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Subscribing to Runs
|
||||
|
||||
```tsx
|
||||
"use client";
|
||||
import { useRealtimeRun, useRealtimeRunsWithTag } from "@trigger.dev/react-hooks";
|
||||
import type { myTask } from "../trigger/tasks";
|
||||
|
||||
function SubscribeComponent({ runId, accessToken }: { runId: string; accessToken: string }) {
|
||||
// Subscribe to specific run
|
||||
const { run, error } = useRealtimeRun<typeof myTask>(runId, {
|
||||
accessToken,
|
||||
onComplete: (run) => {
|
||||
console.log("Task completed:", run.output);
|
||||
},
|
||||
});
|
||||
|
||||
// Subscribe to tagged runs
|
||||
const { runs } = useRealtimeRunsWithTag("user-123", { accessToken });
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
if (!run) return <div>Loading...</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>Status: {run.status}</div>
|
||||
<div>Progress: {run.metadata?.progress || 0}%</div>
|
||||
{run.output && <div>Result: {JSON.stringify(run.output)}</div>}
|
||||
|
||||
<h3>Tagged Runs:</h3>
|
||||
{runs.map((r) => (
|
||||
<div key={r.id}>
|
||||
{r.id}: {r.status}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Streams with React
|
||||
|
||||
```tsx
|
||||
"use client";
|
||||
import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks";
|
||||
import type { streamingTask, STREAMS } from "../trigger/tasks";
|
||||
|
||||
function StreamComponent({ runId, accessToken }: { runId: string; accessToken: string }) {
|
||||
const { run, streams } = useRealtimeRunWithStreams<typeof streamingTask, STREAMS>(runId, {
|
||||
accessToken,
|
||||
});
|
||||
|
||||
const text = streams.openai
|
||||
.filter((chunk) => chunk.choices[0]?.delta?.content)
|
||||
.map((chunk) => chunk.choices[0].delta.content)
|
||||
.join("");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>Status: {run?.status}</div>
|
||||
<div>Streamed Text: {text}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Wait Tokens
|
||||
|
||||
```tsx
|
||||
"use client";
|
||||
import { useWaitToken } from "@trigger.dev/react-hooks";
|
||||
|
||||
function WaitTokenComponent({ tokenId, accessToken }: { tokenId: string; accessToken: string }) {
|
||||
const { complete } = useWaitToken(tokenId, { accessToken });
|
||||
|
||||
return <button onClick={() => complete({ approved: true })}>Approve Task</button>;
|
||||
}
|
||||
```
|
||||
|
||||
### SWR Hooks (Fetch Once)
|
||||
|
||||
```tsx
|
||||
"use client";
|
||||
import { useRun } from "@trigger.dev/react-hooks";
|
||||
import type { myTask } from "../trigger/tasks";
|
||||
|
||||
function SWRComponent({ runId, accessToken }: { runId: string; accessToken: string }) {
|
||||
const { run, error, isLoading } = useRun<typeof myTask>(runId, {
|
||||
accessToken,
|
||||
refreshInterval: 0, // Disable polling (recommended)
|
||||
});
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
return <div>Run: {run?.status}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## Run Object Properties
|
||||
|
||||
Key properties available in run subscriptions:
|
||||
|
||||
- `id`: Unique run identifier
|
||||
- `status`: `QUEUED`, `EXECUTING`, `COMPLETED`, `FAILED`, `CANCELED`, etc.
|
||||
- `payload`: Task input data (typed)
|
||||
- `output`: Task result (typed, when completed)
|
||||
- `metadata`: Real-time updatable data
|
||||
- `createdAt`, `updatedAt`: Timestamps
|
||||
- `costInCents`: Execution cost
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Use Realtime over SWR**: Recommended for most use cases due to rate limits
|
||||
- **Scope tokens properly**: Only grant necessary read/trigger permissions
|
||||
- **Handle errors**: Always check for errors in hooks and subscriptions
|
||||
- **Type safety**: Use task types for proper payload/output typing
|
||||
- **Cleanup subscriptions**: Backend subscriptions auto-complete, frontend hooks auto-cleanup
|
||||
Reference in New Issue
Block a user