Merge branch 'main' into v3/fix-consecutive-waits

This commit is contained in:
nicktrn
2024-04-26 17:39:24 +01:00
120 changed files with 2643 additions and 441 deletions
+7
View File
@@ -0,0 +1,7 @@
---
"@trigger.dev/sdk": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
Implement task.onSuccess/onFailure and config.onSuccess/onFailure
+83
View File
@@ -0,0 +1,83 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---
Adds support for `emitDecoratorMetadata: true` and `experimentalDecorators: true` in your tsconfig using the [`@anatine/esbuild-decorators`](https://github.com/anatine/esbuildnx/tree/main/packages/esbuild-decorators) package. This allows you to use libraries like TypeORM:
```ts orm/index.ts
import "reflect-metadata";
import { DataSource } from "typeorm";
import { Entity, Column, PrimaryColumn } from "typeorm";
@Entity()
export class Photo {
@PrimaryColumn()
id!: number;
@Column()
name!: string;
@Column()
description!: string;
@Column()
filename!: string;
@Column()
views!: number;
@Column()
isPublished!: boolean;
}
export const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "postgres",
password: "postgres",
database: "v3-catalog",
entities: [Photo],
synchronize: true,
logging: false,
});
```
And then in your trigger.config.ts file you can initialize the datasource using the new `init` option:
```ts trigger.config.ts
import type { TriggerConfig } from "@trigger.dev/sdk/v3";
import { AppDataSource } from "@/trigger/orm";
export const config: TriggerConfig = {
// ... other options here
init: async (payload, { ctx }) => {
await AppDataSource.initialize();
},
};
```
Now you are ready to use this in your tasks:
```ts
import { task } from "@trigger.dev/sdk/v3";
import { AppDataSource, Photo } from "./orm";
export const taskThatUsesDecorators = task({
id: "taskThatUsesDecorators",
run: async (payload: { message: string }) => {
console.log("Creating a photo...");
const photo = new Photo();
photo.id = 2;
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.views = 1;
photo.isPublished = true;
await AppDataSource.manager.save(photo);
},
});
```
+3
View File
@@ -50,6 +50,7 @@
"chilled-hornets-move",
"clean-pianos-listen",
"clever-apes-collect",
"clever-carrots-travel",
"cool-glasses-bake",
"cuddly-feet-approve",
"dry-walls-check",
@@ -68,6 +69,7 @@
"loud-actors-remember",
"many-ligers-pump",
"mighty-camels-joke",
"mighty-flowers-train",
"nasty-jars-pump",
"new-rivers-tell",
"ninety-pets-travel",
@@ -75,6 +77,7 @@
"polite-ducks-switch",
"polite-rockets-matter",
"poor-flowers-cross",
"purple-garlics-shop",
"rare-roses-float",
"real-planets-stare",
"rich-kangaroos-unite",
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Add typescript as a dependency so the esbuild-decorator will work even when running in npx
@@ -206,3 +206,39 @@ export function TriggerDevStepV3() {
</ClientTabs>
);
}
export function TriggerLoginStepV3() {
return (
<ClientTabs defaultValue="npm">
<ClientTabsList>
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
</ClientTabsList>
<ClientTabsContent value={"npm"}>
<ClipboardField
variant="primary/medium"
iconButton
className="mb-4"
value={`npx trigger.dev@${v3PackageTag} login`}
/>
</ClientTabsContent>
<ClientTabsContent value={"pnpm"}>
<ClipboardField
variant="primary/medium"
iconButton
className="mb-4"
value={`pnpm dlx trigger.dev@${v3PackageTag} login`}
/>
</ClientTabsContent>
<ClientTabsContent value={"yarn"}>
<ClipboardField
variant="primary/medium"
iconButton
className="mb-4"
value={`yarn dlx trigger.dev@${v3PackageTag} login`}
/>
</ClientTabsContent>
</ClientTabs>
);
}
@@ -73,14 +73,7 @@ export function SpanCodePathAccessory({
>
{accessory.items.map((item, index) => (
<Fragment key={index}>
<span
className={cn(
"truncate",
index === accessory.items.length - 1 ? "text-sun-100" : "text-text-dimmed"
)}
>
{item.text}
</span>
<span className={cn("truncate", "text-text-dimmed")}>{item.text}</span>
{index < accessory.items.length - 1 && (
<span className="text-text-dimmed">
<ChevronRightIcon className="h-4 w-4" />
@@ -27,7 +27,7 @@ type TaskFunctionNameProps = {
export function TaskFunctionName({ variant, functionName, className }: TaskFunctionNameProps) {
return (
<InlineCode variant={variant} className={cn("text-sun-100", className)}>
<InlineCode variant={variant} className={cn("text-text-dimmed", className)}>
{`${functionName}()`}
</InlineCode>
);
@@ -30,6 +30,14 @@ const taskRunStatusDescriptions: Record<TaskRunStatus, string> = {
CRASHED: "Task has crashed and won't be retried",
};
export const QUEUED_STATUSES: TaskRunStatus[] = ["PENDING", "WAITING_FOR_DEPLOY"];
export const RUNNING_STATUSES: TaskRunStatus[] = [
"EXECUTING",
"RETRYING_AFTER_FAILURE",
"WAITING_TO_RESUME",
];
export function descriptionForTaskRunStatus(status: TaskRunStatus): string {
return taskRunStatusDescriptions[status];
}
@@ -36,7 +36,6 @@ type RunsTableProps = {
showJob?: boolean;
runs: RunListItem[];
isLoading?: boolean;
currentUser: User;
};
export function TaskRunsTable({
@@ -45,7 +44,6 @@ export function TaskRunsTable({
filters,
runs,
isLoading = false,
currentUser,
}: RunsTableProps) {
const organization = useOrganization();
const project = useProject();
@@ -78,15 +76,16 @@ export function TaskRunsTable({
) : (
runs.map((run) => {
const path = v3RunSpanPath(organization, project, run, { spanId: run.spanId });
const usernameForEnv =
currentUser.id !== run.environment.userId ? run.environment.userName : undefined;
return (
<TableRow key={run.id}>
<TableCell to={path}>#{run.number}</TableCell>
<TableCell to={path}>{run.taskIdentifier}</TableCell>
<TableCell to={path}>{run.version ?? ""}</TableCell>
<TableCell to={path}>
<EnvironmentLabel environment={run.environment} userName={usernameForEnv} />
<EnvironmentLabel
environment={run.environment}
userName={run.environment.userName}
/>
</TableCell>
<TableCell to={path}>
<TaskRunStatusCombo status={run.status} />
+72 -12
View File
@@ -70,23 +70,21 @@ export { Prisma };
export const prisma = singleton("prisma", getClient);
export const $replica: Omit<PrismaClient, "$transaction"> = singleton(
"replica",
() => getReplicaClient() ?? prisma
);
function getClient() {
const { DATABASE_URL } = process.env;
invariant(typeof DATABASE_URL === "string", "DATABASE_URL env var not set");
const databaseUrl = new URL(DATABASE_URL);
const databaseUrl = extendQueryParams(DATABASE_URL, {
connection_limit: env.DATABASE_CONNECTION_LIMIT.toString(),
pool_timeout: env.DATABASE_POOL_TIMEOUT.toString(),
});
// We need to add the connection_limit and pool_timeout query params to the url, in a way that works if the DATABASE_URL already has query params
const query = databaseUrl.searchParams;
query.set("connection_limit", env.DATABASE_CONNECTION_LIMIT.toString());
query.set("pool_timeout", env.DATABASE_POOL_TIMEOUT.toString());
databaseUrl.search = query.toString();
// Remove the username:password in the url and print that to the console
const urlWithoutCredentials = new URL(databaseUrl.href);
urlWithoutCredentials.password = "";
console.log(`🔌 setting up prisma client to ${urlWithoutCredentials.toString()}`);
console.log(`🔌 setting up prisma client to ${redactUrlSecrets(databaseUrl)}`);
const client = new PrismaClient({
datasources: {
@@ -134,6 +132,68 @@ function getClient() {
return client;
}
function getReplicaClient() {
if (!env.DATABASE_READ_REPLICA_URL) {
console.log(`🔌 No database replica, using the regular client`);
return;
}
const replicaUrl = extendQueryParams(env.DATABASE_READ_REPLICA_URL, {
connection_limit: env.DATABASE_CONNECTION_LIMIT.toString(),
pool_timeout: env.DATABASE_POOL_TIMEOUT.toString(),
});
console.log(`🔌 setting up read replica connection to ${redactUrlSecrets(replicaUrl)}`);
const replicaClient = new PrismaClient({
datasources: {
db: {
url: replicaUrl.href,
},
},
log: [
{
emit: "stdout",
level: "error",
},
{
emit: "stdout",
level: "info",
},
{
emit: "stdout",
level: "warn",
},
],
});
// connect eagerly
replicaClient.$connect();
console.log(`🔌 read replica connected`);
return replicaClient;
}
function extendQueryParams(hrefOrUrl: string | URL, queryParams: Record<string, string>) {
const url = new URL(hrefOrUrl);
const query = url.searchParams;
for (const [key, val] of Object.entries(queryParams)) {
query.set(key, val);
}
url.search = query.toString();
return url;
}
function redactUrlSecrets(hrefOrUrl: string | URL) {
const url = new URL(hrefOrUrl);
url.password = "";
return url.href;
}
export type { PrismaClient } from "@trigger.dev/database";
export const PrismaErrorSchema = z.object({
+1
View File
@@ -19,6 +19,7 @@ const EnvironmentSchema = z.object({
isValidDatabaseUrl,
"DIRECT_URL is invalid, for details please check the additional output above this message."
),
DATABASE_READ_REPLICA_URL: z.string().optional(),
SESSION_SECRET: z.string(),
MAGIC_LINK_SECRET: z.string(),
ENCRYPTION_KEY: z.string(),
-17
View File
@@ -10,20 +10,3 @@ export function useEnvironments(matches?: UIMatch[]) {
return project.environments;
}
export function useDevEnvironment(matches?: UIMatch[]) {
const user = useUser();
const environments = useEnvironments(matches);
if (!environments) return;
return environments.find(
(environment) => environment.type === "DEVELOPMENT" && environment.userId === user.id
);
}
export function useProdEnvironment(matches?: UIMatch[]) {
const environments = useEnvironments(matches);
if (!environments) return;
return environments.find((environment) => environment.type === "PRODUCTION");
}
@@ -87,8 +87,8 @@ export async function createOrganization(
}
export async function createEnvironment(
organization: Organization,
project: Project,
organization: Pick<Organization, "id">,
project: Pick<Project, "id">,
type: RuntimeEnvironment["type"],
member?: OrgMember,
prismaClient: PrismaClientOrTransaction = prisma
+1 -1
View File
@@ -87,7 +87,7 @@ export async function createProject(
// Create the dev and prod environments
await createEnvironment(organization, project, "PRODUCTION");
if (project.version === "V2") {
if (version === "v2") {
await createEnvironment(organization, project, "STAGING");
}
@@ -1,5 +1,6 @@
import type { RuntimeEnvironment } from "@trigger.dev/database";
import type { Prisma, RuntimeEnvironment } from "@trigger.dev/database";
import { prisma } from "~/db.server";
import { getUsername } from "~/utils/username";
export type { RuntimeEnvironment };
@@ -118,3 +119,36 @@ export async function disconnectSession(environmentId: string) {
return session;
});
}
type DisplayableInputEnvironment = Prisma.RuntimeEnvironmentGetPayload<{
select: {
id: true;
type: true;
orgMember: {
select: {
user: {
select: {
id: true;
name: true;
displayName: true;
};
};
};
};
};
}>;
export function displayableEnvironments(
environment: DisplayableInputEnvironment,
userId: string | undefined
) {
return {
id: environment.id,
type: environment.type,
userName: environment.orgMember
? environment.orgMember.user.id === userId
? undefined
: getUsername(environment.orgMember.user)
: undefined,
};
}
@@ -1,6 +1,8 @@
import { PrismaClient, prisma } from "~/db.server";
import { Project } from "~/models/project.server";
import { displayableEnvironments } from "~/models/runtimeEnvironment.server";
import { User } from "~/models/user.server";
import { sortEnvironments } from "~/services/environmentSort.server";
export class ProjectPresenter {
#prismaClient: PrismaClient;
@@ -49,7 +51,13 @@ export class ProjectPresenter {
type: true,
orgMember: {
select: {
userId: true,
user: {
select: {
id: true,
name: true,
displayName: true,
},
},
},
},
apiKey: true,
@@ -76,13 +84,12 @@ export class ProjectPresenter {
hasInactiveExternalTriggers: project._count.sources > 0,
jobCount: project._count.jobs,
httpEndpointCount: project._count.httpEndpoints,
environments: project.environments.map((environment) => ({
id: environment.id,
slug: environment.slug,
type: environment.type,
apiKey: environment.apiKey,
userId: environment.orgMember?.userId,
})),
environments: sortEnvironments(
project.environments.map((environment) => ({
...displayableEnvironments(environment, userId),
userId: environment.orgMember?.user.id,
}))
),
};
}
}
@@ -74,6 +74,7 @@ export class ApiKeysPresenter {
environmentVariableCount: environment._count.environmentVariableValues,
}))
),
hasStaging: environments.some((environment) => environment.type === "STAGING"),
};
}
}
@@ -1,10 +1,12 @@
import { Prisma, TaskRunStatus } from "@trigger.dev/database";
import { Direction } from "~/components/runs/RunStatuses";
import { sqlDatabaseSchema, PrismaClient, prisma } from "~/db.server";
import { displayableEnvironments } from "~/models/runtimeEnvironment.server";
import { getUsername } from "~/utils/username";
import { CANCELLABLE_STATUSES } from "~/v3/services/cancelTaskRun.server";
type RunListOptions = {
userId?: string;
projectSlug: string;
//filters
tasks?: string[];
@@ -34,6 +36,7 @@ export class RunListPresenter {
}
public async call({
userId,
projectSlug,
tasks,
versions,
@@ -231,12 +234,7 @@ export class RunListPresenter {
attempts: Number(run.attempts),
isReplayable: true,
isCancellable: CANCELLABLE_STATUSES.includes(run.status),
environment: {
type: environment.type,
slug: environment.slug,
userId: environment.orgMember?.user.id,
userName: getUsername(environment.orgMember?.user),
},
environment: displayableEnvironments(environment, userId),
};
}),
pagination: {
@@ -1,19 +1,42 @@
import { Prisma, TaskRunStatus, TaskTriggerSource } from "@trigger.dev/database";
import {
Prisma,
RuntimeEnvironmentType,
TaskRunStatus,
TaskTriggerSource,
} from "@trigger.dev/database";
import { PrismaClient, prisma, sqlDatabaseSchema } from "~/db.server";
import { Organization } from "~/models/organization.server";
import { Project } from "~/models/project.server";
import { User } from "~/models/user.server";
import { sortEnvironments } from "~/services/environmentSort.server";
import { logger } from "~/services/logger.server";
import { getUsername } from "~/utils/username";
import { BasePresenter } from "./basePresenter.server";
import { QUEUED_STATUSES, RUNNING_STATUSES } from "~/components/runs/v3/TaskRunStatus";
import { displayableEnvironments } from "~/models/runtimeEnvironment.server";
export type Task = Awaited<ReturnType<TaskListPresenter["call"]>>[0];
export type Task = {
slug: string;
exportName: string;
filePath: string;
createdAt: Date;
triggerSource: TaskTriggerSource;
environments: {
id: string;
type: RuntimeEnvironmentType;
userName?: string;
}[];
latestRun?: {
createdAt: Date;
status: TaskRunStatus;
};
};
export class TaskListPresenter {
#prismaClient: PrismaClient;
type Return = Awaited<ReturnType<TaskListPresenter["call"]>>;
constructor(prismaClient: PrismaClient = prisma) {
this.#prismaClient = prismaClient;
}
export type TaskActivity = Awaited<Return["activity"]>[string];
export class TaskListPresenter extends BasePresenter {
public async call({
userId,
projectSlug,
@@ -23,7 +46,7 @@ export class TaskListPresenter {
projectSlug: Project["slug"];
organizationSlug: Organization["slug"];
}) {
const project = await this.#prismaClient.project.findFirstOrThrow({
const project = await this._replica.project.findFirstOrThrow({
select: {
id: true,
environments: {
@@ -53,7 +76,7 @@ export class TaskListPresenter {
},
});
const tasks = await this.#prismaClient.$queryRaw<
const tasks = await this._replica.$queryRaw<
{
id: string;
slug: string;
@@ -64,73 +87,243 @@ export class TaskListPresenter {
triggerSource: TaskTriggerSource;
}[]
>`
SELECT DISTINCT ON(bwt.slug, bwt."runtimeEnvironmentId")
bwt.slug,
bwt.id,
bwt."exportName",
bwt."filePath",
bwt."runtimeEnvironmentId",
bwt."createdAt",
bwt."triggerSource"
FROM
${sqlDatabaseSchema}."BackgroundWorkerTask" as bwt
WHERE bwt."projectId" = ${project.id}
ORDER BY
bwt.slug,
bwt."runtimeEnvironmentId",
bwt."createdAt" DESC;`;
WITH workers AS (
SELECT DISTINCT ON ("runtimeEnvironmentId") id, "runtimeEnvironmentId", version
FROM ${sqlDatabaseSchema}."BackgroundWorker"
WHERE "runtimeEnvironmentId" IN (${Prisma.join(project.environments.map((e) => e.id))})
ORDER BY "runtimeEnvironmentId", "createdAt" DESC
)
SELECT tasks.id, slug, "filePath", "exportName", "triggerSource", tasks."runtimeEnvironmentId", tasks."createdAt"
FROM workers
JOIN ${sqlDatabaseSchema}."BackgroundWorkerTask" tasks ON tasks."workerId" = workers.id
ORDER BY slug ASC;`;
let latestRuns = [] as {
createdAt: Date;
status: TaskRunStatus;
lockedById: string;
taskIdentifier: string;
}[];
if (tasks.length > 0) {
latestRuns = await this.#prismaClient.$queryRaw<
const uniqueTaskSlugs = new Set(tasks.map((t) => t.slug));
latestRuns = await this._replica.$queryRaw<
{
createdAt: Date;
status: TaskRunStatus;
lockedById: string;
taskIdentifier: string;
}[]
>`
SELECT * FROM (
SELECT
"createdAt",
"status",
"lockedById",
ROW_NUMBER() OVER (PARTITION BY "lockedById" ORDER BY "updatedAt" DESC) AS rn
"taskIdentifier",
ROW_NUMBER() OVER (PARTITION BY "taskIdentifier" ORDER BY "updatedAt" DESC) AS rn
FROM
${sqlDatabaseSchema}."TaskRun"
WHERE
"lockedById" IN(${Prisma.join(tasks.map((t) => t.id))})
) t
WHERE rn = 1;`;
"taskIdentifier" IN(${Prisma.join(Array.from(uniqueTaskSlugs))})
AND "projectId" = ${project.id}
) t
WHERE rn = 1;`;
}
return tasks.map((task) => {
const latestRun = latestRuns.find((r) => r.lockedById === task.id);
//group by the task identifier (task.slug). Add the latestRun and add all the environments.
const outputTasks = tasks.reduce((acc, task) => {
const latestRun = latestRuns.find((r) => r.taskIdentifier === task.slug);
const environment = project.environments.find((env) => env.id === task.runtimeEnvironmentId);
if (!environment) {
throw new Error(`Environment not found for TaskRun ${task.id}`);
}
return {
...task,
environment: {
id: environment.id,
type: environment.type,
slug: environment.slug,
userId: environment.orgMember?.user.id,
userName: getUsername(environment.orgMember?.user),
},
latestRun: latestRun
? {
createdAt: latestRun.createdAt,
status: latestRun.status,
}
: undefined,
};
});
let existingTask = acc.find((t) => t.slug === task.slug);
if (!existingTask) {
existingTask = {
...task,
environments: [],
};
acc.push(existingTask);
}
existingTask.environments.push(displayableEnvironments(environment, userId));
//order the environments
existingTask.environments = sortEnvironments(existingTask.environments);
existingTask.latestRun = latestRun
? {
createdAt: latestRun.createdAt,
status: latestRun.status,
}
: undefined;
return acc;
}, [] as Task[]);
//then get the activity for each task
const activity = this.#getActivity(
outputTasks.map((t) => t.slug),
project.id
);
const runningStats = this.#getRunningStats(
outputTasks.map((t) => t.slug),
project.id
);
const durations = this.#getAverageDurations(
outputTasks.map((t) => t.slug),
project.id
);
const userEnvironment = project.environments.find((e) => e.orgMember?.user.id === userId);
const userHasTasks = userEnvironment
? outputTasks.some((t) => t.environments.some((e) => e.id === userEnvironment.id))
: false;
return { tasks: outputTasks, userHasTasks, activity, runningStats, durations };
}
async #getActivity(tasks: string[], projectId: string) {
const activity = await this._replica.$queryRaw<
{
taskIdentifier: string;
status: TaskRunStatus;
day: Date;
count: BigInt;
}[]
>`
SELECT
tr."taskIdentifier",
tr."status",
DATE(tr."createdAt") as day,
COUNT(*)
FROM
${sqlDatabaseSchema}."TaskRun" as tr
WHERE
tr."taskIdentifier" IN (${Prisma.join(tasks)})
AND tr."projectId" = ${projectId}
AND tr."createdAt" >= (current_date - interval '6 days')
GROUP BY
tr."taskIdentifier",
tr."status",
day
ORDER BY
tr."taskIdentifier" ASC,
day ASC,
tr."status" ASC;`;
//today with no time
const today = new Date();
today.setUTCHours(0, 0, 0, 0);
return activity.reduce((acc, a) => {
let existingTask = acc[a.taskIdentifier];
if (!existingTask) {
existingTask = [];
//populate the array with the past 7 days
for (let i = 6; i >= 0; i--) {
const day = new Date(today);
day.setUTCDate(today.getDate() - i);
day.setUTCHours(0, 0, 0, 0);
existingTask.push({
day: day.toISOString(),
[TaskRunStatus.COMPLETED_SUCCESSFULLY]: 0,
} as { day: string } & Record<TaskRunStatus, number>);
}
acc[a.taskIdentifier] = existingTask;
}
const dayString = a.day.toISOString();
const day = existingTask.find((d) => d.day === dayString);
if (!day) {
logger.warn(`Day not found for TaskRun`, {
day: dayString,
taskIdentifier: a.taskIdentifier,
existingTask,
});
return acc;
}
day[a.status] = Number(a.count);
return acc;
}, {} as Record<string, ({ day: string } & Record<TaskRunStatus, number>)[]>);
}
async #getRunningStats(tasks: string[], projectId: string) {
const statuses = await this._replica.$queryRaw<
{
taskIdentifier: string;
status: TaskRunStatus;
count: BigInt;
}[]
>`
SELECT
tr."taskIdentifier",
tr."status",
COUNT(*)
FROM
${sqlDatabaseSchema}."TaskRun" as tr
WHERE
tr."taskIdentifier" IN (${Prisma.join(tasks)})
AND tr."projectId" = ${projectId}
AND tr."status" IN ('PENDING', 'WAITING_FOR_DEPLOY', 'EXECUTING', 'RETRYING_AFTER_FAILURE', 'WAITING_TO_RESUME')
GROUP BY
tr."taskIdentifier",
tr."status"
ORDER BY
tr."taskIdentifier" ASC,
tr."status" ASC;`;
return statuses.reduce((acc, a) => {
let existingTask = acc[a.taskIdentifier];
if (!existingTask) {
existingTask = {
queued: 0,
running: 0,
};
acc[a.taskIdentifier] = existingTask;
}
if (QUEUED_STATUSES.includes(a.status)) {
existingTask.queued += Number(a.count);
}
if (RUNNING_STATUSES.includes(a.status)) {
existingTask.running += Number(a.count);
}
return acc;
}, {} as Record<string, { queued: number; running: number }>);
}
async #getAverageDurations(tasks: string[], projectId: string) {
const durations = await this._replica.$queryRaw<
{
taskIdentifier: string;
duration: Number;
}[]
>`
SELECT
tr."taskIdentifier",
AVG(EXTRACT(EPOCH FROM (tr."updatedAt" - tr."lockedAt"))) as duration
FROM
${sqlDatabaseSchema}."TaskRun" as tr
WHERE
tr."taskIdentifier" IN (${Prisma.join(tasks)})
AND tr."projectId" = ${projectId}
AND tr."createdAt" >= (current_date - interval '6 days')
AND tr."status" IN ('COMPLETED_SUCCESSFULLY', 'COMPLETED_WITH_ERRORS')
GROUP BY
tr."taskIdentifier";`;
return Object.fromEntries(durations.map((s) => [s.taskIdentifier, Number(s.duration)]));
}
}
@@ -1,10 +1,13 @@
import { Span, SpanKind } from "@opentelemetry/api";
import { PrismaClientOrTransaction, prisma } from "~/db.server";
import { $replica, PrismaClientOrTransaction, prisma } from "~/db.server";
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { attributesFromAuthenticatedEnv, tracer } from "../../v3/tracer.server";
export abstract class BasePresenter {
constructor(protected readonly _prisma: PrismaClientOrTransaction = prisma) {}
constructor(
protected readonly _prisma: PrismaClientOrTransaction = prisma,
protected readonly _replica: PrismaClientOrTransaction = $replica
) {}
protected async traceWithEnv<T>(
trace: string,
@@ -1,5 +1,4 @@
import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
import invariant from "tiny-invariant";
import { AstroLogo } from "~/assets/logos/AstroLogo";
import { Feedback } from "~/components/Feedback";
import { InitCommand, RunDevCommand, TriggerDevStep } from "~/components/SetupCommands";
@@ -11,18 +10,17 @@ import { Header1 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import { StepNumber } from "~/components/primitives/StepNumber";
import { useAppOrigin } from "~/hooks/useAppOrigin";
import { useDevEnvironment } from "~/hooks/useEnvironments";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
import { projectSetupPath } from "~/utils/pathBuilder";
import { useV2OnboardingApiKey } from "../_app.orgs.$organizationSlug.projects.$projectParam.setup/route";
export default function SetUpAstro() {
const organization = useOrganization();
const project = useProject();
useProjectSetupComplete();
const devEnvironment = useDevEnvironment();
invariant(devEnvironment, "Dev environment must be defined");
const { apiKey } = useV2OnboardingApiKey();
const appOrigin = useAppOrigin();
return (
@@ -67,7 +65,7 @@ export default function SetUpAstro() {
title="Run the CLI 'init' command in an existing Astro project"
/>
<StepContentContainer>
<InitCommand appOrigin={appOrigin} apiKey={devEnvironment.apiKey} />
<InitCommand appOrigin={appOrigin} apiKey={apiKey} />
<Paragraph spacing variant="small">
Youll notice a new folder in your project called 'jobs'. Weve added a very simple
@@ -1,5 +1,4 @@
import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
import invariant from "tiny-invariant";
import { ExpressLogo } from "~/assets/logos/ExpressLogo";
import { Feedback } from "~/components/Feedback";
import { RunDevCommand, TriggerDevStep } from "~/components/SetupCommands";
@@ -11,20 +10,17 @@ import { ClipboardField } from "~/components/primitives/ClipboardField";
import { Header1 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import { StepNumber } from "~/components/primitives/StepNumber";
import { useAppOrigin } from "~/hooks/useAppOrigin";
import { useDevEnvironment } from "~/hooks/useEnvironments";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
import { projectSetupPath } from "~/utils/pathBuilder";
import { useV2OnboardingApiKey } from "../_app.orgs.$organizationSlug.projects.$projectParam.setup/route";
export default function Page() {
const organization = useOrganization();
const project = useProject();
useProjectSetupComplete();
const devEnvironment = useDevEnvironment();
invariant(devEnvironment, "Dev environment must be defined");
const appOrigin = useAppOrigin();
const { apiKey } = useV2OnboardingApiKey();
return (
<div className="mx-auto max-w-3xl pt-16">
@@ -73,7 +69,7 @@ export default function Page() {
<ClipboardField
secure
className="w-fit"
value={devEnvironment.apiKey}
value={apiKey}
variant={"secondary/medium"}
icon={<Badge variant="outline">Server</Badge>}
/>
@@ -1,5 +1,4 @@
import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
import invariant from "tiny-invariant";
import { NestjsLogo } from "~/assets/logos/NestjsLogo";
import { Feedback } from "~/components/Feedback";
import { TriggerDevStep } from "~/components/SetupCommands";
@@ -11,12 +10,12 @@ import { Header1 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import { StepNumber } from "~/components/primitives/StepNumber";
import { useAppOrigin } from "~/hooks/useAppOrigin";
import { useDevEnvironment } from "~/hooks/useEnvironments";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
import { projectSetupPath } from "~/utils/pathBuilder";
import { CodeBlock } from "../../components/code/CodeBlock";
import { useV2OnboardingApiKey } from "../_app.orgs.$organizationSlug.projects.$projectParam.setup/route";
const AppModuleCode = `
import { Module } from '@nestjs/common';
@@ -114,11 +113,9 @@ export default function SetupNestJS() {
const organization = useOrganization();
const project = useProject();
useProjectSetupComplete();
const devEnvironment = useDevEnvironment();
const { apiKey } = useV2OnboardingApiKey();
const appOrigin = useAppOrigin();
invariant(devEnvironment, "devEnvironment is required");
return (
<div className="mx-auto max-w-3xl pt-16">
<div className="mb-12 grid place-items-center">
@@ -161,7 +158,7 @@ export default function SetupNestJS() {
<CodeBlock
fileName=".env"
showChrome
code={`TRIGGER_API_KEY=${devEnvironment.apiKey}\nTRIGGER_API_URL=${appOrigin}`}
code={`TRIGGER_API_KEY=${apiKey}\nTRIGGER_API_URL=${appOrigin}`}
/>
</StepContentContainer>
<StepNumber stepNumber="3" title="Add the TriggerDevModule" />
@@ -1,6 +1,5 @@
import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
import { useState } from "react";
import invariant from "tiny-invariant";
import { NextjsLogo } from "~/assets/logos/NextjsLogo";
import { Feedback } from "~/components/Feedback";
import { InitCommand, RunDevCommand, TriggerDevStep } from "~/components/SetupCommands";
@@ -21,11 +20,11 @@ import { Paragraph } from "~/components/primitives/Paragraph";
import { RadioGroup, RadioGroupItem } from "~/components/primitives/RadioButton";
import { StepNumber } from "~/components/primitives/StepNumber";
import { useAppOrigin } from "~/hooks/useAppOrigin";
import { useDevEnvironment } from "~/hooks/useEnvironments";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
import { projectSetupPath } from "~/utils/pathBuilder";
import { useV2OnboardingApiKey } from "../_app.orgs.$organizationSlug.projects.$projectParam.setup/route";
type SelectionChoices = "use-existing-project" | "create-new-next-app";
@@ -33,12 +32,10 @@ export default function SetupNextjs() {
const organization = useOrganization();
const project = useProject();
useProjectSetupComplete();
const devEnvironment = useDevEnvironment();
const { apiKey } = useV2OnboardingApiKey();
const appOrigin = useAppOrigin();
const [selectedValue, setSelectedValue] = useState<SelectionChoices | null>(null);
invariant(devEnvironment, "devEnvironment is required");
return (
<div className="mx-auto max-w-3xl pt-16">
<div className="mb-12 grid place-items-center">
@@ -152,7 +149,7 @@ export default function SetupNextjs() {
title="Run the CLI 'init' command in your new Next.js project"
/>
<StepContentContainer>
<InitCommand appOrigin={appOrigin} apiKey={devEnvironment.apiKey} />
<InitCommand appOrigin={appOrigin} apiKey={apiKey} />
<Paragraph spacing variant="small">
Youll notice a new folder in your project called 'jobs'. Weve added a very
simple example Job in <InlineCode variant="extra-small">examples.ts</InlineCode>{" "}
@@ -179,7 +176,7 @@ export default function SetupNextjs() {
title="Run the CLI 'init' command in an existing Next.js project"
/>
<StepContentContainer>
<InitCommand appOrigin={appOrigin} apiKey={devEnvironment.apiKey} />
<InitCommand appOrigin={appOrigin} apiKey={apiKey} />
<Paragraph spacing variant="small">
Youll notice a new folder in your project called 'jobs'. Weve added a very
@@ -1,5 +1,4 @@
import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
import invariant from "tiny-invariant";
import { RemixLogo } from "~/assets/logos/RemixLogo";
import { Feedback } from "~/components/Feedback";
import { InitCommand, RunDevCommand, TriggerDevStep } from "~/components/SetupCommands";
@@ -11,18 +10,17 @@ import { Header1 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import { StepNumber } from "~/components/primitives/StepNumber";
import { useAppOrigin } from "~/hooks/useAppOrigin";
import { useDevEnvironment } from "~/hooks/useEnvironments";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
import { projectSetupPath } from "~/utils/pathBuilder";
import { useV2OnboardingApiKey } from "../_app.orgs.$organizationSlug.projects.$projectParam.setup/route";
export default function SetUpRemix() {
const organization = useOrganization();
const project = useProject();
useProjectSetupComplete();
const devEnvironment = useDevEnvironment();
invariant(devEnvironment, "Dev environment must be defined");
const { apiKey } = useV2OnboardingApiKey();
const appOrigin = useAppOrigin();
return (
@@ -67,7 +65,7 @@ export default function SetUpRemix() {
title="Run the CLI 'init' command in an existing Remix project"
/>
<StepContentContainer>
<InitCommand appOrigin={appOrigin} apiKey={devEnvironment.apiKey} />
<InitCommand appOrigin={appOrigin} apiKey={apiKey} />
<Paragraph spacing variant="small">
Youll notice a new folder in your project called 'jobs'. Weve added a very simple
@@ -1,5 +1,4 @@
import { ChatBubbleLeftRightIcon, Squares2X2Icon } from "@heroicons/react/20/solid";
import invariant from "tiny-invariant";
import { SvelteKitLogo } from "~/assets/logos/SveltekitLogo";
import { Feedback } from "~/components/Feedback";
import { RunDevCommand, TriggerDevStep } from "~/components/SetupCommands";
@@ -11,18 +10,18 @@ import { ClipboardField } from "~/components/primitives/ClipboardField";
import { Header1 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import { StepNumber } from "~/components/primitives/StepNumber";
import { useDevEnvironment } from "~/hooks/useEnvironments";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useProjectSetupComplete } from "~/hooks/useProjectSetupComplete";
import { projectSetupPath } from "~/utils/pathBuilder";
import { useV2OnboardingApiKey } from "../_app.orgs.$organizationSlug.projects.$projectParam.setup/route";
export default function SetUpSveltekit() {
const organization = useOrganization();
const project = useProject();
useProjectSetupComplete();
const devEnvironment = useDevEnvironment();
invariant(devEnvironment, "Dev environment must be defined");
const { apiKey } = useV2OnboardingApiKey();
return (
<div className="mx-auto max-w-3xl pt-16">
<div className="mb-12 grid place-items-center">
@@ -70,7 +69,7 @@ export default function SetUpSveltekit() {
<ClipboardField
secure
className="w-fit"
value={devEnvironment.apiKey}
value={apiKey}
variant={"secondary/medium"}
icon={<Badge variant="outline">Server</Badge>}
/>
@@ -1,4 +1,48 @@
import { Outlet } from "@remix-run/react";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson } from "remix-typedjson";
import { prisma } from "~/db.server";
import { useTypedMatchData, useTypedMatchesData } from "~/hooks/useTypedMatchData";
import { requireUserId } from "~/services/session.server";
import { ProjectParamSchema } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
const environment = await prisma.runtimeEnvironment.findFirst({
where: {
organization: {
slug: organizationSlug,
},
project: {
slug: projectParam,
},
orgMember: {
userId,
},
},
});
if (!environment) {
throw new Response("Not Found", { status: 404 });
}
return typedjson({
apiKey: environment.apiKey,
});
};
export function useV2OnboardingApiKey() {
const routeMatch = useTypedMatchesData<typeof loader>({
id: "routes/_app.orgs.$organizationSlug.projects.$projectParam.setup",
});
if (!routeMatch) {
throw new Error("Route match not found");
}
return routeMatch;
}
export default function Page() {
return (
@@ -1,21 +1,24 @@
import { ChatBubbleLeftRightIcon } from "@heroicons/react/20/solid";
import { ChatBubbleLeftRightIcon, ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/20/solid";
import { useRevalidator } from "@remix-run/react";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { TaskRunAttemptStatus, TaskRunStatus } from "@trigger.dev/database";
import { useEffect } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import invariant from "tiny-invariant";
import { formatDuration, formatDurationMilliseconds } from "@trigger.dev/core/v3";
import { TaskRunStatus } from "@trigger.dev/database";
import { Fragment, Suspense, useEffect, useState } from "react";
import { Bar, BarChart, ResponsiveContainer, Tooltip, TooltipProps, XAxis, YAxis } from "recharts";
import { TypedAwait, typeddefer, useTypedLoaderData } from "remix-typedjson";
import { Feedback } from "~/components/Feedback";
import { InitCommandV3, TriggerDevStepV3 } from "~/components/SetupCommands";
import { InitCommandV3, TriggerDevStepV3, TriggerLoginStepV3 } from "~/components/SetupCommands";
import { StepContentContainer } from "~/components/StepContentContainer";
import { InlineCode } from "~/components/code/InlineCode";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
import { MainCenteredContainer, PageBody, PageContainer } from "~/components/layout/AppLayout";
import { Button } from "~/components/primitives/Buttons";
import { DateTime } from "~/components/primitives/DateTime";
import { Header1 } from "~/components/primitives/Headers";
import { Callout } from "~/components/primitives/Callout";
import { DateTime, formatDateTime } from "~/components/primitives/DateTime";
import { Header1, Header2, Header3 } from "~/components/primitives/Headers";
import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
import { Paragraph } from "~/components/primitives/Paragraph";
import { Spinner } from "~/components/primitives/Spinner";
import { StepNumber } from "~/components/primitives/StepNumber";
import {
Table,
@@ -28,18 +31,22 @@ import {
TableRow,
} from "~/components/primitives/Table";
import { SimpleTooltip } from "~/components/primitives/Tooltip";
import { TaskFunctionName, TaskPath } from "~/components/runs/v3/TaskPath";
import { TaskRunStatusCombo } from "~/components/runs/v3/TaskRunStatus";
import { TaskFunctionName } from "~/components/runs/v3/TaskPath";
import {
TaskRunStatusCombo,
TaskRunStatusIcon,
runStatusClassNameColor,
runStatusTitle,
} from "~/components/runs/v3/TaskRunStatus";
import {
TaskTriggerSourceIcon,
taskTriggerSourceDescription,
} from "~/components/runs/v3/TaskTriggerSource";
import { useDevEnvironment } from "~/hooks/useEnvironments";
import { useEventSource } from "~/hooks/useEventSource";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useUser } from "~/hooks/useUser";
import { TaskListPresenter } from "~/presenters/v3/TaskListPresenter.server";
import { TaskActivity, TaskListPresenter } from "~/presenters/v3/TaskListPresenter.server";
import { requireUserId } from "~/services/session.server";
import { cn } from "~/utils/cn";
import { ProjectParamSchema, v3RunsPath, v3TasksStreamingPath } from "~/utils/pathBuilder";
@@ -50,14 +57,18 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
try {
const presenter = new TaskListPresenter();
const tasks = await presenter.call({
const { tasks, userHasTasks, activity, runningStats, durations } = await presenter.call({
userId,
organizationSlug,
projectSlug: projectParam,
});
return typedjson({
return typeddefer({
tasks,
userHasTasks,
activity,
runningStats,
durations,
});
} catch (error) {
console.error(error);
@@ -71,8 +82,8 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
export default function Page() {
const organization = useOrganization();
const project = useProject();
const user = useUser();
const { tasks } = useTypedLoaderData<typeof loader>();
const { tasks, userHasTasks, activity, runningStats, durations } =
useTypedLoaderData<typeof loader>();
const hasTasks = tasks.length > 0;
//live reload the page when the tasks change
@@ -97,35 +108,30 @@ export default function Page() {
<div className={cn("grid h-full grid-cols-1 gap-4")}>
<div className="h-full">
{hasTasks ? (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-4 pb-4">
{!userHasTasks && <UserHasNoTasks />}
<Table>
<TableHeader>
<TableRow>
<TableHeaderCell>Task ID</TableHeaderCell>
<TableHeaderCell>Task</TableHeaderCell>
<TableHeaderCell>Path</TableHeaderCell>
<TableHeaderCell>Environment</TableHeaderCell>
<TableHeaderCell>Running</TableHeaderCell>
<TableHeaderCell>Queued</TableHeaderCell>
<TableHeaderCell>Activity (7d)</TableHeaderCell>
<TableHeaderCell>Avg. duration</TableHeaderCell>
<TableHeaderCell>Environments</TableHeaderCell>
<TableHeaderCell>Last run</TableHeaderCell>
<TableHeaderCell>
<div className="sr-only">Last run status</div>
</TableHeaderCell>
<TableHeaderCell>Created at</TableHeaderCell>
<TableHeaderCell hiddenLabel>Go to page</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{tasks.length > 0 ? (
tasks.map((task) => {
const usernameForEnv =
user.id !== task.environment.userId
? task.environment.userName
: undefined;
const path = v3RunsPath(organization, project, {
tasks: [task.slug],
environments: [task.environment.id],
});
return (
<TableRow key={task.id} className="group">
<TableRow key={task.slug} className="group">
<TableCell to={path}>
<div className="flex items-center gap-2">
<SimpleTooltip
@@ -135,44 +141,101 @@ export default function Page() {
<span>{task.slug}</span>
</div>
</TableCell>
<TableCell to={path}>
<TableCell to={path} className="py-0" actionClassName="py-0">
<TaskFunctionName
functionName={task.exportName}
variant="extra-small"
variant="extra-extra-small"
/>
</TableCell>
<TableCell to={path}>{task.filePath}</TableCell>
<TableCell to={path} className="p-0">
<Suspense
fallback={
<>
<Spinner color="muted" />
</>
}
>
<TypedAwait resolve={runningStats}>
{(data) => {
const taskData = data[task.slug];
return taskData?.running ?? "0";
}}
</TypedAwait>
</Suspense>
</TableCell>
<TableCell to={path} className="p-0">
<Suspense fallback={<></>}>
<TypedAwait resolve={runningStats}>
{(data) => {
const taskData = data[task.slug];
return taskData?.queued ?? "0";
}}
</TypedAwait>
</Suspense>
</TableCell>
<TableCell to={path} className="p-0" actionClassName="py-0">
<Suspense fallback={<TaskActivityBlankState />}>
<TypedAwait resolve={activity}>
{(data) => {
const taskData = data[task.slug];
return (
<>
{taskData !== undefined ? (
<div className="h-6 w-[5.125rem] rounded-sm">
<TaskActivityGraph activity={taskData} />
</div>
) : (
<TaskActivityBlankState />
)}
</>
);
}}
</TypedAwait>
</Suspense>
</TableCell>
<TableCell to={path} className="p-0">
<Suspense fallback={<></>}>
<TypedAwait resolve={durations}>
{(data) => {
const taskData = data[task.slug];
return taskData
? formatDurationMilliseconds(taskData * 1000, {
style: "short",
})
: "";
}}
</TypedAwait>
</Suspense>
</TableCell>
<TableCell to={path}>
<EnvironmentLabel
environment={task.environment}
userName={usernameForEnv}
/>
<div className="space-x-2">
{task.environments.map((environment) => (
<EnvironmentLabel
key={environment.id}
environment={environment}
userName={environment.userName}
/>
))}
</div>
</TableCell>
<TableCell to={path}>
{task.latestRun ? (
<div
className={cn(
"flex items-center gap-2",
classForTaskRunStatus(task.latestRun.status)
"flex items-center gap-1",
runStatusClassNameColor(task.latestRun.status)
)}
>
<TaskRunStatusIcon
status={task.latestRun.status}
className="h-4 w-4"
/>
<DateTime date={task.latestRun.createdAt} />
</div>
) : (
"Never run"
)}
</TableCell>
<TableCell to={path}>
{task.latestRun ? (
<TaskRunStatusCombo status={task.latestRun.status} />
) : (
""
)}
</TableCell>
<TableCell to={path}>
<DateTime date={task.createdAt} />
</TableCell>
<TableCellChevron to={path} />
</TableRow>
);
@@ -188,7 +251,9 @@ export default function Page() {
</Table>
</div>
) : (
<CreateTaskInstructions />
<MainCenteredContainer className="max-w-prose">
<CreateTaskInstructions />
</MainCenteredContainer>
)}
</div>
</div>
@@ -197,19 +262,9 @@ export default function Page() {
);
}
function classForTaskRunStatus(status: TaskRunStatus) {
switch (status) {
case "SYSTEM_FAILURE":
case "COMPLETED_WITH_ERRORS":
return "text-error";
default:
return "";
}
}
function CreateTaskInstructions() {
return (
<MainCenteredContainer className="max-w-prose">
<div>
<div className="mb-6 flex items-center justify-between border-b">
<Header1 spacing>Get setup in 3 minutes</Header1>
<div className="flex items-center gap-2">
@@ -240,6 +295,150 @@ function CreateTaskInstructions() {
<StepContentContainer>
<Paragraph>This page will automatically refresh.</Paragraph>
</StepContentContainer>
</MainCenteredContainer>
</div>
);
}
function UserHasNoTasks() {
const [open, setOpen] = useState(false);
return (
<Callout
variant="info"
cta={
<Button
variant="tertiary/small"
TrailingIcon={open ? ChevronUpIcon : ChevronDownIcon}
onClick={() => setOpen((o) => !o)}
>
{open ? "Close" : "Setup your dev environment"}
</Button>
}
>
{open ? (
<div>
<Header2 spacing>Get setup in 3 minutes</Header2>
<StepNumber stepNumber="1" title="Open up your project" className="mt-6" />
<StepContentContainer>
<Paragraph>You'll need to open a terminal at the root of your project.</Paragraph>
</StepContentContainer>
<StepNumber stepNumber="2" title="Run the CLI 'login' command" />
<StepContentContainer>
<TriggerLoginStepV3 />
</StepContentContainer>
<StepNumber stepNumber="3" title="Run the CLI 'dev' command" />
<StepContentContainer>
<TriggerDevStepV3 />
</StepContentContainer>
<StepNumber stepNumber="4" title="Waiting for tasks" displaySpinner />
<StepContentContainer>
<Paragraph>This page will automatically refresh.</Paragraph>
</StepContentContainer>
</div>
) : (
"Your DEV environment isn't setup yet."
)}
</Callout>
);
}
function TaskActivityGraph({ activity }: { activity: TaskActivity }) {
return (
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={activity}
margin={{
top: 0,
right: 0,
left: 0,
bottom: 0,
}}
width={82}
height={24}
>
<Tooltip
cursor={{ fill: "transparent" }}
content={<CustomTooltip />}
allowEscapeViewBox={{ x: true, y: true }}
wrapperStyle={{ zIndex: 1000 }}
/>
{/* The background */}
<Bar
dataKey="bg"
background={{ fill: "#212327" }}
strokeWidth={0}
stackId="a"
barSize={10}
isAnimationActive={false}
/>
<Bar dataKey="PENDING" fill="#5F6570" stackId="a" strokeWidth={0} barSize={10} />
<Bar dataKey="WAITING_FOR_DEPLOY" fill="#F59E0B" stackId="a" strokeWidth={0} barSize={10} />
<Bar dataKey="EXECUTING" fill="#3B82F6" stackId="a" strokeWidth={0} barSize={10} />
<Bar
dataKey="RETRYING_AFTER_FAILURE"
fill="#3B82F6"
stackId="a"
strokeWidth={0}
barSize={10}
/>
<Bar dataKey="WAITING_TO_RESUME" fill="#3B82F6" stackId="a" strokeWidth={0} barSize={10} />
<Bar
dataKey="COMPLETED_SUCCESSFULLY"
fill="#28BF5C"
stackId="a"
strokeWidth={0}
barSize={10}
/>
<Bar dataKey="CANCELED" fill="#5F6570" stackId="a" strokeWidth={0} barSize={10} />
<Bar
dataKey="COMPLETED_WITH_ERRORS"
fill="#F43F5E"
stackId="a"
strokeWidth={0}
barSize={10}
/>
<Bar dataKey="INTERRUPTED" fill="#F43F5E" stackId="a" strokeWidth={0} barSize={10} />
<Bar dataKey="SYSTEM_FAILURE" fill="#F43F5E" stackId="a" strokeWidth={0} barSize={10} />
<Bar dataKey="PAUSED" fill="#FCD34D" stackId="a" strokeWidth={0} barSize={10} />
<Bar dataKey="CRASHED" fill="#F43F5E" stackId="a" strokeWidth={0} barSize={10} />
</BarChart>
</ResponsiveContainer>
);
}
function TaskActivityBlankState() {
return (
<div className="flex h-6 w-[5.125rem] items-center gap-0.5 rounded-sm">
{[...Array(7)].map((_, i) => (
<div key={i} className="h-full w-2.5 bg-[#212327]" />
))}
</div>
);
}
const CustomTooltip = ({ active, payload, label }: TooltipProps<number, string>) => {
if (active && payload) {
const items = payload.map((p) => ({
status: p.dataKey as TaskRunStatus,
value: p.value,
}));
const title = payload[0].payload.day as string;
const formattedDate = formatDateTime(new Date(title), "UTC", [], false, false);
return (
<div className="rounded-sm border border-grid-bright bg-background-dimmed px-3 py-2">
<Header3 className="border-b-charcoal-650 border-b pb-2">{formattedDate}</Header3>
<div className="mt-2 grid grid-cols-[1fr_auto] gap-2 text-xs text-text-bright">
{items.map((item) => (
<Fragment key={item.status}>
<TaskRunStatusCombo status={item.status} />
<p>{item.value}</p>
</Fragment>
))}
</div>
</div>
);
}
return null;
};
@@ -1,10 +1,12 @@
import { BookOpenIcon } from "@heroicons/react/20/solid";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { Form } from "@remix-run/react";
import { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { EnvironmentLabel, environmentTitle } from "~/components/environments/EnvironmentLabel";
import { RegenerateApiKeyModal } from "~/components/environments/RegenerateApiKeyModal";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
import { LinkButton } from "~/components/primitives/Buttons";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Callout } from "~/components/primitives/Callout";
import { ClipboardField } from "~/components/primitives/ClipboardField";
import { DateTime } from "~/components/primitives/DateTime";
import { Header3 } from "~/components/primitives/Headers";
@@ -20,11 +22,15 @@ import {
TableRow,
} from "~/components/primitives/Table";
import { TextLink } from "~/components/primitives/TextLink";
import { prisma } from "~/db.server";
import { useFeatures } from "~/hooks/useFeatures";
import { useProject } from "~/hooks/useProject";
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
import { createEnvironment } from "~/models/organization.server";
import { ApiKeysPresenter } from "~/presenters/v3/ApiKeysPresenter.server";
import { requireUserId } from "~/services/session.server";
import { cn } from "~/utils/cn";
import { ProjectParamSchema, docsPath } from "~/utils/pathBuilder";
import { ProjectParamSchema, docsPath, v3ApiKeysPath } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
@@ -32,13 +38,14 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
try {
const presenter = new ApiKeysPresenter();
const { environments } = await presenter.call({
const { environments, hasStaging } = await presenter.call({
userId,
projectSlug: projectParam,
});
return typedjson({
environments,
hasStaging,
});
} catch (error) {
console.error(error);
@@ -49,9 +56,76 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
}
};
export const action = async ({ request, params }: ActionFunctionArgs) => {
const userId = await requireUserId(request);
const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
if (request.method.toUpperCase() !== "POST") {
return { status: 405, body: "Method Not Allowed" };
}
const project = await prisma.project.findUnique({
where: {
slug: params.projectParam,
organization: {
members: {
some: {
userId,
},
},
},
},
select: {
id: true,
organizationId: true,
environments: {
select: {
type: true,
},
},
},
});
if (!project) {
return redirectWithErrorMessage(
v3ApiKeysPath({ slug: organizationSlug }, { slug: projectParam }),
request,
"Project not found"
);
}
if (project.environments.some((env) => env.type === "STAGING")) {
return redirectWithErrorMessage(
v3ApiKeysPath({ slug: organizationSlug }, { slug: projectParam }),
request,
"You already have a staging environment"
);
}
const environment = await createEnvironment(
{ id: project.organizationId },
{ id: project.id },
"STAGING"
);
if (!environment) {
return redirectWithErrorMessage(
v3ApiKeysPath({ slug: organizationSlug }, { slug: projectParam }),
request,
"Failed to create staging environment"
);
}
return redirectWithSuccessMessage(
v3ApiKeysPath({ slug: organizationSlug }, { slug: projectParam }),
request,
"Staging environment created"
);
};
export default function Page() {
const { environments } = useTypedLoaderData<typeof loader>();
const project = useProject();
const { environments, hasStaging } = useTypedLoaderData<typeof loader>();
const { isManagedCloud } = useFeatures();
return (
<PageContainer>
@@ -127,6 +201,21 @@ export default function Page() {
))}
</TableBody>
</Table>
{!hasStaging && (
<Callout
variant="info"
cta={
<Form method="post">
<Button variant="tertiary/small">Enable Staging</Button>
</Form>
}
>
{isManagedCloud
? "The Staging environment will be a paid feature when we add billing. In the interim you can enable it for free."
: "You can add a Staging environment to your project."}
</Callout>
)}
</div>
</div>
</PageBody>
@@ -208,7 +208,7 @@ export default function Page() {
</InputGroup>
<Callout variant="info" className="inline-flex">
Dev environment variables specified here will be overriden by ones in your{" "}
Dev environment variables specified here will be overridden by ones in your{" "}
<InlineCode variant="extra-small">.env</InlineCode> file when running locally.
</Callout>
@@ -247,7 +247,7 @@ export default function Page() {
</Table>
<Callout variant="info" className="mb-4">
Dev environment variables specified here will be overriden by ones in your .env file
Dev environment variables specified here will be overridden by ones in your .env file
when running locally.
</Callout>
</div>
@@ -34,6 +34,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const presenter = new RunListPresenter();
const list = await presenter.call({
userId,
projectSlug: projectParam,
tasks,
versions,
@@ -88,7 +89,6 @@ export default function Page() {
filters={list.filters}
runs={list.runs}
isLoading={isLoading}
currentUser={user}
/>
<ListPagination list={list} className="mt-2 justify-end" />
</div>
@@ -247,7 +247,6 @@ export default function Page() {
}}
runs={schedule.runs}
isLoading={false}
currentUser={user}
/>
</div>
<div className="flex flex-col gap-1">
@@ -1,4 +1,5 @@
import { RuntimeEnvironmentType } from "@trigger.dev/database";
import { logger } from "./logger.server";
const environmentSortOrder: RuntimeEnvironmentType[] = [
"DEVELOPMENT",
@@ -9,12 +10,23 @@ const environmentSortOrder: RuntimeEnvironmentType[] = [
type SortType = {
type: RuntimeEnvironmentType;
userName?: string | null;
};
export function sortEnvironments<T extends SortType>(environments: T[]): T[] {
return environments.sort((a, b) => {
const aIndex = environmentSortOrder.indexOf(a.type);
const bIndex = environmentSortOrder.indexOf(b.type);
return aIndex - bIndex;
const difference = aIndex - bIndex;
if (difference === 0) {
//same environment so sort by name
const usernameA = a.userName || "";
const usernameB = b.userName || "";
return usernameA.localeCompare(usernameB);
}
return difference;
});
}
@@ -32,6 +32,14 @@ export class ContinueRunService {
throw new Error("Run is not resumable");
}
// Delete any tasks that are errored
const erroredTasks = await tx.task.findMany({
where: {
runId: runId,
status: "ERRORED",
},
});
await tx.jobRun.update({
where: { id: runId },
data: {
@@ -45,17 +53,15 @@ export class ContinueRunService {
},
});
// Delete any tasks that are errored
await tx.task.deleteMany({
where: {
runId: runId,
status: "ERRORED",
},
});
for (const task of erroredTasks) {
await tx.task.delete({
where: { id: task.id },
});
}
await ResumeRunService.enqueue(run, tx);
},
{ timeout: 10000 }
{ timeout: 30_000 }
);
}
}
+1 -1
View File
@@ -139,7 +139,7 @@
"react-resizable-panels": "^2.0.9",
"react-stately": "^3.29.1",
"react-use": "^17.4.0",
"recharts": "^2.8.0",
"recharts": "^2.12.6",
"remix-auth": "^3.6.0",
"remix-auth-email-link": "2.0.2",
"remix-auth-github": "^1.6.0",
+126 -10
View File
@@ -79,7 +79,7 @@ export const taskWithRetries = task({
maxTimeoutInMs: 30_000,
randomize: false,
},
run: async ({ payload, ctx }) => {
run: async (payload: any, { ctx }) => {
//...
},
});
@@ -99,7 +99,7 @@ export const oneAtATime = task({
queue: {
concurrencyLimit: 1,
},
run: async ({ payload, ctx }) => {
run: async (payload: any, { ctx }) => {
//...
},
});
@@ -116,7 +116,7 @@ export const heavyTask = task({
cpu: 2,
memory: 4,
},
run: async ({ payload, ctx }) => {
run: async (payload: any, { ctx }) => {
//...
},
});
@@ -124,27 +124,143 @@ export const heavyTask = task({
### `init` function
This function is called before a run attempt.
This function is called before a run attempt:
```ts /trigger/init.ts
export const taskWithInit = task({
id: "task-with-init",
init: async (payload, { ctx }) => {
//...
},
run: async (payload: any, { ctx }) => {
//...
},
});
```
You can also return data from the `init` function that will be available in the params of the `run`, `cleanup`, `onSuccess`, and `onFailure` functions.
```ts /trigger/init-return.ts
export const taskWithInitReturn = task({
id: "task-with-init-return",
init: async (payload, { ctx }) => {
return { someData: "someValue" };
},
run: async (payload: any, { ctx, init }) => {
console.log(init.someData); // "someValue"
},
});
```
### `cleanup` function
This function is called after a run attempt has succeeded or failed.
This function is called after the `run` function is executed, regardless of whether the run was successful or not. It's useful for cleaning up resources, logging, or other side effects.
```ts /trigger/cleanup.ts
export const taskWithCleanup = task({
id: "task-with-cleanup",
cleanup: async (payload, { ctx }) => {
//...
},
run: async (payload: any, { ctx }) => {
//...
},
});
```
### `middleware` function
This function is called before the `run` function, it allows you to wrap the run function with custom code. For more information [read the guide](/v3/middleware).
### `onStart` function
When a task run starts, the `onStart` function is called. It's useful for sending notifications, logging, and other side effects. This function will only be called one per run (not per retry). If you want to run code before each retry, use the `init` function.
```ts /trigger/on-start.ts
export const taskWithOnStart = task({
id: "task-with-on-start",
onStart: async (payload, { ctx }) => {
//...
},
run: async (payload: any, { ctx }) => {
//...
},
});
```
You can also define an `onStart` function in your `trigger.config.ts` file to get notified when any task starts.
```ts trigger.config.ts
import type { TriggerConfig } from "@trigger.dev/sdk/v3";
export const config: TriggerConfig = {
onStart: async (payload, { ctx }) => {
console.log("Task started", ctx.task.id);
},
};
```
### `onSuccess` function
When a task attempt succeeds, the `onSuccess` function is called. It's useful for sending notifications, logging, or other side effects.
When a task run succeeds, the `onSuccess` function is called. It's useful for sending notifications, logging, syncing state to your database, or other side effects.
<Snippet file="coming-soon-slim.mdx" />
```ts /trigger/on-success.ts
export const taskWithOnSuccess = task({
id: "task-with-on-success",
onSuccess: async (payload, output, { ctx }) => {
//...
},
run: async (payload: any, { ctx }) => {
//...
},
});
```
### `onError` function
You can also define an `onSuccess` function in your `trigger.config.ts` file to get notified when any task succeeds.
When a task attempt fails, the `onError` function is called. It's useful for sending notifications, logging, or other side effects.
```ts trigger.config.ts
import type { TriggerConfig } from "@trigger.dev/sdk/v3";
<Snippet file="coming-soon-slim.mdx" />
export const config: TriggerConfig = {
onSuccess: async (payload, output, { ctx }) => {
console.log("Task succeeded", ctx.task.id);
},
};
```
### `onFailure` function
When a task run fails, the `onFailure` function is called. It's useful for sending notifications, logging, or other side effects. It will only be executed once the task run has exhausted all its retries.
```ts /trigger/on-failure.ts
export const taskWithOnFailure = task({
id: "task-with-on-failure",
onFailure: async (payload, error, { ctx }) => {
//...
},
run: async (payload: any, { ctx }) => {
//...
},
});
```
You can also define an `onFailure` function in your `trigger.config.ts` file to get notified when any task fails.
```ts trigger.config.ts
import type { TriggerConfig } from "@trigger.dev/sdk/v3";
export const config: TriggerConfig = {
onFailure: async (payload, error, { ctx }) => {
console.log("Task failed", ctx.task.id);
},
};
```
### `handleError` functions
You can define a function that will be called when an error is thrown in the `run` function, that allows you to control how the error is handled and whether the task should be retried.
Read more about `handleError` in our [Errors and Retrying guide](/v3/errors-retrying).
## Next steps
+121 -1
View File
@@ -31,6 +31,46 @@ export const config: TriggerConfig = {
Most of the time you don't need to change anything in this file, or if you do then we will tell you when you the run the CLI command.
## Global initialization
You can run code before any task is run by adding a `init` function to your `trigger.config.ts` file.
```ts trigger.config.ts
import type { TriggerConfig } from "@trigger.dev/sdk/v3";
export const config: TriggerConfig = {
//..other stuff
init: async (payload, { ctx }) => {
console.log("I run before any task is run");
},
};
```
You'll have access to the run payload and the context object. Currently you cannot return anything from this function.
## Lifecycle functions
You can add lifecycle functions to get notified when any task starts, succeeds, or fails using `onStart`, `onSuccess` and `onFailure`:
```ts trigger.config.ts
import type { TriggerConfig } from "@trigger.dev/sdk/v3";
export const config: TriggerConfig = {
//..other stuff
onSuccess: async (payload, output, { ctx }) => {
console.log("Task succeeded", ctx.task.id);
},
onFailure: async (payload, error, { ctx }) => {
console.log("Task failed", ctx.task.id);
},
onStart: async (payload, { ctx }) => {
console.log("Task started", ctx.task.id);
},
};
```
Read more about task lifecycle functions in the [tasks overview](/v3/tasks-overview).
## Instrumentations
We use OpenTelemetry (OTEL) for our run logs. This means you get a lot of information about your tasks with no effort. But you probably want to add more information to your logs. For example, here's all the Prisma calls automatically logged:
@@ -92,7 +132,6 @@ Prisma works by generating a client from your `prisma.schema` file. This means y
<Step title="package.json postinstall `prisma generate`">
<CodeGroup>
```json default path
@@ -140,6 +179,87 @@ Prisma works by generating a client from your `prisma.schema` file. This means y
</Steps>
## TypeORM support
We support using TypeORM with Trigger. You can use decorators in your entities and then use them in your tasks. Here's an example:
```ts orm/index.ts
import "reflect-metadata";
import { DataSource } from "typeorm";
import { Entity, Column, PrimaryColumn } from "typeorm";
@Entity()
export class Photo {
@PrimaryColumn()
id!: number;
@Column()
name!: string;
@Column()
description!: string;
@Column()
filename!: string;
@Column()
views!: number;
@Column()
isPublished!: boolean;
}
export const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "postgres",
password: "postgres",
database: "my-database",
entities: [Photo],
synchronize: true,
logging: false,
});
```
And then in your trigger.config.ts file you can initialize the datasource using the `onStart` lifecycle function option:
```ts trigger.config.ts
import type { TriggerConfig } from "@trigger.dev/sdk/v3";
import { AppDataSource } from "@/trigger/orm";
export const config: TriggerConfig = {
// ... other options here
onStart: async (payload, { ctx }) => {
await AppDataSource.initialize();
},
};
```
Now you are ready to use this in your tasks:
```ts
import { task } from "@trigger.dev/sdk/v3";
import { AppDataSource, Photo } from "./orm";
export const taskThatUsesDecorators = task({
id: "task-that-uses-decorators",
run: async (payload: { message: string }) => {
console.log("Creating a photo...");
const photo = new Photo();
photo.id = 2;
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.views = 1;
photo.isPublished = true;
await AppDataSource.manager.save(photo);
},
});
```
## Troubleshooting
If you have an issue with bundling let us know on [Discord](https://trigger.dev/discord) or [via email](https://trigger.dev/contact).
+15
View File
@@ -1,5 +1,20 @@
# @trigger.dev/airtable
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.22
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
- @trigger.dev/integration-kit@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/airtable",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "Trigger.dev integration for airtable",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,8 +25,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.22",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"airtable": "^0.12.1",
"zod": "3.22.3"
},
+15
View File
@@ -1,5 +1,20 @@
# @trigger.dev/github
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.22
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
- @trigger.dev/integration-kit@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/github",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "The official GitHub integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -30,8 +30,8 @@
"@octokit/request-error": "^5.0.1",
"@octokit/webhooks": "^12.0.10",
"octokit": "^3.1.2",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.22",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"zod": "3.22.3"
},
"engines": {
+15
View File
@@ -1,5 +1,20 @@
# @trigger.dev/linear
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.22
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
- @trigger.dev/integration-kit@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/linear",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "Trigger.dev integration for @linear/sdk",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@linear/sdk": "^8.0.0",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.22",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"zod": "3.22.3"
},
"engines": {
+15
View File
@@ -1,5 +1,20 @@
# @trigger.dev/slack
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.22
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
- @trigger.dev/integration-kit@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/openai",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "The official OpenAI integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -42,8 +42,8 @@
},
"dependencies": {
"openai": "^4.16.1",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.20"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.22"
},
"engines": {
"node": ">=18.0.0"
+15
View File
@@ -1,5 +1,20 @@
# @trigger.dev/plain
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.22
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
- @trigger.dev/integration-kit@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/plain",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "The official Plain.com integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -24,8 +24,8 @@
"build:tsup": "tsup"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.22",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"@team-plain/typescript-sdk": "^2.7.0"
},
"engines": {
+15
View File
@@ -1,5 +1,20 @@
# @trigger.dev/replicate
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.22
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
- @trigger.dev/integration-kit@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/replicate",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "Trigger.dev integration for replicate",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,8 +25,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.22",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"replicate": "^0.18.1",
"zod": "3.22.3"
},
+15
View File
@@ -1,5 +1,20 @@
# @trigger.dev/resend
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.22
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
- @trigger.dev/integration-kit@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/resend",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "The official Resend.com integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -24,8 +24,8 @@
"build:tsup": "tsup"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.22",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"resend": "^2.1.0"
},
"engines": {
+15
View File
@@ -1,5 +1,20 @@
# @trigger.dev/sendgrid
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.22
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
- @trigger.dev/integration-kit@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sendgrid",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "Trigger.dev integration for @sendgrid/mail",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@sendgrid/mail": "^7.7.0",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.20"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.22"
},
"engines": {
"node": ">=16.8.0"
+15
View File
@@ -1,5 +1,20 @@
# @trigger.dev/shopify
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.22
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
- @trigger.dev/integration-kit@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/shopify",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "Trigger.dev integration for @shopify/shopify-api",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@shopify/shopify-api": "^8.0.2",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.22",
"zod": "3.22.3"
},
"engines": {
+13
View File
@@ -1,5 +1,18 @@
# @trigger.dev/slack
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/slack",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "The official Slack integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,7 +25,7 @@
},
"dependencies": {
"@slack/web-api": "^6.8.1",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"zod": "3.22.3"
},
"engines": {
+15
View File
@@ -1,5 +1,20 @@
# @trigger.dev/stripe
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.22
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
- @trigger.dev/integration-kit@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/stripe",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "Trigger.dev integration for stripe",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,8 +25,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.22",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"stripe": "^12.14.0",
"zod": "3.22.3"
},
+15
View File
@@ -1,5 +1,20 @@
# @trigger.dev/supabase
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.22
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
- @trigger.dev/integration-kit@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/supabase",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "Trigger.dev integration for @supabase/supabase-js",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@supabase/supabase-js": "^2.26.0",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.22",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"supabase-management-js": "^1.0.0",
"zod": "3.22.3"
},
+15
View File
@@ -1,5 +1,20 @@
# @trigger.dev/typeform
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.22
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
- @trigger.dev/integration-kit@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/typeform",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "The official Typeform integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -24,8 +24,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.22",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"@typeform/api-client": "^1.8.0",
"zod": "3.22.3"
},
+13
View File
@@ -1,5 +1,18 @@
# @trigger.dev/astro
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@trigger.dev/astro",
"description": "An Astro-native integration for Trigger.dev background jobs platform",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
@@ -20,7 +20,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22"
},
"devDependencies": {
"astro": "^3.0.12",
+95
View File
@@ -1,5 +1,100 @@
# trigger.dev
## 3.0.0-beta.22
### Patch Changes
- ebeb79052: Add typescript as a dependency so the esbuild-decorator will work even when running in npx
- @trigger.dev/core@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- 9491a1649: Implement task.onSuccess/onFailure and config.onSuccess/onFailure
- 9491a1649: Adds support for `emitDecoratorMetadata: true` and `experimentalDecorators: true` in your tsconfig using the [`@anatine/esbuild-decorators`](https://github.com/anatine/esbuildnx/tree/main/packages/esbuild-decorators) package. This allows you to use libraries like TypeORM:
```ts orm/index.ts
import "reflect-metadata";
import { DataSource } from "typeorm";
import { Entity, Column, PrimaryColumn } from "typeorm";
@Entity()
export class Photo {
@PrimaryColumn()
id!: number;
@Column()
name!: string;
@Column()
description!: string;
@Column()
filename!: string;
@Column()
views!: number;
@Column()
isPublished!: boolean;
}
export const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "postgres",
password: "postgres",
database: "v3-catalog",
entities: [Photo],
synchronize: true,
logging: false,
});
```
And then in your trigger.config.ts file you can initialize the datasource using the new `init` option:
```ts trigger.config.ts
import type { TriggerConfig } from "@trigger.dev/sdk/v3";
import { AppDataSource } from "@/trigger/orm";
export const config: TriggerConfig = {
// ... other options here
init: async (payload, { ctx }) => {
await AppDataSource.initialize();
},
};
```
Now you are ready to use this in your tasks:
```ts
import { task } from "@trigger.dev/sdk/v3";
import { AppDataSource, Photo } from "./orm";
export const taskThatUsesDecorators = task({
id: "taskThatUsesDecorators",
run: async (payload: { message: string }) => {
console.log("Creating a photo...");
const photo = new Photo();
photo.id = 2;
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.views = 1;
photo.isPublished = true;
await AppDataSource.manager.save(photo);
},
});
```
- Updated dependencies [9491a1649]
- Updated dependencies [9491a1649]
- @trigger.dev/core@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+5 -3
View File
@@ -1,6 +1,6 @@
{
"name": "trigger.dev",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "A Command-Line Interface for Trigger.dev (v3) projects",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -71,6 +71,7 @@
"test": "vitest"
},
"dependencies": {
"@anatine/esbuild-decorators": "^0.2.19",
"@clack/prompts": "^0.7.0",
"@depot/cli": "0.0.1-cli.2.55.0",
"@opentelemetry/api": "^1.8.0",
@@ -85,7 +86,7 @@
"@opentelemetry/sdk-trace-base": "^1.22.0",
"@opentelemetry/sdk-trace-node": "^1.22.0",
"@opentelemetry/semantic-conventions": "^1.22.0",
"@trigger.dev/core": "workspace:3.0.0-beta.20",
"@trigger.dev/core": "workspace:3.0.0-beta.22",
"@types/degit": "^2.8.3",
"chalk": "^5.2.0",
"chokidar": "^3.5.3",
@@ -124,7 +125,8 @@
"url": "^0.11.1",
"ws": "^8.12.0",
"zod": "3.22.3",
"zod-validation-error": "^1.5.0"
"zod-validation-error": "^1.5.0",
"typescript": "^5.4.0"
},
"engines": {
"node": ">=18.0.0"
+10 -5
View File
@@ -8,14 +8,13 @@ import {
flattenAttributes,
} from "@trigger.dev/core/v3";
import { recordSpanException } from "@trigger.dev/core/v3/workers";
import chalk from "chalk";
import { Command, Option as CommandOption } from "commander";
import { Metafile, build } from "esbuild";
import { execa } from "execa";
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, join, relative, posix } from "node:path";
import { dirname, join, posix, relative } from "node:path";
import { setTimeout } from "node:timers/promises";
import terminalLink from "terminal-link";
import invariant from "tiny-invariant";
@@ -32,7 +31,7 @@ import {
wrapCommandAction,
} from "../cli/common.js";
import { readConfig } from "../utilities/configFiles.js";
import { createTempDir, readJSONFile, writeJSONFile } from "../utilities/fileSystem";
import { createTempDir, writeJSONFile } from "../utilities/fileSystem";
import { printStandloneInitialBanner } from "../utilities/initialBanner.js";
import {
detectPackageNameFromImportPath,
@@ -43,6 +42,7 @@ import { logger } from "../utilities/logger.js";
import { createTaskFileImports, gatherTaskFiles } from "../utilities/taskFiles";
import { login } from "./login";
import { esbuildDecorators } from "@anatine/esbuild-decorators";
import { Glob, GlobOptions } from "glob";
import type { SetOptional } from "type-fest";
import { bundleDependenciesPlugin, workerSetupImportConfigPlugin } from "../utilities/build";
@@ -53,12 +53,12 @@ import {
parseBuildErrorStack,
parseNpmInstallError,
} from "../utilities/deployErrors";
import { safeJsonParse } from "../utilities/safeJsonParse";
import { JavascriptProject } from "../utilities/javascriptProject";
import { docs, getInTouch } from "../utilities/links";
import { cliRootPath } from "../utilities/resolveInternalFilePath";
import { safeJsonParse } from "../utilities/safeJsonParse";
import { escapeImportPath, spinner } from "../utilities/windows";
import { updateTriggerPackages } from "./update";
import { docs, getInTouch } from "../utilities/links";
const DeployCommandOptions = CommonCommandOptions.extend({
skipTypecheck: z.boolean().default(false),
@@ -1137,6 +1137,11 @@ async function compileProject(
config.tsconfigPath
),
workerSetupImportConfigPlugin(configPath),
esbuildDecorators({
tsconfig: config.tsconfigPath,
tsx: true,
force: false,
}),
],
});
+6
View File
@@ -53,6 +53,7 @@ import { findUp, pathExists } from "find-up";
import { cliRootPath } from "../utilities/resolveInternalFilePath";
import { escapeImportPath } from "../utilities/windows";
import { updateTriggerPackages } from "./update";
import { esbuildDecorators } from "@anatine/esbuild-decorators";
let apiClient: CliApiClient | undefined;
@@ -409,6 +410,11 @@ function useDev({
config.tsconfigPath
),
workerSetupImportConfigPlugin(configPath),
esbuildDecorators({
tsconfig: config.tsconfigPath,
tsx: true,
force: false,
}),
{
name: "trigger.dev v3",
setup(build) {
@@ -10,6 +10,7 @@ import { createTempDir, readJSONFileSync } from "./fileSystem.js";
import { logger } from "./logger.js";
import { findTriggerDirectories, resolveTriggerDirectories } from "./taskFiles.js";
import { build } from "esbuild";
import { esbuildDecorators } from "@anatine/esbuild-decorators";
function getGlobalConfigFolderPath() {
const configDir = xdgAppPaths("trigger").config();
@@ -172,6 +173,13 @@ export async function readConfig(
target: ["es2018", "node18"],
outfile: builtConfigFilePath,
logLevel: "silent",
plugins: [
esbuildDecorators({
cwd: absoluteDir,
tsx: false,
force: false,
}),
],
});
// import the config file
+16
View File
@@ -1,5 +1,21 @@
# create-trigger
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/core@3.0.0-beta.22
- @trigger.dev/yalt@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- Updated dependencies [9491a1649]
- @trigger.dev/core@3.0.0-beta.21
- @trigger.dev/yalt@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/cli",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "The Trigger.dev CLI",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+4
View File
@@ -1,5 +1,9 @@
# @trigger.dev/core-apps
## 3.0.0-beta.22
## 3.0.0-beta.21
## 3.0.0-beta.20
## 3.0.0-beta.19
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@trigger.dev/core-apps",
"description": "Backend core code used across apps",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"private": true,
"license": "MIT",
"main": "./dist/index.js",
+4
View File
@@ -1,5 +1,9 @@
# @trigger.dev/core-backend
## 3.0.0-beta.22
## 3.0.0-beta.21
## 3.0.0-beta.20
## 3.0.0-beta.19
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/core-backend",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "Core code used across `@trigger.dev/sdk` and Trigger.dev server",
"license": "MIT",
"main": "./dist/index.js",
+86
View File
@@ -1,5 +1,91 @@
# internal-platform
## 3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- 9491a1649: Implement task.onSuccess/onFailure and config.onSuccess/onFailure
- 9491a1649: Adds support for `emitDecoratorMetadata: true` and `experimentalDecorators: true` in your tsconfig using the [`@anatine/esbuild-decorators`](https://github.com/anatine/esbuildnx/tree/main/packages/esbuild-decorators) package. This allows you to use libraries like TypeORM:
```ts orm/index.ts
import "reflect-metadata";
import { DataSource } from "typeorm";
import { Entity, Column, PrimaryColumn } from "typeorm";
@Entity()
export class Photo {
@PrimaryColumn()
id!: number;
@Column()
name!: string;
@Column()
description!: string;
@Column()
filename!: string;
@Column()
views!: number;
@Column()
isPublished!: boolean;
}
export const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "postgres",
password: "postgres",
database: "v3-catalog",
entities: [Photo],
synchronize: true,
logging: false,
});
```
And then in your trigger.config.ts file you can initialize the datasource using the new `init` option:
```ts trigger.config.ts
import type { TriggerConfig } from "@trigger.dev/sdk/v3";
import { AppDataSource } from "@/trigger/orm";
export const config: TriggerConfig = {
// ... other options here
init: async (payload, { ctx }) => {
await AppDataSource.initialize();
},
};
```
Now you are ready to use this in your tasks:
```ts
import { task } from "@trigger.dev/sdk/v3";
import { AppDataSource, Photo } from "./orm";
export const taskThatUsesDecorators = task({
id: "taskThatUsesDecorators",
run: async (payload: { message: string }) => {
console.log("Creating a photo...");
const photo = new Photo();
photo.id = 2;
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.views = 1;
photo.isPublished = true;
await AppDataSource.manager.save(photo);
},
});
```
## 3.0.0-beta.20
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/core",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "Core code used across the Trigger.dev SDK and platform",
"license": "MIT",
"main": "./dist/index.js",
+22 -1
View File
@@ -1,3 +1,4 @@
import { FailureFnParams, InitFnParams, StartFnParams, SuccessFnParams } from ".";
import { LogLevel } from "../logger/taskLogger";
import { RetryOptions } from "../schemas";
import type { InstrumentationOption } from "@opentelemetry/instrumentation";
@@ -16,7 +17,7 @@ export interface ProjectConfig {
* List of additional files to include in your trigger.dev bundle. e.g. ["./prisma/schema.prisma"]
*
* Supports glob patterns.
*
*
* Note: The path separator for glob patterns is `/`, even on Windows!
*/
additionalFiles?: string[];
@@ -48,4 +49,24 @@ export interface ProjectConfig {
* Enable console logging while running the dev CLI. This will print out logs from console.log, console.warn, and console.error. By default all logs are sent to the trigger.dev backend, and not logged to the console.
*/
enableConsoleLogging?: boolean;
/**
* Run before a task is executed, for all tasks. This is useful for setting up any global state that is needed for all tasks.
*/
init?: (payload: unknown, params: InitFnParams) => void | Promise<void>;
/**
* onSuccess is called after the run function has successfully completed.
*/
onSuccess?: (payload: unknown, output: unknown, params: SuccessFnParams<any>) => Promise<void>;
/**
* onFailure is called after a task run has failed (meaning the run function threw an error and won't be retried anymore)
*/
onFailure?: (payload: unknown, error: unknown, params: FailureFnParams<any>) => Promise<void>;
/**
* onStart is called the first time a task is executed in a run (not before every retry)
*/
onStart?: (payload: unknown, params: StartFnParams) => Promise<void>;
}
+10 -4
View File
@@ -22,12 +22,15 @@ export type InitFnParams = Prettify<{
ctx: Context;
}>;
export type StartFnParams = Prettify<{
ctx: Context;
}>;
export type Context = TaskRunContext;
export type SuccessFnParams<TOutput, TInitOutput extends InitOutput> = RunFnParams<TInitOutput> &
Prettify<{
output: TOutput;
}>;
export type SuccessFnParams<TInitOutput extends InitOutput> = RunFnParams<TInitOutput>;
export type FailureFnParams<TInitOutput extends InitOutput> = RunFnParams<TInitOutput>;
export type HandleErrorFnParams<TInitOutput extends InitOutput> = RunFnParams<TInitOutput> &
Prettify<{
@@ -74,5 +77,8 @@ export type TaskMetadataWithFunctions = TaskMetadata & {
error: unknown,
params: HandleErrorFnParams<any>
) => HandleErrorResult;
onSuccess?: (payload: any, output: any, params: SuccessFnParams<any>) => Promise<void>;
onFailure?: (payload: any, error: unknown, params: FailureFnParams<any>) => Promise<void>;
onStart?: (payload: any, params: StartFnParams) => Promise<void>;
};
};
+198 -5
View File
@@ -90,10 +90,16 @@ export class TaskExecutor {
parsedPayload = await parsePacket(payloadPacket);
initOutput = await this.#callTaskInit(parsedPayload, ctx);
if (execution.attempt.number === 1) {
await this.#callOnStartFunctions(parsedPayload, ctx);
}
initOutput = await this.#callInitFunctions(parsedPayload, ctx);
const output = await this.#callRun(parsedPayload, ctx, initOutput);
await this.#callOnSuccessFunctions(parsedPayload, output, ctx, initOutput);
try {
const stringifiedOutput = await stringifyIO(output);
@@ -148,6 +154,15 @@ export class TaskExecutor {
recordSpanException(span, handleErrorResult.error ?? runError);
if (handleErrorResult.status !== "retry") {
await this.#callOnFailureFunctions(
parsedPayload,
handleErrorResult.error ?? runError,
ctx,
initOutput
);
}
return {
id: execution.run.id,
ok: false,
@@ -218,16 +233,194 @@ export class TaskExecutor {
return middlewareFn(payload, { ctx, next: async () => runFn(payload, { ctx, init }) });
}
async #callTaskInit(payload: unknown, ctx: TaskRunContext) {
async #callInitFunctions(payload: unknown, ctx: TaskRunContext) {
await this.#callConfigInit(payload, ctx);
const initFn = this.task.fns.init;
if (!initFn) {
return {};
}
return this._tracer.startActiveSpan("init", async (span) => {
return await initFn(payload, { ctx });
});
return this._tracer.startActiveSpan(
"init",
async (span) => {
return await initFn(payload, { ctx });
},
{
attributes: {
[SemanticInternalAttributes.STYLE_ICON]: "function",
},
}
);
}
async #callConfigInit(payload: unknown, ctx: TaskRunContext) {
const initFn = this._importedConfig?.init;
if (!initFn) {
return {};
}
return this._tracer.startActiveSpan(
"config.init",
async (span) => {
return await initFn(payload, { ctx });
},
{
attributes: {
[SemanticInternalAttributes.STYLE_ICON]: "function",
},
}
);
}
async #callOnSuccessFunctions(
payload: unknown,
output: any,
ctx: TaskRunContext,
initOutput: any
) {
await this.#callOnSuccessFunction(
this.task.fns.onSuccess,
"task.onSuccess",
payload,
output,
ctx,
initOutput
);
await this.#callOnSuccessFunction(
this._importedConfig?.onSuccess,
"config.onSuccess",
payload,
output,
ctx,
initOutput
);
}
async #callOnSuccessFunction(
onSuccessFn: TaskMetadataWithFunctions["fns"]["onSuccess"],
name: string,
payload: unknown,
output: any,
ctx: TaskRunContext,
initOutput: any
) {
if (!onSuccessFn) {
return;
}
try {
await this._tracer.startActiveSpan(
name,
async (span) => {
return await onSuccessFn(payload, output, { ctx, init: initOutput });
},
{
attributes: {
[SemanticInternalAttributes.STYLE_ICON]: "function",
},
}
);
} catch {
// Ignore errors from onSuccess functions
}
}
async #callOnFailureFunctions(
payload: unknown,
error: unknown,
ctx: TaskRunContext,
initOutput: any
) {
await this.#callOnFailureFunction(
this.task.fns.onFailure,
"task.onFailure",
payload,
error,
ctx,
initOutput
);
await this.#callOnFailureFunction(
this._importedConfig?.onFailure,
"config.onFailure",
payload,
error,
ctx,
initOutput
);
}
async #callOnFailureFunction(
onFailureFn: TaskMetadataWithFunctions["fns"]["onFailure"],
name: string,
payload: unknown,
error: unknown,
ctx: TaskRunContext,
initOutput: any
) {
if (!onFailureFn) {
return;
}
try {
return await this._tracer.startActiveSpan(
name,
async (span) => {
return await onFailureFn(payload, error, { ctx, init: initOutput });
},
{
attributes: {
[SemanticInternalAttributes.STYLE_ICON]: "function",
},
}
);
} catch (e) {
// Ignore errors from onFailure functions
}
}
async #callOnStartFunctions(payload: unknown, ctx: TaskRunContext) {
await this.#callOnStartFunction(
this._importedConfig?.onStart,
"config.onStart",
payload,
ctx,
{}
);
await this.#callOnStartFunction(this.task.fns.onStart, "task.onStart", payload, ctx, {});
}
async #callOnStartFunction(
onStartFn: TaskMetadataWithFunctions["fns"]["onStart"],
name: string,
payload: unknown,
ctx: TaskRunContext,
initOutput: any
) {
if (!onStartFn) {
return;
}
try {
await this._tracer.startActiveSpan(
name,
async (span) => {
return await onStartFn(payload, { ctx });
},
{
attributes: {
[SemanticInternalAttributes.STYLE_ICON]: "function",
},
}
);
} catch {
// Ignore errors from onStart functions
}
}
async #callTaskCleanup(payload: unknown, ctx: TaskRunContext, init: unknown) {
@@ -0,0 +1,5 @@
-- DropForeignKey
ALTER TABLE "JobRunExecution" DROP CONSTRAINT "JobRunExecution_resumeTaskId_fkey";
-- AddForeignKey
ALTER TABLE "JobRunExecution" ADD CONSTRAINT "JobRunExecution_resumeTaskId_fkey" FOREIGN KEY ("resumeTaskId") REFERENCES "Task"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX "TaskRun_projectId_createdAt_taskIdentifier_idx" ON "TaskRun"("projectId", "createdAt", "taskIdentifier");
+3 -1
View File
@@ -991,7 +991,7 @@ model JobRunExecution {
reason JobRunExecutionReason @default(EXECUTE_JOB)
status JobRunExecutionStatus @default(PENDING)
resumeTask Task? @relation(fields: [resumeTaskId], references: [id], onDelete: Cascade, onUpdate: Cascade)
resumeTask Task? @relation(fields: [resumeTaskId], references: [id], onDelete: SetNull, onUpdate: Cascade)
resumeTaskId String?
graphileJobId String?
@@ -1636,6 +1636,8 @@ model TaskRun {
scheduleId String?
@@unique([runtimeEnvironmentId, idempotencyKey])
// Task activity graph
@@index([projectId, createdAt, taskIdentifier])
}
enum TaskRunStatus {
+4
View File
@@ -1,5 +1,9 @@
# @trigger.dev/eslint-plugin
## 3.0.0-beta.22
## 3.0.0-beta.21
## 3.0.0-beta.20
## 3.0.0-beta.19
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/eslint-plugin",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "ESLint plugin with trigger.dev best practices",
"keywords": [
"eslint",
+13
View File
@@ -1,5 +1,18 @@
# @trigger.dev/express
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/express",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "Official Express adapter for Trigger.dev",
"license": "MIT",
"main": "./dist/index.js",
@@ -23,7 +23,7 @@
"./package.json": "./package.json"
},
"devDependencies": {
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"@trigger.dev/tsconfig": "workspace:*",
"@types/debug": "^4.1.7",
"@types/express": "^4.17.13",
@@ -39,7 +39,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22"
},
"dependencies": {
"debug": "^4.3.4",
+13
View File
@@ -1,5 +1,18 @@
# @trigger.dev/hono
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/hono",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "A Trigger.dev adapter for Hono.dev",
"license": "MIT",
"main": "./dist/index.js",
@@ -32,7 +32,7 @@
},
"peerDependencies": {
"hono": "3.x",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22"
},
"devDependencies": {
"@trigger.dev/tsconfig": "workspace:*",
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/integration-kit
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/core@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- Updated dependencies [9491a1649]
- @trigger.dev/core@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/integration-kit",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "Trigger.dev Integration Kit has helpers to make creating integrations easier",
"license": "MIT",
"main": "./dist/index.js",
+13
View File
@@ -1,5 +1,18 @@
# @trigger.dev/nestjs
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/nestjs",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "Official NestJS adapter for Trigger.dev",
"license": "MIT",
"main": "./dist/index.js",
@@ -23,7 +23,7 @@
"./package.json": "./package.json"
},
"devDependencies": {
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"@trigger.dev/tsconfig": "workspace:*",
"@types/debug": "^4.1.7",
"@types/express": "^4.17.13",
@@ -41,7 +41,7 @@
},
"peerDependencies": {
"@nestjs/common": ">=10.0.0",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22"
},
"dependencies": {
"@nestjs/common": "^10.2.4",
+13
View File
@@ -1,5 +1,18 @@
# @trigger.dev/nextjs
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- @trigger.dev/sdk@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/nextjs",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "Trigger.dev Next.js integration",
"license": "MIT",
"main": "./dist/index.js",
@@ -41,7 +41,7 @@
"build:tsup": "tsup"
},
"peerDependencies": {
"@trigger.dev/sdk": "workspace:^3.0.0-beta.20",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.22",
"next": ">=12.0.0"
},
"dependencies": {
+4
View File
@@ -1,5 +1,9 @@
# @trigger.dev/otlp-importer
## 3.0.0-beta.22
## 3.0.0-beta.21
## 3.0.0-beta.20
## 3.0.0-beta.19
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/otlp-importer",
"version": "3.0.0-beta.20",
"version": "3.0.0-beta.22",
"description": "OpenTelemetry OTLP Importer for Node.js written in TypeScript",
"license": "MIT",
"main": "./dist/index.js",
+14
View File
@@ -1,5 +1,19 @@
# @trigger.dev/react
## 3.0.0-beta.22
### Patch Changes
- @trigger.dev/core@3.0.0-beta.22
## 3.0.0-beta.21
### Patch Changes
- Updated dependencies [9491a1649]
- Updated dependencies [9491a1649]
- @trigger.dev/core@3.0.0-beta.21
## 3.0.0-beta.20
### Patch Changes

Some files were not shown because too many files have changed in this diff Show More