React status hooks (#493)
* Added stripInternal to SDK tsconfig * Statuses can now be set from a run, and are stored in the database * Added the key to the returned status * Made the test job have an extra step and only pass in some of the options * client.getRunStatuses() and the corresponding endpoint * client.getRun() now includes status info * Fixed circular dependency schema * Translate null to undefined * Added the react package to the nextjs-reference tsconfig * Removed unused OpenAI integration from nextjs-reference project * New hooks for getting the statuses * Disabled most of the nextjs-reference jobs * Updated the hooks UI * Updated the endpoints to deal with null statuses values * The hook is working, with an example * Changeset: “You can create statuses in your Jobs that can then be read using React hooks” * Changeset config is back to the old changelog style * WIP on new React hooks guide * Guide docs for the new hooks * Added the status hooks to the React hooks guide * Removed the links to the status hooks reference for now * Re-ordered the hooks * Fix for an error in the docs * Set a default of a blank array for the GetRunSchema
This commit is contained in:
@@ -1,11 +1,6 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json",
|
||||
"changelog": [
|
||||
"@remix-run/changelog-github",
|
||||
{
|
||||
"repo": "triggerdotdev/trigger.dev"
|
||||
}
|
||||
],
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [
|
||||
[
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@trigger.dev/sdk": patch
|
||||
"@trigger.dev/react": patch
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
You can create statuses in your Jobs that can then be read using React hooks
|
||||
@@ -0,0 +1,151 @@
|
||||
import type { ActionArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import { TaskStatus } from "@trigger.dev/database";
|
||||
import {
|
||||
RunTaskBodyOutput,
|
||||
RunTaskBodyOutputSchema,
|
||||
ServerTask,
|
||||
StatusHistory,
|
||||
StatusHistorySchema,
|
||||
StatusUpdate,
|
||||
StatusUpdateData,
|
||||
StatusUpdateSchema,
|
||||
StatusUpdateState,
|
||||
} from "@trigger.dev/core";
|
||||
import { z } from "zod";
|
||||
import { $transaction, PrismaClient, prisma } from "~/db.server";
|
||||
import { taskWithAttemptsToServerTask } from "~/models/task.server";
|
||||
import { authenticateApiRequest } from "~/services/apiAuth.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { ulid } from "~/services/ulid.server";
|
||||
import { workerQueue } from "~/services/worker.server";
|
||||
import { JobRunStatusRecordSchema } from "@trigger.dev/core";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
runId: z.string(),
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export async function action({ request, params }: ActionArgs) {
|
||||
// Ensure this is a POST request
|
||||
if (request.method.toUpperCase() !== "PUT") {
|
||||
return { status: 405, body: "Method Not Allowed" };
|
||||
}
|
||||
|
||||
// Next authenticate the request
|
||||
const authenticationResult = await authenticateApiRequest(request);
|
||||
|
||||
if (!authenticationResult) {
|
||||
return json({ error: "Invalid or Missing API key" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { runId, id } = ParamsSchema.parse(params);
|
||||
|
||||
// Now parse the request body
|
||||
const anyBody = await request.json();
|
||||
|
||||
logger.debug("SetStatusService.call() request body", {
|
||||
body: anyBody,
|
||||
runId,
|
||||
id,
|
||||
});
|
||||
|
||||
const body = StatusUpdateSchema.safeParse(anyBody);
|
||||
|
||||
if (!body.success) {
|
||||
return json({ error: "Invalid request body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const service = new SetStatusService();
|
||||
|
||||
try {
|
||||
const statusRecord = await service.call(runId, id, body.data);
|
||||
|
||||
logger.debug("SetStatusService.call() response body", {
|
||||
runId,
|
||||
id,
|
||||
statusRecord,
|
||||
});
|
||||
|
||||
if (!statusRecord) {
|
||||
return json({ error: "Something went wrong" }, { status: 500 });
|
||||
}
|
||||
|
||||
const status = JobRunStatusRecordSchema.parse({
|
||||
...statusRecord,
|
||||
state: statusRecord.state ?? undefined,
|
||||
history: statusRecord.history ?? undefined,
|
||||
data: statusRecord.data ?? undefined,
|
||||
});
|
||||
|
||||
return json(status);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return json({ error: error.message }, { status: 400 });
|
||||
}
|
||||
|
||||
return json({ error: "Something went wrong" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export class SetStatusService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(runId: string, id: string, status: StatusUpdate) {
|
||||
const statusRecord = await $transaction(this.#prismaClient, async (tx) => {
|
||||
const existingStatus = await tx.jobRunStatusRecord.findUnique({
|
||||
where: {
|
||||
runId_key: {
|
||||
runId,
|
||||
key: id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const history: StatusHistory = [];
|
||||
const historyResult = StatusHistorySchema.safeParse(existingStatus?.history);
|
||||
if (historyResult.success) {
|
||||
history.push(...historyResult.data);
|
||||
}
|
||||
if (existingStatus) {
|
||||
history.push({
|
||||
label: existingStatus.label,
|
||||
state: (existingStatus.state ?? undefined) as StatusUpdateState,
|
||||
data: (existingStatus.data ?? undefined) as StatusUpdateData,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedStatus = await tx.jobRunStatusRecord.upsert({
|
||||
where: {
|
||||
runId_key: {
|
||||
runId,
|
||||
key: id,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
key: id,
|
||||
runId,
|
||||
//this shouldn't ever use the id in reality, as the SDK makess it compulsory on the first call
|
||||
label: status.label ?? id,
|
||||
state: status.state,
|
||||
data: status.data as any,
|
||||
history: [],
|
||||
},
|
||||
update: {
|
||||
label: status.label,
|
||||
state: status.state,
|
||||
data: status.data as any,
|
||||
history: history as any[],
|
||||
},
|
||||
});
|
||||
|
||||
return updatedStatus;
|
||||
});
|
||||
|
||||
return statusRecord;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import type { LoaderArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import { JobRunStatusRecordSchema } from "@trigger.dev/core";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "~/db.server";
|
||||
import { authenticateApiRequest } from "~/services/apiAuth.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { apiCors } from "~/utils/apiCors";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
runId: z.string(),
|
||||
});
|
||||
|
||||
const RecordsSchema = z.array(JobRunStatusRecordSchema);
|
||||
|
||||
export async function loader({ request, params }: LoaderArgs) {
|
||||
if (request.method.toUpperCase() === "OPTIONS") {
|
||||
return apiCors(request, json({}));
|
||||
}
|
||||
|
||||
// Next authenticate the request
|
||||
const authenticationResult = await authenticateApiRequest(request, { allowPublicKey: true });
|
||||
|
||||
if (!authenticationResult) {
|
||||
return apiCors(request, json({ error: "Invalid or Missing API key" }, { status: 401 }));
|
||||
}
|
||||
|
||||
const { runId } = ParamsSchema.parse(params);
|
||||
|
||||
logger.debug("Get run statuses", {
|
||||
runId,
|
||||
});
|
||||
|
||||
try {
|
||||
const run = await prisma.jobRun.findUnique({
|
||||
where: {
|
||||
id: runId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
output: true,
|
||||
statuses: {
|
||||
orderBy: {
|
||||
createdAt: "asc",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!run) {
|
||||
return apiCors(request, json({ error: `No run found for id ${runId}` }, { status: 404 }));
|
||||
}
|
||||
|
||||
const parsedStatuses = RecordsSchema.parse(
|
||||
run.statuses.map((s) => ({
|
||||
...s,
|
||||
state: s.state ?? undefined,
|
||||
data: s.data ?? undefined,
|
||||
history: s.history ?? undefined,
|
||||
}))
|
||||
);
|
||||
|
||||
return apiCors(
|
||||
request,
|
||||
json({
|
||||
run: {
|
||||
id: run.id,
|
||||
status: run.status,
|
||||
output: run.output,
|
||||
},
|
||||
statuses: parsedStatuses,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return apiCors(request, json({ error: error.message }, { status: 400 }));
|
||||
}
|
||||
|
||||
return apiCors(request, json({ error: "Something went wrong" }, { status: 500 }));
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { LoaderArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import { cors } from "remix-utils";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "~/db.server";
|
||||
import { authenticateApiRequest, getApiKeyFromRequest } from "~/services/apiAuth.server";
|
||||
import { authenticateApiRequest } from "~/services/apiAuth.server";
|
||||
import { apiCors } from "~/utils/apiCors";
|
||||
import { taskListToTree } from "~/utils/taskListToTree";
|
||||
|
||||
@@ -93,6 +92,9 @@ export async function loader({ request, params }: LoaderArgs) {
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
statuses: {
|
||||
select: { key: true, label: true, state: true, data: true, history: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -122,6 +124,12 @@ export async function loader({ request, params }: LoaderArgs) {
|
||||
const { parentId, ...rest } = task;
|
||||
return { ...rest };
|
||||
}),
|
||||
statuses: jobRun.statuses.map((s) => ({
|
||||
...s,
|
||||
state: s.state ?? undefined,
|
||||
data: s.data ?? undefined,
|
||||
history: s.history ?? undefined,
|
||||
})),
|
||||
nextCursor: nextTask ? nextTask.id : undefined,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<Accordion title="How do I get a Run id?">
|
||||
You can call [client.getRuns()](/sdk/triggerclient/instancemethods/getruns) with a Job id to get a
|
||||
list of the most recent Runs for that Job. You can then pass that run id to your frontend to use
|
||||
in the hook.
|
||||
</Accordion>
|
||||
@@ -0,0 +1,24 @@
|
||||
## The two types of Run progress you can use
|
||||
|
||||
1. Automatic updates of Run and Task progress (no extra Job code required)
|
||||
2. Explicitly created and updated `statuses` (more flexible and powerful)
|
||||
|
||||
### Automatic updates
|
||||
|
||||
These require no changes inside your Job code. You can receive:
|
||||
|
||||
- Info about an event you sent, including the Runs it triggered.
|
||||
- The overall status of the Run (in progress, success and fail statuses).
|
||||
- Metadata like start and completed times.
|
||||
- The Run output (what is returned or an error that failed the Job)
|
||||
- Information about the Tasks that have completed/failed/are running.
|
||||
|
||||
### Explicit `statuses`
|
||||
|
||||
You can create `statuses` in your Job code. This gives you fine grained control over what you want to expose.
|
||||
|
||||
It allows you to:
|
||||
|
||||
- Show exactly what you want in your UI (with as many statuses as you want).
|
||||
- Pass arbitrary data to your UI, which you can use to render elements.
|
||||
- Update existing elements in your UI as the progress of the run continues.
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
title: "Automatic React hooks"
|
||||
description: "These allow you to show Run and Task progress without adding extra code to your Jobs."
|
||||
---
|
||||
|
||||
## The data you can receive
|
||||
|
||||
- Info about an event you sent, including the Runs it triggered.
|
||||
- The overall status of the Run (in progress, success and fail statuses).
|
||||
- Metadata like start and completed times.
|
||||
- The Run output (what is returned or an error that failed the Job)
|
||||
- Information about the Tasks that have completed/failed/are running.
|
||||
|
||||
## The hooks
|
||||
|
||||
- [useEventDetails](/sdk/react/useeventdetails): get the details of a specific event
|
||||
- [useRunDetails](/sdk/react/userundetails): get the details of a specific Run
|
||||
- [useEventRunDetails](/sdk/react/useeventrundetails): get the details of a Run triggered from a specific event
|
||||
|
||||
All of these hooks will automatically refresh your components as the state of your Runs or events change.
|
||||
|
||||
#### useEventDetails
|
||||
|
||||
The `useEventDetails` hook will get the details of a specific event. You can use this to show the status of a specific event.
|
||||
|
||||
<Snippet file="how-to-get-event-id.mdx" />
|
||||
|
||||
This component will show the details of an event and the overall status of Runs that were triggered by the event:
|
||||
|
||||
```tsx
|
||||
import { useEventDetails } from "@trigger.dev/react";
|
||||
|
||||
export default function EventDetails({ eventId }: { eventId: string }) {
|
||||
const { data, error } = useEventDetails(eventId);
|
||||
|
||||
if (error) {
|
||||
return <div>Error: {error.message}</div>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{data.name}</h1>
|
||||
<p>Runs: {data.runs?.length}</p>
|
||||
<div>
|
||||
{data.runs?.map((run) => (
|
||||
<div key={run.id}>
|
||||
<p>
|
||||
Run {run.id}: {run.status}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### useRunDetails
|
||||
|
||||
The `useRunDetails` hook will get the details of a specific Run. You can use this to show the status of a specific Run.
|
||||
|
||||
<Snippet file="how-to-get-run-id.mdx" />
|
||||
|
||||
This component will show the details of a Run and the status of each task in the Run:
|
||||
|
||||
```tsx
|
||||
import { useRunDetails } from "@trigger.dev/react";
|
||||
|
||||
export default function RunDetails({ runId }: { runId: string }) {
|
||||
const { data, error } = useRunDetails(runId);
|
||||
|
||||
if (error) {
|
||||
return <div>Error: {error.message}</div>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Run {data.id}</h1>
|
||||
<p>Status: {data.status}</p>
|
||||
<div>
|
||||
{data.tasks?.map((task) => (
|
||||
<div key={task.id}>
|
||||
<p>
|
||||
Task {task.id}: {task.status}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### useEventRunDetails
|
||||
|
||||
The `useEventRunDetails` hook will get the details of a specific Run that was triggered by a specific event. You can use this to show the status of a specific Run.
|
||||
|
||||
<Snippet file="how-to-get-event-id.mdx" />
|
||||
|
||||
This component will show the details of a Run and the status of each task in the Run:
|
||||
|
||||
```tsx
|
||||
import { useEventRunDetails } from "@trigger.dev/react";
|
||||
|
||||
export default function EventRunDetails({ eventId }: { eventId: string }) {
|
||||
const { data, error } = useEventRunDetails(eventId);
|
||||
|
||||
if (error) {
|
||||
return <div>Error: {error.message}</div>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Run {data.id}</h1>
|
||||
<p>Status: {data.status}</p>
|
||||
<div>
|
||||
{data.tasks?.map((task) => (
|
||||
<div key={task.id}>
|
||||
<p>
|
||||
Task {task.id}: {task.status}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,192 @@
|
||||
---
|
||||
title: "Explicit status hooks"
|
||||
description: "How to add `statuses` to your Job code and then subscribe using the hooks"
|
||||
---
|
||||
|
||||
You can create `statuses` in your Job code. This gives you fine grained control over what you want to expose.
|
||||
|
||||
It allows you to:
|
||||
|
||||
- Show exactly what you want in your UI (with as many statuses as you want).
|
||||
- Pass arbitrary data to your UI, which you can use to render elements.
|
||||
- Update existing elements in your UI as the progress of the run continues.
|
||||
|
||||
## In your Job code
|
||||
|
||||
You should create a "status", which you can then update throughout the Job if you'd like to. You can create more than one status. Each of these will come through to the hook with the latest status and the history for each.
|
||||
|
||||
```ts jobs/yourjob.ts
|
||||
//your job
|
||||
client.defineJob({
|
||||
id: "meme-generator",
|
||||
name: "Generate memes",
|
||||
version: "0.1.1",
|
||||
trigger: eventTrigger({
|
||||
name: "generate-memes",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
//create a status "generating-memes"
|
||||
//you give it the starting state. Only label is required
|
||||
const generatingMemes = await io.createStatus("generating-memes", {
|
||||
//the label is compulsory on this first call
|
||||
label: "Generating memes",
|
||||
//state is optional
|
||||
state: "loading",
|
||||
//data is an optional object. the values can be any type that is JSON serializable
|
||||
data: {
|
||||
progress: 0.1,
|
||||
},
|
||||
});
|
||||
|
||||
//...do stuff, like generate memes
|
||||
|
||||
//update the generatingMemes status.
|
||||
//anything set here will override the previous values, but you'll be able to view the full history with hooks
|
||||
await generatingMemes.update("middle-generation", {
|
||||
//label isn't specified so will remain the same
|
||||
//state will be updated to "success"
|
||||
state: "success",
|
||||
//set data, this overrides the previous value
|
||||
data: {
|
||||
progress: 1,
|
||||
urls: [
|
||||
"https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnZoMndsdWh0MmhvY2kyaDF6YjZjZzg1ZGsxdnhhYm13a3Q1Y3lkbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif",
|
||||
],
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
In this case we created just a single status `generating-memes` and then updated it. It's worth noting that you can create as many statuses as you'd like in a single Job and you can update them as many times as you want.
|
||||
|
||||
This allows you to fine-grained control over how you report progress and output data from your Job.
|
||||
|
||||
## Using the React hooks
|
||||
|
||||
There are two hooks you can use in your UI to display the Run statuses.
|
||||
|
||||
- **useEventRunStatuses**: get the statuses of a run that was triggered by a specified event
|
||||
- **useRunStatuses**: get the statuses of the specified run
|
||||
|
||||
## useEventRunStatuses
|
||||
|
||||
The `useEventRunStatuses` hook will give you the statuses and overview data of **the first run** that is triggered by an event.
|
||||
|
||||
<Snippet file="how-to-get-event-id.mdx" />
|
||||
|
||||
This component will show the details of a Run and the status of each task in the Run:
|
||||
|
||||
```ts
|
||||
import { useEventRunStatuses } from "@trigger.dev/react";
|
||||
|
||||
export function EventRunData({ id }: { id: string }) {
|
||||
const { fetchStatus, error, statuses, run } = useEventRunStatuses(id);
|
||||
|
||||
if (fetchStatus === "loading") {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
if (fetchStatus === "error") {
|
||||
return (
|
||||
<div>
|
||||
<p>{error.name}</p>
|
||||
<p>{error.message}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
//you receive the overall status of the run, e.g. SUCCESS, FAIL
|
||||
<div>Run status: {run.status}</div>
|
||||
<div>
|
||||
{statuses.map((status) => {
|
||||
switch (status.key) {
|
||||
case "generating-memes": {
|
||||
const urls = status.data?.urls as string[] | undefined;
|
||||
return (
|
||||
<div key={status.key}>
|
||||
// Will display: "Generating memes: loading"
|
||||
<p>
|
||||
{status.label}: {status.state}
|
||||
</p>
|
||||
//will render the memes as images
|
||||
{urls?.map((url) => <img key={url} src={url} />)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
//this is what's returned from the run function
|
||||
{run.output && (
|
||||
<code>
|
||||
<pre>{JSON.stringify(run.output, null, 2)}</pre>
|
||||
</code>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## useRunStatuses
|
||||
|
||||
The `useRunStatuses` hook will give you the statuses and overview data of a specific Run.
|
||||
|
||||
<Snippet file="how-to-get-run-id.mdx" />
|
||||
|
||||
This component will show the details of a Run and the status of each task in the Run:
|
||||
|
||||
```ts
|
||||
import { useRunStatuses } from "@trigger.dev/react";
|
||||
|
||||
export function RunData({ id }: { id: string }) {
|
||||
const { fetchStatus, error, statuses, run } = useRunStatuses(id);
|
||||
|
||||
if (fetchStatus === "loading") {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
if (fetchStatus === "error") {
|
||||
return (
|
||||
<div>
|
||||
<p>{error.name}</p>
|
||||
<p>{error.message}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
//you receive the overall status of the run, e.g. SUCCESS, FAIL
|
||||
<div>Run status: {run.status}</div>
|
||||
<div>
|
||||
{statuses.map((status) => {
|
||||
switch (status.key) {
|
||||
case "generating-memes": {
|
||||
const urls = status.data?.urls as string[] | undefined;
|
||||
return (
|
||||
<div key={status.key}>
|
||||
// Will display: "Generating memes: loading"
|
||||
<p>
|
||||
{status.label}: {status.state}
|
||||
</p>
|
||||
//will render the memes as images
|
||||
{urls?.map((url) => <img key={url} src={url} />)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
//this is what's returned from the run function
|
||||
{run.output && (
|
||||
<code>
|
||||
<pre>{JSON.stringify(run.output, null, 2)}</pre>
|
||||
</code>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "React hooks"
|
||||
title: "Overview"
|
||||
description: "How to show the live status of Job Runs in your React app"
|
||||
---
|
||||
|
||||
@@ -9,207 +9,96 @@ You can display the live progress of a Job Run to your users, including the stat
|
||||
<img src="/images/react-hooks.png" />
|
||||
</Frame>
|
||||
|
||||
## Steps
|
||||
## Setting up your project for hooks
|
||||
|
||||
This guide assumes that your project is already setup and you have a Job running. If not, you should follow the [quick start guide](/documentation/quickstart) first.
|
||||
|
||||
### 1. Install the package
|
||||
<Steps titleSize="h3">
|
||||
<Step title="Install the package">
|
||||
Add the `@trigger.dev/react` package to your project:
|
||||
|
||||
Add the `@trigger.dev/react` package to your project:
|
||||
<CodeGroup>
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install @trigger.dev/react@latest
|
||||
```
|
||||
|
||||
```bash npm
|
||||
npm install @trigger.dev/react@latest
|
||||
```
|
||||
```bash pnpm
|
||||
pnpm install @trigger.dev/react@latest
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm install @trigger.dev/react@latest
|
||||
```
|
||||
```bash yarn
|
||||
yarn add @trigger.dev/react@latest
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn add @trigger.dev/react@latest
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
</CodeGroup>
|
||||
</Step>
|
||||
<Step title="Get your public API key">
|
||||
In the Trigger.dev dashboard you should go to your Project and then the "Environments & API Keys" page.
|
||||
|
||||
### 2. Get your public API key
|
||||

|
||||
|
||||
In the Trigger.dev dashboard you should go to your Project and then the "Environments & API Keys" page.
|
||||
You should copy the `PUBLIC` API key for the dev environment.
|
||||
|
||||

|
||||
<Accordion title="What's a public API key?">
|
||||
A public API key is a key that can be used in the browser. It can only be used to read certain
|
||||
data from the API and can not write data. This means that it can be used to get the status of a
|
||||
Job Run, but not to start a new Job Run.
|
||||
</Accordion>
|
||||
|
||||
You should copy the `PUBLIC` API key for the dev environment.
|
||||
</Step>
|
||||
<Step title="Add the env var to your project">
|
||||
Add the `NEXT_PUBLIC_TRIGGER_API_KEY` environment variable to your project. This will be used by the `TriggerProvider` component to connect to the Trigger API.
|
||||
|
||||
<Accordion title="What's a public API key?">
|
||||
A public API key is a key that can be used in the browser. It can only be used to read certain
|
||||
data from the API and can not write data. This means that it can be used to get the status of a
|
||||
Job Run, but not to start a new Job Run.
|
||||
</Accordion>
|
||||
```sh .env.local
|
||||
#...
|
||||
TRIGGER_API_KEY=[your_private_api_key]
|
||||
NEXT_PUBLIC_TRIGGER_API_KEY=[your_public_api_key]
|
||||
#...
|
||||
```
|
||||
|
||||
### 3. Add the env var to your project
|
||||
Your private API key should already be in there.
|
||||
|
||||
Add the `NEXT_PUBLIC_TRIGGER_API_KEY` environment variable to your project. This will be used by the `TriggerProvider` component to connect to the Trigger API.
|
||||
</Step>
|
||||
|
||||
```sh .env.local
|
||||
#...
|
||||
TRIGGER_API_KEY=[your_private_api_key]
|
||||
NEXT_PUBLIC_TRIGGER_API_KEY=[your_public_api_key]
|
||||
#...
|
||||
```
|
||||
<Step title="Add the <TriggerProvider> component">
|
||||
The [TriggerProvider](/sdk/react/triggerprovider) component is a React Context Provider that will make the Trigger API client available to all child components.
|
||||
|
||||
Your private API key should already be in there.
|
||||
Generally you'll want to add this to the root of your app, so that it's available everywhere. However, you can add it lower in the hierarchy but it must be above any of the hooks.
|
||||
|
||||
### 4. Add the `TriggerProvider` component
|
||||
```tsx app/layout.tsx
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<TriggerProvider publicApiKey={process.env.NEXT_PUBLIC_TRIGGER_API_KEY!}>
|
||||
{children}
|
||||
</TriggerProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The [TriggerProvider](/sdk/react/triggerprovider) component is a React Context Provider that will make the Trigger API client available to all child components.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Generally you'll want to add this to the root of your app, so that it's available everywhere. However, you can add it lower in the hierarchy but it must be above any of the hooks.
|
||||
## Two ways to report Run progress
|
||||
|
||||
```tsx app/layout.tsx
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<TriggerProvider publicApiKey={process.env.NEXT_PUBLIC_TRIGGER_API_KEY!}>
|
||||
{children}
|
||||
</TriggerProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
**Automatic progress** – without writing additional code in your Job you can get updates on the overall run status and individual tasks inside the run.
|
||||
|
||||
### 5. Add hooks to your components
|
||||
**Explicit status** – add code to your Job that reports the status of the things you're doing. This gives you full flexibility for displaying progress in your UI.
|
||||
|
||||
There are three hooks that you can use to show statuses:
|
||||
|
||||
- [useEventDetails](/sdk/react/useeventdetails): get the details of a specific event
|
||||
- [useRunDetails](/sdk/react/userundetails): get the details of a specific Run
|
||||
- [useEventRunDetails](/sdk/react/useeventrundetails): get the details of a Run triggered from a specific event
|
||||
|
||||
All of these hooks will automatically refresh your components as the state of your Runs or events change.
|
||||
|
||||
#### 5.a useEventDetails
|
||||
|
||||
The `useEventDetails` hook will get the details of a specific event. You can use this to show the status of a specific event.
|
||||
|
||||
<Snippet file="how-to-get-event-id.mdx" />
|
||||
|
||||
This component will show the details of an event and the overall status of Runs that were triggered by the event:
|
||||
|
||||
```tsx
|
||||
import { useEventDetails } from "@trigger.dev/react";
|
||||
|
||||
export default function EventDetails({ eventId }: { eventId: string }) {
|
||||
const { data, error } = useEventDetails(eventId);
|
||||
|
||||
if (error) {
|
||||
return <div>Error: {error.message}</div>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{data.name}</h1>
|
||||
<p>Runs: {data.runs?.length}</p>
|
||||
<div>
|
||||
{data.runs?.map((run) => (
|
||||
<div key={run.id}>
|
||||
<p>
|
||||
Run {run.id}: {run.status}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.b useRunDetails
|
||||
|
||||
The `useRunDetails` hook will get the details of a specific Run. You can use this to show the status of a specific Run.
|
||||
|
||||
<Accordion title="How do I get a Run id?">
|
||||
You can call [client.getRuns()](/sdk/triggerclient/instancemethods/getruns) with a Job id to get a
|
||||
list of the most recent Runs for that Job. You can then pass that run id to your frontend to use
|
||||
in the hook.
|
||||
</Accordion>
|
||||
|
||||
This component will show the details of a Run and the status of each task in the Run:
|
||||
|
||||
```tsx
|
||||
import { useRunDetails } from "@trigger.dev/react";
|
||||
|
||||
export default function RunDetails({ runId }: { runId: string }) {
|
||||
const { data, error } = useRunDetails(runId);
|
||||
|
||||
if (error) {
|
||||
return <div>Error: {error.message}</div>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Run {data.id}</h1>
|
||||
<p>Status: {data.status}</p>
|
||||
<div>
|
||||
{data.tasks?.map((task) => (
|
||||
<div key={task.id}>
|
||||
<p>
|
||||
Task {task.id}: {task.status}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.c useEventRunDetails
|
||||
|
||||
The `useEventRunDetails` hook will get the details of a specific Run that was triggered by a specific event. You can use this to show the status of a specific Run.
|
||||
|
||||
<Snippet file="how-to-get-event-id.mdx" />
|
||||
|
||||
This component will show the details of a Run and the status of each task in the Run:
|
||||
|
||||
```tsx
|
||||
import { useEventRunDetails } from "@trigger.dev/react";
|
||||
|
||||
export default function EventRunDetails({ eventId }: { eventId: string }) {
|
||||
const { data, error } = useEventRunDetails(eventId);
|
||||
|
||||
if (error) {
|
||||
return <div>Error: {error.message}</div>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Run {data.id}</h1>
|
||||
<p>Status: {data.status}</p>
|
||||
<div>
|
||||
{data.tasks?.map((task) => (
|
||||
<div key={task.id}>
|
||||
<p>
|
||||
Task {task.id}: {task.status}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Automatic hooks" icon="wrench" href="/documentation/guides/react-hooks-automatic">
|
||||
Receive coarse updates without writing additional Job code
|
||||
</Card>
|
||||
<Card
|
||||
title="Explicit status hooks"
|
||||
icon="wrench"
|
||||
href="/documentation/guides/react-hooks-statuses"
|
||||
>
|
||||
Add statuses to your Job code for fine-grained UI updates
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
+8
-1
@@ -171,7 +171,14 @@
|
||||
"documentation/guides/using-integrations-oauth"
|
||||
]
|
||||
},
|
||||
"documentation/guides/react-hooks",
|
||||
{
|
||||
"group": "React hooks",
|
||||
"pages": [
|
||||
"documentation/guides/react-hooks",
|
||||
"documentation/guides/react-hooks-automatic",
|
||||
"documentation/guides/react-hooks-statuses"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Deployment",
|
||||
"pages": [
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
} from "./schedules";
|
||||
import { CachedTaskSchema, TaskSchema } from "./tasks";
|
||||
import { EventSpecificationSchema, TriggerMetadataSchema } from "./triggers";
|
||||
import { RunStatusSchema } from "./runs";
|
||||
import { JobRunStatusRecordSchema } from "./statuses";
|
||||
|
||||
export const UpdateTriggerSourceBodyV1Schema = z.object({
|
||||
registeredEvents: z.array(z.string()),
|
||||
@@ -729,3 +731,9 @@ export const CreateExternalConnectionBodySchema = z.object({
|
||||
});
|
||||
|
||||
export type CreateExternalConnectionBody = z.infer<typeof CreateExternalConnectionBodySchema>;
|
||||
|
||||
export const GetRunStatusesSchema = z.object({
|
||||
run: z.object({ id: z.string(), status: RunStatusSchema, output: z.any().optional() }),
|
||||
statuses: z.array(JobRunStatusRecordSchema),
|
||||
});
|
||||
export type GetRunStatuses = z.infer<typeof GetRunStatusesSchema>;
|
||||
|
||||
@@ -12,3 +12,4 @@ export * from "./fetch";
|
||||
export * from "./events";
|
||||
export * from "./runs";
|
||||
export * from "./addMissingVersionField";
|
||||
export * from "./statuses";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ZodObject, z } from "zod";
|
||||
import { TaskStatusSchema } from "./tasks";
|
||||
import { JobRunStatusRecordSchema } from "./statuses";
|
||||
|
||||
export const RunStatusSchema = z.union([
|
||||
z.literal("PENDING"),
|
||||
@@ -78,6 +79,8 @@ export const GetRunSchema = RunSchema.extend({
|
||||
output: z.any().optional(),
|
||||
/** The tasks from the run */
|
||||
tasks: z.array(RunTaskWithSubtasksSchema),
|
||||
/** Any status updates that were published from the run */
|
||||
statuses: z.array(JobRunStatusRecordSchema).default([]),
|
||||
/** If there are more tasks, you can use this to get them */
|
||||
nextCursor: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { z } from "zod";
|
||||
import { SerializableJsonSchema } from "./json";
|
||||
import { RunStatusSchema } from "./runs";
|
||||
|
||||
export const StatusUpdateStateSchema = z.union([
|
||||
z.literal("loading"),
|
||||
z.literal("success"),
|
||||
z.literal("failure"),
|
||||
]);
|
||||
export type StatusUpdateState = z.infer<typeof StatusUpdateStateSchema>;
|
||||
|
||||
const StatusUpdateDataSchema = z.record(SerializableJsonSchema);
|
||||
export type StatusUpdateData = z.infer<typeof StatusUpdateDataSchema>;
|
||||
|
||||
export const StatusUpdateSchema = z.object({
|
||||
label: z.string().optional(),
|
||||
state: StatusUpdateStateSchema.optional(),
|
||||
data: StatusUpdateDataSchema.optional(),
|
||||
});
|
||||
export type StatusUpdate = z.infer<typeof StatusUpdateSchema>;
|
||||
|
||||
const InitalStatusUpdateSchema = StatusUpdateSchema.required({ label: true });
|
||||
export type InitialStatusUpdate = z.infer<typeof InitalStatusUpdateSchema>;
|
||||
|
||||
export const StatusHistorySchema = z.array(StatusUpdateSchema);
|
||||
export type StatusHistory = z.infer<typeof StatusHistorySchema>;
|
||||
|
||||
export const JobRunStatusRecordSchema = InitalStatusUpdateSchema.extend({
|
||||
key: z.string(),
|
||||
history: StatusHistorySchema,
|
||||
});
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "JobRunStatusRecord" (
|
||||
"id" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"runId" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"state" TEXT,
|
||||
"data" JSONB,
|
||||
"history" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "JobRunStatusRecord_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "JobRunStatusRecord_runId_key_key" ON "JobRunStatusRecord"("runId", "key");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "JobRunStatusRecord" ADD CONSTRAINT "JobRunStatusRecord_runId_fkey" FOREIGN KEY ("runId") REFERENCES "JobRun"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -716,6 +716,7 @@ model JobRun {
|
||||
runConnections RunConnection[]
|
||||
missingConnections MissingConnection[]
|
||||
executions JobRunExecution[]
|
||||
statuses JobRunStatusRecord[]
|
||||
}
|
||||
|
||||
enum JobRunStatus {
|
||||
@@ -850,6 +851,25 @@ enum TaskAttemptStatus {
|
||||
ERRORED
|
||||
}
|
||||
|
||||
model JobRunStatusRecord {
|
||||
id String @id @default(cuid())
|
||||
key String
|
||||
|
||||
run JobRun @relation(fields: [runId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
runId String
|
||||
|
||||
label String
|
||||
state String?
|
||||
data Json?
|
||||
|
||||
history Json?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([runId, key])
|
||||
}
|
||||
|
||||
model SecretReference {
|
||||
id String @id @default(cuid())
|
||||
key String @unique
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./TriggerProvider";
|
||||
export * from "./events";
|
||||
export * from "./runs";
|
||||
export * from "./statuses";
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
"use client";
|
||||
|
||||
import { UseQueryResult, useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
GetRun,
|
||||
GetRunOptions,
|
||||
GetRunSchema,
|
||||
GetRunStatuses,
|
||||
GetRunStatusesSchema,
|
||||
urlWithSearchParams,
|
||||
} from "@trigger.dev/core";
|
||||
import { useTriggerProvider } from "./TriggerProvider";
|
||||
import { zodfetch } from "./fetch";
|
||||
import { useEventDetails } from "./events";
|
||||
import { runResolvedStatuses } from "./runs";
|
||||
|
||||
const defaultRefreshInterval = 1000;
|
||||
|
||||
export type RunStatusesOptions = {
|
||||
/** How often you want to refresh, the default is 1000. Min is 500 */
|
||||
refreshIntervalMs?: number;
|
||||
};
|
||||
|
||||
export type UseRunStatusesResult =
|
||||
| {
|
||||
fetchStatus: "loading";
|
||||
error: undefined;
|
||||
statuses: undefined;
|
||||
run: undefined;
|
||||
}
|
||||
| {
|
||||
fetchStatus: "error";
|
||||
error: Error;
|
||||
statuses: undefined;
|
||||
run: undefined;
|
||||
}
|
||||
| ({
|
||||
fetchStatus: "success";
|
||||
error: undefined;
|
||||
} & GetRunStatuses);
|
||||
|
||||
export function useRunStatuses(
|
||||
runId: string | undefined,
|
||||
options?: RunStatusesOptions
|
||||
): UseRunStatusesResult {
|
||||
const { apiUrl, publicApiKey, queryClient } = useTriggerProvider();
|
||||
|
||||
const queryResult = useQuery(
|
||||
{
|
||||
queryKey: [`triggerdotdev-run-${runId}`],
|
||||
queryFn: async () => {
|
||||
return await zodfetch(GetRunStatusesSchema, `${apiUrl}/api/v1/runs/${runId}/statuses`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${publicApiKey}`,
|
||||
},
|
||||
});
|
||||
},
|
||||
enabled: !!runId,
|
||||
refetchInterval: (data) => {
|
||||
if (data?.run.status && runResolvedStatuses.includes(data.run.status)) {
|
||||
return false;
|
||||
}
|
||||
if (options?.refreshIntervalMs !== undefined) {
|
||||
return Math.max(options.refreshIntervalMs, 500);
|
||||
}
|
||||
|
||||
return defaultRefreshInterval;
|
||||
},
|
||||
},
|
||||
queryClient
|
||||
);
|
||||
|
||||
switch (queryResult.status) {
|
||||
case "pending": {
|
||||
return {
|
||||
fetchStatus: "loading",
|
||||
error: undefined,
|
||||
statuses: undefined,
|
||||
run: undefined,
|
||||
};
|
||||
}
|
||||
case "error": {
|
||||
return {
|
||||
fetchStatus: "error",
|
||||
error: queryResult.error,
|
||||
statuses: undefined,
|
||||
run: undefined,
|
||||
};
|
||||
}
|
||||
case "success": {
|
||||
return {
|
||||
fetchStatus: "success",
|
||||
error: undefined,
|
||||
run: queryResult.data.run,
|
||||
statuses: queryResult.data.statuses,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function useEventRunStatuses(
|
||||
eventId: string | undefined,
|
||||
options?: RunStatusesOptions
|
||||
): UseRunStatusesResult {
|
||||
const event = useEventDetails(eventId);
|
||||
return useRunStatuses(event.data?.runs[0]?.id, options);
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
"jsx": "react",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"lib": ["es2015", "dom"],
|
||||
"paths": {
|
||||
"@trigger.dev/core": ["../core/src/index"],
|
||||
|
||||
@@ -24,6 +24,9 @@ import {
|
||||
urlWithSearchParams,
|
||||
UpdateTriggerSourceBodyV2,
|
||||
RegisterTriggerBodyV2,
|
||||
GetRunStatusesSchema,
|
||||
JobRunStatusRecordSchema,
|
||||
StatusUpdate,
|
||||
} from "@trigger.dev/core";
|
||||
|
||||
import fetch, { type RequestInit } from "node-fetch";
|
||||
@@ -198,6 +201,28 @@ export class ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
async updateStatus(runId: string, id: string, status: StatusUpdate) {
|
||||
const apiKey = await this.#apiKey();
|
||||
|
||||
this.#logger.debug("Update status", {
|
||||
id,
|
||||
status,
|
||||
});
|
||||
|
||||
return await zodfetch(
|
||||
JobRunStatusRecordSchema,
|
||||
`${this.#apiUrl}/api/v1/runs/${runId}/statuses/${id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify(status),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async updateSource(
|
||||
client: string,
|
||||
key: string,
|
||||
@@ -359,6 +384,21 @@ export class ApiClient {
|
||||
);
|
||||
}
|
||||
|
||||
async getRunStatuses(runId: string) {
|
||||
const apiKey = await this.#apiKey();
|
||||
|
||||
this.#logger.debug("Getting Run statuses", {
|
||||
runId,
|
||||
});
|
||||
|
||||
return await zodfetch(GetRunStatusesSchema, `${this.#apiUrl}/api/v1/runs/${runId}/statuses`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getRuns(jobSlug: string, options?: GetRunsOptions) {
|
||||
const apiKey = await this.#apiKey();
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ import {
|
||||
ErrorWithStackSchema,
|
||||
FetchRequestInit,
|
||||
FetchRetryOptions,
|
||||
InitialStatusUpdate,
|
||||
IntervalOptions,
|
||||
LogLevel,
|
||||
Logger,
|
||||
RunTaskOptions,
|
||||
SendEvent,
|
||||
SendEventOptions,
|
||||
SerializableJson,
|
||||
SerializableJsonSchema,
|
||||
ServerTask,
|
||||
UpdateTriggerSourceBodyV2,
|
||||
@@ -25,13 +25,14 @@ import {
|
||||
RetryWithTaskError,
|
||||
isTriggerError,
|
||||
} from "./errors";
|
||||
import { createIOWithIntegrations } from "./ioWithIntegrations";
|
||||
import { calculateRetryAt } from "./retry";
|
||||
import { TriggerClient } from "./triggerClient";
|
||||
import { DynamicTrigger } from "./triggers/dynamic";
|
||||
import { ExternalSource, ExternalSourceParams } from "./triggers/externalSource";
|
||||
import { DynamicSchedule } from "./triggers/scheduled";
|
||||
import { EventSpecification, TaskLogger, TriggerContext } from "./types";
|
||||
import { IntegrationTaskKey } from "./integrations";
|
||||
import { TriggerStatus } from "./status";
|
||||
|
||||
export type IOTask = ServerTask;
|
||||
|
||||
@@ -88,6 +89,16 @@ export class IO {
|
||||
this._context = options.context;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
get runId() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
get triggerClient() {
|
||||
return this._triggerClient;
|
||||
}
|
||||
|
||||
/** Used to send log messages to the [Run log](https://trigger.dev/docs/documentation/guides/viewing-runs). */
|
||||
get logger() {
|
||||
return new IOLogger(async (level, message, data) => {
|
||||
@@ -150,6 +161,48 @@ export class IO {
|
||||
});
|
||||
}
|
||||
|
||||
/** `io.createStatus()` allows you to set a status with associated data during the Run. Statuses can be used by your UI using the react package
|
||||
* @param key Should be a stable and unique key inside the `run()`. See [resumability](https://trigger.dev/docs/documentation/concepts/resumability) for more information.
|
||||
* @param initialStatus The initial status you want this status to have. You can update it during the rub using the returned object.
|
||||
* @returns a TriggerStatus object that you can call `update()` on, to update the status.
|
||||
* @example
|
||||
* ```ts
|
||||
* client.defineJob(
|
||||
//...
|
||||
run: async (payload, io, ctx) => {
|
||||
const generatingImages = await io.createStatus("generating-images", {
|
||||
label: "Generating Images",
|
||||
state: "loading",
|
||||
data: {
|
||||
progress: 0.1,
|
||||
},
|
||||
});
|
||||
|
||||
//...do stuff
|
||||
|
||||
await generatingImages.update("completed-generation", {
|
||||
label: "Generated images",
|
||||
state: "success",
|
||||
data: {
|
||||
progress: 1.0,
|
||||
urls: ["http://..."]
|
||||
},
|
||||
});
|
||||
|
||||
//...
|
||||
});
|
||||
* ```
|
||||
*/
|
||||
async createStatus(
|
||||
key: IntegrationTaskKey,
|
||||
initialStatus: InitialStatusUpdate
|
||||
): Promise<TriggerStatus> {
|
||||
const id = typeof key === "string" ? key : key.join("-");
|
||||
const status = new TriggerStatus(id, this);
|
||||
await status.update(key, initialStatus);
|
||||
return status;
|
||||
}
|
||||
|
||||
/** `io.backgroundFetch()` fetches data from a URL that can take longer that the serverless timeout. The actual `fetch` request is performed on the Trigger.dev platform, and the response is sent back to you.
|
||||
* @param key Should be a stable and unique key inside the `run()`. See [resumability](https://trigger.dev/docs/documentation/concepts/resumability) for more information.
|
||||
* @param url The URL to fetch from.
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { DisplayProperty, StatusUpdate } from "@trigger.dev/core/schemas";
|
||||
import { IntegrationTaskKey } from "./integrations";
|
||||
import { IO } from "./io";
|
||||
import { TriggerClient } from "./triggerClient";
|
||||
|
||||
export class TriggerStatus {
|
||||
constructor(
|
||||
private id: string,
|
||||
private io: IO
|
||||
) {}
|
||||
|
||||
async update(key: IntegrationTaskKey, status: StatusUpdate) {
|
||||
const properties: DisplayProperty[] = [];
|
||||
|
||||
if (status.label) {
|
||||
properties.push({
|
||||
label: "Label",
|
||||
text: status.label,
|
||||
});
|
||||
}
|
||||
|
||||
if (status.state) {
|
||||
properties.push({
|
||||
label: "State",
|
||||
text: status.state,
|
||||
});
|
||||
}
|
||||
|
||||
return await this.io.runTask(
|
||||
key,
|
||||
async (task) => {
|
||||
return await this.io.triggerClient.updateStatus(this.io.runId, this.id, status);
|
||||
},
|
||||
{
|
||||
name: status.label ?? `Status update`,
|
||||
icon: "clock",
|
||||
params: {
|
||||
...status,
|
||||
},
|
||||
properties,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
SendEvent,
|
||||
SendEventOptions,
|
||||
SourceMetadataV2,
|
||||
StatusUpdate,
|
||||
} from "@trigger.dev/core";
|
||||
import { ApiClient } from "./apiClient";
|
||||
import { CanceledWithTaskError, ResumeWithTaskError, RetryWithTaskError } from "./errors";
|
||||
@@ -567,6 +568,10 @@ export class TriggerClient {
|
||||
return this.#client.cancelEvent(eventId);
|
||||
}
|
||||
|
||||
async updateStatus(runId: string, id: string, status: StatusUpdate) {
|
||||
return this.#client.updateStatus(runId, id, status);
|
||||
}
|
||||
|
||||
async registerSchedule(id: string, key: string, schedule: ScheduleMetadata) {
|
||||
return this.#client.registerSchedule(this.id, id, key, schedule);
|
||||
}
|
||||
@@ -587,6 +592,10 @@ export class TriggerClient {
|
||||
return this.#client.getRuns(jobSlug, options);
|
||||
}
|
||||
|
||||
async getRunStatuses(runId: string) {
|
||||
return this.#client.getRunStatuses(runId);
|
||||
}
|
||||
|
||||
authorized(
|
||||
apiKey?: string | null
|
||||
): "authorized" | "unauthorized" | "missing-client" | "missing-header" {
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
},
|
||||
"lib": ["DOM", "DOM.Iterable"],
|
||||
"declaration": false,
|
||||
"declarationMap": false
|
||||
"declarationMap": false,
|
||||
"stripInternal": true
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"dynamic-schedule": "nodemon --watch src/dynamic-schedule.ts -r tsconfig-paths/register -r dotenv/config src/dynamic-schedule.ts",
|
||||
"dynamic-triggers": "nodemon --watch src/dynamic-triggers.ts -r tsconfig-paths/register -r dotenv/config src/dynamic-triggers.ts",
|
||||
"background-fetch": "nodemon --watch src/background-fetch.ts -r tsconfig-paths/register -r dotenv/config src/background-fetch.ts",
|
||||
"status": "nodemon --watch src/status.ts -r tsconfig-paths/register -r dotenv/config src/status.ts",
|
||||
"dev:trigger": "trigger-cli dev --port 8080"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -52,4 +53,4 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^3.14.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { createExpressServer } from "@trigger.dev/express";
|
||||
import { TriggerClient, eventTrigger } from "@trigger.dev/sdk";
|
||||
import { z } from "zod";
|
||||
|
||||
export const client = new TriggerClient({
|
||||
id: "job-catalog",
|
||||
apiKey: process.env["TRIGGER_API_KEY"],
|
||||
apiUrl: process.env["TRIGGER_API_URL"],
|
||||
verbose: false,
|
||||
ioLogLocalEnabled: true,
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "status",
|
||||
name: "Status: updating status",
|
||||
version: "0.0.2",
|
||||
trigger: eventTrigger({
|
||||
name: "status",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
const generatingText = await io.createStatus("generating-images", {
|
||||
label: "Generating Images",
|
||||
state: "loading",
|
||||
data: {
|
||||
progress: 0.1,
|
||||
},
|
||||
});
|
||||
|
||||
await io.wait("wait", 2);
|
||||
|
||||
//...do stuff
|
||||
await generatingText.update("middle-generation", {
|
||||
data: {
|
||||
progress: 0.5,
|
||||
urls: ["http://www."],
|
||||
},
|
||||
});
|
||||
|
||||
await io.wait("wait-again", 2);
|
||||
|
||||
//...do stuff
|
||||
await generatingText.update("completed-generation", {
|
||||
label: "Generated images",
|
||||
state: "success",
|
||||
data: {
|
||||
progress: 1.0,
|
||||
urls: ["http://www.", "http://www.", "http://www."],
|
||||
},
|
||||
});
|
||||
|
||||
await io.runTask("get statuses", async () => {
|
||||
return await client.getRunStatuses(ctx.run.id);
|
||||
});
|
||||
|
||||
await io.runTask("get run", async () => {
|
||||
return await client.getRun(ctx.run.id);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
createExpressServer(client);
|
||||
@@ -1,19 +1,6 @@
|
||||
import { createAppRoute } from "@trigger.dev/nextjs";
|
||||
import { client } from "@/trigger";
|
||||
|
||||
import "@/jobs/events";
|
||||
import "@/jobs/general";
|
||||
import "@/jobs/github";
|
||||
import "@/jobs/logging";
|
||||
import "@/jobs/openai";
|
||||
import "@/jobs/plain";
|
||||
import "@/jobs/resend";
|
||||
import "@/jobs/schedules";
|
||||
import "@/jobs/slack";
|
||||
import "@/jobs/typeform";
|
||||
import "@/jobs/edgeCases";
|
||||
import "@/jobs/hooks";
|
||||
import "@/jobs/supabase";
|
||||
import "@/jobs/stripe";
|
||||
|
||||
export const { POST, dynamic } = createAppRoute(client);
|
||||
|
||||
@@ -1,89 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import styles from "@/styles/Home.module.css";
|
||||
import { useEventDetails, useEventRunDetails, useRunDetails } from "@trigger.dev/react";
|
||||
|
||||
export function EventData({ id }: { id: string }) {
|
||||
const { isLoading, data, error } = useEventDetails(id);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className={styles.title}>Event</h1>
|
||||
{isLoading ? (
|
||||
<p>Loading</p>
|
||||
) : error ? (
|
||||
JSON.stringify(error, null, 2)
|
||||
) : data ? (
|
||||
<p>
|
||||
<strong>Event ID:</strong> {data.id}
|
||||
</p>
|
||||
) : (
|
||||
<p></p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function RunData({ id }: { id: string }) {
|
||||
const { isLoading, isError, data, error } = useRunDetails(id);
|
||||
|
||||
if (isLoading) {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <p>Error</p>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>Run status: {data.status}</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "0.3rem" }}>
|
||||
{data.tasks?.map((task) => (
|
||||
<div key={task.id} style={{ display: "flex", gap: "0.5rem" }}>
|
||||
<h4>{task.name}</h4>
|
||||
<p>Status: {task.status}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
import { useEventRunStatuses } from "@trigger.dev/react";
|
||||
|
||||
export function EventRunData({ id }: { id: string }) {
|
||||
const { isLoading, isError, data, error } = useEventRunDetails(id);
|
||||
const { fetchStatus, error, statuses, run } = useEventRunStatuses(id);
|
||||
|
||||
if (isLoading) {
|
||||
if (fetchStatus === "loading") {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <p>Error</p>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <p>Loading...</p>;
|
||||
if (fetchStatus === "error") {
|
||||
return (
|
||||
<div>
|
||||
<p>{error.name}</p>
|
||||
<p>{error.message}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>Run status: {data.status}</div>
|
||||
<div>Run status: {run.status}</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "0.2rem" }}>
|
||||
{data.tasks?.map((task) => (
|
||||
<div key={task.id} style={{ display: "flex", gap: "0.3rem", alignItems: "center" }}>
|
||||
<h4>{task.displayKey ?? task.name}</h4>
|
||||
<p>{task.icon}</p>
|
||||
<p>Status: {task.status}</p>
|
||||
</div>
|
||||
))}
|
||||
{statuses.map((status) => {
|
||||
switch (status.key) {
|
||||
case "getting-input-data": {
|
||||
return (
|
||||
<div key={status.key}>
|
||||
<h4>{status.label}</h4>
|
||||
<p>Status: {status.state}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case "generating-memes": {
|
||||
const urls = status.data?.urls as string[] | undefined;
|
||||
return (
|
||||
<div key={status.key}>
|
||||
{urls?.map((url) => <img key={url} src={url} style={{ display: "block" }} />)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
{data.output && (
|
||||
{run.output && (
|
||||
<code>
|
||||
<pre>{JSON.stringify(data.output, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(run.output, null, 2)}</pre>
|
||||
</code>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { client } from "@/trigger";
|
||||
import { Job, eventTrigger } from "@trigger.dev/sdk";
|
||||
import { z } from "zod";
|
||||
|
||||
client.defineJob({
|
||||
id: "test-long-running-cpu",
|
||||
name: "Test long running CPU",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: "test.cpu",
|
||||
schema: z.object({
|
||||
iterations: z.number(),
|
||||
sleepDuration: z.number(),
|
||||
}),
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
console.log(`Running run ${ctx.run.id} at ${new Date().toISOString()}`);
|
||||
|
||||
for (let i = 0; i < payload.iterations ?? 1; i++) {
|
||||
await new Promise((resolve) => setTimeout(resolve, payload.sleepDuration ?? 1000));
|
||||
}
|
||||
|
||||
console.log(`Finishing run ${ctx.run.id} at ${new Date().toISOString()}`);
|
||||
},
|
||||
});
|
||||
@@ -1,60 +0,0 @@
|
||||
import { client } from "@/trigger";
|
||||
import { Job, eventTrigger } from "@trigger.dev/sdk";
|
||||
import { z } from "zod";
|
||||
|
||||
client.defineJob({
|
||||
id: "test-event-trigger-1",
|
||||
name: "Test Event Trigger 1",
|
||||
version: "0.0.1",
|
||||
logLevel: "debug",
|
||||
trigger: eventTrigger({
|
||||
name: "test-event-trigger-1",
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
payload: z.any(),
|
||||
}),
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.sendEvent(
|
||||
"send",
|
||||
{
|
||||
name: payload.name,
|
||||
payload: payload.payload,
|
||||
timestamp: new Date(),
|
||||
},
|
||||
{ deliverAt: new Date(Date.now() + 1000 * 30) }
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "test-event-trigger-2",
|
||||
name: "Test Event Trigger 2",
|
||||
version: "0.0.1",
|
||||
logLevel: "debug",
|
||||
trigger: eventTrigger({
|
||||
name: "test-event-trigger-2",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
for (let index = 0; index < 100; index++) {
|
||||
await io.sendEvent(`send-${index}`, {
|
||||
name: "test-event-trigger-1",
|
||||
payload: { name: "whatever", payload: { index } },
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "test-multiple-events",
|
||||
name: "Test Multiple Events",
|
||||
version: "0.0.1",
|
||||
logLevel: "debug",
|
||||
trigger: eventTrigger({
|
||||
name: ["test.event.1", "test.event.2"],
|
||||
examples: [{ id: "test", name: "Test", payload: { name: "test" } }],
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.log(`Triggered by the ${ctx.event.name} event`, { ctx });
|
||||
},
|
||||
});
|
||||
@@ -1,269 +0,0 @@
|
||||
import { client, github, githubUser, openai, slack } from "@/trigger";
|
||||
import { events } from "@trigger.dev/github";
|
||||
import {
|
||||
Job,
|
||||
cronTrigger,
|
||||
eventTrigger,
|
||||
intervalTrigger,
|
||||
isTriggerError,
|
||||
missingConnectionNotification,
|
||||
missingConnectionResolvedNotification,
|
||||
} from "@trigger.dev/sdk";
|
||||
import { z } from "zod";
|
||||
|
||||
const enabled = true;
|
||||
|
||||
client.defineJob({
|
||||
id: "on-missing-auth-connection",
|
||||
name: "On missing auth connection",
|
||||
version: "0.1.1",
|
||||
enabled,
|
||||
trigger: missingConnectionNotification([githubUser]),
|
||||
integrations: {
|
||||
slack,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
switch (payload.type) {
|
||||
case "DEVELOPER": {
|
||||
return await io.slack.postMessage("message", {
|
||||
text: `Missing developer connection: ${JSON.stringify(payload)}`,
|
||||
channel: "C04GWUTDC3W",
|
||||
});
|
||||
}
|
||||
case "EXTERNAL": {
|
||||
return await io.slack.postMessage("message", {
|
||||
text: `Missing external connection: account: ${JSON.stringify(
|
||||
payload.account
|
||||
)}, payload: ${JSON.stringify(payload)}`,
|
||||
channel: "C04GWUTDC3W",
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "on-missing-auth-connection-resolved",
|
||||
name: "On missing auth connection-resolved",
|
||||
version: "0.1.1",
|
||||
enabled,
|
||||
trigger: missingConnectionResolvedNotification([githubUser]),
|
||||
integrations: {
|
||||
slack,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
switch (payload.type) {
|
||||
case "DEVELOPER": {
|
||||
return await io.slack.postMessage("message", {
|
||||
text: `Missing developer connection resolved: ${JSON.stringify(payload)}`,
|
||||
channel: "C04GWUTDC3W",
|
||||
});
|
||||
}
|
||||
case "EXTERNAL": {
|
||||
return await io.slack.postMessage("message", {
|
||||
text: `Missing external connection resolved: ${JSON.stringify(payload)}`,
|
||||
channel: "C04GWUTDC3W",
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "get-user-repo",
|
||||
name: "Get User Repo",
|
||||
version: "0.1.1",
|
||||
enabled,
|
||||
trigger: eventTrigger({
|
||||
name: "get.repo",
|
||||
schema: z.object({
|
||||
owner: z.string(),
|
||||
repo: z.string(),
|
||||
}),
|
||||
}),
|
||||
integrations: {
|
||||
github: githubUser,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
return await io.github.getRepo("get.repo", payload);
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "event-1",
|
||||
name: "Run when the foo.bar event happens",
|
||||
version: "0.0.1",
|
||||
enabled: true,
|
||||
trigger: eventTrigger({
|
||||
name: "foo.bar",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.try(
|
||||
async () => {
|
||||
return await io.runTask(
|
||||
"task-1",
|
||||
async (task) => {
|
||||
if (task.attempts > 2) {
|
||||
return {
|
||||
bar: "foo",
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Task failed on ${task.attempts} attempt(s)`);
|
||||
},
|
||||
{ name: "task-1", retry: { limit: 3 } }
|
||||
);
|
||||
},
|
||||
async (error) => {
|
||||
// These should never be reached
|
||||
await io.wait("wait-after-error", 5);
|
||||
|
||||
await io.logger.error("This is a log error message", {
|
||||
payload,
|
||||
error,
|
||||
});
|
||||
|
||||
return {
|
||||
foo: "bar",
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
await io.runTask(
|
||||
"task-2",
|
||||
async (task) => {
|
||||
throw new Error(`Task failed on ${task.attempts} attempt(s)`);
|
||||
},
|
||||
{ name: "task-2", retry: { limit: 5 } }
|
||||
);
|
||||
} catch (error) {
|
||||
if (isTriggerError(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
await io.wait("wait-after-error", 5);
|
||||
|
||||
await io.logger.error("This is a log error message", {
|
||||
payload,
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
payload,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "scheduled-job-1",
|
||||
name: "Scheduled Job 1",
|
||||
version: "0.1.1",
|
||||
enabled: true,
|
||||
trigger: intervalTrigger({
|
||||
seconds: 60,
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.wait("wait", 5); // wait for 5 seconds
|
||||
|
||||
await io.logger.info("This is a log info message", {
|
||||
payload,
|
||||
});
|
||||
|
||||
await io.sendEvent("send-event", {
|
||||
name: "custom.event",
|
||||
payload,
|
||||
context: ctx,
|
||||
});
|
||||
|
||||
await io.runTask("level 1", async () => {
|
||||
await io.runTask("level 2", async () => {
|
||||
await io.runTask("level 3", async () => {
|
||||
await io.runTask("level 4", async () => {
|
||||
await io.runTask("level 5", async () => {});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await io.wait("5 minutes", 5 * 60);
|
||||
|
||||
await io.runTask("Fingers crossed", async () => {
|
||||
throw new Error("You messed up buddy!");
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "scheduled-job-2",
|
||||
name: "Scheduled Job 2",
|
||||
version: "0.1.1",
|
||||
enabled,
|
||||
trigger: cronTrigger({
|
||||
cron: "*/5 * * * *", // every 5 minutes
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.wait("wait", 5); // wait for 5 seconds
|
||||
await io.logger.info("This is a log info message", {
|
||||
payload,
|
||||
ctx,
|
||||
});
|
||||
|
||||
return {
|
||||
message: "Hello from scheduled job 1",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "test-io-functions",
|
||||
name: "Test IO functions",
|
||||
version: "0.1.1",
|
||||
enabled,
|
||||
trigger: eventTrigger({
|
||||
name: "test.io",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.wait("wait", 5); // wait for 5 seconds
|
||||
await io.logger.info("This is a log info message", {
|
||||
payload,
|
||||
});
|
||||
await io.sendEvent("send-event", {
|
||||
name: "custom.event",
|
||||
payload,
|
||||
context: ctx,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "alert-on-new-github-issues-3",
|
||||
name: "Alert on new GitHub issues",
|
||||
version: "0.1.1",
|
||||
enabled,
|
||||
integrations: {
|
||||
slack,
|
||||
},
|
||||
trigger: github.triggers.repo({
|
||||
event: events.onIssueOpened,
|
||||
owner: "ericallam",
|
||||
repo: "basic-starter-12k",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.runTask("slow task", async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
});
|
||||
|
||||
await io.logger.info("This is a simple log info message");
|
||||
|
||||
await io.wait("wait", 5); // wait for 5 seconds
|
||||
|
||||
const response = await io.slack.postMessage("Slack 📝", {
|
||||
text: `New Issue opened: ${payload.issue.html_url}`,
|
||||
channel: "C04GWUTDC3W",
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -1,371 +0,0 @@
|
||||
import { client, github, slack } from "@/trigger";
|
||||
import { Github } from "@trigger.dev/github";
|
||||
import { events } from "@trigger.dev/github";
|
||||
import { Job } from "@trigger.dev/sdk";
|
||||
|
||||
const githubApiKey = new Github({
|
||||
id: "github-api-key",
|
||||
token: process.env["GITHUB_API_KEY"]!,
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-on-issue",
|
||||
name: "GitHub Integration - On Issue",
|
||||
version: "0.1.0",
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onIssue,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-on-issue-opened",
|
||||
name: "GitHub Integration - On Issue Opened",
|
||||
version: "0.1.0",
|
||||
integrations: { github: githubApiKey },
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onIssueOpened,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.github.addIssueAssignees("add assignee", {
|
||||
owner: payload.repository.owner.login,
|
||||
repo: payload.repository.name,
|
||||
issueNumber: payload.issue.number,
|
||||
assignees: ["matt-aitken"],
|
||||
});
|
||||
|
||||
await io.github.addIssueLabels("add label", {
|
||||
owner: payload.repository.owner.login,
|
||||
repo: payload.repository.name,
|
||||
issueNumber: payload.issue.number,
|
||||
labels: ["bug"],
|
||||
});
|
||||
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "new-github-issue-reminder",
|
||||
name: "New GitHub issue reminder",
|
||||
version: "0.1.0",
|
||||
integrations: { github, slack },
|
||||
trigger: github.triggers.repo({
|
||||
event: events.onIssueOpened,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
//delay for 24 hours (or 60 seconds in development)
|
||||
const delayDuration = ctx.environment.type === "DEVELOPMENT" ? 60 : 60 * 60 * 24;
|
||||
await io.wait("wait 24 hours", delayDuration);
|
||||
|
||||
const issue = await io.github.getIssue("get issue", {
|
||||
owner: payload.repository.owner.login,
|
||||
repo: payload.repository.name,
|
||||
issueNumber: payload.issue.number,
|
||||
});
|
||||
|
||||
//if the issue has had no activity
|
||||
if (issue.updated_at === payload.issue.updated_at) {
|
||||
await io.slack.postMessage("Slack reminder", {
|
||||
text: `New issue needs attention: <${issue.html_url}|${issue.title}>`,
|
||||
channel: "C04GWUTDC3W",
|
||||
});
|
||||
|
||||
//assign it to someone, in this case… me
|
||||
await io.github.addIssueAssignees("add assignee", {
|
||||
owner: payload.repository.owner.login,
|
||||
repo: payload.repository.name,
|
||||
issueNumber: payload.issue.number,
|
||||
assignees: ["ericallam"],
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-on-issue-assigned",
|
||||
name: "GitHub Integration - On Issue assigned",
|
||||
version: "0.1.0",
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onIssueAssigned,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-on-issue-commented",
|
||||
name: "GitHub Integration - On Issue commented",
|
||||
version: "0.1.0",
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onIssueComment,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "star-slack-notification",
|
||||
name: "New Star Slack Notification",
|
||||
version: "0.1.0",
|
||||
integrations: { slack },
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onNewStar,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
const response = await io.slack.postMessage("Slack star", {
|
||||
text: `${payload.sender.login} starred ${payload.repository.full_name}.\nTotal: ${payload.repository.stargazers_count}⭐️`,
|
||||
channel: "C04GWUTDC3W",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-on-new-star",
|
||||
name: "GitHub Integration - On New Star",
|
||||
version: "0.1.0",
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onNewStar,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-on-new-repo",
|
||||
name: "GitHub Integration - On New Repository",
|
||||
version: "0.1.0",
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onNewRepository,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-on-new-branch-or-tag",
|
||||
name: "GitHub Integration - On New Branch or Tag",
|
||||
version: "0.1.0",
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onNewBranchOrTag,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-on-new-branch",
|
||||
name: "GitHub Integration - On New Branch",
|
||||
version: "0.1.0",
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onNewBranch,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-on-push",
|
||||
name: "GitHub Integration - On Push",
|
||||
version: "0.1.0",
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onPush,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-on-pull-request",
|
||||
name: "GitHub Integration - On Pull Request",
|
||||
version: "0.1.0",
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onPullRequest,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-on-pull-request-review",
|
||||
name: "GitHub Integration - On Pull Request Review",
|
||||
version: "0.1.0",
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onPullRequestReview,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-on-pull-request-merge-commit",
|
||||
name: "GitHub Integration - on Pull Request Merge Commit",
|
||||
version: "0.1.0",
|
||||
integrations: { github },
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onPullRequest,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
|
||||
if (payload.pull_request.merged && payload.pull_request.merge_commit_sha) {
|
||||
const commit = await io.github.getCommit("get merge commit", {
|
||||
owner: payload.repository.owner.login,
|
||||
repo: payload.repository.name,
|
||||
commitSHA: payload.pull_request.merge_commit_sha,
|
||||
});
|
||||
await io.logger.info("Merge Commit Details", commit);
|
||||
}
|
||||
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-get-tree",
|
||||
name: "GitHub Integration - Get Tree",
|
||||
version: "0.1.0",
|
||||
integrations: { github },
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onPullRequest,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
|
||||
if (payload.pull_request.merged && payload.pull_request.merge_commit_sha) {
|
||||
const tree = await io.github.getTree("get merge commit", {
|
||||
owner: payload.repository.owner.login,
|
||||
repo: payload.repository.name,
|
||||
treeSHA: payload.pull_request.merge_commit_sha,
|
||||
});
|
||||
await io.logger.info("Tree ", tree);
|
||||
}
|
||||
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-get-reference",
|
||||
name: "GitHub Integration - Get Reference",
|
||||
integrations: { github },
|
||||
version: "0.1.0",
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onNewBranch,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
|
||||
const ref = await io.github.getReference("Get reference", {
|
||||
owner: payload.repository.owner.login,
|
||||
repo: payload.repository.name,
|
||||
ref: payload.ref,
|
||||
});
|
||||
|
||||
await io.logger.info("Reference ", ref);
|
||||
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-list-matching-references",
|
||||
name: "GitHub Integration - List Matching References",
|
||||
integrations: { github },
|
||||
version: "0.1.0",
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onNewBranch,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
|
||||
const ref = await io.github.listMatchingReferences("List Matching References", {
|
||||
owner: payload.repository.owner.login,
|
||||
repo: payload.repository.name,
|
||||
ref: payload.ref,
|
||||
});
|
||||
|
||||
await io.logger.info("Reference ", ref);
|
||||
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "github-integration-get-tag",
|
||||
name: "GitHub Integration - Get Tag",
|
||||
version: "0.1.0",
|
||||
integrations: { github },
|
||||
trigger: githubApiKey.triggers.repo({
|
||||
event: events.onNewBranchOrTag,
|
||||
owner: "triggerdotdev",
|
||||
repo: "empty",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("This is a simple log info message");
|
||||
if (payload.ref_type === "tag") {
|
||||
const tag = io.github.getTag("Get Tag", {
|
||||
owner: payload.repository.owner.login,
|
||||
repo: payload.repository.name,
|
||||
tagSHA: payload.ref,
|
||||
});
|
||||
await io.logger.info("Tag ", tag);
|
||||
}
|
||||
return { payload, ctx };
|
||||
},
|
||||
});
|
||||
@@ -9,14 +9,64 @@ client.defineJob({
|
||||
name: "test-event",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.log("This Job is triggered from a button in the frontend");
|
||||
await io.wait("wait", 5);
|
||||
await io.logger.log("It runs for a while to test the React hooks");
|
||||
await io.wait("wait 2", 5);
|
||||
await io.logger.log("This is the end of the job");
|
||||
const gettingInputData = await io.createStatus("getting-input-data", {
|
||||
label: "Getting input data",
|
||||
// state: "loading",
|
||||
});
|
||||
|
||||
return {
|
||||
myMessage: "This is the output of the job",
|
||||
};
|
||||
await io.wait("wait-input", 2);
|
||||
|
||||
await gettingInputData.update("input-data-complete", {
|
||||
label: "Input data complete",
|
||||
state: "success",
|
||||
});
|
||||
|
||||
const generatingMemes = await io.createStatus("generating-memes", {
|
||||
label: "Generating memes",
|
||||
state: "loading",
|
||||
data: {
|
||||
progress: 0.1,
|
||||
},
|
||||
});
|
||||
|
||||
await io.wait("wait", 2);
|
||||
|
||||
//...do stuff
|
||||
await generatingMemes.update("middle-generation", {
|
||||
data: {
|
||||
progress: 0.3,
|
||||
urls: [
|
||||
"https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnZoMndsdWh0MmhvY2kyaDF6YjZjZzg1ZGsxdnhhYm13a3Q1Y3lkbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await io.wait("wait-again", 4);
|
||||
|
||||
//...do stuff
|
||||
await generatingMemes.update("generating-more-memes", {
|
||||
data: {
|
||||
progress: 0.6,
|
||||
urls: [
|
||||
"https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnZoMndsdWh0MmhvY2kyaDF6YjZjZzg1ZGsxdnhhYm13a3Q1Y3lkbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif",
|
||||
"https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbXdhNGhjaXVoZzFrMWJ0dmYyM2ZuOTIxN2J3aWYwY3J1OHI4eW13cCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/scZPhLqaVOM1qG4lT9/giphy.gif",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await io.wait("wait-again", 4);
|
||||
|
||||
await generatingMemes.update("completed-generation", {
|
||||
label: "Generated memes",
|
||||
state: "success",
|
||||
data: {
|
||||
progress: 1.0,
|
||||
urls: [
|
||||
"https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZnZoMndsdWh0MmhvY2kyaDF6YjZjZzg1ZGsxdnhhYm13a3Q1Y3lkbyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif",
|
||||
"https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbXdhNGhjaXVoZzFrMWJ0dmYyM2ZuOTIxN2J3aWYwY3J1OHI4eW13cCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/scZPhLqaVOM1qG4lT9/giphy.gif",
|
||||
"https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExdHJhdXJ2Nnl6YnR3bXZuejZ3Y3Q5a2w3Mng2ZXZmMmJjeWdtZWhibCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/yYSSBtDgbbRzq/giphy-downsized.gif",
|
||||
],
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { client } from "@/trigger";
|
||||
import { Job, eventTrigger } from "@trigger.dev/sdk";
|
||||
|
||||
client.defineJob({
|
||||
id: "test-logging",
|
||||
name: "Test logging",
|
||||
version: "0.0.1",
|
||||
logLevel: "debug",
|
||||
trigger: eventTrigger({
|
||||
name: "test.logging",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.log("Hello log level", { payload });
|
||||
await io.logger.error("Hello error level", { payload });
|
||||
await io.logger.warn("Hello warn level", { payload });
|
||||
await io.logger.info("Hello info level", { payload });
|
||||
await io.logger.debug("Hello debug level", { payload });
|
||||
},
|
||||
});
|
||||
@@ -1,157 +0,0 @@
|
||||
import { client } from "@/trigger";
|
||||
import { OpenAI } from "@trigger.dev/openai";
|
||||
import { Job, eventTrigger } from "@trigger.dev/sdk";
|
||||
import { z } from "zod";
|
||||
|
||||
const openai = new OpenAI({
|
||||
id: "openai",
|
||||
apiKey: process.env["OPENAI_API_KEY"]!,
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "openai-tasks",
|
||||
name: "OpenAI Tasks",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: "openai.tasks",
|
||||
schema: z.object({}),
|
||||
}),
|
||||
integrations: {
|
||||
openai,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
const models = await io.openai.listModels("list-models");
|
||||
|
||||
if (models.data.length > 0) {
|
||||
await io.openai.retrieveModel("get-model", {
|
||||
model: models.data[0].id,
|
||||
});
|
||||
}
|
||||
|
||||
await io.openai.backgroundCreateChatCompletion("background-chat-completion", {
|
||||
model: "gpt-3.5-turbo",
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Create a good programming joke about background jobs",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await io.openai.createChatCompletion("chat-completion", {
|
||||
model: "gpt-3.5-turbo",
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Create a good programming joke about background jobs",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await io.openai.backgroundCreateCompletion("background-completion", {
|
||||
model: "text-davinci-003",
|
||||
prompt: "Create a good programming joke about Tasks",
|
||||
});
|
||||
|
||||
await io.openai.createCompletion("completion", {
|
||||
model: "text-davinci-003",
|
||||
prompt: "Create a good programming joke about Tasks",
|
||||
});
|
||||
|
||||
await io.openai.createEdit("edit", {
|
||||
model: "text-davinci-edit-001",
|
||||
input: "Thsi is ridddled with erors",
|
||||
instruction: "Fix the spelling errors",
|
||||
});
|
||||
|
||||
await io.openai.createEmbedding("embedding", {
|
||||
model: "text-embedding-ada-002",
|
||||
input: "The food was delicious and the waiter...",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "openai-images",
|
||||
name: "OpenAI Images",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: "openai.images",
|
||||
schema: z.object({}),
|
||||
}),
|
||||
integrations: {
|
||||
openai,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.openai.createImage("image", {
|
||||
prompt: "A hedgehog wearing a party hat",
|
||||
n: 2,
|
||||
size: "256x256",
|
||||
response_format: "url",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "openai-files",
|
||||
name: "OpenAI Files",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: "openai.files",
|
||||
schema: z.object({}),
|
||||
}),
|
||||
integrations: {
|
||||
openai,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
// jsonl string
|
||||
await io.openai.createFile("file-string", {
|
||||
file: `{ "prompt": "Tell me a joke", "completion": "Something funny" }\n{ "prompt": "Tell me another joke", "completion": "Something also funny" }`,
|
||||
fileName: "cool-file.jsonl",
|
||||
purpose: "fine-tune",
|
||||
});
|
||||
|
||||
// fine tune file
|
||||
const fineTuneFile = await io.openai.createFineTuneFile("file-fine-tune", {
|
||||
fileName: "fine-tune.jsonl",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Tell me a joke",
|
||||
completion: "Why did the chicken cross the road? No one knows",
|
||||
},
|
||||
{
|
||||
prompt: "Tell me another joke",
|
||||
completion: "Why did the chicken cross the road? To get to the other side",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const model = await io.openai.createFineTune("fine-tune", {
|
||||
model: "davinci",
|
||||
training_file: fineTuneFile.id,
|
||||
});
|
||||
|
||||
const fineTunes = await io.openai.listFineTunes("list-fine-tunes");
|
||||
|
||||
const fineTune = await io.openai.retrieveFineTune("get-fine-tune", {
|
||||
fineTuneId: model.id,
|
||||
});
|
||||
|
||||
const events = await io.openai.listFineTuneEvents("list-fine-tune-events", {
|
||||
fineTuneId: model.id,
|
||||
});
|
||||
|
||||
const cancelFineTune = await io.openai.cancelFineTune("cancel-fine-tune", {
|
||||
fineTuneId: model.id,
|
||||
});
|
||||
|
||||
const files = await io.openai.listFiles("list-files");
|
||||
await io.logger.info("files", files);
|
||||
|
||||
//this will fail because the fine tune didn't complete
|
||||
await io.logger.info("This next task will fail because the model never completed");
|
||||
const deleteFineTune = await io.openai.deleteFineTune("delete-fine-tune", {
|
||||
fineTunedModelId: model.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,88 +0,0 @@
|
||||
import { client } from "@/trigger";
|
||||
import {
|
||||
ComponentDividerSpacingSize,
|
||||
ComponentTextColor,
|
||||
ComponentTextSize,
|
||||
Plain,
|
||||
} from "@trigger.dev/plain";
|
||||
import { Job, eventTrigger } from "@trigger.dev/sdk";
|
||||
|
||||
export const plain = new Plain({
|
||||
id: "plain-1",
|
||||
apiKey: process.env["PLAIN_API_KEY"]!,
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "plain-playground",
|
||||
name: "Plain Playground",
|
||||
version: "0.1.1",
|
||||
integrations: {
|
||||
plain,
|
||||
},
|
||||
trigger: eventTrigger({
|
||||
name: "plain.playground",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
const { customer } = await io.plain.upsertCustomer("upsert-customer", {
|
||||
identifier: {
|
||||
emailAddress: "eric@trigger.dev",
|
||||
},
|
||||
onCreate: {
|
||||
email: {
|
||||
email: "eric@trigger.dev",
|
||||
isVerified: true,
|
||||
},
|
||||
fullName: "Eric Allam",
|
||||
externalId: "123",
|
||||
},
|
||||
onUpdate: {
|
||||
fullName: {
|
||||
value: "Eric Allam",
|
||||
},
|
||||
externalId: {
|
||||
value: "123",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await io.plain.runTask("create-issue", async (client) =>
|
||||
client.createIssue({
|
||||
customerId: "abcdefghij",
|
||||
issueTypeId: "123456",
|
||||
})
|
||||
);
|
||||
|
||||
const foundCustomer = await io.plain.getCustomerById("get-customer", {
|
||||
customerId: customer.id,
|
||||
});
|
||||
|
||||
const timelineEntry = await io.plain.upsertCustomTimelineEntry("upsert-timeline-entry", {
|
||||
customerId: customer.id,
|
||||
title: "My timeline entry",
|
||||
components: [
|
||||
{
|
||||
componentText: {
|
||||
text: `This is a nice title`,
|
||||
},
|
||||
},
|
||||
{
|
||||
componentDivider: {
|
||||
dividerSpacingSize: ComponentDividerSpacingSize.M,
|
||||
},
|
||||
},
|
||||
{
|
||||
componentText: {
|
||||
textSize: ComponentTextSize.S,
|
||||
textColor: ComponentTextColor.Muted,
|
||||
text: "External id",
|
||||
},
|
||||
},
|
||||
{
|
||||
componentText: {
|
||||
text: foundCustomer?.externalId ?? "",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import { client } from "@/trigger";
|
||||
import { Job, cronTrigger } from "@trigger.dev/sdk";
|
||||
|
||||
client.defineJob({
|
||||
id: "test-cron-schedule-5",
|
||||
name: "Test Cron Schedule 5",
|
||||
version: "0.0.1",
|
||||
logLevel: "debug",
|
||||
trigger: cronTrigger({
|
||||
cron: "*/1 * * * *",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.debug("Hello cron schedule 2a", {
|
||||
payload,
|
||||
payload2: payload,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,92 +0,0 @@
|
||||
import { client } from "@/trigger";
|
||||
import { Slack } from "@trigger.dev/slack";
|
||||
import { Job, cronTrigger, eventTrigger } from "@trigger.dev/sdk";
|
||||
|
||||
const db = {
|
||||
getKpiSummary: async (date: Date) => {
|
||||
return {
|
||||
revenue: 23_269,
|
||||
orders: 1_234,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const slack = new Slack({ id: "slack-6" });
|
||||
export const slackMissing = new Slack({ id: "slack-7" });
|
||||
|
||||
client.defineJob({
|
||||
id: "slack-kpi-summary",
|
||||
name: "Slack kpi summary",
|
||||
version: "0.1.1",
|
||||
integrations: {
|
||||
slack,
|
||||
},
|
||||
trigger: cronTrigger({
|
||||
cron: "0 9 * * *", // 9am every day (UTC)
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
const { revenue } = await db.getKpiSummary(payload.ts);
|
||||
const response = await io.slack.postMessage("Slack 📝", {
|
||||
text: `Yesterday's revenue was $${revenue}`,
|
||||
channel: "C04GWUTDC3W",
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "slack-auto-join",
|
||||
name: "Slack Auto Join",
|
||||
version: "0.1.1",
|
||||
integrations: {
|
||||
slack,
|
||||
},
|
||||
trigger: eventTrigger({
|
||||
name: "slack.auto_join",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
const response = await io.slack.postMessage("Slack 📝", {
|
||||
channel: "C05G130TH4G",
|
||||
text: "Welcome to the team, Eric!",
|
||||
blocks: [
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `Welcome to the team, Eric!`,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `I'm here to help you get started with Trigger!`,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "slack-missing-integration",
|
||||
name: "Slack with missing integration",
|
||||
version: "0.1.1",
|
||||
integrations: {
|
||||
slack: slackMissing,
|
||||
},
|
||||
trigger: eventTrigger({
|
||||
name: "missing.integration",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
const response = await io.slack.postMessage("message", {
|
||||
text: `There's no Slack connection, or is there?`,
|
||||
channel: "C04GWUTDC3W",
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -1,160 +0,0 @@
|
||||
import { Stripe } from "@trigger.dev/stripe";
|
||||
import { client } from "@/trigger";
|
||||
import { eventTrigger } from "@trigger.dev/sdk";
|
||||
import { z } from "zod";
|
||||
|
||||
const stripe = new Stripe({
|
||||
id: "stripe",
|
||||
apiKey: process.env["STRIPE_API_KEY"]!,
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "stripe-example-1",
|
||||
name: "Stripe Example 1",
|
||||
version: "0.1.0",
|
||||
trigger: eventTrigger({
|
||||
name: "stripe.example",
|
||||
schema: z.object({
|
||||
customerId: z.string(),
|
||||
source: z.string(),
|
||||
}),
|
||||
}),
|
||||
integrations: {
|
||||
stripe,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.stripe.createCharge("create-charge", {
|
||||
amount: 100,
|
||||
currency: "usd",
|
||||
source: payload.source,
|
||||
customer: payload.customerId,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "stripe-example-1",
|
||||
name: "Stripe Example 1",
|
||||
version: "0.1.0",
|
||||
trigger: eventTrigger({
|
||||
name: "stripe.example",
|
||||
schema: z.object({
|
||||
customerId: z.string(),
|
||||
source: z.string(),
|
||||
}),
|
||||
}),
|
||||
integrations: {
|
||||
stripe,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.stripe.createCharge("create-charge", {
|
||||
amount: 100,
|
||||
currency: "usd",
|
||||
source: payload.source,
|
||||
customer: payload.customerId,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "stripe-create-customer",
|
||||
name: "Stripe Create Customer",
|
||||
version: "0.1.0",
|
||||
trigger: eventTrigger({
|
||||
name: "stripe.new.customer",
|
||||
schema: z.object({
|
||||
email: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
}),
|
||||
integrations: {
|
||||
stripe,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.stripe.createCustomer("create-customer", {
|
||||
email: payload.email,
|
||||
name: payload.name,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "stripe-update-customer",
|
||||
name: "Stripe Update Customer",
|
||||
version: "0.1.0",
|
||||
trigger: eventTrigger({
|
||||
name: "stripe.update.customer",
|
||||
schema: z.object({
|
||||
customerId: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
}),
|
||||
integrations: {
|
||||
stripe,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.stripe.updateCustomer("update-customer", {
|
||||
id: payload.customerId,
|
||||
name: payload.name,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "stripe-retrieve-subscription",
|
||||
name: "Stripe Retrieve Subscription",
|
||||
version: "0.1.0",
|
||||
trigger: eventTrigger({
|
||||
name: "stripe.retrieve.subscription",
|
||||
schema: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
}),
|
||||
integrations: {
|
||||
stripe,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
const subscription = await io.stripe.retrieveSubscription("get", {
|
||||
id: payload.id,
|
||||
expand: ["customer"],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "stripe-on-price",
|
||||
name: "Stripe On Price",
|
||||
version: "0.1.0",
|
||||
trigger: stripe.onPrice({ events: ["price.created", "price.updated"] }),
|
||||
run: async (payload, io, ctx) => {
|
||||
if (ctx.event.name === "price.created") {
|
||||
await io.logger.info("price created!", { ctx });
|
||||
} else {
|
||||
await io.logger.info("price updated!", { ctx });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "stripe-on-price-created",
|
||||
name: "Stripe On Price Created",
|
||||
version: "0.1.0",
|
||||
trigger: stripe.onPriceCreated(),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("ctx", { ctx });
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "stripe-on-subscription-created",
|
||||
name: "Stripe On Subscription Created",
|
||||
version: "0.1.0",
|
||||
trigger: stripe.onCustomerSubscriptionCreated({
|
||||
filter: {
|
||||
currency: ["usd"],
|
||||
},
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.info("ctx", { ctx });
|
||||
},
|
||||
});
|
||||
@@ -1,341 +0,0 @@
|
||||
import { Database } from "@/supabase.types";
|
||||
import { client } from "@/trigger";
|
||||
import {
|
||||
type IntegrationIO,
|
||||
Job,
|
||||
eventTrigger,
|
||||
type JobPayload,
|
||||
type JobIO,
|
||||
type TriggerPayload,
|
||||
type IOWithIntegrations,
|
||||
} from "@trigger.dev/sdk";
|
||||
import { SupabaseManagement, Supabase } from "@trigger.dev/supabase";
|
||||
import { z } from "zod";
|
||||
|
||||
const supabase = new SupabaseManagement({
|
||||
id: "supabase",
|
||||
});
|
||||
|
||||
const db = supabase.db<Database>(process.env["SUPABASE_ID"]!);
|
||||
|
||||
const dbNoTypes = supabase.db(process.env["SUPABASE_ID"]!);
|
||||
|
||||
const supabaseManagementKey = new SupabaseManagement({
|
||||
id: "supabase-management-key",
|
||||
apiKey: process.env["SUPABASE_API_KEY"]!,
|
||||
});
|
||||
|
||||
const dbKey = supabase.db<Database>(process.env["SUPABASE_ID"]!);
|
||||
|
||||
const supabaseDB = new Supabase<Database>({
|
||||
id: "supabase-db",
|
||||
supabaseUrl: `https://${process.env["SUPABASE_ID"]}.supabase.co`,
|
||||
supabaseKey: process.env["SUPABASE_KEY"]!,
|
||||
});
|
||||
|
||||
async function doPlaygroundStuff(io: IntegrationIO<typeof supabaseManagementKey>, ref: string) {
|
||||
await io.getPGConfig("get-pg-config", {
|
||||
ref,
|
||||
});
|
||||
}
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-playground",
|
||||
name: "Supabase Playground",
|
||||
version: "0.1.1",
|
||||
trigger: eventTrigger({
|
||||
name: "supabase.playground",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
supabaseDB,
|
||||
supabaseManagementKey,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.supabaseManagementKey.getPGConfig("get-pg-config", {
|
||||
ref: payload.ref,
|
||||
});
|
||||
|
||||
await io.supabase.getOrganizations("get-orgs");
|
||||
await io.supabase.getProjects("get-projects");
|
||||
|
||||
await io.supabase.listFunctions("list-functions", {
|
||||
ref: payload.ref,
|
||||
});
|
||||
|
||||
await io.supabase.runQuery("run-query", {
|
||||
ref: payload.ref,
|
||||
query: "SELECT * FROM users",
|
||||
});
|
||||
|
||||
await io.supabase.getTypescriptTypes("get-typescript-types", {
|
||||
ref: payload.ref,
|
||||
});
|
||||
|
||||
const users = await io.supabaseDB.runTask(
|
||||
"fetch-users",
|
||||
async (db) => {
|
||||
const { data, error } = await db.from("users").select("*");
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return data;
|
||||
},
|
||||
{ name: "Fetch Users" }
|
||||
);
|
||||
|
||||
const newUser = await io.supabaseDB.runTask(
|
||||
"create-user",
|
||||
async (db) => {
|
||||
return await db
|
||||
.from("users")
|
||||
.insert({
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
email_address: "john@trigger.dev",
|
||||
})
|
||||
.select();
|
||||
},
|
||||
{ name: "New Users" }
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const createTodoJob = new Job(client, {
|
||||
id: "supabase-create-todo",
|
||||
name: "Supabase Create Todo",
|
||||
version: "0.1.1",
|
||||
trigger: eventTrigger({
|
||||
name: "supabase.create-todo",
|
||||
schema: z.object({
|
||||
contents: z.string(),
|
||||
user_id: z.number(),
|
||||
}),
|
||||
}),
|
||||
integrations: {
|
||||
supabaseDB,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
const newTodo = await io.supabaseDB.runTask(
|
||||
"create-todo",
|
||||
async (db) => {
|
||||
const { data, error } = await db
|
||||
.from("todos")
|
||||
.insert({
|
||||
contents: payload.contents,
|
||||
user_id: payload.user_id,
|
||||
is_complete: false,
|
||||
})
|
||||
.select();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return data;
|
||||
},
|
||||
{
|
||||
name: "Create Todo",
|
||||
properties: [{ label: "Contents", text: payload.contents }],
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
type CreateTodo = JobPayload<typeof createTodoJob>;
|
||||
type CreateTodoIO = JobIO<typeof createTodoJob>;
|
||||
|
||||
async function runCreateTodo(payload: CreateTodo, io: CreateTodoIO) {
|
||||
const newTodo = await io.supabaseDB.runTask(
|
||||
"create-todo",
|
||||
async (db) => {
|
||||
const { data, error } = await db
|
||||
.from("todos")
|
||||
.insert({
|
||||
contents: payload.contents,
|
||||
user_id: payload.user_id,
|
||||
is_complete: false,
|
||||
})
|
||||
.select();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return data;
|
||||
},
|
||||
{
|
||||
name: "Create Todo",
|
||||
properties: [{ label: "Contents", text: payload.contents }],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const createProjectTrigger = eventTrigger({
|
||||
name: "supabase.create",
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
organization_id: z.string(),
|
||||
plan: z.enum(["free", "pro"]),
|
||||
region: z.enum(["us-east-1", "us-west-1"]),
|
||||
password: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
async function doRun(
|
||||
payload: TriggerPayload<typeof createProjectTrigger>,
|
||||
io: IOWithIntegrations<{ supabase: typeof supabase }>
|
||||
) {
|
||||
await io.supabase.createProject("create-project", {
|
||||
name: payload.name,
|
||||
organization_id: payload.organization_id,
|
||||
plan: payload.plan,
|
||||
region: payload.region,
|
||||
kps_enabled: true,
|
||||
db_pass: payload.password,
|
||||
});
|
||||
}
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-create-project",
|
||||
name: "Supabase Create Project",
|
||||
version: "0.1.1",
|
||||
trigger: createProjectTrigger,
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.supabase.createProject("create-project", {
|
||||
name: payload.name,
|
||||
organization_id: payload.organization_id,
|
||||
plan: payload.plan,
|
||||
region: payload.region,
|
||||
kps_enabled: true,
|
||||
db_pass: payload.password,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-user-insert",
|
||||
name: "Supabase On User Insert",
|
||||
version: "0.1.1",
|
||||
trigger: db.onInserted({
|
||||
table: "users",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-user-insert-2",
|
||||
name: "Supabase On User Insert 2",
|
||||
version: "0.1.1",
|
||||
trigger: db.onInserted({
|
||||
table: "users",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-user-email-changed",
|
||||
name: "Supabase On User Email Changed",
|
||||
version: "0.1.1",
|
||||
trigger: db.onUpdated({
|
||||
table: "users",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-user-deleted",
|
||||
name: "Supabase On User Deleted",
|
||||
version: "0.1.1",
|
||||
trigger: db.onDeleted({
|
||||
table: "users",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-todo-created",
|
||||
name: "Supabase On TODO created",
|
||||
version: "0.1.1",
|
||||
trigger: dbKey.onInserted({
|
||||
table: "todos",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-todo-created",
|
||||
name: "Supabase On TODO created",
|
||||
version: "0.1.1",
|
||||
trigger: dbKey.onInserted({
|
||||
table: "todos",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-todo-completed",
|
||||
name: "Supabase On TODO completed",
|
||||
version: "0.1.1",
|
||||
trigger: dbKey.onUpdated({
|
||||
table: "todos",
|
||||
filter: {
|
||||
old_record: {
|
||||
is_complete: [false],
|
||||
},
|
||||
record: {
|
||||
is_complete: [true],
|
||||
},
|
||||
},
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.logger.log("Todo Completed", { payload });
|
||||
},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-tweet-created-or-deleted",
|
||||
name: "Supabase On Tweet Created or Deleted",
|
||||
version: "0.1.1",
|
||||
trigger: dbKey.onDeleted({
|
||||
schema: "public_2",
|
||||
table: "tweets",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
new Job(client, {
|
||||
id: "supabase-on-todo-created-no-types",
|
||||
name: "Supabase On TODO created",
|
||||
version: "0.1.1",
|
||||
trigger: dbNoTypes.onInserted({
|
||||
table: "todos",
|
||||
}),
|
||||
integrations: {
|
||||
supabase,
|
||||
},
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
@@ -1,84 +0,0 @@
|
||||
import { client } from "@/trigger";
|
||||
import { Typeform, events } from "@trigger.dev/typeform";
|
||||
import { Job, eventTrigger } from "@trigger.dev/sdk";
|
||||
import { DynamicTrigger } from "@trigger.dev/sdk";
|
||||
import { z } from "zod";
|
||||
|
||||
export const typeform = new Typeform({
|
||||
id: "typeform-1",
|
||||
token: process.env["TYPEFORM_API_KEY"]!,
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "typeform-playground",
|
||||
name: "Typeform Playground",
|
||||
version: "0.1.1",
|
||||
integrations: {
|
||||
typeform,
|
||||
},
|
||||
trigger: eventTrigger({
|
||||
name: "typeform.playground",
|
||||
schema: z.object({
|
||||
formId: z.string().optional(),
|
||||
}),
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
await io.typeform.listForms("list-forms");
|
||||
|
||||
if (payload.formId) {
|
||||
const form = await io.typeform.getForm("get-form", {
|
||||
uid: payload.formId,
|
||||
});
|
||||
|
||||
const listResponses = await io.typeform.listResponses("list-responses", {
|
||||
uid: payload.formId,
|
||||
pageSize: 50,
|
||||
});
|
||||
|
||||
const allResponses = await io.typeform.getAllResponses("get-all-responses", {
|
||||
uid: payload.formId,
|
||||
});
|
||||
}
|
||||
|
||||
await io.typeform.runTask(
|
||||
"create-form",
|
||||
async (client) => {
|
||||
return client.forms.create({
|
||||
data: {
|
||||
title: "My Form",
|
||||
fields: [
|
||||
{
|
||||
title: "What is your name?",
|
||||
type: "short_text",
|
||||
ref: "name",
|
||||
},
|
||||
{
|
||||
title: "What is your email?",
|
||||
type: "email",
|
||||
ref: "email",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
},
|
||||
{ name: "Create Form" }
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "typeform-webhook-2",
|
||||
name: "Typeform Webhook 2",
|
||||
version: "0.1.1",
|
||||
trigger: typeform.onFormResponse({
|
||||
uid: "QQnotGJM",
|
||||
tag: "tag4",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
const dynamicTrigger = new DynamicTrigger(client, {
|
||||
id: "typeform-dynamic-trigger",
|
||||
source: typeform.source,
|
||||
event: events.onFormResponse,
|
||||
});
|
||||
@@ -19,11 +19,6 @@ export const client = new TriggerClient({
|
||||
ioLogLocalEnabled: true,
|
||||
});
|
||||
|
||||
export const openai = new OpenAI({
|
||||
id: "openai",
|
||||
apiKey: process.env["OPENAI_API_KEY"]!,
|
||||
});
|
||||
|
||||
export const github = new Github({
|
||||
id: "github",
|
||||
octokitRequest: { fetch },
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2015",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"ES2015"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "ES2015"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@@ -19,87 +15,35 @@
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
"@trigger.dev/sdk": [
|
||||
"../../packages/trigger-sdk/src/index"
|
||||
],
|
||||
"@trigger.dev/sdk/*": [
|
||||
"../../packages/trigger-sdk/src/*"
|
||||
],
|
||||
"@trigger.dev/nextjs": [
|
||||
"../../packages/nextjs/src/index"
|
||||
],
|
||||
"@trigger.dev/nextjs/*": [
|
||||
"../../packages/nextjs/src/*"
|
||||
],
|
||||
"@trigger.dev/core": [
|
||||
"../../packages/core/src/index"
|
||||
],
|
||||
"@trigger.dev/core/*": [
|
||||
"../../packages/core/src/*"
|
||||
],
|
||||
"@trigger.dev/integration-kit": [
|
||||
"../../packages/integration-kit/src/index"
|
||||
],
|
||||
"@trigger.dev/integration-kit/*": [
|
||||
"../../packages/integration-kit/src/*"
|
||||
],
|
||||
"@trigger.dev/github": [
|
||||
"../../integrations/github/src/index"
|
||||
],
|
||||
"@trigger.dev/github/*": [
|
||||
"../../integrations/github/src/*"
|
||||
],
|
||||
"@trigger.dev/slack": [
|
||||
"../../integrations/slack/src/index"
|
||||
],
|
||||
"@trigger.dev/slack/*": [
|
||||
"../../integrations/slack/src/*"
|
||||
],
|
||||
"@trigger.dev/openai": [
|
||||
"../../integrations/openai/src/index"
|
||||
],
|
||||
"@trigger.dev/openai/*": [
|
||||
"../../integrations/openai/src/*"
|
||||
],
|
||||
"@trigger.dev/resend": [
|
||||
"../../integrations/resend/src/index"
|
||||
],
|
||||
"@trigger.dev/resend/*": [
|
||||
"../../integrations/resend/src/*"
|
||||
],
|
||||
"@trigger.dev/typeform": [
|
||||
"../../integrations/typeform/src/index"
|
||||
],
|
||||
"@trigger.dev/typeform/*": [
|
||||
"../../integrations/typeform/src/*"
|
||||
],
|
||||
"@trigger.dev/plain": [
|
||||
"../../integrations/plain/src/index"
|
||||
],
|
||||
"@trigger.dev/plain/*": [
|
||||
"../../integrations/plain/src/*"
|
||||
],
|
||||
"@trigger.dev/supabase": [
|
||||
"../../integrations/supabase/src/index"
|
||||
],
|
||||
"@trigger.dev/supabase/*": [
|
||||
"../../integrations/supabase/src/*"
|
||||
],
|
||||
"@trigger.dev/stripe": [
|
||||
"../../integrations/stripe/src/index"
|
||||
],
|
||||
"@trigger.dev/stripe/*": [
|
||||
"../../integrations/stripe/src/*"
|
||||
],
|
||||
"@trigger.dev/sendgrid": [
|
||||
"../../integrations/sendgrid/src/index"
|
||||
],
|
||||
"@trigger.dev/sendgrid/*": [
|
||||
"../../integrations/sendgrid/src/*"
|
||||
]
|
||||
"@/*": ["./src/*"],
|
||||
"@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"],
|
||||
"@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"],
|
||||
"@trigger.dev/react": ["../../packages/react/src/index"],
|
||||
"@trigger.dev/react/*": ["../../packages/react/src/*"],
|
||||
"@trigger.dev/nextjs": ["../../packages/nextjs/src/index"],
|
||||
"@trigger.dev/nextjs/*": ["../../packages/nextjs/src/*"],
|
||||
"@trigger.dev/core": ["../../packages/core/src/index"],
|
||||
"@trigger.dev/core/*": ["../../packages/core/src/*"],
|
||||
"@trigger.dev/integration-kit": ["../../packages/integration-kit/src/index"],
|
||||
"@trigger.dev/integration-kit/*": ["../../packages/integration-kit/src/*"],
|
||||
"@trigger.dev/github": ["../../integrations/github/src/index"],
|
||||
"@trigger.dev/github/*": ["../../integrations/github/src/*"],
|
||||
"@trigger.dev/slack": ["../../integrations/slack/src/index"],
|
||||
"@trigger.dev/slack/*": ["../../integrations/slack/src/*"],
|
||||
"@trigger.dev/openai": ["../../integrations/openai/src/index"],
|
||||
"@trigger.dev/openai/*": ["../../integrations/openai/src/*"],
|
||||
"@trigger.dev/resend": ["../../integrations/resend/src/index"],
|
||||
"@trigger.dev/resend/*": ["../../integrations/resend/src/*"],
|
||||
"@trigger.dev/typeform": ["../../integrations/typeform/src/index"],
|
||||
"@trigger.dev/typeform/*": ["../../integrations/typeform/src/*"],
|
||||
"@trigger.dev/plain": ["../../integrations/plain/src/index"],
|
||||
"@trigger.dev/plain/*": ["../../integrations/plain/src/*"],
|
||||
"@trigger.dev/supabase": ["../../integrations/supabase/src/index"],
|
||||
"@trigger.dev/supabase/*": ["../../integrations/supabase/src/*"],
|
||||
"@trigger.dev/stripe": ["../../integrations/stripe/src/index"],
|
||||
"@trigger.dev/stripe/*": ["../../integrations/stripe/src/*"],
|
||||
"@trigger.dev/sendgrid": ["../../integrations/sendgrid/src/index"],
|
||||
"@trigger.dev/sendgrid/*": ["../../integrations/sendgrid/src/*"]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
@@ -107,13 +51,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user