This package provides vanilla JavaScript support for Trigger.dev's Realtime functionality, allowing you to subscribe to task runs and receive real-time updates without React dependencies.
npm install @trigger.dev/sdk
import { ApiClient } from "@trigger.dev/core/v3"; import { createRealtimeRun } from "@trigger.dev/sdk/v3/browser"; // Create an API client const apiClient = new ApiClient( "https://api.trigger.dev", "your-access-token" ); // Subscribe to a run const subscription = createRealtimeRun("run-id-123", apiClient);
Subscribe to realtime updates of a single task run.
Parameters:
runId: string- The unique identifier of the run to subscribe toapiClient: ApiClient- The API client instanceoptions?: RealtimeSingleRunOptions<TTask>- Configuration options
Returns: RealtimeRunInstance<TTask>
Example:
const subscription = createRealtimeRun("run-id-123", apiClient, { onComplete: (run, error) => { if (error) { console.error("Run failed:", error); } else { console.log("Run completed:", run); } }, stopOnCompletion: true, skipColumns: ["payload", "output"] }); // Access the current run state console.log("Current run:", subscription.run); console.log("Has error:", subscription.error); console.log("Is complete:", subscription.isComplete); // Stop the subscription subscription.stop();
Subscribe to realtime updates of a task run with associated data streams.
Parameters:
runId: string- The unique identifier of the run to subscribe toapiClient: ApiClient- The API client instanceoptions?: RealtimeSingleRunOptions<TTask>- Configuration options
Returns: RealtimeRunWithStreamsInstance<TTask, TStreams>
Example:
const subscription = createRealtimeRunWithStreams< typeof myTask, { output: string; logs: string[] } >("run-id-123", apiClient, { experimental_throttleInMs: 100, // Throttle stream updates onComplete: (run, error) => { console.log("Run completed with streams:", run); console.log("Final stream data:", subscription.streams); } }); // Access run and stream data console.log("Current run:", subscription.run); console.log("Stream data:", subscription.streams); console.log("Output chunks:", subscription.streams.output); console.log("Log chunks:", subscription.streams.logs);
Subscribe to realtime updates of task runs filtered by tag(s).
Parameters:
tag: string | string[]- The tag or array of tags to filter runs byapiClient: ApiClient- The API client instanceoptions?: RealtimeRunsWithTagOptions- Configuration options
Returns: RealtimeRunsInstance<TTask>
Example:
// Single tag const subscription = createRealtimeRunsWithTag("my-tag", apiClient, { createdAt: "1h", // Only runs from the last hour skipColumns: ["payload", "output"] }); // Multiple tags const subscription = createRealtimeRunsWithTag(["tag1", "tag2"], apiClient); // Access the runs array console.log("Current runs:", subscription.runs); console.log("Number of runs:", subscription.runs.length); // Filter runs by status const completedRuns = subscription.runs.filter(run => run.isCompleted);
Subscribe to realtime updates of a batch of task runs.
Parameters:
batchId: string- The unique identifier of the batch to subscribe toapiClient: ApiClient- The API client instanceoptions?: RealtimeRunOptions- Configuration options
Returns: RealtimeRunsInstance<TTask>
Example:
const subscription = createRealtimeBatch("batch-id-123", apiClient); // Access the runs array console.log("Batch runs:", subscription.runs); // Calculate batch progress const totalRuns = subscription.runs.length; const completedRuns = subscription.runs.filter(run => run.isCompleted).length; const progress = totalRuns > 0 ? (completedRuns / totalRuns) * 100 : 0; console.log(`Batch progress: ${progress.toFixed(1)}%`);
interface RealtimeSingleRunOptions<TTask extends AnyTask = AnyTask> { id?: string; // Unique identifier for the subscription enabled?: boolean; // Whether the subscription is enabled experimental_throttleInMs?: number; // Throttle stream updates (ms) onComplete?: (run: RealtimeRun<TTask>, err?: Error) => void; // Completion callback stopOnCompletion?: boolean; // Stop when run completes (default: true) skipColumns?: RealtimeRunSkipColumns; // Columns to skip from subscription }
interface RealtimeRunsWithTagOptions extends RealtimeRunOptions { createdAt?: string; // Filter by creation time (e.g., "1h", "30m") skipColumns?: RealtimeRunSkipColumns; // Columns to skip from subscription }
import { ref, onUnmounted } from 'vue'; import { createRealtimeRun } from '@trigger.dev/sdk/v3/browser'; export function useRealtimeRun(runId: string, apiClient: ApiClient) { const subscription = createRealtimeRun(runId, apiClient); const run = ref(subscription.run); const error = ref(subscription.error); const isComplete = ref(subscription.isComplete); // Update reactive refs when subscription state changes const updateState = () => { run.value = subscription.run; error.value = subscription.error; isComplete.value = subscription.isComplete; }; // Poll for updates (in a real implementation, you'd want a more efficient approach) const interval = setInterval(updateState, 100); onUnmounted(() => { clearInterval(interval); subscription.stop(); }); return { run, error, isComplete, stop: subscription.stop }; }
import { writable, onDestroy } from 'svelte/store'; import { createRealtimeRun } from '@trigger.dev/sdk/v3/browser'; export function createRealtimeStore(runId: string, apiClient: ApiClient) { const subscription = createRealtimeRun(runId, apiClient); const run = writable(subscription.run); const error = writable(subscription.error); const isComplete = writable(subscription.isComplete); const updateState = () => { run.set(subscription.run); error.set(subscription.error); isComplete.set(subscription.isComplete); }; const interval = setInterval(updateState, 100); onDestroy(() => { clearInterval(interval); subscription.stop(); }); return { run, error, isComplete, stop: subscription.stop }; }
import { createRealtimeRun } from '@trigger.dev/sdk/v3/browser'; class RealtimeSubscription { private subscription; private listeners = new Set<() => void>(); constructor(runId: string, apiClient: ApiClient) { this.subscription = createRealtimeRun(runId, apiClient); // Poll for updates setInterval(() => { this.notify(); }, 100); } get run() { return this.subscription.run; } get error() { return this.subscription.error; } get isComplete() { return this.subscription.isComplete; } subscribe(listener: () => void) { this.listeners.add(listener); return () => this.listeners.delete(listener); } private notify() { this.listeners.forEach(listener => listener()); } stop() { this.subscription.stop(); } }
All subscription functions handle errors gracefully:
const subscription = createRealtimeRun("run-id-123", apiClient, { onComplete: (run, error) => { if (error) { console.error("Subscription error:", error); // Handle error appropriately } else { console.log("Run completed successfully:", run); } } }); // Check for errors if (subscription.error) { console.error("Current error:", subscription.error); }
- Throttling: Use
experimental_throttleInMsto throttle stream updates and reduce CPU usage - Skip Columns: Use
skipColumnsto exclude heavy data like payloads and outputs when not needed - Stop on Completion: Set
stopOnCompletion: falseonly when you need to continue receiving updates after completion - Cleanup: Always call
stop()when done with subscriptions to prevent memory leaks
If you're migrating from the React hooks package:
// Before (React) const { run, error, stop } = useRealtimeRun(runId, options); // After (Vanilla JS) const subscription = createRealtimeRun(runId, apiClient, options); const { run, error, stop } = subscription;
The main differences:
- You need to pass the
apiClientexplicitly - The subscription is returned as an object with getters
- No automatic cleanup - you need to call
stop()manually - No React-specific features like
useId()oruseEffect()