Compare commits

...

20 Commits

Author SHA1 Message Date
Eric Allam c9811beae2 Release 3.1.1
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 0s
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
2024-11-01 21:33:00 +00:00
github-actions[bot] 97533cba2e chore: Update version for release (#1442)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-01 21:32:04 +00:00
Dan e92459bf1e Added revalidation docs (#1444)
* Added revalidation docs

* Typo

* Copy tweak
2024-11-01 21:25:44 +00:00
Eric Allam cc74cae393 chore: remove unnecessary browser export condition (#1455) 2024-11-01 21:11:45 +00:00
nicktrn 58def055d0 Add new internal error for very outdated SDK versions (#1453)
* add outdated sdk error and crash outdated runs instead of staying queued forever

* add changeset
2024-11-01 15:41:31 +00:00
D-K-P 41f5261fe5 Added drizzle docs and updated prisma 2024-11-01 14:40:25 +00:00
Eric Allam ce1a54eb3b Renamed useBatch to useRealtimeBatch (#1447)
* Renamed useBatch to useRealtimeBatch

* Use the new hook name in the nextjs-realtime ref
2024-11-01 13:23:02 +00:00
Eric Allam 25285488f8 Add individual run ids to auto-generated public access token when calling batchTrigger (#1449) 2024-11-01 13:22:34 +00:00
Eric Allam befdcef1f0 Add known issue for realtime (#1452) 2024-11-01 13:21:01 +00:00
nicktrn fa18e6c012 Increase maximum retry count for deploy run controller (#1450)
* increase max retries for all deploy run controller operations

* add changeset
2024-11-01 11:23:11 +00:00
nicktrn 4194bce4a3 Forked worker processes now inherit PATH from parent (#1448)
* set PATH when forking

* add changeset
2024-11-01 11:23:00 +00:00
nicktrn da08e5015f Pass init output to all error handlers (#1441)
* pass init output to all error handlers

* add changeset
2024-10-31 11:23:47 +00:00
D-K-P 9b7472844d Updated deploy docs 2024-10-31 09:49:59 +00:00
Dan eace78fa04 Added fal AI example (#1439) 2024-10-30 17:30:23 +00:00
Dan e81b2a7839 Firecrawl example docs (#1438)
* Added firecrawl example docs

* Added Firecrawl and scrape hn to the intro page table
2024-10-30 16:41:04 +00:00
Eric Allam cdcfc81aca Add API_ORIGIN env var to be able to split dashboard & API servers 2024-10-29 16:43:22 +00:00
Eric Allam 816b0f9e3d Add some more react hooks & realtime docs 2024-10-29 16:41:57 +00:00
Eric Allam 6fb073672d Add realtime API docs 2024-10-29 13:57:02 +00:00
Eric Allam 9203bd8e67 Add realtime walkthrough video to docs 2024-10-29 13:23:57 +00:00
Eric Allam 3252d9ec31 docs: realtime, react hooks, and frontend usage (#1434)
* WIP realtime/frontend docs

* WIP

* Deleted old examples

* Improved styling

* Improved the generated example styles

* More style improvements

* More realtime docs

* Couple doc tweeks

* runs-and-attempts -> runs

* Remove the prerelease package version

---------

Co-authored-by: James Ritchie <james@trigger.dev>
2024-10-29 12:31:04 +00:00
69 changed files with 2672 additions and 395 deletions
+1
View File
@@ -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
View File
@@ -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 />
+242
View File
@@ -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).
+446
View File
@@ -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
```
+148
View File
@@ -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" />.
![Environment variables page](/images/environment-variables-page.jpg)
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.
![Environment variables
page](/images/environment-variables-panel.jpg)
</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 />
+145 -1
View File
@@ -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
+9 -8
View File
@@ -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. ![Environment variables page](/images/environment-variables-page.jpg)
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
}
```
+18 -15
View File
@@ -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
+5 -4
View File
@@ -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
View File
@@ -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",
+267
View File
@@ -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.
+49
View File
@@ -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 />
+38
View File
@@ -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>
+45 -18
View File
@@ -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).
![Run with triggerAndWait](/images/run-with-triggerAndWait().png)
![Run with triggerAndWait](</images/run-with-triggerAndWait().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).
![Run with batchTriggerAndWait](/images/run-with-batchTriggerAndWait().png)
![Run with batchTriggerAndWait](</images/run-with-batchTriggerAndWait().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.
+108
View File
@@ -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>
+261
View File
@@ -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);
},
});
```
+7
View File
@@ -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
+2 -2
View File
@@ -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"
+10
View File
@@ -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
+3 -3
View File
@@ -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`, {
+8
View File
@@ -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 -1
View File
@@ -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": {
+1 -1
View File
@@ -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",
});
+9
View File
@@ -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 => {
+3
View File
@@ -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",
+1
View File
@@ -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(),
+8 -7
View File
@@ -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,
+8
View File
@@ -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
+2 -2
View File
@@ -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();
+8
View File
@@ -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
+2 -13
View File
@@ -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",
+132 -27
View File
@@ -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
+8 -3
View File
@@ -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 },
},
-18
View File
@@ -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"
}
}
@@ -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;
-6
View File
@@ -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,
}),
],
},
});
-15
View File
@@ -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"]
}
+8
View File
@@ -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": {
+13 -3
View File
@@ -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);
+139 -12
View File
@@ -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>;