Triggering docs and adding options to all trigger hooks (#1528)

This commit is contained in:
Eric Allam
2024-12-03 10:15:49 +00:00
committed by GitHub
parent cd7a45101e
commit 8db1da69e9
9 changed files with 995 additions and 831 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/react-hooks": patch
---
Add trigger options to all trigger hooks
-29
View File
@@ -89,35 +89,6 @@ const publicToken = await auth.createPublicToken({
});
```
### Write scopes
You can also specify write scopes, which is required for triggering tasks from your frontend application:
```ts
const publicToken = await auth.createPublicToken({
scopes: {
write: {
tasks: ["my-task-1", "my-task-2"],
},
},
});
```
This will allow the token to trigger the specified tasks. `tasks` is the only write scope available at the moment.
We **strongly** recommend creating short-lived tokens for write scopes, as they can be used to trigger tasks from your frontend application:
```ts
const publicToken = await auth.createPublicToken({
scopes: {
write: {
tasks: ["my-task-1"], // ✅ this token can trigger this task
},
},
expirationTime: "1m", // ✅ this token will expire after 1 minute
});
```
### Expiration
By default, Public Access Token's expire after 15 minutes. You can specify a different expiration time when creating a Public Access Token:
-797
View File
@@ -1,797 +0,0 @@
---
title: React hooks
sidebarTitle: React hooks
description: Using the Trigger.dev v3 API from your React application.
---
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, batches, and subscribe to real-time updates.
## Installation
Install the `@trigger.dev/react-hooks` package in your project:
<CodeGroup>
```bash npm
npm add @trigger.dev/react-hooks
```
```bash pnpm
pnpm add @trigger.dev/react-hooks
```
```bash yarn
yarn install @trigger.dev/react-hooks
```
</CodeGroup>
## 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 (
<TriggerAuthContext.Provider value={{ accessToken: publicAccessToken }}>
<MyComponent />
</TriggerAuthContext.Provider>
);
}
```
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 (
<TriggerAuthContext.Provider
value={{
accessToken: publicAccessToken,
baseURL: "https://your-trigger-dev-instance.com",
}}
>
<MyComponent />
</TriggerAuthContext.Provider>
);
}
```
### 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 (
<TriggerAuthContext.Provider value={{ accessToken: "your-access-token" }}>
<MyComponent />
</TriggerAuthContext.Provider>
);
}
```
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 (
<TriggerAuthContext.Provider
value={{
accessToken,
}}
>
{children}
</TriggerAuthContext.Provider>
);
}
```
### 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<typeof exampleTask>("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 (
<TriggerProvider accessToken={publicAccessToken}>
<RunDetails id={params.id} />
</TriggerProvider>
);
}
```
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<typeof exampleTask>("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 (
<TriggerProvider accessToken={searchParams.publicAccessToken}>
<RunDetails id={params.id} />
</TriggerProvider>
);
}
```
Another alternative would be to use a server-side rendered page to fetch the token and pass it to the frontend:
<CodeGroup>
```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 (
<TriggerProvider accessToken={publicAccessToken}>
<RunDetails id={params.id} />
</TriggerProvider>
);
}
```
```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",
});
}
```
</CodeGroup>
## 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.
<Note>
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.
</Note>
All hooks named `useRealtime*` are Realtime hooks, and all hooks named `use*` are SWR hooks.
## Realtime 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 <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
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<typeof myTask>(runId, {
accessToken: publicAccessToken,
});
if (error) return <div>Error: {error.message}</div>;
// Now run.payload and run.output are correctly typed
return <div>Run: {run.id}</div>;
}
```
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 <div>Error: {error.message}</div>;
return (
<div>
{runs.map((run) => (
<div key={run.id}>Run: {run.id}</div>
))}
</div>
);
}
```
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<typeof myTask>(tag);
if (error) return <div>Error: {error.message}</div>;
// Now runs[i].payload and runs[i].output are correctly typed
return (
<div>
{runs.map((run) => (
<div key={run.id}>Run: {run.id}</div>
))}
</div>
);
}
```
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<typeof myTask1 | typeof myTask2>(tag);
if (error) return <div>Error: {error.message}</div>;
// 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 (
<div>
{runs.map((run) => (
<div key={run.id}>Run: {run.id}</div>
))}
</div>
);
}
```
See our [Realtime documentation](/realtime) for more information.
### 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 <div>Error: {error.message}</div>;
return (
<div>
{runs.map((run) => (
<div key={run.id}>Run: {run.id}</div>
))}
</div>
);
}
```
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 <div>Error: {error.message}</div>;
return (
<div>
<div>Run: {run.id}</div>
<div>
{Object.keys(streams).map((stream) => (
<div key={stream}>Stream: {stream}</div>
))}
</div>
</div>
);
}
```
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<typeof myTask, STREAMS>(runId, {
accessToken: publicAccessToken,
});
if (error) return <div>Error: {error.message}</div>;
const text = streams.openai?.map((part) => part).join("");
return (
<div>
<div>Run: {run.id}</div>
<div>{text}</div>
</div>
);
}
```
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<typeof myTask, STREAMS>(runId, {
accessToken: publicAccessToken,
});
if (error) return <div>Error: {error.message}</div>;
const text = streams.openai
?.filter((stream) => stream.type === "text-delta")
?.map((part) => part.text)
.join("");
return (
<div>
<div>Run: {run.id}</div>
<div>{text}</div>
</div>
);
}
```
### Common options
#### 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 <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
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 <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
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 <div>Error: {error.message}</div>;
return (
<div>
{runs.map((run) => (
<div key={run.id}>Run: {run.id}</div>
))}
</div>
);
}
```
## 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 <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
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<typeof myTask>(runId, {
refreshInterval: 0, // Disable polling
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
// Now run.payload and run.output are correctly typed
return <div>Run: {run.id}</div>;
}
```
### Common options
You can pass the following options to the all SWR hooks:
<ParamField path="revalidateOnFocus" type="boolean">
Revalidate the data when the window regains focus.
</ParamField>
<ParamField path="revalidateOnReconnect" type="boolean">
Revalidate the data when the browser regains a network connection.
</ParamField>
<ParamField path="refreshInterval" type="number">
Poll for updates at the specified interval (in milliseconds). Polling is not recommended for most
use-cases. Use the Realtime hooks instead.
</ParamField>
### Common return values
<ResponseField name="error" type="Error">
An error object if an error occurred while fetching the data.
</ResponseField>
<ResponseField name="isLoading" type="boolean">
A boolean indicating if the data is currently being fetched.
</ResponseField>
<ResponseField name="isValidating" type="boolean">
A boolean indicating if the data is currently being revalidated.
</ResponseField>
<ResponseField name="isError" type="boolean">
A boolean indicating if an error occurred while fetching the data.
</ResponseField>
## Trigger Hooks
We provide a set of hooks that can be used to trigger tasks from your frontend application. You'll need to generate a Public Access Token with `write` permissions to use these hooks. See our [frontend guide](/frontend/overview#write-scopes) for more information.
### useTaskTrigger
The `useTaskTrigger` hook allows you to trigger a task from your frontend application.
```tsx
"use client"; // This is needed for Next.js App Router or other RSC frameworks
import { useTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";
export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
const { submit, handle, error, isLoading } = useTaskTrigger<typeof myTask>("my-task", {
accessToken: publicAccessToken,
});
if (error) {
return <div>Error: {error.message}</div>;
}
if (handle) {
return <div>Run ID: {handle.id}</div>;
}
return (
<button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
{isLoading ? "Loading..." : "Trigger Task"}
</button>
);
}
```
### useRealtimeTaskTrigger
The `useRealtimeTaskTrigger` hook allows you to trigger a task from your frontend application and then subscribe to the run in using Realtime:
```tsx
"use client"; // This is needed for Next.js App Router or other RSC frameworks
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";
export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
const { submit, run, error, isLoading } = useRealtimeTaskTrigger<typeof myTask>("my-task", {
accessToken: publicAccessToken,
});
if (error) {
return <div>Error: {error.message}</div>;
}
// This is the realtime run object, which will automatically update when the run changes
if (run) {
return <div>Run ID: {run.id}</div>;
}
return (
<button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
{isLoading ? "Loading..." : "Trigger Task"}
</button>
);
}
```
### useRealtimeTaskTriggerWithStreams
The `useRealtimeTaskTriggerWithStreams` hook allows you to trigger a task from your frontend application and then subscribe to the run in using Realtime, and also receive any streams that are emitted by the task.
```tsx
"use client"; // This is needed for Next.js App Router or other RSC frameworks
import { useRealtimeTaskTriggerWithStreams } 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({ publicAccessToken }: { publicAccessToken: string }) {
const { submit, run, streams, error, isLoading } = useRealtimeTaskTriggerWithStreams<
typeof myTask,
STREAMS
>("my-task", {
accessToken: publicAccessToken,
});
if (error) {
return <div>Error: {error.message}</div>;
}
if (streams && run) {
const text = streams.openai?.map((part) => part).join("");
return (
<div>
<div>Run ID: {run.id}</div>
<div>{text}</div>
</div>
);
}
return (
<button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
{isLoading ? "Loading..." : "Trigger Task"}
</button>
);
}
```
+333
View File
@@ -0,0 +1,333 @@
---
title: Overview
sidebarTitle: Overview
description: Using the Trigger.dev v3 API from your React application.
---
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:
<CodeGroup>
```bash npm
npm add @trigger.dev/react-hooks
```
```bash pnpm
pnpm add @trigger.dev/react-hooks
```
```bash yarn
yarn install @trigger.dev/react-hooks
```
</CodeGroup>
## 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 (
<TriggerAuthContext.Provider value={{ accessToken: publicAccessToken }}>
<MyComponent />
</TriggerAuthContext.Provider>
);
}
```
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 (
<TriggerAuthContext.Provider
value={{
accessToken: publicAccessToken,
baseURL: "https://your-trigger-dev-instance.com",
}}
>
<MyComponent />
</TriggerAuthContext.Provider>
);
}
```
### 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 (
<TriggerAuthContext.Provider value={{ accessToken: "your-access-token" }}>
<MyComponent />
</TriggerAuthContext.Provider>
);
}
```
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 (
<TriggerAuthContext.Provider
value={{
accessToken,
}}
>
{children}
</TriggerAuthContext.Provider>
);
}
```
### 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<typeof exampleTask>("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 (
<TriggerProvider accessToken={publicAccessToken}>
<RunDetails id={params.id} />
</TriggerProvider>
);
}
```
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<typeof exampleTask>("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 (
<TriggerProvider accessToken={searchParams.publicAccessToken}>
<RunDetails id={params.id} />
</TriggerProvider>
);
}
```
Another alternative would be to use a server-side rendered page to fetch the token and pass it to the frontend:
<CodeGroup>
```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 (
<TriggerProvider accessToken={publicAccessToken}>
<RunDetails id={params.id} />
</TriggerProvider>
);
}
```
```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",
});
}
```
</CodeGroup>
## 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.
<Note>
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.
</Note>
## 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 <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
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<typeof myTask>(runId, {
refreshInterval: 0, // Disable polling
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
// Now run.payload and run.output are correctly typed
return <div>Run: {run.id}</div>;
}
```
### Common options
You can pass the following options to the all SWR hooks:
<ParamField path="revalidateOnFocus" type="boolean">
Revalidate the data when the window regains focus.
</ParamField>
<ParamField path="revalidateOnReconnect" type="boolean">
Revalidate the data when the browser regains a network connection.
</ParamField>
<ParamField path="refreshInterval" type="number">
Poll for updates at the specified interval (in milliseconds). Polling is not recommended for most
use-cases. Use the Realtime hooks instead.
</ParamField>
### Common return values
<ResponseField name="error" type="Error">
An error object if an error occurred while fetching the data.
</ResponseField>
<ResponseField name="isLoading" type="boolean">
A boolean indicating if the data is currently being fetched.
</ResponseField>
<ResponseField name="isValidating" type="boolean">
A boolean indicating if the data is currently being revalidated.
</ResponseField>
<ResponseField name="isError" type="boolean">
A boolean indicating if an error occurred while fetching the data.
</ResponseField>
## 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.
+391
View File
@@ -0,0 +1,391 @@
---
title: Realtime hooks
sidebarTitle: Realtime
description: Get live updates from the Trigger.dev API in your frontend application.
---
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 <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
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<typeof myTask>(runId, {
accessToken: publicAccessToken,
});
if (error) return <div>Error: {error.message}</div>;
// Now run.payload and run.output are correctly typed
return <div>Run: {run.id}</div>;
}
```
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 <div>Error: {error.message}</div>;
return (
<div>
{runs.map((run) => (
<div key={run.id}>Run: {run.id}</div>
))}
</div>
);
}
```
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<typeof myTask>(tag);
if (error) return <div>Error: {error.message}</div>;
// Now runs[i].payload and runs[i].output are correctly typed
return (
<div>
{runs.map((run) => (
<div key={run.id}>Run: {run.id}</div>
))}
</div>
);
}
```
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<typeof myTask1 | typeof myTask2>(tag);
if (error) return <div>Error: {error.message}</div>;
// 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 (
<div>
{runs.map((run) => (
<div key={run.id}>Run: {run.id}</div>
))}
</div>
);
}
```
### 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 <div>Error: {error.message}</div>;
return (
<div>
{runs.map((run) => (
<div key={run.id}>Run: {run.id}</div>
))}
</div>
);
}
```
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 <div>Error: {error.message}</div>;
return (
<div>
<div>Run: {run.id}</div>
<div>
{Object.keys(streams).map((stream) => (
<div key={stream}>Stream: {stream}</div>
))}
</div>
</div>
);
}
```
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<typeof myTask, STREAMS>(runId, {
accessToken: publicAccessToken,
});
if (error) return <div>Error: {error.message}</div>;
const text = streams.openai?.map((part) => part).join("");
return (
<div>
<div>Run: {run.id}</div>
<div>{text}</div>
</div>
);
}
```
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<typeof myTask, STREAMS>(runId, {
accessToken: publicAccessToken,
});
if (error) return <div>Error: {error.message}</div>;
const text = streams.openai
?.filter((stream) => stream.type === "text-delta")
?.map((part) => part.text)
.join("");
return (
<div>
<div>Run: {run.id}</div>
<div>{text}</div>
</div>
);
}
```
## 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 <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
### 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 <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
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 <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
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 <div>Error: {error.message}</div>;
return (
<div>
{runs.map((run) => (
<div key={run.id}>Run: {run.id}</div>
))}
</div>
);
}
```
+250
View File
@@ -0,0 +1,250 @@
---
title: Trigger hooks
sidebarTitle: Triggering
description: Triggering tasks from your frontend application.
---
We provide a set of hooks that can be used to trigger tasks from your frontend application.
## Demo
We've created a [Demo application](https://github.com/triggerdotdev/realtime-llm-battle) that demonstrates how to use our React hooks to trigger tasks in a Next.js application. The application uses the `@trigger.dev/react-hooks` package to trigger a task and subscribe to the run in real-time.
## Installation
Install the `@trigger.dev/react-hooks` package in your project:
<CodeGroup>
```bash npm
npm add @trigger.dev/react-hooks
```
```bash pnpm
pnpm add @trigger.dev/react-hooks
```
```bash yarn
yarn install @trigger.dev/react-hooks
```
</CodeGroup>
## Authentication
To authenticate a trigger hook, you must provide a special one-time use "trigger" token. These tokens are very similar to [Public Access Tokens](/frontend/overview#authentication), but they can only be used once to trigger a task. You can generate a trigger token using the `auth.createTriggerPublicToken` function in your backend code:
```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 by passing a `expirationTime` parameter:
```ts
import { auth } from "@trigger.dev/sdk/v3";
// Somewhere in your backend code
const triggerToken = await auth.createTriggerPublicToken("my-task", {
expirationTime: "24hr",
});
```
You can also pass multiple tasks to the `createTriggerPublicToken` function 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 pass the `multipleUse` parameter to create a token 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!
});
```
<Note>
After generating the trigger token in your backend, you must pass it to your frontend application.
We have a guide on how to do this in the [React hooks
overview](/frontend/react-hooks/overview#passing-the-token-to-the-frontend).
</Note>
## Hooks
### useTaskTrigger
The `useTaskTrigger` hook allows you to trigger a task from your frontend application.
```tsx
"use client"; // This is needed for Next.js App Router or other RSC frameworks
import { useTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";
// 👆 This is the type of your task
export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
// pass the type of your task here 👇
const { submit, handle, error, isLoading } = useTaskTrigger<typeof myTask>("my-task", {
accessToken: publicAccessToken, // 👈 this is the "trigger" token
});
if (error) {
return <div>Error: {error.message}</div>;
}
if (handle) {
return <div>Run ID: {handle.id}</div>;
}
return (
<button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
{isLoading ? "Loading..." : "Trigger Task"}
</button>
);
}
```
`useTaskTrigger` returns an object with the following properties:
- `submit`: A function that triggers the task. It takes the payload of the task as an argument.
- `handle`: The run handle object. This object contains the ID of the run that was triggered, along with a Public Access Token that can be used to access the run.
- `isLoading`: A boolean that indicates whether the task is currently being triggered.
- `error`: An error object that contains any errors that occurred while triggering the task.
The `submit` function triggers the task with the specified payload. You can additionally pass an optional [options](/triggering#options) argument to the `submit` function:
```tsx
submit({ foo: "bar" }, { tags: ["tag1", "tag2"] });
```
#### Using the handle object
You can use the `handle` object to initiate a subsequent [realtime hook](/frontend/react-hooks/realtime#userealtimerun) to subscribe to the run.
```tsx
"use client"; // This is needed for Next.js App Router or other RSC frameworks
import { useTaskTrigger, useRealtimeRun } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";
// 👆 This is the type of your task
export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
// pass the type of your task here 👇
const { submit, handle, error, isLoading } = useTaskTrigger<typeof myTask>("my-task", {
accessToken: publicAccessToken, // 👈 this is the "trigger" token
});
// use the handle object to preserve type-safety 👇
const { run, error: realtimeError } = useRealtimeRun(handle, {
accessToken: handle?.publicAccessToken,
enabled: !!handle, // Only subscribe to the run if the handle is available
});
if (error) {
return <div>Error: {error.message}</div>;
}
if (handle) {
return <div>Run ID: {handle.id}</div>;
}
if (realtimeError) {
return <div>Error: {realtimeError.message}</div>;
}
if (run) {
return <div>Run ID: {run.id}</div>;
}
return (
<button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
{isLoading ? "Loading..." : "Trigger Task"}
</button>
);
}
```
We've also created some additional hooks that allow you to trigger tasks and subscribe to the run in one step:
### useRealtimeTaskTrigger
The `useRealtimeTaskTrigger` hook allows you to trigger a task from your frontend application and then subscribe to the run in using Realtime:
```tsx
"use client"; // This is needed for Next.js App Router or other RSC frameworks
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";
export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
const { submit, run, error, isLoading } = useRealtimeTaskTrigger<typeof myTask>("my-task", {
accessToken: publicAccessToken,
});
if (error) {
return <div>Error: {error.message}</div>;
}
// This is the realtime run object, which will automatically update when the run changes
if (run) {
return <div>Run ID: {run.id}</div>;
}
return (
<button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
{isLoading ? "Loading..." : "Trigger Task"}
</button>
);
}
```
### useRealtimeTaskTriggerWithStreams
The `useRealtimeTaskTriggerWithStreams` hook allows you to trigger a task from your frontend application and then subscribe to the run in using Realtime, and also receive any streams that are emitted by the task.
```tsx
"use client"; // This is needed for Next.js App Router or other RSC frameworks
import { useRealtimeTaskTriggerWithStreams } 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({ publicAccessToken }: { publicAccessToken: string }) {
const { submit, run, streams, error, isLoading } = useRealtimeTaskTriggerWithStreams<
typeof myTask,
STREAMS
>("my-task", {
accessToken: publicAccessToken,
});
if (error) {
return <div>Error: {error.message}</div>;
}
if (streams && run) {
const text = streams.openai?.map((part) => part).join("");
return (
<div>
<div>Run ID: {run.id}</div>
<div>{text}</div>
</div>
);
}
return (
<button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
{isLoading ? "Loading..." : "Trigger Task"}
</button>
);
}
```
+12 -1
View File
@@ -110,6 +110,10 @@
{
"source": "/runs-and-attempts",
"destination": "/runs"
},
{
"source": "/frontend/react-hooks",
"destination": "/frontend/react-hooks/overview"
}
],
"anchors": [
@@ -207,7 +211,14 @@
"group": "Frontend usage",
"pages": [
"frontend/overview",
"frontend/react-hooks"
{
"group": "React hooks",
"pages": [
"frontend/react-hooks/overview",
"frontend/react-hooks/realtime",
"frontend/react-hooks/triggering"
]
}
]
},
{
+1 -1
View File
@@ -699,7 +699,7 @@ export const childTask2 = task({
## Triggering from your frontend
If you want to trigger a task directly from a frontend application, you can use our [React
hooks](/frontend/react-hooks#trigger-hooks).
hooks](/frontend/react-hooks/triggering).
## Options
@@ -8,7 +8,7 @@ import {
makeIdempotencyKey,
RunHandleFromTypes,
stringifyIO,
TriggerOptions,
type TriggerOptions,
} from "@trigger.dev/core/v3";
import useSWRMutation from "swr/mutation";
import { useApiClient, UseApiClientOptions } from "./useApiClient.js";
@@ -118,7 +118,7 @@ export type RealtimeTriggerInstanceWithStreams<
TTask extends AnyTask,
TStreams extends Record<string, any> = Record<string, any>,
> = UseRealtimeRunWithStreamsInstance<TTask, TStreams> & {
submit: (payload: TaskPayload<TTask>) => void;
submit: (payload: TaskPayload<TTask>, options?: TriggerOptions) => void;
isLoading: boolean;
handle?: RunHandleFromTypes<InferRunTypes<TTask>>;
};
@@ -166,7 +166,7 @@ export function useRealtimeTaskTriggerWithStreams<
}
export type RealtimeTriggerInstance<TTask extends AnyTask> = UseRealtimeRunInstance<TTask> & {
submit: (payload: TaskPayload<TTask>) => void;
submit: (payload: TaskPayload<TTask>, options?: TriggerOptions) => void;
isLoading: boolean;
handle?: RunHandleFromTypes<InferRunTypes<TTask>>;
};