Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c9811beae2 | |||
| 97533cba2e | |||
| e92459bf1e | |||
| cc74cae393 | |||
| 58def055d0 | |||
| 41f5261fe5 | |||
| ce1a54eb3b | |||
| 25285488f8 | |||
| befdcef1f0 | |||
| fa18e6c012 | |||
| 4194bce4a3 | |||
| da08e5015f | |||
| 9b7472844d | |||
| eace78fa04 | |||
| e81b2a7839 | |||
| cdcfc81aca | |||
| 816b0f9e3d | |||
| 6fb073672d | |||
| 9203bd8e67 | |||
| 3252d9ec31 |
@@ -31,6 +31,7 @@ const EnvironmentSchema = z.object({
|
||||
REMIX_APP_PORT: z.string().optional(),
|
||||
LOGIN_ORIGIN: z.string().default("http://localhost:3030"),
|
||||
APP_ORIGIN: z.string().default("http://localhost:3030"),
|
||||
API_ORIGIN: z.string().optional(),
|
||||
ELECTRIC_ORIGIN: z.string().default("http://localhost:3060"),
|
||||
APP_ENV: z.string().default(process.env.NODE_ENV),
|
||||
SERVICE_NAME: z.string().default("trigger.dev webapp"),
|
||||
|
||||
@@ -732,7 +732,7 @@ async function resolveBuiltInProdVariables(runtimeEnvironment: RuntimeEnvironmen
|
||||
},
|
||||
{
|
||||
key: "TRIGGER_API_URL",
|
||||
value: env.APP_ORIGIN,
|
||||
value: env.API_ORIGIN ?? env.APP_ORIGIN,
|
||||
},
|
||||
{
|
||||
key: "TRIGGER_RUNTIME_WAIT_THRESHOLD_IN_MS",
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ProdTaskRunExecution,
|
||||
ProdTaskRunExecutionPayload,
|
||||
TaskRunError,
|
||||
TaskRunErrorCodes,
|
||||
TaskRunExecution,
|
||||
TaskRunExecutionLazyAttemptPayload,
|
||||
TaskRunExecutionResult,
|
||||
@@ -519,6 +520,11 @@ export class SharedQueueConsumer {
|
||||
taskRun: lockedTaskRun.id,
|
||||
});
|
||||
|
||||
const service = new CrashTaskRunService();
|
||||
await service.call(lockedTaskRun.id, {
|
||||
errorCode: TaskRunErrorCodes.OUTDATED_SDK_VERSION,
|
||||
});
|
||||
|
||||
await this.#ackAndDoMoreWork(message.messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
---
|
||||
title: "CLI deploy command"
|
||||
description: "The `trigger.dev deploy` command can be used to manually deploy."
|
||||
description: "The `trigger.dev deploy` command can be used to deploy your tasks to our infrastructure."
|
||||
---
|
||||
|
||||
import CliDeployCommands from '/snippets/cli-commands-deploy.mdx';
|
||||
import CliDeployCommands from "/snippets/cli-commands-deploy.mdx";
|
||||
|
||||
<CliDeployCommands/>
|
||||
<CliDeployCommands />
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
---
|
||||
title: Overview & Authentication
|
||||
sidebarTitle: Overview & Auth
|
||||
description: Using the Trigger.dev SDK from your frontend application.
|
||||
---
|
||||
|
||||
You can use certain SDK functions in your frontend application to interact with the Trigger.dev API. This guide will show you how to authenticate your requests and use the SDK in your frontend application.
|
||||
|
||||
## Authentication
|
||||
|
||||
You must authenticate your requests using a "Public Access Token" when using the SDK in your frontend application. To create a Public Access Token, you can use the `auth.createPublicToken` function in your backend code:
|
||||
|
||||
```tsx
|
||||
const publicToken = await auth.createPublicToken();
|
||||
```
|
||||
|
||||
To use a Public Access Token in your frontend application, you can call the `auth.configure` function or the `auth.withAuth` function:
|
||||
|
||||
```ts
|
||||
import { auth } from "@trigger.dev/sdk/v3";
|
||||
|
||||
auth.configure({
|
||||
accessToken: publicToken,
|
||||
});
|
||||
|
||||
// or
|
||||
await auth.withAuth({ accessToken: publicToken }, async () => {
|
||||
// Your code here will use the public token
|
||||
});
|
||||
```
|
||||
|
||||
### Scopes
|
||||
|
||||
By default a Public Access Token has limited permissions. You can specify the scopes you need when creating a Public Access Token:
|
||||
|
||||
```ts
|
||||
const publicToken = await auth.createPublicToken({
|
||||
scopes: {
|
||||
read: {
|
||||
runs: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This will allow the token to read all runs, which is probably not what you want. You can specify only certain runs by passing an array of run IDs:
|
||||
|
||||
```ts
|
||||
const publicToken = await auth.createPublicToken({
|
||||
scopes: {
|
||||
read: {
|
||||
runs: ["run_1234", "run_5678"],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You can scope the token to only read certain tasks:
|
||||
|
||||
```ts
|
||||
const publicToken = await auth.createPublicToken({
|
||||
scopes: {
|
||||
read: {
|
||||
tasks: ["my-task-1", "my-task-2"],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Or tags:
|
||||
|
||||
```ts
|
||||
const publicToken = await auth.createPublicToken({
|
||||
scopes: {
|
||||
read: {
|
||||
tags: ["my-tag-1", "my-tag-2"],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Or a specific batch of runs:
|
||||
|
||||
```ts
|
||||
const publicToken = await auth.createPublicToken({
|
||||
scopes: {
|
||||
read: {
|
||||
batch: "batch_1234",
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You can also combine scopes. For example, to read only certain tasks and tags:
|
||||
|
||||
```ts
|
||||
const publicToken = await auth.createPublicToken({
|
||||
scopes: {
|
||||
read: {
|
||||
tasks: ["my-task-1", "my-task-2"],
|
||||
tags: ["my-tag-1", "my-tag-2"],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Expiration
|
||||
|
||||
By default, Public Access Token's expire after 15 minutes. You can specify a different expiration time when creating a Public Access Token:
|
||||
|
||||
```ts
|
||||
const publicToken = await auth.createPublicToken({
|
||||
expirationTime: "1hr",
|
||||
});
|
||||
```
|
||||
|
||||
- If `expirationTime` is a string, it will be treated as a time span
|
||||
- If `expirationTime` is a number, it will be treated as a Unix timestamp
|
||||
- If `expirationTime` is a `Date`, it will be treated as a date
|
||||
|
||||
The format used for a time span is the same as the [jose package](https://github.com/panva/jose), which is a number followed by a unit. Valid units are: "sec", "secs", "second", "seconds", "s", "minute", "minutes", "min", "mins", "m", "hour", "hours", "hr", "hrs", "h", "day", "days", "d", "week", "weeks", "w", "year", "years", "yr", "yrs", and "y". It is not possible to specify months. 365.25 days is used as an alias for a year. If the string is suffixed with "ago", or prefixed with a "-", the resulting time span gets subtracted from the current unix timestamp. A "from now" suffix can also be used for readability when adding to the current unix timestamp.
|
||||
|
||||
## Auto-generated tokens
|
||||
|
||||
When triggering a task from your backend, the `handle` received from the `trigger` function now includes a `publicAccessToken` field. This token can be used to authenticate requests in your frontend application:
|
||||
|
||||
```ts
|
||||
import { tasks } from "@trigger.dev/sdk/v3";
|
||||
|
||||
const handle = await tasks.trigger("my-task", { some: "data" });
|
||||
|
||||
console.log(handle.publicAccessToken);
|
||||
```
|
||||
|
||||
By default, tokens returned from the `trigger` function expire after 15 minutes and have a read scope for that specific run, and any tags associated with it. You can customize the expiration of the auto-generated tokens by passing a `publicTokenOptions` object to the `trigger` function:
|
||||
|
||||
```ts
|
||||
const handle = await tasks.trigger(
|
||||
"my-task",
|
||||
{ some: "data" },
|
||||
{
|
||||
tags: ["my-tag"],
|
||||
},
|
||||
{
|
||||
publicAccessToken: {
|
||||
expirationTime: "1hr",
|
||||
},
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
You will also get back a Public Access Token when using the `batchTrigger` function:
|
||||
|
||||
```ts
|
||||
import { tasks } from "@trigger.dev/sdk/v3";
|
||||
|
||||
const handle = await tasks.batchTrigger("my-task", [
|
||||
{ payload: { some: "data" } },
|
||||
{ payload: { some: "data" } },
|
||||
{ payload: { some: "data" } },
|
||||
]);
|
||||
|
||||
console.log(handle.publicAccessToken);
|
||||
```
|
||||
|
||||
## Available SDK functions
|
||||
|
||||
Currently the following functions are available in the frontend SDK:
|
||||
|
||||
### runs.retrieve
|
||||
|
||||
The `runs.retrieve` function allows you to retrieve a run by its ID.
|
||||
|
||||
```ts
|
||||
import { runs, auth } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Somewhere in your backend code
|
||||
const handle = await tasks.trigger("my-task", { some: "data" });
|
||||
|
||||
// In your frontend code
|
||||
auth.configure({
|
||||
accessToken: handle.publicAccessToken,
|
||||
});
|
||||
|
||||
const run = await runs.retrieve(handle.id);
|
||||
```
|
||||
|
||||
Learn more about the `runs.retrieve` function in the [runs.retrieve doc](/management/runs/retrieve).
|
||||
|
||||
### runs.subscribeToRun
|
||||
|
||||
The `runs.subscribeToRun` function allows you to subscribe to a run by its ID, and receive updates in real-time when the run changes.
|
||||
|
||||
```ts
|
||||
import { runs, auth } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Somewhere in your backend code
|
||||
const handle = await tasks.trigger("my-task", { some: "data" });
|
||||
|
||||
// In your frontend code
|
||||
auth.configure({
|
||||
accessToken: handle.publicAccessToken,
|
||||
});
|
||||
|
||||
for await (const run of runs.subscribeToRun(handle.id)) {
|
||||
// This will log the run every time it changes
|
||||
console.log(run);
|
||||
}
|
||||
```
|
||||
|
||||
See the [Realtime doc](/realtime) for more information.
|
||||
|
||||
### runs.subscribeToRunsWithTag
|
||||
|
||||
The `runs.subscribeToRunsWithTag` function allows you to subscribe to runs with a specific tag, and receive updates in real-time when the runs change.
|
||||
|
||||
```ts
|
||||
import { runs, auth } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Somewhere in your backend code
|
||||
const handle = await tasks.trigger("my-task", { some: "data" }, { tags: ["my-tag"] });
|
||||
|
||||
// In your frontend code
|
||||
auth.configure({
|
||||
accessToken: handle.publicAccessToken,
|
||||
});
|
||||
|
||||
for await (const run of runs.subscribeToRunsWithTag("my-tag")) {
|
||||
// This will log the run every time it changes
|
||||
console.log(run);
|
||||
}
|
||||
```
|
||||
|
||||
See the [Realtime doc](/realtime) for more information.
|
||||
|
||||
## React hooks
|
||||
|
||||
We also provide React hooks to make it easier to use the SDK in your React application. See our [React hooks](/frontend/react-hooks) documentation for more information.
|
||||
|
||||
## Triggering tasks
|
||||
|
||||
We don't currently support triggering tasks from the frontend SDK. If this is something you need, please let us know by [upvoting the feature](https://feedback.trigger.dev/p/ability-to-trigger-tasks-from-frontend).
|
||||
@@ -0,0 +1,446 @@
|
||||
---
|
||||
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
|
||||
|
||||
Before you can use the hooks, you need to provide a public access token to the `TriggerAuthContext` provider. Learn more about [authentication in the frontend guide](/frontend/overview).
|
||||
|
||||
```tsx
|
||||
import { TriggerAuthContext } from "@trigger.dev/react-hooks";
|
||||
|
||||
export function SetupTrigger() {
|
||||
return (
|
||||
<TriggerAuthContext.Provider value={{ accessToken: "your-access-token" }}>
|
||||
<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() {
|
||||
return (
|
||||
<TriggerAuthContext.Provider
|
||||
value={{
|
||||
accessToken: "your-access-token",
|
||||
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);
|
||||
|
||||
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>
|
||||
|
||||
## Usage
|
||||
|
||||
### 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.
|
||||
|
||||
#### Common SWR hook 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 SWR hook 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>
|
||||
|
||||
### 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);
|
||||
|
||||
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>;
|
||||
}
|
||||
```
|
||||
|
||||
### 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 }: { runId: string }) {
|
||||
const { run, error } = useRealtimeRun(runId);
|
||||
|
||||
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 }: { runId: string }) {
|
||||
const { run, error } = useRealtimeRun<typeof myTask>(runId);
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: "Convert an image to a cartoon using fal AI"
|
||||
sidebarTitle: "fal AI image to cartoon"
|
||||
description: "This example demonstrates how to convert an image to a cartoon using fal AI with Trigger.dev."
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Fal AI is a platform that provides access to advanced AI models for tasks such as image generation, text summarization, and hyperparameter tuning.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A project with [Trigger.dev initialized](/quick-start)
|
||||
- A [fal AI](https://fal.ai/) account
|
||||
- A [Cloudflare](https://developers.cloudflare.com/r2/) account and bucket
|
||||
|
||||
## Task code
|
||||
|
||||
This task converts an image to a cartoon using fal AI, and uploads the result to Cloudflare R2.
|
||||
|
||||
```ts trigger/fal-ai-image-to-cartoon.ts
|
||||
import { logger, task } from "@trigger.dev/sdk/v3";
|
||||
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
||||
import * as fal from "@fal-ai/serverless-client";
|
||||
import fetch from "node-fetch";
|
||||
import { z } from "zod";
|
||||
|
||||
// Initialize fal.ai client
|
||||
fal.config({
|
||||
credentials: process.env.FAL_KEY, // Get this from your fal AI dashboard
|
||||
});
|
||||
|
||||
// Initialize S3-compatible client for Cloudflare R2
|
||||
const s3Client = new S3Client({
|
||||
// How to authenticate to R2: https://developers.cloudflare.com/r2/api/s3/tokens/
|
||||
region: "auto",
|
||||
endpoint: process.env.R2_ENDPOINT,
|
||||
credentials: {
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "",
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
export const FalResult = z.object({
|
||||
images: z.tuple([z.object({ url: z.string() })]),
|
||||
});
|
||||
|
||||
export const falAiImageToCartoon = task({
|
||||
id: "fal-ai-image-to-cartoon",
|
||||
run: async (payload: { imageUrl: string; fileName: string }) => {
|
||||
logger.log("Converting image to cartoon", payload);
|
||||
|
||||
// Convert image to cartoon using fal.ai
|
||||
const result = await fal.subscribe("fal-ai/flux/dev/image-to-image", {
|
||||
input: {
|
||||
prompt: "Turn the image into a cartoon in the style of a Pixar character",
|
||||
image_url: payload.imageUrl,
|
||||
},
|
||||
onQueueUpdate: (update) => {
|
||||
logger.info("Fal.ai processing update", { update });
|
||||
},
|
||||
});
|
||||
|
||||
const $result = FalResult.parse(result);
|
||||
const [{ url: cartoonImageUrl }] = $result.images;
|
||||
|
||||
// Download the cartoon image
|
||||
const imageResponse = await fetch(cartoonImageUrl);
|
||||
const imageBuffer = await imageResponse.arrayBuffer().then(Buffer.from);
|
||||
|
||||
// Upload to Cloudflare R2
|
||||
const r2Key = `cartoons/${payload.fileName}`;
|
||||
const uploadParams = {
|
||||
Bucket: process.env.R2_BUCKET, // Create a bucket in your Cloudflare dashboard
|
||||
Key: r2Key,
|
||||
Body: imageBuffer,
|
||||
ContentType: "image/png",
|
||||
};
|
||||
|
||||
logger.log("Uploading cartoon to R2", { key: r2Key });
|
||||
await s3Client.send(new PutObjectCommand(uploadParams));
|
||||
|
||||
logger.log("Cartoon uploaded to R2", { key: r2Key });
|
||||
|
||||
return {
|
||||
originalUrl: payload.imageUrl,
|
||||
cartoonUrl: `File uploaded to storage at: ${r2Key}`,
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Testing your task
|
||||
|
||||
You can test your task by triggering it from the Trigger.dev dashboard.
|
||||
|
||||
```json
|
||||
"imageUrl": "<image-url>", // Replace with the URL of the image you want to convert to a cartoon
|
||||
"fileName": "<file-name>" // Replace with the name you want to save the file as in Cloudflare R2
|
||||
```
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
title: "Crawl a URL using Firecrawl"
|
||||
sidebarTitle: "Firecrawl URL crawl"
|
||||
description: "This example demonstrates how to crawl a URL using Firecrawl with Trigger.dev."
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Firecrawl is a tool for crawling websites and extracting clean markdown that's structured in an LLM-ready format.
|
||||
|
||||
Here are two examples of how to use Firecrawl with Trigger.dev:
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A project with [Trigger.dev initialized](/quick-start)
|
||||
- A [Firecrawl](https://firecrawl.dev/) account
|
||||
|
||||
## Example 1: crawl an entire website with Firecrawl
|
||||
|
||||
This task crawls a website and returns the `crawlResult` object. You can set the `limit` parameter to control the number of URLs that are crawled.
|
||||
|
||||
```ts trigger/firecrawl-url-crawl.ts
|
||||
import FirecrawlApp from "@mendable/firecrawl-js";
|
||||
import { task } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Initialize the Firecrawl client with your API key
|
||||
const firecrawlClient = new FirecrawlApp({
|
||||
apiKey: process.env.FIRECRAWL_API_KEY, // Get this from your Firecrawl dashboard
|
||||
});
|
||||
|
||||
export const firecrawlCrawl = task({
|
||||
id: "firecrawl-crawl",
|
||||
run: async (payload: { url: string }) => {
|
||||
const { url } = payload;
|
||||
|
||||
// Crawl: scrapes all the URLs of a web page and return content in LLM-ready format
|
||||
const crawlResult = await firecrawlClient.crawlUrl(url, {
|
||||
limit: 100, // Limit the number of URLs to crawl
|
||||
scrapeOptions: {
|
||||
formats: ["markdown", "html"],
|
||||
},
|
||||
});
|
||||
|
||||
if (!crawlResult.success) {
|
||||
throw new Error(`Failed to crawl: ${crawlResult.error}`);
|
||||
}
|
||||
|
||||
return {
|
||||
data: crawlResult,
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Testing your task
|
||||
|
||||
You can test your task by triggering it from the Trigger.dev dashboard.
|
||||
|
||||
```json
|
||||
"url": "<url-to-crawl>" // Replace with the URL you want to crawl
|
||||
```
|
||||
|
||||
## Example 2: scrape a single URL with Firecrawl
|
||||
|
||||
This task scrapes a single URL and returns the `scrapeResult` object.
|
||||
|
||||
```ts trigger/firecrawl-url-scrape.ts
|
||||
import FirecrawlApp, { ScrapeResponse } from "@mendable/firecrawl-js";
|
||||
import { task } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Initialize the Firecrawl client with your API key
|
||||
const firecrawlClient = new FirecrawlApp({
|
||||
apiKey: process.env.FIRECRAWL_API_KEY, // Get this from your Firecrawl dashboard
|
||||
});
|
||||
|
||||
export const firecrawlScrape = task({
|
||||
id: "firecrawl-scrape",
|
||||
run: async (payload: { url: string }) => {
|
||||
const { url } = payload;
|
||||
|
||||
// Scrape: scrapes a URL and get its content in LLM-ready format (markdown, structured data via LLM Extract, screenshot, html)
|
||||
const scrapeResult = (await firecrawlClient.scrapeUrl(url, {
|
||||
formats: ["markdown", "html"],
|
||||
})) as ScrapeResponse;
|
||||
|
||||
if (!scrapeResult.success) {
|
||||
throw new Error(`Failed to scrape: ${scrapeResult.error}`);
|
||||
}
|
||||
|
||||
return {
|
||||
data: scrapeResult,
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Testing your task
|
||||
|
||||
You can test your task by triggering it from the Trigger.dev dashboard.
|
||||
|
||||
```json
|
||||
"url": "<url-to-scrape>" // Replace with the URL you want to scrape
|
||||
```
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
title: "Drizzle setup guide"
|
||||
sidebarTitle: "Drizzle setup guide"
|
||||
description: "This guide will show you how to set up Drizzle ORM with Trigger.dev"
|
||||
icon: "D"
|
||||
---
|
||||
|
||||
import Prerequisites from "/snippets/framework-prerequisites.mdx";
|
||||
import CliInitStep from "/snippets/step-cli-init.mdx";
|
||||
import CliDevStep from "/snippets/step-cli-dev.mdx";
|
||||
import CliRunTestStep from "/snippets/step-run-test.mdx";
|
||||
import CliViewRunStep from "/snippets/step-view-run.mdx";
|
||||
import UsefulNextSteps from "/snippets/useful-next-steps.mdx";
|
||||
|
||||
## Overview
|
||||
|
||||
This guide will show you how to set up [Drizzle ORM](https://orm.drizzle.team/) with Trigger.dev, test and view an example task run.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An existing Node.js project with a `package.json` file
|
||||
- Ensure TypeScript is installed
|
||||
- A [PostgreSQL](https://www.postgresql.org/) database server running locally, or accessible via a connection string
|
||||
- Drizzle ORM [installed and initialized](https://orm.drizzle.team/docs/get-started) in your project
|
||||
- A `DATABASE_URL` environment variable set in your `.env` file, pointing to your PostgreSQL database (e.g. `postgresql://user:password@localhost:5432/dbname`)
|
||||
|
||||
## Initial setup (optional)
|
||||
|
||||
Follow these steps if you don't already have Trigger.dev set up in your project.
|
||||
|
||||
<Steps>
|
||||
<CliInitStep />
|
||||
<CliDevStep />
|
||||
<CliRunTestStep />
|
||||
<CliViewRunStep />
|
||||
</Steps>
|
||||
|
||||
## Creating a task using Drizzle and deploying it to production
|
||||
|
||||
<Steps>
|
||||
<Step title="The task using Drizzle">
|
||||
|
||||
First, create a new task file in your `trigger` folder.
|
||||
|
||||
This is a simple task that will add a new user to your database, we will call it `drizzle-add-new-user`.
|
||||
|
||||
<Note>
|
||||
For this task to work correctly, you will need to have a `users` table schema defined with Drizzle
|
||||
that includes `name`, `age` and `email` fields.
|
||||
</Note>
|
||||
|
||||
```ts /trigger/drizzle-add-new-user.ts
|
||||
import { eq } from "drizzle-orm";
|
||||
import { task } from "@trigger.dev/sdk/v3";
|
||||
import { users } from "src/db/schema";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
|
||||
// Initialize Drizzle client
|
||||
const db = drizzle(process.env.DATABASE_URL!);
|
||||
|
||||
export const addNewUser = task({
|
||||
id: "drizzle-add-new-user",
|
||||
run: async (payload: typeof users.$inferInsert) => {
|
||||
// Create new user
|
||||
const [user] = await db.insert(users).values(payload).returning();
|
||||
|
||||
return {
|
||||
createdUser: user,
|
||||
message: "User created and updated successfully",
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Configuring the build">
|
||||
|
||||
Next, in your `trigger.config.js` file, add `pg` to the `externals` array. `pg` is a non-blocking PostgreSQL client for Node.js.
|
||||
|
||||
It is marked as an external to ensure that it is not bundled into the task's bundle, and instead will be installed and loaded from `node_modules` at runtime.
|
||||
|
||||
```js /trigger.config.js
|
||||
import { defineConfig } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export default defineConfig({
|
||||
project: "<project ref>", // Your project reference
|
||||
// Your other config settings...
|
||||
build: {
|
||||
externals: ["pg"],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Deploying your task">
|
||||
Once the build configuration is added, you can now deploy your task using the Trigger.dev CLI.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash npm
|
||||
npx trigger.dev@latest deploy
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm dlx trigger.dev@latest deploy
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn dlx trigger.dev@latest deploy
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Adding your DATABASE_URL environment variable to Trigger.dev">
|
||||
|
||||
In your Trigger.dev dashboard sidebar click "Environment Variables" <Icon icon="circle-1" iconType="solid" size={20} color="A8FF53" />, and then the "New environment variable" button <Icon icon="circle-2" iconType="solid" size={20} color="A8FF53" />.
|
||||
|
||||

|
||||
|
||||
You can add values for your local dev environment, staging and prod. in this case we will add the `DATABASE_URL` for the production environment.
|
||||
|
||||

|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Running your task">
|
||||
|
||||
To test this task, go to the 'test' page in the Trigger.dev dashboard and run the task with the following payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "<a-name>", // e.g. "John Doe"
|
||||
"age": "<an-age>", // e.g. 25
|
||||
"email": "<an-email>" // e.g. "john@doe.test"
|
||||
}
|
||||
```
|
||||
|
||||
Congratulations! You should now see a new completed run, and a new user with the credentials you provided should be added to your database.
|
||||
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
<UsefulNextSteps />
|
||||
@@ -248,12 +248,156 @@ Here are the steps to trigger your task in the Next.js App and Pages router and
|
||||
|
||||
<DeployingYourTask />
|
||||
|
||||
## Troubleshooting
|
||||
## Troubleshooting & extra resources
|
||||
|
||||
<NextjsTroubleshootingMissingApiKey/>
|
||||
<NextjsTroubleshootingButtonSyntax/>
|
||||
<WorkerFailedToStartWhenRunningDevCommand/>
|
||||
|
||||
### Revalidation from your Trigger.dev tasks
|
||||
|
||||
[Revalidation](https://vercel.com/docs/incremental-static-regeneration/quickstart#on-demand-revalidation) allows you to purge the cache for an ISR route. To revalidate an ISR route from a Trigger.dev task, you have to set up a handler for the `revalidate` event. This is an API route that you can add to your Next.js app.
|
||||
|
||||
This handler will run the `revalidatePath` function from Next.js, which purges the cache for the given path.
|
||||
|
||||
The handlers are slightly different for the App and Pages router:
|
||||
|
||||
#### Revalidation handler: App Router
|
||||
|
||||
If you are using the App router, create a new revalidation route at `app/api/revalidate/path/route.ts`:
|
||||
|
||||
```ts app/api/revalidate/path/route.ts
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const { path, type, secret } = await request.json();
|
||||
// Create a REVALIDATION_SECRET and set it in your environment variables
|
||||
if (secret !== process.env.REVALIDATION_SECRET) {
|
||||
return NextResponse.json({ message: "Invalid secret" }, { status: 401 });
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
return NextResponse.json({ message: "Path is required" }, { status: 400 });
|
||||
}
|
||||
|
||||
revalidatePath(path, type);
|
||||
|
||||
return NextResponse.json({ revalidated: true });
|
||||
} catch (err) {
|
||||
console.error("Error revalidating path:", err);
|
||||
return NextResponse.json({ message: "Error revalidating path" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Revalidation handler: Pages Router
|
||||
|
||||
If you are using the Pages router, create a new revalidation route at `pages/api/revalidate/path.ts`:
|
||||
|
||||
```ts pages/api/revalidate/path.ts
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ message: "Method not allowed" });
|
||||
}
|
||||
|
||||
const { path, secret } = req.body;
|
||||
|
||||
if (secret !== process.env.REVALIDATION_SECRET) {
|
||||
return res.status(401).json({ message: "Invalid secret" });
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
return res.status(400).json({ message: "Path is required" });
|
||||
}
|
||||
|
||||
await res.revalidate(path);
|
||||
|
||||
return res.json({ revalidated: true });
|
||||
} catch (err) {
|
||||
console.error("Error revalidating path:", err);
|
||||
return res.status(500).json({ message: "Error revalidating path" });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Revalidation task
|
||||
|
||||
This task takes a `path` as a payload and will revalidate the path you specify, using the handler you set up previously.
|
||||
|
||||
<Note>
|
||||
|
||||
To run this task locally you will need to set the `REVALIDATION_SECRET` environment variable in your `.env.local` file (or `.env` file if using Pages router).
|
||||
|
||||
To run this task in production, you will need to set the `REVALIDATION_SECRET` environment variable in Vercel, in your project settings, and also in your environment variables in the Trigger.dev dashboard.
|
||||
|
||||
</Note>
|
||||
|
||||
```ts trigger/revalidate-path.ts
|
||||
import { logger, task } from "@trigger.dev/sdk/v3";
|
||||
|
||||
const NEXTJS_APP_URL = process.env.NEXTJS_APP_URL; // e.g. "http://localhost:3000" or "https://my-nextjs-app.vercel.app"
|
||||
const REVALIDATION_SECRET = process.env.REVALIDATION_SECRET; // Create a REVALIDATION_SECRET and set it in your environment variables
|
||||
|
||||
export const revalidatePath = task({
|
||||
id: "revalidate-path",
|
||||
run: async (payload: { path: string }) => {
|
||||
const { path } = payload;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${NEXTJS_APP_URL}/api/revalidate/path`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
path: `${NEXTJS_APP_URL}/${path}`,
|
||||
secret: REVALIDATION_SECRET,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
logger.log("Path revalidation successful", { path });
|
||||
return { success: true };
|
||||
} else {
|
||||
logger.error("Path revalidation failed", {
|
||||
path,
|
||||
statusCode: response.status,
|
||||
statusText: response.statusText,
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: `Revalidation failed with status ${response.status}: ${response.statusText}`,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Path revalidation encountered an error", {
|
||||
path,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to revalidate path due to an unexpected error`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Testing the revalidation task
|
||||
|
||||
You can test your revalidation task in the Trigger.dev dashboard on the testing page, using the following payload.
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "<path-to-revalidate>" // e.g. "blog"
|
||||
}
|
||||
```
|
||||
|
||||
## Additional resources for Next.js
|
||||
|
||||
<Card
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Prisma setup guide"
|
||||
sidebarTitle: "Prisma setup guide"
|
||||
description: "This guide will show you how to setup Prisma with Trigger.dev"
|
||||
description: "This guide will show you how to set up Prisma with Trigger.dev"
|
||||
icon: "Triangle"
|
||||
---
|
||||
|
||||
@@ -14,7 +14,7 @@ import UsefulNextSteps from "/snippets/useful-next-steps.mdx";
|
||||
|
||||
## Overview
|
||||
|
||||
This guide will show you how to set up Prisma with Trigger.dev, test and view an example task run.
|
||||
This guide will show you how to set up [Prisma](https://www.prisma.io/) with Trigger.dev, test and view an example task run.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -24,7 +24,9 @@ This guide will show you how to set up Prisma with Trigger.dev, test and view an
|
||||
- Prisma ORM [installed and initialized](https://www.prisma.io/docs/getting-started/quickstart) in your project
|
||||
- A `DATABASE_URL` environment variable set in your `.env` file, pointing to your PostgreSQL database (e.g. `postgresql://user:password@localhost:5432/dbname`)
|
||||
|
||||
## Initial setup
|
||||
## Initial setup (optional)
|
||||
|
||||
Follow these steps if you don't already have Trigger.dev set up in your project.
|
||||
|
||||
<Steps>
|
||||
<CliInitStep />
|
||||
@@ -151,8 +153,7 @@ With the build extension and task configured, you can now deploy your task using
|
||||
|
||||
<Step title="Adding your DATABASE_URL environment variable to Trigger.dev">
|
||||
|
||||
In the sidebar select the "Environment Variables" page, then press the "New environment variable"
|
||||
button. 
|
||||
In your Trigger.dev dashboard sidebar click "Environment Variables" <Icon icon="circle-1" iconType="solid" size={20} color="A8FF53" />, and then the "New environment variable" button <Icon icon="circle-2" iconType="solid" size={20} color="A8FF53" />.
|
||||
|
||||
You can add values for your local dev environment, staging and prod. in this case we will add the `DATABASE_URL` for the production environment.
|
||||
|
||||
@@ -167,9 +168,9 @@ You can add values for your local dev environment, staging and prod. in this cas
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "John Doe",
|
||||
"email": "john@doe.test",
|
||||
"id": 12345
|
||||
"name": "<a-name>", // e.g. "John Doe"
|
||||
"email": "<a-email>", // e.g. "john@doe.test"
|
||||
"id": <a-number> // e.g. 12345
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -37,21 +37,24 @@ Get set up fast using our detailed walk-through guides.
|
||||
|
||||
Tasks you can copy and paste to get started with Trigger.dev. They can all be extended and customized to fit your needs.
|
||||
|
||||
| Example task | Description |
|
||||
| :---------------------------------------------------------------------------- | :----------------------------------------------------------------------------- |
|
||||
| [DALL·E 3 image generation](/guides/examples/dall-e3-generate-image) | Use OpenAI's GPT-4o and DALL·E 3 to generate an image and text. |
|
||||
| [Deepgram audio transcription](/guides/examples/deepgram-transcribe-audio) | Transcribe audio using Deepgram's speech recognition API. |
|
||||
| [FFmpeg video processing](/guides/examples/ffmpeg-video-processing) | Use FFmpeg to process a video in various ways and save it to Cloudflare R2. |
|
||||
| [OpenAI with retrying](/guides/examples/open-ai-with-retrying) | Create a reusable OpenAI task with custom retry options. |
|
||||
| [PDF to image](/guides/examples/pdf-to-image) | Use `MuPDF` to turn a PDF into images and save them to Cloudflare R2. |
|
||||
| [React to PDF](/guides/examples/react-pdf) | Use `react-pdf` to generate a PDF and save it to Cloudflare R2. |
|
||||
| [Puppeteer](/guides/examples/puppeteer) | Use Puppeteer to generate a PDF or scrape a webpage. |
|
||||
| [Resend email sequence](/guides/examples/resend-email-sequence) | Send a sequence of emails over several days using Resend with Trigger.dev. |
|
||||
| [Sentry error tracking](/guides/examples/sentry-error-tracking) | Automatically send errors to Sentry from your tasks. |
|
||||
| [Sharp image processing](/guides/examples/sharp-image-processing) | Use Sharp to process an image and save it to Cloudflare R2. |
|
||||
| [Supabase database operations](/guides/examples/supabase-database-operations) | Run basic CRUD operations on a table in a Supabase database using Trigger.dev. |
|
||||
| [Supabase Storage upload](/guides/examples/supabase-storage-upload) | Download a video from a URL and upload it to Supabase Storage using S3. |
|
||||
| [Vercel AI SDK](/guides/examples/vercel-ai-sdk) | Use Vercel AI SDK to generate text using OpenAI. |
|
||||
| Example task | Description |
|
||||
| :---------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [DALL·E 3 image generation](/guides/examples/dall-e3-generate-image) | Use OpenAI's GPT-4o and DALL·E 3 to generate an image and text. |
|
||||
| [Deepgram audio transcription](/guides/examples/deepgram-transcribe-audio) | Transcribe audio using Deepgram's speech recognition API. |
|
||||
| [fal AI image to cartoon](/guides/examples/fal-ai-image-to-cartoon) | Convert an image to a cartoon using fal AI, and upload the result to Cloudflare R2. |
|
||||
| [FFmpeg video processing](/guides/examples/ffmpeg-video-processing) | Use FFmpeg to process a video in various ways and save it to Cloudflare R2. |
|
||||
| [Firecrawl URL crawl](/guides/examples/firecrawl-url-crawl) | Learn how to use Firecrawl to crawl a URL and return LLM-ready markdown. |
|
||||
| [OpenAI with retrying](/guides/examples/open-ai-with-retrying) | Create a reusable OpenAI task with custom retry options. |
|
||||
| [PDF to image](/guides/examples/pdf-to-image) | Use `MuPDF` to turn a PDF into images and save them to Cloudflare R2. |
|
||||
| [React to PDF](/guides/examples/react-pdf) | Use `react-pdf` to generate a PDF and save it to Cloudflare R2. |
|
||||
| [Puppeteer](/guides/examples/puppeteer) | Use Puppeteer to generate a PDF or scrape a webpage. |
|
||||
| [Resend email sequence](/guides/examples/resend-email-sequence) | Send a sequence of emails over several days using Resend with Trigger.dev. |
|
||||
| [Scrape Hacker News](/guides/examples/scrape-hacker-news) | Scrape Hacker News using BrowserBase and Puppeteer, summarize the articles with ChatGPT and send an email of the summary every weekday using Resend. |
|
||||
| [Sentry error tracking](/guides/examples/sentry-error-tracking) | Automatically send errors to Sentry from your tasks. |
|
||||
| [Sharp image processing](/guides/examples/sharp-image-processing) | Use Sharp to process an image and save it to Cloudflare R2. |
|
||||
| [Supabase database operations](/guides/examples/supabase-database-operations) | Run basic CRUD operations on a table in a Supabase database using Trigger.dev. |
|
||||
| [Supabase Storage upload](/guides/examples/supabase-storage-upload) | Download a video from a URL and upload it to Supabase Storage using S3. |
|
||||
| [Vercel AI SDK](/guides/examples/vercel-ai-sdk) | Use Vercel AI SDK to generate text using OpenAI. |
|
||||
|
||||
<Note>
|
||||
If you would like to see a guide for your framework, or an example task for your use case, please
|
||||
|
||||
@@ -50,10 +50,11 @@ main().catch(console.error);
|
||||
|
||||
There are two methods of authenticating with the management API: using a secret key associated with a specific environment in a project (`secretKey`), or using a personal access token (`personalAccessToken`). Both methods should only be used in a backend server, as they provide full access to the project.
|
||||
|
||||
<Info>
|
||||
Support for client-side authentication is coming soon to v3 but is not available at the time of
|
||||
writing.
|
||||
</Info>
|
||||
<Note>
|
||||
There is a separate authentication strategy when making requests from your frontend application.
|
||||
See the [Frontend guide](/frontend/overview) for more information. This guide is for backend usage
|
||||
only.
|
||||
</Note>
|
||||
|
||||
Certain API functions work with both authentication methods, but require different arguments depending on the method used. For example, the `runs.list` function can be called using either a `secretKey` or a `personalAccessToken`, but the `projectRef` argument is required when using a `personalAccessToken`:
|
||||
|
||||
|
||||
+28
-3
@@ -99,6 +99,14 @@
|
||||
{
|
||||
"source": "/examples/:slug*",
|
||||
"destination": "/guides/examples/:slug*"
|
||||
},
|
||||
{
|
||||
"source": "/realtime",
|
||||
"destination": "/realtime/overview"
|
||||
},
|
||||
{
|
||||
"source": "/runs-and-attempts",
|
||||
"destination": "/runs"
|
||||
}
|
||||
],
|
||||
"anchors": [
|
||||
@@ -118,10 +126,10 @@
|
||||
"pages": [
|
||||
{
|
||||
"group": "Tasks",
|
||||
"pages": ["tasks/overview", "tasks/scheduled"]
|
||||
"pages": ["tasks/overview", "tasks/schemaTask", "tasks/scheduled"]
|
||||
},
|
||||
"triggering",
|
||||
"runs-and-attempts",
|
||||
"runs",
|
||||
"apikeys",
|
||||
{
|
||||
"group": "Configuration",
|
||||
@@ -136,8 +144,8 @@
|
||||
{
|
||||
"group": "Deployment",
|
||||
"pages": [
|
||||
"deploy-environment-variables",
|
||||
"cli-deploy",
|
||||
"deploy-environment-variables",
|
||||
"github-actions",
|
||||
{
|
||||
"group": "Deployment integrations",
|
||||
@@ -167,6 +175,20 @@
|
||||
"context"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Frontend usage",
|
||||
"pages": ["frontend/overview", "frontend/react-hooks"]
|
||||
},
|
||||
{
|
||||
"group": "Realtime API",
|
||||
"pages": [
|
||||
"realtime/overview",
|
||||
"realtime/subscribe-to-run",
|
||||
"realtime/subscribe-to-runs-with-tag",
|
||||
"realtime/use-realtime-run",
|
||||
"realtime/use-realtime-runs-with-tag"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "API reference",
|
||||
"pages": [
|
||||
@@ -277,6 +299,7 @@
|
||||
"group": "Guides",
|
||||
"pages": [
|
||||
"guides/frameworks/prisma",
|
||||
"guides/frameworks/drizzle",
|
||||
"guides/frameworks/sequin",
|
||||
{
|
||||
"group": "Supabase",
|
||||
@@ -306,7 +329,9 @@
|
||||
"pages": [
|
||||
"guides/examples/dall-e3-generate-image",
|
||||
"guides/examples/deepgram-transcribe-audio",
|
||||
"guides/examples/fal-ai-image-to-cartoon",
|
||||
"guides/examples/ffmpeg-video-processing",
|
||||
"guides/examples/firecrawl-url-crawl",
|
||||
"guides/examples/open-ai-with-retrying",
|
||||
"guides/examples/pdf-to-image",
|
||||
"guides/examples/puppeteer",
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
---
|
||||
title: Realtime overview
|
||||
sidebarTitle: Overview
|
||||
description: Using the Trigger.dev v3 realtime API
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
## How it works
|
||||
|
||||
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.
|
||||
|
||||
## Walkthrough
|
||||
|
||||
<div className="w-full h-full aspect-video">
|
||||
<iframe
|
||||
width="100%"
|
||||
height="100%"
|
||||
src="https://www.youtube.com/embed/RhJAbSGkS88?si=4Z72SfygeklNI3As"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
## Usage
|
||||
|
||||
After you trigger a task, you can subscribe to the run using the `runs.subscribeToRun` function. This function returns an async iterator that you can use to get updates on the run status.
|
||||
|
||||
```ts
|
||||
import { runs, tasks } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Every time the run changes, the async iterator will yield the updated run. You can use this to update your UI, log the run status, or take any other action.
|
||||
|
||||
Alternatively, you can subscribe to changes to any run that includes a specific tag (or tags) using the `runs.subscribeToRunsWithTag` function.
|
||||
|
||||
```ts
|
||||
import { runs } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Somewhere in your backend code
|
||||
for await (const run of runs.subscribeToRunsWithTag("user:1234")) {
|
||||
// This will log the run every time it changes, for all runs with the tag "user:1234"
|
||||
console.log(run);
|
||||
}
|
||||
```
|
||||
|
||||
If you've used `batchTrigger` to trigger multiple runs, you can also subscribe to changes to all the runs triggered in the batch using the `runs.subscribeToBatch` function.
|
||||
|
||||
```ts
|
||||
import { runs } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Somewhere in your backend code
|
||||
for await (const run of runs.subscribeToBatch("batch-id")) {
|
||||
// This will log the run every time it changes, for all runs in the batch with the ID "batch-id"
|
||||
console.log(run);
|
||||
}
|
||||
```
|
||||
|
||||
### React hooks
|
||||
|
||||
We also provide a set of React hooks that make it easy to use the Realtime API in your React components. See the [React hooks doc](/frontend/react-hooks) for more information.
|
||||
|
||||
## 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 the async iterator is NOT the same as the run object returned by the `runs.retrieve` function. This is because Electric SQL streams changes from a single PostgreSQL table, and the run object returned by `runs.retrieve` is a combination of multiple tables.
|
||||
|
||||
The run object returned by the async iterator has the following fields:
|
||||
|
||||
<ParamField path="id" type="string" required>
|
||||
The run ID.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="taskIdentifier" type="string" required>
|
||||
The task identifier.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="payload" type="object" required>
|
||||
The input payload for the run.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="output" type="object">
|
||||
The output result of the run.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="createdAt" type="Date" required>
|
||||
Timestamp when the run was created.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="updatedAt" type="Date" required>
|
||||
Timestamp when the run was last updated.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="number" type="number" required>
|
||||
Sequential number assigned to the run.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="status" type="RunStatus" required>
|
||||
Current status of the run.
|
||||
|
||||
<Accordion title="RunStatus enum">
|
||||
|
||||
| Status | Description |
|
||||
| -------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| `WAITING_FOR_DEPLOY` | Task hasn't been deployed yet but is waiting to be executed |
|
||||
| `QUEUED` | Run is waiting to be executed by a worker |
|
||||
| `EXECUTING` | Run is currently being executed by a worker |
|
||||
| `REATTEMPTING` | Run has failed and is waiting to be retried |
|
||||
| `FROZEN` | Run has been paused by the system, and will be resumed by the system |
|
||||
| `COMPLETED` | Run has been completed successfully |
|
||||
| `CANCELED` | Run has been canceled by the user |
|
||||
| `FAILED` | Run has been completed with errors |
|
||||
| `CRASHED` | Run has crashed and won't be retried, most likely the worker ran out of resources, e.g. memory or storage |
|
||||
| `INTERRUPTED` | Run was interrupted during execution, mostly this happens in development environments |
|
||||
| `SYSTEM_FAILURE` | Run has failed to complete, due to an error in the system |
|
||||
| `DELAYED` | Run has been scheduled to run at a specific time |
|
||||
| `EXPIRED` | Run has expired and won't be executed |
|
||||
| `TIMED_OUT` | Run has reached it's maxDuration and has been stopped |
|
||||
|
||||
</Accordion>
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="durationMs" type="number" required>
|
||||
Duration of the run in milliseconds.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="costInCents" type="number" required>
|
||||
Total cost of the run in cents.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="baseCostInCents" type="number" required>
|
||||
Base cost of the run in cents before any additional charges.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="tags" type="string[]" required>
|
||||
Array of tags associated with the run.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="idempotencyKey" type="string">
|
||||
Key used to ensure idempotent execution.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="expiredAt" type="Date">
|
||||
Timestamp when the run expired.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="ttl" type="string">
|
||||
Time-to-live duration for the run.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="finishedAt" type="Date">
|
||||
Timestamp when the run finished.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="startedAt" type="Date">
|
||||
Timestamp when the run started.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="delayedUntil" type="Date">
|
||||
Timestamp until which the run is delayed.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="queuedAt" type="Date">
|
||||
Timestamp when the run was queued.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="metadata" type="Record<string, DeserializedJson>">
|
||||
Additional metadata associated with the run.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="error" type="SerializedError">
|
||||
Error information if the run failed.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="isTest" type="boolean" required>
|
||||
Indicates whether this is a test run.
|
||||
</ParamField>
|
||||
|
||||
## 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<typeof myTask>(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<typeof myTask | typeof myOtherTask>("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](/frontend/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.
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: runs.subscribeToRun
|
||||
sidebarTitle: subscribeToRun
|
||||
description: Subscribes to all changes to a run.
|
||||
---
|
||||
|
||||
import RunObject from "/snippets/realtime/run-object.mdx";
|
||||
|
||||
<RequestExample>
|
||||
|
||||
```ts Example
|
||||
import { runs } from "@trigger.dev/sdk/v3";
|
||||
|
||||
for await (const run of runs.subscribeToRun("run_1234")) {
|
||||
console.log(run);
|
||||
}
|
||||
```
|
||||
|
||||
</RequestExample>
|
||||
|
||||
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 one of the following scopes:
|
||||
|
||||
- `read:runs`
|
||||
- `read:runs:<runId>`
|
||||
|
||||
To generate a public access token, use the `auth.createPublicToken` function:
|
||||
|
||||
```ts
|
||||
import { auth } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Somewhere in your backend code
|
||||
const publicToken = await auth.createPublicToken({
|
||||
scopes: {
|
||||
read: {
|
||||
runs: ["run_1234"],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
The AsyncIterator yields an object with the following properties:
|
||||
|
||||
<RunObject />
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: runs.subscribeToRunsWithTag
|
||||
sidebarTitle: subscribeToRunsWithTag
|
||||
description: Subscribes to all changes to runs with a specific tag.
|
||||
---
|
||||
|
||||
import RunObject from "/snippets/realtime/run-object.mdx";
|
||||
|
||||
<RequestExample>
|
||||
|
||||
```ts Example
|
||||
import { runs } from "@trigger.dev/sdk/v3";
|
||||
|
||||
for await (const run of runs.subscribeToRunsWithTag("user:1234")) {
|
||||
console.log(run);
|
||||
}
|
||||
```
|
||||
|
||||
</RequestExample>
|
||||
|
||||
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 one of the following scopes:
|
||||
|
||||
- `read:runs`
|
||||
- `read:tags:<tagName>`
|
||||
|
||||
To generate a public access token, use the `auth.createPublicToken` function:
|
||||
|
||||
```ts
|
||||
import { auth } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Somewhere in your backend code
|
||||
const publicToken = await auth.createPublicToken({
|
||||
scopes: {
|
||||
read: {
|
||||
tags: ["user:1234"],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
The AsyncIterator yields an object with the following properties:
|
||||
|
||||
<RunObject />
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: useRealtimeRun
|
||||
sidebarTitle: useRealtimeRun
|
||||
description: Subscribes to all changes to a run in a React component.
|
||||
---
|
||||
|
||||
import RunObject from "/snippets/realtime/run-object.mdx";
|
||||
|
||||
<RequestExample>
|
||||
|
||||
```ts Example
|
||||
import { useRealtimeRun } from "@trigger.dev/react-hooks";
|
||||
import type { myTask } from "./trigger/tasks";
|
||||
|
||||
export function MyComponent({ runId }: { runId: string }) {
|
||||
const { run, error } = useRealtimeRun<typeof myTask>(runId);
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
return <div>Run: {run.id}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
</RequestExample>
|
||||
|
||||
This react hook subscribes to all changes to a run. See the [React hooks doc](/frontend/react-hooks) for more information on how to use this hook.
|
||||
|
||||
### Response
|
||||
|
||||
The react hook returns an object with the following properties:
|
||||
|
||||
<ParamField path="run" type="object" required>
|
||||
The run object. See the [Run object doc](realtime/overview#run-object) for more information.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="error" type="Error">
|
||||
An error object if an error occurred while subscribing to a run.
|
||||
</ParamField>
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: useRealtimeRunsWithTag
|
||||
sidebarTitle: useRealtimeRunsWithTag
|
||||
description: Subscribes to all changes to runs with a specific tag in a React component.
|
||||
---
|
||||
|
||||
import RunObject from "/snippets/realtime/run-object.mdx";
|
||||
|
||||
<RequestExample>
|
||||
|
||||
```ts Example
|
||||
import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks";
|
||||
import type { myTask, myOtherTask } from "./trigger/tasks";
|
||||
|
||||
export function MyComponent({ tag }: { tag: string }) {
|
||||
const { runs, error } = useRealtimeRunsWithTag<typeof myTask | typeof myOtherTask>(tag);
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{runs.map((run) => (
|
||||
<div key={run.id}>Run: {run.id}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</RequestExample>
|
||||
|
||||
This react hook subscribes to all changes to runs with a specific tag. See the [React hooks doc](/frontend/react-hooks) for more information on how to use this hook.
|
||||
|
||||
### Response
|
||||
|
||||
The react hook returns an object with the following properties:
|
||||
|
||||
<ParamField path="runs" type="object[]" required>
|
||||
An array of run objects. See the [Run object doc](/realtime/overview#run-object) for more
|
||||
information.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="error" type="Error">
|
||||
An error object if an error occurred while subscribing.
|
||||
</ParamField>
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Runs & attempts"
|
||||
description: "Understanding the lifecycle of task execution in Trigger.dev"
|
||||
title: "Runs"
|
||||
description: "Understanding the lifecycle of task run execution in Trigger.dev"
|
||||
---
|
||||
|
||||
In Trigger.dev, the concepts of runs and attempts are fundamental to understanding how tasks are executed and managed. This article explains these concepts in detail and provides insights into the various states a run can go through during its lifecycle.
|
||||
@@ -24,37 +24,52 @@ Runs can also find themselves in lots of other states depending on what's happen
|
||||
|
||||
### Initial States
|
||||
|
||||
<Icon icon="rectangle-history" iconType="solid" color="#FBBF24" size={17} /> **Waiting for deploy**: If a task is triggered before it has been deployed, the run enters this state and waits for the task to be deployed.
|
||||
<Icon icon="rectangle-history" iconType="solid" color="#FBBF24" size={17} /> **Waiting for deploy**:
|
||||
If a task is triggered before it has been deployed, the run enters this state and waits for the task
|
||||
to be deployed.
|
||||
|
||||
<Icon icon="clock" iconType="solid" color="#878C99" size={17} /> **Delayed**: When a run is triggered with a delay, it enters this state until the specified delay period has passed.
|
||||
<Icon icon="clock" iconType="solid" color="#878C99" size={17} /> **Delayed**: When a run is triggered
|
||||
with a delay, it enters this state until the specified delay period has passed.
|
||||
|
||||
<Icon icon="rectangle-history" iconType="solid" color="#878C99" size={17} /> **Queued**: The run is ready to be executed and is waiting in the queue.
|
||||
<Icon icon="rectangle-history" iconType="solid" color="#878C99" size={17} /> **Queued**: The run is ready
|
||||
to be executed and is waiting in the queue.
|
||||
|
||||
### Execution States
|
||||
|
||||
<Icon icon="spinner-third" iconType="duotone" color="#3B82F6" size={17} /> **Executing**: The task is currently running.
|
||||
<Icon icon="spinner-third" iconType="duotone" color="#3B82F6" size={17} /> **Executing**: The task is
|
||||
currently running.
|
||||
|
||||
<Icon icon="arrows-rotate" iconType="solid" color="#3B82F6" size={17} /> **Reattempting**: The task has failed and is being retried.
|
||||
<Icon icon="arrows-rotate" iconType="solid" color="#3B82F6" size={17} /> **Reattempting**: The task has
|
||||
failed and is being retried.
|
||||
|
||||
<Icon icon="snowflake" iconType="solid" color="#68BAF2" size={17} /> **Frozen**: Task has been frozen and is waiting to be resumed.
|
||||
<Icon icon="snowflake" iconType="solid" color="#68BAF2" size={17} /> **Frozen**: Task has been frozen
|
||||
and is waiting to be resumed.
|
||||
|
||||
### Final States
|
||||
|
||||
<Icon icon="circle-check" iconType="solid" color="#28BF5C" size={17} /> **Completed**: The task has successfully finished execution.
|
||||
<Icon icon="circle-check" iconType="solid" color="#28BF5C" size={17} /> **Completed**: The task has successfully
|
||||
finished execution.
|
||||
|
||||
<Icon icon="ban" iconType="solid" color="#878C99" size={17} /> **Canceled**: The run was manually canceled by the user.
|
||||
<Icon icon="ban" iconType="solid" color="#878C99" size={17} /> **Canceled**: The run was manually canceled
|
||||
by the user.
|
||||
|
||||
<Icon icon="circle-xmark" iconType="solid" color="#E11D48" size={17} /> **Failed**: The task has failed to complete successfully.
|
||||
<Icon icon="circle-xmark" iconType="solid" color="#E11D48" size={17} /> **Failed**: The task has failed
|
||||
to complete successfully.
|
||||
|
||||
<Icon icon="alarm-exclamation" iconType="solid" color="#E11D48" size={17} /> **Timed out**: Task has failed because it exceeded its `maxDuration`.
|
||||
<Icon icon="alarm-exclamation" iconType="solid" color="#E11D48" size={17} /> **Timed out**: Task has
|
||||
failed because it exceeded its `maxDuration`.
|
||||
|
||||
<Icon icon="fire" iconType="solid" color="#E11D48" size={17} /> **Crashed**: The worker process crashed during execution (likely due to an Out of Memory error).
|
||||
<Icon icon="fire" iconType="solid" color="#E11D48" size={17} /> **Crashed**: The worker process crashed
|
||||
during execution (likely due to an Out of Memory error).
|
||||
|
||||
<Icon icon="bolt-slash" iconType="solid" color="#E11D48" size={17} /> **Interrupted**: In development mode, when the CLI is disconnected.
|
||||
<Icon icon="bolt-slash" iconType="solid" color="#E11D48" size={17} /> **Interrupted**: In development
|
||||
mode, when the CLI is disconnected.
|
||||
|
||||
<Icon icon="bug" iconType="solid" color="#E11D48" size={17} /> **System failure**: An unrecoverable system error has occurred.
|
||||
<Icon icon="bug" iconType="solid" color="#E11D48" size={17} /> **System failure**: An unrecoverable system
|
||||
error has occurred.
|
||||
|
||||
<Icon icon="trash-can" iconType="solid" color="#878C99" size={17} /> **Expired**: The run's Time-to-Live (TTL) has passed before it could start executing.
|
||||
<Icon icon="trash-can" iconType="solid" color="#878C99" size={17} /> **Expired**: The run's Time-to-Live
|
||||
(TTL) has passed before it could start executing.
|
||||
|
||||
## Attempts
|
||||
|
||||
@@ -150,13 +165,13 @@ You can also replay runs from the dashboard using the same or different payload.
|
||||
|
||||
The `triggerAndWait()` function triggers a task and then lets you wait for the result before continuing. [Learn more about triggerAndWait()](/triggering#yourtask-triggerandwait).
|
||||
|
||||
.png)
|
||||
.png>)
|
||||
|
||||
#### batchTriggerAndWait()
|
||||
|
||||
Similar to `triggerAndWait()`, the `batchTriggerAndWait()` function lets you batch trigger a task and wait for all the results [Learn more about batchTriggerAndWait()](/triggering#yourtask-batchtriggerandwait).
|
||||
|
||||
.png)
|
||||
.png>)
|
||||
|
||||
### Runs API
|
||||
|
||||
@@ -181,6 +196,18 @@ runs.cancel(runId);
|
||||
|
||||
These methods allow you to access detailed information about runs and their attempts, including payloads, outputs, parent runs, and child runs.
|
||||
|
||||
### Real-time updates
|
||||
|
||||
You can subscribe to run updates in real-time using the `subscribeToRun()` function:
|
||||
|
||||
```ts
|
||||
for await (const run of runs.subscribeToRun(runId)) {
|
||||
console.log(run);
|
||||
}
|
||||
```
|
||||
|
||||
For more on real-time updates, see the [Realtime](/realtime) documentation.
|
||||
|
||||
### Triggering runs for undeployed tasks
|
||||
|
||||
It's possible to trigger a run for a task that hasn't been deployed yet. The run will enter the "Waiting for deploy" state until the task is deployed. Once deployed, the run will be queued and executed normally.
|
||||
@@ -0,0 +1,108 @@
|
||||
<ParamField path="id" type="string" required>
|
||||
The run ID.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="taskIdentifier" type="string" required>
|
||||
The task identifier.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="payload" type="object" required>
|
||||
The input payload for the run.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="output" type="object">
|
||||
The output result of the run.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="createdAt" type="Date" required>
|
||||
Timestamp when the run was created.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="updatedAt" type="Date" required>
|
||||
Timestamp when the run was last updated.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="number" type="number" required>
|
||||
Sequential number assigned to the run.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="status" type="RunStatus" required>
|
||||
Current status of the run.
|
||||
|
||||
<Accordion title="RunStatus enum">
|
||||
|
||||
| Status | Description |
|
||||
| -------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| `WAITING_FOR_DEPLOY` | Task hasn't been deployed yet but is waiting to be executed |
|
||||
| `QUEUED` | Run is waiting to be executed by a worker |
|
||||
| `EXECUTING` | Run is currently being executed by a worker |
|
||||
| `REATTEMPTING` | Run has failed and is waiting to be retried |
|
||||
| `FROZEN` | Run has been paused by the system, and will be resumed by the system |
|
||||
| `COMPLETED` | Run has been completed successfully |
|
||||
| `CANCELED` | Run has been canceled by the user |
|
||||
| `FAILED` | Run has been completed with errors |
|
||||
| `CRASHED` | Run has crashed and won't be retried, most likely the worker ran out of resources, e.g. memory or storage |
|
||||
| `INTERRUPTED` | Run was interrupted during execution, mostly this happens in development environments |
|
||||
| `SYSTEM_FAILURE` | Run has failed to complete, due to an error in the system |
|
||||
| `DELAYED` | Run has been scheduled to run at a specific time |
|
||||
| `EXPIRED` | Run has expired and won't be executed |
|
||||
| `TIMED_OUT` | Run has reached it's maxDuration and has been stopped |
|
||||
|
||||
</Accordion>
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="durationMs" type="number" required>
|
||||
Duration of the run in milliseconds.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="costInCents" type="number" required>
|
||||
Total cost of the run in cents.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="baseCostInCents" type="number" required>
|
||||
Base cost of the run in cents before any additional charges.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="tags" type="string[]" required>
|
||||
Array of tags associated with the run.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="idempotencyKey" type="string">
|
||||
Key used to ensure idempotent execution.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="expiredAt" type="Date">
|
||||
Timestamp when the run expired.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="ttl" type="string">
|
||||
Time-to-live duration for the run.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="finishedAt" type="Date">
|
||||
Timestamp when the run finished.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="startedAt" type="Date">
|
||||
Timestamp when the run started.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="delayedUntil" type="Date">
|
||||
Timestamp until which the run is delayed.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="queuedAt" type="Date">
|
||||
Timestamp when the run was queued.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="metadata" type="Record<string, DeserializedJson>">
|
||||
Additional metadata associated with the run.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="error" type="SerializedError">
|
||||
Error information if the run failed.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="isTest" type="boolean" required>
|
||||
Indicates whether this is a test run.
|
||||
</ParamField>
|
||||
@@ -0,0 +1,261 @@
|
||||
---
|
||||
title: "schemaTask"
|
||||
sidebarTitle: "Schema task"
|
||||
description: "Define tasks with a runtime payload schema and validate the payload before running the task."
|
||||
---
|
||||
|
||||
The `schemaTask` function allows you to define a task with a runtime payload schema. This schema is used to validate the payload before running the task or when triggering a task directly. If the payload does not match the schema, the task will not execute.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { schemaTask } from "@trigger.dev/sdk/v3";
|
||||
import { z } from "zod";
|
||||
|
||||
const myTask = schemaTask({
|
||||
id: "my-task",
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
age: z.number(),
|
||||
}),
|
||||
run: async (payload) => {
|
||||
console.log(payload.name, payload.age);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
`schemaTask` takes all the same options as [task](/tasks/overview), with the addition of a `schema` field. The `schema` field is a schema parser function from a schema library or or a custom parser function.
|
||||
|
||||
<Note>
|
||||
We will probably eventually combine `task` and `schemaTask` into a single function, but because
|
||||
that would be a breaking change, we are keeping them separate for now.
|
||||
</Note>
|
||||
|
||||
When you trigger the task directly, the payload will be validated against the schema before the [run](/runs) is created:
|
||||
|
||||
```ts
|
||||
import { tasks } from "@trigger.dev/sdk/v3";
|
||||
import { myTask } from "./trigger/myTasks";
|
||||
|
||||
// This will call the schema parser function and validate the payload
|
||||
await myTask.trigger({ name: "Alice", age: "oops" }); // this will throw an error
|
||||
|
||||
// This will NOT call the schema parser function
|
||||
await tasks.trigger<typeof myTask>("my-task", { name: "Alice", age: "oops" }); // this will not throw an error
|
||||
```
|
||||
|
||||
The error thrown when the payload does not match the schema will be the same as the error thrown by the schema parser function. For example, if you are using Zod, the error will be a `ZodError`.
|
||||
|
||||
We will also validate the payload every time before the task is run, so you can be sure that the payload is always valid. In the example above, the task would fail with a `TaskPayloadParsedError` error and skip retrying if the payload does not match the schema.
|
||||
|
||||
## Input/output schemas
|
||||
|
||||
Certain schema libraries, like Zod, split their type inference into "schema in" and "schema out". This means that you can define a single schema that will produce different types when triggering the task and when running the task. For example, you can define a schema that has a default value for a field, or a string coerced into a date:
|
||||
|
||||
```ts
|
||||
import { schemaTask } from "@trigger.dev/sdk/v3";
|
||||
import { z } from "zod";
|
||||
|
||||
const myTask = schemaTask({
|
||||
id: "my-task",
|
||||
schema: z.object({
|
||||
name: z.string().default("John"),
|
||||
age: z.number(),
|
||||
dob: z.coerce.date(),
|
||||
}),
|
||||
run: async (payload) => {
|
||||
console.log(payload.name, payload.age);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
In this case, the trigger payload type is `{ name?: string, age: number; dob: string }`, but the run payload type is `{ name: string, age: number; dob: Date }`. So you can trigger the task with a payload like this:
|
||||
|
||||
```ts
|
||||
await myTask.trigger({ age: 30, dob: "2020-01-01" }); // this is valid
|
||||
await myTask.trigger({ name: "Alice", age: 30, dob: "2020-01-01" }); // this is also valid
|
||||
```
|
||||
|
||||
## Supported schema types
|
||||
|
||||
### Zod
|
||||
|
||||
You can use the [Zod](https://zod.dev) schema library to define your schema. The schema will be validated using Zod's `parse` function.
|
||||
|
||||
```ts
|
||||
import { schemaTask } from "@trigger.dev/sdk/v3";
|
||||
import { z } from "zod";
|
||||
|
||||
export const zodTask = schemaTask({
|
||||
id: "types/zod",
|
||||
schema: z.object({
|
||||
bar: z.string(),
|
||||
baz: z.string().default("foo"),
|
||||
}),
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Yup
|
||||
|
||||
```ts
|
||||
import { schemaTask } from "@trigger.dev/sdk/v3";
|
||||
import * as yup from "yup";
|
||||
|
||||
export const yupTask = schemaTask({
|
||||
id: "types/yup",
|
||||
schema: yup.object({
|
||||
bar: yup.string().required(),
|
||||
baz: yup.string().default("foo"),
|
||||
}),
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Superstruct
|
||||
|
||||
```ts
|
||||
import { schemaTask } from "@trigger.dev/sdk/v3";
|
||||
import { object, string } from "superstruct";
|
||||
|
||||
export const superstructTask = schemaTask({
|
||||
id: "types/superstruct",
|
||||
schema: object({
|
||||
bar: string(),
|
||||
baz: string(),
|
||||
}),
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### ArkType
|
||||
|
||||
```ts
|
||||
import { schemaTask } from "@trigger.dev/sdk/v3";
|
||||
import { type } from "arktype";
|
||||
|
||||
export const arktypeTask = schemaTask({
|
||||
id: "types/arktype",
|
||||
schema: type({
|
||||
bar: "string",
|
||||
baz: "string",
|
||||
}).assert,
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### @effect/schema
|
||||
|
||||
```ts
|
||||
import { schemaTask } from "@trigger.dev/sdk/v3";
|
||||
import * as Schema from "@effect/schema/Schema";
|
||||
|
||||
// For some funny typescript reason, you cannot pass the Schema.decodeUnknownSync directly to schemaTask
|
||||
const effectSchemaParser = Schema.decodeUnknownSync(
|
||||
Schema.Struct({ bar: Schema.String, baz: Schema.String })
|
||||
);
|
||||
|
||||
export const effectTask = schemaTask({
|
||||
id: "types/effect",
|
||||
schema: effectSchemaParser,
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### runtypes
|
||||
|
||||
```ts
|
||||
import { schemaTask } from "@trigger.dev/sdk/v3";
|
||||
import * as T from "runtypes";
|
||||
|
||||
export const runtypesTask = schemaTask({
|
||||
id: "types/runtypes",
|
||||
schema: T.Record({
|
||||
bar: T.String,
|
||||
baz: T.String,
|
||||
}),
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### valibot
|
||||
|
||||
```ts
|
||||
import { schemaTask } from "@trigger.dev/sdk/v3";
|
||||
|
||||
import * as v from "valibot";
|
||||
|
||||
// For some funny typescript reason, you cannot pass the v.parser directly to schemaTask
|
||||
const valibotParser = v.parser(
|
||||
v.object({
|
||||
bar: v.string(),
|
||||
baz: v.string(),
|
||||
})
|
||||
);
|
||||
|
||||
export const valibotTask = schemaTask({
|
||||
id: "types/valibot",
|
||||
schema: valibotParser,
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### typebox
|
||||
|
||||
```ts
|
||||
import { schemaTask } from "@trigger.dev/sdk/v3";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { wrap } from "@typeschema/typebox";
|
||||
|
||||
export const typeboxTask = schemaTask({
|
||||
id: "types/typebox",
|
||||
schema: wrap(
|
||||
Type.Object({
|
||||
bar: Type.String(),
|
||||
baz: Type.String(),
|
||||
})
|
||||
),
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Custom parser function
|
||||
|
||||
You can also define a custom parser function that will be called with the payload before the task is run. The parser function should return the parsed payload or throw an error if the payload is invalid.
|
||||
|
||||
```ts
|
||||
import { schemaTask } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const customParserTask = schemaTask({
|
||||
id: "types/custom-parser",
|
||||
schema: (data: unknown) => {
|
||||
// This is a custom parser, and should do actual parsing (not just casting)
|
||||
if (typeof data !== "object") {
|
||||
throw new Error("Invalid data");
|
||||
}
|
||||
|
||||
const { bar, baz } = data as { bar: string; baz: string };
|
||||
|
||||
return { bar, baz };
|
||||
},
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -1,5 +1,12 @@
|
||||
# @trigger.dev/build
|
||||
|
||||
## 3.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies:
|
||||
- `@trigger.dev/core@3.1.1`
|
||||
|
||||
## 3.1.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/build",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"description": "trigger.dev build extensions",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
@@ -65,7 +65,7 @@
|
||||
"check-exports": "attw --pack ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/core": "workspace:3.1.0",
|
||||
"@trigger.dev/core": "workspace:3.1.1",
|
||||
"pkg-types": "^1.1.3",
|
||||
"tinyglobby": "^0.2.2",
|
||||
"tsconfck": "3.1.3"
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# trigger.dev
|
||||
|
||||
## 3.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Increase max retry count for deploy run controller operations ([#1450](https://github.com/triggerdotdev/trigger.dev/pull/1450))
|
||||
- Set parent PATH on forked worker processes ([#1448](https://github.com/triggerdotdev/trigger.dev/pull/1448))
|
||||
- Updated dependencies:
|
||||
- `@trigger.dev/core@3.1.1`
|
||||
- `@trigger.dev/build@3.1.1`
|
||||
|
||||
## 3.1.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trigger.dev",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"description": "A Command-Line Interface for Trigger.dev (v3) projects",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -87,8 +87,8 @@
|
||||
"@opentelemetry/sdk-trace-base": "1.25.1",
|
||||
"@opentelemetry/sdk-trace-node": "1.25.1",
|
||||
"@opentelemetry/semantic-conventions": "1.25.1",
|
||||
"@trigger.dev/build": "workspace:3.1.0",
|
||||
"@trigger.dev/core": "workspace:3.1.0",
|
||||
"@trigger.dev/build": "workspace:3.1.1",
|
||||
"@trigger.dev/core": "workspace:3.1.1",
|
||||
"c12": "^1.11.1",
|
||||
"chalk": "^5.2.0",
|
||||
"cli-table3": "^0.6.3",
|
||||
|
||||
@@ -43,7 +43,7 @@ const SHORT_HASH = env.TRIGGER_CONTENT_HASH!.slice(0, 9);
|
||||
const logger = new SimpleLogger(`[${MACHINE_NAME}][${SHORT_HASH}]`);
|
||||
|
||||
const defaultBackoff = new ExponentialBackoff("FullJitter", {
|
||||
maxRetries: 5,
|
||||
maxRetries: 7,
|
||||
});
|
||||
|
||||
cliLogger.loggerLevel = "debug";
|
||||
@@ -418,7 +418,7 @@ class ProdWorker {
|
||||
|
||||
// Retry if we don't receive EXECUTE_TASK_RUN_LAZY_ATTEMPT in a reasonable time
|
||||
// ..but we also have to be fast to avoid failing the task due to missing heartbeat
|
||||
for await (const { delay, retry } of defaultBackoff.min(10).maxRetries(3)) {
|
||||
for await (const { delay, retry } of defaultBackoff.min(10).maxRetries(7)) {
|
||||
if (retry > 0) {
|
||||
logger.log("retrying ready for lazy attempt", { retry });
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ export class TaskRunProcess {
|
||||
OTEL_IMPORT_HOOK_INCLUDES: workerManifest.otelImportHook?.include?.join(","),
|
||||
// TODO: this will probably need to use something different for bun (maybe --preload?)
|
||||
NODE_OPTIONS: execOptionsForRuntime(workerManifest.runtime, workerManifest),
|
||||
PATH: process.env.PATH,
|
||||
};
|
||||
|
||||
logger.debug(`[${this.runId}] initializing task run process`, {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# internal-platform
|
||||
|
||||
## 3.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Pass init output to both local and global `handleError` functions ([#1441](https://github.com/triggerdotdev/trigger.dev/pull/1441))
|
||||
- Add outdated SDK error ([#1453](https://github.com/triggerdotdev/trigger.dev/pull/1453))
|
||||
- Add individual run ids to auto-generated public access token when calling batchTrigger ([#1449](https://github.com/triggerdotdev/trigger.dev/pull/1449))
|
||||
|
||||
## 3.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/core",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"description": "Core code used across the Trigger.dev SDK and platform",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -230,7 +230,7 @@ export class ApiClient {
|
||||
secretKey: this.accessToken,
|
||||
payload: {
|
||||
...claims,
|
||||
scopes: [`read:batch:${data.batchId}`],
|
||||
scopes: [`read:batch:${data.batchId}`].concat(data.runs.map((r) => `read:runs:${r}`)),
|
||||
},
|
||||
expirationTime: requestOptions?.publicAccessToken?.expirationTime ?? "1h",
|
||||
});
|
||||
|
||||
@@ -167,6 +167,7 @@ export function shouldRetryError(error: TaskRunError): boolean {
|
||||
case "MAX_DURATION_EXCEEDED":
|
||||
case "DISK_SPACE_EXCEEDED":
|
||||
case "TASK_RUN_HEARTBEAT_TIMEOUT":
|
||||
case "OUTDATED_SDK_VERSION":
|
||||
return false;
|
||||
|
||||
case "GRACEFUL_EXIT_TIMEOUT":
|
||||
@@ -428,6 +429,14 @@ const prettyInternalErrors: Partial<
|
||||
magic: "CONTACT_FORM",
|
||||
},
|
||||
},
|
||||
OUTDATED_SDK_VERSION: {
|
||||
message:
|
||||
"Your task is using an outdated version of the SDK. Please upgrade to the latest version.",
|
||||
link: {
|
||||
name: "Beta upgrade guide",
|
||||
href: links.docs.upgrade.beta,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const getPrettyTaskRunError = (code: TaskRunInternalError["code"]): TaskRunInternalError => {
|
||||
|
||||
@@ -9,6 +9,9 @@ export const links = {
|
||||
machines: {
|
||||
home: "https://trigger.dev/docs/v3/machines",
|
||||
},
|
||||
upgrade: {
|
||||
beta: "https://trigger.dev/docs/upgrading-beta",
|
||||
},
|
||||
},
|
||||
site: {
|
||||
home: "https://trigger.dev",
|
||||
|
||||
@@ -104,6 +104,7 @@ export const TaskRunInternalError = z.object({
|
||||
"DISK_SPACE_EXCEEDED",
|
||||
"POD_EVICTED",
|
||||
"POD_UNKNOWN_ERROR",
|
||||
"OUTDATED_SDK_VERSION",
|
||||
]),
|
||||
message: z.string().optional(),
|
||||
stackTrace: z.string().optional(),
|
||||
|
||||
@@ -127,6 +127,7 @@ export type HandleErrorResult =
|
||||
|
||||
export type HandleErrorArgs = {
|
||||
ctx: Context;
|
||||
init: unknown;
|
||||
retry?: RetryOptions;
|
||||
retryAt?: Date;
|
||||
retryDelayInMs?: number;
|
||||
@@ -152,9 +153,9 @@ type CommonTaskOptions<
|
||||
/** The retry settings when an uncaught error is thrown.
|
||||
*
|
||||
* If omitted it will use the values in your `trigger.config.ts` file.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
*
|
||||
*
|
||||
* ```
|
||||
* export const taskWithRetries = task({
|
||||
id: "task-with-retries",
|
||||
@@ -174,10 +175,10 @@ type CommonTaskOptions<
|
||||
retry?: RetryOptions;
|
||||
|
||||
/** Used to configure what should happen when more than one run is triggered at the same time.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* @example
|
||||
* one at a time execution
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* export const oneAtATime = task({
|
||||
id: "one-at-a-time",
|
||||
@@ -192,9 +193,9 @@ type CommonTaskOptions<
|
||||
*/
|
||||
queue?: QueueOptions;
|
||||
/** Configure the spec of the machine you want your task to run on.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* export const heavyTask = task({
|
||||
id: "heavy-task",
|
||||
|
||||
@@ -176,6 +176,7 @@ export class TaskExecutor {
|
||||
runError,
|
||||
parsedPayload,
|
||||
ctx,
|
||||
initOutput,
|
||||
signal
|
||||
);
|
||||
|
||||
@@ -498,6 +499,7 @@ export class TaskExecutor {
|
||||
error: unknown,
|
||||
payload: any,
|
||||
ctx: TaskRunContext,
|
||||
init: unknown,
|
||||
signal?: AbortSignal
|
||||
): Promise<
|
||||
| { status: "retry"; retry: TaskRunExecutionRetry; error?: unknown }
|
||||
@@ -550,6 +552,7 @@ export class TaskExecutor {
|
||||
const handleErrorResult = this.task.fns.handleError
|
||||
? await this.task.fns.handleError(payload, error, {
|
||||
ctx,
|
||||
init,
|
||||
retry,
|
||||
retryDelayInMs: delay,
|
||||
retryAt: delay ? new Date(Date.now() + delay) : undefined,
|
||||
@@ -558,6 +561,7 @@ export class TaskExecutor {
|
||||
: this._importedConfig
|
||||
? await this._handleErrorFn?.(payload, error, {
|
||||
ctx,
|
||||
init,
|
||||
retry,
|
||||
retryDelayInMs: delay,
|
||||
retryAt: delay ? new Date(Date.now() + delay) : undefined,
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @trigger.dev/react-hooks
|
||||
|
||||
## 3.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- useBatch renamed to useRealtimeBatch ([#1447](https://github.com/triggerdotdev/trigger.dev/pull/1447))
|
||||
- Updated dependencies:
|
||||
- `@trigger.dev/core@3.1.1`
|
||||
|
||||
## 3.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/react-hooks",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"description": "trigger.dev react hooks",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
@@ -37,7 +37,7 @@
|
||||
"check-exports": "attw --pack ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/core": "workspace:^3.1.0",
|
||||
"@trigger.dev/core": "workspace:^3.1.1",
|
||||
"swr": "^2.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -4,7 +4,21 @@ import { AnyTask, InferRunTypes, TaskRunShape } from "@trigger.dev/core/v3";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useApiClient } from "./useApiClient.js";
|
||||
|
||||
export function useBatch<TTask extends AnyTask>(batchId: string) {
|
||||
/**
|
||||
* hook to subscribe to realtime updates of a batch of task runs.
|
||||
*
|
||||
* @template TTask - The type of the task.
|
||||
* @param {string} batchId - The unique identifier of the batch to subscribe to.
|
||||
* @returns {{ runs: TaskRunShape<TTask>[], error: Error | null }} An object containing the current state of the runs and any error encountered.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* import type { myTask } from './path/to/task';
|
||||
* const { runs, error } = useRealtimeBatch<typeof myTask>('batch-id-123');
|
||||
* ```
|
||||
*/
|
||||
export function useRealtimeBatch<TTask extends AnyTask>(batchId: string) {
|
||||
const [runShapes, setRunShapes] = useState<TaskRunShape<TTask>[]>([]);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const apiClient = useApiClient();
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @trigger.dev/sdk
|
||||
|
||||
## 3.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Remove browser export condition - not necessary with the react-hooks package that uses core ([#1455](https://github.com/triggerdotdev/trigger.dev/pull/1455))
|
||||
- Updated dependencies:
|
||||
- `@trigger.dev/core@3.1.1`
|
||||
|
||||
## 3.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@trigger.dev/sdk",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"description": "trigger.dev Node.JS SDK",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
@@ -27,9 +27,6 @@
|
||||
},
|
||||
"sourceDialects": [
|
||||
"@triggerdotdev/source"
|
||||
],
|
||||
"esmDialects": [
|
||||
"browser"
|
||||
]
|
||||
},
|
||||
"typesVersions": {
|
||||
@@ -51,7 +48,7 @@
|
||||
"@opentelemetry/api": "1.9.0",
|
||||
"@opentelemetry/api-logs": "0.52.1",
|
||||
"@opentelemetry/semantic-conventions": "1.25.1",
|
||||
"@trigger.dev/core": "workspace:3.1.0",
|
||||
"@trigger.dev/core": "workspace:3.1.1",
|
||||
"chalk": "^5.2.0",
|
||||
"cronstrue": "^2.21.0",
|
||||
"debug": "^4.3.4",
|
||||
@@ -83,10 +80,6 @@
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"browser": {
|
||||
"types": "./dist/browser/index.d.ts",
|
||||
"default": "./dist/browser/index.js"
|
||||
},
|
||||
"import": {
|
||||
"@triggerdotdev/source": "./src/index.ts",
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
@@ -98,10 +91,6 @@
|
||||
}
|
||||
},
|
||||
"./v3": {
|
||||
"browser": {
|
||||
"types": "./dist/browser/v3/index.d.ts",
|
||||
"default": "./dist/browser/v3/index.js"
|
||||
},
|
||||
"import": {
|
||||
"@triggerdotdev/source": "./src/v3/index.ts",
|
||||
"types": "./dist/esm/v3/index.d.ts",
|
||||
|
||||
Generated
+132
-27
@@ -1012,7 +1012,7 @@ importers:
|
||||
packages/build:
|
||||
dependencies:
|
||||
'@trigger.dev/core':
|
||||
specifier: workspace:3.1.0
|
||||
specifier: workspace:3.1.1
|
||||
version: link:../core
|
||||
pkg-types:
|
||||
specifier: ^1.1.3
|
||||
@@ -1091,10 +1091,10 @@ importers:
|
||||
specifier: 1.25.1
|
||||
version: 1.25.1
|
||||
'@trigger.dev/build':
|
||||
specifier: workspace:3.1.0
|
||||
specifier: workspace:3.1.1
|
||||
version: link:../build
|
||||
'@trigger.dev/core':
|
||||
specifier: workspace:3.1.0
|
||||
specifier: workspace:3.1.1
|
||||
version: link:../core
|
||||
c12:
|
||||
specifier: ^1.11.1
|
||||
@@ -1375,7 +1375,7 @@ importers:
|
||||
packages/react-hooks:
|
||||
dependencies:
|
||||
'@trigger.dev/core':
|
||||
specifier: workspace:^3.1.0
|
||||
specifier: workspace:^3.1.1
|
||||
version: link:../core
|
||||
react:
|
||||
specifier: '>=18 || >=19.0.0-beta'
|
||||
@@ -1424,7 +1424,7 @@ importers:
|
||||
specifier: 1.25.1
|
||||
version: 1.25.1
|
||||
'@trigger.dev/core':
|
||||
specifier: workspace:3.1.0
|
||||
specifier: workspace:3.1.1
|
||||
version: link:../core
|
||||
chalk:
|
||||
specifier: ^5.2.0
|
||||
@@ -1614,30 +1614,11 @@ importers:
|
||||
specifier: ^5
|
||||
version: 5.5.4
|
||||
|
||||
references/prisma-catalog:
|
||||
dependencies:
|
||||
'@prisma/client':
|
||||
specifier: 5.19.0
|
||||
version: 5.19.0(prisma@5.19.0)
|
||||
'@trigger.dev/sdk':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/trigger-sdk
|
||||
devDependencies:
|
||||
'@trigger.dev/build':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/build
|
||||
prisma:
|
||||
specifier: 5.19.0
|
||||
version: 5.19.0
|
||||
trigger.dev:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/cli-v3
|
||||
typescript:
|
||||
specifier: ^5.5.4
|
||||
version: 5.5.4
|
||||
|
||||
references/v3-catalog:
|
||||
dependencies:
|
||||
'@effect/schema':
|
||||
specifier: ^0.75.5
|
||||
version: 0.75.5(effect@3.9.2)
|
||||
'@infisical/sdk':
|
||||
specifier: ^2.1.9
|
||||
version: 2.3.5
|
||||
@@ -1656,6 +1637,9 @@ importers:
|
||||
'@sentry/esbuild-plugin':
|
||||
specifier: ^2.22.2
|
||||
version: 2.22.2
|
||||
'@sinclair/typebox':
|
||||
specifier: ^0.33.17
|
||||
version: 0.33.17
|
||||
'@sindresorhus/slugify':
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1
|
||||
@@ -1671,9 +1655,15 @@ importers:
|
||||
'@trigger.dev/sdk':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/trigger-sdk
|
||||
'@typeschema/typebox':
|
||||
specifier: ^0.14.0
|
||||
version: 0.14.0(@sinclair/typebox@0.33.17)
|
||||
ai:
|
||||
specifier: ^3.3.24
|
||||
version: 3.3.24(openai@4.56.0)(react@19.0.0-rc.0)(svelte@4.2.19)(vue@3.4.38)(zod@3.22.3)
|
||||
arktype:
|
||||
specifier: 2.0.0-rc.17
|
||||
version: 2.0.0-rc.17
|
||||
dotenv:
|
||||
specifier: ^16.4.5
|
||||
version: 16.4.5
|
||||
@@ -1713,21 +1703,33 @@ importers:
|
||||
reflect-metadata:
|
||||
specifier: ^0.1.13
|
||||
version: 0.1.14
|
||||
runtypes:
|
||||
specifier: ^6.7.0
|
||||
version: 6.7.0
|
||||
server-only:
|
||||
specifier: ^0.0.1
|
||||
version: 0.0.1
|
||||
stripe:
|
||||
specifier: ^12.14.0
|
||||
version: 12.18.0
|
||||
superstruct:
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2
|
||||
typeorm:
|
||||
specifier: ^0.3.20
|
||||
version: 0.3.20(pg@8.11.5)(ts-node@10.9.2)
|
||||
valibot:
|
||||
specifier: ^0.42.1
|
||||
version: 0.42.1(typescript@5.5.4)
|
||||
wrangler:
|
||||
specifier: 3.70.0
|
||||
version: 3.70.0
|
||||
yt-dlp-wrap:
|
||||
specifier: ^2.3.12
|
||||
version: 2.3.12
|
||||
yup:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
zod:
|
||||
specifier: 3.22.3
|
||||
version: 3.22.3
|
||||
@@ -2010,6 +2012,16 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@ark/schema@0.19.0:
|
||||
resolution: {integrity: sha512-m+NLBrfewxH1IieXxK7i2cjJtMksZm2PlobbZDcsNDpYKjuDAzWimXZb+GaxawKMBV9rhO3XY2Lnvf2TLq+JTQ==}
|
||||
dependencies:
|
||||
'@ark/util': 0.18.0
|
||||
dev: false
|
||||
|
||||
/@ark/util@0.18.0:
|
||||
resolution: {integrity: sha512-TpHY532LKQwwYHui5NN/eO/6eSiSMvf652YNt1BsV7fya7RzXL27IiU9x4bm7jTFZxLQGYDQTB7nw41TqeuF4g==}
|
||||
dev: false
|
||||
|
||||
/@aws-crypto/crc32@3.0.0:
|
||||
resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==}
|
||||
dependencies:
|
||||
@@ -4773,6 +4785,15 @@ packages:
|
||||
fast-check: 3.22.0
|
||||
dev: false
|
||||
|
||||
/@effect/schema@0.75.5(effect@3.9.2):
|
||||
resolution: {integrity: sha512-TQInulTVCuF+9EIbJpyLP6dvxbQJMphrnRqgexm/Ze39rSjfhJuufF7XvU3SxTgg3HnL7B/kpORTJbHhlE6thw==}
|
||||
peerDependencies:
|
||||
effect: ^3.9.2
|
||||
dependencies:
|
||||
effect: 3.9.2
|
||||
fast-check: 3.22.0
|
||||
dev: false
|
||||
|
||||
/@electric-sql/client@0.4.0:
|
||||
resolution: {integrity: sha512-YVYSqHitqVIDC1RBTfmHMfAfqDNAKMK9/AFVTDFQQxN3Q85dIQS49zThAuJVecYiuYRJvTiqf40c4n39jZSNrQ==}
|
||||
optionalDependencies:
|
||||
@@ -13897,6 +13918,10 @@ packages:
|
||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||
dev: true
|
||||
|
||||
/@sinclair/typebox@0.33.17:
|
||||
resolution: {integrity: sha512-75232GRx3wp3P7NP+yc4nRK3XUAnaQShxTAzapgmQrgs0QvSq0/mOJGoZXRpH15cFCKyys+4laCPbBselqJ5Ag==}
|
||||
dev: false
|
||||
|
||||
/@sindresorhus/is@0.14.0:
|
||||
resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -15440,6 +15465,29 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@typeschema/core@0.14.0:
|
||||
resolution: {integrity: sha512-Ia6PtZHcL3KqsAWXjMi5xIyZ7XMH4aSnOQes8mfMLx+wGFGtGRNlwe6Y7cYvX+WfNK67OL0/HSe9t8QDygV0/w==}
|
||||
peerDependencies:
|
||||
'@types/json-schema': ^7.0.15
|
||||
peerDependenciesMeta:
|
||||
'@types/json-schema':
|
||||
optional: true
|
||||
dev: false
|
||||
|
||||
/@typeschema/typebox@0.14.0(@sinclair/typebox@0.33.17):
|
||||
resolution: {integrity: sha512-+Td4CHkWQ17T60gEtA2SzeFp382CHEwsI7aWiqBq9YeqAwbkTrluGh6R9MNFHJzOLaYL+AG60b8fX9Rbcex0Tg==}
|
||||
peerDependencies:
|
||||
'@sinclair/typebox': ^0.33.7
|
||||
peerDependenciesMeta:
|
||||
'@sinclair/typebox':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@sinclair/typebox': 0.33.17
|
||||
'@typeschema/core': 0.14.0
|
||||
transitivePeerDependencies:
|
||||
- '@types/json-schema'
|
||||
dev: false
|
||||
|
||||
/@typescript-eslint/eslint-plugin@5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.31.0)(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
@@ -16525,6 +16573,13 @@ packages:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
|
||||
/arktype@2.0.0-rc.17:
|
||||
resolution: {integrity: sha512-1m1VG9ZGcGx8OIbeA4ghw8n1QVpu7MYcel3My2Tob17mMBaLy6+M116RRwx9GvaCyGpHhgu1RK5XfhP4wX17ug==}
|
||||
dependencies:
|
||||
'@ark/schema': 0.19.0
|
||||
'@ark/util': 0.18.0
|
||||
dev: false
|
||||
|
||||
/array-buffer-byte-length@1.0.1:
|
||||
resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -18815,6 +18870,10 @@ packages:
|
||||
resolution: {integrity: sha512-pV7l1+LSZFvVObj4zuy4nYiBaC7qZOfrKV6s/Ef4p3KueiQwZFgamazklwyZ+x7Nyj2etRDFvHE/xkThTfQD1w==}
|
||||
dev: false
|
||||
|
||||
/effect@3.9.2:
|
||||
resolution: {integrity: sha512-1sx/v1HTWHTodXfzWxAFg+SCF+ACgpJVruaAMIh/NmDVvrUsf0x9PzpXvkgJUbQ1fMdmKYK//FqxeHSQ+Zxv/Q==}
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium@1.4.433:
|
||||
resolution: {integrity: sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ==}
|
||||
dev: true
|
||||
@@ -25496,6 +25555,10 @@ packages:
|
||||
mkdirp: 1.0.4
|
||||
dev: true
|
||||
|
||||
/property-expr@2.0.6:
|
||||
resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==}
|
||||
dev: false
|
||||
|
||||
/property-information@6.2.0:
|
||||
resolution: {integrity: sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==}
|
||||
dev: true
|
||||
@@ -26953,6 +27016,10 @@ packages:
|
||||
dependencies:
|
||||
queue-microtask: 1.2.3
|
||||
|
||||
/runtypes@6.7.0:
|
||||
resolution: {integrity: sha512-3TLdfFX8YHNFOhwHrSJza6uxVBmBrEjnNQlNXvXCdItS0Pdskfg5vVXUTWIN+Y23QR09jWpSl99UHkA83m4uWA==}
|
||||
dev: false
|
||||
|
||||
/rusha@0.8.14:
|
||||
resolution: {integrity: sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==}
|
||||
dev: false
|
||||
@@ -27945,6 +28012,11 @@ packages:
|
||||
copy-anything: 3.0.5
|
||||
dev: false
|
||||
|
||||
/superstruct@2.0.2:
|
||||
resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dev: false
|
||||
|
||||
/supertest@7.0.0:
|
||||
resolution: {integrity: sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==}
|
||||
engines: {node: '>=14.18.0'}
|
||||
@@ -28444,6 +28516,10 @@ packages:
|
||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
dev: false
|
||||
|
||||
/tiny-case@1.0.3:
|
||||
resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==}
|
||||
dev: false
|
||||
|
||||
/tiny-glob@0.2.9:
|
||||
resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}
|
||||
dependencies:
|
||||
@@ -28574,6 +28650,10 @@ packages:
|
||||
resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==}
|
||||
dev: true
|
||||
|
||||
/toposort@2.0.2:
|
||||
resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==}
|
||||
dev: false
|
||||
|
||||
/tough-cookie@2.5.0:
|
||||
resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}
|
||||
engines: {node: '>=0.8'}
|
||||
@@ -28993,6 +29073,11 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/type-fest@2.19.0:
|
||||
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
|
||||
engines: {node: '>=12.20'}
|
||||
dev: false
|
||||
|
||||
/type-fest@4.10.3:
|
||||
resolution: {integrity: sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -29605,6 +29690,17 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/valibot@0.42.1(typescript@5.5.4):
|
||||
resolution: {integrity: sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==}
|
||||
peerDependencies:
|
||||
typescript: '>=5'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
typescript: 5.5.4
|
||||
dev: false
|
||||
|
||||
/validate-npm-package-license@3.0.4:
|
||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||
dependencies:
|
||||
@@ -30832,6 +30928,15 @@ packages:
|
||||
resolution: {integrity: sha512-P8fJ+6M1YjukyJENCTviNLiZ8mokxprR54ho3DsSKPWDcac489OjRiStGEARJr6un6ETS6goTn4CWl/b/rM3aA==}
|
||||
dev: false
|
||||
|
||||
/yup@1.4.0:
|
||||
resolution: {integrity: sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==}
|
||||
dependencies:
|
||||
property-expr: 2.0.6
|
||||
tiny-case: 1.0.3
|
||||
toposort: 2.0.2
|
||||
type-fest: 2.19.0
|
||||
dev: false
|
||||
|
||||
/zip-stream@4.1.1:
|
||||
resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { TriggerAuthContext, useBatch } from "@trigger.dev/react-hooks";
|
||||
import { TriggerAuthContext, useRealtimeBatch } from "@trigger.dev/react-hooks";
|
||||
import type { exampleTask } from "@/trigger/example";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -46,76 +46,78 @@ const ProgressBar = ({ run }: { run: AnyRunShape }) => {
|
||||
const StatusBadge = ({ run }: { run: AnyRunShape }) => {
|
||||
switch (run.status) {
|
||||
case "WAITING_FOR_DEPLOY": {
|
||||
return <Badge className={`bg-purple-100 text-purple-800 font-semibold`}>{run.status}</Badge>;
|
||||
return <Badge className={`bg-purple-800 text-purple-100 font-semibold`}>{run.status}</Badge>;
|
||||
}
|
||||
case "DELAYED": {
|
||||
return <Badge className={`bg-yellow-100 text-yellow-800 font-semibold`}>{run.status}</Badge>;
|
||||
return <Badge className={`bg-yellow-800 text-yellow-100 font-semibold`}>{run.status}</Badge>;
|
||||
}
|
||||
case "EXPIRED": {
|
||||
return <Badge className={`bg-red-100 text-red-800 font-semibold`}>{run.status}</Badge>;
|
||||
return <Badge className={`bg-red-800 text-red-100 font-semibold`}>{run.status}</Badge>;
|
||||
}
|
||||
case "QUEUED": {
|
||||
return <Badge className={`bg-yellow-100 text-yellow-800 font-semibold`}>{run.status}</Badge>;
|
||||
return <Badge className={`bg-yellow-800 text-yellow-100 font-semibold`}>{run.status}</Badge>;
|
||||
}
|
||||
case "FROZEN":
|
||||
case "REATTEMPTING":
|
||||
case "EXECUTING": {
|
||||
return <Badge className={`bg-blue-100 text-blue-800 font-semibold`}>{run.status}</Badge>;
|
||||
return <Badge className={`bg-blue-800 text-blue-100 font-semibold`}>{run.status}</Badge>;
|
||||
}
|
||||
case "COMPLETED": {
|
||||
return <Badge className={`bg-green-100 text-green-800 font-semibold`}>{run.status}</Badge>;
|
||||
return <Badge className={`bg-green-800 text-green-100 font-semibold`}>{run.status}</Badge>;
|
||||
}
|
||||
case "TIMED_OUT":
|
||||
case "SYSTEM_FAILURE":
|
||||
case "INTERRUPTED":
|
||||
case "CRASHED":
|
||||
case "FAILED": {
|
||||
return <Badge className={`bg-red-100 text-red-800 font-semibold`}>{run.status}</Badge>;
|
||||
return <Badge className={`bg-red-800 text-red-100 font-semibold`}>{run.status}</Badge>;
|
||||
}
|
||||
case "CANCELED": {
|
||||
return <Badge className={`bg-gray-100 text-gray-800 font-semibold`}>{run.status}</Badge>;
|
||||
return <Badge className={`bg-gray-800 text-gray-100 font-semibold`}>{run.status}</Badge>;
|
||||
}
|
||||
default: {
|
||||
return <Badge className={`bg-gray-100 text-gray-800 font-semibold`}>{run.status}</Badge>;
|
||||
return <Badge className={`bg-gray-800 text-gray-100 font-semibold`}>{run.status}</Badge>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function BackgroundRunsTable({ runs }: { runs: TaskRunShape<typeof exampleTask>[] }) {
|
||||
return (
|
||||
<Table>
|
||||
<TableCaption>A list of your recent background runs.</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[150px]">Run ID / Task</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Payload ID</TableHead>
|
||||
<TableHead className="w-[200px]">Progress</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{runs.map((run) => (
|
||||
<TableRow key={run.id}>
|
||||
<TableCell>
|
||||
<div className="font-medium">{run.id}</div>
|
||||
<div className="text-sm text-gray-500">{run.taskIdentifier}</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StatusBadge run={run} />
|
||||
</TableCell>
|
||||
<TableCell>{run.payload.id}</TableCell>
|
||||
<TableCell>
|
||||
<ProgressBar run={run} />
|
||||
</TableCell>
|
||||
<div className="max-w-6xl mx-auto mt-8">
|
||||
<h1 className="text-gray-200 text-2xl font-semibold mb-8">Recent Background Runs</h1>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-b border-gray-700">
|
||||
<TableHead className="w-[150px] text-gray-200 text-base">Run ID / Task</TableHead>
|
||||
<TableHead className="text-gray-200 text-base">Status</TableHead>
|
||||
<TableHead className="text-gray-200 text-base">Payload ID</TableHead>
|
||||
<TableHead className="w-[200px] text-gray-200 text-base">Progress</TableHead>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{runs.map((run) => (
|
||||
<TableRow key={run.id} className="border-b border-gray-700 hover:bg-gray-800">
|
||||
<TableCell>
|
||||
<div className="font-medium">{run.id}</div>
|
||||
<div className="text-sm text-gray-500">{run.taskIdentifier}</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StatusBadge run={run} />
|
||||
</TableCell>
|
||||
<TableCell>{run.payload.id}</TableCell>
|
||||
<TableCell>
|
||||
<ProgressBar run={run} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BatchRunTableWrapper({ batchId }: { batchId: string }) {
|
||||
const { runs, error } = useBatch<typeof exampleTask>(batchId);
|
||||
const { runs, error } = useRealtimeBatch<typeof exampleTask>(batchId);
|
||||
|
||||
console.log(runs);
|
||||
|
||||
@@ -132,7 +134,7 @@ function BatchRunTableWrapper({ batchId }: { batchId: string }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full min-h-screen bg-gray-100 p-4 space-y-6">
|
||||
<div className="w-full min-h-screen bg-gray-900 text-gray-200 p-4 space-y-6">
|
||||
<BackgroundRunsTable runs={runs} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ export default async function DetailsPage({ params }: { params: { id: string } }
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen items-center justify-center p-4 bg-gray-100">
|
||||
<main className="flex min-h-screen items-center justify-center p-4 bg-gray-900">
|
||||
<ClientBatchRunDetails batchId={params.id} jwt={jwt.value} />
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
|
||||
<body className={`${geistSans.variable} ${geistMono.variable} antialiased bg-gray-900`}>
|
||||
<NextSSRPlugin
|
||||
/**
|
||||
* The `extractRouterConfig` will extract **only** the route configs
|
||||
|
||||
@@ -4,11 +4,16 @@ import { ImageUploadDropzone } from "@/components/ImageUploadButton";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="flex min-h-screen items-center justify-center">
|
||||
<div className="flex flex-col space-y-4">
|
||||
<main className="grid grid-rows-[1fr_auto] min-h-screen items-center justify-center w-full bg-gray-900">
|
||||
<div className="flex flex-col space-y-8">
|
||||
<h1 className="text-gray-200 text-4xl max-w-xl text-center font-bold">
|
||||
Trigger.dev Realtime + UploadThing + fal.ai
|
||||
</h1>
|
||||
<ImageUploadDropzone />
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 justify-center w-full">
|
||||
<RunButton />
|
||||
<BatchRunButton />
|
||||
<ImageUploadDropzone />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -10,8 +10,8 @@ function RunDetailsWrapper({ runId }: { runId: string }) {
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="w-full min-h-screen bg-gray-100 p-4">
|
||||
<Card className="w-full bg-white shadow-md">
|
||||
<div className="w-full min-h-screen bg-gray-900 p-4">
|
||||
<Card className="w-full bg-gray-800 shadow-md">
|
||||
<CardContent className="pt-6">
|
||||
<p className="text-red-600">Error: {error.message}</p>
|
||||
</CardContent>
|
||||
@@ -22,10 +22,10 @@ function RunDetailsWrapper({ runId }: { runId: string }) {
|
||||
|
||||
if (!run) {
|
||||
return (
|
||||
<div className="w-full min-h-screen bg-gray-100 p-4">
|
||||
<Card className="w-full bg-white shadow-md">
|
||||
<div className="w-full min-h-screen bg-gray-900 py-4 px-6 grid place-items-center">
|
||||
<Card className="w-fit bg-gray-800 border border-gray-700 shadow-md">
|
||||
<CardContent className="pt-6">
|
||||
<p>Loading run details...</p>
|
||||
<p className="text-gray-200">Loading run details…</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -33,7 +33,7 @@ function RunDetailsWrapper({ runId }: { runId: string }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full min-h-screen bg-gray-100 p-4 space-y-6">
|
||||
<div className="w-full min-h-screen bg-gray-900 text-gray-200 p-4 space-y-6">
|
||||
<RunDetails record={run} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ export default async function DetailsPage({ params }: { params: { id: string } }
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen items-center justify-center p-4 bg-gray-100">
|
||||
<main className="flex min-h-screen items-center justify-center p-4 bg-gray-900">
|
||||
<ClientRunDetails runId={params.id} jwt={jwt.value} />
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -11,8 +11,8 @@ function UploadDetailsWrapper({ fileId }: { fileId: string }) {
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="w-full min-h-screen bg-gray-100 p-4">
|
||||
<Card className="w-full bg-white shadow-md">
|
||||
<div className="w-full min-h-screen bg-gray-900 p-4">
|
||||
<Card className="w-full bg-gray-800 shadow-md">
|
||||
<CardContent className="pt-6">
|
||||
<p className="text-red-600">Error: {error.message}</p>
|
||||
</CardContent>
|
||||
@@ -23,10 +23,10 @@ function UploadDetailsWrapper({ fileId }: { fileId: string }) {
|
||||
|
||||
if (!run) {
|
||||
return (
|
||||
<div className="w-full min-h-screen bg-gray-100 p-4">
|
||||
<Card className="w-full bg-white shadow-md">
|
||||
<div className="w-full min-h-screen bg-gray-900 py-4 px-8 grid place-items-center">
|
||||
<Card className="w-fit bg-gray-800 border border-gray-700 shadow-md">
|
||||
<CardContent className="pt-6">
|
||||
<p>Loading run details...</p>
|
||||
<p className="text-gray-200">Loading run details…</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -45,7 +45,7 @@ function UploadDetailsWrapper({ fileId }: { fileId: string }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full min-h-screen bg-gray-100 p-4 space-y-6">
|
||||
<div className="w-full min-h-screen bg-gray-900 text-gray-200 p-4 space-y-6">
|
||||
<ImageDisplay
|
||||
uploadedImage={run.payload.appUrl}
|
||||
uploadedCaption={run.payload.name}
|
||||
|
||||
@@ -15,7 +15,7 @@ export default async function UploadPage({
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen items-center justify-center p-4 bg-gray-100">
|
||||
<main className="flex min-h-screen items-center justify-center p-4 bg-gray-900">
|
||||
<ClientUploadDetails fileId={params.id} publicAccessToken={publicAccessToken} />
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,11 @@ function SubmitButton() {
|
||||
const { pending } = useFormStatus();
|
||||
|
||||
return (
|
||||
<Button type="submit" disabled={pending}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
className="p-0 bg-transparent hover:bg-transparent hover:text-gray-200 text-gray-400"
|
||||
>
|
||||
{pending ? "Running..." : "Run Batch Task"}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { AnyRunShape, TaskRunShape } from "@trigger.dev/sdk/v3";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { ChevronLeft, ExternalLink } from "lucide-react";
|
||||
import type { handleUpload } from "@/trigger/images";
|
||||
|
||||
interface HandleUploadFooterProps {
|
||||
@@ -26,27 +26,36 @@ export function HandleUploadFooter({ run, viewRunUrl }: HandleUploadFooterProps)
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0 bg-background border-t border-border p-4 shadow-lg">
|
||||
<div className="container mx-auto flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="text-sm font-medium">Run ID: {run.id}</span>
|
||||
<span className="text-sm">Processing {run.payload.name}</span>
|
||||
<Badge variant="secondary" className={`${getStatusColor(run.status)} text-white`}>
|
||||
{run.status}
|
||||
</Badge>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a
|
||||
href={viewRunUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center"
|
||||
>
|
||||
View Run
|
||||
<ExternalLink className="ml-2 h-4 w-4" />
|
||||
</a>
|
||||
</Button>
|
||||
<div className="fixed flex items-center justify-between bottom-0 left-0 right-0 bg-gray-800 border-t border-gray-700 p-4">
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a
|
||||
href="/"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center bg-green-700 text-white px-2 py-1 rounded-md border-transparent hover:bg-green-600 hover:text-white"
|
||||
>
|
||||
<ChevronLeft className="mr-1 size-4" />
|
||||
Upload another image
|
||||
</a>
|
||||
</Button>
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="text-sm font-medium">Run ID: {run.id}</span>
|
||||
<span className="text-gray-400">|</span>
|
||||
<span className="text-sm">Processing {run.payload.name}</span>
|
||||
<Badge variant="secondary" className={`${getStatusColor(run.status)} text-gray-200`}>
|
||||
{run.status}
|
||||
</Badge>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a
|
||||
href={viewRunUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center bg-green-700 text-white px-2 py-1 rounded-md border-transparent hover:bg-green-600 hover:text-white"
|
||||
>
|
||||
View Run
|
||||
<ExternalLink className="ml-2 size-4" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ export function ImageUploadDropzone() {
|
||||
// Do something with the error.
|
||||
console.error(`ERROR! ${error.message}`);
|
||||
}}
|
||||
className="border-gray-600"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,11 @@ function SubmitButton() {
|
||||
const { pending } = useFormStatus();
|
||||
|
||||
return (
|
||||
<Button type="submit" disabled={pending}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
className="p-0 bg-transparent hover:bg-transparent hover:text-gray-200 text-gray-400"
|
||||
>
|
||||
{pending ? "Running..." : "Run Task"}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import type { RetrieveRunResult } from "@trigger.dev/sdk/v3";
|
||||
import { exampleTask } from "@/trigger/example";
|
||||
import type { RetrieveRunResult } from "@trigger.dev/sdk/v3";
|
||||
import { AlertTriangleIcon, CheckCheckIcon, XIcon } from "lucide-react";
|
||||
|
||||
function formatDate(date: Date | undefined) {
|
||||
return date ? new Date(date).toLocaleString() : "N/A";
|
||||
@@ -10,7 +11,7 @@ function formatDate(date: Date | undefined) {
|
||||
|
||||
function JsonDisplay({ data }: { data: any }) {
|
||||
return (
|
||||
<ScrollArea className="h-[200px] w-full rounded-md border p-4">
|
||||
<ScrollArea className="h-[200px] w-full rounded-md border p-4 bg-gray-900 border-gray-700">
|
||||
<pre className="text-sm">{JSON.stringify(data, null, 2)}</pre>
|
||||
</ScrollArea>
|
||||
);
|
||||
@@ -18,7 +19,7 @@ function JsonDisplay({ data }: { data: any }) {
|
||||
|
||||
export default function RunDetails({ record }: { record: RetrieveRunResult<typeof exampleTask> }) {
|
||||
return (
|
||||
<Card className="w-full max-w-screen-xl mx-auto">
|
||||
<Card className="w-full max-w-4xl mx-auto bg-gray-800 border-gray-700 text-gray-200">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl font-bold">Run Details</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -40,9 +41,16 @@ export default function RunDetails({ record }: { record: RetrieveRunResult<typeo
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold mb-1">Is Test</h3>
|
||||
<Badge variant={record.isTest ? "default" : "outline"}>
|
||||
{record.isTest ? "Yes" : "No"}
|
||||
</Badge>
|
||||
{record.isTest ? (
|
||||
<span className="text-gray-200 flex items-center gap-1 text-sm">
|
||||
<CheckCheckIcon className="size-4 text-green-500" />
|
||||
Yes
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-gray-200 flex items-center gap-1 text-sm">
|
||||
<XIcon className="size-4" /> No
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{record.idempotencyKey && (
|
||||
<div>
|
||||
@@ -121,14 +129,16 @@ export default function RunDetails({ record }: { record: RetrieveRunResult<typeo
|
||||
|
||||
{record.error && (
|
||||
<div>
|
||||
<h3 className="font-semibold mb-1">Error</h3>
|
||||
<Card className="bg-red-50">
|
||||
<h3 className="font-semibold mb-1 flex items-center gap-1 text-rose-500">
|
||||
<AlertTriangleIcon className="size-5" /> Error
|
||||
</h3>
|
||||
<Card className="bg-gray-900 border-rose-500">
|
||||
<CardContent className="pt-6">
|
||||
<p className="font-semibold text-red-600">{record.error.name}</p>
|
||||
<p className="text-sm text-red-700">{record.error.message}</p>
|
||||
<p className="font-semibold text-rose-500">{record.error.name}</p>
|
||||
<p className="text-sm text-rose-500">{record.error.message}</p>
|
||||
{record.error.stackTrace && (
|
||||
<ScrollArea className="h-[100px] w-full mt-2">
|
||||
<pre className="text-xs text-red-800">{record.error.stackTrace}</pre>
|
||||
<pre className="text-xs text-rose-800">{record.error.stackTrace}</pre>
|
||||
</ScrollArea>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { LoaderPinwheel } from "lucide-react";
|
||||
import { LoaderCircleIcon, LoaderPinwheel } from "lucide-react";
|
||||
|
||||
type PendingGridImage = {
|
||||
status: "pending";
|
||||
@@ -35,8 +35,9 @@ export default function ImageDisplay({
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
{/* Main uploaded image */}
|
||||
<div className="mb-12">
|
||||
<div className="relative w-full max-w-3xl mx-auto aspect-video">
|
||||
<div className="mb-12 max-w-3xl mx-auto">
|
||||
<p className="text-base text-gray-400 mb-2">Original</p>
|
||||
<div className="relative w-full aspect-video border border-gray-700 rounded-lg">
|
||||
<Image
|
||||
src={uploadedImage}
|
||||
alt={uploadedCaption}
|
||||
@@ -53,27 +54,32 @@ export default function ImageDisplay({
|
||||
{/* Grid of smaller images */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 gap-6">
|
||||
{gridImages.map((image, index) => (
|
||||
<Card key={index} className="overflow-hidden">
|
||||
<CardContent className="p-0">
|
||||
<div
|
||||
className={`relative aspect-square ${
|
||||
image.status === "pending" ? "bg-gray-100" : ""
|
||||
}`}
|
||||
>
|
||||
{image.status === "completed" ? (
|
||||
<Image src={image.src} alt={image.caption} fill className="object-cover" />
|
||||
) : (
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center p-4">
|
||||
<LoaderPinwheel className="w-8 h-8 animate-spin text-primary mb-2" />
|
||||
<p className="text-xs text-center text-primary">{image.message}</p>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<p className="text-base text-gray-400 mb-2">Style {index + 1}</p>
|
||||
<Card key={index} className="overflow-hidden border border-gray-700 rounded-lg">
|
||||
<CardContent className="p-0">
|
||||
<div
|
||||
className={`relative aspect-video h-full ${
|
||||
image.status === "pending" ? "bg-gray-800" : ""
|
||||
}`}
|
||||
>
|
||||
{image.status === "completed" ? (
|
||||
<Image src={image.src} alt={image.caption} fill className="object-cover" />
|
||||
) : (
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center p-4">
|
||||
<LoaderCircleIcon className="size-8 animate-spin text-blue-500 mb-4" />
|
||||
<p className="text-sm text-white text-center">Model: {image.message}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{image.status === "completed" && (
|
||||
<p className="p-2 text-center text-xs text-gray-400 bg-gray-800">
|
||||
{image.caption}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{image.status === "completed" && (
|
||||
<p className="p-2 text-center text-xs text-primary">{image.caption}</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,12 +17,9 @@ export const handleUpload = schemaTask({
|
||||
const results = await runFalModel.batchTriggerAndWait([
|
||||
{
|
||||
payload: {
|
||||
model: "fal-ai/image-preprocessors/canny",
|
||||
model: "fal-ai/image-preprocessors/lineart",
|
||||
url: file.url,
|
||||
input: {
|
||||
low_threshold: 100,
|
||||
high_threshold: 200,
|
||||
},
|
||||
input: {},
|
||||
},
|
||||
options: {
|
||||
tags: ctx.run.tags,
|
||||
@@ -30,9 +27,16 @@ export const handleUpload = schemaTask({
|
||||
},
|
||||
{
|
||||
payload: {
|
||||
model: "fal-ai/aura-sr",
|
||||
model: "fal-ai/omni-zero",
|
||||
url: file.url,
|
||||
input: {},
|
||||
input: {
|
||||
prompt: "Turn the image into a cartoon",
|
||||
image_url: file.url,
|
||||
composition_image_url: file.url,
|
||||
style_image_url:
|
||||
"https://storage.googleapis.com/falserverless/model_tests/omni_zero/style.jpg",
|
||||
identity_image_url: file.url,
|
||||
},
|
||||
},
|
||||
options: { tags: ctx.run.tags },
|
||||
},
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "references-prisma-catalog",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"trigger.dev": "workspace:*",
|
||||
"@trigger.dev/build": "workspace:*",
|
||||
"typescript": "^5.5.4",
|
||||
"prisma": "5.19.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/sdk": "workspace:*",
|
||||
"@prisma/client": "5.19.0"
|
||||
},
|
||||
"scripts": {
|
||||
"generate:prisma": "prisma generate --sql"
|
||||
}
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Post" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"authorId" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Post_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Post" ADD CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -1,3 +0,0 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
@@ -1,26 +0,0 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
previewFeatures = ["typedSql"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
directUrl = env("DIRECT_DATABASE_URL")
|
||||
}
|
||||
|
||||
// user.prisma
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
posts Post[]
|
||||
}
|
||||
|
||||
// post.prisma
|
||||
model Post {
|
||||
id Int @id @default(autoincrement())
|
||||
title String
|
||||
content String
|
||||
authorId Int
|
||||
author User @relation(fields: [authorId], references: [id])
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
SELECT
|
||||
u.id,
|
||||
u.name,
|
||||
COUNT(p.id) as "postCount"
|
||||
FROM
|
||||
"User" u
|
||||
LEFT JOIN "Post" p ON u.id = p."authorId"
|
||||
GROUP BY
|
||||
u.id,
|
||||
u.name;
|
||||
@@ -1,6 +0,0 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { getUsersWithPosts } from "@prisma/client/sql";
|
||||
|
||||
export const prisma = new PrismaClient();
|
||||
|
||||
export { getUsersWithPosts };
|
||||
@@ -1,21 +0,0 @@
|
||||
import { getUsersWithPosts, prisma } from "../db.js";
|
||||
import { logger, task } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const prismaTask = task({
|
||||
id: "prisma-task",
|
||||
run: async () => {
|
||||
const users = await prisma.user.findMany();
|
||||
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
name: "Alice",
|
||||
},
|
||||
});
|
||||
|
||||
const usersWithPosts = await prisma.$queryRawTyped(getUsersWithPosts());
|
||||
|
||||
logger.info("Users with posts", { usersWithPosts });
|
||||
|
||||
return users;
|
||||
},
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
import { prismaExtension } from "@trigger.dev/build/extensions/prisma";
|
||||
import { defineConfig } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export default defineConfig({
|
||||
runtime: "node",
|
||||
project: "proj_mpzmrzygzbvmfjnnpcsk",
|
||||
retries: {
|
||||
enabledInDev: false,
|
||||
default: {
|
||||
maxAttempts: 3,
|
||||
minTimeoutInMs: 5_000,
|
||||
maxTimeoutInMs: 30_000,
|
||||
factor: 2,
|
||||
randomize: true,
|
||||
},
|
||||
},
|
||||
build: {
|
||||
extensions: [
|
||||
prismaExtension({
|
||||
schema: "prisma/schema.prisma",
|
||||
directUrlEnvVarName: "DIRECT_DATABASE_URL",
|
||||
typedSql: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2023",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"customConditions": ["@triggerdotdev/source"],
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "DOM.Iterable"],
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["./src/**/*.ts", "trigger.config.ts"]
|
||||
}
|
||||
@@ -16,18 +16,22 @@
|
||||
"generate:prisma": "prisma generate --sql"
|
||||
},
|
||||
"dependencies": {
|
||||
"@effect/schema": "^0.75.5",
|
||||
"@infisical/sdk": "^2.1.9",
|
||||
"@opentelemetry/api": "1.4.1",
|
||||
"@prisma/client": "5.19.0",
|
||||
"@react-email/components": "0.0.24",
|
||||
"@react-email/render": "1.0.1",
|
||||
"@sentry/esbuild-plugin": "^2.22.2",
|
||||
"@sinclair/typebox": "^0.33.17",
|
||||
"@sindresorhus/slugify": "^2.2.1",
|
||||
"@t3-oss/env-core": "^0.11.0",
|
||||
"@t3-oss/env-nextjs": "^0.10.1",
|
||||
"@traceloop/instrumentation-openai": "^0.10.0",
|
||||
"@trigger.dev/sdk": "workspace:*",
|
||||
"@typeschema/typebox": "^0.14.0",
|
||||
"ai": "^3.3.24",
|
||||
"arktype": "2.0.0-rc.17",
|
||||
"dotenv": "^16.4.5",
|
||||
"email-reply-parser": "^1.8.0",
|
||||
"execa": "^8.0.1",
|
||||
@@ -41,11 +45,15 @@
|
||||
"react": "19.0.0-rc.0",
|
||||
"react-email": "^3.0.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"runtypes": "^6.7.0",
|
||||
"server-only": "^0.0.1",
|
||||
"stripe": "^12.14.0",
|
||||
"superstruct": "^2.0.2",
|
||||
"typeorm": "^0.3.20",
|
||||
"valibot": "^0.42.1",
|
||||
"wrangler": "3.70.0",
|
||||
"yt-dlp-wrap": "^2.3.12",
|
||||
"yup": "^1.4.0",
|
||||
"zod": "3.22.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { auth, runs, tasks } from "@trigger.dev/sdk/v3";
|
||||
import type { task1, task2 } from "./trigger/taskTypes.js";
|
||||
import type { task1, zodTask } from "./trigger/taskTypes.js";
|
||||
import { randomUUID } from "crypto";
|
||||
|
||||
async function main() {
|
||||
@@ -22,8 +22,18 @@ async function main() {
|
||||
|
||||
console.log("Auto JWT", anyHandle.publicAccessToken);
|
||||
|
||||
const publicToken = await auth.createPublicToken({
|
||||
scopes: {
|
||||
read: {
|
||||
runs: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await auth.withAuth({ accessToken: anyHandle.publicAccessToken }, async () => {
|
||||
const subscription = runs.subscribeToRunsWithTag<typeof task1 | typeof task2>(`user:${userId}`);
|
||||
const subscription = runs.subscribeToRunsWithTag<typeof task1 | typeof zodTask>(
|
||||
`user:${userId}`
|
||||
);
|
||||
|
||||
for await (const run of subscription) {
|
||||
switch (run.taskIdentifier) {
|
||||
@@ -33,7 +43,7 @@ async function main() {
|
||||
console.log("Payload:", run.payload);
|
||||
break;
|
||||
}
|
||||
case "types/task-2": {
|
||||
case "types/zod": {
|
||||
console.log("Run update:", run);
|
||||
console.log("Output:", run.output);
|
||||
console.log("Payload:", run.payload);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { task, schemaTask } from "@trigger.dev/sdk/v3";
|
||||
import { task, schemaTask, type TaskPayload } from "@trigger.dev/sdk/v3";
|
||||
import { z } from "zod";
|
||||
|
||||
export const task1 = task({
|
||||
@@ -8,16 +8,143 @@ export const task1 = task({
|
||||
},
|
||||
});
|
||||
|
||||
const Task2Payload = z.object({
|
||||
bar: z.string(),
|
||||
});
|
||||
|
||||
export const task2 = schemaTask({
|
||||
id: "types/task-2",
|
||||
schema: Task2Payload,
|
||||
run: async (payload, { ctx }) => {
|
||||
console.log(ctx.run.idempotencyKey);
|
||||
|
||||
return { goodbye: "world" as const };
|
||||
export const zodTask = schemaTask({
|
||||
id: "types/zod",
|
||||
schema: z.object({
|
||||
bar: z.string(),
|
||||
baz: z.string().default("foo"),
|
||||
}),
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
|
||||
type ZodPayload = TaskPayload<typeof zodTask>;
|
||||
|
||||
import * as yup from "yup";
|
||||
|
||||
export const yupTask = schemaTask({
|
||||
id: "types/yup",
|
||||
schema: yup.object({
|
||||
bar: yup.string().required(),
|
||||
baz: yup.string().default("foo"),
|
||||
}),
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
|
||||
type YupPayload = TaskPayload<typeof yupTask>;
|
||||
|
||||
import { object, string } from "superstruct";
|
||||
|
||||
export const superstructTask = schemaTask({
|
||||
id: "types/superstruct",
|
||||
schema: object({
|
||||
bar: string(),
|
||||
baz: string(),
|
||||
}),
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
|
||||
type SuperstructPayload = TaskPayload<typeof superstructTask>;
|
||||
|
||||
import { type } from "arktype";
|
||||
|
||||
export const arktypeTask = schemaTask({
|
||||
id: "types/arktype",
|
||||
schema: type({
|
||||
bar: "string",
|
||||
baz: "string",
|
||||
}).assert,
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
|
||||
type ArktypePayload = TaskPayload<typeof arktypeTask>;
|
||||
|
||||
import * as Schema from "@effect/schema/Schema";
|
||||
|
||||
const effectSchemaParser = Schema.decodeUnknownSync(
|
||||
Schema.Struct({ bar: Schema.String, baz: Schema.String })
|
||||
);
|
||||
|
||||
export const effectTask = schemaTask({
|
||||
id: "types/effect",
|
||||
schema: effectSchemaParser,
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
|
||||
type EffectPayload = TaskPayload<typeof effectTask>;
|
||||
|
||||
import * as T from "runtypes";
|
||||
|
||||
export const runtypesTask = schemaTask({
|
||||
id: "types/runtypes",
|
||||
schema: T.Record({
|
||||
bar: T.String,
|
||||
baz: T.String,
|
||||
}),
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
|
||||
type RuntypesPayload = TaskPayload<typeof runtypesTask>;
|
||||
|
||||
import * as v from "valibot";
|
||||
|
||||
const valibotParser = v.parser(
|
||||
v.object({
|
||||
bar: v.string(),
|
||||
baz: v.string(),
|
||||
})
|
||||
);
|
||||
|
||||
export const valibotTask = schemaTask({
|
||||
id: "types/valibot",
|
||||
schema: valibotParser,
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { wrap } from "@typeschema/typebox";
|
||||
|
||||
export const typeboxTask = schemaTask({
|
||||
id: "types/typebox",
|
||||
schema: wrap(
|
||||
Type.Object({
|
||||
bar: Type.String(),
|
||||
baz: Type.String(),
|
||||
})
|
||||
),
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
|
||||
export const customParserTask = schemaTask({
|
||||
id: "types/custom-parser",
|
||||
schema: (data: unknown) => {
|
||||
// This is a custom parser, and should do actual parsing (not just casting)
|
||||
if (typeof data !== "object") {
|
||||
throw new Error("Invalid data");
|
||||
}
|
||||
|
||||
const { bar, baz } = data as { bar: string; baz: string };
|
||||
|
||||
return { bar, baz };
|
||||
},
|
||||
run: async (payload) => {
|
||||
console.log(payload.bar, payload.baz);
|
||||
},
|
||||
});
|
||||
|
||||
type CustomParserPayload = TaskPayload<typeof customParserTask>;
|
||||
|
||||
Reference in New Issue
Block a user