From 38965d8921c4e99bda3a3aab3439325cadd131ed Mon Sep 17 00:00:00 2001 From: Jacob Paris Date: Sat, 18 May 2024 01:31:22 -0600 Subject: [PATCH 01/27] fix: vite cant resolve real values from prisma, only types are ok --- .../app/components/runs/v3/RunFilters.tsx | 9 ++-- .../runs/v3/TaskRunAttemptStatus.tsx | 10 +++- apps/webapp/app/models/api-key.server.ts | 9 +++- apps/webapp/app/models/taskRun.server.ts | 49 +++++++++++++++---- .../presenters/v3/TaskListPresenter.server.ts | 36 ++++++++++---- ...endpointSlug.index.$indexHookIdentifier.ts | 11 ++++- .../endpoints/createEndpoint.server.ts | 8 ++- .../validateCreateEndpoint.server.ts | 8 ++- .../events/cancelRunsForEvent.server.ts | 28 +++++++++-- .../services/jobs/cancelRunsForJob.server.ts | 21 +++++++- .../sources/handleHttpSource.server.ts | 10 ++-- .../sources/handleWebhookRequest.server.ts | 7 ++- 12 files changed, 166 insertions(+), 40 deletions(-) diff --git a/apps/webapp/app/components/runs/v3/RunFilters.tsx b/apps/webapp/app/components/runs/v3/RunFilters.tsx index 8eac8b5ec..f3e153e3b 100644 --- a/apps/webapp/app/components/runs/v3/RunFilters.tsx +++ b/apps/webapp/app/components/runs/v3/RunFilters.tsx @@ -1,9 +1,11 @@ import * as Ariakit from "@ariakit/react"; import { CalendarIcon, CpuChipIcon, XMarkIcon } from "@heroicons/react/20/solid"; import { Form } from "@remix-run/react"; -import { RuntimeEnvironment, TaskRunStatus, TaskTriggerSource } from "@trigger.dev/database"; +import type { RuntimeEnvironment, TaskTriggerSource } from "@trigger.dev/database"; +import { TaskRunStatus } from "@trigger.dev/database"; import { ListFilterIcon } from "lucide-react"; -import { ReactNode, startTransition, useCallback, useMemo, useState } from "react"; +import type { ReactNode} from "react"; +import { startTransition, useCallback, useMemo, useState } from "react"; import { z } from "zod"; import { TaskIcon } from "~/assets/icons/TaskIcon"; import { EnvironmentLabel, environmentTitle } from "~/components/environments/EnvironmentLabel"; @@ -37,7 +39,8 @@ import { } from "./TaskRunStatus"; import { TaskTriggerSourceIcon } from "./TaskTriggerSource"; -export const TaskAttemptStatus = z.nativeEnum(TaskRunStatus); +const TaskRunStatus = ["PENDING", "WAITING_FOR_DEPLOY", "EXECUTING", "WAITING_TO_RESUME", "RETRYING_AFTER_FAILURE", "PAUSED", "CANCELED", "INTERRUPTED", "COMPLETED_SUCCESSFULLY", "COMPLETED_WITH_ERRORS", "SYSTEM_FAILURE", "CRASHED"] as const +export const TaskAttemptStatus = z.enum(TaskRunStatus) export const TaskRunListSearchFilters = z.object({ cursor: z.string().optional(), diff --git a/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx b/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx index 557b62e08..2ddb6f93e 100644 --- a/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx +++ b/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx @@ -7,14 +7,20 @@ import { XCircleIcon, } from "@heroicons/react/20/solid"; import type { TaskRunAttemptStatus as TaskRunAttemptStatusType } from "@trigger.dev/database"; -import { TaskRunAttemptStatus } from "@trigger.dev/database"; import assertNever from "assert-never"; import { SnowflakeIcon } from "lucide-react"; import { Spinner } from "~/components/primitives/Spinner"; import { cn } from "~/utils/cn"; export const allTaskRunAttemptStatuses = Object.values( - TaskRunAttemptStatus + { + PENDING: "PENDING", + EXECUTING: "EXECUTING", + PAUSED: "PAUSED", + FAILED: "FAILED", + CANCELED: "CANCELED", + COMPLETED: "COMPLETED", +} ) as TaskRunAttemptStatusType[]; export type ExtendedTaskAttemptStatus = (typeof allTaskRunAttemptStatuses)[number] | "ENQUEUED"; diff --git a/apps/webapp/app/models/api-key.server.ts b/apps/webapp/app/models/api-key.server.ts index a54fdae9a..417afdd94 100644 --- a/apps/webapp/app/models/api-key.server.ts +++ b/apps/webapp/app/models/api-key.server.ts @@ -1,7 +1,14 @@ -import { RuntimeEnvironmentType, type RuntimeEnvironment } from "@trigger.dev/database"; +import type { RuntimeEnvironment } from "@trigger.dev/database"; import { prisma } from "~/db.server"; import { customAlphabet } from "nanoid"; +const RuntimeEnvironmentType = { + PRODUCTION: "PRODUCTION", + STAGING: "STAGING", + DEVELOPMENT: "DEVELOPMENT", + PREVIEW: "PREVIEW" +} as const + const apiKeyId = customAlphabet( "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 12 diff --git a/apps/webapp/app/models/taskRun.server.ts b/apps/webapp/app/models/taskRun.server.ts index 0e2eccbf1..f18ee5e66 100644 --- a/apps/webapp/app/models/taskRun.server.ts +++ b/apps/webapp/app/models/taskRun.server.ts @@ -1,19 +1,49 @@ -import { - TaskRunError, +import type { TaskRunExecutionResult, TaskRunFailedExecutionResult, - TaskRunSuccessfulExecutionResult, + TaskRunSuccessfulExecutionResult} from "@trigger.dev/core/v3"; +import { + TaskRunError } from "@trigger.dev/core/v3"; import { - BatchTaskRunItemStatus, - TaskRun, - TaskRunAttempt, - TaskRunAttemptStatus, - TaskRunStatus, + type TaskRun, + type TaskRunAttempt, } from "@trigger.dev/database"; + import { assertNever } from "assert-never"; import { logger } from "~/services/logger.server"; +const BatchTaskRunItemStatus = { + PENDING: "PENDING", + FAILED: "FAILED", + CANCELED: "CANCELED", + COMPLETED: "COMPLETED", +} as const + +const TaskRunAttemptStatus= { + PENDING: "PENDING", + EXECUTING: "EXECUTING", + PAUSED: "PAUSED", + FAILED: "FAILED", + CANCELED: "CANCELED", + COMPLETED: "COMPLETED", +} as const + +const TaskRunStatus = { + PENDING: 'PENDING', + WAITING_FOR_DEPLOY: 'WAITING_FOR_DEPLOY', + EXECUTING: 'EXECUTING', + WAITING_TO_RESUME: 'WAITING_TO_RESUME', + RETRYING_AFTER_FAILURE: 'RETRYING_AFTER_FAILURE', + PAUSED: 'PAUSED', + CANCELED: 'CANCELED', + INTERRUPTED: 'INTERRUPTED', + COMPLETED_SUCCESSFULLY: 'COMPLETED_SUCCESSFULLY', + COMPLETED_WITH_ERRORS: 'COMPLETED_WITH_ERRORS', + SYSTEM_FAILURE: 'SYSTEM_FAILURE', + CRASHED: 'CRASHED' +} as const + const SUCCESSFUL_STATUSES = [TaskRunStatus.COMPLETED_SUCCESSFULLY]; const FAILURE_STATUSES = [ TaskRunStatus.CANCELED, @@ -104,7 +134,7 @@ export function executionResultForTaskRun( } } -export function batchTaskRunItemStatusForRunStatus(status: TaskRunStatus): BatchTaskRunItemStatus { +export function batchTaskRunItemStatusForRunStatus(status: keyof typeof TaskRunStatus): keyof typeof BatchTaskRunItemStatus { switch (status) { case TaskRunStatus.COMPLETED_SUCCESSFULLY: return BatchTaskRunItemStatus.COMPLETED; @@ -113,7 +143,6 @@ export function batchTaskRunItemStatusForRunStatus(status: TaskRunStatus): Batch case TaskRunStatus.COMPLETED_WITH_ERRORS: case TaskRunStatus.SYSTEM_FAILURE: case TaskRunStatus.CRASHED: - case TaskRunStatus.COMPLETED_WITH_ERRORS: return BatchTaskRunItemStatus.FAILED; case TaskRunStatus.PENDING: case TaskRunStatus.WAITING_FOR_DEPLOY: diff --git a/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts b/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts index c0a8935ba..dd83c9ca2 100644 --- a/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts @@ -1,19 +1,37 @@ -import { - Prisma, +import type { RuntimeEnvironmentType, - TaskRunStatus, - TaskTriggerSource, + TaskTriggerSource +} from "@trigger.dev/database"; +import { + Prisma } from "@trigger.dev/database"; import { QUEUED_STATUSES, RUNNING_STATUSES } from "~/components/runs/v3/TaskRunStatus"; import { sqlDatabaseSchema } from "~/db.server"; -import { Organization } from "~/models/organization.server"; -import { Project } from "~/models/project.server"; +import type { Organization } from "~/models/organization.server"; +import type { Project } from "~/models/project.server"; import { displayableEnvironments } from "~/models/runtimeEnvironment.server"; -import { User } from "~/models/user.server"; +import type { User } from "~/models/user.server"; import { sortEnvironments } from "~/utils/environmentSort"; import { logger } from "~/services/logger.server"; import { BasePresenter } from "./basePresenter.server"; +const TaskRunStatus = { + PENDING: 'PENDING', + WAITING_FOR_DEPLOY: 'WAITING_FOR_DEPLOY', + EXECUTING: 'EXECUTING', + WAITING_TO_RESUME: 'WAITING_TO_RESUME', + RETRYING_AFTER_FAILURE: 'RETRYING_AFTER_FAILURE', + PAUSED: 'PAUSED', + CANCELED: 'CANCELED', + INTERRUPTED: 'INTERRUPTED', + COMPLETED_SUCCESSFULLY: 'COMPLETED_SUCCESSFULLY', + COMPLETED_WITH_ERRORS: 'COMPLETED_WITH_ERRORS', + SYSTEM_FAILURE: 'SYSTEM_FAILURE', + CRASHED: 'CRASHED' +} as const; + +type TaskRunStatus = typeof TaskRunStatus[keyof typeof TaskRunStatus] + export type Task = { slug: string; exportName: string; @@ -150,7 +168,7 @@ export class TaskListPresenter extends BasePresenter { const activity = await this._replica.$queryRaw< { taskIdentifier: string; - status: TaskRunStatus; + status: typeof TaskRunStatus; day: Date; count: BigInt; }[] @@ -193,7 +211,7 @@ export class TaskListPresenter extends BasePresenter { existingTask.push({ day: day.toISOString(), [TaskRunStatus.COMPLETED_SUCCESSFULLY]: 0, - } as { day: string } & Record); + } as unknown as { day: string } & Record); } acc[a.taskIdentifier] = existingTask; diff --git a/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts b/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts index 53e770563..41d767920 100644 --- a/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts +++ b/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts @@ -1,11 +1,18 @@ import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime"; -import { RuntimeEnvironmentType } from "@trigger.dev/database"; import { z } from "zod"; -import { $transaction, PrismaClient, prisma } from "~/db.server"; +import type { PrismaClient} from "~/db.server"; +import { $transaction, prisma } from "~/db.server"; import { logger } from "~/services/logger.server"; import { workerQueue } from "~/services/worker.server"; import { safeJsonParse } from "~/utils/json"; +const RuntimeEnvironmentType= { + PRODUCTION: "PRODUCTION", + STAGING: "STAGING", + DEVELOPMENT: "DEVELOPMENT", + PREVIEW: "PREVIEW", +} as const + const ParamsSchema = z.object({ environmentId: z.string(), endpointSlug: z.string(), diff --git a/apps/webapp/app/services/endpoints/createEndpoint.server.ts b/apps/webapp/app/services/endpoints/createEndpoint.server.ts index a77bb844f..512e6a49a 100644 --- a/apps/webapp/app/services/endpoints/createEndpoint.server.ts +++ b/apps/webapp/app/services/endpoints/createEndpoint.server.ts @@ -4,7 +4,13 @@ import { AuthenticatedEnvironment } from "../apiAuth.server"; import { EndpointApi } from "../endpointApi.server"; import { workerQueue } from "../worker.server"; import { env } from "~/env.server"; -import { RuntimeEnvironmentType } from "@trigger.dev/database"; + +const RuntimeEnvironmentType = { + PRODUCTION: "PRODUCTION", + STAGING: "STAGING", + DEVELOPMENT: "DEVELOPMENT", + PREVIEW: "PREVIEW", +} as const const indexingHookIdentifier = customAlphabet("0123456789abcdefghijklmnopqrstuvxyz", 10); diff --git a/apps/webapp/app/services/endpoints/validateCreateEndpoint.server.ts b/apps/webapp/app/services/endpoints/validateCreateEndpoint.server.ts index 84baa4c22..b0d14cd4d 100644 --- a/apps/webapp/app/services/endpoints/validateCreateEndpoint.server.ts +++ b/apps/webapp/app/services/endpoints/validateCreateEndpoint.server.ts @@ -5,7 +5,13 @@ import { AuthenticatedEnvironment } from "../apiAuth.server"; import { workerQueue } from "../worker.server"; import { CreateEndpointError } from "./createEndpoint.server"; import { EndpointApi } from "../endpointApi.server"; -import { RuntimeEnvironmentType } from "@trigger.dev/database"; + +const RuntimeEnvironmentType = { + PRODUCTION: "PRODUCTION", + STAGING: "STAGING", + DEVELOPMENT: "DEVELOPMENT", + PREVIEW: "PREVIEW", +} as const const indexingHookIdentifier = customAlphabet("0123456789abcdefghijklmnopqrstuvxyz", 10); diff --git a/apps/webapp/app/services/events/cancelRunsForEvent.server.ts b/apps/webapp/app/services/events/cancelRunsForEvent.server.ts index dca8b3228..32aa6697f 100644 --- a/apps/webapp/app/services/events/cancelRunsForEvent.server.ts +++ b/apps/webapp/app/services/events/cancelRunsForEvent.server.ts @@ -1,11 +1,29 @@ -import { $transaction, PrismaClient, prisma } from "~/db.server"; -import { AuthenticatedEnvironment } from "../apiAuth.server"; -import { JobRunStatus } from "@trigger.dev/database"; +import type { PrismaClient} from "~/db.server"; +import { $transaction, prisma } from "~/db.server"; +import type { AuthenticatedEnvironment } from "../apiAuth.server"; import { CancelRunService } from "../runs/cancelRun.server"; import { logger } from "../logger.server"; -import { CancelRunsForEvent } from "@trigger.dev/core"; +import type { CancelRunsForEvent } from "@trigger.dev/core"; -const CANCELLABLE_JOB_RUN_STATUS: JobRunStatus[] = [ +export const JobRunStatus = { + PENDING: 'PENDING', + QUEUED: 'QUEUED', + WAITING_ON_CONNECTIONS: 'WAITING_ON_CONNECTIONS', + PREPROCESSING: 'PREPROCESSING', + STARTED: 'STARTED', + EXECUTING: 'EXECUTING', + WAITING_TO_CONTINUE: 'WAITING_TO_CONTINUE', + WAITING_TO_EXECUTE: 'WAITING_TO_EXECUTE', + SUCCESS: 'SUCCESS', + FAILURE: 'FAILURE', + TIMED_OUT: 'TIMED_OUT', + ABORTED: 'ABORTED', + CANCELED: 'CANCELED', + UNRESOLVED_AUTH: 'UNRESOLVED_AUTH', + INVALID_PAYLOAD: 'INVALID_PAYLOAD' +} as const; + +const CANCELLABLE_JOB_RUN_STATUS: Array = [ JobRunStatus.PENDING, JobRunStatus.QUEUED, JobRunStatus.WAITING_ON_CONNECTIONS, diff --git a/apps/webapp/app/services/jobs/cancelRunsForJob.server.ts b/apps/webapp/app/services/jobs/cancelRunsForJob.server.ts index ff6e112d7..a3e9a689b 100644 --- a/apps/webapp/app/services/jobs/cancelRunsForJob.server.ts +++ b/apps/webapp/app/services/jobs/cancelRunsForJob.server.ts @@ -1,11 +1,28 @@ import { $transaction, PrismaClient, prisma } from "~/db.server"; import { AuthenticatedEnvironment } from "../apiAuth.server"; -import { JobRunStatus } from "@trigger.dev/database"; import { CancelRunService } from "../runs/cancelRun.server"; import { logger } from "../logger.server"; import { CancelRunsForJob } from "@trigger.dev/core"; -const CANCELLABLE_JOB_RUN_STATUS: JobRunStatus[] = [ +export const JobRunStatus = { + PENDING: 'PENDING', + QUEUED: 'QUEUED', + WAITING_ON_CONNECTIONS: 'WAITING_ON_CONNECTIONS', + PREPROCESSING: 'PREPROCESSING', + STARTED: 'STARTED', + EXECUTING: 'EXECUTING', + WAITING_TO_CONTINUE: 'WAITING_TO_CONTINUE', + WAITING_TO_EXECUTE: 'WAITING_TO_EXECUTE', + SUCCESS: 'SUCCESS', + FAILURE: 'FAILURE', + TIMED_OUT: 'TIMED_OUT', + ABORTED: 'ABORTED', + CANCELED: 'CANCELED', + UNRESOLVED_AUTH: 'UNRESOLVED_AUTH', + INVALID_PAYLOAD: 'INVALID_PAYLOAD' +} as const; + +const CANCELLABLE_JOB_RUN_STATUS: Array = [ JobRunStatus.PENDING, JobRunStatus.QUEUED, JobRunStatus.WAITING_ON_CONNECTIONS, diff --git a/apps/webapp/app/services/sources/handleHttpSource.server.ts b/apps/webapp/app/services/sources/handleHttpSource.server.ts index 518c65eaf..b51f6d589 100644 --- a/apps/webapp/app/services/sources/handleHttpSource.server.ts +++ b/apps/webapp/app/services/sources/handleHttpSource.server.ts @@ -1,10 +1,14 @@ import type { PrismaClient } from "~/db.server"; import { prisma } from "~/db.server"; import { workerQueue } from "../worker.server"; -import { requestUrl } from "~/utils/requestUrl.server"; -import { RuntimeEnvironmentType } from "@trigger.dev/database"; import { createHttpSourceRequest } from "~/utils/createHttpSourceRequest"; -import { logger } from "../logger.server"; + +const RuntimeEnvironmentType = { + PRODUCTION: "PRODUCTION", + STAGING: "STAGING", + DEVELOPMENT: "DEVELOPMENT", + PREVIEW: "PREVIEW", +} as const export class HandleHttpSourceService { #prismaClient: PrismaClient; diff --git a/apps/webapp/app/services/sources/handleWebhookRequest.server.ts b/apps/webapp/app/services/sources/handleWebhookRequest.server.ts index c0c3ddf0e..18ee15f12 100644 --- a/apps/webapp/app/services/sources/handleWebhookRequest.server.ts +++ b/apps/webapp/app/services/sources/handleWebhookRequest.server.ts @@ -1,11 +1,16 @@ import type { PrismaClient } from "~/db.server"; import { prisma } from "~/db.server"; import { workerQueue } from "../worker.server"; -import { RuntimeEnvironmentType } from "@trigger.dev/database"; import { createHttpSourceRequest } from "~/utils/createHttpSourceRequest"; import { WebhookContextMetadata } from "@trigger.dev/core"; import { createHash } from "crypto"; +const RuntimeEnvironmentType = { + PRODUCTION: "PRODUCTION", + STAGING: "STAGING", + DEVELOPMENT: "DEVELOPMENT", + PREVIEW: "PREVIEW", +} as const export class HandleWebhookRequestService { #prismaClient: PrismaClient; From a98b62652c4a678d2377b5519bffb8154d1a42b2 Mon Sep 17 00:00:00 2001 From: Jacob Paris Date: Sat, 18 May 2024 01:33:25 -0600 Subject: [PATCH 02/27] fix: vite cant resolve real values from prisma, only types are ok --- apps/webapp/app/components/runs/v3/RunFilters.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/webapp/app/components/runs/v3/RunFilters.tsx b/apps/webapp/app/components/runs/v3/RunFilters.tsx index f3e153e3b..e7489df51 100644 --- a/apps/webapp/app/components/runs/v3/RunFilters.tsx +++ b/apps/webapp/app/components/runs/v3/RunFilters.tsx @@ -2,7 +2,6 @@ import * as Ariakit from "@ariakit/react"; import { CalendarIcon, CpuChipIcon, XMarkIcon } from "@heroicons/react/20/solid"; import { Form } from "@remix-run/react"; import type { RuntimeEnvironment, TaskTriggerSource } from "@trigger.dev/database"; -import { TaskRunStatus } from "@trigger.dev/database"; import { ListFilterIcon } from "lucide-react"; import type { ReactNode} from "react"; import { startTransition, useCallback, useMemo, useState } from "react"; @@ -334,7 +333,7 @@ function AppliedStatusFilter() { }> runStatusTitle(v as TaskRunStatus)))} + value={appliedSummary(statuses.map((v) => runStatusTitle(v as typeof TaskRunStatus[number])))} onRemove={() => del(["statuses", "cursor", "direction"])} /> From ae5ec86d0be277ef4e72b2cb65de51f2cfd2c77d Mon Sep 17 00:00:00 2001 From: Jacob Paris Date: Tue, 21 May 2024 03:44:22 -0600 Subject: [PATCH 03/27] fix: ensure enums satisfy prisma types --- .../app/components/runs/v3/RunFilters.tsx | 9 ++- .../runs/v3/TaskRunAttemptStatus.tsx | 20 +++--- .../app/components/runs/v3/TaskRunStatus.tsx | 3 +- apps/webapp/app/database-types.ts | 68 +++++++++++++++++++ apps/webapp/app/models/api-key.server.ts | 8 +-- apps/webapp/app/models/taskRun.server.ts | 51 ++++---------- .../presenters/v3/TaskListPresenter.server.ts | 33 +++------ ...endpointSlug.index.$indexHookIdentifier.ts | 10 +-- .../endpoints/createEndpoint.server.ts | 8 +-- .../validateCreateEndpoint.server.ts | 8 +-- .../events/cancelRunsForEvent.server.ts | 23 ++----- .../services/jobs/cancelRunsForJob.server.ts | 19 +----- .../sources/handleHttpSource.server.ts | 8 +-- .../sources/handleWebhookRequest.server.ts | 7 +- 14 files changed, 116 insertions(+), 159 deletions(-) create mode 100644 apps/webapp/app/database-types.ts diff --git a/apps/webapp/app/components/runs/v3/RunFilters.tsx b/apps/webapp/app/components/runs/v3/RunFilters.tsx index e7489df51..8b4fa1309 100644 --- a/apps/webapp/app/components/runs/v3/RunFilters.tsx +++ b/apps/webapp/app/components/runs/v3/RunFilters.tsx @@ -1,9 +1,9 @@ import * as Ariakit from "@ariakit/react"; import { CalendarIcon, CpuChipIcon, XMarkIcon } from "@heroicons/react/20/solid"; import { Form } from "@remix-run/react"; -import type { RuntimeEnvironment, TaskTriggerSource } from "@trigger.dev/database"; +import type { RuntimeEnvironment, TaskTriggerSource, TaskRunStatus } from "@trigger.dev/database"; import { ListFilterIcon } from "lucide-react"; -import type { ReactNode} from "react"; +import type { ReactNode } from "react"; import { startTransition, useCallback, useMemo, useState } from "react"; import { z } from "zod"; import { TaskIcon } from "~/assets/icons/TaskIcon"; @@ -38,8 +38,7 @@ import { } from "./TaskRunStatus"; import { TaskTriggerSourceIcon } from "./TaskTriggerSource"; -const TaskRunStatus = ["PENDING", "WAITING_FOR_DEPLOY", "EXECUTING", "WAITING_TO_RESUME", "RETRYING_AFTER_FAILURE", "PAUSED", "CANCELED", "INTERRUPTED", "COMPLETED_SUCCESSFULLY", "COMPLETED_WITH_ERRORS", "SYSTEM_FAILURE", "CRASHED"] as const -export const TaskAttemptStatus = z.enum(TaskRunStatus) +export const TaskAttemptStatus = z.enum(allTaskRunStatuses); export const TaskRunListSearchFilters = z.object({ cursor: z.string().optional(), @@ -333,7 +332,7 @@ function AppliedStatusFilter() { }> runStatusTitle(v as typeof TaskRunStatus[number])))} + value={appliedSummary(statuses.map((v) => runStatusTitle(v as TaskRunStatus)))} onRemove={() => del(["statuses", "cursor", "direction"])} /> diff --git a/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx b/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx index 2ddb6f93e..4610edec6 100644 --- a/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx +++ b/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx @@ -12,18 +12,16 @@ import { SnowflakeIcon } from "lucide-react"; import { Spinner } from "~/components/primitives/Spinner"; import { cn } from "~/utils/cn"; -export const allTaskRunAttemptStatuses = Object.values( - { - PENDING: "PENDING", - EXECUTING: "EXECUTING", - PAUSED: "PAUSED", - FAILED: "FAILED", - CANCELED: "CANCELED", - COMPLETED: "COMPLETED", -} -) as TaskRunAttemptStatusType[]; +export const allTaskRunAttemptStatuses = [ + "PENDING", + "EXECUTING", + "PAUSED", + "FAILED", + "CANCELED", + "COMPLETED", +] as const satisfies TaskRunAttemptStatusType[]; -export type ExtendedTaskAttemptStatus = (typeof allTaskRunAttemptStatuses)[number] | "ENQUEUED"; +export type ExtendedTaskAttemptStatus = TaskRunAttemptStatusType | "ENQUEUED"; export function TaskRunAttemptStatusCombo({ status, diff --git a/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx b/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx index a0ae7788d..20d950a56 100644 --- a/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx +++ b/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx @@ -25,9 +25,10 @@ export const allTaskRunStatuses = [ "CANCELED", "COMPLETED_WITH_ERRORS", "CRASHED", + "PAUSED", "INTERRUPTED", "SYSTEM_FAILURE", -] as TaskRunStatus[]; +] as const satisfies Array; const taskRunStatusDescriptions: Record = { PENDING: "Task is waiting to be executed", diff --git a/apps/webapp/app/database-types.ts b/apps/webapp/app/database-types.ts new file mode 100644 index 000000000..9afc5a5d4 --- /dev/null +++ b/apps/webapp/app/database-types.ts @@ -0,0 +1,68 @@ +// There's a weird issue with importing values from the prisma client +// when using Remix Vite + pnpm + prisma +// As long as they're only used as types it's ok +// Import types here and validate hardcoded enums + +import type { + BatchTaskRunItemStatus as BatchTaskRunItemStatusType, + TaskRunAttemptStatus as TaskRunAttemptStatusType, + TaskRunStatus as TaskRunStatusType, + JobRunStatus as JobRunStatusType, + RuntimeEnvironmentType as RuntimeEnvironmentTypeType, +} from "@trigger.dev/database"; + +export const BatchTaskRunItemStatus = { + PENDING: "PENDING", + FAILED: "FAILED", + CANCELED: "CANCELED", + COMPLETED: "COMPLETED", +} as const satisfies Record; + +export const TaskRunAttemptStatus = { + PENDING: "PENDING", + EXECUTING: "EXECUTING", + PAUSED: "PAUSED", + FAILED: "FAILED", + CANCELED: "CANCELED", + COMPLETED: "COMPLETED", +} as const satisfies Record; + +export const TaskRunStatus = { + PENDING: "PENDING", + WAITING_FOR_DEPLOY: "WAITING_FOR_DEPLOY", + EXECUTING: "EXECUTING", + WAITING_TO_RESUME: "WAITING_TO_RESUME", + RETRYING_AFTER_FAILURE: "RETRYING_AFTER_FAILURE", + PAUSED: "PAUSED", + CANCELED: "CANCELED", + INTERRUPTED: "INTERRUPTED", + COMPLETED_SUCCESSFULLY: "COMPLETED_SUCCESSFULLY", + COMPLETED_WITH_ERRORS: "COMPLETED_WITH_ERRORS", + SYSTEM_FAILURE: "SYSTEM_FAILURE", + CRASHED: "CRASHED", +} as const satisfies Record; + +export const JobRunStatus = { + PENDING: "PENDING", + QUEUED: "QUEUED", + WAITING_ON_CONNECTIONS: "WAITING_ON_CONNECTIONS", + PREPROCESSING: "PREPROCESSING", + STARTED: "STARTED", + EXECUTING: "EXECUTING", + WAITING_TO_CONTINUE: "WAITING_TO_CONTINUE", + WAITING_TO_EXECUTE: "WAITING_TO_EXECUTE", + SUCCESS: "SUCCESS", + FAILURE: "FAILURE", + TIMED_OUT: "TIMED_OUT", + ABORTED: "ABORTED", + CANCELED: "CANCELED", + UNRESOLVED_AUTH: "UNRESOLVED_AUTH", + INVALID_PAYLOAD: "INVALID_PAYLOAD", +} as const satisfies Record; + +export const RuntimeEnvironmentType = { + PRODUCTION: "PRODUCTION", + STAGING: "STAGING", + DEVELOPMENT: "DEVELOPMENT", + PREVIEW: "PREVIEW", +} as const satisfies Record; diff --git a/apps/webapp/app/models/api-key.server.ts b/apps/webapp/app/models/api-key.server.ts index 417afdd94..0389782d8 100644 --- a/apps/webapp/app/models/api-key.server.ts +++ b/apps/webapp/app/models/api-key.server.ts @@ -1,13 +1,7 @@ import type { RuntimeEnvironment } from "@trigger.dev/database"; import { prisma } from "~/db.server"; import { customAlphabet } from "nanoid"; - -const RuntimeEnvironmentType = { - PRODUCTION: "PRODUCTION", - STAGING: "STAGING", - DEVELOPMENT: "DEVELOPMENT", - PREVIEW: "PREVIEW" -} as const +import { RuntimeEnvironmentType } from "~/database-types"; const apiKeyId = customAlphabet( "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", diff --git a/apps/webapp/app/models/taskRun.server.ts b/apps/webapp/app/models/taskRun.server.ts index f18ee5e66..7922c1aed 100644 --- a/apps/webapp/app/models/taskRun.server.ts +++ b/apps/webapp/app/models/taskRun.server.ts @@ -1,49 +1,22 @@ import type { TaskRunExecutionResult, TaskRunFailedExecutionResult, - TaskRunSuccessfulExecutionResult} from "@trigger.dev/core/v3"; -import { - TaskRunError + TaskRunSuccessfulExecutionResult, } from "@trigger.dev/core/v3"; -import { - type TaskRun, - type TaskRunAttempt, +import { TaskRunError } from "@trigger.dev/core/v3"; + +import type { + TaskRun, + TaskRunAttempt, + TaskRunAttemptStatus as TaskRunAttemptStatusType, + TaskRunStatus as TaskRunStatusType, + BatchTaskRunItemStatus as BatchTaskRunItemStatusType, } from "@trigger.dev/database"; import { assertNever } from "assert-never"; +import { BatchTaskRunItemStatus, TaskRunAttemptStatus, TaskRunStatus } from "~/database-types"; import { logger } from "~/services/logger.server"; -const BatchTaskRunItemStatus = { - PENDING: "PENDING", - FAILED: "FAILED", - CANCELED: "CANCELED", - COMPLETED: "COMPLETED", -} as const - -const TaskRunAttemptStatus= { - PENDING: "PENDING", - EXECUTING: "EXECUTING", - PAUSED: "PAUSED", - FAILED: "FAILED", - CANCELED: "CANCELED", - COMPLETED: "COMPLETED", -} as const - -const TaskRunStatus = { - PENDING: 'PENDING', - WAITING_FOR_DEPLOY: 'WAITING_FOR_DEPLOY', - EXECUTING: 'EXECUTING', - WAITING_TO_RESUME: 'WAITING_TO_RESUME', - RETRYING_AFTER_FAILURE: 'RETRYING_AFTER_FAILURE', - PAUSED: 'PAUSED', - CANCELED: 'CANCELED', - INTERRUPTED: 'INTERRUPTED', - COMPLETED_SUCCESSFULLY: 'COMPLETED_SUCCESSFULLY', - COMPLETED_WITH_ERRORS: 'COMPLETED_WITH_ERRORS', - SYSTEM_FAILURE: 'SYSTEM_FAILURE', - CRASHED: 'CRASHED' -} as const - const SUCCESSFUL_STATUSES = [TaskRunStatus.COMPLETED_SUCCESSFULLY]; const FAILURE_STATUSES = [ TaskRunStatus.CANCELED, @@ -134,7 +107,9 @@ export function executionResultForTaskRun( } } -export function batchTaskRunItemStatusForRunStatus(status: keyof typeof TaskRunStatus): keyof typeof BatchTaskRunItemStatus { +export function batchTaskRunItemStatusForRunStatus( + status: TaskRunStatusType +): BatchTaskRunItemStatusType { switch (status) { case TaskRunStatus.COMPLETED_SUCCESSFULLY: return BatchTaskRunItemStatus.COMPLETED; diff --git a/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts b/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts index dd83c9ca2..c6a26f970 100644 --- a/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts @@ -1,10 +1,9 @@ import type { RuntimeEnvironmentType, - TaskTriggerSource -} from "@trigger.dev/database"; -import { - Prisma + TaskTriggerSource, + TaskRunStatus as TaskRunStatusType, } from "@trigger.dev/database"; +import { Prisma } from "@trigger.dev/database"; import { QUEUED_STATUSES, RUNNING_STATUSES } from "~/components/runs/v3/TaskRunStatus"; import { sqlDatabaseSchema } from "~/db.server"; import type { Organization } from "~/models/organization.server"; @@ -14,23 +13,7 @@ import type { User } from "~/models/user.server"; import { sortEnvironments } from "~/utils/environmentSort"; import { logger } from "~/services/logger.server"; import { BasePresenter } from "./basePresenter.server"; - -const TaskRunStatus = { - PENDING: 'PENDING', - WAITING_FOR_DEPLOY: 'WAITING_FOR_DEPLOY', - EXECUTING: 'EXECUTING', - WAITING_TO_RESUME: 'WAITING_TO_RESUME', - RETRYING_AFTER_FAILURE: 'RETRYING_AFTER_FAILURE', - PAUSED: 'PAUSED', - CANCELED: 'CANCELED', - INTERRUPTED: 'INTERRUPTED', - COMPLETED_SUCCESSFULLY: 'COMPLETED_SUCCESSFULLY', - COMPLETED_WITH_ERRORS: 'COMPLETED_WITH_ERRORS', - SYSTEM_FAILURE: 'SYSTEM_FAILURE', - CRASHED: 'CRASHED' -} as const; - -type TaskRunStatus = typeof TaskRunStatus[keyof typeof TaskRunStatus] +import { TaskRunStatus } from "~/database-types"; export type Task = { slug: string; @@ -168,7 +151,7 @@ export class TaskListPresenter extends BasePresenter { const activity = await this._replica.$queryRaw< { taskIdentifier: string; - status: typeof TaskRunStatus; + status: TaskRunStatusType; day: Date; count: BigInt; }[] @@ -211,7 +194,7 @@ export class TaskListPresenter extends BasePresenter { existingTask.push({ day: day.toISOString(), [TaskRunStatus.COMPLETED_SUCCESSFULLY]: 0, - } as unknown as { day: string } & Record); + } as { day: string } & Record); } acc[a.taskIdentifier] = existingTask; @@ -232,7 +215,7 @@ export class TaskListPresenter extends BasePresenter { day[a.status] = Number(a.count); return acc; - }, {} as Record)[]>); + }, {} as Record)[]>); } async #getRunningStats(tasks: string[], projectId: string) { @@ -243,7 +226,7 @@ export class TaskListPresenter extends BasePresenter { const statuses = await this._replica.$queryRaw< { taskIdentifier: string; - status: TaskRunStatus; + status: TaskRunStatusType; count: BigInt; }[] >` diff --git a/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts b/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts index 41d767920..fb011392a 100644 --- a/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts +++ b/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts @@ -1,17 +1,11 @@ import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; -import type { PrismaClient} from "~/db.server"; +import type { PrismaClient } from "~/db.server"; import { $transaction, prisma } from "~/db.server"; import { logger } from "~/services/logger.server"; import { workerQueue } from "~/services/worker.server"; import { safeJsonParse } from "~/utils/json"; - -const RuntimeEnvironmentType= { - PRODUCTION: "PRODUCTION", - STAGING: "STAGING", - DEVELOPMENT: "DEVELOPMENT", - PREVIEW: "PREVIEW", -} as const +import { RuntimeEnvironmentType } from "~/database-types"; const ParamsSchema = z.object({ environmentId: z.string(), diff --git a/apps/webapp/app/services/endpoints/createEndpoint.server.ts b/apps/webapp/app/services/endpoints/createEndpoint.server.ts index 512e6a49a..76a4418fa 100644 --- a/apps/webapp/app/services/endpoints/createEndpoint.server.ts +++ b/apps/webapp/app/services/endpoints/createEndpoint.server.ts @@ -4,13 +4,7 @@ import { AuthenticatedEnvironment } from "../apiAuth.server"; import { EndpointApi } from "../endpointApi.server"; import { workerQueue } from "../worker.server"; import { env } from "~/env.server"; - -const RuntimeEnvironmentType = { - PRODUCTION: "PRODUCTION", - STAGING: "STAGING", - DEVELOPMENT: "DEVELOPMENT", - PREVIEW: "PREVIEW", -} as const +import { RuntimeEnvironmentType } from "~/database-types"; const indexingHookIdentifier = customAlphabet("0123456789abcdefghijklmnopqrstuvxyz", 10); diff --git a/apps/webapp/app/services/endpoints/validateCreateEndpoint.server.ts b/apps/webapp/app/services/endpoints/validateCreateEndpoint.server.ts index b0d14cd4d..f5d205312 100644 --- a/apps/webapp/app/services/endpoints/validateCreateEndpoint.server.ts +++ b/apps/webapp/app/services/endpoints/validateCreateEndpoint.server.ts @@ -5,13 +5,7 @@ import { AuthenticatedEnvironment } from "../apiAuth.server"; import { workerQueue } from "../worker.server"; import { CreateEndpointError } from "./createEndpoint.server"; import { EndpointApi } from "../endpointApi.server"; - -const RuntimeEnvironmentType = { - PRODUCTION: "PRODUCTION", - STAGING: "STAGING", - DEVELOPMENT: "DEVELOPMENT", - PREVIEW: "PREVIEW", -} as const +import { RuntimeEnvironmentType } from "~/database-types"; const indexingHookIdentifier = customAlphabet("0123456789abcdefghijklmnopqrstuvxyz", 10); diff --git a/apps/webapp/app/services/events/cancelRunsForEvent.server.ts b/apps/webapp/app/services/events/cancelRunsForEvent.server.ts index 32aa6697f..a392c3e8f 100644 --- a/apps/webapp/app/services/events/cancelRunsForEvent.server.ts +++ b/apps/webapp/app/services/events/cancelRunsForEvent.server.ts @@ -1,29 +1,14 @@ -import type { PrismaClient} from "~/db.server"; +import type { PrismaClient } from "~/db.server"; import { $transaction, prisma } from "~/db.server"; import type { AuthenticatedEnvironment } from "../apiAuth.server"; import { CancelRunService } from "../runs/cancelRun.server"; import { logger } from "../logger.server"; import type { CancelRunsForEvent } from "@trigger.dev/core"; +import type { JobRunStatus as JobRunStatusType } from "@trigger.dev/database"; +import { JobRunStatus } from "~/database-types"; -export const JobRunStatus = { - PENDING: 'PENDING', - QUEUED: 'QUEUED', - WAITING_ON_CONNECTIONS: 'WAITING_ON_CONNECTIONS', - PREPROCESSING: 'PREPROCESSING', - STARTED: 'STARTED', - EXECUTING: 'EXECUTING', - WAITING_TO_CONTINUE: 'WAITING_TO_CONTINUE', - WAITING_TO_EXECUTE: 'WAITING_TO_EXECUTE', - SUCCESS: 'SUCCESS', - FAILURE: 'FAILURE', - TIMED_OUT: 'TIMED_OUT', - ABORTED: 'ABORTED', - CANCELED: 'CANCELED', - UNRESOLVED_AUTH: 'UNRESOLVED_AUTH', - INVALID_PAYLOAD: 'INVALID_PAYLOAD' -} as const; -const CANCELLABLE_JOB_RUN_STATUS: Array = [ +const CANCELLABLE_JOB_RUN_STATUS: Array = [ JobRunStatus.PENDING, JobRunStatus.QUEUED, JobRunStatus.WAITING_ON_CONNECTIONS, diff --git a/apps/webapp/app/services/jobs/cancelRunsForJob.server.ts b/apps/webapp/app/services/jobs/cancelRunsForJob.server.ts index a3e9a689b..0ad57ac46 100644 --- a/apps/webapp/app/services/jobs/cancelRunsForJob.server.ts +++ b/apps/webapp/app/services/jobs/cancelRunsForJob.server.ts @@ -3,24 +3,7 @@ import { AuthenticatedEnvironment } from "../apiAuth.server"; import { CancelRunService } from "../runs/cancelRun.server"; import { logger } from "../logger.server"; import { CancelRunsForJob } from "@trigger.dev/core"; - -export const JobRunStatus = { - PENDING: 'PENDING', - QUEUED: 'QUEUED', - WAITING_ON_CONNECTIONS: 'WAITING_ON_CONNECTIONS', - PREPROCESSING: 'PREPROCESSING', - STARTED: 'STARTED', - EXECUTING: 'EXECUTING', - WAITING_TO_CONTINUE: 'WAITING_TO_CONTINUE', - WAITING_TO_EXECUTE: 'WAITING_TO_EXECUTE', - SUCCESS: 'SUCCESS', - FAILURE: 'FAILURE', - TIMED_OUT: 'TIMED_OUT', - ABORTED: 'ABORTED', - CANCELED: 'CANCELED', - UNRESOLVED_AUTH: 'UNRESOLVED_AUTH', - INVALID_PAYLOAD: 'INVALID_PAYLOAD' -} as const; +import { JobRunStatus } from "~/database-types"; const CANCELLABLE_JOB_RUN_STATUS: Array = [ JobRunStatus.PENDING, diff --git a/apps/webapp/app/services/sources/handleHttpSource.server.ts b/apps/webapp/app/services/sources/handleHttpSource.server.ts index b51f6d589..ed96c90b2 100644 --- a/apps/webapp/app/services/sources/handleHttpSource.server.ts +++ b/apps/webapp/app/services/sources/handleHttpSource.server.ts @@ -2,13 +2,7 @@ import type { PrismaClient } from "~/db.server"; import { prisma } from "~/db.server"; import { workerQueue } from "../worker.server"; import { createHttpSourceRequest } from "~/utils/createHttpSourceRequest"; - -const RuntimeEnvironmentType = { - PRODUCTION: "PRODUCTION", - STAGING: "STAGING", - DEVELOPMENT: "DEVELOPMENT", - PREVIEW: "PREVIEW", -} as const +import { RuntimeEnvironmentType } from "~/database-types"; export class HandleHttpSourceService { #prismaClient: PrismaClient; diff --git a/apps/webapp/app/services/sources/handleWebhookRequest.server.ts b/apps/webapp/app/services/sources/handleWebhookRequest.server.ts index 18ee15f12..166d8025e 100644 --- a/apps/webapp/app/services/sources/handleWebhookRequest.server.ts +++ b/apps/webapp/app/services/sources/handleWebhookRequest.server.ts @@ -4,13 +4,8 @@ import { workerQueue } from "../worker.server"; import { createHttpSourceRequest } from "~/utils/createHttpSourceRequest"; import { WebhookContextMetadata } from "@trigger.dev/core"; import { createHash } from "crypto"; +import { RuntimeEnvironmentType } from "~/database-types"; -const RuntimeEnvironmentType = { - PRODUCTION: "PRODUCTION", - STAGING: "STAGING", - DEVELOPMENT: "DEVELOPMENT", - PREVIEW: "PREVIEW", -} as const export class HandleWebhookRequestService { #prismaClient: PrismaClient; From 0a7ad232fb7870add599f0430f45bdaa2e0be317 Mon Sep 17 00:00:00 2001 From: Jacob Paris Date: Tue, 21 May 2024 03:49:58 -0600 Subject: [PATCH 04/27] fix: reuse taskrunattemptstatus --- .../app/components/runs/v3/TaskRunAttemptStatus.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx b/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx index 4610edec6..de0fa038b 100644 --- a/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx +++ b/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx @@ -7,19 +7,15 @@ import { XCircleIcon, } from "@heroicons/react/20/solid"; import type { TaskRunAttemptStatus as TaskRunAttemptStatusType } from "@trigger.dev/database"; +import { TaskRunAttemptStatus } from "~/database-types"; import assertNever from "assert-never"; import { SnowflakeIcon } from "lucide-react"; import { Spinner } from "~/components/primitives/Spinner"; import { cn } from "~/utils/cn"; -export const allTaskRunAttemptStatuses = [ - "PENDING", - "EXECUTING", - "PAUSED", - "FAILED", - "CANCELED", - "COMPLETED", -] as const satisfies TaskRunAttemptStatusType[]; +export const allTaskRunAttemptStatuses = Object.values( + TaskRunAttemptStatus +) as TaskRunAttemptStatusType[]; export type ExtendedTaskAttemptStatus = TaskRunAttemptStatusType | "ENQUEUED"; From 3f8b6d8fcea7412864ff617701a4a3e4a0c172b5 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Tue, 21 May 2024 14:07:16 +0100 Subject: [PATCH 05/27] v2: Better handle recovering from platform communication errors by auto-yielding back to the platform in case of temporary API failures --- .changeset/strange-sheep-pull.md | 6 + apps/webapp/package.json | 3 +- packages/core/src/v3/apiClient/index.ts | 8 +- packages/trigger-sdk/src/apiClient.ts | 296 +++++++++++++++------- packages/trigger-sdk/src/io.ts | 69 +++-- packages/trigger-sdk/src/triggerClient.ts | 6 +- references/job-catalog/src/stressTest.ts | 15 +- 7 files changed, 271 insertions(+), 132 deletions(-) create mode 100644 .changeset/strange-sheep-pull.md diff --git a/.changeset/strange-sheep-pull.md b/.changeset/strange-sheep-pull.md new file mode 100644 index 000000000..518a7949b --- /dev/null +++ b/.changeset/strange-sheep-pull.md @@ -0,0 +1,6 @@ +--- +"@trigger.dev/sdk": patch +"@trigger.dev/core": patch +--- + +v2: Better handle recovering from platform communication errors by auto-yielding back to the platform in case of temporary API failures diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 5e335b8f3..3f37bc6b4 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -9,6 +9,7 @@ "build:remix": "remix build", "build:server": "esbuild --platform=node --format=cjs ./server.ts --outdir=build", "dev": "cross-env PORT=3030 remix dev -c \"node ./build/server.js\"", + "dev:worker": "cross-env NODE_PATH=../../node_modules/.pnpm/node_modules node ./build/server.js", "format": "prettier --write .", "lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .", "start": "cross-env NODE_ENV=production node --max-old-space-size=8192 ./build/server.js", @@ -237,4 +238,4 @@ "engines": { "node": ">=16.0.0" } -} +} \ No newline at end of file diff --git a/packages/core/src/v3/apiClient/index.ts b/packages/core/src/v3/apiClient/index.ts index b98b67613..5af8061f9 100644 --- a/packages/core/src/v3/apiClient/index.ts +++ b/packages/core/src/v3/apiClient/index.ts @@ -1,5 +1,6 @@ import { context, propagation } from "@opentelemetry/api"; -import { ZodFetchOptions, zodfetch } from "../zodfetch"; +import { version } from "../../../package.json"; +import { APIError } from "../apiErrors"; import { BatchTaskRunExecutionResult, BatchTriggerTaskRequestBody, @@ -19,10 +20,7 @@ import { UpdateScheduleOptions, } from "../schemas"; import { taskContext } from "../task-context-api"; -import { getEnvVar } from "../utils/getEnv"; -import { SafeAsyncLocalStorage } from "../utils/safeAsyncLocalStorage"; -import { APIError } from "../apiErrors"; -import { version } from "../../../package.json"; +import { ZodFetchOptions, zodfetch } from "../zodfetch"; export type TriggerOptions = { spanParentAsLink?: boolean; diff --git a/packages/trigger-sdk/src/apiClient.ts b/packages/trigger-sdk/src/apiClient.ts index c487691f7..e97fe9bfc 100644 --- a/packages/trigger-sdk/src/apiClient.ts +++ b/packages/trigger-sdk/src/apiClient.ts @@ -74,6 +74,18 @@ export type RunRecord = { event: ApiEventLog; }; +export class UnknownVersionError extends Error { + constructor(version: string) { + super(`Unknown version ${version}`); + } +} + +const MAX_RETRIES = 8; +const EXPONENT_FACTOR = 2; +const MIN_DELAY_IN_MS = 80; +const MAX_DELAY_IN_MS = 2000; +const JITTER_IN_MS = 50; + export class ApiClient { #apiUrl: string; #options: ApiClientOptions; @@ -129,11 +141,10 @@ export class ApiClient { ) { const apiKey = await this.#apiKey(); - this.#logger.debug("Running Task", { - task, - }); + this.#logger.debug(`[ApiClient] runTask ${task.displayKey}`); return await zodfetchWithVersions( + this.#logger, { [API_VERSIONS.LAZY_LOADED_CACHED_TASKS]: RunTaskResponseWithCachedTasksBodySchema, }, @@ -771,6 +782,7 @@ async function zodfetchWithVersions< TUnversionedResponseBodySchema extends z.ZodTypeAny, TOptional extends boolean = false, >( + logger: Logger, versionedSchemaMap: TVersionedResponseBodyMap, unversionedSchema: TUnversionedResponseBodySchema, url: string, @@ -785,66 +797,132 @@ async function zodfetchWithVersions< ? VersionedResponseBody | undefined : VersionedResponseBody > { - const response = await fetch(url, requestInitWithCache(requestInit)); + try { + const fullRequestInit = requestInitWithCache(requestInit); - if ( - (!requestInit || requestInit.method === "GET") && - response.status === 404 && - options?.optional - ) { - // @ts-ignore - return; - } + const response = await fetch(url, fullRequestInit); - if (response.status >= 400 && response.status < 500) { - const body = await response.json(); - - throw new Error(body.error); - } - - if (response.status >= 500 && retryCount < 6) { - // retry with exponential backoff and jitter - const delay = exponentialBackoff(retryCount + 1, 2, 50, 1150, 50); - - await new Promise((resolve) => setTimeout(resolve, delay)); - - return zodfetchWithVersions( - versionedSchemaMap, - unversionedSchema, + logger.debug(`[ApiClient] zodfetchWithVersions ${url} (attempt ${retryCount + 1})`, { url, - requestInit, - options, - retryCount + 1 - ); - } + retryCount, + requestHeaders: fullRequestInit?.headers, + responseHeaders: Object.fromEntries(response.headers.entries()), + }); - if (response.status !== 200) { - throw new Error( - options?.errorMessage ?? `Failed to fetch ${url}, got status code ${response.status}` - ); - } + if ( + (!requestInit || requestInit.method === "GET") && + response.status === 404 && + options?.optional + ) { + // @ts-ignore + return; + } - const jsonBody = await response.json(); + if (response.status >= 400 && response.status < 500) { + const rawBody = await safeResponseText(response); + const body = safeJsonParse(rawBody); - const version = response.headers.get("trigger-version"); + logger.error(`[ApiClient] zodfetchWithVersions failed with ${response.status}`, { + url, + retryCount, + requestHeaders: fullRequestInit?.headers, + responseHeaders: Object.fromEntries(response.headers.entries()), + status: response.status, + rawBody, + }); + + if (body && body.error) { + throw new Error(body.error); + } else { + throw new Error(rawBody); + } + } + + if (response.status >= 500 && retryCount < MAX_RETRIES) { + // retry with exponential backoff and jitter + const delay = exponentialBackoff(retryCount + 1); + + await new Promise((resolve) => setTimeout(resolve, delay)); + + return zodfetchWithVersions( + logger, + versionedSchemaMap, + unversionedSchema, + url, + requestInit, + options, + retryCount + 1 + ); + } + + if (response.status !== 200) { + const rawBody = await safeResponseText(response); + + logger.error(`[ApiClient] zodfetchWithVersions failed with ${response.status}`, { + url, + retryCount, + requestHeaders: fullRequestInit?.headers, + responseHeaders: Object.fromEntries(response.headers.entries()), + status: response.status, + rawBody, + }); + + throw new Error( + options?.errorMessage ?? `Failed to fetch ${url}, got status code ${response.status}` + ); + } + + const jsonBody = await response.json(); + + const version = response.headers.get("trigger-version"); + + if (!version) { + return { + version: "unversioned", + body: unversionedSchema.parse(jsonBody), + }; + } + + const versionedSchema = versionedSchemaMap[version]; + + if (!versionedSchema) { + throw new UnknownVersionError(version); + } - if (!version) { return { - version: "unversioned", - body: unversionedSchema.parse(jsonBody), + version, + body: versionedSchema.parse(jsonBody), }; + } catch (error) { + if (error instanceof UnknownVersionError) { + throw error; + } + + logger.error(`[ApiClient] zodfetchWithVersions failed with a connection error`, { + url, + retryCount, + error, + }); + + if (retryCount < MAX_RETRIES) { + // retry with exponential backoff and jitter + const delay = exponentialBackoff(retryCount + 1); + + await new Promise((resolve) => setTimeout(resolve, delay)); + + return zodfetchWithVersions( + logger, + versionedSchemaMap, + unversionedSchema, + url, + requestInit, + options, + retryCount + 1 + ); + } + + throw error; } - - const versionedSchema = versionedSchemaMap[version]; - - if (!versionedSchema) { - throw new Error(`Unknown version ${version}`); - } - - return { - version, - body: versionedSchema.parse(jsonBody), - }; } function requestInitWithCache(requestInit?: RequestInit): RequestInit { @@ -873,9 +951,9 @@ async function fetchHead( }; const response = await fetch(url, requestInitWithCache(requestInit)); - if (response.status >= 500 && retryCount < 6) { + if (response.status >= 500 && retryCount < MAX_RETRIES) { // retry with exponential backoff and jitter - const delay = exponentialBackoff(retryCount + 1, 2, 50, 1150, 50); + const delay = exponentialBackoff(retryCount + 1); await new Promise((resolve) => setTimeout(resolve, delay)); @@ -897,56 +975,80 @@ async function zodfetch | undefined : z.infer > { - const response = await fetch(url, requestInitWithCache(requestInit)); + try { + const response = await fetch(url, requestInitWithCache(requestInit)); - if ( - (!requestInit || requestInit.method === "GET") && - response.status === 404 && - options?.optional - ) { - // @ts-ignore - return; + if ( + (!requestInit || requestInit.method === "GET") && + response.status === 404 && + options?.optional + ) { + // @ts-ignore + return; + } + + if (response.status >= 400 && response.status < 500) { + const body = await response.json(); + + throw new Error(body.error); + } + + if (response.status >= 500 && retryCount < MAX_RETRIES) { + // retry with exponential backoff and jitter + const delay = exponentialBackoff(retryCount + 1); + + await new Promise((resolve) => setTimeout(resolve, delay)); + + return zodfetch(schema, url, requestInit, options, retryCount + 1); + } + + if (response.status !== 200) { + throw new Error( + options?.errorMessage ?? `Failed to fetch ${url}, got status code ${response.status}` + ); + } + + const jsonBody = await response.json(); + + return schema.parse(jsonBody); + } catch (error) { + if (retryCount < MAX_RETRIES) { + // retry with exponential backoff and jitter + const delay = exponentialBackoff(retryCount + 1); + + await new Promise((resolve) => setTimeout(resolve, delay)); + + return zodfetch(schema, url, requestInit, options, retryCount + 1); + } + + throw error; } - - if (response.status >= 400 && response.status < 500) { - const body = await response.json(); - - throw new Error(body.error); - } - - if (response.status >= 500 && retryCount < 6) { - // retry with exponential backoff and jitter - const delay = exponentialBackoff(retryCount + 1, 2, 50, 1150, 50); - - await new Promise((resolve) => setTimeout(resolve, delay)); - - return zodfetch(schema, url, requestInit, options, retryCount + 1); - } - - if (response.status !== 200) { - throw new Error( - options?.errorMessage ?? `Failed to fetch ${url}, got status code ${response.status}` - ); - } - - const jsonBody = await response.json(); - - return schema.parse(jsonBody); } -function exponentialBackoff( - retryCount: number, - exponential: number, - minDelay: number, - maxDelay: number, - jitter: number -): number { +// First retry will have a delay of 80ms, second 160ms, third 320ms, etc. +function exponentialBackoff(retryCount: number): number { // Calculate the delay using the exponential backoff formula - const delay = Math.min(Math.pow(exponential, retryCount) * minDelay, maxDelay); + const delay = Math.min(Math.pow(EXPONENT_FACTOR, retryCount) * MIN_DELAY_IN_MS, MAX_DELAY_IN_MS); // Calculate the jitter - const jitterValue = Math.random() * jitter; + const jitterValue = Math.random() * JITTER_IN_MS; // Return the calculated delay with jitter return delay + jitterValue; } + +function safeJsonParse(rawBody: string) { + try { + return JSON.parse(rawBody); + } catch (error) { + return; + } +} + +async function safeResponseText(response: Response) { + try { + return await response.text(); + } catch (error) { + return ""; + } +} diff --git a/packages/trigger-sdk/src/io.ts b/packages/trigger-sdk/src/io.ts index 47c4d0685..b57c4a7c4 100644 --- a/packages/trigger-sdk/src/io.ts +++ b/packages/trigger-sdk/src/io.ts @@ -1,6 +1,7 @@ import { API_VERSIONS, CachedTask, + CompleteTaskBodyV2Input, ConnectionAuth, CronOptions, ErrorWithStackSchema, @@ -11,6 +12,7 @@ import { FetchTimeoutOptions, InitialStatusUpdate, IntervalOptions, + RunTaskBodyInput, RunTaskOptions, SendEvent, SendEventOptions, @@ -1161,19 +1163,18 @@ export class IO { const runOptions = { ...(options ?? {}), parseOutput: undefined }; - const response = await this._apiClient.runTask( - this._id, - { - idempotencyKey, - displayKey: typeof cacheKey === "string" ? cacheKey : undefined, - noop: false, - ...(runOptions ?? {}), - parentId, - }, - { - cachedTasksCursor: this._cachedTasksCursor, - } - ); + const response = await this.#doRunTask({ + idempotencyKey, + displayKey: typeof cacheKey === "string" ? cacheKey : undefined, + noop: false, + ...(runOptions ?? {}), + parentId, + }); + + if (!response) { + this.#forceYield("failed_task_run"); + throw new Error("Failed to run task"); // this shouldn't actually happen, because forceYield will throw + } const task = response.version === API_VERSIONS.LAZY_LOADED_CACHED_TASKS @@ -1267,11 +1268,16 @@ export class IO { this.#detectAutoYield("before_complete_task", 500, task, output); - const completedTask = await this._apiClient.completeTask(this._id, task.id, { + const completedTask = await this.#doCompleteTask(task.id, { output, properties: task.outputProperties ?? undefined, }); + if (!completedTask) { + this.#forceYield("before_complete_task", task, output); + throw new Error("Failed to complete task"); // this shouldn't actually happen, because forceYield will throw + } + if (completedTask.forceYield) { this._logger.debug("Forcing yield after task completed", { idempotencyKey, @@ -1448,6 +1454,24 @@ export class IO { this._cachedTasks.set(task.idempotencyKey, task); } + async #doRunTask(task: RunTaskBodyInput) { + try { + return await this._apiClient.runTask(this._id, task, { + cachedTasksCursor: this._cachedTasksCursor, + }); + } catch (error) { + return; + } + } + + async #doCompleteTask(id: string, task: CompleteTaskBodyV2Input) { + try { + return await this._apiClient.completeTask(this._id, id, task); + } catch (error) { + return; + } + } + #detectAutoYield(location: string, threshold: number = 1500, task?: ServerTask, output?: string) { const timeRemaining = this.#getRemainingTimeInMillis(); @@ -1469,11 +1493,24 @@ export class IO { } } - #forceYield(location: string) { + #forceYield(location: string, task?: ServerTask, output?: string) { const timeRemaining = this.#getRemainingTimeInMillis(); if (timeRemaining) { - throw new AutoYieldExecutionError(location, timeRemaining, this.#getTimeElapsed()); + if (task) { + throw new AutoYieldWithCompletedTaskExecutionError( + task.id, + task.outputProperties ?? [], + { + location, + timeRemaining, + timeElapsed: this.#getTimeElapsed(), + }, + output + ); + } else { + throw new AutoYieldExecutionError(location, timeRemaining, this.#getTimeElapsed()); + } } } diff --git a/packages/trigger-sdk/src/triggerClient.ts b/packages/trigger-sdk/src/triggerClient.ts index 2edfdacfc..95598cf3f 100644 --- a/packages/trigger-sdk/src/triggerClient.ts +++ b/packages/trigger-sdk/src/triggerClient.ts @@ -198,11 +198,15 @@ export class TriggerClient { constructor(options: Prettify) { this.id = options.id; this.#options = options; - this.#client = new ApiClient(this.#options); this.#internalLogger = new Logger("trigger.dev", this.#options.verbose ? "debug" : "log", [ "output", "noopTasksSet", ]); + this.#client = new ApiClient({ + logLevel: this.#options.verbose ? "debug" : "log", + ...this.#options, + }); + this.#envStore = new KeyValueStore(this.#client); } diff --git a/references/job-catalog/src/stressTest.ts b/references/job-catalog/src/stressTest.ts index 305dc152d..9a1b483a3 100644 --- a/references/job-catalog/src/stressTest.ts +++ b/references/job-catalog/src/stressTest.ts @@ -23,23 +23,14 @@ client.defineJob({ `task-${i}`, async (task) => { return { - output: "a".repeat(300 * 1024), + output: "a".repeat(30), }; }, { name: `Task ${i}` } ); - } - // Now run a single task with 5MB output - await io.runTask( - `task-5mb`, - async (task) => { - return { - output: "a".repeat(5 * 1024 * 1024), - }; - }, - { name: `Task 5MB` } - ); + await new Promise((resolve) => setTimeout(resolve, 2000)); + } // Now do a wait for 5 seconds await io.wait("wait", 5); From 3f45738612e57fed11bbe1be98139554ccfc121c Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Tue, 21 May 2024 14:19:33 +0100 Subject: [PATCH 06/27] Fixed typescript errors --- apps/webapp/app/components/runs/v3/RunFilters.tsx | 7 ++++++- apps/webapp/app/components/runs/v3/TaskRunStatus.tsx | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/components/runs/v3/RunFilters.tsx b/apps/webapp/app/components/runs/v3/RunFilters.tsx index 915570ec2..6ca258b4d 100644 --- a/apps/webapp/app/components/runs/v3/RunFilters.tsx +++ b/apps/webapp/app/components/runs/v3/RunFilters.tsx @@ -7,7 +7,12 @@ import { XMarkIcon, } from "@heroicons/react/20/solid"; import { Form } from "@remix-run/react"; -import type { RuntimeEnvironment, TaskTriggerSource, TaskRunStatus } from "@trigger.dev/database"; +import type { + RuntimeEnvironment, + TaskTriggerSource, + TaskRunStatus, + BulkActionType, +} from "@trigger.dev/database"; import { ListFilterIcon } from "lucide-react"; import type { ReactNode } from "react"; import { startTransition, useCallback, useMemo, useState } from "react"; diff --git a/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx b/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx index 20d950a56..1a49eb8ed 100644 --- a/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx +++ b/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx @@ -28,7 +28,7 @@ export const allTaskRunStatuses = [ "PAUSED", "INTERRUPTED", "SYSTEM_FAILURE", -] as const satisfies Array; +] as const satisfies Readonly>; const taskRunStatusDescriptions: Record = { PENDING: "Task is waiting to be executed", From 7cb20fbeadce696c344bff87849fd5697db1e3da Mon Sep 17 00:00:00 2001 From: Jacob Paris Date: Tue, 21 May 2024 07:36:21 -0600 Subject: [PATCH 07/27] fix: upgrade prism react renderer (#1111) * fix: upgrade prism react renderer * Update CodeBlock.tsx --- apps/webapp/app/components/code/CodeBlock.tsx | 8 +++---- apps/webapp/package.json | 2 +- pnpm-lock.yaml | 22 ++++++++++--------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/webapp/app/components/code/CodeBlock.tsx b/apps/webapp/app/components/code/CodeBlock.tsx index a1a496ad5..ff0956a35 100644 --- a/apps/webapp/app/components/code/CodeBlock.tsx +++ b/apps/webapp/app/components/code/CodeBlock.tsx @@ -1,6 +1,6 @@ import { Clipboard, ClipboardCheck } from "lucide-react"; import type { Language, PrismTheme } from "prism-react-renderer"; -import Highlight, { defaultProps } from "prism-react-renderer"; +import { Highlight } from "prism-react-renderer"; import { forwardRef, useCallback, useState } from "react"; import { cn } from "~/utils/cn"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip"; @@ -238,7 +238,7 @@ export const CodeBlock = forwardRef( )} {shouldHighlight ? ( - + {({ className: inheritedClassName, style: inheritedStyle, @@ -283,7 +283,7 @@ export const CodeBlock = forwardRef( return (
( const tokenProps = getTokenProps({ token, key }); return ( =0.14.9' - dependencies: - react: 18.2.0 - dev: false - /prism-react-renderer@2.1.0(react@18.2.0): resolution: {integrity: sha512-I5cvXHjA1PVGbGm1MsWCpvBCRrYyxEri0MC7/JbfIfYfcXAxHyO5PaUjs3A8H5GW6kJcLhTHxxMaOZZpRZD2iQ==} peerDependencies: @@ -29388,6 +29380,16 @@ packages: react: 18.2.0 dev: false + /prism-react-renderer@2.3.1(react@18.2.0): + resolution: {integrity: sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==} + peerDependencies: + react: '>=16.0.0' + dependencies: + '@types/prismjs': 1.26.0 + clsx: 2.1.0 + react: 18.2.0 + dev: false + /prisma@5.4.1: resolution: {integrity: sha512-op9PmU8Bcw5dNAas82wBYTG0yHnpq9/O3bhxbDBrNzwZTwBqsVCxxYRLf6wHNh9HVaDGhgjjHlu1+BcW8qdnBg==} engines: {node: '>=16.13'} From 5a6e79e0c050326cf9d4addb2f5fd440f432deda Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Tue, 21 May 2024 14:38:06 +0100 Subject: [PATCH 08/27] Fix missing logs when client includes their own @opentelemetry/api package (#1106) --- .changeset/cool-comics-burn.md | 5 + packages/cli-v3/src/commands/deploy.ts | 4 +- packages/cli-v3/src/commands/dev.tsx | 4 +- .../src/workers/dev/backgroundWorker.ts | 1 + .../cli-v3/src/workers/dev/worker-facade.ts | 8 +- .../cli-v3/src/workers/dev/worker-setup.ts | 21 +- .../cli-v3/src/workers/prod/worker-facade.ts | 8 +- .../cli-v3/src/workers/prod/worker-setup.ts | 12 +- packages/core/src/v3/otel/tracingSDK.ts | 2 + pnpm-lock.yaml | 401 ++++++++++++++++-- references/v3-catalog/package.json | 20 +- references/v3-catalog/src/telemetry.ts | 13 + references/v3-catalog/src/trigger/simple.ts | 5 + 13 files changed, 440 insertions(+), 64 deletions(-) create mode 100644 .changeset/cool-comics-burn.md create mode 100644 references/v3-catalog/src/telemetry.ts diff --git a/.changeset/cool-comics-burn.md b/.changeset/cool-comics-burn.md new file mode 100644 index 000000000..eaaf10cea --- /dev/null +++ b/.changeset/cool-comics-burn.md @@ -0,0 +1,5 @@ +--- +"trigger.dev": patch +--- + +Fixing missing logs when importing client @opentelemetry/api diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index f265cf1f0..262f2bf1d 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -1117,7 +1117,9 @@ async function compileProject( .replace("__TASKS__", createTaskFileImports(taskFiles)) .replace( "__WORKER_SETUP__", - `import { tracingSDK } from "${escapeImportPath(workerSetupPath)}";` + `import { tracingSDK, otelTracer, otelLogger } from "${escapeImportPath( + workerSetupPath + )}";` ); if (configPath) { diff --git a/packages/cli-v3/src/commands/dev.tsx b/packages/cli-v3/src/commands/dev.tsx index 569b9e9e9..870402120 100644 --- a/packages/cli-v3/src/commands/dev.tsx +++ b/packages/cli-v3/src/commands/dev.tsx @@ -364,7 +364,9 @@ function useDev({ .replace("__TASKS__", createTaskFileImports(taskFiles)) .replace( "__WORKER_SETUP__", - `import { tracingSDK, sender } from "${escapeImportPath(workerSetupPath)}";` + `import { tracingSDK, otelTracer, otelLogger, sender } from "${escapeImportPath( + workerSetupPath + )}";` ); if (configPath) { diff --git a/packages/cli-v3/src/workers/dev/backgroundWorker.ts b/packages/cli-v3/src/workers/dev/backgroundWorker.ts index 0b2b2d715..a345fb086 100644 --- a/packages/cli-v3/src/workers/dev/backgroundWorker.ts +++ b/packages/cli-v3/src/workers/dev/backgroundWorker.ts @@ -323,6 +323,7 @@ export class BackgroundWorker { const fullEnv = { ...this.params.env, ...this.#readEnvVars(), + ...(this.params.debugOtel ? { OTEL_LOG_LEVEL: "debug" } : {}), }; logger.debug("Initializing worker", { path: this.path, cwd, fullEnv }); diff --git a/packages/cli-v3/src/workers/dev/worker-facade.ts b/packages/cli-v3/src/workers/dev/worker-facade.ts index 14358bddb..efc65c93d 100644 --- a/packages/cli-v3/src/workers/dev/worker-facade.ts +++ b/packages/cli-v3/src/workers/dev/worker-facade.ts @@ -26,9 +26,8 @@ declare const handleError: HandleErrorFunction | undefined; declare const __PROJECT_CONFIG__: Config; declare const tracingSDK: TracingSDK; - -const otelTracer = tracingSDK.getTracer("trigger-dev-worker", packageJson.version); -const otelLogger = tracingSDK.getLogger("trigger-dev-worker", packageJson.version); +declare const otelTracer: Tracer; +declare const otelLogger: Logger; import { TaskRunErrorCodes, @@ -45,7 +44,8 @@ import { ZodMessageSender, ZodSchemaParsedError, } from "@trigger.dev/core/v3/zodMessageHandler"; -import * as packageJson from "../../../package.json"; +import type { Tracer } from "@opentelemetry/api"; +import type { Logger } from "@opentelemetry/api-logs"; declare const sender: ZodMessageSender; diff --git a/packages/cli-v3/src/workers/dev/worker-setup.ts b/packages/cli-v3/src/workers/dev/worker-setup.ts index a4364d1eb..f7804ffaa 100644 --- a/packages/cli-v3/src/workers/dev/worker-setup.ts +++ b/packages/cli-v3/src/workers/dev/worker-setup.ts @@ -1,17 +1,14 @@ -import "source-map-support/register.js"; -import { Resource } from "@opentelemetry/resources"; -import { - ProjectConfig, - SemanticInternalAttributes, - childToWorkerMessages, - taskCatalog, -} from "@trigger.dev/core/v3"; +import type { Tracer } from "@opentelemetry/api"; +import type { Logger } from "@opentelemetry/api-logs"; +import { ProjectConfig, childToWorkerMessages, taskCatalog } from "@trigger.dev/core/v3"; import { + StandardTaskCatalog, TracingDiagnosticLogLevel, TracingSDK, - StandardTaskCatalog, } from "@trigger.dev/core/v3/workers"; import { ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler"; +import "source-map-support/register.js"; +import * as packageJson from "../../../package.json"; __SETUP_IMPORTED_PROJECT_CONFIG__; declare const __SETUP_IMPORTED_PROJECT_CONFIG__: unknown; @@ -19,13 +16,13 @@ declare const setupImportedConfig: ProjectConfig | undefined; export const tracingSDK = new TracingSDK({ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318", - resource: new Resource({ - [SemanticInternalAttributes.CLI_VERSION]: "3.0.0", - }), instrumentations: setupImportedConfig?.instrumentations ?? [], diagLogLevel: (process.env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none", }); +export const otelTracer: Tracer = tracingSDK.getTracer("trigger-dev-worker", packageJson.version); +export const otelLogger: Logger = tracingSDK.getLogger("trigger-dev-worker", packageJson.version); + export const sender = new ZodMessageSender({ schema: childToWorkerMessages, sender: async (message) => { diff --git a/packages/cli-v3/src/workers/prod/worker-facade.ts b/packages/cli-v3/src/workers/prod/worker-facade.ts index 2ec213763..77271b801 100644 --- a/packages/cli-v3/src/workers/prod/worker-facade.ts +++ b/packages/cli-v3/src/workers/prod/worker-facade.ts @@ -31,9 +31,8 @@ declare const handleError: HandleErrorFunction | undefined; declare const __PROJECT_CONFIG__: Config; declare const tracingSDK: TracingSDK; - -const otelTracer = tracingSDK.getTracer("trigger-prod-worker", packageJson.version); -const otelLogger = tracingSDK.getLogger("trigger-prod-worker", packageJson.version); +declare const otelTracer: Tracer; +declare const otelLogger: Logger; import { TaskRunErrorCodes, @@ -43,7 +42,8 @@ import { runtime, } from "@trigger.dev/core/v3"; import { ProdRuntimeManager } from "@trigger.dev/core/v3/prod"; -import * as packageJson from "../../../package.json"; +import type { Tracer } from "@opentelemetry/api"; +import type { Logger } from "@opentelemetry/api-logs"; const durableClock = new DurableClock(); clock.setGlobalClock(durableClock); diff --git a/packages/cli-v3/src/workers/prod/worker-setup.ts b/packages/cli-v3/src/workers/prod/worker-setup.ts index 4bad77fd5..0546322e4 100644 --- a/packages/cli-v3/src/workers/prod/worker-setup.ts +++ b/packages/cli-v3/src/workers/prod/worker-setup.ts @@ -1,10 +1,12 @@ -import { Resource } from "@opentelemetry/resources"; -import { ProjectConfig, SemanticInternalAttributes, taskCatalog } from "@trigger.dev/core/v3"; +import type { Tracer } from "@opentelemetry/api"; +import * as packageJson from "../../../package.json"; +import { ProjectConfig, taskCatalog } from "@trigger.dev/core/v3"; import { TracingDiagnosticLogLevel, TracingSDK, StandardTaskCatalog, } from "@trigger.dev/core/v3/workers"; +import type { Logger } from "@opentelemetry/api-logs"; __SETUP_IMPORTED_PROJECT_CONFIG__; declare const __SETUP_IMPORTED_PROJECT_CONFIG__: unknown; @@ -12,11 +14,11 @@ declare const setupImportedConfig: ProjectConfig | undefined; export const tracingSDK = new TracingSDK({ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318", - resource: new Resource({ - [SemanticInternalAttributes.CLI_VERSION]: "3.0.0", - }), instrumentations: setupImportedConfig?.instrumentations ?? [], diagLogLevel: (process.env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none", }); +export const otelTracer: Tracer = tracingSDK.getTracer("trigger-prod-worker", packageJson.version); +export const otelLogger: Logger = tracingSDK.getLogger("trigger-prod-worker", packageJson.version); + taskCatalog.setGlobalTaskCatalog(new StandardTaskCatalog()); diff --git a/packages/core/src/v3/otel/tracingSDK.ts b/packages/core/src/v3/otel/tracingSDK.ts index 50d344da5..ce2b20f10 100644 --- a/packages/core/src/v3/otel/tracingSDK.ts +++ b/packages/core/src/v3/otel/tracingSDK.ts @@ -40,6 +40,7 @@ import { import { SemanticInternalAttributes } from "../semanticInternalAttributes"; import { TaskContextLogProcessor, TaskContextSpanProcessor } from "../taskContext/otelProcessors"; import { getEnvVar } from "../utils/getEnv"; +import { version } from "../../../package.json"; class AsyncResourceDetector implements DetectorSync { private _promise: Promise; @@ -111,6 +112,7 @@ export class TracingSDK { new Resource({ [SemanticResourceAttributes.CLOUD_PROVIDER]: "trigger.dev", [SemanticInternalAttributes.TRIGGER]: true, + [SemanticInternalAttributes.CLI_VERSION]: version, }) ) .merge(config.resource ?? new Resource({})) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34d2cc843..79d34dfcc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3066,8 +3066,8 @@ importers: specifier: ^2.1.2 version: 2.1.2 '@opentelemetry/api': - specifier: ^1.8.0 - version: 1.8.0 + specifier: 1.4.1 + version: 1.4.1 '@react-email/components': specifier: ^0.0.17 version: 0.0.17(@types/react@18.3.1)(react@18.2.0) @@ -3079,7 +3079,7 @@ importers: version: 2.2.1 '@traceloop/instrumentation-openai': specifier: ^0.3.9 - version: 0.3.9(@opentelemetry/api@1.8.0) + version: 0.3.9(@opentelemetry/api@1.4.1) '@trigger.dev/core': specifier: workspace:^3.0.0-beta.0 version: link:../../packages/core @@ -3125,43 +3125,43 @@ importers: devDependencies: '@opentelemetry/core': specifier: ^1.22.0 - version: 1.22.0(@opentelemetry/api@1.8.0) + version: 1.22.0(@opentelemetry/api@1.4.1) '@opentelemetry/exporter-logs-otlp-http': specifier: ^0.49.1 - version: 0.49.1(@opentelemetry/api@1.8.0) + version: 0.49.1(@opentelemetry/api@1.4.1) '@opentelemetry/exporter-trace-otlp-http': specifier: ^0.49.1 - version: 0.49.1(@opentelemetry/api@1.8.0) + version: 0.49.1(@opentelemetry/api@1.4.1) '@opentelemetry/instrumentation': specifier: ^0.49.1 - version: 0.49.1(@opentelemetry/api@1.8.0) + version: 0.49.1(@opentelemetry/api@1.4.1) '@opentelemetry/instrumentation-express': specifier: ^0.36.1 - version: 0.36.1(@opentelemetry/api@1.8.0) + version: 0.36.1(@opentelemetry/api@1.4.1) '@opentelemetry/instrumentation-fetch': specifier: ^0.49.1 - version: 0.49.1(@opentelemetry/api@1.8.0) + version: 0.49.1(@opentelemetry/api@1.4.1) '@opentelemetry/instrumentation-http': specifier: ^0.49.1 - version: 0.49.1(@opentelemetry/api@1.8.0) + version: 0.49.1(@opentelemetry/api@1.4.1) '@opentelemetry/instrumentation-undici': specifier: 0.2.0 - version: 0.2.0(@opentelemetry/api@1.8.0) + version: 0.2.0(@opentelemetry/api@1.4.1) '@opentelemetry/resources': specifier: ^1.22.0 - version: 1.22.0(@opentelemetry/api@1.8.0) + version: 1.22.0(@opentelemetry/api@1.4.1) '@opentelemetry/sdk-logs': specifier: ^0.49.1 - version: 0.49.1(@opentelemetry/api-logs@0.49.1)(@opentelemetry/api@1.8.0) + version: 0.49.1(@opentelemetry/api-logs@0.49.1)(@opentelemetry/api@1.4.1) '@opentelemetry/sdk-node': specifier: ^0.49.1 - version: 0.49.1(@opentelemetry/api@1.8.0) + version: 0.49.1(@opentelemetry/api@1.4.1) '@opentelemetry/sdk-trace-base': specifier: ^1.22.0 - version: 1.22.0(@opentelemetry/api@1.8.0) + version: 1.22.0(@opentelemetry/api@1.4.1) '@opentelemetry/sdk-trace-node': specifier: ^1.22.0 - version: 1.22.0(@opentelemetry/api@1.8.0) + version: 1.22.0(@opentelemetry/api@1.4.1) '@opentelemetry/semantic-conventions': specifier: ^1.22.0 version: 1.22.0 @@ -9290,10 +9290,23 @@ packages: '@opentelemetry/api': 1.8.0 dev: true + /@opentelemetry/api@1.4.1: + resolution: {integrity: sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==} + engines: {node: '>=8.0.0'} + /@opentelemetry/api@1.8.0: resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==} engines: {node: '>=8.0.0'} + /@opentelemetry/context-async-hooks@1.22.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-Nfdxyg8YtWqVWkyrCukkundAjPhUXi93JtVQmqDT1mZRVKqA7e2r7eJCrI+F651XUBMp0hsOJSGiFk3QSpaIJw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + dependencies: + '@opentelemetry/api': 1.4.1 + dev: true + /@opentelemetry/context-async-hooks@1.22.0(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-Nfdxyg8YtWqVWkyrCukkundAjPhUXi93JtVQmqDT1mZRVKqA7e2r7eJCrI+F651XUBMp0hsOJSGiFk3QSpaIJw==} engines: {node: '>=14'} @@ -9301,6 +9314,16 @@ packages: '@opentelemetry/api': '>=1.0.0 <1.9.0' dependencies: '@opentelemetry/api': 1.8.0 + dev: false + + /@opentelemetry/core@1.22.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-0VoAlT6x+Xzik1v9goJ3pZ2ppi6+xd3aUfg4brfrLkDBHRIVjMP0eBHrKrhB+NKcDyMAg8fAbGL3Npg/F6AwWA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/semantic-conventions': 1.22.0 /@opentelemetry/core@1.22.0(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-0VoAlT6x+Xzik1v9goJ3pZ2ppi6+xd3aUfg4brfrLkDBHRIVjMP0eBHrKrhB+NKcDyMAg8fAbGL3Npg/F6AwWA==} @@ -9310,6 +9333,21 @@ packages: dependencies: '@opentelemetry/api': 1.8.0 '@opentelemetry/semantic-conventions': 1.22.0 + dev: false + + /@opentelemetry/exporter-logs-otlp-http@0.49.1(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-3QoBnIGCmEkujynUP0mK155QtOM0MSf9FNrEw7u9ieCFsoMiyatg2hPp+alEDONJ8N8wGEK+wP2q3icgXBiggw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/api-logs': 0.49.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/otlp-exporter-base': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/otlp-transformer': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-logs': 0.49.1(@opentelemetry/api-logs@0.49.1)(@opentelemetry/api@1.4.1) + dev: true /@opentelemetry/exporter-logs-otlp-http@0.49.1(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-3QoBnIGCmEkujynUP0mK155QtOM0MSf9FNrEw7u9ieCFsoMiyatg2hPp+alEDONJ8N8wGEK+wP2q3icgXBiggw==} @@ -9323,6 +9361,22 @@ packages: '@opentelemetry/otlp-exporter-base': 0.49.1(@opentelemetry/api@1.8.0) '@opentelemetry/otlp-transformer': 0.49.1(@opentelemetry/api@1.8.0) '@opentelemetry/sdk-logs': 0.49.1(@opentelemetry/api-logs@0.49.1)(@opentelemetry/api@1.8.0) + dev: false + + /@opentelemetry/exporter-trace-otlp-grpc@0.49.1(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-Zbd7f3zF7fI2587MVhBizaW21cO/SordyrZGtMtvhoxU6n4Qb02Gx71X4+PzXH620e0+JX+Pcr9bYb1HTeVyJA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + dependencies: + '@grpc/grpc-js': 1.8.17 + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/otlp-transformer': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.4.1) + dev: true /@opentelemetry/exporter-trace-otlp-grpc@0.49.1(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-Zbd7f3zF7fI2587MVhBizaW21cO/SordyrZGtMtvhoxU6n4Qb02Gx71X4+PzXH620e0+JX+Pcr9bYb1HTeVyJA==} @@ -9337,6 +9391,21 @@ packages: '@opentelemetry/otlp-transformer': 0.49.1(@opentelemetry/api@1.8.0) '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.8.0) + dev: false + + /@opentelemetry/exporter-trace-otlp-http@0.49.1(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-KOLtZfZvIrpGZLVvblKsiVQT7gQUZNKcUUH24Zz6Xbi7LJb9Vt6xtUZFYdR5IIjvt47PIqBKDWUQlU0o1wAsRw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/otlp-exporter-base': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/otlp-transformer': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.4.1) + dev: true /@opentelemetry/exporter-trace-otlp-http@0.49.1(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-KOLtZfZvIrpGZLVvblKsiVQT7gQUZNKcUUH24Zz6Xbi7LJb9Vt6xtUZFYdR5IIjvt47PIqBKDWUQlU0o1wAsRw==} @@ -9350,6 +9419,22 @@ packages: '@opentelemetry/otlp-transformer': 0.49.1(@opentelemetry/api@1.8.0) '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.8.0) + dev: false + + /@opentelemetry/exporter-trace-otlp-proto@0.49.1(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-n8ON/c9pdMyYAfSFWKkgsPwjYoxnki+6Olzo+klKfW7KqLWoyEkryNkbcMIYnGGNXwdkMIrjoaP0VxXB26Oxcg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/otlp-exporter-base': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/otlp-proto-exporter-base': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/otlp-transformer': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.4.1) + dev: true /@opentelemetry/exporter-trace-otlp-proto@0.49.1(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-n8ON/c9pdMyYAfSFWKkgsPwjYoxnki+6Olzo+klKfW7KqLWoyEkryNkbcMIYnGGNXwdkMIrjoaP0VxXB26Oxcg==} @@ -9364,6 +9449,20 @@ packages: '@opentelemetry/otlp-transformer': 0.49.1(@opentelemetry/api@1.8.0) '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.8.0) + dev: false + + /@opentelemetry/exporter-zipkin@1.22.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-XcFs6rGvcTz0qW5uY7JZDYD0yNEXdekXAb6sFtnZgY/cHY6BQ09HMzOjv9SX+iaXplRDcHr1Gta7VQKM1XXM6g==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.22.0 + dev: true /@opentelemetry/exporter-zipkin@1.22.0(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-XcFs6rGvcTz0qW5uY7JZDYD0yNEXdekXAb6sFtnZgY/cHY6BQ09HMzOjv9SX+iaXplRDcHr1Gta7VQKM1XXM6g==} @@ -9376,6 +9475,21 @@ packages: '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/semantic-conventions': 1.22.0 + dev: false + + /@opentelemetry/instrumentation-express@0.36.1(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-ltIE4kIMa+83QjW/p7oe7XCESF29w3FQ9/T1VgShdX7fzm56K2a0xfEX1vF8lnHRGERYxIWX9D086C6gJOjVGA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/instrumentation': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.22.0 + transitivePeerDependencies: + - supports-color + dev: true /@opentelemetry/instrumentation-express@0.36.1(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-ltIE4kIMa+83QjW/p7oe7XCESF29w3FQ9/T1VgShdX7fzm56K2a0xfEX1vF8lnHRGERYxIWX9D086C6gJOjVGA==} @@ -9389,6 +9503,22 @@ packages: '@opentelemetry/semantic-conventions': 1.22.0 transitivePeerDependencies: - supports-color + dev: false + + /@opentelemetry/instrumentation-fetch@0.49.1(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-hizhULZXlq02y8YC0vPQ4WtUWiXcwxPdEqHBy8p75jzF9rAuP/ldrVr0Oxvz5Xr9qQcdEOFLvEl0ZxbVL76WKw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/instrumentation': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-web': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.22.0 + transitivePeerDependencies: + - supports-color + dev: true /@opentelemetry/instrumentation-fetch@0.49.1(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-hizhULZXlq02y8YC0vPQ4WtUWiXcwxPdEqHBy8p75jzF9rAuP/ldrVr0Oxvz5Xr9qQcdEOFLvEl0ZxbVL76WKw==} @@ -9403,6 +9533,22 @@ packages: '@opentelemetry/semantic-conventions': 1.22.0 transitivePeerDependencies: - supports-color + dev: false + + /@opentelemetry/instrumentation-http@0.49.1(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-Yib5zrW2s0V8wTeUK/B3ZtpyP4ldgXj9L3Ws/axXrW1dW0/mEFKifK50MxMQK9g5NNJQS9dWH7rvcEGZdWdQDA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/instrumentation': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.22.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + dev: true /@opentelemetry/instrumentation-http@0.49.1(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-Yib5zrW2s0V8wTeUK/B3ZtpyP4ldgXj9L3Ws/axXrW1dW0/mEFKifK50MxMQK9g5NNJQS9dWH7rvcEGZdWdQDA==} @@ -9417,27 +9563,28 @@ packages: semver: 7.5.4 transitivePeerDependencies: - supports-color + dev: false - /@opentelemetry/instrumentation-undici@0.2.0(@opentelemetry/api@1.8.0): + /@opentelemetry/instrumentation-undici@0.2.0(@opentelemetry/api@1.4.1): resolution: {integrity: sha512-RH9WdVRtpnyp8kvya2RYqKsJouPxvHl7jKPsIfrbL8u2QCKloAGi0uEqDHoOS15ZRYPQTDXZ7d8jSpUgSQmvpA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.7.0 dependencies: - '@opentelemetry/api': 1.8.0 - '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) - '@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0) + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.4.1) transitivePeerDependencies: - supports-color dev: true - /@opentelemetry/instrumentation@0.48.0(@opentelemetry/api@1.8.0): + /@opentelemetry/instrumentation@0.48.0(@opentelemetry/api@1.4.1): resolution: {integrity: sha512-sjtZQB5PStIdCw5ovVTDGwnmQC+GGYArJNgIcydrDSqUTdYBnMrN9P4pwQZgS3vTGIp+TU1L8vMXGe51NVmIKQ==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 dependencies: - '@opentelemetry/api': 1.8.0 + '@opentelemetry/api': 1.4.1 '@types/shimmer': 1.0.2 import-in-the-middle: 1.7.1 require-in-the-middle: 7.1.1 @@ -9447,6 +9594,23 @@ packages: - supports-color dev: false + /@opentelemetry/instrumentation@0.49.1(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-0DLtWtaIppuNNRRllSD4bjU8ZIiLp1cDXvJEbp752/Zf+y3gaLNaoGRGIlX4UHhcsrmtL+P2qxi3Hodi8VuKiQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/api-logs': 0.49.1 + '@types/shimmer': 1.0.2 + import-in-the-middle: 1.7.1 + require-in-the-middle: 7.1.1 + semver: 7.5.4 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + dev: true + /@opentelemetry/instrumentation@0.49.1(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-0DLtWtaIppuNNRRllSD4bjU8ZIiLp1cDXvJEbp752/Zf+y3gaLNaoGRGIlX4UHhcsrmtL+P2qxi3Hodi8VuKiQ==} engines: {node: '>=14'} @@ -9462,14 +9626,15 @@ packages: shimmer: 1.2.1 transitivePeerDependencies: - supports-color + dev: false - /@opentelemetry/instrumentation@0.51.1(@opentelemetry/api@1.8.0): + /@opentelemetry/instrumentation@0.51.1(@opentelemetry/api@1.4.1): resolution: {integrity: sha512-JIrvhpgqY6437QIqToyozrUG1h5UhwHkaGK/WAX+fkrpyPtc+RO5FkRtUd9BH0MibabHHvqsnBGKfKVijbmp8w==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 dependencies: - '@opentelemetry/api': 1.8.0 + '@opentelemetry/api': 1.4.1 '@opentelemetry/api-logs': 0.51.1 '@types/shimmer': 1.0.2 import-in-the-middle: 1.7.4 @@ -9480,6 +9645,16 @@ packages: - supports-color dev: true + /@opentelemetry/otlp-exporter-base@0.49.1(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-z6sHliPqDgJU45kQatAettY9/eVF58qVPaTuejw9YWfSRqid9pXPYeegDCSdyS47KAUgAtm+nC28K3pfF27HWg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + dev: true + /@opentelemetry/otlp-exporter-base@0.49.1(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-z6sHliPqDgJU45kQatAettY9/eVF58qVPaTuejw9YWfSRqid9pXPYeegDCSdyS47KAUgAtm+nC28K3pfF27HWg==} engines: {node: '>=14'} @@ -9488,6 +9663,20 @@ packages: dependencies: '@opentelemetry/api': 1.8.0 '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) + dev: false + + /@opentelemetry/otlp-grpc-exporter-base@0.49.1(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-DNDNUWmOqtKTFJAyOyHHKotVox0NQ/09ETX8fUOeEtyNVHoGekAVtBbvIA3AtK+JflP7LC0PTjlLfruPM3Wy6w==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + dependencies: + '@grpc/grpc-js': 1.8.17 + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/otlp-exporter-base': 0.49.1(@opentelemetry/api@1.4.1) + protobufjs: 7.2.6 + dev: true /@opentelemetry/otlp-grpc-exporter-base@0.49.1(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-DNDNUWmOqtKTFJAyOyHHKotVox0NQ/09ETX8fUOeEtyNVHoGekAVtBbvIA3AtK+JflP7LC0PTjlLfruPM3Wy6w==} @@ -9500,6 +9689,19 @@ packages: '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/otlp-exporter-base': 0.49.1(@opentelemetry/api@1.8.0) protobufjs: 7.2.6 + dev: false + + /@opentelemetry/otlp-proto-exporter-base@0.49.1(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-x1qB4EUC7KikUl2iNuxCkV8yRzrSXSyj4itfpIO674H7dhI7Zv37SFaOJTDN+8Z/F50gF2ISFH9CWQ4KCtGm2A==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/otlp-exporter-base': 0.49.1(@opentelemetry/api@1.4.1) + protobufjs: 7.2.6 + dev: true /@opentelemetry/otlp-proto-exporter-base@0.49.1(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-x1qB4EUC7KikUl2iNuxCkV8yRzrSXSyj4itfpIO674H7dhI7Zv37SFaOJTDN+8Z/F50gF2ISFH9CWQ4KCtGm2A==} @@ -9511,6 +9713,22 @@ packages: '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/otlp-exporter-base': 0.49.1(@opentelemetry/api@1.8.0) protobufjs: 7.2.6 + dev: false + + /@opentelemetry/otlp-transformer@0.49.1(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-Z+koA4wp9L9e3jkFacyXTGphSWTbOKjwwXMpb0CxNb0kjTHGUxhYRN8GnkLFsFo5NbZPjP07hwAqeEG/uCratQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.9.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/api-logs': 0.49.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-logs': 0.49.1(@opentelemetry/api-logs@0.49.1)(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-metrics': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.4.1) + dev: true /@opentelemetry/otlp-transformer@0.49.1(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-Z+koA4wp9L9e3jkFacyXTGphSWTbOKjwwXMpb0CxNb0kjTHGUxhYRN8GnkLFsFo5NbZPjP07hwAqeEG/uCratQ==} @@ -9525,6 +9743,17 @@ packages: '@opentelemetry/sdk-logs': 0.49.1(@opentelemetry/api-logs@0.49.1)(@opentelemetry/api@1.8.0) '@opentelemetry/sdk-metrics': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.8.0) + dev: false + + /@opentelemetry/propagator-b3@1.22.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-qBItJm9ygg/jCB5rmivyGz1qmKZPsL/sX715JqPMFgq++Idm0x+N9sLQvWFHFt2+ZINnCSojw7FVBgFW6izcXA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + dev: true /@opentelemetry/propagator-b3@1.22.0(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-qBItJm9ygg/jCB5rmivyGz1qmKZPsL/sX715JqPMFgq++Idm0x+N9sLQvWFHFt2+ZINnCSojw7FVBgFW6izcXA==} @@ -9534,6 +9763,17 @@ packages: dependencies: '@opentelemetry/api': 1.8.0 '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) + dev: false + + /@opentelemetry/propagator-jaeger@1.22.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-pMLgst3QIwrUfepraH5WG7xfpJ8J3CrPKrtINK0t7kBkuu96rn+HDYQ8kt3+0FXvrZI8YJE77MCQwnJWXIrgpA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + dev: true /@opentelemetry/propagator-jaeger@1.22.0(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-pMLgst3QIwrUfepraH5WG7xfpJ8J3CrPKrtINK0t7kBkuu96rn+HDYQ8kt3+0FXvrZI8YJE77MCQwnJWXIrgpA==} @@ -9543,6 +9783,18 @@ packages: dependencies: '@opentelemetry/api': 1.8.0 '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) + dev: false + + /@opentelemetry/resources@1.22.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-+vNeIFPH2hfcNL0AJk/ykJXoUCtR1YaDUZM+p3wZNU4Hq98gzq+7b43xbkXjadD9VhWIUQqEwXyY64q6msPj6A==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.22.0 + dev: true /@opentelemetry/resources@1.22.0(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-+vNeIFPH2hfcNL0AJk/ykJXoUCtR1YaDUZM+p3wZNU4Hq98gzq+7b43xbkXjadD9VhWIUQqEwXyY64q6msPj6A==} @@ -9553,6 +9805,7 @@ packages: '@opentelemetry/api': 1.8.0 '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/semantic-conventions': 1.22.0 + dev: false /@opentelemetry/sdk-logs@0.49.1(@opentelemetry/api-logs@0.48.0)(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-gCzYWsJE0h+3cuh3/cK+9UwlVFyHvj3PReIOCDOmdeXOp90ZjKRoDOJBc3mvk1LL6wyl1RWIivR8Rg9OToyesw==} @@ -9567,6 +9820,19 @@ packages: '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0) dev: false + /@opentelemetry/sdk-logs@0.49.1(@opentelemetry/api-logs@0.49.1)(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-gCzYWsJE0h+3cuh3/cK+9UwlVFyHvj3PReIOCDOmdeXOp90ZjKRoDOJBc3mvk1LL6wyl1RWIivR8Rg9OToyesw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.9.0' + '@opentelemetry/api-logs': '>=0.39.1' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/api-logs': 0.49.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.4.1) + dev: true + /@opentelemetry/sdk-logs@0.49.1(@opentelemetry/api-logs@0.49.1)(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-gCzYWsJE0h+3cuh3/cK+9UwlVFyHvj3PReIOCDOmdeXOp90ZjKRoDOJBc3mvk1LL6wyl1RWIivR8Rg9OToyesw==} engines: {node: '>=14'} @@ -9578,6 +9844,19 @@ packages: '@opentelemetry/api-logs': 0.49.1 '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0) + dev: false + + /@opentelemetry/sdk-metrics@1.22.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-k6iIx6H3TZ+BVMr2z8M16ri2OxWaljg5h8ihGJxi/KQWcjign6FEaEzuigXt5bK9wVEhqAcWLCfarSftaNWkkg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.9.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.4.1) + lodash.merge: 4.6.2 + dev: true /@opentelemetry/sdk-metrics@1.22.0(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-k6iIx6H3TZ+BVMr2z8M16ri2OxWaljg5h8ihGJxi/KQWcjign6FEaEzuigXt5bK9wVEhqAcWLCfarSftaNWkkg==} @@ -9589,6 +9868,31 @@ packages: '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0) lodash.merge: 4.6.2 + dev: false + + /@opentelemetry/sdk-node@0.49.1(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-feBIT85ndiSHXsQ2gfGpXC/sNeX4GCHLksC4A9s/bfpUbbgbCSl0RvzZlmEpCHarNrkZMwFRi4H0xFfgvJEjrg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.9.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/api-logs': 0.49.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/exporter-trace-otlp-grpc': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/exporter-trace-otlp-http': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/exporter-trace-otlp-proto': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/exporter-zipkin': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/instrumentation': 0.49.1(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-logs': 0.49.1(@opentelemetry/api-logs@0.49.1)(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-metrics': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-node': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.22.0 + transitivePeerDependencies: + - supports-color + dev: true /@opentelemetry/sdk-node@0.49.1(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-feBIT85ndiSHXsQ2gfGpXC/sNeX4GCHLksC4A9s/bfpUbbgbCSl0RvzZlmEpCHarNrkZMwFRi4H0xFfgvJEjrg==} @@ -9612,6 +9916,19 @@ packages: '@opentelemetry/semantic-conventions': 1.22.0 transitivePeerDependencies: - supports-color + dev: false + + /@opentelemetry/sdk-trace-base@1.22.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-pfTuSIpCKONC6vkTpv6VmACxD+P1woZf4q0K46nSUvXFvOFqjBYKFaAMkKD3M1mlKUUh0Oajwj35qNjMl80m1Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.22.0 + dev: true /@opentelemetry/sdk-trace-base@1.22.0(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-pfTuSIpCKONC6vkTpv6VmACxD+P1woZf4q0K46nSUvXFvOFqjBYKFaAMkKD3M1mlKUUh0Oajwj35qNjMl80m1Q==} @@ -9623,6 +9940,22 @@ packages: '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/resources': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/semantic-conventions': 1.22.0 + dev: false + + /@opentelemetry/sdk-trace-node@1.22.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-gTGquNz7ue8uMeiWPwp3CU321OstQ84r7PCDtOaCicjbJxzvO8RZMlEC4geOipTeiF88kss5n6w+//A0MhP1lQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/context-async-hooks': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/propagator-b3': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/propagator-jaeger': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.4.1) + semver: 7.5.4 + dev: true /@opentelemetry/sdk-trace-node@1.22.0(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-gTGquNz7ue8uMeiWPwp3CU321OstQ84r7PCDtOaCicjbJxzvO8RZMlEC4geOipTeiF88kss5n6w+//A0MhP1lQ==} @@ -9637,6 +9970,19 @@ packages: '@opentelemetry/propagator-jaeger': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.8.0) semver: 7.5.4 + dev: false + + /@opentelemetry/sdk-trace-web@1.22.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-id5bUhWYg475xbm4hjwWA4PnWM4duNK1EyFRkZxa3BZNuCITwiKCLvDkVhlE9RK2kvuDOPmcRxgSbU1apF9/1w==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.9.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.22.0 + dev: true /@opentelemetry/sdk-trace-web@1.22.0(@opentelemetry/api@1.8.0): resolution: {integrity: sha512-id5bUhWYg475xbm4hjwWA4PnWM4duNK1EyFRkZxa3BZNuCITwiKCLvDkVhlE9RK2kvuDOPmcRxgSbU1apF9/1w==} @@ -9648,6 +9994,7 @@ packages: '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/sdk-trace-base': 1.22.0(@opentelemetry/api@1.8.0) '@opentelemetry/semantic-conventions': 1.22.0 + dev: false /@opentelemetry/semantic-conventions@1.22.0: resolution: {integrity: sha512-CAOgFOKLybd02uj/GhCdEeeBjOS0yeoDeo/CA7ASBSmenpZHAKGB3iDm/rv3BQLcabb/OprDEsSQ1y0P8A7Siw==} @@ -14878,12 +15225,12 @@ packages: engines: {node: '>=14'} dev: false - /@traceloop/instrumentation-openai@0.3.9(@opentelemetry/api@1.8.0): + /@traceloop/instrumentation-openai@0.3.9(@opentelemetry/api@1.4.1): resolution: {integrity: sha512-Ru2QBK6crx0dtWXd4aMyFNRP9cA2WF+8Rjt2ctOgu/R7M7fmNvtcXveY0qXZ1doKYzzt0+icZqs0ahJEn8WYcQ==} engines: {node: '>=14'} dependencies: - '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.8.0) - '@opentelemetry/instrumentation': 0.48.0(@opentelemetry/api@1.8.0) + '@opentelemetry/core': 1.22.0(@opentelemetry/api@1.4.1) + '@opentelemetry/instrumentation': 0.48.0(@opentelemetry/api@1.4.1) '@opentelemetry/semantic-conventions': 1.22.0 '@traceloop/ai-semantic-conventions': 0.3.8 transitivePeerDependencies: diff --git a/references/v3-catalog/package.json b/references/v3-catalog/package.json index 9bbc8631c..d1f97e766 100644 --- a/references/v3-catalog/package.json +++ b/references/v3-catalog/package.json @@ -9,7 +9,7 @@ "dependencies": { "@ffmpeg-installer/ffmpeg": "^1.1.0", "@ffprobe-installer/ffprobe": "^2.1.2", - "@opentelemetry/api": "^1.8.0", + "@opentelemetry/api": "1.4.1", "@react-email/components": "^0.0.17", "@react-email/render": "^0.0.7", "@sindresorhus/slugify": "^2.2.1", @@ -30,27 +30,27 @@ "yt-dlp-wrap": "^2.3.12" }, "devDependencies": { - "@trigger.dev/tsconfig": "workspace:*", - "@types/node": "20.4.2", - "@types/react": "^18.3.1", - "trigger.dev": "workspace:*", - "ts-node": "^10.9.2", - "tsconfig-paths": "^4.2.0", - "typescript": "^5.3.0", "@opentelemetry/api": "^1.8.0", "@opentelemetry/core": "^1.22.0", "@opentelemetry/exporter-logs-otlp-http": "^0.49.1", "@opentelemetry/exporter-trace-otlp-http": "^0.49.1", "@opentelemetry/instrumentation": "^0.49.1", "@opentelemetry/instrumentation-express": "^0.36.1", + "@opentelemetry/instrumentation-fetch": "^0.49.1", "@opentelemetry/instrumentation-http": "^0.49.1", + "@opentelemetry/instrumentation-undici": "0.2.0", "@opentelemetry/resources": "^1.22.0", "@opentelemetry/sdk-logs": "^0.49.1", "@opentelemetry/sdk-node": "^0.49.1", "@opentelemetry/sdk-trace-base": "^1.22.0", "@opentelemetry/sdk-trace-node": "^1.22.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@opentelemetry/instrumentation-fetch": "^0.49.1", - "@opentelemetry/instrumentation-undici": "0.2.0" + "@trigger.dev/tsconfig": "workspace:*", + "@types/node": "20.4.2", + "@types/react": "^18.3.1", + "trigger.dev": "workspace:*", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.3.0" } } \ No newline at end of file diff --git a/references/v3-catalog/src/telemetry.ts b/references/v3-catalog/src/telemetry.ts new file mode 100644 index 000000000..105c9e40c --- /dev/null +++ b/references/v3-catalog/src/telemetry.ts @@ -0,0 +1,13 @@ +import { trace } from "@opentelemetry/api"; + +export const tracer = trace.getTracer("v3-catalog", "3.0.0.dp.1"); + +export function traceAsync(name: string, fn: () => Promise): Promise { + return tracer.startActiveSpan(name, async (span) => { + try { + return await fn(); + } finally { + span.end(); + } + }); +} diff --git a/references/v3-catalog/src/trigger/simple.ts b/references/v3-catalog/src/trigger/simple.ts index da727e750..6b150625d 100644 --- a/references/v3-catalog/src/trigger/simple.ts +++ b/references/v3-catalog/src/trigger/simple.ts @@ -1,5 +1,6 @@ import "server-only"; import { logger, task, wait } from "@trigger.dev/sdk/v3"; +import { traceAsync } from "@/telemetry"; export const simplestTask = task({ id: "fetch-post-task", @@ -20,6 +21,10 @@ export const simplestTask = task({ export const taskWithSpecialCharacters = task({ id: "admin:special-characters", run: async (payload: { url: string }) => { + await traceAsync("taskWithSpecialCharacters", async () => { + await new Promise((resolve) => setTimeout(resolve, 1000)); + }); + return { message: "This task has special characters in its ID", }; From a86f36cefac27f97e3bb2194ca35fa21aa45d6a4 Mon Sep 17 00:00:00 2001 From: Alexandre Costa Date: Tue, 21 May 2024 10:40:49 -0300 Subject: [PATCH 09/27] Fix TypeScript inclusion in tsconfig.json for `cli-v3 init` (#1105) * Fix TypeScript inclusion in tsconfig.json for cli-v3 init Fixed an issue where TypeScript files were included in the project directory when no include directive was present in tsconfig.json. Previously, the CLI added trigger.config.ts to the inclusion list by default, causing TypeScript compilation errors for other files. The fix ensures that trigger.config.ts is only added to the inclusion list if there's an existing include directive present in tsconfig.json * Create hot-fishes-retire.md --------- Co-authored-by: Eric Allam --- .changeset/hot-fishes-retire.md | 5 +++++ packages/cli-v3/src/commands/init.ts | 25 +++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 .changeset/hot-fishes-retire.md diff --git a/.changeset/hot-fishes-retire.md b/.changeset/hot-fishes-retire.md new file mode 100644 index 000000000..778a5d5d4 --- /dev/null +++ b/.changeset/hot-fishes-retire.md @@ -0,0 +1,5 @@ +--- +trigger.dev: patch +--- + +Fix TypeScript inclusion in tsconfig.json for `cli-v3 init` diff --git a/packages/cli-v3/src/commands/init.ts b/packages/cli-v3/src/commands/init.ts index 675728a8e..3611b0486 100644 --- a/packages/cli-v3/src/commands/init.ts +++ b/packages/cli-v3/src/commands/init.ts @@ -5,7 +5,7 @@ import { recordSpanException } from "@trigger.dev/core/v3/workers"; import chalk from "chalk"; import { Command } from "commander"; import { execa } from "execa"; -import { applyEdits, modify } from "jsonc-parser"; +import { applyEdits, modify, findNodeAtLocation, parseTree, getNodeValue } from "jsonc-parser"; import { writeFile } from "node:fs/promises"; import { join, relative, resolve } from "node:path"; import terminalLink from "terminal-link"; @@ -329,8 +329,29 @@ async function addConfigFileToTsConfig(dir: string, options: InitCommandOptions) }); const tsconfigContent = await readFile(tsconfigPath); + const tsconfigContentTree = parseTree(tsconfigContent, undefined); + if (!tsconfigContentTree) { + span.end(); - const edits = modify(tsconfigContent, ["include", -1], "trigger.config.ts", { + return; + } + + const tsconfigIncludeOption = findNodeAtLocation(tsconfigContentTree, ["include"]); + if (!tsconfigIncludeOption) { + span.end(); + + return; + } + + const tsConfigFileName = "trigger.config.ts"; + const tsconfigIncludeOptionValue: string[] = getNodeValue(tsconfigIncludeOption); + if (tsconfigIncludeOptionValue.includes(tsConfigFileName)) { + span.end(); + + return; + } + + const edits = modify(tsconfigContent, ["include", -1], tsConfigFileName, { isArrayInsertion: true, formattingOptions: { tabSize: 2, From c092c0f9db70039e2f1bc495547a723c1f1a21cf Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Wed, 22 May 2024 15:46:13 +0100 Subject: [PATCH 10/27] v3: Prevent legacy-peer-deps=true from breaking deploys --- .changeset/warm-planes-taste.md | 7 +++++++ packages/cli-v3/src/commands/deploy.ts | 29 ++++++++++++++++++++++---- pnpm-lock.yaml | 13 ++++++------ references/v3-catalog/package.json | 2 +- 4 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 .changeset/warm-planes-taste.md diff --git a/.changeset/warm-planes-taste.md b/.changeset/warm-planes-taste.md new file mode 100644 index 000000000..baef6d88c --- /dev/null +++ b/.changeset/warm-planes-taste.md @@ -0,0 +1,7 @@ +--- +"trigger.dev": patch +--- + +v3: Prevent legacy-peer-deps=true from breaking deploys + +When a global `.npmrc` file includes `legacy-peer-deps=true`, deploys would fail on the `npm ci` step because the package-lock.json wouldn't match the `package.json` file. This is because inside the image build, the `.npmrc` file would not be picked up and so `legacy-peer-deps` would end up being false (which is the default). This change forces the `package-lock.json` file to be created using `legacy-peer-deps=false` diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 262f2bf1d..5b9c8f196 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -1452,10 +1452,31 @@ async function resolveDependencies( logger.debug(`No cached package-lock.json found for ${digest}`); try { - await execa("npm", ["install", "--package-lock-only", "--ignore-scripts", "--no-audit"], { - cwd: projectDir, - stdio: logger.loggerLevel === "debug" ? "inherit" : "pipe", - }); + if (logger.loggerLevel === "debug") { + const childProcess = await execa("npm", ["config", "list"], { + cwd: projectDir, + stdio: "inherit", + }); + + logger.debug("npm config list"); + console.log(childProcess.stdout); + } + + await execa( + "npm", + [ + "install", + "--package-lock-only", + "--ignore-scripts", + "--no-audit", + "--legacy-peer-deps=false", + "--strict-peer-deps=false", + ], + { + cwd: projectDir, + stdio: logger.loggerLevel === "debug" ? "inherit" : "pipe", + } + ); const packageLockContents = await readFile(join(projectDir, "package-lock.json"), "utf-8"); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79d34dfcc..908897bfd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3096,8 +3096,8 @@ importers: specifier: ^2.2.1 version: 2.2.1(typescript@5.3.3) openai: - specifier: ^4.28.0 - version: 4.28.0 + specifier: ^4.47.0 + version: 4.47.1 pg: specifier: ^8.11.5 version: 8.11.5 @@ -28164,15 +28164,14 @@ packages: - encoding dev: false - /openai@4.28.0: - resolution: {integrity: sha512-JM8fhcpmpGN0vrUwGquYIzdcEQHtFuom6sRCbbCM6CfzZXNuRk33G7KfeRAIfnaCxSpzrP5iHtwJzIm6biUZ2Q==} + /openai@4.33.1: + resolution: {integrity: sha512-0DH572aSxGTT1JPOXgJQ9mjiuSPg/7scPot8hLc5I1mfQxPxLXTZWJpWerKaIWOuPkR2nrB0SamGDEehH8RuWA==} hasBin: true dependencies: '@types/node': 18.19.20 '@types/node-fetch': 2.6.4 abort-controller: 3.0.0 agentkeepalive: 4.5.0 - digest-fetch: 1.3.0 form-data-encoder: 1.7.2 formdata-node: 4.4.1 node-fetch: 2.6.12 @@ -28181,8 +28180,8 @@ packages: - encoding dev: false - /openai@4.33.1: - resolution: {integrity: sha512-0DH572aSxGTT1JPOXgJQ9mjiuSPg/7scPot8hLc5I1mfQxPxLXTZWJpWerKaIWOuPkR2nrB0SamGDEehH8RuWA==} + /openai@4.47.1: + resolution: {integrity: sha512-WWSxhC/69ZhYWxH/OBsLEirIjUcfpQ5+ihkXKp06hmeYXgBBIUCa9IptMzYx6NdkiOCsSGYCnTIsxaic3AjRCQ==} hasBin: true dependencies: '@types/node': 18.19.20 diff --git a/references/v3-catalog/package.json b/references/v3-catalog/package.json index d1f97e766..40b5da4c0 100644 --- a/references/v3-catalog/package.json +++ b/references/v3-catalog/package.json @@ -19,7 +19,7 @@ "dotenv": "^16.4.5", "execa": "^8.0.1", "msw": "^2.2.1", - "openai": "^4.28.0", + "openai": "^4.47.0", "pg": "^8.11.5", "react": "^18.2.0", "react-email": "^1.6.1", From c815f28c8484b37eb31ec114024c21f0807393f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 22 May 2024 17:12:40 +0200 Subject: [PATCH 11/27] Update contributing instructions (#1121) * Add OPEN_API_KEY env var in example file * Update prerequisites and first-time setup --- CONTRIBUTING.md | 5 +++++ references/v3-catalog/.env.example | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f4a8ad5f0..a5ca12fc1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,6 +17,7 @@ branch are tagged into a release periodically. - [Node.js](https://nodejs.org/en) version 20.11.1 - [pnpm package manager](https://pnpm.io/installation) version 8.15.5 - [Docker](https://www.docker.com/get-started/) +- [protobuf](https://github.com/protocolbuffers/protobuf) ### Setup @@ -102,13 +103,17 @@ First, make sure you are running the webapp according to the instructions above. 4. Build the CLI ```sh +# Build the CLI pnpm run build --filter trigger.dev +# Make it accessible to `pnpm exec` +pnpm i ``` 5. Change into the `/references/v3-catalog` directory and authorize the CLI to the local server: ```sh cd references/v3-catalog +cp .env.example .env pnpm exec triggerdev login -a http://localhost:3030 ``` diff --git a/references/v3-catalog/.env.example b/references/v3-catalog/.env.example index b6ec995cb..9001181b6 100644 --- a/references/v3-catalog/.env.example +++ b/references/v3-catalog/.env.example @@ -1,2 +1,3 @@ TRIGGER_SECRET_KEY= -TRIGGER_API_URL= \ No newline at end of file +TRIGGER_API_URL= +OPENAI_API_KEY="My API Key" \ No newline at end of file From 116766f3984bee4711dcf33a717ddb412dbf931c Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 22 May 2024 19:38:42 +0100 Subject: [PATCH 12/27] Request v3 access (from the app) and disable v2 projects by default (#1123) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added v2Enabled and hasRequestedV3 columns to Organization * Don’t create a project when you create an org * Form for requesting v3 access * Reworked the new project form with the different version states. Refined copy on early access * If the project isn’t in the org then redirect to the new project page * Better message for existing users * Tidy imports * If it’s not the managed cloud then allow them to create v2 projects --- apps/webapp/app/assets/icons/v3.svg | 4 + apps/webapp/app/models/organization.server.ts | 17 +- .../OrganizationsPresenter.server.ts | 4 + .../route.tsx | 42 +++- .../webapp/app/routes/_app.orgs.new/route.tsx | 71 +----- ...urces.orgs.$organizationSlug.v3-access.tsx | 210 ++++++++++++++++++ .../migration.sql | 2 + .../migration.sql | 2 + packages/database/prisma/schema.prisma | 4 +- 9 files changed, 267 insertions(+), 89 deletions(-) create mode 100644 apps/webapp/app/assets/icons/v3.svg create mode 100644 apps/webapp/app/routes/resources.orgs.$organizationSlug.v3-access.tsx create mode 100644 packages/database/prisma/migrations/20240522130825_org_v2_enabled_flag_defaults_to_false/migration.sql create mode 100644 packages/database/prisma/migrations/20240522134117_organization_added_has_requested_v3_column/migration.sql diff --git a/apps/webapp/app/assets/icons/v3.svg b/apps/webapp/app/assets/icons/v3.svg new file mode 100644 index 000000000..d9aa6523c --- /dev/null +++ b/apps/webapp/app/assets/icons/v3.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/webapp/app/models/organization.server.ts b/apps/webapp/app/models/organization.server.ts index e2dd1d87d..814f946f3 100644 --- a/apps/webapp/app/models/organization.server.ts +++ b/apps/webapp/app/models/organization.server.ts @@ -21,16 +21,12 @@ export async function createOrganization( { title, userId, - projectName, companySize, - projectVersion, }: Pick & { userId: User["id"]; - projectName: string; - projectVersion: "v2" | "v3"; }, attemptCount = 0 -): Promise { +): Promise { if (typeof process.env.BLOCKED_USERS === "string" && process.env.BLOCKED_USERS.includes(userId)) { throw new Error("Organization could not be created."); } @@ -50,9 +46,7 @@ export async function createOrganization( { title, userId, - projectName, companySize, - projectVersion, }, attemptCount + 1 ); @@ -76,14 +70,7 @@ export async function createOrganization( }, }); - const project = await createProject({ - organizationSlug: organization.slug, - name: projectName, - userId, - version: projectVersion, - }); - - return { ...organization, projects: [project] }; + return { ...organization }; } export async function createEnvironment( diff --git a/apps/webapp/app/presenters/OrganizationsPresenter.server.ts b/apps/webapp/app/presenters/OrganizationsPresenter.server.ts index becd33013..d4888ea3f 100644 --- a/apps/webapp/app/presenters/OrganizationsPresenter.server.ts +++ b/apps/webapp/app/presenters/OrganizationsPresenter.server.ts @@ -64,6 +64,10 @@ export class OrganizationsPresenter { ); } + if (project.organizationId !== organization.id) { + throw redirect(newProjectPath({ slug: organizationSlug }), request); + } + return { organizations, organization, project }; } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx index 16c0a5c58..ffbc6b592 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx @@ -16,13 +16,17 @@ import { FormTitle } from "~/components/primitives/FormTitle"; import { Input } from "~/components/primitives/Input"; import { InputGroup } from "~/components/primitives/InputGroup"; import { Label } from "~/components/primitives/Label"; +import { Paragraph } from "~/components/primitives/Paragraph"; import { Select, SelectItem } from "~/components/primitives/Select"; +import { TextLink } from "~/components/primitives/TextLink"; import { prisma } from "~/db.server"; import { useFeatures } from "~/hooks/useFeatures"; +import { useUser } from "~/hooks/useUser"; import { redirectWithSuccessMessage } from "~/models/message.server"; import { createProject } from "~/models/project.server"; import { requireUserId } from "~/services/session.server"; import { OrganizationParamsSchema, organizationPath, projectPath } from "~/utils/pathBuilder"; +import { RequestV3Access } from "../resources.orgs.$organizationSlug.v3-access"; export async function loader({ params, request }: LoaderFunctionArgs) { const userId = await requireUserId(request); @@ -34,10 +38,14 @@ export async function loader({ params, request }: LoaderFunctionArgs) { id: true, title: true, v3Enabled: true, + v2Enabled: true, + hasRequestedV3: true, _count: { select: { projects: { - where: { deletedAt: null }, + where: { + deletedAt: null, + }, }, }, }, @@ -57,6 +65,8 @@ export async function loader({ params, request }: LoaderFunctionArgs) { slug: organizationSlug, projectsCount: organization._count.projects, v3Enabled: organization.v3Enabled, + v2Enabled: organization.v2Enabled, + hasRequestedV3: organization.hasRequestedV3, }, defaultVersion: url.searchParams.get("version") ?? "v2", }); @@ -98,11 +108,23 @@ export const action: ActionFunction = async ({ request, params }) => { }; export default function NewOrganizationPage() { - const { organization, defaultVersion } = useTypedLoaderData(); + const { organization } = useTypedLoaderData(); const lastSubmission = useActionData(); - const { v3Enabled } = useFeatures(); + const { v3Enabled, isManagedCloud } = useFeatures(); const canCreateV3Projects = organization.v3Enabled && v3Enabled; + const canCreateV2Projects = organization.v2Enabled || !isManagedCloud; + const canCreateProjects = canCreateV2Projects || canCreateV3Projects; + + if (!canCreateProjects) { + return ( + + ); + } const [form, { projectName, projectVersion }] = useForm({ id: "create-project", @@ -119,7 +141,7 @@ export default function NewOrganizationPage() {
{organization.projectsCount === 0 && ( @@ -138,7 +160,7 @@ export default function NewOrganizationPage() { /> {projectName.error} - {canCreateV3Projects ? ( + {canCreateV2Projects && canCreateV3Projects ? ( + ) : ( - + <> + This will be a v2 project + + )} { const organization = await createOrganization({ title: submission.value.orgName, userId, - projectName: submission.value.projectName, companySize: submission.value.companySize ?? null, - projectVersion: submission.value.projectVersion, }); - const project = organization.projects[0]; - const session = await setCurrentProjectId(project.id, request); - - const { isManagedCloud } = featuresForRequest(request); - - const headers = { - "Set-Cookie": await commitCurrentProjectSession(session), - }; - - if (isManagedCloud && submission.value.projectVersion === "v2") { - return redirect(selectPlanPath(organization), { - headers, - }); - } - - return redirect(projectPath(organization, project), { - headers, - }); + return redirect(organizationPath(organization)); } catch (error: any) { return json({ errors: { body: error.message } }, { status: 400 }); } @@ -91,10 +67,7 @@ export default function NewOrganizationPage() { const { isManagedCloud } = useFeatures(); const navigation = useNavigation(); - //this is temporary whilst v3 is invite-only. Switch to the useFeatures value when v3 is generally available. - const v3Enabled = false; - - const [form, { orgName, projectName, projectVersion }] = useForm({ + const [form, { orgName }] = useForm({ id: "create-organization", // TODO: type this lastSubmission: lastSubmission as any, @@ -123,45 +96,9 @@ export default function NewOrganizationPage() { E.g. your company name or your workspace name. {orgName.error} - - - - Your Jobs will live inside this Project. - {projectName.error} - - {v3Enabled ? ( - - - - {projectVersion.error} - - ) : ( - - )} {isManagedCloud && ( - + { + if (request.method.toLowerCase() !== "post") { + return redirectWithErrorMessage("/", request, "Invalid request method"); + } + + const user = await requireUser(request); + const { organizationSlug } = ParamSchema.parse(params); + + const failedRedirectPath = organizationPath({ slug: organizationSlug }); + + try { + if (!env.PLAIN_API_KEY) { + return redirectWithErrorMessage( + failedRedirectPath, + request, + "Error requesting V3 access: Plain API key" + ); + } + + //mark them as having requested v3 + const organization = await prisma.organization.update({ + where: { + slug: organizationSlug, + members: { + some: { + userId: user.id, + }, + }, + }, + data: { + hasRequestedV3: true, + }, + }); + + //update Plain + const client = new PlainClient({ + apiKey: env.PLAIN_API_KEY, + }); + + const upsertCustomerRes = await client.upsertCustomer({ + identifier: { + emailAddress: user.email, + }, + onCreate: { + fullName: user.name ?? user.email, + email: { + email: user.email, + isVerified: true, + }, + }, + onUpdate: {}, + }); + + if (upsertCustomerRes.error) { + logger.error("Error upserting customer", upsertCustomerRes.error); + return redirectWithErrorMessage(failedRedirectPath, request, "Error requesting V3 access"); + } + + const groupResult = await client.addCustomerToCustomerGroups({ + customerId: upsertCustomerRes.data.customer.id, + customerGroupIdentifiers: [ + { + customerGroupKey: "interested-in-v3", + }, + ], + }); + + if (groupResult.error) { + logger.error("Error adding customer to group", groupResult.error); + return redirectWithErrorMessage(failedRedirectPath, request, "Error requesting V3 access"); + } + + const createThreadRes = await client.createThread({ + customerIdentifier: { + customerId: upsertCustomerRes.data.customer.id, + }, + title: "v3 early access request", + components: [ + { + componentText: { + text: `${upsertCustomerRes.data.customer.email.email} has been added to the v3 early access group`, + }, + }, + { + componentText: { + text: `Company: ${organization.title ?? "–"}`, + }, + }, + ], + }); + + if (createThreadRes.error) { + logger.error("Error creating thread", createThreadRes.error); + return redirectWithErrorMessage(failedRedirectPath, request, "Error requesting V3 access"); + } + + return redirectWithSuccessMessage( + organizationPath(organization), + request, + "V3 access requested" + ); + } catch (error) { + logger.error("Error requesting V3 access", { error }); + return redirectWithErrorMessage(failedRedirectPath, request, "Error requesting V3 access"); + } +}; + +export function RequestV3Access({ + hasRequestedV3, + organizationSlug, + projectsCount, +}: { + hasRequestedV3: boolean; + organizationSlug: string; + projectsCount: number; +}) { + const user = useUser(); + + if (hasRequestedV3) { + return ( + +
+
+ v3 +
+ +
+
+ + We’ve received your request for v3 and we’ll notify you as soon as you have access. + We’re granting new users access every day so you won’t be waiting long. + + + Right now v3 is completely free to use but{" "} + + paid tiers + {" "} + will be introduced soon. + + + In the meantime, check out the{" "} + v3 docs, the{" "} + + v3 blog post + {" "} + and join our Discord. + +
+
+ ); + } + + return ( + + v3 + + {projectsCount > 0 ? ( + + You can no longer create v2 projects and your organization doesn't have access to v3 + yet. We are approving access requests daily. + + ) : ( + + Trigger.dev v3 is currently in Developer Preview and we’re operating a waitlist as we + focus on the platform’s reliability and scaleability. + + )} + + For more info, check out our{" "} + + v3 blog post + + . + +
+ {projectsCount > 0 ? ( + + Cancel + + ) : null} + +
+ +
+ ); +} diff --git a/packages/database/prisma/migrations/20240522130825_org_v2_enabled_flag_defaults_to_false/migration.sql b/packages/database/prisma/migrations/20240522130825_org_v2_enabled_flag_defaults_to_false/migration.sql new file mode 100644 index 000000000..2763f29d5 --- /dev/null +++ b/packages/database/prisma/migrations/20240522130825_org_v2_enabled_flag_defaults_to_false/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Organization" ADD COLUMN "v2Enabled" BOOLEAN NOT NULL DEFAULT false; diff --git a/packages/database/prisma/migrations/20240522134117_organization_added_has_requested_v3_column/migration.sql b/packages/database/prisma/migrations/20240522134117_organization_added_has_requested_v3_column/migration.sql new file mode 100644 index 000000000..5524c68fa --- /dev/null +++ b/packages/database/prisma/migrations/20240522134117_organization_added_has_requested_v3_column/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Organization" ADD COLUMN "hasRequestedV3" BOOLEAN NOT NULL DEFAULT false; diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index 75551d0cb..e0918c39a 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -120,7 +120,9 @@ model Organization { runsEnabled Boolean @default(true) - v3Enabled Boolean @default(false) + v3Enabled Boolean @default(false) + v2Enabled Boolean @default(false) + hasRequestedV3 Boolean @default(false) environments RuntimeEnvironment[] connections IntegrationConnection[] From 1281d40e4b174dce191496265983117dbb82d356 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 23 May 2024 13:26:03 +0100 Subject: [PATCH 13/27] v2: When a run hits the rate limit reschedule the re-execution (#1125) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: API rate limit error has the correct seconds until reset * When a v2 run hits the rate limit, reschedule using the reset timestamp * Still throw AutoYieldRateLimitErrors * Reschedule runs from the rate limit * The stress test timeout should be inside the task * If the rate limit error is thrown, don’t retry the API request --- .changeset/strong-owls-know.md | 6 ++ .../app/services/apiRateLimit.server.ts | 10 +-- .../runs/performRunExecutionV3.server.ts | 64 +++++++++++++++---- packages/core/src/schemas/api.ts | 9 +++ packages/trigger-sdk/src/apiClient.ts | 25 +++++++- packages/trigger-sdk/src/errors.ts | 6 ++ packages/trigger-sdk/src/io.ts | 9 +++ packages/trigger-sdk/src/triggerClient.ts | 8 +++ references/job-catalog/src/stressTest.ts | 4 +- 9 files changed, 121 insertions(+), 20 deletions(-) create mode 100644 .changeset/strong-owls-know.md diff --git a/.changeset/strong-owls-know.md b/.changeset/strong-owls-know.md new file mode 100644 index 000000000..2cb3e37e7 --- /dev/null +++ b/.changeset/strong-owls-know.md @@ -0,0 +1,6 @@ +--- +"@trigger.dev/sdk": patch +"@trigger.dev/core": patch +--- + +When a v2 run hits the rate limit, reschedule with the reset date diff --git a/apps/webapp/app/services/apiRateLimit.server.ts b/apps/webapp/app/services/apiRateLimit.server.ts index 29d9f81c7..ec3c9aff3 100644 --- a/apps/webapp/app/services/apiRateLimit.server.ts +++ b/apps/webapp/app/services/apiRateLimit.server.ts @@ -157,16 +157,18 @@ export function authorizationRateLimitMiddleware({ } res.setHeader("Content-Type", "application/problem+json"); + const secondsUntilReset = Math.max(0, (reset - new Date().getTime()) / 1000); return res.status(429).send( JSON.stringify( { title: "Rate Limit Exceeded", status: 429, type: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429", - detail: `Rate limit exceeded ${remaining}/${limit} requests remaining. Retry after ${reset} seconds.`, - reset: reset, - limit: limit, - error: `Rate limit exceeded ${remaining}/${limit} requests remaining. Retry after ${reset} seconds.`, + detail: `Rate limit exceeded ${remaining}/${limit} requests remaining. Retry in ${secondsUntilReset} seconds.`, + reset, + limit, + secondsUntilReset, + error: `Rate limit exceeded ${remaining}/${limit} requests remaining. Retry in ${secondsUntilReset} seconds.`, }, null, 2 diff --git a/apps/webapp/app/services/runs/performRunExecutionV3.server.ts b/apps/webapp/app/services/runs/performRunExecutionV3.server.ts index ad1c28217..d533448c7 100644 --- a/apps/webapp/app/services/runs/performRunExecutionV3.server.ts +++ b/apps/webapp/app/services/runs/performRunExecutionV3.server.ts @@ -441,6 +441,10 @@ export class PerformRunExecutionV3Service { await this.#resumeAutoYieldedRunWithCompletedTask(run, safeBody.data, durationInMs); break; } + case "AUTO_YIELD_RATE_LIMIT": { + await this.#rescheduleRun(run, safeBody.data.reset, durationInMs); + break; + } case "RESUME_WITH_PARALLEL_TASK": { await this.#resumeParallelRunWithTask(run, safeBody.data, durationInMs); @@ -667,6 +671,10 @@ export class PerformRunExecutionV3Service { break; } + case "AUTO_YIELD_RATE_LIMIT": { + await this.#rescheduleRun(run, childError.reset, durationInMs); + break; + } case "CANCELED": { break; } @@ -801,9 +809,9 @@ export class PerformRunExecutionV3Service { }); } - async #resumeAutoYieldedRun( + async #rescheduleRun( run: FoundRun, - data: AutoYieldMetadata, + reset: number, durationInMs: number, executionCount: number = 1 ) { @@ -820,16 +828,6 @@ export class PerformRunExecutionV3Service { executionCount: { increment: executionCount, }, - autoYieldExecution: { - create: [ - { - location: data.location, - timeRemaining: data.timeRemaining, - timeElapsed: data.timeElapsed, - limit: data.limit ?? 0, - }, - ], - }, forceYieldImmediately: false, }, select: { @@ -837,7 +835,7 @@ export class PerformRunExecutionV3Service { }, }); - await ResumeRunService.enqueue(run, tx); + await ResumeRunService.enqueue(run, tx, new Date(reset)); }); } @@ -888,6 +886,46 @@ export class PerformRunExecutionV3Service { }); } + async #resumeAutoYieldedRun( + run: FoundRun, + data: AutoYieldMetadata, + durationInMs: number, + executionCount: number = 1 + ) { + await $transaction(this.#prismaClient, async (tx) => { + await tx.jobRun.update({ + where: { + id: run.id, + }, + data: { + status: "WAITING_TO_EXECUTE", + executionDuration: { + increment: durationInMs, + }, + executionCount: { + increment: executionCount, + }, + autoYieldExecution: { + create: [ + { + location: data.location, + timeRemaining: data.timeRemaining, + timeElapsed: data.timeElapsed, + limit: data.limit ?? 0, + }, + ], + }, + forceYieldImmediately: false, + }, + select: { + executionCount: true, + }, + }); + + await ResumeRunService.enqueue(run, tx); + }); + } + async #retryRunWithTask( run: FoundRun, data: RunJobRetryWithTask, diff --git a/packages/core/src/schemas/api.ts b/packages/core/src/schemas/api.ts index 9a7882eae..9b3aefe94 100644 --- a/packages/core/src/schemas/api.ts +++ b/packages/core/src/schemas/api.ts @@ -671,6 +671,13 @@ export type RunJobAutoYieldWithCompletedTaskExecutionError = z.infer< typeof RunJobAutoYieldWithCompletedTaskExecutionErrorSchema >; +export const RunJobAutoYieldRateLimitErrorSchema = z.object({ + status: z.literal("AUTO_YIELD_RATE_LIMIT"), + reset: z.coerce.number(), +}); + +export type RunJobAutoYieldRateLimitError = z.infer; + export const RunJobInvalidPayloadErrorSchema = z.object({ status: z.literal("INVALID_PAYLOAD"), errors: z.array(SchemaErrorSchema), @@ -719,6 +726,7 @@ export const RunJobErrorResponseSchema = z.union([ RunJobAutoYieldExecutionErrorSchema, RunJobAutoYieldWithCompletedTaskExecutionErrorSchema, RunJobYieldExecutionErrorSchema, + RunJobAutoYieldRateLimitErrorSchema, RunJobErrorSchema, RunJobUnresolvedAuthErrorSchema, RunJobInvalidPayloadErrorSchema, @@ -741,6 +749,7 @@ export const RunJobResponseSchema = z.discriminatedUnion("status", [ RunJobAutoYieldExecutionErrorSchema, RunJobAutoYieldWithCompletedTaskExecutionErrorSchema, RunJobYieldExecutionErrorSchema, + RunJobAutoYieldRateLimitErrorSchema, RunJobErrorSchema, RunJobUnresolvedAuthErrorSchema, RunJobInvalidPayloadErrorSchema, diff --git a/packages/trigger-sdk/src/apiClient.ts b/packages/trigger-sdk/src/apiClient.ts index e97fe9bfc..abb3d6a70 100644 --- a/packages/trigger-sdk/src/apiClient.ts +++ b/packages/trigger-sdk/src/apiClient.ts @@ -44,6 +44,7 @@ import { env } from "node:process"; import { z } from "zod"; import { KeyValueStoreClient } from "./store/keyValueStoreClient"; +import { AutoYieldRateLimitError } from "./errors"; export type ApiClientOptions = { apiKey?: string; @@ -818,6 +819,15 @@ async function zodfetchWithVersions< return; } + //rate limit, so we want to reschedule + if (response.status === 429) { + //unix timestamp in milliseconds + const retryAfter = response.headers.get("x-ratelimit-reset"); + if (retryAfter) { + throw new AutoYieldRateLimitError(parseInt(retryAfter)); + } + } + if (response.status >= 400 && response.status < 500) { const rawBody = await safeResponseText(response); const body = safeJsonParse(rawBody); @@ -894,7 +904,7 @@ async function zodfetchWithVersions< body: versionedSchema.parse(jsonBody), }; } catch (error) { - if (error instanceof UnknownVersionError) { + if (error instanceof UnknownVersionError || error instanceof AutoYieldRateLimitError) { throw error; } @@ -987,6 +997,15 @@ async function zodfetch= 400 && response.status < 500) { const body = await response.json(); @@ -1012,6 +1031,10 @@ async function zodfetch { + await new Promise((resolve) => setTimeout(resolve, 2000)); + return { output: "a".repeat(30), }; }, { name: `Task ${i}` } ); - - await new Promise((resolve) => setTimeout(resolve, 2000)); } // Now do a wait for 5 seconds From 60cbb87b08768de7ac7670d71e6d04f77f1709f5 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 23 May 2024 13:28:23 +0100 Subject: [PATCH 14/27] New TaskRunNumberCounter that uses taskIdentifier + environmentId (#1124) * Switch to new TaskRunNumberCounter that uses taskIdentifier + environmentId * Drop old TaskRunCounter table (not used anymore) * Revert "Drop old TaskRunCounter table (not used anymore)" This reverts commit d7311a3d03cd8020df02105da155ceec4fa24b1b. --- .../app/v3/services/triggerTask.server.ts | 11 +++++++--- .../migration.sql | 15 +++++++++++++ packages/database/prisma/schema.prisma | 21 ++++++++++++++++--- 3 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 packages/database/prisma/migrations/20240523081426_added_task_run_number_counter_with_environment_as_well_as_task_identifier/migration.sql diff --git a/apps/webapp/app/v3/services/triggerTask.server.ts b/apps/webapp/app/v3/services/triggerTask.server.ts index b906d2f4e..d935724b6 100644 --- a/apps/webapp/app/v3/services/triggerTask.server.ts +++ b/apps/webapp/app/v3/services/triggerTask.server.ts @@ -100,10 +100,15 @@ export class TriggerTaskService extends BaseService { }) : undefined; - const counter = await tx.taskRunCounter.upsert({ - where: { taskIdentifier: taskId }, + const counter = await tx.taskRunNumberCounter.upsert({ + where: { + taskIdentifier_environmentId: { + taskIdentifier: taskId, + environmentId: environment.id, + }, + }, update: { lastNumber: { increment: 1 } }, - create: { taskIdentifier: taskId, lastNumber: 1 }, + create: { taskIdentifier: taskId, environmentId: environment.id, lastNumber: 1 }, select: { lastNumber: true }, }); diff --git a/packages/database/prisma/migrations/20240523081426_added_task_run_number_counter_with_environment_as_well_as_task_identifier/migration.sql b/packages/database/prisma/migrations/20240523081426_added_task_run_number_counter_with_environment_as_well_as_task_identifier/migration.sql new file mode 100644 index 000000000..09b243f4e --- /dev/null +++ b/packages/database/prisma/migrations/20240523081426_added_task_run_number_counter_with_environment_as_well_as_task_identifier/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE "TaskRunNumberCounter" ( + "id" TEXT NOT NULL, + "taskIdentifier" TEXT NOT NULL, + "environmentId" TEXT NOT NULL, + "lastNumber" INTEGER NOT NULL DEFAULT 0, + + CONSTRAINT "TaskRunNumberCounter_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "TaskRunNumberCounter_taskIdentifier_environmentId_key" ON "TaskRunNumberCounter"("taskIdentifier", "environmentId"); + +-- AddForeignKey +ALTER TABLE "TaskRunNumberCounter" ADD CONSTRAINT "TaskRunNumberCounter_environmentId_fkey" FOREIGN KEY ("environmentId") REFERENCES "RuntimeEnvironment"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index e0918c39a..ddcd9c869 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -406,9 +406,10 @@ model RuntimeEnvironment { taskScheduleInstances TaskScheduleInstance[] alerts ProjectAlert[] - sessions RuntimeEnvironmentSession[] - currentSession RuntimeEnvironmentSession? @relation("currentSession", fields: [currentSessionId], references: [id], onDelete: SetNull, onUpdate: Cascade) - currentSessionId String? + sessions RuntimeEnvironmentSession[] + currentSession RuntimeEnvironmentSession? @relation("currentSession", fields: [currentSessionId], references: [id], onDelete: SetNull, onUpdate: Cascade) + currentSessionId String? + taskRunNumberCounter TaskRunNumberCounter[] @@unique([projectId, slug, orgMemberId]) @@unique([projectId, shortcode]) @@ -1730,11 +1731,25 @@ model TaskRunDependency { updatedAt DateTime @updatedAt } +/// deprecated, we hadn't included the project id in the unique constraint model TaskRunCounter { taskIdentifier String @id lastNumber Int @default(0) } +/// Used for the TaskRun number (e.g. #1,421) +model TaskRunNumberCounter { + id String @id @default(cuid()) + + taskIdentifier String + environment RuntimeEnvironment @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade) + environmentId String + + lastNumber Int @default(0) + + @@unique([taskIdentifier, environmentId]) +} + model TaskTag { id String @id @default(cuid()) name String From 974bcc49e8dab5fb3e1052c50f1b99fca1b72ba5 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 23 May 2024 15:32:43 +0100 Subject: [PATCH 15/27] Speed up the Run log queries (#1126) * Added traceId index to TaskEvent * Get less data and get the trace from the run * Use the read replica in the EventRepository * Use the regular client to get the run because the redirect from test can be very fast --- apps/webapp/app/db.server.ts | 4 +- .../app/presenters/v3/SpanPresenter.server.ts | 28 +++++--- .../route.tsx | 5 +- apps/webapp/app/v3/eventRepository.server.ts | 68 +++++++++++++------ .../migration.sql | 2 + packages/database/prisma/schema.prisma | 3 + 6 files changed, 76 insertions(+), 34 deletions(-) create mode 100644 packages/database/prisma/migrations/20240523135511_added_task_event_trace_id_index/migration.sql diff --git a/apps/webapp/app/db.server.ts b/apps/webapp/app/db.server.ts index 3c36ae83f..5aa24b3a0 100644 --- a/apps/webapp/app/db.server.ts +++ b/apps/webapp/app/db.server.ts @@ -70,7 +70,9 @@ export { Prisma }; export const prisma = singleton("prisma", getClient); -export const $replica: Omit = singleton( +export type PrismaReplicaClient = Omit; + +export const $replica: PrismaReplicaClient = singleton( "replica", () => getReplicaClient() ?? prisma ); diff --git a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts index c21a05bde..8932fcf61 100644 --- a/apps/webapp/app/presenters/v3/SpanPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SpanPresenter.server.ts @@ -1,29 +1,26 @@ import { prettyPrintPacket } from "@trigger.dev/core/v3"; import { PrismaClient, prisma } from "~/db.server"; import { eventRepository } from "~/v3/eventRepository.server"; +import { BasePresenter } from "./basePresenter.server"; type Result = Awaited>; export type Span = NonNullable["event"]; -export class SpanPresenter { - #prismaClient: PrismaClient; - - constructor(prismaClient: PrismaClient = prisma) { - this.#prismaClient = prismaClient; - } - +export class SpanPresenter extends BasePresenter { public async call({ userId, projectSlug, organizationSlug, spanId, + runFriendlyId, }: { userId: string; projectSlug: string; organizationSlug: string; spanId: string; + runFriendlyId: string; }) { - const project = await this.#prismaClient.project.findUnique({ + const project = await this._replica.project.findUnique({ where: { slug: projectSlug, }, @@ -33,7 +30,20 @@ export class SpanPresenter { throw new Error("Project not found"); } - const span = await eventRepository.getSpan(spanId); + const run = await this._prisma.taskRun.findFirst({ + select: { + traceId: true, + }, + where: { + friendlyId: runFriendlyId, + }, + }); + + if (!run) { + return; + } + + const span = await eventRepository.getSpan(spanId, run.traceId); if (!span) { return; diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route.tsx index 4b280f3b5..195c66d83 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route.tsx @@ -4,11 +4,11 @@ import { QueueListIcon, StopCircleIcon, } from "@heroicons/react/20/solid"; -import { useFetcher, useParams } from "@remix-run/react"; +import { useParams } from "@remix-run/react"; import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { formatDurationNanoseconds, nanosecondsToMilliseconds } from "@trigger.dev/core/v3"; import { useEffect } from "react"; -import { typedjson, useTypedFetcher, useTypedLoaderData } from "remix-typedjson"; +import { typedjson, useTypedFetcher } from "remix-typedjson"; import { ExitIcon } from "~/assets/icons/ExitIcon"; import { CodeBlock } from "~/components/code/CodeBlock"; import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel"; @@ -46,6 +46,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { organizationSlug, projectSlug: projectParam, spanId: spanParam, + runFriendlyId: runParam, }); if (!span) { diff --git a/apps/webapp/app/v3/eventRepository.server.ts b/apps/webapp/app/v3/eventRepository.server.ts index 6e9ea22cd..324975e51 100644 --- a/apps/webapp/app/v3/eventRepository.server.ts +++ b/apps/webapp/app/v3/eventRepository.server.ts @@ -22,7 +22,7 @@ import { Prisma, TaskEvent, TaskEventStatus, type TaskEventKind } from "@trigger import Redis, { RedisOptions } from "ioredis"; import { createHash } from "node:crypto"; import { EventEmitter } from "node:stream"; -import { PrismaClient, prisma } from "~/db.server"; +import { $replica, PrismaClient, PrismaReplicaClient, prisma } from "~/db.server"; import { env } from "~/env.server"; import { AuthenticatedEnvironment } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; @@ -101,9 +101,26 @@ export type QueryOptions = Prisma.TaskEventWhereInput; export type TaskEventRecord = TaskEvent; -export type QueriedEvent = TaskEvent; +export type QueriedEvent = Prisma.TaskEventGetPayload<{ + select: { + id: true; + spanId: true; + parentId: true; + runId: true; + idempotencyKey: true; + message: true; + style: true; + startTime: true; + duration: true; + isError: true; + isPartial: true; + isCancelled: true; + level: true; + events: true; + }; +}>; -export type PreparedEvent = Omit & { +export type PreparedEvent = Omit & { duration: number; events: SpanEvents; style: TaskEventStyle; @@ -160,7 +177,11 @@ export class EventRepository { return this._subscriberCount; } - constructor(private db: PrismaClient = prisma, private readonly _config: EventRepoConfig) { + constructor( + private db: PrismaClient = prisma, + private readReplica: PrismaReplicaClient = $replica, + private readonly _config: EventRepoConfig + ) { this._flushScheduler = new DynamicFlushScheduler({ batchSize: _config.batchSize, flushInterval: _config.batchInterval, @@ -349,7 +370,23 @@ export class EventRepository { } public async getTraceSummary(traceId: string): Promise { - const events = await this.db.taskEvent.findMany({ + const events = await this.readReplica.taskEvent.findMany({ + select: { + id: true, + spanId: true, + parentId: true, + runId: true, + idempotencyKey: true, + message: true, + style: true, + startTime: true, + duration: true, + isError: true, + isPartial: true, + isCancelled: true, + level: true, + events: true, + }, where: { traceId, }, @@ -407,21 +444,8 @@ export class EventRepository { // A Span can be cancelled if it is partial and has a parent that is cancelled // And a span's duration, if it is partial and has a cancelled parent, is the time between the start of the span and the time of the cancellation event of the parent - public async getSpan(spanId: string) { - const traceSearch = await this.db.taskEvent.findFirst({ - where: { - spanId, - }, - select: { - traceId: true, - }, - }); - - if (!traceSearch) { - return; - } - - const traceSummary = await this.getTraceSummary(traceSearch.traceId); + public async getSpan(spanId: string, traceId: string) { + const traceSummary = await this.getTraceSummary(traceId); const span = traceSummary?.spans.find((span) => span.id === spanId); @@ -429,7 +453,7 @@ export class EventRepository { return; } - const fullEvent = await this.db.taskEvent.findUnique({ + const fullEvent = await this.readReplica.taskEvent.findUnique({ where: { id: span.recordId, }, @@ -814,7 +838,7 @@ export class EventRepository { export const eventRepository = singleton("eventRepo", initializeEventRepo); function initializeEventRepo() { - const repo = new EventRepository(prisma, { + const repo = new EventRepository(prisma, $replica, { batchSize: env.EVENTS_BATCH_SIZE, batchInterval: env.EVENTS_BATCH_INTERVAL, retentionInDays: env.EVENTS_DEFAULT_LOG_RETENTION, diff --git a/packages/database/prisma/migrations/20240523135511_added_task_event_trace_id_index/migration.sql b/packages/database/prisma/migrations/20240523135511_added_task_event_trace_id_index/migration.sql new file mode 100644 index 000000000..6864254e1 --- /dev/null +++ b/packages/database/prisma/migrations/20240523135511_added_task_event_trace_id_index/migration.sql @@ -0,0 +1,2 @@ +-- CreateIndex +CREATE INDEX "TaskEvent_traceId_idx" ON "TaskEvent"("traceId"); diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index ddcd9c869..b0c87beef 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -1898,6 +1898,9 @@ model TaskEvent { payloadType String? createdAt DateTime @default(now()) + + /// Used on the run page + @@index([traceId]) } enum TaskEventLevel { From f537778dc74b35cc430268a46ca9e024801d7f95 Mon Sep 17 00:00:00 2001 From: Jacob Paris Date: Thu, 23 May 2024 09:03:45 -0600 Subject: [PATCH 16/27] Separate classes from client code (#1119) * fix: move route to route folder * fix: move service class to its own file --- .../CreateExternalConnectionService.server.ts | 49 ++++++++++ .../route.ts} | 56 +---------- ...TriggerEndpointIndexHookService.server.ts} | 66 +------------ .../route.ts | 67 ++++++++++++++ .../CreateRunLogService.server.ts | 20 ++++ .../route.ts} | 23 +---- .../SetStatusService.server.ts | 71 ++++++++++++++ .../route.ts} | 74 +-------------- ....runs.$runId.tasks.$id.callback.$secret.ts | 2 +- .../CompleteRunTaskService.server.ts | 92 +++++++++++++++++++ .../route.ts} | 92 +------------------ .../FailRunTaskService.server.ts} | 71 +------------- .../route.ts | 69 ++++++++++++++ ...angeRequestLazyLoadedCachedTasks.server.ts | 49 ++++++++++ .../route.ts} | 55 +---------- .../CallbackRunTaskService.server.ts} | 36 -------- .../route.ts | 38 ++++++++ .../registerProjectMetrics.server.ts} | 48 ---------- .../projects.v3.$projectRef.metrics/route.ts | 51 ++++++++++ .../runs/performRunExecutionV3.server.ts | 2 +- 20 files changed, 523 insertions(+), 508 deletions(-) create mode 100644 apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug/CreateExternalConnectionService.server.ts rename apps/webapp/app/routes/{api.v1.accounts.$accountId.connections.$clientSlug.ts => api.v1.accounts.$accountId.connections.$clientSlug/route.ts} (52%) rename apps/webapp/app/routes/{api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts => api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier/TriggerEndpointIndexHookService.server.ts} (75%) create mode 100644 apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier/route.ts create mode 100644 apps/webapp/app/routes/api.v1.runs.$runId.logs/CreateRunLogService.server.ts rename apps/webapp/app/routes/{api.v1.runs.$runId.logs.ts => api.v1.runs.$runId.logs/route.ts} (65%) create mode 100644 apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id/SetStatusService.server.ts rename apps/webapp/app/routes/{api.v1.runs.$runId.statuses.$id.ts => api.v1.runs.$runId.statuses.$id/route.ts} (50%) create mode 100644 apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete/CompleteRunTaskService.server.ts rename apps/webapp/app/routes/{api.v1.runs.$runId.tasks.$id.complete.ts => api.v1.runs.$runId.tasks.$id.complete/route.ts} (61%) rename apps/webapp/app/routes/{api.v1.runs.$runId.tasks.$id.fail.ts => api.v1.runs.$runId.tasks.$id.fail/FailRunTaskService.server.ts} (55%) create mode 100644 apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail/route.ts create mode 100644 apps/webapp/app/routes/api.v1.runs.$runId.tasks/ChangeRequestLazyLoadedCachedTasks.server.ts rename apps/webapp/app/routes/{api.v1.runs.$runId.tasks.ts => api.v1.runs.$runId.tasks/route.ts} (71%) rename apps/webapp/app/routes/{api.v1.tasks.$id.callback.$secret.ts => api.v1.tasks.$id.callback.$secret/CallbackRunTaskService.server.ts} (67%) create mode 100644 apps/webapp/app/routes/api.v1.tasks.$id.callback.$secret/route.ts rename apps/webapp/app/routes/{projects.v3.$projectRef.metrics.ts => projects.v3.$projectRef.metrics/registerProjectMetrics.server.ts} (82%) create mode 100644 apps/webapp/app/routes/projects.v3.$projectRef.metrics/route.ts diff --git a/apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug/CreateExternalConnectionService.server.ts b/apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug/CreateExternalConnectionService.server.ts new file mode 100644 index 000000000..b6277b9c9 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug/CreateExternalConnectionService.server.ts @@ -0,0 +1,49 @@ +import { CreateExternalConnectionBody } from "@trigger.dev/core"; +import { PrismaClientOrTransaction, prisma } from "~/db.server"; +import { AuthenticatedEnvironment } from "~/services/apiAuth.server"; +import { integrationAuthRepository } from "~/services/externalApis/integrationAuthRepository.server"; + +export class CreateExternalConnectionService { + #prismaClient: PrismaClientOrTransaction; + + constructor(prismaClient: PrismaClientOrTransaction = prisma) { + this.#prismaClient = prismaClient; + } + + public async call( + accountIdentifier: string, + clientSlug: string, + environment: AuthenticatedEnvironment, + payload: CreateExternalConnectionBody + ) { + const externalAccount = await this.#prismaClient.externalAccount.upsert({ + where: { + environmentId_identifier: { + environmentId: environment.id, + identifier: accountIdentifier, + }, + }, + create: { + environmentId: environment.id, + organizationId: environment.organizationId, + identifier: accountIdentifier, + }, + update: {}, + }); + + const integration = await this.#prismaClient.integration.findUniqueOrThrow({ + where: { + organizationId_slug: { + organizationId: environment.organizationId, + slug: clientSlug, + }, + }, + }); + + return await integrationAuthRepository.createConnectionFromToken({ + externalAccount: externalAccount, + integration, + token: payload, + }); + } +} diff --git a/apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug.ts b/apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug/route.ts similarity index 52% rename from apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug.ts rename to apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug/route.ts index 42a155e54..dc2c8edfb 100644 --- a/apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug.ts +++ b/apps/webapp/app/routes/api.v1.accounts.$accountId.connections.$clientSlug/route.ts @@ -1,15 +1,10 @@ import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; -import { - CreateExternalConnectionBody, - CreateExternalConnectionBodySchema, - ErrorWithStackSchema, -} from "@trigger.dev/core"; +import { CreateExternalConnectionBodySchema, ErrorWithStackSchema } from "@trigger.dev/core"; import { z } from "zod"; import { generateErrorMessage } from "zod-error"; -import { PrismaClientOrTransaction, prisma } from "~/db.server"; -import { AuthenticatedEnvironment, authenticateApiRequest } from "~/services/apiAuth.server"; -import { integrationAuthRepository } from "~/services/externalApis/integrationAuthRepository.server"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; +import { CreateExternalConnectionService } from "./CreateExternalConnectionService.server"; const ParamsSchema = z.object({ accountId: z.string(), @@ -67,48 +62,3 @@ export async function action({ request, params }: ActionFunctionArgs) { return json({ message: parsedError.data.message }, { status: 500 }); } } - -class CreateExternalConnectionService { - #prismaClient: PrismaClientOrTransaction; - - constructor(prismaClient: PrismaClientOrTransaction = prisma) { - this.#prismaClient = prismaClient; - } - - public async call( - accountIdentifier: string, - clientSlug: string, - environment: AuthenticatedEnvironment, - payload: CreateExternalConnectionBody - ) { - const externalAccount = await this.#prismaClient.externalAccount.upsert({ - where: { - environmentId_identifier: { - environmentId: environment.id, - identifier: accountIdentifier, - }, - }, - create: { - environmentId: environment.id, - organizationId: environment.organizationId, - identifier: accountIdentifier, - }, - update: {}, - }); - - const integration = await this.#prismaClient.integration.findUniqueOrThrow({ - where: { - organizationId_slug: { - organizationId: environment.organizationId, - slug: clientSlug, - }, - }, - }); - - return await integrationAuthRepository.createConnectionFromToken({ - externalAccount: externalAccount, - integration, - token: payload, - }); - } -} diff --git a/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts b/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier/TriggerEndpointIndexHookService.server.ts similarity index 75% rename from apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts rename to apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier/TriggerEndpointIndexHookService.server.ts index fb011392a..f17cf5d09 100644 --- a/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier.ts +++ b/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier/TriggerEndpointIndexHookService.server.ts @@ -1,74 +1,10 @@ -import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { z } from "zod"; import type { PrismaClient } from "~/db.server"; import { $transaction, prisma } from "~/db.server"; import { logger } from "~/services/logger.server"; import { workerQueue } from "~/services/worker.server"; -import { safeJsonParse } from "~/utils/json"; import { RuntimeEnvironmentType } from "~/database-types"; - -const ParamsSchema = z.object({ - environmentId: z.string(), - endpointSlug: z.string(), - indexHookIdentifier: z.string(), -}); - -export async function loader({ params }: LoaderFunctionArgs) { - const parsedParams = ParamsSchema.safeParse(params); - - if (!parsedParams.success) { - return { - status: 400, - json: { - error: "Invalid params", - }, - }; - } - - const { environmentId, endpointSlug, indexHookIdentifier } = parsedParams.data; - - const service = new TriggerEndpointIndexHookService(); - - await service.call({ - environmentId, - endpointSlug, - indexHookIdentifier, - }); - - return json({ - ok: true, - }); -} - -export async function action({ request, params }: ActionFunctionArgs) { - const parsedParams = ParamsSchema.safeParse(params); - - if (!parsedParams.success) { - return { - status: 400, - json: { - error: "Invalid params", - }, - }; - } - - const { environmentId, endpointSlug, indexHookIdentifier } = parsedParams.data; - - const body = await request.text(); - - const service = new TriggerEndpointIndexHookService(); - - await service.call({ - environmentId, - endpointSlug, - indexHookIdentifier, - body: body ? safeJsonParse(body) : undefined, - }); - - return json({ - ok: true, - }); -} +import type { ParamsSchema } from "./route"; type TriggerEndpointDeployHookOptions = z.infer & { body?: any; diff --git a/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier/route.ts b/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier/route.ts new file mode 100644 index 000000000..80b3e4483 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.endpoints.$environmentId.$endpointSlug.index.$indexHookIdentifier/route.ts @@ -0,0 +1,67 @@ +import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime"; +import { z } from "zod"; +import { safeJsonParse } from "~/utils/json"; +import { TriggerEndpointIndexHookService } from "./TriggerEndpointIndexHookService.server"; + +export const ParamsSchema = z.object({ + environmentId: z.string(), + endpointSlug: z.string(), + indexHookIdentifier: z.string(), +}); + +export async function loader({ params }: LoaderFunctionArgs) { + const parsedParams = ParamsSchema.safeParse(params); + + if (!parsedParams.success) { + return { + status: 400, + json: { + error: "Invalid params", + }, + }; + } + + const { environmentId, endpointSlug, indexHookIdentifier } = parsedParams.data; + + const service = new TriggerEndpointIndexHookService(); + + await service.call({ + environmentId, + endpointSlug, + indexHookIdentifier, + }); + + return json({ + ok: true, + }); +} + +export async function action({ request, params }: ActionFunctionArgs) { + const parsedParams = ParamsSchema.safeParse(params); + + if (!parsedParams.success) { + return { + status: 400, + json: { + error: "Invalid params", + }, + }; + } + + const { environmentId, endpointSlug, indexHookIdentifier } = parsedParams.data; + + const body = await request.text(); + + const service = new TriggerEndpointIndexHookService(); + + await service.call({ + environmentId, + endpointSlug, + indexHookIdentifier, + body: body ? safeJsonParse(body) : undefined, + }); + + return json({ + ok: true, + }); +} diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.logs/CreateRunLogService.server.ts b/apps/webapp/app/routes/api.v1.runs.$runId.logs/CreateRunLogService.server.ts new file mode 100644 index 000000000..01fbf5497 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.runs.$runId.logs/CreateRunLogService.server.ts @@ -0,0 +1,20 @@ +import type { LogMessage } from "@trigger.dev/core"; +import type { PrismaClient } from "@trigger.dev/database"; +import { prisma } from "~/db.server"; +import { AuthenticatedEnvironment } from "~/services/apiAuth.server"; +import { logger } from "~/services/logger.server"; + +export class CreateRunLogService { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call(environment: AuthenticatedEnvironment, runId: string, logMessage: LogMessage) { + // @ts-ignore + logger.debug(logMessage.message, logMessage.data ?? {}); + + return logMessage; + } +} diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.logs.ts b/apps/webapp/app/routes/api.v1.runs.$runId.logs/route.ts similarity index 65% rename from apps/webapp/app/routes/api.v1.runs.$runId.logs.ts rename to apps/webapp/app/routes/api.v1.runs.$runId.logs/route.ts index 364efe511..9c3f155f5 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.logs.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.logs/route.ts @@ -1,13 +1,9 @@ -import type { Organization, RuntimeEnvironment } from "@trigger.dev/database"; import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; -import type { LogMessage } from "@trigger.dev/core"; import { LogMessageSchema } from "@trigger.dev/core"; import { z } from "zod"; -import type { PrismaClient } from "~/db.server"; -import { prisma } from "~/db.server"; -import { authenticateApiRequest, AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { logger } from "~/services/logger.server"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; +import { CreateRunLogService } from "./CreateRunLogService.server"; const ParamsSchema = z.object({ runId: z.string(), @@ -53,18 +49,3 @@ export async function action({ request, params }: ActionFunctionArgs) { return json({ error: "Something went wrong" }, { status: 500 }); } } - -export class CreateRunLogService { - #prismaClient: PrismaClient; - - constructor(prismaClient: PrismaClient = prisma) { - this.#prismaClient = prismaClient; - } - - public async call(environment: AuthenticatedEnvironment, runId: string, logMessage: LogMessage) { - // @ts-ignore - logger.debug(logMessage.message, logMessage.data ?? {}); - - return logMessage; - } -} diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id/SetStatusService.server.ts b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id/SetStatusService.server.ts new file mode 100644 index 000000000..a3d4c5a39 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id/SetStatusService.server.ts @@ -0,0 +1,71 @@ +import { + StatusUpdate, + StatusHistory, + StatusHistorySchema, + StatusUpdateState, + StatusUpdateData, +} from "@trigger.dev/core"; +import { PrismaClient } from "@trigger.dev/database"; +import { prisma, $transaction } from "~/db.server"; + +export class SetStatusService { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call(runId: string, id: string, status: StatusUpdate) { + const statusRecord = await $transaction(this.#prismaClient, async (tx) => { + const existingStatus = await tx.jobRunStatusRecord.findUnique({ + where: { + runId_key: { + runId, + key: id, + }, + }, + }); + + const history: StatusHistory = []; + const historyResult = StatusHistorySchema.safeParse(existingStatus?.history); + if (historyResult.success) { + history.push(...historyResult.data); + } + if (existingStatus) { + history.push({ + label: existingStatus.label, + state: (existingStatus.state ?? undefined) as StatusUpdateState, + data: (existingStatus.data ?? undefined) as StatusUpdateData, + }); + } + + const updatedStatus = await tx.jobRunStatusRecord.upsert({ + where: { + runId_key: { + runId, + key: id, + }, + }, + create: { + key: id, + runId, + //this shouldn't ever use the id in reality, as the SDK makess it compulsory on the first call + label: status.label ?? id, + state: status.state, + data: status.data as any, + history: [], + }, + update: { + label: status.label, + state: status.state, + data: status.data as any, + history: history as any[], + }, + }); + + return updatedStatus; + }); + + return statusRecord; + } +} diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id/route.ts similarity index 50% rename from apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts rename to apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id/route.ts index 585ec5733..1da8a7b6b 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.statuses.$id/route.ts @@ -1,18 +1,10 @@ import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; -import { - JobRunStatusRecordSchema, - StatusHistory, - StatusHistorySchema, - StatusUpdate, - StatusUpdateData, - StatusUpdateSchema, - StatusUpdateState, -} from "@trigger.dev/core"; +import { JobRunStatusRecordSchema, StatusUpdateSchema } from "@trigger.dev/core"; import { z } from "zod"; -import { $transaction, PrismaClient, prisma } from "~/db.server"; import { authenticateApiRequest } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; +import { SetStatusService } from "./SetStatusService.server"; const ParamsSchema = z.object({ runId: z.string(), @@ -80,65 +72,3 @@ export async function action({ request, params }: ActionFunctionArgs) { return json({ error: "Something went wrong" }, { status: 500 }); } } - -export class SetStatusService { - #prismaClient: PrismaClient; - - constructor(prismaClient: PrismaClient = prisma) { - this.#prismaClient = prismaClient; - } - - public async call(runId: string, id: string, status: StatusUpdate) { - const statusRecord = await $transaction(this.#prismaClient, async (tx) => { - const existingStatus = await tx.jobRunStatusRecord.findUnique({ - where: { - runId_key: { - runId, - key: id, - }, - }, - }); - - const history: StatusHistory = []; - const historyResult = StatusHistorySchema.safeParse(existingStatus?.history); - if (historyResult.success) { - history.push(...historyResult.data); - } - if (existingStatus) { - history.push({ - label: existingStatus.label, - state: (existingStatus.state ?? undefined) as StatusUpdateState, - data: (existingStatus.data ?? undefined) as StatusUpdateData, - }); - } - - const updatedStatus = await tx.jobRunStatusRecord.upsert({ - where: { - runId_key: { - runId, - key: id, - }, - }, - create: { - key: id, - runId, - //this shouldn't ever use the id in reality, as the SDK makess it compulsory on the first call - label: status.label ?? id, - state: status.state, - data: status.data as any, - history: [], - }, - update: { - label: status.label, - state: status.state, - data: status.data as any, - history: history as any[], - }, - }); - - return updatedStatus; - }); - - return statusRecord; - } -} diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.callback.$secret.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.callback.$secret.ts index dfae03e18..b1483f4ad 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.callback.$secret.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.callback.$secret.ts @@ -1,3 +1,3 @@ -import { action } from "./api.v1.tasks.$id.callback.$secret"; +import { action } from "./api.v1.tasks.$id.callback.$secret/route"; export { action }; diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete/CompleteRunTaskService.server.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete/CompleteRunTaskService.server.ts new file mode 100644 index 000000000..0937bd9ac --- /dev/null +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete/CompleteRunTaskService.server.ts @@ -0,0 +1,92 @@ +import type { CompleteTaskBodyOutput, ServerTask } from "@trigger.dev/core"; +import { PrismaClientOrTransaction, prisma } from "~/db.server"; +import { taskWithAttemptsToServerTask } from "~/models/task.server"; +import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; +import { logger } from "~/services/logger.server"; + +export class CompleteRunTaskService { + #prismaClient: PrismaClientOrTransaction; + + constructor(prismaClient: PrismaClientOrTransaction = prisma) { + this.#prismaClient = prismaClient; + } + + public async call( + environment: AuthenticatedEnvironment, + runId: string, + id: string, + taskBody: CompleteTaskBodyOutput + ): Promise { + const existingTask = await this.#prismaClient.task.findUnique({ + where: { + id, + }, + include: { + run: true, + attempts: { + where: { + status: "PENDING", + }, + orderBy: { + number: "desc", + }, + take: 1, + }, + }, + }); + + if (!existingTask) { + return; + } + + if (existingTask.runId !== runId) { + return; + } + + if (existingTask.run.environmentId !== environment.id) { + return; + } + + if ( + existingTask.status === "COMPLETED" || + existingTask.status === "ERRORED" || + existingTask.status === "CANCELED" + ) { + logger.debug("Task already completed", { + existingTask, + }); + + return taskWithAttemptsToServerTask(existingTask); + } + + if (existingTask.attempts.length === 1) { + await this.#prismaClient.taskAttempt.update({ + where: { + id: existingTask.attempts[0].id, + }, + data: { + status: "COMPLETED", + }, + }); + } + + const updatedTask = await this.#prismaClient.task.update({ + where: { + id, + }, + data: { + status: "COMPLETED", + output: taskBody.output as any, + outputIsUndefined: typeof taskBody.output === "undefined", + completedAt: new Date(), + outputProperties: taskBody.properties, + }, + include: { + attempts: true, + run: true, + }, + }); + + return taskWithAttemptsToServerTask(updatedTask); + } +} diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete/route.ts similarity index 61% rename from apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete.ts rename to apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete/route.ts index 44a5be5e0..3eab7b0cb 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.complete/route.ts @@ -1,17 +1,16 @@ import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; -import type { CompleteTaskBodyOutput, ServerTask } from "@trigger.dev/core"; +import type { CompleteTaskBodyOutput } from "@trigger.dev/core"; import { API_VERSIONS, CompleteTaskBodyInputSchema, CompleteTaskBodyV2InputSchema, } from "@trigger.dev/core"; import { z } from "zod"; -import { PrismaClientOrTransaction, prisma } from "~/db.server"; -import { taskWithAttemptsToServerTask } from "~/models/task.server"; import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; import { authenticateApiRequest } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; +import { CompleteRunTaskService } from "./CompleteRunTaskService.server"; const ParamsSchema = z.object({ runId: z.string(), @@ -118,90 +117,3 @@ async function completeRunTask( return json({ error: "Something went wrong" }, { status: 500 }); } } - -export class CompleteRunTaskService { - #prismaClient: PrismaClientOrTransaction; - - constructor(prismaClient: PrismaClientOrTransaction = prisma) { - this.#prismaClient = prismaClient; - } - - public async call( - environment: AuthenticatedEnvironment, - runId: string, - id: string, - taskBody: CompleteTaskBodyOutput - ): Promise { - const existingTask = await this.#prismaClient.task.findUnique({ - where: { - id, - }, - include: { - run: true, - attempts: { - where: { - status: "PENDING", - }, - orderBy: { - number: "desc", - }, - take: 1, - }, - }, - }); - - if (!existingTask) { - return; - } - - if (existingTask.runId !== runId) { - return; - } - - if (existingTask.run.environmentId !== environment.id) { - return; - } - - if ( - existingTask.status === "COMPLETED" || - existingTask.status === "ERRORED" || - existingTask.status === "CANCELED" - ) { - logger.debug("Task already completed", { - existingTask, - }); - - return taskWithAttemptsToServerTask(existingTask); - } - - if (existingTask.attempts.length === 1) { - await this.#prismaClient.taskAttempt.update({ - where: { - id: existingTask.attempts[0].id, - }, - data: { - status: "COMPLETED", - }, - }); - } - - const updatedTask = await this.#prismaClient.task.update({ - where: { - id, - }, - data: { - status: "COMPLETED", - output: taskBody.output as any, - outputIsUndefined: typeof taskBody.output === "undefined", - completedAt: new Date(), - outputProperties: taskBody.properties, - }, - include: { - attempts: true, - run: true, - }, - }); - - return taskWithAttemptsToServerTask(updatedTask); - } -} diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail/FailRunTaskService.server.ts similarity index 55% rename from apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail.ts rename to apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail/FailRunTaskService.server.ts index a9ac8ca02..0551c535a 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail/FailRunTaskService.server.ts @@ -1,76 +1,11 @@ -import type { ActionFunctionArgs } from "@remix-run/server-runtime"; -import { json } from "@remix-run/server-runtime"; -import { FailTaskBodyInput, FailTaskBodyInputSchema, ServerTask } from "@trigger.dev/core"; -import { z } from "zod"; -import { PrismaClient, prisma } from "~/db.server"; +import { FailTaskBodyInput, ServerTask } from "@trigger.dev/core"; +import { PrismaClient } from "@trigger.dev/database"; +import { prisma } from "~/db.server"; import { taskWithAttemptsToServerTask } from "~/models/task.server"; import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { authenticateApiRequest } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; import { formatError } from "~/utils/formatErrors.server"; -const ParamsSchema = z.object({ - runId: z.string(), - id: z.string(), -}); - -export async function action({ request, params }: ActionFunctionArgs) { - // Ensure this is a POST request - if (request.method.toUpperCase() !== "POST") { - return { status: 405, body: "Method Not Allowed" }; - } - - // Next authenticate the request - const authenticationResult = await authenticateApiRequest(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing API key" }, { status: 401 }); - } - - const authenticatedEnv = authenticationResult.environment; - - const { runId, id } = ParamsSchema.parse(params); - - // Now parse the request body - const anyBody = await request.json(); - - logger.debug("FailRunTaskService.call() request body", { - body: anyBody, - runId, - id, - }); - - const body = FailTaskBodyInputSchema.safeParse(anyBody); - - if (!body.success) { - return json({ error: "Invalid request body" }, { status: 400 }); - } - - const service = new FailRunTaskService(); - - try { - const task = await service.call(authenticatedEnv, runId, id, body.data); - - logger.debug("FailRunTaskService.call() response body", { - runId, - id, - task, - }); - - if (!task) { - return json({ message: "Task not found" }, { status: 404 }); - } - - return json(task); - } catch (error) { - if (error instanceof Error) { - return json({ error: error.message }, { status: 400 }); - } - - return json({ error: "Something went wrong" }, { status: 500 }); - } -} - export class FailRunTaskService { #prismaClient: PrismaClient; diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail/route.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail/route.ts new file mode 100644 index 000000000..78e1cc39d --- /dev/null +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks.$id.fail/route.ts @@ -0,0 +1,69 @@ +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; +import { json } from "@remix-run/server-runtime"; +import { FailTaskBodyInputSchema } from "@trigger.dev/core"; +import { z } from "zod"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; +import { logger } from "~/services/logger.server"; +import { FailRunTaskService } from "./FailRunTaskService.server"; + +const ParamsSchema = z.object({ + runId: z.string(), + id: z.string(), +}); + +export async function action({ request, params }: ActionFunctionArgs) { + // Ensure this is a POST request + if (request.method.toUpperCase() !== "POST") { + return { status: 405, body: "Method Not Allowed" }; + } + + // Next authenticate the request + const authenticationResult = await authenticateApiRequest(request); + + if (!authenticationResult) { + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const authenticatedEnv = authenticationResult.environment; + + const { runId, id } = ParamsSchema.parse(params); + + // Now parse the request body + const anyBody = await request.json(); + + logger.debug("FailRunTaskService.call() request body", { + body: anyBody, + runId, + id, + }); + + const body = FailTaskBodyInputSchema.safeParse(anyBody); + + if (!body.success) { + return json({ error: "Invalid request body" }, { status: 400 }); + } + + const service = new FailRunTaskService(); + + try { + const task = await service.call(authenticatedEnv, runId, id, body.data); + + logger.debug("FailRunTaskService.call() response body", { + runId, + id, + task, + }); + + if (!task) { + return json({ message: "Task not found" }, { status: 404 }); + } + + return json(task); + } catch (error) { + if (error instanceof Error) { + return json({ error: error.message }, { status: 400 }); + } + + return json({ error: "Something went wrong" }, { status: 500 }); + } +} diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks/ChangeRequestLazyLoadedCachedTasks.server.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks/ChangeRequestLazyLoadedCachedTasks.server.ts new file mode 100644 index 000000000..1b9711927 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks/ChangeRequestLazyLoadedCachedTasks.server.ts @@ -0,0 +1,49 @@ +import { ServerTask, RunTaskResponseWithCachedTasksBody } from "@trigger.dev/core"; +import { PrismaClient } from "@trigger.dev/database"; +import { prisma } from "~/db.server"; +import { prepareTasksForCaching } from "~/models/task.server"; + +export class ChangeRequestLazyLoadedCachedTasks { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call( + runId: string, + task: ServerTask, + cursor?: string | null + ): Promise { + if (!cursor) { + return { + task, + }; + } + + // We need to limit the cached tasks to not be too large >2MB when serialized + const TOTAL_CACHED_TASK_BYTE_LIMIT = 2000000; + + const nextTasks = await this.#prismaClient.task.findMany({ + where: { + runId, + status: "COMPLETED", + noop: false, + }, + take: 250, + cursor: { + id: cursor, + }, + orderBy: { + id: "asc", + }, + }); + + const preparedTasks = prepareTasksForCaching(nextTasks, TOTAL_CACHED_TASK_BYTE_LIMIT); + + return { + task, + cachedTasks: preparedTasks, + }; + } +} diff --git a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts b/apps/webapp/app/routes/api.v1.runs.$runId.tasks/route.ts similarity index 71% rename from apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts rename to apps/webapp/app/routes/api.v1.runs.$runId.tasks/route.ts index 0a8b99c56..5d73394f7 100644 --- a/apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts +++ b/apps/webapp/app/routes/api.v1.runs.$runId.tasks/route.ts @@ -1,17 +1,11 @@ import type { ActionFunctionArgs } from "@remix-run/server-runtime"; import { json } from "@remix-run/server-runtime"; -import { - API_VERSIONS, - RunTaskBodyOutputSchema, - RunTaskResponseWithCachedTasksBody, - ServerTask, -} from "@trigger.dev/core"; +import { API_VERSIONS, RunTaskBodyOutputSchema } from "@trigger.dev/core"; import { z } from "zod"; -import { PrismaClient, prisma } from "~/db.server"; -import { prepareTasksForCaching } from "~/models/task.server"; import { authenticateApiRequest } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; import { RunTaskService } from "~/services/tasks/runTask.server"; +import { ChangeRequestLazyLoadedCachedTasks } from "./ChangeRequestLazyLoadedCachedTasks.server"; const ParamsSchema = z.object({ runId: z.string(), @@ -111,48 +105,3 @@ export async function action({ request, params }: ActionFunctionArgs) { return json({ error: "Something went wrong" }, { status: 500 }); } } - -class ChangeRequestLazyLoadedCachedTasks { - #prismaClient: PrismaClient; - - constructor(prismaClient: PrismaClient = prisma) { - this.#prismaClient = prismaClient; - } - - public async call( - runId: string, - task: ServerTask, - cursor?: string | null - ): Promise { - if (!cursor) { - return { - task, - }; - } - - // We need to limit the cached tasks to not be too large >2MB when serialized - const TOTAL_CACHED_TASK_BYTE_LIMIT = 2000000; - - const nextTasks = await this.#prismaClient.task.findMany({ - where: { - runId, - status: "COMPLETED", - noop: false, - }, - take: 250, - cursor: { - id: cursor, - }, - orderBy: { - id: "asc", - }, - }); - - const preparedTasks = prepareTasksForCaching(nextTasks, TOTAL_CACHED_TASK_BYTE_LIMIT); - - return { - task, - cachedTasks: preparedTasks, - }; - } -} diff --git a/apps/webapp/app/routes/api.v1.tasks.$id.callback.$secret.ts b/apps/webapp/app/routes/api.v1.tasks.$id.callback.$secret/CallbackRunTaskService.server.ts similarity index 67% rename from apps/webapp/app/routes/api.v1.tasks.$id.callback.$secret.ts rename to apps/webapp/app/routes/api.v1.tasks.$id.callback.$secret/CallbackRunTaskService.server.ts index fedf57015..5859d7a0d 100644 --- a/apps/webapp/app/routes/api.v1.tasks.$id.callback.$secret.ts +++ b/apps/webapp/app/routes/api.v1.tasks.$id.callback.$secret/CallbackRunTaskService.server.ts @@ -1,44 +1,8 @@ -import type { ActionFunctionArgs } from "@remix-run/server-runtime"; -import { json } from "@remix-run/server-runtime"; -import { z } from "zod"; import { $transaction, PrismaClient, PrismaClientOrTransaction, prisma } from "~/db.server"; import { logger } from "~/services/logger.server"; import { ResumeTaskService } from "~/services/tasks/resumeTask.server"; import { workerQueue } from "~/services/worker.server"; -const ParamsSchema = z.object({ - id: z.string(), - secret: z.string(), -}); - -export async function action({ request, params }: ActionFunctionArgs) { - // Ensure this is a POST request - if (request.method.toUpperCase() !== "POST") { - return { status: 405, body: "Method Not Allowed" }; - } - - const { id } = ParamsSchema.parse(params); - - // Parse body as JSON (no schema parsing) - const body = await request.json(); - - const service = new CallbackRunTaskService(); - - try { - // Complete task with request body as output - await service.call(id, body, request.url); - - return json({ success: true }); - } catch (error) { - if (error instanceof Error) { - logger.error("Error while processing task callback:", { error }); - - return json({ error: `Something went wrong: ${error.message}` }, { status: 500 }); - } - return json({ error: "Something went wrong" }, { status: 500 }); - } -} - export class CallbackRunTaskService { #prismaClient: PrismaClient; diff --git a/apps/webapp/app/routes/api.v1.tasks.$id.callback.$secret/route.ts b/apps/webapp/app/routes/api.v1.tasks.$id.callback.$secret/route.ts new file mode 100644 index 000000000..688ab6242 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.tasks.$id.callback.$secret/route.ts @@ -0,0 +1,38 @@ +import type { ActionFunctionArgs } from "@remix-run/server-runtime"; +import { json } from "@remix-run/server-runtime"; +import { z } from "zod"; +import { logger } from "~/services/logger.server"; +import { CallbackRunTaskService } from "./CallbackRunTaskService.server"; + +const ParamsSchema = z.object({ + id: z.string(), + secret: z.string(), +}); + +export async function action({ request, params }: ActionFunctionArgs) { + // Ensure this is a POST request + if (request.method.toUpperCase() !== "POST") { + return { status: 405, body: "Method Not Allowed" }; + } + + const { id } = ParamsSchema.parse(params); + + // Parse body as JSON (no schema parsing) + const body = await request.json(); + + const service = new CallbackRunTaskService(); + + try { + // Complete task with request body as output + await service.call(id, body, request.url); + + return json({ success: true }); + } catch (error) { + if (error instanceof Error) { + logger.error("Error while processing task callback:", { error }); + + return json({ error: `Something went wrong: ${error.message}` }, { status: 500 }); + } + return json({ error: "Something went wrong" }, { status: 500 }); + } +} diff --git a/apps/webapp/app/routes/projects.v3.$projectRef.metrics.ts b/apps/webapp/app/routes/projects.v3.$projectRef.metrics/registerProjectMetrics.server.ts similarity index 82% rename from apps/webapp/app/routes/projects.v3.$projectRef.metrics.ts rename to apps/webapp/app/routes/projects.v3.$projectRef.metrics/registerProjectMetrics.server.ts index 16168361b..f6503112f 100644 --- a/apps/webapp/app/routes/projects.v3.$projectRef.metrics.ts +++ b/apps/webapp/app/routes/projects.v3.$projectRef.metrics/registerProjectMetrics.server.ts @@ -1,57 +1,9 @@ -import { LoaderFunctionArgs, json } from "@remix-run/server-runtime"; import { TaskQueue } from "@trigger.dev/database"; import { Gauge, Registry } from "prom-client"; -import { z } from "zod"; import { prisma } from "~/db.server"; import { AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; import { marqs } from "~/v3/marqs/index.server"; -const ParamsSchema = z.object({ - projectRef: z.string(), -}); - -export async function loader({ params, request }: LoaderFunctionArgs) { - const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); - - if (!authenticationResult) { - return json({ error: "Invalid or Missing Access Token" }, { status: 401 }); - } - - const validatedParams = ParamsSchema.parse(params); - - const project = await prisma.project.findFirst({ - where: { - externalRef: validatedParams.projectRef, - organization: { - members: { - some: { - userId: authenticationResult.userId, - }, - }, - }, - }, - include: { - organization: true, - }, - }); - - if (!project) { - return new Response("Not found", { status: 404 }); - } - - const registry = new Registry(); - // Return prometheus metrics for the project (queues) - - await registerProjectMetrics(registry, project.id, authenticationResult.userId); - - return new Response(await registry.metrics(), { - headers: { - "Content-Type": registry.contentType, - }, - }); -} - export async function registerProjectMetrics( registry: Registry, projectId: string, diff --git a/apps/webapp/app/routes/projects.v3.$projectRef.metrics/route.ts b/apps/webapp/app/routes/projects.v3.$projectRef.metrics/route.ts new file mode 100644 index 000000000..ca09b3232 --- /dev/null +++ b/apps/webapp/app/routes/projects.v3.$projectRef.metrics/route.ts @@ -0,0 +1,51 @@ +import { LoaderFunctionArgs, json } from "@remix-run/server-runtime"; +import { Registry } from "prom-client"; +import { z } from "zod"; +import { prisma } from "~/db.server"; +import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; +import { registerProjectMetrics } from "./registerProjectMetrics.server"; + +const ParamsSchema = z.object({ + projectRef: z.string(), +}); + +export async function loader({ params, request }: LoaderFunctionArgs) { + const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request); + + if (!authenticationResult) { + return json({ error: "Invalid or Missing Access Token" }, { status: 401 }); + } + + const validatedParams = ParamsSchema.parse(params); + + const project = await prisma.project.findFirst({ + where: { + externalRef: validatedParams.projectRef, + organization: { + members: { + some: { + userId: authenticationResult.userId, + }, + }, + }, + }, + include: { + organization: true, + }, + }); + + if (!project) { + return new Response("Not found", { status: 404 }); + } + + const registry = new Registry(); + // Return prometheus metrics for the project (queues) + + await registerProjectMetrics(registry, project.id, authenticationResult.userId); + + return new Response(await registry.metrics(), { + headers: { + "Content-Type": registry.contentType, + }, + }); +} diff --git a/apps/webapp/app/services/runs/performRunExecutionV3.server.ts b/apps/webapp/app/services/runs/performRunExecutionV3.server.ts index d533448c7..6f0ecd56c 100644 --- a/apps/webapp/app/services/runs/performRunExecutionV3.server.ts +++ b/apps/webapp/app/services/runs/performRunExecutionV3.server.ts @@ -34,7 +34,7 @@ import { detectResponseIsTimeout } from "~/models/endpoint.server"; import { isRunCompleted } from "~/models/jobRun.server"; import { resolveRunConnections } from "~/models/runConnection.server"; import { prepareTasksForCaching, prepareTasksForCachingLegacy } from "~/models/task.server"; -import { CompleteRunTaskService } from "~/routes/api.v1.runs.$runId.tasks.$id.complete"; +import { CompleteRunTaskService } from "~/routes/api.v1.runs.$runId.tasks.$id.complete/CompleteRunTaskService.server"; import { formatError } from "~/utils/formatErrors.server"; import { safeJsonZodParse } from "~/utils/json"; import { EndpointApi } from "../endpointApi.server"; From 1f462eaa2ff9e43431312db659d483cc1b0fe7de Mon Sep 17 00:00:00 2001 From: Jacob Paris Date: Thu, 23 May 2024 09:04:21 -0600 Subject: [PATCH 17/27] fix: secret store options was a circular import (#1117) --- apps/webapp/app/env.server.ts | 2 +- apps/webapp/app/services/secrets/secretStore.server.ts | 4 +--- .../app/services/secrets/secretStoreOptionsSchema.server.ts | 4 ++++ 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 apps/webapp/app/services/secrets/secretStoreOptionsSchema.server.ts diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 80dfa3ee1..71edf6a6c 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { SecretStoreOptionsSchema } from "./services/secrets/secretStore.server"; +import { SecretStoreOptionsSchema } from "./services/secrets/secretStoreOptionsSchema.server"; import { isValidRegex } from "./utils/regex"; import { isValidDatabaseUrl } from "./utils/db"; diff --git a/apps/webapp/app/services/secrets/secretStore.server.ts b/apps/webapp/app/services/secrets/secretStore.server.ts index 43ebf32a7..855463fcd 100644 --- a/apps/webapp/app/services/secrets/secretStore.server.ts +++ b/apps/webapp/app/services/secrets/secretStore.server.ts @@ -4,9 +4,7 @@ import { env } from "~/env.server"; import nodeCrypto from "node:crypto"; import { safeJsonParse } from "~/utils/json"; import { logger } from "../logger.server"; - -export const SecretStoreOptionsSchema = z.enum(["DATABASE", "AWS_PARAM_STORE"]); -export type SecretStoreOptions = z.infer; +import type { SecretStoreOptions } from "./secretStoreOptionsSchema.server"; type ProviderInitializationOptions = { DATABASE: { diff --git a/apps/webapp/app/services/secrets/secretStoreOptionsSchema.server.ts b/apps/webapp/app/services/secrets/secretStoreOptionsSchema.server.ts new file mode 100644 index 000000000..7ffc22743 --- /dev/null +++ b/apps/webapp/app/services/secrets/secretStoreOptionsSchema.server.ts @@ -0,0 +1,4 @@ +import { z } from "zod"; + +export const SecretStoreOptionsSchema = z.enum(["DATABASE", "AWS_PARAM_STORE"]); +export type SecretStoreOptions = z.infer; From 3a1b0c486a302234ab83f3e77c5ffb9c69f8f771 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 23 May 2024 16:11:25 +0100 Subject: [PATCH 18/27] v3: env var management API (#1116) * WIP env var management API * Add import env var API endpoint * Adding docs and support for using both API keys and PATs when interacting with the env var endpoints * WIP envvar SDK * Uploading env vars in a variety of formats now works * Finish env var endpoints and add resolveEnvVars hook * Add changeset --- .changeset/five-toes-destroy.md | 7 + .vscode/launch.json | 8 + .../EnvironmentVariablesPresenter.server.ts | 2 +- .../route.tsx | 17 +- .../route.tsx | 12 +- ...rojects.$projectRef.envvars.$slug.$name.ts | 137 ++ ...ojects.$projectRef.envvars.$slug.import.ts | 84 ++ ...i.v1.projects.$projectRef.envvars.$slug.ts | 86 ++ apps/webapp/app/services/apiAuth.server.ts | 117 ++ .../services/personalAccessToken.server.ts | 6 +- .../environmentVariablesRepository.server.ts | 209 +-- .../app/v3/environmentVariables/repository.ts | 31 +- docs/mint.json | 113 +- docs/v3-openapi.json | 942 ------------- docs/v3-openapi.yaml | 1176 +++++++++++++++++ docs/v3/management-envvars-create.mdx | 4 + docs/v3/management-envvars-delete.mdx | 4 + docs/v3/management-envvars-import.mdx | 4 + docs/v3/management-envvars-list.mdx | 4 + docs/v3/management-envvars-retrieve.mdx | 4 + docs/v3/management-envvars-update.mdx | 4 + packages/cli-v3/src/apiClient.ts | 25 + packages/cli-v3/src/commands/deploy.ts | 94 +- packages/cli-v3/src/commands/dev.tsx | 43 +- packages/cli-v3/src/utilities/configFiles.ts | 10 + .../cli-v3/src/utilities/resolveEnvVars.ts | 62 + .../src/workers/dev/backgroundWorker.ts | 34 +- packages/core/package.json | 9 +- packages/core/src/v3/apiClient/index.ts | 104 +- packages/core/src/v3/apiClient/types.ts | 24 + .../core/src/v3/apiClientManager/index.ts | 2 +- packages/core/src/v3/schemas/api.ts | 47 + packages/core/src/v3/types/index.ts | 21 + packages/core/src/v3/zodfetch.ts | 275 +++- packages/trigger-sdk/src/v3/config.ts | 3 + packages/trigger-sdk/src/v3/envvars.ts | 364 +++++ packages/trigger-sdk/src/v3/index.ts | 4 +- .../src/v3/{management.ts => runs.ts} | 0 pnpm-lock.yaml | 137 ++ references/v3-catalog/.uploadable-env | 2 + references/v3-catalog/package.json | 1 + references/v3-catalog/src/management.ts | 87 +- references/v3-catalog/src/trigger/simple.ts | 16 +- references/v3-catalog/trigger.config.ts | 26 +- 44 files changed, 3271 insertions(+), 1090 deletions(-) create mode 100644 .changeset/five-toes-destroy.md create mode 100644 apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.$name.ts create mode 100644 apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.import.ts create mode 100644 apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.ts delete mode 100644 docs/v3-openapi.json create mode 100644 docs/v3-openapi.yaml create mode 100644 docs/v3/management-envvars-create.mdx create mode 100644 docs/v3/management-envvars-delete.mdx create mode 100644 docs/v3/management-envvars-import.mdx create mode 100644 docs/v3/management-envvars-list.mdx create mode 100644 docs/v3/management-envvars-retrieve.mdx create mode 100644 docs/v3/management-envvars-update.mdx create mode 100644 packages/cli-v3/src/utilities/resolveEnvVars.ts create mode 100644 packages/core/src/v3/apiClient/types.ts create mode 100644 packages/trigger-sdk/src/v3/envvars.ts rename packages/trigger-sdk/src/v3/{management.ts => runs.ts} (100%) create mode 100644 references/v3-catalog/.uploadable-env diff --git a/.changeset/five-toes-destroy.md b/.changeset/five-toes-destroy.md new file mode 100644 index 000000000..b07e4f91e --- /dev/null +++ b/.changeset/five-toes-destroy.md @@ -0,0 +1,7 @@ +--- +"@trigger.dev/sdk": patch +"trigger.dev": patch +"@trigger.dev/core": patch +--- + +v3: Environment variable management API and SDK, along with resolveEnvVars CLI hook diff --git a/.vscode/launch.json b/.vscode/launch.json index f70c3c97b..8bbed5139 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -45,6 +45,14 @@ "cwd": "${workspaceFolder}/references/v3-catalog", "sourceMaps": true }, + { + "type": "node-terminal", + "request": "launch", + "name": "Debug V3 Management", + "command": "pnpm run management", + "cwd": "${workspaceFolder}/references/v3-catalog", + "sourceMaps": true + }, { "type": "node", "request": "attach", diff --git a/apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts b/apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts index bc200dc10..9d17debdb 100644 --- a/apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts @@ -87,7 +87,7 @@ export class EnvironmentVariablesPresenter { ); const repository = new EnvironmentVariablesRepository(this.#prismaClient); - const variables = await repository.getProject(project.id, userId); + const variables = await repository.getProject(project.id); return { environmentVariables: environmentVariables.map((environmentVariable) => { diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.environment-variables.new/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.environment-variables.new/route.tsx index 016dc204c..3cba0fa2e 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.environment-variables.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.environment-variables.new/route.tsx @@ -70,7 +70,7 @@ const Variable = z.object({ type Variable = z.infer; const schema = z.object({ - overwrite: z.preprocess((i) => { + override: z.preprocess((i) => { if (i === "true") return true; if (i === "false") return false; return; @@ -115,6 +115,13 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { const project = await prisma.project.findUnique({ where: { slug: params.projectParam, + organization: { + members: { + some: { + userId, + }, + }, + }, }, select: { id: true, @@ -126,7 +133,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { } const repository = new EnvironmentVariablesRepository(prisma); - const result = await repository.create(project.id, userId, submission.value); + const result = await repository.create(project.id, submission.value); if (!result.success) { if (result.variableErrors) { @@ -249,7 +256,7 @@ export default function Page() { type="submit" variant="primary/small" disabled={isLoading} - name="overwrite" + name="override" value="false" > {isLoading ? "Saving" : "Save"} @@ -257,10 +264,10 @@ export default function Page() {
} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.environment-variables/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.environment-variables/route.tsx index e0d5c40a2..52f391d1b 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.environment-variables/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.environment-variables/route.tsx @@ -106,6 +106,13 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { const project = await prisma.project.findUnique({ where: { slug: params.projectParam, + organization: { + members: { + some: { + userId, + }, + }, + }, }, select: { id: true, @@ -119,7 +126,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { switch (submission.value.action) { case "edit": { const repository = new EnvironmentVariablesRepository(prisma); - const result = await repository.edit(project.id, userId, submission.value); + const result = await repository.edit(project.id, submission.value); if (!result.success) { submission.error.key = result.error; @@ -138,7 +145,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { } case "delete": { const repository = new EnvironmentVariablesRepository(prisma); - const result = await repository.delete(project.id, userId, submission.value); + const result = await repository.delete(project.id, submission.value); if (!result.success) { submission.error.key = result.error; @@ -334,6 +341,7 @@ function EditEnvironmentVariablePanel({ name={`values[${index}].value`} placeholder="Not set" defaultValue={value} + type="password" /> ); diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.$name.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.$name.ts new file mode 100644 index 000000000..71c947bef --- /dev/null +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.$name.ts @@ -0,0 +1,137 @@ +import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime"; +import { UpdateEnvironmentVariableRequestBody } from "@trigger.dev/core/v3"; +import { z } from "zod"; +import { prisma } from "~/db.server"; +import { + authenticateProjectApiKeyOrPersonalAccessToken, + authenticatedEnvironmentForAuthentication, +} from "~/services/apiAuth.server"; +import { EnvironmentVariablesRepository } from "~/v3/environmentVariables/environmentVariablesRepository.server"; + +const ParamsSchema = z.object({ + projectRef: z.string(), + slug: z.string(), + name: z.string(), +}); + +export async function action({ params, request }: ActionFunctionArgs) { + const parsedParams = ParamsSchema.safeParse(params); + + if (!parsedParams.success) { + return json({ error: "Invalid params" }, { status: 400 }); + } + + const authenticationResult = await authenticateProjectApiKeyOrPersonalAccessToken(request); + + if (!authenticationResult) { + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const environment = await authenticatedEnvironmentForAuthentication( + authenticationResult, + parsedParams.data.projectRef, + parsedParams.data.slug + ); + + // Find the environment variable + const variable = await prisma.environmentVariable.findFirst({ + where: { + key: parsedParams.data.name, + projectId: environment.project.id, + }, + }); + + if (!variable) { + return json({ error: "Environment variable not found" }, { status: 404 }); + } + + const repository = new EnvironmentVariablesRepository(); + + switch (request.method.toUpperCase()) { + case "DELETE": { + const result = await repository.deleteValue(environment.project.id, { + id: variable.id, + environmentId: environment.id, + }); + + if (result.success) { + return json({ success: true }); + } else { + return json({ error: result.error }, { status: 400 }); + } + } + case "PUT": + case "POST": { + const jsonBody = await request.json(); + + const body = UpdateEnvironmentVariableRequestBody.safeParse(jsonBody); + + if (!body.success) { + return json({ error: "Invalid request body", issues: body.error.issues }, { status: 400 }); + } + + const result = await repository.edit(environment.project.id, { + values: [ + { + value: body.data.value, + environmentId: environment.id, + }, + ], + id: variable.id, + keepEmptyValues: true, + }); + + if (result.success) { + return json({ success: true }); + } else { + return json({ error: result.error }, { status: 400 }); + } + } + } +} + +export async function loader({ params, request }: LoaderFunctionArgs) { + const parsedParams = ParamsSchema.safeParse(params); + + if (!parsedParams.success) { + return json({ error: "Invalid params" }, { status: 400 }); + } + + const authenticationResult = await authenticateProjectApiKeyOrPersonalAccessToken(request); + + if (!authenticationResult) { + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const environment = await authenticatedEnvironmentForAuthentication( + authenticationResult, + parsedParams.data.projectRef, + parsedParams.data.slug + ); + + // Find the environment variable + const variable = await prisma.environmentVariable.findFirst({ + where: { + key: parsedParams.data.name, + projectId: environment.project.id, + }, + }); + + if (!variable) { + return json({ error: "Environment variable not found" }, { status: 404 }); + } + + const repository = new EnvironmentVariablesRepository(); + + const variables = await repository.getEnvironment(environment.project.id, environment.id, true); + + const environmentVariable = variables.find((v) => v.key === parsedParams.data.name); + + if (!environmentVariable) { + return json({ error: "Environment variable not found" }, { status: 404 }); + } + + return json({ + value: environmentVariable.value, + }); +} diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.import.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.import.ts new file mode 100644 index 000000000..a90eac3b4 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.import.ts @@ -0,0 +1,84 @@ +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; +import { ImportEnvironmentVariablesRequestBody } from "@trigger.dev/core/v3"; +import { parse } from "dotenv"; +import { z } from "zod"; +import { + authenticateProjectApiKeyOrPersonalAccessToken, + authenticatedEnvironmentForAuthentication, +} from "~/services/apiAuth.server"; +import { EnvironmentVariablesRepository } from "~/v3/environmentVariables/environmentVariablesRepository.server"; + +const ParamsSchema = z.object({ + projectRef: z.string(), + slug: z.string(), +}); + +export async function action({ params, request }: ActionFunctionArgs) { + const parsedParams = ParamsSchema.safeParse(params); + + if (!parsedParams.success) { + return json({ error: "Invalid params" }, { status: 400 }); + } + + const authenticationResult = await authenticateProjectApiKeyOrPersonalAccessToken(request); + + if (!authenticationResult) { + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const environment = await authenticatedEnvironmentForAuthentication( + authenticationResult, + parsedParams.data.projectRef, + parsedParams.data.slug + ); + + const repository = new EnvironmentVariablesRepository(); + + const body = await parseImportBody(request); + + const result = await repository.create(environment.project.id, { + override: typeof body.override === "boolean" ? body.override : false, + environmentIds: [environment.id], + variables: Object.entries(body.variables).map(([key, value]) => ({ + key, + value, + })), + }); + + if (result.success) { + return json({ success: true }); + } else { + return json({ error: result.error, variableErrors: result.variableErrors }, { status: 400 }); + } +} + +async function parseImportBody(request: Request): Promise { + const contentType = request.headers.get("content-type") ?? "application/json"; + + if (contentType.includes("multipart/form-data")) { + const formData = await request.formData(); + + const file = formData.get("variables"); + const override = formData.get("override") === "true"; + + if (file instanceof File) { + const buffer = await file.arrayBuffer(); + + const variables = parse(Buffer.from(buffer)); + + return { variables, override }; + } else { + throw json({ error: "Invalid file" }, { status: 400 }); + } + } else { + const rawBody = await request.json(); + + const body = ImportEnvironmentVariablesRequestBody.safeParse(rawBody); + + if (!body.success) { + throw json({ error: "Invalid body" }, { status: 400 }); + } + + return body.data; + } +} diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.ts new file mode 100644 index 000000000..770c7084f --- /dev/null +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.ts @@ -0,0 +1,86 @@ +import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime"; +import { CreateEnvironmentVariableRequestBody } from "@trigger.dev/core/v3"; +import { z } from "zod"; +import { + authenticateProjectApiKeyOrPersonalAccessToken, + authenticatedEnvironmentForAuthentication, +} from "~/services/apiAuth.server"; +import { EnvironmentVariablesRepository } from "~/v3/environmentVariables/environmentVariablesRepository.server"; + +const ParamsSchema = z.object({ + projectRef: z.string(), + slug: z.string(), +}); + +export async function action({ params, request }: ActionFunctionArgs) { + const parsedParams = ParamsSchema.safeParse(params); + + if (!parsedParams.success) { + return json({ error: "Invalid params" }, { status: 400 }); + } + + const authenticationResult = await authenticateProjectApiKeyOrPersonalAccessToken(request); + + if (!authenticationResult) { + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const environment = await authenticatedEnvironmentForAuthentication( + authenticationResult, + parsedParams.data.projectRef, + parsedParams.data.slug + ); + + const jsonBody = await request.json(); + + const body = CreateEnvironmentVariableRequestBody.safeParse(jsonBody); + + if (!body.success) { + return json({ error: "Invalid request body", issues: body.error.issues }, { status: 400 }); + } + + const repository = new EnvironmentVariablesRepository(); + + const result = await repository.create(environment.project.id, { + override: true, + environmentIds: [environment.id], + variables: [ + { + key: body.data.name, + value: body.data.value, + }, + ], + }); + + if (result.success) { + return json({ success: true }); + } else { + return json({ error: result.error, variableErrors: result.variableErrors }, { status: 400 }); + } +} + +export async function loader({ params, request }: LoaderFunctionArgs) { + const parsedParams = ParamsSchema.safeParse(params); + + if (!parsedParams.success) { + return json({ error: "Invalid params" }, { status: 400 }); + } + + const authenticationResult = await authenticateProjectApiKeyOrPersonalAccessToken(request); + + if (!authenticationResult) { + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const environment = await authenticatedEnvironmentForAuthentication( + authenticationResult, + parsedParams.data.projectRef, + parsedParams.data.slug + ); + + const repository = new EnvironmentVariablesRepository(); + + const variables = await repository.getEnvironment(environment.project.id, environment.id, true); + + return json(variables.map((variable) => ({ name: variable.key, value: variable.value }))); +} diff --git a/apps/webapp/app/services/apiAuth.server.ts b/apps/webapp/app/services/apiAuth.server.ts index 3375b2fd9..57806aa60 100644 --- a/apps/webapp/app/services/apiAuth.server.ts +++ b/apps/webapp/app/services/apiAuth.server.ts @@ -4,6 +4,14 @@ import { findEnvironmentByApiKey, findEnvironmentByPublicApiKey, } from "~/models/runtimeEnvironment.server"; +import { + PersonalAccessTokenAuthenticationResult, + authenticateApiRequestWithPersonalAccessToken, + isPersonalAccessToken, +} from "./personalAccessToken.server"; +import { prisma } from "~/db.server"; +import { json } from "@remix-run/server-runtime"; +import { findProjectByRef } from "~/models/project.server"; type Optional = Prettify & Partial>>; @@ -92,3 +100,112 @@ export function getApiKeyResult(apiKey: string) { const type = isPublicApiKey(apiKey) ? ("PUBLIC" as const) : ("PRIVATE" as const); return { apiKey, type }; } + +export type DualAuthenticationResult = + | { + type: "personalAccessToken"; + result: PersonalAccessTokenAuthenticationResult; + } + | { + type: "apiKey"; + result: ApiAuthenticationResult; + }; + +export async function authenticateProjectApiKeyOrPersonalAccessToken( + request: Request +): Promise { + const apiKey = getApiKeyFromRequest(request); + if (!apiKey) { + return; + } + + if (isPersonalAccessToken(apiKey)) { + const result = await authenticateApiRequestWithPersonalAccessToken(request); + + if (!result) { + return; + } + + return { + type: "personalAccessToken", + result, + }; + } + + const result = await authenticateApiKey(apiKey, { allowPublicKey: false }); + + if (!result) { + return; + } + + return { + type: "apiKey", + result, + }; +} + +export async function authenticatedEnvironmentForAuthentication( + auth: DualAuthenticationResult, + projectRef: string, + slug: string +): Promise { + switch (auth.type) { + case "apiKey": { + if (auth.result.environment.project.externalRef !== projectRef) { + throw json( + { + error: + "Invalid project ref for this API key. Make sure you are using an API key associated with that project.", + }, + { status: 400 } + ); + } + + if (auth.result.environment.slug !== slug) { + throw json( + { + error: + "Invalid environment slug for this API key. Make sure you are using an API key associated with that environment.", + }, + { status: 400 } + ); + } + + return auth.result.environment; + } + case "personalAccessToken": { + const user = await prisma.user.findUnique({ + where: { + id: auth.result.userId, + }, + }); + + if (!user) { + throw json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const project = await findProjectByRef(projectRef, user.id); + + if (!project) { + throw json({ error: "Project not found" }, { status: 404 }); + } + + const environment = await prisma.runtimeEnvironment.findFirst({ + where: { + projectId: project.id, + slug: slug, + }, + include: { + project: true, + organization: true, + }, + }); + + if (!environment) { + throw json({ error: "Environment not found" }, { status: 404 }); + } + + return environment; + } + } +} diff --git a/apps/webapp/app/services/personalAccessToken.server.ts b/apps/webapp/app/services/personalAccessToken.server.ts index 60f7a03e4..c349b08ef 100644 --- a/apps/webapp/app/services/personalAccessToken.server.ts +++ b/apps/webapp/app/services/personalAccessToken.server.ts @@ -90,7 +90,7 @@ export async function revokePersonalAccessToken(tokenId: string) { }); } -type PersonalAccessTokenAuthenticationResult = { +export type PersonalAccessTokenAuthenticationResult = { userId: string; }; @@ -169,6 +169,10 @@ export async function authenticatePersonalAccessToken( }; } +export function isPersonalAccessToken(token: string) { + return token.startsWith(tokenPrefix); +} + export function createAuthorizationCode() { return prisma.authorizationCode.create({ data: { diff --git a/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts b/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts index 3a4e6ba6c..c7623af58 100644 --- a/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts +++ b/apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts @@ -7,6 +7,8 @@ import { getSecretStore } from "~/services/secrets/secretStore.server"; import { generateFriendlyId } from "../friendlyIdentifiers"; import { CreateResult, + DeleteEnvironmentVariable, + DeleteEnvironmentVariableValue, EnvironmentVariable, ProjectEnvironmentVariable, Repository, @@ -41,9 +43,8 @@ export class EnvironmentVariablesRepository implements Repository { async create( projectId: string, - userId: string, options: { - overwrite: boolean; + override: boolean; environmentIds: string[]; variables: { key: string; @@ -54,13 +55,6 @@ export class EnvironmentVariablesRepository implements Repository { const project = await this.prismaClient.project.findUnique({ where: { id: projectId, - organization: { - members: { - some: { - userId, - }, - }, - }, deletedAt: null, }, select: { @@ -92,6 +86,15 @@ export class EnvironmentVariablesRepository implements Repository { return { success: false as const, error: `Environment not found` }; } + // Check to see if any of the variables are `TRIGGER_SECRET_KEY` or `TRIGGER_API_URL` + const triggerKeys = options.variables.map((v) => v.key); + if (triggerKeys.includes("TRIGGER_SECRET_KEY") || triggerKeys.includes("TRIGGER_API_URL")) { + return { + success: false as const, + error: `You cannot set the variables TRIGGER_SECRET_KEY or TRIGGER_API_URL as they will be set automatically`, + }; + } + //get rid of empty variables const values = options.variables.filter((v) => v.key.trim() !== "" && v.value.trim() !== ""); if (values.length === 0) { @@ -99,7 +102,7 @@ export class EnvironmentVariablesRepository implements Repository { } //check if any of them exist in an environment we're setting - if (!options.overwrite) { + if (!options.override) { const existingVariableKeys: { key: string; environments: RuntimeEnvironmentType[] }[] = []; for (const variable of values) { const existingVariable = project.environmentVariables.find((v) => v.key === variable.key); @@ -119,7 +122,7 @@ export class EnvironmentVariablesRepository implements Repository { if (existingVariableKeys.length > 0) { return { success: false as const, - error: `Some of the variables are already set for these environments`, + error: `Some of the variables are already set for these environments. Set override to true to override them.`, variableErrors: existingVariableKeys.map((val) => ({ key: val.key, error: `Variable already set in ${val.environments @@ -217,19 +220,15 @@ export class EnvironmentVariablesRepository implements Repository { async edit( projectId: string, - userId: string, - options: { values: { value: string; environmentId: string }[]; id: string } + options: { + values: { value: string; environmentId: string }[]; + id: string; + keepEmptyValues?: boolean; + } ): Promise { const project = await this.prismaClient.project.findUnique({ where: { id: projectId, - organization: { - members: { - some: { - userId, - }, - }, - }, deletedAt: null, }, select: { @@ -237,18 +236,6 @@ export class EnvironmentVariablesRepository implements Repository { select: { id: true, }, - where: { - OR: [ - { - orgMember: null, - }, - { - orgMember: { - userId, - }, - }, - ], - }, }, }, }); @@ -266,12 +253,15 @@ export class EnvironmentVariablesRepository implements Repository { //add in empty values for environments that don't have a value const environmentIds = project.environments.map((e) => e.id); - for (const environmentId of environmentIds) { - if (!values.some((v) => v.environmentId === environmentId)) { - values.push({ - environmentId, - value: "", - }); + + if (!options.keepEmptyValues) { + for (const environmentId of environmentIds) { + if (!values.some((v) => v.environmentId === environmentId)) { + values.push({ + environmentId, + value: "", + }); + } } } @@ -364,17 +354,10 @@ export class EnvironmentVariablesRepository implements Repository { } } - async getProject(projectId: string, userId: string): Promise { + async getProject(projectId: string): Promise { const project = await this.prismaClient.project.findUnique({ where: { id: projectId, - organization: { - members: { - some: { - userId, - }, - }, - }, deletedAt: null, }, select: { @@ -446,19 +429,12 @@ export class EnvironmentVariablesRepository implements Repository { async getEnvironment( projectId: string, - userId: string, - environmentId: string + environmentId: string, + excludeInternalVariables?: boolean ): Promise { const project = await this.prismaClient.project.findUnique({ where: { id: projectId, - organization: { - members: { - some: { - userId, - }, - }, - }, deletedAt: null, }, select: { @@ -477,7 +453,7 @@ export class EnvironmentVariablesRepository implements Repository { return []; } - return this.getEnvironmentVariables(projectId, environmentId); + return this.getEnvironmentVariables(projectId, environmentId, excludeInternalVariables); } async #getTriggerEnvironmentVariables(environmentId: string): Promise { @@ -621,25 +597,24 @@ export class EnvironmentVariablesRepository implements Repository { async getEnvironmentVariables( projectId: string, - environmentId: string + environmentId: string, + excludeInternalVariables?: boolean ): Promise { const secretEnvVars = await this.#getSecretEnvironmentVariables(projectId, environmentId); + + if (excludeInternalVariables) { + return secretEnvVars; + } + const triggerEnvVars = await this.#getTriggerEnvironmentVariables(environmentId); return [...secretEnvVars, ...triggerEnvVars]; } - async delete(projectId: string, userId: string, options: { id: string }): Promise { + async delete(projectId: string, options: DeleteEnvironmentVariable): Promise { const project = await this.prismaClient.project.findUnique({ where: { id: projectId, - organization: { - members: { - some: { - userId, - }, - }, - }, deletedAt: null, }, select: { @@ -647,18 +622,6 @@ export class EnvironmentVariablesRepository implements Repository { select: { id: true, }, - where: { - OR: [ - { - orgMember: null, - }, - { - orgMember: { - userId, - }, - }, - ], - }, }, }, }); @@ -703,7 +666,7 @@ export class EnvironmentVariablesRepository implements Repository { prismaClient: tx, }); - //create the secret values and references + //delete the secret values and references for (const value of environmentVariable.values) { const key = secretKey(projectId, value.environmentId, environmentVariable.key); await secretStore.deleteSecret(key); @@ -728,4 +691,94 @@ export class EnvironmentVariablesRepository implements Repository { }; } } + + async deleteValue(projectId: string, options: DeleteEnvironmentVariableValue): Promise { + const project = await this.prismaClient.project.findUnique({ + where: { + id: projectId, + deletedAt: null, + }, + select: { + environments: { + select: { + id: true, + }, + }, + }, + }); + + if (!project) { + return { success: false as const, error: "Project not found" }; + } + + const environmentVariable = await this.prismaClient.environmentVariable.findUnique({ + select: { + id: true, + key: true, + values: { + select: { + id: true, + environmentId: true, + valueReference: { + select: { + key: true, + }, + }, + }, + }, + }, + where: { + id: options.id, + }, + }); + + if (!environmentVariable) { + return { success: false as const, error: "Environment variable not found" }; + } + + const value = environmentVariable.values.find((v) => v.environmentId === options.environmentId); + + if (!value) { + return { success: false as const, error: "Environment variable value not found" }; + } + + // If this is the last value, delete the whole variable + if (environmentVariable.values.length === 1) { + return this.delete(projectId, { id: options.id }); + } + + try { + await $transaction(this.prismaClient, async (tx) => { + const secretStore = getSecretStore("DATABASE", { + prismaClient: tx, + }); + + const key = secretKey(projectId, options.environmentId, environmentVariable.key); + await secretStore.deleteSecret(key); + + if (value.valueReference) { + await tx.secretReference.delete({ + where: { + key: value.valueReference.key, + }, + }); + } + + await tx.environmentVariableValue.delete({ + where: { + id: value.id, + }, + }); + }); + + return { + success: true as const, + }; + } catch (error) { + return { + success: false as const, + error: error instanceof Error ? error.message : "Something went wrong", + }; + } + } } diff --git a/apps/webapp/app/v3/environmentVariables/repository.ts b/apps/webapp/app/v3/environmentVariables/repository.ts index 920720290..ece858721 100644 --- a/apps/webapp/app/v3/environmentVariables/repository.ts +++ b/apps/webapp/app/v3/environmentVariables/repository.ts @@ -31,14 +31,22 @@ export const EditEnvironmentVariable = z.object({ value: z.string(), }) ), + keepEmptyValues: z.boolean().optional(), }); export type EditEnvironmentVariable = z.infer; export const DeleteEnvironmentVariable = z.object({ id: z.string(), + environmentId: z.string().optional(), }); export type DeleteEnvironmentVariable = z.infer; +export const DeleteEnvironmentVariableValue = z.object({ + id: z.string(), + environmentId: z.string(), +}); +export type DeleteEnvironmentVariableValue = z.infer; + export type Result = | { success: true; @@ -65,18 +73,19 @@ export type EnvironmentVariable = { }; export interface Repository { - create( - projectId: string, - userId: string, - options: CreateEnvironmentVariables - ): Promise; - edit(projectId: string, userId: string, options: EditEnvironmentVariable): Promise; - getProject(projectId: string, userId: string): Promise; + create(projectId: string, options: CreateEnvironmentVariables): Promise; + edit(projectId: string, options: EditEnvironmentVariable): Promise; + getProject(projectId: string): Promise; getEnvironment( projectId: string, - userId: string, - environmentId: string + environmentId: string, + excludeInternalVariables?: boolean ): Promise; - getEnvironmentVariables(projectId: string, environmentId: string): Promise; - delete(projectId: string, userId: string, options: DeleteEnvironmentVariable): Promise; + getEnvironmentVariables( + projectId: string, + environmentId: string, + excludeInternalVariables?: boolean + ): Promise; + delete(projectId: string, options: DeleteEnvironmentVariable): Promise; + deleteValue(projectId: string, options: DeleteEnvironmentVariableValue): Promise; } diff --git a/docs/mint.json b/docs/mint.json index 13726189d..243ca4446 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -1,8 +1,20 @@ { "$schema": "https://mintlify.com/schema.json", "name": "Trigger.dev", - "openapi": ["/openapi.yml", "/v3-openapi.json"], - "versions": ["v3 (Developer Preview)", "v2"], + "openapi": [ + "/openapi.yml", + "/v3-openapi.yaml" + ], + "versions": [ + "v3 (Developer Preview)", + "v2" + ], + "api": { + "playground": { + "mode": "hide" + }, + "maintainOrder": true + }, "logo": { "dark": "/logo/dark.png", "light": "/logo/light.png", @@ -90,12 +102,19 @@ { "group": "", "version": "v3 (Developer Preview)", - "pages": ["v3/introduction"] + "pages": [ + "v3/introduction" + ] }, { "group": "Getting Started", "version": "v3 (Developer Preview)", - "pages": ["v3/quick-start", "v3/upgrading-from-v2", "v3/changelog", "v3/feature-matrix"] + "pages": [ + "v3/quick-start", + "v3/upgrading-from-v2", + "v3/changelog", + "v3/feature-matrix" + ] }, { "group": "Fundamentals", @@ -107,7 +126,10 @@ "v3/apikeys", { "group": "Task types", - "pages": ["v3/tasks-regular", "v3/tasks-scheduled"] + "pages": [ + "v3/tasks-regular", + "v3/tasks-scheduled" + ] }, "v3/trigger-config" ] @@ -115,7 +137,10 @@ { "group": "Development", "version": "v3 (Developer Preview)", - "pages": ["v3/cli-dev", "v3/run-tests"] + "pages": [ + "v3/cli-dev", + "v3/run-tests" + ] }, { "group": "Deployment", @@ -126,7 +151,9 @@ "v3/github-actions", { "group": "Deployment integrations", - "pages": ["v3/vercel-integration"] + "pages": [ + "v3/vercel-integration" + ] } ] }, @@ -178,13 +205,28 @@ "v3/management-deactivate-schedule", "v3/management-activate-schedule" ] + }, + { + "group": "Env Vars API", + "pages": [ + "v3/management-envvars-list", + "v3/management-envvars-import", + "v3/management-envvars-create", + "v3/management-envvars-retrieve", + "v3/management-envvars-update", + "v3/management-envvars-delete" + ] } ] }, { "group": "Open source", "version": "v3 (Developer Preview)", - "pages": ["v3/github-repo", "v3/open-source-self-hosting", "v3/open-source-contributing"] + "pages": [ + "v3/github-repo", + "v3/open-source-self-hosting", + "v3/open-source-contributing" + ] }, { "group": "Troubleshooting", @@ -199,7 +241,11 @@ { "group": "Help", "version": "v3 (Developer Preview)", - "pages": ["v3/community", "v3/help-slack", "v3/help-email"] + "pages": [ + "v3/community", + "v3/help-slack", + "v3/help-email" + ] }, { "group": "Getting Started", @@ -391,7 +437,10 @@ "pages": [ { "group": "Airtable", - "pages": ["integrations/apis/airtable", "integrations/apis/airtable-tasks"] + "pages": [ + "integrations/apis/airtable", + "integrations/apis/airtable-tasks" + ] }, { "group": "GitHub", @@ -417,16 +466,25 @@ }, { "group": "Plain", - "pages": ["integrations/apis/plain", "integrations/apis/plain-tasks"] + "pages": [ + "integrations/apis/plain", + "integrations/apis/plain-tasks" + ] }, "integrations/apis/replicate", { "group": "SendGrid", - "pages": ["integrations/apis/sendgrid", "integrations/apis/sendgrid-tasks"] + "pages": [ + "integrations/apis/sendgrid", + "integrations/apis/sendgrid-tasks" + ] }, { "group": "Resend", - "pages": ["integrations/apis/resend", "integrations/apis/resend-tasks"] + "pages": [ + "integrations/apis/resend", + "integrations/apis/resend-tasks" + ] }, { "group": "Shopify", @@ -438,7 +496,10 @@ }, { "group": "Slack", - "pages": ["integrations/apis/slack", "integrations/apis/slack-tasks"] + "pages": [ + "integrations/apis/slack", + "integrations/apis/slack-tasks" + ] }, "integrations/apis/stripe", { @@ -464,7 +525,9 @@ "sdk/triggerclient/constructor", { "group": "Instance properties", - "pages": ["sdk/triggerclient/store"] + "pages": [ + "sdk/triggerclient/store" + ] }, { "group": "Instance methods", @@ -527,7 +590,10 @@ "sdk/dynamictrigger/constructor", { "group": "Instance methods", - "pages": ["sdk/dynamictrigger/register", "sdk/dynamictrigger/unregister"] + "pages": [ + "sdk/dynamictrigger/register", + "sdk/dynamictrigger/unregister" + ] } ] }, @@ -538,7 +604,10 @@ "sdk/dynamicschedule/constructor", { "group": "Instance methods", - "pages": ["sdk/dynamicschedule/register", "sdk/dynamicschedule/unregister"] + "pages": [ + "sdk/dynamicschedule/register", + "sdk/dynamicschedule/unregister" + ] } ] }, @@ -551,7 +620,9 @@ { "group": "HTTP Reference", "version": "v2", - "pages": ["sdk/api-reference/events/create-an-event"] + "pages": [ + "sdk/api-reference/events/create-an-event" + ] }, { "group": "React SDK", @@ -567,7 +638,9 @@ { "group": "Overview", "version": "v2", - "pages": ["examples/introduction"] + "pages": [ + "examples/introduction" + ] } ], "footerSocials": { @@ -575,4 +648,4 @@ "github": "https://github.com/triggerdotdev", "linkedin": "https://www.linkedin.com/company/triggerdotdev" } -} +} \ No newline at end of file diff --git a/docs/v3-openapi.json b/docs/v3-openapi.json deleted file mode 100644 index 8683ee099..000000000 --- a/docs/v3-openapi.json +++ /dev/null @@ -1,942 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "Trigger.dev v3 REST API", - "description": "The REST API lets you trigger and manage runs on Trigger.dev. You can trigger a run, get the status of a run, and get the results of a run. ", - "version": "2024-04" - }, - "servers": [ - { - "url": "https://api.trigger.dev", - "description": "Trigger.dev API" - } - ], - "paths": { - "/api/v1/schedules": { - "post": { - "operationId": "create_schedule_v1", - "description": "Create a new schedule based on the specified options.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateScheduleOptions" - } - } - } - }, - "responses": { - "200": { - "description": "Schedule created successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ScheduleObject" - } - } - } - }, - "400": { - "description": "Invalid request parameters" - }, - "422": { - "description": "Unprocessable Entity" - }, - "401": { - "description": "Unauthorized" - } - }, - "tags": [ - "schedules" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "x-codeSamples": [ - { - "lang": "typescript", - "source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nconst schedule = await schedules.create({\n task: 'my-task',\n cron: '0 0 * * *'\n});" - }, - { - "lang": "sh", - "source": "curl --request POST \\\n\t--url https://api.trigger.dev/api/v1/schedules \\\n\t--header 'Authorization: Bearer ' \\\n\t--header 'Content-Type: application/json' \\\n\t--data '{\"task\":\"my-task\",\"cron\":\"0 0 * * *\"}'" - } - ] - }, - "get": { - "operationId": "list_schedules_v1", - "description": "List all schedules.", - "parameters": [ - { - "in": "query", - "name": "page", - "schema": { - "type": "integer" - }, - "required": false, - "description": "Page number of the schedule listing" - }, - { - "in": "query", - "name": "perPage", - "schema": { - "type": "integer" - }, - "required": false, - "description": "Number of schedules per page" - } - ], - "responses": { - "200": { - "description": "Successful request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListSchedulesResult" - } - } - } - }, - "401": { - "description": "Unauthorized request" - } - }, - "tags": [ - "schedules" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "x-codeSamples": [ - { - "lang": "typescript", - "source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nconst allSchedules = await schedules.list();" - }, - { - "lang": "sh", - "source": "curl --request GET \\\n\t--url https://api.trigger.dev/api/v1/schedules \\\n\t--header 'Authorization: Bearer '" - } - ] - } - }, - "/api/v1/schedules/{schedule_id}": { - "get": { - "operationId": "get_schedule_v1", - "description": "Get a schedule by its ID.", - "parameters": [ - { - "in": "path", - "name": "schedule_id", - "required": true, - "schema": { - "type": "string" - }, - "description": "The ID of the schedule." - } - ], - "responses": { - "200": { - "description": "Successful request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ScheduleObject" - } - } - } - }, - "401": { - "description": "Unauthorized request" - }, - "404": { - "description": "Resource not found" - } - }, - "tags": [ - "schedules" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "x-codeSamples": [ - { - "lang": "typescript", - "source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nconst schedule = await schedules.retrieve(scheduleId);" - }, - { - "lang": "sh", - "source": "curl --request GET \\\n\t--url https://api.trigger.dev/api/v1/schedules/{schedule_id} \\\n\t--header 'Authorization: Bearer '" - } - ] - }, - "put": { - "operationId": "update_schedule_v1", - "description": "Update a schedule by its ID.", - "parameters": [ - { - "in": "path", - "name": "schedule_id", - "required": true, - "schema": { - "type": "string" - }, - "description": "The ID of the schedule." - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateScheduleOptions" - } - } - } - }, - "responses": { - "200": { - "description": "Schedule updated successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ScheduleObject" - } - } - } - }, - "400": { - "description": "Invalid request parameters" - }, - "401": { - "description": "Unauthorized" - }, - "404": { - "description": "Resource not found" - }, - "422": { - "description": "Unprocessable Entity" - } - }, - "tags": [ - "schedules" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "x-codeSamples": [ - { - "lang": "typescript", - "source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nconst updatedSchedule = await schedules.update(scheduleId, {\n task: 'my-updated-task',\n cron: '0 0 * * *'\n});" - }, - { - "lang": "sh", - "source": "curl --request PUT \\\n\t--url https://api.trigger.dev/api/v1/schedules/{schedule_id} \\\n\t--header 'Authorization: Bearer ' \\\n\t--header 'Content-Type: application/json' \\\n\t--data '{\"task\":\"my-updated-task\",\"cron\":\"0 0 * * *\"}'" - } - ] - }, - "delete": { - "operationId": "delete_schedule_v1", - "description": "Delete a schedule by its ID.", - "parameters": [ - { - "in": "path", - "name": "schedule_id", - "required": true, - "schema": { - "type": "string" - }, - "description": "The ID of the schedule." - } - ], - "responses": { - "200": { - "description": "Schedule deleted successfully" - }, - "401": { - "description": "Unauthorized request" - }, - "404": { - "description": "Resource not found" - } - }, - "tags": [ - "schedules" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "x-codeSamples": [ - { - "lang": "typescript", - "source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nawait schedules.del(scheduleId);" - }, - { - "lang": "sh", - "source": "curl --request DELETE \\\n\t--url https://api.trigger.dev/api/v1/schedules/{schedule_id} \\\n\t--header 'Authorization: Bearer '" - } - ] - } - }, - "/api/v1/schedules/{schedule_id}/deactivate": { - "post": { - "operationId": "deactivate_schedule_v1", - "description": "Deactivate a schedule by its ID.", - "parameters": [ - { - "in": "path", - "name": "schedule_id", - "required": true, - "schema": { - "type": "string" - }, - "description": "The ID of the schedule." - } - ], - "responses": { - "200": { - "description": "Schedule updated successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ScheduleObject" - } - } - } - }, - "401": { - "description": "Unauthorized request" - }, - "404": { - "description": "Resource not found" - } - }, - "tags": [ - "schedules" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "x-codeSamples": [ - { - "lang": "typescript", - "source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nconst schedule = await schedules.deactivate(scheduleId);" - }, - { - "lang": "sh", - "source": "curl --request POST \\\n\t--url https://api.trigger.dev/api/v1/schedules/{schedule_id}/deactivate \\\n\t--header 'Authorization: Bearer '" - } - ] - } - }, - "/api/v1/schedules/{schedule_id}/activate": { - "post": { - "operationId": "activate_schedule_v1", - "description": "Activate a schedule by its ID.", - "parameters": [ - { - "in": "path", - "name": "schedule_id", - "required": true, - "schema": { - "type": "string" - }, - "description": "The ID of the schedule." - } - ], - "responses": { - "200": { - "description": "Schedule updated successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ScheduleObject" - } - } - } - }, - "401": { - "description": "Unauthorized request" - }, - "404": { - "description": "Resource not found" - } - }, - "tags": [ - "schedules" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "x-codeSamples": [ - { - "lang": "typescript", - "source": "import { schedules } from \"@trigger.dev/sdk/v3\";\n\nconst schedule = await schedules.activate(scheduleId);" - }, - { - "lang": "sh", - "source": "curl --request POST \\\n\t--url https://api.trigger.dev/api/v1/schedules/{schedule_id}/activate \\\n\t--header 'Authorization: Bearer '" - } - ] - } - }, - "/api/v1/runs/{run_id}/replay": { - "post": { - "description": "Creates a new run with the same payload and options as the original run.", - "parameters": [ - { - "in": "path", - "name": "run_id", - "required": true, - "schema": { - "type": "string" - }, - "description": "The ID of an existing run. When you trigger a run you will get an id in the response." - } - ], - "responses": { - "200": { - "description": "Successful request", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The ID of the new run." - } - } - } - } - } - }, - "400": { - "description": "Invalid request", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "Invalid or missing run ID", - "Failed to create new run" - ] - } - } - } - } - } - }, - "401": { - "description": "Unauthorized request", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "Invalid or Missing API key" - ] - } - } - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "Run not found" - ] - } - } - } - } - } - } - }, - "tags": [ - "run" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "operationId": "replay_run_v1", - "x-codeSamples": [ - { - "lang": "typescript", - "source": "import { runs } from \"@trigger.dev/sdk/v3\";\n\nconst handle = await runs.replay(\"run_1234\");" - }, - { - "lang": "sh", - "source": "curl --request POST \\\n\t--url https://api.trigger.dev/api/v1/runs/{run_id}/replay \\\n\t--header 'Authorization: Bearer '" - } - ] - } - }, - "/api/v1/runs/{run_id}/cancel": { - "post": { - "description": "Cancels a run.", - "parameters": [ - { - "in": "path", - "name": "run_id", - "required": true, - "schema": { - "type": "string" - }, - "description": "The ID of an existing run. When you trigger a run you will get an id in the response." - } - ], - "responses": { - "200": { - "description": "Successful request", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "Confirmation message that the run was canceled." - } - } - } - } - } - }, - "400": { - "description": "Invalid request", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "Invalid or missing run ID", - "Failed to create new run" - ] - } - } - } - } - } - }, - "401": { - "description": "Unauthorized request", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "Invalid or Missing API key" - ] - } - } - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "Run not found" - ] - } - } - } - } - } - } - }, - "tags": [ - "run" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "operationId": "replay_run_v1", - "x-codeSamples": [ - { - "lang": "typescript", - "source": "import { runs } from \"@trigger.dev/sdk/v3\";\n\nawait runs.cancel(\"run_1234\");" - }, - { - "lang": "sh", - "source": "curl --request POST \\\n\t--url https://api.trigger.dev/api/v1/runs/{run_id}/cancel \\\n\t--header 'Authorization: Bearer '" - } - ] - } - }, - "/api/v3/runs/{run_id}": { - "get": { - "description": "Retrieve a run", - "parameters": [ - { - "in": "path", - "name": "run_id", - "required": true, - "schema": { - "type": "string" - }, - "description": "The ID of an existing run. When you trigger a run you will get an id in the response." - } - ], - "responses": { - "200": { - "description": "Successful request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RetrieveRunResponse" - } - } - } - }, - "400": { - "description": "Invalid request", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "Invalid or missing run ID" - ] - } - } - } - } - } - }, - "401": { - "description": "Unauthorized request", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "Invalid or Missing API key" - ] - } - } - } - } - } - }, - "404": { - "description": "Resource not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "Run not found" - ] - } - } - } - } - } - } - }, - "tags": [ - "run" - ], - "security": [ - { - "bearerAuth": [] - } - ], - "operationId": "retrieve_run_v1", - "x-codeSamples": [ - { - "lang": "typescript", - "source": "import { runs } from \"@trigger.dev/sdk/v3\";\n\nawait runs.retrieve(\"run_1234\");" - }, - { - "lang": "sh", - "source": "curl --request GET \\\n\t--url https://api.trigger.dev/api/v3/runs/{run_id} \\\n\t--header 'Authorization: Bearer '" - } - ] - } - } - }, - "components": { - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "description": "Use your Secret API key in the form 'Bearer ' (without the quotation marks)" - } - }, - "schemas": { - "RetrieveRunResponse": { - "type": "object", - "required": [ - "id", - "status", - "taskIdentifier", - "createdAt", - "updatedAt", - "attempts" - ], - "properties": { - "id": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "PENDING", - "EXECUTING", - "PAUSED", - "COMPLETED", - "FAILED", - "CANCELED" - ] - }, - "taskIdentifier": { - "type": "string" - }, - "idempotencyKey": { - "type": "string" - }, - "version": { - "type": "string" - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "updatedAt": { - "type": "string", - "format": "date-time" - }, - "attempts": { - "type": "array", - "items": { - "type": "object", - "required": [ - "id", - "status", - "createdAt", - "updatedAt" - ], - "properties": { - "id": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "PENDING", - "EXECUTING", - "PAUSED", - "COMPLETED", - "FAILED", - "CANCELED" - ] - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "updatedAt": { - "type": "string", - "format": "date-time" - }, - "startedAt": { - "type": "string", - "format": "date-time" - }, - "completedAt": { - "type": "string", - "format": "date-time" - } - } - } - } - } - }, - "CreateScheduleOptions": { - "type": "object", - "properties": { - "task": { - "type": "string" - }, - "cron": { - "type": "string" - }, - "deduplicationKey": { - "type": "string" - }, - "externalId": { - "type": "string" - } - }, - "required": [ - "task", - "cron" - ] - }, - "ScheduleObject": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "sched_1234", - "description": "The unique ID of the schedule, prefixed with 'sched_'" - }, - "task": { - "type": "string", - "example": "my-scheduled-task", - "description": "The id of the scheduled task that will be triggered by this schedule" - }, - "active": { - "type": "boolean", - "example": true, - "description": "Whether the schedule is active or not" - }, - "deduplicationKey": { - "type": "string", - "example": "dedup_key_1234", - "description": "The deduplication key used to prevent creating duplicate schedules" - }, - "externalId": { - "type": "string", - "example": "user_1234", - "description": "The external ID of the schedule. Can be anything that is useful to you (e.g., user ID, org ID, etc.)" - }, - "generator": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "CRON" - ] - }, - "expression": { - "type": "string", - "description": "The cron expression used to generate the schedule", - "example": "0 0 * * *" - }, - "description": { - "type": "string", - "description": "The description of the generator in plain english", - "example": "Every day at midnight" - } - } - }, - "nextRun": { - "type": "string", - "format": "date-time", - "description": "The next time the schedule will run", - "example": "2024-04-01T00:00:00Z" - }, - "environments": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ScheduleEnvironment" - } - } - } - }, - "ListSchedulesResult": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ScheduleObject" - } - }, - "pagination": { - "type": "object", - "properties": { - "currentPage": { - "type": "integer" - }, - "totalPages": { - "type": "integer" - }, - "count": { - "type": "integer" - } - } - } - } - }, - "ScheduleEnvironment": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string" - }, - "userName": { - "type": "string" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] -} \ No newline at end of file diff --git a/docs/v3-openapi.yaml b/docs/v3-openapi.yaml new file mode 100644 index 000000000..c9b2198b1 --- /dev/null +++ b/docs/v3-openapi.yaml @@ -0,0 +1,1176 @@ +--- +openapi: 3.1.0 +info: + title: Trigger.dev v3 REST API + description: "The REST API lets you trigger and manage runs on Trigger.dev. You + can trigger a run, get the status of a run, and get the results of a run. " + version: 2024-04 + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html +servers: + - url: https://api.trigger.dev + description: Trigger.dev API +paths: + "/api/v1/schedules": + post: + operationId: create_schedule_v1 + summary: Create a schedule + description: Create a new schedule based on the specified options. + requestBody: + required: true + content: + application/json: + schema: + "$ref": "#/components/schemas/CreateScheduleOptions" + responses: + "200": + description: Schedule created successfully + content: + application/json: + schema: + "$ref": "#/components/schemas/ScheduleObject" + "400": + description: Invalid request parameters + "401": + description: Unauthorized + "422": + description: Unprocessable Entity + tags: + - schedules + security: + - apiKey: [] + x-codeSamples: + - lang: typescript + source: |- + import { schedules } from "@trigger.dev/sdk/v3"; + + const schedule = await schedules.create({ + task: 'my-task', + cron: '0 0 * * *' + }); + + get: + operationId: list_schedules_v1 + summary: List all schedules + description: List all schedules. You can also paginate the results. + parameters: + - in: query + name: page + schema: + type: integer + required: false + description: Page number of the schedule listing + - in: query + name: perPage + schema: + type: integer + required: false + description: Number of schedules per page + responses: + "200": + description: Successful request + content: + application/json: + schema: + "$ref": "#/components/schemas/ListSchedulesResult" + "401": + description: Unauthorized request + tags: + - schedules + security: + - apiKey: [] + x-codeSamples: + - lang: typescript + source: |- + import { schedules } from "@trigger.dev/sdk/v3"; + + const allSchedules = await schedules.list(); + + "/api/v1/schedules/{schedule_id}": + get: + operationId: get_schedule_v1 + summary: Retrieve Schedule + description: Get a schedule by its ID. + parameters: + - in: path + name: schedule_id + required: true + schema: + type: string + description: The ID of the schedule. + example: sched_1234 + responses: + "200": + description: Successful request + content: + application/json: + schema: + "$ref": "#/components/schemas/ScheduleObject" + "401": + description: Unauthorized request + "404": + description: Resource not found + tags: + - schedules + security: + - apiKey: [] + x-codeSamples: + - lang: typescript + source: |- + import { schedules } from "@trigger.dev/sdk/v3"; + + const schedule = await schedules.retrieve(scheduleId); + + put: + operationId: update_schedule_v1 + summary: Update Schedule + description: Update a schedule by its ID. + parameters: + - in: path + name: schedule_id + required: true + schema: + type: string + description: The ID of the schedule. + requestBody: + required: true + content: + application/json: + schema: + "$ref": "#/components/schemas/CreateScheduleOptions" + responses: + "200": + description: Schedule updated successfully + content: + application/json: + schema: + "$ref": "#/components/schemas/ScheduleObject" + "400": + description: Invalid request parameters + "401": + description: Unauthorized + "404": + description: Resource not found + "422": + description: Unprocessable Entity + tags: + - schedules + security: + - apiKey: [] + x-codeSamples: + - lang: typescript + source: |- + import { schedules } from "@trigger.dev/sdk/v3"; + + const updatedSchedule = await schedules.update(scheduleId, { + task: 'my-updated-task', + cron: '0 0 * * *' + }); + + delete: + operationId: delete_schedule_v1 + summary: Delete Schedule + description: Delete a schedule by its ID. + parameters: + - in: path + name: schedule_id + required: true + schema: + type: string + description: The ID of the schedule. + responses: + "200": + description: Schedule deleted successfully + "401": + description: Unauthorized request + "404": + description: Resource not found + tags: + - schedules + security: + - apiKey: [] + x-codeSamples: + - lang: typescript + source: |- + import { schedules } from "@trigger.dev/sdk/v3"; + + await schedules.del(scheduleId); + + "/api/v1/schedules/{schedule_id}/deactivate": + post: + operationId: deactivate_schedule_v1 + summary: Deactivate Schedule. + description: Deactivate a schedule by its ID. + parameters: + - in: path + name: schedule_id + required: true + schema: + type: string + description: The ID of the schedule. + responses: + "200": + description: Schedule updated successfully + content: + application/json: + schema: + "$ref": "#/components/schemas/ScheduleObject" + "401": + description: Unauthorized request + "404": + description: Resource not found + tags: + - schedules + security: + - apiKey: [] + x-codeSamples: + - lang: typescript + source: |- + import { schedules } from "@trigger.dev/sdk/v3"; + + const schedule = await schedules.deactivate(scheduleId); + + "/api/v1/schedules/{schedule_id}/activate": + post: + operationId: activate_schedule_v1 + summary: Activate Schedule + description: Activate a schedule by its ID. + parameters: + - in: path + name: schedule_id + required: true + schema: + type: string + description: The ID of the schedule. + responses: + "200": + description: Schedule updated successfully + content: + application/json: + schema: + "$ref": "#/components/schemas/ScheduleObject" + "401": + description: Unauthorized request + "404": + description: Resource not found + tags: + - schedules + security: + - apiKey: [] + x-codeSamples: + - lang: typescript + source: |- + import { schedules } from "@trigger.dev/sdk/v3"; + + const schedule = await schedules.activate(scheduleId); + + "/api/v1/runs/{run_id}/replay": + post: + operationId: replay_run_v1 + summary: Replay a run + description: Creates a new run with the same payload and options as the original + run. + parameters: + - in: path + name: run_id + required: true + schema: + type: string + description: The ID of an existing run. When you trigger a run you will get + an id in the response. + responses: + "200": + description: Successful request + content: + application/json: + schema: + type: object + properties: + id: + type: string + description: The ID of the new run. + "400": + description: Invalid request + content: + application/json: + schema: + type: object + properties: + error: + type: string + enum: + - Invalid or missing run ID + - Failed to create new run + "401": + description: Unauthorized request + content: + application/json: + schema: + type: object + properties: + error: + type: string + enum: + - Invalid or Missing API key + "404": + description: Resource not found + content: + application/json: + schema: + type: object + properties: + error: + type: string + enum: + - Run not found + tags: + - run + security: + - apiKey: [] + x-codeSamples: + - lang: typescript + source: |- + import { runs } from "@trigger.dev/sdk/v3"; + + const handle = await runs.replay("run_1234"); + + "/api/v1/runs/{run_id}/cancel": + post: + operationId: cancel_run_v1 + description: Cancels a run. + summary: Cancel a run + parameters: + - in: path + name: run_id + required: true + schema: + type: string + description: The ID of an existing run. When you trigger a run you will get + an id in the response. + responses: + "200": + description: Successful request + content: + application/json: + schema: + type: object + properties: + message: + type: string + description: Confirmation message that the run was canceled. + "400": + description: Invalid request + content: + application/json: + schema: + type: object + properties: + error: + type: string + enum: + - Invalid or missing run ID + - Failed to create new run + "401": + description: Unauthorized request + content: + application/json: + schema: + type: object + properties: + error: + type: string + enum: + - Invalid or Missing API key + "404": + description: Resource not found + content: + application/json: + schema: + type: object + properties: + error: + type: string + enum: + - Run not found + tags: + - run + security: + - apiKey: [] + x-codeSamples: + - lang: typescript + source: |- + import { runs } from "@trigger.dev/sdk/v3"; + + await runs.cancel("run_1234"); + + "/api/v3/runs/{run_id}": + get: + operationId: retrieve_run_v1 + description: Retrieve a run + summary: Retrieve a run + parameters: + - in: path + name: run_id + required: true + schema: + type: string + description: The ID of an existing run. When you trigger a run you will get + an id in the response. + responses: + "200": + description: Successful request + content: + application/json: + schema: + "$ref": "#/components/schemas/RetrieveRunResponse" + "400": + description: Invalid request + content: + application/json: + schema: + type: object + properties: + error: + type: string + enum: + - Invalid or missing run ID + "401": + description: Unauthorized request + content: + application/json: + schema: + type: object + properties: + error: + type: string + enum: + - Invalid or Missing API key + "404": + description: Resource not found + content: + application/json: + schema: + type: object + properties: + error: + type: string + enum: + - Run not found + tags: + - run + security: + - apiKey: [] + x-codeSamples: + - lang: typescript + source: |- + import { runs } from "@trigger.dev/sdk/v3"; + + await runs.retrieve("run_1234"); + + "/api/v1/projects/{projectRef}/envvars/{env}": + parameters: + - $ref: "#/components/parameters/projectRef" + - $ref: "#/components/parameters/env" + get: + operationId: list_project_envvars_v1 + summary: List environment variables + description: List all environment variables for a specific project and environment. + responses: + "200": + description: Successful request + content: + application/json: + schema: + "$ref": "#/components/schemas/ListEnvironmentVariablesResponse" + "400": + description: Invalid request parameters or body + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorResponse" + "401": + description: Unauthorized request + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorResponse" + "404": + description: Resource not found + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorResponse" + tags: + - envvars + security: + - apiKey: [] + - personalAccessToken: [] + x-codeSamples: + - lang: typescript + label: Outside of a task + source: |- + import { envvars, configure } from "@trigger.dev/sdk/v3"; + + const variables = await envvars.list("proj_yubjwjsfkxnylobaqvqz", "dev"); + + for (const variable of variables) { + console.log(`Name: ${variable.name}, Value: ${variable.value}`); + } + - lang: typescript + label: Inside a task + source: |- + import { envvars, task } from "@trigger.dev/sdk/v3"; + + export const myTask = task({ + id: "my-task", + run: async () => { + // projectRef and env are automatically inferred from the task context + const variables = await envvars.list(); + + for (const variable of variables) { + console.log(`Name: ${variable.name}, Value: ${variable.value}`); + } + } + }) + post: + operationId: create_project_envvar_v1 + summary: Create environment variable + description: Create a new environment variable for a specific project and environment. + requestBody: + required: true + content: + application/json: + schema: + "$ref": "#/components/schemas/EnvVar" + responses: + "200": + description: Environment variable created successfully + content: + application/json: + schema: + "$ref": "#/components/schemas/SucceedResponse" + "400": + description: Invalid request parameters or body + content: + application/json: + schema: + "$ref": "#/components/schemas/InvalidEnvVarsRequestResponse" + "401": + description: Unauthorized request + content: + application/json: + schema: + type: object + properties: + error: + type: string + "404": + description: Resource not found + content: + application/json: + schema: + type: object + properties: + error: + type: string + tags: + - envvars + security: + - apiKey: [] + - personalAccessToken: [] + x-codeSamples: + - lang: typescript + label: Outside of a task + source: |- + import { envvars } from "@trigger.dev/sdk/v3"; + + await envvars.create("proj_yubjwjsfkxnylobaqvqz", "dev", { + name: "SLACK_API_KEY", + value: "slack_123456" + }); + - lang: typescript + label: Inside a task + source: |- + import { envvars, task } from "@trigger.dev/sdk/v3"; + + export const myTask = task({ + id: "my-task", + run: async () => { + // projectRef and env are automatically inferred from the task context + await envvars.create({ + name: "SLACK_API_KEY", + value: "slack_123456" + }); + } + }) + + "/api/v1/projects/{projectRef}/envvars/{env}/import": + parameters: + - $ref: "#/components/parameters/projectRef" + - $ref: "#/components/parameters/env" + post: + operationId: upload_project_envvars_v1 + summary: Upload environment variables + description: Upload mulitple environment variables for a specific project and environment. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + variables: + type: array + items: + "$ref": "#/components/schemas/EnvVar" + override: + type: boolean + description: Whether to override existing variables or not + default: false + required: ["variables"] + multipart/form-data: + schema: + type: object + properties: + variables: + type: string + format: binary + override: + type: boolean + required: + - variables + responses: + "200": + description: Environment variables imported successfully + content: + application/json: + schema: + "$ref": "#/components/schemas/SucceedResponse" + "400": + description: Invalid request parameters or body + content: + application/json: + schema: + "$ref": "#/components/schemas/InvalidEnvVarsRequestResponse" + "401": + description: Unauthorized request + content: + application/json: + schema: + type: object + properties: + error: + type: string + "404": + description: Resource not found + content: + application/json: + schema: + type: object + properties: + error: + type: string + tags: + - envvars + security: + - apiKey: [] + - personalAccessToken: [] + x-codeSamples: + - lang: typescript + label: Import variables from an array + source: |- + import { envvars } from "@trigger.dev/sdk/v3"; + + // Import variables from an array + await envvars.upload("proj_yubjwjsfkxnylobaqvqz", "dev", { + variables: [ + { + name: "SLACK_API_KEY", + value: "slack_123456" + } + ], + override: false + }); + - lang: typescript + label: Import variables from a read stream + source: |- + import { envvars } from "@trigger.dev/sdk/v3"; + import { createReadStream } from "node:fs"; + + // Import variables in dotenv format from a file + await envvars.upload("proj_yubjwjsfkxnylobaqvqz", "dev", { + variables: createReadStream(".env"), + override: false + }); + - lang: typescript + label: Import variables from a response + source: |- + import { envvars } from "@trigger.dev/sdk/v3"; + + // Import variables in dotenv format from a response + await envvars.upload("proj_yubjwjsfkxnylobaqvqz", "dev", { + variables: await fetch("https://example.com/.env"), + override: false + }); + - lang: typescript + label: Import variables from a Buffer + source: |- + import { envvars } from "@trigger.dev/sdk/v3"; + + // Import variables in dotenv format from a buffer + await envvars.upload("proj_yubjwjsfkxnylobaqvqz", "dev", { + variables: Buffer.from("SLACK_API_KEY=slack_1234"), + override: false + }); + - lang: typescript + label: Import variables from a File + source: |- + import { envvars } from "@trigger.dev/sdk/v3"; + + // Import variables in dotenv format from a file + await envvars.upload("proj_yubjwjsfkxnylobaqvqz", "dev", { + variables: new File(["SLACK_API_KEY=slack_1234"], ".env"), + override: false + }); + + + "/api/v1/projects/{projectRef}/envvars/{env}/{name}": + parameters: + - $ref: "#/components/parameters/projectRef" + - $ref: "#/components/parameters/env" + - $ref: "#/components/parameters/envvarName" + get: + operationId: get_project_envvar_v1 + summary: Retrieve environment variable + description: Retrieve a specific environment variable for a specific project and environment. + responses: + "200": + description: Successful request + content: + application/json: + schema: + "$ref": "#/components/schemas/EnvVarValue" + "400": + description: Invalid request parameters or body + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorResponse" + "401": + description: Unauthorized request + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorResponse" + "404": + description: Resource not found + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorResponse" + tags: + - envvars + security: + - apiKey: [] + - personalAccessToken: [] + x-codeSamples: + - lang: typescript + label: Outside of a task + source: |- + import { envvars } from "@trigger.dev/sdk/v3"; + + const variable = await envvars.retrieve("proj_yubjwjsfkxnylobaqvqz", "dev", "SLACK_API_KEY"); + + console.log(`Value: ${variable.value}`); + - lang: typescript + label: Inside a task + source: |- + import { envvars, task } from "@trigger.dev/sdk/v3"; + + export const myTask = task({ + id: "my-task", + run: async () => { + // projectRef and env are automatically inferred from the task context + const variable = await envvars.retrieve("SLACK_API_KEY"); + + console.log(`Value: ${variable.value}`); + } + }) + + delete: + operationId: delete_project_envvar_v1 + summary: Delete environment variable + description: Delete a specific environment variable for a specific project and environment. + responses: + "200": + description: Environment variable deleted successfully + content: + application/json: + schema: + "$ref": "#/components/schemas/SucceedResponse" + "400": + description: Invalid request parameters or body + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorResponse" + "401": + description: Unauthorized request + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorResponse" + "404": + description: Resource not found + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorResponse" + tags: + - envvars + security: + - apiKey: [] + - personalAccessToken: [] + x-codeSamples: + - lang: typescript + label: Outside of a task + source: |- + import { envvars } from "@trigger.dev/sdk/v3"; + + await envvars.del("proj_yubjwjsfkxnylobaqvqz", "dev", "SLACK_API_KEY"); + - lang: typescript + label: Inside a task + source: |- + import { envvars, task } from "@trigger.dev/sdk/v3"; + + export const myTask = task({ + id: "my-task", + run: async () => { + // projectRef and env are automatically inferred from the task context + await envvars.del("SLACK_API_KEY"); + } + }) + put: + operationId: update_project_envvar_v1 + summary: Update environment variable + description: Update a specific environment variable for a specific project and environment. + requestBody: + required: true + content: + application/json: + schema: + "$ref": "#/components/schemas/EnvVarValue" + responses: + "200": + description: Environment variable updated successfully + content: + application/json: + schema: + "$ref": "#/components/schemas/SucceedResponse" + "400": + description: Invalid request parameters or body + content: + application/json: + schema: + "$ref": "#/components/schemas/InvalidEnvVarsRequestResponse" + "401": + description: Unauthorized request + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorResponse" + "404": + description: Resource not found + content: + application/json: + schema: + "$ref": "#/components/schemas/ErrorResponse" + tags: + - envvars + security: + - apiKey: [] + - personalAccessToken: [] + x-codeSamples: + - lang: typescript + label: Outside of a task + source: |- + import { envvars } from "@trigger.dev/sdk/v3"; + + await envvars.update("proj_yubjwjsfkxnylobaqvqz", "dev", "SLACK_API_KEY", { + value: "slack_123456" + }); + - lang: typescript + label: Inside a task + source: |- + import { envvars, task } from "@trigger.dev/sdk/v3"; + + export const myTask = task({ + id: "my-task", + run: async () => { + // projectRef and env are automatically inferred from the task context + await envvars.update("SLACK_API_KEY", { + value: "slack_123456" + }); + } + }) + + +components: + parameters: + projectRef: + in: path + name: projectRef + required: true + schema: + type: string + description: The external ref of the project. You can find this in the project settings. Starts with `proj_`. + example: proj_yubjwjsfkxnylobaqvqz + env: + in: path + name: env + required: true + schema: + type: string + enum: [dev, staging, prod] + description: The environment of the project to list variables for. + example: dev + envvarName: + in: path + name: name + required: true + schema: + type: string + description: The name of the environment variable. + example: SLACK_API_KEY + securitySchemes: + apiKey: + type: http + scheme: bearer + description: | + Use your project-specific Secret API key. Will start with `tr_dev_`, `tr_prod`, `tr_stg`, etc. + + You can find your Secret API key in the API Keys section of your Trigger.dev project dashboard. + + Our TypeScript SDK will default to using the value of the `TRIGGER_SECRET_KEY` environment variable if it is set. If you are using the SDK in a different environment, you can set the key using the `configure` function. + + ```typescript + import { configure } from "@trigger.dev/sdk/v3"; + + configure({ secretKey: "tr_dev_1234" }); + ``` + + personalAccessToken: + type: http + scheme: bearer + description: | + Use your user-specific Personal Access Token, which you can generate from the Trigger.dev dashboard in your account settings. (It will start with `tr_pat_`.) + + Our TypeScript SDK will default to using the value of the `TRIGGER_ACCESS_TOKEN` environment variable if it is set. If you are using the SDK in a different environment, you can set the key using the `configure` function. + + ```typescript + import { configure } from "@trigger.dev/sdk/v3"; + + configure({ secretKey: "tr_pat_1234" }); + ``` + schemas: + InvalidEnvVarsRequestResponse: + type: object + properties: + error: + type: string + issues: + type: array + items: + type: object + variableErrors: + type: array + items: + type: object + SucceedResponse: + type: object + properties: + success: + type: boolean + required: ["success"] + ErrorResponse: + type: object + properties: + error: + type: string + required: ["error"] + ListEnvironmentVariablesResponse: + type: array + items: + "$ref": "#/components/schemas/EnvVar" + EnvVarValue: + type: object + properties: + value: + type: string + example: slack_123456 + required: ["value"] + EnvVar: + type: object + properties: + name: + type: string + example: SLACK_API_KEY + value: + type: string + example: slack_123456 + required: ["name", "value"] + RetrieveRunResponse: + type: object + required: + - id + - status + - taskIdentifier + - createdAt + - updatedAt + - attempts + properties: + id: + type: string + status: + type: string + enum: + - PENDING + - EXECUTING + - PAUSED + - COMPLETED + - FAILED + - CANCELED + taskIdentifier: + type: string + idempotencyKey: + type: string + version: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + attempts: + type: array + items: + type: object + required: + - id + - status + - createdAt + - updatedAt + properties: + id: + type: string + status: + type: string + enum: + - PENDING + - EXECUTING + - PAUSED + - COMPLETED + - FAILED + - CANCELED + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + startedAt: + type: string + format: date-time + completedAt: + type: string + format: date-time + CreateScheduleOptions: + type: object + properties: + task: + type: string + cron: + type: string + deduplicationKey: + type: string + externalId: + type: string + required: + - task + - cron + ScheduleObject: + type: object + properties: + id: + type: string + example: sched_1234 + description: The unique ID of the schedule, prefixed with 'sched_' + task: + type: string + example: my-scheduled-task + description: The id of the scheduled task that will be triggered by this + schedule + active: + type: boolean + example: true + description: Whether the schedule is active or not + deduplicationKey: + type: string + example: dedup_key_1234 + description: The deduplication key used to prevent creating duplicate schedules + externalId: + type: string + example: user_1234 + description: The external ID of the schedule. Can be anything that is useful + to you (e.g., user ID, org ID, etc.) + generator: + type: object + properties: + type: + type: string + enum: + - CRON + expression: + type: string + description: The cron expression used to generate the schedule + example: 0 0 * * * + description: + type: string + description: The description of the generator in plain english + example: Every day at midnight + nextRun: + type: string + format: date-time + description: The next time the schedule will run + example: "2024-04-01T00:00:00Z" + environments: + type: array + items: + "$ref": "#/components/schemas/ScheduleEnvironment" + ListSchedulesResult: + type: object + properties: + data: + type: array + items: + "$ref": "#/components/schemas/ScheduleObject" + pagination: + type: object + properties: + currentPage: + type: integer + totalPages: + type: integer + count: + type: integer + ScheduleEnvironment: + type: object + properties: + id: + type: string + type: + type: string + userName: + type: string diff --git a/docs/v3/management-envvars-create.mdx b/docs/v3/management-envvars-create.mdx new file mode 100644 index 000000000..cf08ed399 --- /dev/null +++ b/docs/v3/management-envvars-create.mdx @@ -0,0 +1,4 @@ +--- +title: "Create Env Var" +openapi: "v3-openapi POST /api/v1/projects/{projectRef}/envvars/{env}" +--- diff --git a/docs/v3/management-envvars-delete.mdx b/docs/v3/management-envvars-delete.mdx new file mode 100644 index 000000000..d14d83e22 --- /dev/null +++ b/docs/v3/management-envvars-delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete Env Var" +openapi: "v3-openapi DELETE /api/v1/projects/{projectRef}/envvars/{env}/{name}" +--- diff --git a/docs/v3/management-envvars-import.mdx b/docs/v3/management-envvars-import.mdx new file mode 100644 index 000000000..4454367b9 --- /dev/null +++ b/docs/v3/management-envvars-import.mdx @@ -0,0 +1,4 @@ +--- +title: "Import Env Vars" +openapi: "v3-openapi POST /api/v1/projects/{projectRef}/envvars/{env}/import" +--- diff --git a/docs/v3/management-envvars-list.mdx b/docs/v3/management-envvars-list.mdx new file mode 100644 index 000000000..dc4538a61 --- /dev/null +++ b/docs/v3/management-envvars-list.mdx @@ -0,0 +1,4 @@ +--- +title: "List Env Vars" +openapi: "v3-openapi GET /api/v1/projects/{projectRef}/envvars/{env}" +--- diff --git a/docs/v3/management-envvars-retrieve.mdx b/docs/v3/management-envvars-retrieve.mdx new file mode 100644 index 000000000..f4909f8b7 --- /dev/null +++ b/docs/v3/management-envvars-retrieve.mdx @@ -0,0 +1,4 @@ +--- +title: "Retrieve Env Var" +openapi: "v3-openapi GET /api/v1/projects/{projectRef}/envvars/{env}/{name}" +--- diff --git a/docs/v3/management-envvars-update.mdx b/docs/v3/management-envvars-update.mdx new file mode 100644 index 000000000..55171817d --- /dev/null +++ b/docs/v3/management-envvars-update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update Env Var" +openapi: "v3-openapi PUT /api/v1/projects/{projectRef}/envvars/{env}/{name}" +--- diff --git a/packages/cli-v3/src/apiClient.ts b/packages/cli-v3/src/apiClient.ts index 7c7751813..39f693b91 100644 --- a/packages/cli-v3/src/apiClient.ts +++ b/packages/cli-v3/src/apiClient.ts @@ -14,6 +14,8 @@ import { GetDeploymentResponseBody, GetProjectsResponseBody, GetProjectResponseBody, + ImportEnvironmentVariablesRequestBody, + EnvironmentVariableResponseBody, } from "@trigger.dev/core/v3"; export class CliApiClient { @@ -139,6 +141,29 @@ export class CliApiClient { ); } + async importEnvVars( + projectRef: string, + slug: "dev" | "prod" | "staging", + params: ImportEnvironmentVariablesRequestBody + ) { + if (!this.accessToken) { + throw new Error("importEnvVars: No access token"); + } + + return zodfetch( + EnvironmentVariableResponseBody, + `${this.apiURL}/api/v1/projects/${projectRef}/envvars/${slug}/import`, + { + method: "POST", + headers: { + Authorization: `Bearer ${this.accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(params), + } + ); + } + async initializeDeployment(body: InitializeDeploymentRequestBody) { if (!this.accessToken) { throw new Error("initializeDeployment: No access token"); diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 5b9c8f196..b9c886105 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -14,7 +14,7 @@ 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, posix, relative } from "node:path"; +import { dirname, join, posix, relative, resolve } from "node:path"; import { setTimeout } from "node:timers/promises"; import terminalLink from "terminal-link"; import invariant from "tiny-invariant"; @@ -30,7 +30,7 @@ import { tracer, wrapCommandAction, } from "../cli/common.js"; -import { readConfig } from "../utilities/configFiles.js"; +import { ReadConfigResult, readConfig } from "../utilities/configFiles.js"; import { createTempDir, writeJSONFile } from "../utilities/fileSystem"; import { printStandloneInitialBanner } from "../utilities/initialBanner.js"; import { @@ -63,6 +63,7 @@ import { cliRootPath } from "../utilities/resolveInternalFilePath"; import { safeJsonParse } from "../utilities/safeJsonParse"; import { escapeImportPath, spinner } from "../utilities/windows"; import { updateTriggerPackages } from "./update"; +import { callResolveEnvVars } from "../utilities/resolveEnvVars"; const DeployCommandOptions = CommonCommandOptions.extend({ skipTypecheck: z.boolean().default(false), @@ -258,6 +259,9 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { logger.debug("Compilation result", { compilation }); + // Optional Step 1.1: resolve environment variables + await resolveEnvironmentVariables(resolvedConfig, environmentClient, options); + // Step 2: Initialize a deployment on the server (response will have everything we need to build an image) const deploymentResponse = await environmentClient.initializeDeployment({ contentHash: compilation.contentHash, @@ -1391,6 +1395,92 @@ async function compileProject( }); } +async function resolveEnvironmentVariables( + config: ReadConfigResult, + apiClient: CliApiClient, + options: DeployCommandOptions +) { + if (config.status !== "file") { + return; + } + + if (!config.module || typeof config.module.resolveEnvVars !== "function") { + return; + } + + const projectConfig = config.config; + + return await tracer.startActiveSpan("resolveEnvironmentVariables", async (span) => { + try { + const $spinner = spinner(); + $spinner.start("Resolving environment variables"); + + let processEnv: Record = { + ...process.env, + }; + + // Step 1: Get existing env vars from the apiClient + const environmentVariables = await apiClient.getEnvironmentVariables(projectConfig.project); + + if (environmentVariables.success) { + processEnv = { + ...processEnv, + ...environmentVariables.data.variables, + }; + } + + logger.debug("Existing environment variables", { + keys: Object.keys(processEnv), + }); + + // Step 2: Call the resolveEnvVars function with the existing env vars (and process.env) + const resolvedEnvVars = await callResolveEnvVars( + config.module, + processEnv, + options.env, + projectConfig.project + ); + + // Step 3: Upload the new env vars via the apiClient + if (resolvedEnvVars) { + const total = Object.keys(resolvedEnvVars.variables).length; + + logger.debug("Resolved env vars", { + keys: Object.keys(resolvedEnvVars.variables), + }); + + if (total > 0) { + $spinner.message( + `Syncing ${total} environment variable${total > 1 ? "s" : ""} with the server` + ); + + const uploadResult = await apiClient.importEnvVars(projectConfig.project, options.env, { + variables: resolvedEnvVars.variables, + override: + typeof resolvedEnvVars.override === "boolean" ? resolvedEnvVars.override : true, + }); + + if (uploadResult.success) { + $spinner.stop(`${total} environment variable${total > 1 ? "s" : ""} synced`); + } else { + $spinner.stop("Failed to sync environment variables"); + + throw new Error(uploadResult.error); + } + } else { + $spinner.stop("No environment variables to sync"); + } + } + } catch (e) { + recordSpanException(span, e); + + throw e; + } finally { + span.end(); + } + }); +} + // Let's first create a digest from the package.json, and then use that digest to lookup a cached package-lock.json // in the `.trigger/cache` directory. If the package-lock.json is found, we'll write it to the project directory // If the package-lock.json is not found, we will run `npm install --package-lock-only` and then write the package-lock.json diff --git a/packages/cli-v3/src/commands/dev.tsx b/packages/cli-v3/src/commands/dev.tsx index 870402120..353f9907c 100644 --- a/packages/cli-v3/src/commands/dev.tsx +++ b/packages/cli-v3/src/commands/dev.tsx @@ -55,6 +55,7 @@ import { cliRootPath } from "../utilities/resolveInternalFilePath"; import { escapeImportPath } from "../utilities/windows"; import { updateTriggerPackages } from "./update"; import { esbuildDecorators } from "@anatine/esbuild-decorators"; +import { callResolveEnvVars } from "../utilities/resolveEnvVars"; let apiClient: CliApiClient | undefined; @@ -162,7 +163,8 @@ async function startDev( async function getDevReactElement( configParam: ResolvedConfig, authorization: { apiUrl: string; accessToken: string }, - configPath?: string + configPath?: string, + configModule?: any ) { const accessToken = authorization.accessToken; const apiUrl = authorization.apiUrl; @@ -201,6 +203,7 @@ async function startDev( debuggerOn={options.debugger} debugOtel={options.debugOtel} configPath={configPath} + configModule={configModule} /> ); } @@ -209,7 +212,8 @@ async function startDev( await getDevReactElement( config.config, authorization, - config.status === "file" ? config.path : undefined + config.status === "file" ? config.path : undefined, + config.status === "file" ? config.module : undefined ) ); @@ -236,6 +240,7 @@ type DevProps = { debuggerOn: boolean; debugOtel: boolean; configPath?: string; + configModule?: any; }; function useDev({ @@ -248,6 +253,7 @@ function useDev({ debuggerOn, debugOtel, configPath, + configModule, }: DevProps) { useEffect(() => { const websocketUrl = new URL(apiUrl); @@ -345,6 +351,8 @@ function useDev({ let ctx: BuildContext | undefined; + let firstBuild = true; + async function runBuild() { if (ctx) { // This will stop the watching @@ -386,8 +394,6 @@ function useDev({ ); } - let firstBuild = true; - logger.log(chalkGrey("○ Building background worker…")); ctx = await context({ @@ -512,6 +518,7 @@ function useDev({ }, debuggerOn, debugOtel, + resolveEnvVariables: createResolveEnvironmentVariablesFunction(configModule), }); try { @@ -870,3 +877,31 @@ async function findPnpmNodeModulesPath(): Promise { { type: "directory" } ); } + +let hasResolvedEnvVars = false; +let resolvedEnvVars: Record = {}; + +function createResolveEnvironmentVariablesFunction(configModule?: any) { + return async ( + env: Record, + worker: BackgroundWorker + ): Promise | undefined> => { + if (hasResolvedEnvVars) { + return resolvedEnvVars; + } + + const $resolvedEnvVars = await callResolveEnvVars( + configModule, + env, + "dev", + worker.params.projectConfig.project + ); + + if ($resolvedEnvVars) { + resolvedEnvVars = $resolvedEnvVars.variables; + hasResolvedEnvVars = true; + } + + return resolvedEnvVars; + }; +} diff --git a/packages/cli-v3/src/utilities/configFiles.ts b/packages/cli-v3/src/utilities/configFiles.ts index cee0df316..2d0715d54 100644 --- a/packages/cli-v3/src/utilities/configFiles.ts +++ b/packages/cli-v3/src/utilities/configFiles.ts @@ -122,6 +122,7 @@ export type ReadConfigResult = status: "file"; config: ResolvedConfig; path: string; + module?: any; } | { status: "in-memory"; @@ -183,6 +184,14 @@ export async function readConfig( tsx: false, force: false, }), + { + name: "native-node-modules", + setup(build) { + const opts = build.initialOptions; + opts.loader = opts.loader || {}; + opts.loader[".node"] = "copy"; + }, + }, ], }); @@ -202,6 +211,7 @@ export async function readConfig( status: "file", config: await resolveConfig(absoluteDir, config), path: configPath, + module: userConfigModule, }; } catch (error) { return { diff --git a/packages/cli-v3/src/utilities/resolveEnvVars.ts b/packages/cli-v3/src/utilities/resolveEnvVars.ts new file mode 100644 index 000000000..a09052a55 --- /dev/null +++ b/packages/cli-v3/src/utilities/resolveEnvVars.ts @@ -0,0 +1,62 @@ +import { logger } from "./logger"; + +export async function callResolveEnvVars( + configModule: any, + env: Record, + environment: string, + projectRef: string +): Promise<{ variables: Record; override: boolean } | undefined> { + if ( + configModule && + configModule.resolveEnvVars && + typeof configModule.resolveEnvVars === "function" + ) { + let resolvedEnvVars: Record = {}; + + try { + let result = await configModule.resolveEnvVars({ + projectRef, + environment, + env, + }); + + if (!result) { + return; + } + + result = await result; + + if (typeof result === "object" && result !== null && "variables" in result) { + const variables = result.variables; + + if (Array.isArray(variables)) { + for (const item of variables) { + if ( + typeof item === "object" && + item !== null && + "name" in item && + "value" in item && + typeof item.name === "string" && + typeof item.value === "string" + ) { + resolvedEnvVars[item.name] = item.value; + } + } + } else if (typeof variables === "object") { + for (const [key, value] of Object.entries(variables)) { + if (typeof key === "string" && typeof value === "string") { + resolvedEnvVars[key] = value; + } + } + } + } + + return { + variables: resolvedEnvVars, + override: result.override, + }; + } catch (error) { + logger.error(error); + } + } +} diff --git a/packages/cli-v3/src/workers/dev/backgroundWorker.ts b/packages/cli-v3/src/workers/dev/backgroundWorker.ts index a345fb086..5172b1f35 100644 --- a/packages/cli-v3/src/workers/dev/backgroundWorker.ts +++ b/packages/cli-v3/src/workers/dev/backgroundWorker.ts @@ -37,6 +37,7 @@ import { safeDeleteFileSync } from "../../utilities/fileSystem.js"; import { installPackages } from "../../utilities/installPackages.js"; import { logger } from "../../utilities/logger.js"; import { TaskMetadataParseError, UncaughtExceptionError } from "../common/errors.js"; +import { env } from "node:process"; export type CurrentWorkers = BackgroundWorkerCoordinator["currentWorkers"]; export class BackgroundWorkerCoordinator { @@ -263,7 +264,12 @@ export type BackgroundWorkerParams = { projectConfig: ResolvedConfig; debuggerOn: boolean; debugOtel?: boolean; + resolveEnvVariables?: ( + env: Record, + worker: BackgroundWorker + ) => Promise | undefined>; }; + export class BackgroundWorker { private _initialized: boolean = false; private _handler = new ZodMessageHandler({ @@ -280,9 +286,11 @@ export class BackgroundWorker { private _closed: boolean = false; + private _fullEnv: Record = {}; + constructor( public path: string, - private params: BackgroundWorkerParams + public params: BackgroundWorkerParams ) {} close() { @@ -320,19 +328,34 @@ export class BackgroundWorker { const cwd = dirname(this.path); - const fullEnv = { + this._fullEnv = { ...this.params.env, ...this.#readEnvVars(), ...(this.params.debugOtel ? { OTEL_LOG_LEVEL: "debug" } : {}), }; - logger.debug("Initializing worker", { path: this.path, cwd, fullEnv }); + let resolvedEnvVars: Record = {}; + + if (this.params.resolveEnvVariables) { + const resolvedEnv = await this.params.resolveEnvVariables(this._fullEnv, this); + + if (resolvedEnv) { + resolvedEnvVars = resolvedEnv; + } + } + + this._fullEnv = { + ...this._fullEnv, + ...resolvedEnvVars, + }; + + logger.debug("Initializing worker", { path: this.path, cwd, fullEnv: this._fullEnv }); this.tasks = await new Promise>((resolve, reject) => { const child = fork(this.path, { stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"], cwd, - env: fullEnv, + env: this._fullEnv, }); // Set a timeout to kill the child process if it doesn't respond @@ -404,9 +427,8 @@ export class BackgroundWorker { payload.execution, this.path, { - ...this.params.env, + ...this._fullEnv, ...(payload.environment ?? {}), - ...this.#readEnvVars(), }, this.metadata, this.params diff --git a/packages/core/package.json b/packages/core/package.json index 6143edc5a..7b6bca42d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -134,13 +134,14 @@ "@opentelemetry/sdk-trace-base": "^1.22.0", "@opentelemetry/sdk-trace-node": "^1.22.0", "@opentelemetry/semantic-conventions": "^1.22.0", + "form-data-encoder": "^4.0.2", "humanize-duration": "^3.27.3", + "socket.io-client": "4.7.4", "superjson": "^2.2.1", "ulidx": "^2.2.1", "zod": "3.22.3", "zod-error": "1.5.0", - "zod-validation-error": "^1.5.0", - "socket.io-client": "4.7.4" + "zod-validation-error": "^1.5.0" }, "devDependencies": { "@trigger.dev/tsconfig": "workspace:*", @@ -150,10 +151,10 @@ "@types/node": "20.12.7", "jest": "^29.6.2", "rimraf": "^3.0.2", + "socket.io": "4.7.4", "ts-jest": "^29.1.1", "tsup": "^8.0.1", - "typescript": "^5.3.0", - "socket.io": "4.7.4" + "typescript": "^5.3.0" }, "engines": { "node": ">=18.0.0" diff --git a/packages/core/src/v3/apiClient/index.ts b/packages/core/src/v3/apiClient/index.ts index 5af8061f9..00449eb40 100644 --- a/packages/core/src/v3/apiClient/index.ts +++ b/packages/core/src/v3/apiClient/index.ts @@ -6,9 +6,13 @@ import { BatchTriggerTaskRequestBody, BatchTriggerTaskResponse, CanceledRunResponse, + CreateEnvironmentVariableRequestBody, CreateScheduleOptions, CreateUploadPayloadUrlResponseBody, DeletedScheduleObject, + EnvironmentVariableResponseBody, + EnvironmentVariableValue, + EnvironmentVariables, ListScheduleOptions, ListSchedulesResult, ReplayRunResponse, @@ -17,10 +21,22 @@ import { TaskRunExecutionResult, TriggerTaskRequestBody, TriggerTaskResponse, + UpdateEnvironmentVariableRequestBody, UpdateScheduleOptions, } from "../schemas"; import { taskContext } from "../task-context-api"; -import { ZodFetchOptions, zodfetch } from "../zodfetch"; +import { ZodFetchOptions, isRecordLike, zodfetch, zodupload } from "../zodfetch"; +import { + ImportEnvironmentVariablesParams, + CreateEnvironmentVariableParams, + UpdateEnvironmentVariableParams, +} from "./types"; + +export type { + ImportEnvironmentVariablesParams, + CreateEnvironmentVariableParams, + UpdateEnvironmentVariableParams, +}; export type TriggerOptions = { spanParentAsLink?: boolean; @@ -234,6 +250,92 @@ export class ApiClient { }); } + listEnvVars(projectRef: string, slug: string) { + return zodfetch( + EnvironmentVariables, + `${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}`, + { + method: "GET", + headers: this.#getHeaders(false), + } + ); + } + + importEnvVars(projectRef: string, slug: string, body: ImportEnvironmentVariablesParams) { + if (isRecordLike(body.variables)) { + return zodfetch( + EnvironmentVariableResponseBody, + `${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}/import`, + { + method: "POST", + headers: this.#getHeaders(false), + body: JSON.stringify(body), + } + ); + } else { + return zodupload( + EnvironmentVariableResponseBody, + `${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}/import`, + body, + { + method: "POST", + headers: this.#getHeaders(false), + } + ); + } + } + + retrieveEnvVar(projectRef: string, slug: string, key: string) { + return zodfetch( + EnvironmentVariableValue, + `${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}/${key}`, + { + method: "GET", + headers: this.#getHeaders(false), + } + ); + } + + createEnvVar(projectRef: string, slug: string, body: CreateEnvironmentVariableRequestBody) { + return zodfetch( + EnvironmentVariableResponseBody, + `${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}`, + { + method: "POST", + headers: this.#getHeaders(false), + body: JSON.stringify(body), + } + ); + } + + updateEnvVar( + projectRef: string, + slug: string, + key: string, + body: UpdateEnvironmentVariableRequestBody + ) { + return zodfetch( + EnvironmentVariableResponseBody, + `${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}/${key}`, + { + method: "PUT", + headers: this.#getHeaders(false), + body: JSON.stringify(body), + } + ); + } + + deleteEnvVar(projectRef: string, slug: string, key: string) { + return zodfetch( + EnvironmentVariableResponseBody, + `${this.baseUrl}/api/v1/projects/${projectRef}/envvars/${slug}/${key}`, + { + method: "DELETE", + headers: this.#getHeaders(false), + } + ); + } + #getHeaders(spanParentAsLink: boolean) { const headers: Record = { "Content-Type": "application/json", diff --git a/packages/core/src/v3/apiClient/types.ts b/packages/core/src/v3/apiClient/types.ts new file mode 100644 index 000000000..602c0079e --- /dev/null +++ b/packages/core/src/v3/apiClient/types.ts @@ -0,0 +1,24 @@ +import { BlobLikePart, Uploadable } from "../zodfetch"; + +export interface ImportEnvironmentVariablesParams { + /** + * The variables to be imported. If a variable with the same key already exists, it will be overwritten when `override` is `true`. + * + * There are two ways to specify the variables: + * + * 1. As a record of key-value pairs. e.g. `{ "key1": "value1", "key2": "value2" }` + * 2. As an "uploadable" object in dotenv format. An uploadable can be a Node readable stream, a string, or a Buffer. You can also pass the return value of a `fetch` call. + */ + variables: Uploadable | BlobLikePart | Record; + + override?: boolean; +} + +export interface CreateEnvironmentVariableParams { + name: string; + value: string; +} + +export interface UpdateEnvironmentVariableParams { + value: string; +} diff --git a/packages/core/src/v3/apiClientManager/index.ts b/packages/core/src/v3/apiClientManager/index.ts index 95b911ef5..ab1c296bc 100644 --- a/packages/core/src/v3/apiClientManager/index.ts +++ b/packages/core/src/v3/apiClientManager/index.ts @@ -33,7 +33,7 @@ export class APIClientManagerAPI { get accessToken(): string | undefined { const store = this.#getConfig(); - return store?.secretKey ?? getEnvVar("TRIGGER_SECRET_KEY"); + return store?.secretKey ?? getEnvVar("TRIGGER_SECRET_KEY") ?? getEnvVar("TRIGGER_ACCESS_TOKEN"); } get client(): ApiClient | undefined { diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 1fb90b6b0..23a32737c 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -363,3 +363,50 @@ export const RetrieveRunResponse = z.object({ }); export type RetrieveRunResponse = z.infer; + +export const CreateEnvironmentVariableRequestBody = z.object({ + name: z.string(), + value: z.string(), +}); + +export type CreateEnvironmentVariableRequestBody = z.infer< + typeof CreateEnvironmentVariableRequestBody +>; + +export const UpdateEnvironmentVariableRequestBody = z.object({ + value: z.string(), +}); + +export type UpdateEnvironmentVariableRequestBody = z.infer< + typeof UpdateEnvironmentVariableRequestBody +>; + +export const ImportEnvironmentVariablesRequestBody = z.object({ + variables: z.record(z.string()), + override: z.boolean().optional(), +}); + +export type ImportEnvironmentVariablesRequestBody = z.infer< + typeof ImportEnvironmentVariablesRequestBody +>; + +export const EnvironmentVariableResponseBody = z.object({ + success: z.boolean(), +}); + +export type EnvironmentVariableResponseBody = z.infer; + +export const EnvironmentVariableValue = z.object({ + value: z.string(), +}); + +export type EnvironmentVariableValue = z.infer; + +export const EnvironmentVariable = z.object({ + name: z.string(), + value: z.string(), +}); + +export const EnvironmentVariables = z.array(EnvironmentVariable); + +export type EnvironmentVariables = z.infer; diff --git a/packages/core/src/v3/types/index.ts b/packages/core/src/v3/types/index.ts index e1a79157f..e3d6f2c00 100644 --- a/packages/core/src/v3/types/index.ts +++ b/packages/core/src/v3/types/index.ts @@ -66,6 +66,27 @@ export type HandleErrorFunction = ( params: HandleErrorArgs ) => HandleErrorResult; +type ResolveEnvironmentVariablesOptions = { + variables: Record | Array<{ name: string; value: string }>; + override?: boolean; +}; + +export type ResolveEnvironmentVariablesResult = + | ResolveEnvironmentVariablesOptions + | Promise + | void + | undefined; + +export type ResolveEnvironmentVariablesParams = { + projectRef: string; + environment: "dev" | "staging" | "prod"; + env: Record; +}; + +export type ResolveEnvironmentVariablesFunction = ( + params: ResolveEnvironmentVariablesParams +) => ResolveEnvironmentVariablesResult; + export type TaskMetadataWithFunctions = TaskMetadata & { fns: { run: (payload: any, params: RunFnParams) => Promise; diff --git a/packages/core/src/v3/zodfetch.ts b/packages/core/src/v3/zodfetch.ts index f1fecf4f5..e77b27f9d 100644 --- a/packages/core/src/v3/zodfetch.ts +++ b/packages/core/src/v3/zodfetch.ts @@ -3,6 +3,8 @@ import { fromZodError } from "zod-validation-error"; import { APIConnectionError, APIError } from "./apiErrors"; import { RetryOptions } from "./schemas"; import { calculateNextRetryDelay } from "./utils/retries"; +import { FormDataEncoder } from "form-data-encoder"; +import { Readable } from "stream"; export const defaultRetryOptions = { maxAttempts: 3, @@ -16,22 +18,75 @@ export type ZodFetchOptions = { retry?: RetryOptions; }; -export async function zodfetch( - schema: z.Schema, +export async function zodfetch( + schema: TResponseBodySchema, url: string, requestInit?: RequestInit, options?: ZodFetchOptions -): Promise { +): Promise> { return await _doZodFetch(schema, url, requestInit, options); } -async function _doZodFetch( - schema: z.Schema, +export class MultipartBody { + constructor(public body: any) {} + get [Symbol.toStringTag](): string { + return "MultipartBody"; + } +} + +export async function zodupload< + TResponseBodySchema extends z.ZodTypeAny, + TBody = Record, +>( + schema: TResponseBodySchema, + url: string, + body: TBody, + requestInit?: RequestInit, + options?: ZodFetchOptions +): Promise> { + const form = await createForm(body); + const encoder = new FormDataEncoder(form); + + const finalHeaders: Record = {}; + + for (const [key, value] of Object.entries(requestInit?.headers || {})) { + finalHeaders[key] = value as string; + } + + for (const [key, value] of Object.entries(encoder.headers)) { + finalHeaders[key] = value; + } + + finalHeaders["Content-Length"] = String(encoder.contentLength); + + const finalRequestInit: RequestInit = { + ...requestInit, + headers: finalHeaders, + body: Readable.from(encoder) as any, + // @ts-expect-error + duplex: "half", + }; + + return await _doZodFetch(schema, url, finalRequestInit, options); +} + +export const createForm = async >( + body: T | undefined +): Promise => { + const form = new FormData(); + await Promise.all( + Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value)) + ); + return form; +}; + +async function _doZodFetch( + schema: TResponseBodySchema, url: string, requestInit?: RequestInit, options?: ZodFetchOptions, attempt = 1 -): Promise { +): Promise> { try { const response = await fetch(url, requestInitWithCache(requestInit)); @@ -172,3 +227,211 @@ function requestInitWithCache(requestInit?: RequestInit): RequestInit { return requestInit ?? {}; } } + +const addFormValue = async (form: FormData, key: string, value: unknown): Promise => { + if (value === undefined) return; + if (value == null) { + throw new TypeError( + `Received null for "${key}"; to pass null in FormData, you must use the string 'null'` + ); + } + + // TODO: make nested formats configurable + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + form.append(key, String(value)); + } else if ( + isUploadable(value) || + isBlobLike(value) || + value instanceof Buffer || + value instanceof ArrayBuffer + ) { + const file = await toFile(value); + form.append(key, file as File); + } else if (Array.isArray(value)) { + await Promise.all(value.map((entry) => addFormValue(form, key + "[]", entry))); + } else if (typeof value === "object") { + await Promise.all( + Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)) + ); + } else { + throw new TypeError( + `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead` + ); + } +}; + +export type ToFileInput = Uploadable | Exclude | AsyncIterable; + +/** + * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats + * @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s + * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible + * @param {Object=} options additional properties + * @param {string=} options.type the MIME type of the content + * @param {number=} options.lastModified the last modified timestamp + * @returns a {@link File} with the given properties + */ +export async function toFile( + value: ToFileInput | PromiseLike, + name?: string | null | undefined, + options?: FilePropertyBag | undefined +): Promise { + // If it's a promise, resolve it. + value = await value; + + // Use the file's options if there isn't one provided + options ??= isFileLike(value) ? { lastModified: value.lastModified, type: value.type } : {}; + + if (isResponseLike(value)) { + const blob = await value.blob(); + name ||= new URL(value.url).pathname.split(/[\\/]/).pop() ?? "unknown_file"; + + return new File([blob as any], name, options); + } + + const bits = await getBytes(value); + + name ||= getName(value) ?? "unknown_file"; + + if (!options.type) { + const type = (bits[0] as any)?.type; + if (typeof type === "string") { + options = { ...options, type }; + } + } + + return new File(bits, name, options); +} + +function getName(value: any): string | undefined { + return ( + getStringFromMaybeBuffer(value.name) || + getStringFromMaybeBuffer(value.filename) || + // For fs.ReadStream + getStringFromMaybeBuffer(value.path)?.split(/[\\/]/).pop() + ); +} + +const getStringFromMaybeBuffer = (x: string | Buffer | unknown): string | undefined => { + if (typeof x === "string") return x; + if (typeof Buffer !== "undefined" && x instanceof Buffer) return String(x); + return undefined; +}; + +async function getBytes(value: ToFileInput): Promise> { + let parts: Array = []; + if ( + typeof value === "string" || + ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc. + value instanceof ArrayBuffer + ) { + parts.push(value); + } else if (isBlobLike(value)) { + parts.push(await value.arrayBuffer()); + } else if ( + isAsyncIterableIterator(value) // includes Readable, ReadableStream, etc. + ) { + for await (const chunk of value) { + parts.push(chunk as BlobPart); // TODO, consider validating? + } + } else { + throw new Error( + `Unexpected data type: ${typeof value}; constructor: ${value?.constructor + ?.name}; props: ${propsForError(value)}` + ); + } + + return parts; +} + +function propsForError(value: any): string { + const props = Object.getOwnPropertyNames(value); + return `[${props.map((p) => `"${p}"`).join(", ")}]`; +} + +const isAsyncIterableIterator = (value: any): value is AsyncIterableIterator => + value != null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function"; + +/** + * Intended to match web.Blob, node.Blob, node-fetch.Blob, etc. + */ +export interface BlobLike { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ + readonly size: number; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ + readonly type: string; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ + text(): Promise; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ + slice(start?: number, end?: number): BlobLike; + // unfortunately @types/node-fetch@^2.6.4 doesn't type the arrayBuffer method +} + +/** + * Intended to match web.File, node.File, node-fetch.File, etc. + */ +export interface FileLike extends BlobLike { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ + readonly lastModified: number; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ + readonly name: string; +} + +/** + * Intended to match web.Response, node.Response, node-fetch.Response, etc. + */ +export interface ResponseLike { + url: string; + blob(): Promise; +} + +export type Uploadable = FileLike | ResponseLike | Readable; + +export const isResponseLike = (value: any): value is ResponseLike => + value != null && + typeof value === "object" && + typeof value.url === "string" && + typeof value.blob === "function"; + +export const isFileLike = (value: any): value is FileLike => + value != null && + typeof value === "object" && + typeof value.name === "string" && + typeof value.lastModified === "number" && + isBlobLike(value); + +/** + * The BlobLike type omits arrayBuffer() because @types/node-fetch@^2.6.4 lacks it; but this check + * adds the arrayBuffer() method type because it is available and used at runtime + */ +export const isBlobLike = ( + value: any +): value is BlobLike & { arrayBuffer(): Promise } => + value != null && + typeof value === "object" && + typeof value.size === "number" && + typeof value.type === "string" && + typeof value.text === "function" && + typeof value.slice === "function" && + typeof value.arrayBuffer === "function"; + +export const isFsReadStream = (value: any): value is Readable => value instanceof Readable; + +export const isUploadable = (value: any): value is Uploadable => { + return isFileLike(value) || isResponseLike(value) || isFsReadStream(value); +}; + +export type BlobLikePart = + | string + | ArrayBuffer + | ArrayBufferView + | BlobLike + | Uint8Array + | DataView; + +export const isRecordLike = (value: any): value is Record => + value != null && + typeof value === "object" && + !Array.isArray(value) && + Object.keys(value).length > 0 && + Object.keys(value).every((key) => typeof key === "string" && typeof value[key] === "string"); diff --git a/packages/trigger-sdk/src/v3/config.ts b/packages/trigger-sdk/src/v3/config.ts index 48c50779b..a3ef9c324 100644 --- a/packages/trigger-sdk/src/v3/config.ts +++ b/packages/trigger-sdk/src/v3/config.ts @@ -2,4 +2,7 @@ export type { ProjectConfig as TriggerConfig, HandleErrorArgs, HandleErrorFunction, + ResolveEnvironmentVariablesFunction, + ResolveEnvironmentVariablesParams, + ResolveEnvironmentVariablesResult, } from "@trigger.dev/core/v3"; diff --git a/packages/trigger-sdk/src/v3/envvars.ts b/packages/trigger-sdk/src/v3/envvars.ts new file mode 100644 index 000000000..cc00b12f2 --- /dev/null +++ b/packages/trigger-sdk/src/v3/envvars.ts @@ -0,0 +1,364 @@ +import type { + ImportEnvironmentVariablesParams, + EnvironmentVariableResponseBody, + EnvironmentVariables, + CreateEnvironmentVariableParams, + EnvironmentVariableValue, + UpdateEnvironmentVariableParams, +} from "@trigger.dev/core/v3"; +import { SemanticInternalAttributes, apiClientManager, taskContext } from "@trigger.dev/core/v3"; +import { apiClientMissingError } from "./shared"; +import { tracer } from "./tracer"; + +export type { ImportEnvironmentVariablesParams, CreateEnvironmentVariableParams }; + +export async function upload( + projectRef: string, + slug: string, + params: ImportEnvironmentVariablesParams +): Promise; +export async function upload( + params: ImportEnvironmentVariablesParams +): Promise; +export async function upload( + projectRefOrParams: string | ImportEnvironmentVariablesParams, + slug?: string, + params?: ImportEnvironmentVariablesParams +): Promise { + let $projectRef: string; + let $params: ImportEnvironmentVariablesParams; + let $slug: string; + + if (taskContext.ctx) { + if (typeof projectRefOrParams === "string") { + $projectRef = projectRefOrParams; + $slug = slug ?? taskContext.ctx.environment.slug; + + if (!params) { + throw new Error("params is required"); + } + + $params = params; + } else { + $params = projectRefOrParams; + $projectRef = taskContext.ctx.project.ref; + $slug = taskContext.ctx.environment.slug; + } + } else { + if (typeof projectRefOrParams !== "string") { + throw new Error("projectRef is required"); + } + + if (!slug) { + throw new Error("slug is required"); + } + + if (!params) { + throw new Error("params is required"); + } + + $projectRef = projectRefOrParams; + $slug = slug; + $params = params; + } + + const apiClient = apiClientManager.client; + + if (!apiClient) { + throw apiClientMissingError(); + } + + return await tracer.startActiveSpan( + "envvars.upload", + async (span) => { + return await apiClient.importEnvVars($projectRef, $slug, $params); + }, + { + attributes: { + [SemanticInternalAttributes.STYLE_ICON]: "file-upload", + }, + } + ); +} + +export async function list(projectRef: string, slug: string): Promise; +export async function list(): Promise; +export async function list(projectRef?: string, slug?: string): Promise { + const $projectRef = projectRef ?? taskContext.ctx?.project.ref; + const $slug = slug ?? taskContext.ctx?.environment.slug; + + if (!$projectRef) { + throw new Error("projectRef is required"); + } + + if (!$slug) { + throw new Error("slug is required"); + } + + const apiClient = apiClientManager.client; + + if (!apiClient) { + throw apiClientMissingError(); + } + + return await tracer.startActiveSpan( + "envvars.list", + async (span) => { + return await apiClient.listEnvVars($projectRef, $slug); + }, + { + attributes: { + [SemanticInternalAttributes.STYLE_ICON]: "id", + }, + } + ); +} + +export async function create( + projectRef: string, + slug: string, + params: CreateEnvironmentVariableParams +): Promise; +export async function create( + params: CreateEnvironmentVariableParams +): Promise; +export async function create( + projectRefOrParams: string | CreateEnvironmentVariableParams, + slug?: string, + params?: CreateEnvironmentVariableParams +): Promise { + let $projectRef: string; + let $slug: string; + let $params: CreateEnvironmentVariableParams; + + if (taskContext.ctx) { + if (typeof projectRefOrParams === "string") { + $projectRef = projectRefOrParams; + $slug = slug ?? taskContext.ctx.environment.slug; + + if (!params) { + throw new Error("params is required"); + } + + $params = params; + } else { + $params = projectRefOrParams; + $projectRef = taskContext.ctx.project.ref; + $slug = taskContext.ctx.environment.slug; + } + } else { + if (typeof projectRefOrParams !== "string") { + throw new Error("projectRef is required"); + } + + if (!slug) { + throw new Error("slug is required"); + } + + if (!params) { + throw new Error("params is required"); + } + + $projectRef = projectRefOrParams; + $slug = slug; + $params = params; + } + + const apiClient = apiClientManager.client; + + if (!apiClient) { + throw apiClientMissingError(); + } + + return await tracer.startActiveSpan( + "envvars.create", + async (span) => { + return await apiClient.createEnvVar($projectRef, $slug, $params); + }, + { + attributes: { + [SemanticInternalAttributes.STYLE_ICON]: "id", + }, + } + ); +} + +export async function retrieve( + projectRef: string, + slug: string, + name: string +): Promise; +export async function retrieve(name: string): Promise; +export async function retrieve( + projectRefOrName: string, + slug?: string, + name?: string +): Promise { + let $projectRef: string; + let $slug: string; + let $name: string; + + if (typeof name === "string") { + $projectRef = projectRefOrName; + $slug = slug!; + $name = name; + } else { + $projectRef = taskContext.ctx?.project.ref!; + $slug = taskContext.ctx?.environment.slug!; + $name = projectRefOrName; + } + + if (!$projectRef) { + throw new Error("projectRef is required"); + } + + if (!$slug) { + throw new Error("slug is required"); + } + + const apiClient = apiClientManager.client; + + if (!apiClient) { + throw apiClientMissingError(); + } + + return await tracer.startActiveSpan( + "envvars.retrieve", + async (span) => { + return await apiClient.retrieveEnvVar($projectRef, $slug, $name); + }, + { + attributes: { + [SemanticInternalAttributes.STYLE_ICON]: "id", + }, + } + ); +} + +export async function del( + projectRef: string, + slug: string, + name: string +): Promise; +export async function del(name: string): Promise; +export async function del( + projectRefOrName: string, + slug?: string, + name?: string +): Promise { + let $projectRef: string; + let $slug: string; + let $name: string; + + if (typeof name === "string") { + $projectRef = projectRefOrName; + $slug = slug!; + $name = name; + } else { + $projectRef = taskContext.ctx?.project.ref!; + $slug = taskContext.ctx?.environment.slug!; + $name = projectRefOrName; + } + + if (!$projectRef) { + throw new Error("projectRef is required"); + } + + if (!$slug) { + throw new Error("slug is required"); + } + + const apiClient = apiClientManager.client; + + if (!apiClient) { + throw apiClientMissingError(); + } + + return await tracer.startActiveSpan( + "envvars.delete", + async (span) => { + return await apiClient.deleteEnvVar($projectRef, $slug, $name); + }, + { + attributes: { + [SemanticInternalAttributes.STYLE_ICON]: "id", + }, + } + ); +} + +export async function update( + projectRef: string, + slug: string, + name: string, + params: UpdateEnvironmentVariableParams +): Promise; +export async function update( + name: string, + params: UpdateEnvironmentVariableParams +): Promise; +export async function update( + projectRefOrName: string, + slugOrParams: string | UpdateEnvironmentVariableParams, + name?: string, + params?: UpdateEnvironmentVariableParams +): Promise { + let $projectRef: string; + let $slug: string; + let $name: string; + let $params: UpdateEnvironmentVariableParams; + + if (taskContext.ctx) { + if (typeof slugOrParams === "string") { + $projectRef = slugOrParams; + $slug = slugOrParams ?? taskContext.ctx.environment.slug; + $name = name!; + + if (!params) { + throw new Error("params is required"); + } + + $params = params; + } else { + $params = slugOrParams; + $projectRef = taskContext.ctx.project.ref; + $slug = taskContext.ctx.environment.slug; + $name = projectRefOrName; + } + } else { + if (typeof slugOrParams !== "string") { + throw new Error("slug is required"); + } + + if (!projectRefOrName) { + throw new Error("projectRef is required"); + } + + if (!params) { + throw new Error("params is required"); + } + + $projectRef = projectRefOrName; + $slug = slugOrParams; + $name = name!; + $params = params; + } + + const apiClient = apiClientManager.client; + + if (!apiClient) { + throw apiClientMissingError(); + } + + return await tracer.startActiveSpan( + "envvars.update", + async (span) => { + return await apiClient.updateEnvVar($projectRef, $slug, $name, $params); + }, + { + attributes: { + [SemanticInternalAttributes.STYLE_ICON]: "id", + }, + } + ); +} diff --git a/packages/trigger-sdk/src/v3/index.ts b/packages/trigger-sdk/src/v3/index.ts index 00e6c1695..760a9cd63 100644 --- a/packages/trigger-sdk/src/v3/index.ts +++ b/packages/trigger-sdk/src/v3/index.ts @@ -27,8 +27,10 @@ export { type LogLevel, } from "@trigger.dev/core/v3"; -export { runs } from "./management"; +export { runs } from "./runs"; export * as schedules from "./schedules"; +export * as envvars from "./envvars"; +export type { ImportEnvironmentVariablesParams } from "./envvars"; /** * Register the global API client configuration. Alternatively, you can set the `TRIGGER_SECRET_KEY` and `TRIGGER_API_URL` environment variables. diff --git a/packages/trigger-sdk/src/v3/management.ts b/packages/trigger-sdk/src/v3/runs.ts similarity index 100% rename from packages/trigger-sdk/src/v3/management.ts rename to packages/trigger-sdk/src/v3/runs.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 908897bfd..825087364 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1748,6 +1748,9 @@ importers: '@opentelemetry/semantic-conventions': specifier: ^1.22.0 version: 1.22.0 + form-data-encoder: + specifier: ^4.0.2 + version: 4.0.2 humanize-duration: specifier: ^3.27.3 version: 3.27.3 @@ -3065,6 +3068,9 @@ importers: '@ffprobe-installer/ffprobe': specifier: ^2.1.2 version: 2.1.2 + '@infisical/sdk': + specifier: ^2.1.9 + version: 2.1.9 '@opentelemetry/api': specifier: 1.4.1 version: 1.4.1 @@ -7828,6 +7834,132 @@ packages: /@humanwhocodes/object-schema@1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + /@infisical/sdk-android-arm-eabi@2.1.9: + resolution: {integrity: sha512-tx0y9efMOmqxdw152BjAxpDcKhlURKftg20aAGUwHxgHaHtqJ1jHaZZ1B3Eo019NB+tDc/ynU1TpjIRKh28kxw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@infisical/sdk-android-arm64@2.1.9: + resolution: {integrity: sha512-kheXrht4rvEb6F7t9Xu821Pdm6ddzvB51NZjSiyIRwRDqv63HJEYr+MEd7ynIgux0JuMPQZIxmsyRZ5p/NDuFw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@infisical/sdk-darwin-arm64@2.1.9: + resolution: {integrity: sha512-qjsg67AKZYpjqG2DzJ/OAr+9thDT0YJg49ULknoBO+yaa6fT7PFRy682yRqnbJjg7vxRObXAyWcXSYjls+D4Lw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@infisical/sdk-darwin-x64@2.1.9: + resolution: {integrity: sha512-ErvwcR095VFIzCrXFyOCuED1jWltpX7S1et6WySdTsA811hLp6Txy/S57YNeGLJu/birVwrJdYbksVWdd+bqkQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@infisical/sdk-linux-arm-gnueabihf@2.1.9: + resolution: {integrity: sha512-2w0EVQc1Xq7ZE6nDsMwBVkaUwTuL0DIdwWdqxVZjo8jn26jwlhSOStxtbrk/cnhtWMQysQ/xKUI+0zZ/T32T3g==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@infisical/sdk-linux-arm64-gnu@2.1.9: + resolution: {integrity: sha512-5DMG5CJxX/wlWDyG3+cy1UkZAYuqt1OOEWwx3GNLU656RQ47AzwDFkvyiT9lalIihPHJFDN55hhSb002ru/zPw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@infisical/sdk-linux-arm64-musl@2.1.9: + resolution: {integrity: sha512-61oxqGh9Ih/d7igtduKS2ZjkZ4NU7PjcNDgK8IUwP6kruTQdOEAiNyAWChDx75zvJExvztjdpeczj62SHxmsRw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@infisical/sdk-linux-x64-gnu@2.1.9: + resolution: {integrity: sha512-X38Z4Q+GT0QU8nbRuWl3zyBv06Rfz1aWpewopxL3jahbKGoV8DaITDIaagVhwFl6YV5mMOLzXis5XrIMBJ6tLw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@infisical/sdk-linux-x64-musl@2.1.9: + resolution: {integrity: sha512-8RuL3MNSr2G/Xo79kdKHddxlprVOn4VmCbNG7K2VoY/ytUCGy8bCp5BrkdZOIr1jGm7GlD8eq1dXQkHa3Rn0og==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@infisical/sdk-win32-arm64-msvc@2.1.9: + resolution: {integrity: sha512-IUnFDIUFXFRUJ0B/wM32xGWu6Xz4w7GAw+75g9AYv1Ol+LtpbzoDcToh0IUFPsNpvIaOUZn1zxUUWsteWlAy3Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@infisical/sdk-win32-ia32-msvc@2.1.9: + resolution: {integrity: sha512-pDMpuP6X3GTSjzeahvCAscA0GBEkpMic0rwY+FdAmDcAX5SKoZ1pbRvcrMpAd4HMxfyLGeISuvzZ6pdMAowG5g==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@infisical/sdk-win32-x64-msvc@2.1.9: + resolution: {integrity: sha512-nrIfVPZPTJX85+8PZmZoA/Sk8rKpORzsYUTZ3zyqdfKAzQCpl587JXWCa5hud0ZWRw8SZuBYY06f9b75N+enyA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@infisical/sdk@2.1.9: + resolution: {integrity: sha512-Ds6W4huyjiEnDisf2UJyVugTpCapjHB4RvL69hSCi/raH7NKITff1R+ifWktQJ1cx7hFAE9wi+U0vRrW+WhUMA==} + engines: {node: '>= 10'} + optionalDependencies: + '@infisical/sdk-android-arm-eabi': 2.1.9 + '@infisical/sdk-android-arm64': 2.1.9 + '@infisical/sdk-darwin-arm64': 2.1.9 + '@infisical/sdk-darwin-x64': 2.1.9 + '@infisical/sdk-linux-arm-gnueabihf': 2.1.9 + '@infisical/sdk-linux-arm64-gnu': 2.1.9 + '@infisical/sdk-linux-arm64-musl': 2.1.9 + '@infisical/sdk-linux-x64-gnu': 2.1.9 + '@infisical/sdk-linux-x64-musl': 2.1.9 + '@infisical/sdk-win32-arm64-msvc': 2.1.9 + '@infisical/sdk-win32-ia32-msvc': 2.1.9 + '@infisical/sdk-win32-x64-msvc': 2.1.9 + dev: false + /@inquirer/confirm@3.0.0: resolution: {integrity: sha512-LHeuYP1D8NmQra1eR4UqvZMXwxEdDXyElJmmZfU44xdNLL6+GcQBS0uE16vyfZVjH8c22p9e+DStROfE/hyHrg==} engines: {node: '>=18'} @@ -22728,6 +22860,11 @@ packages: engines: {node: '>= 14.17'} dev: false + /form-data-encoder@4.0.2: + resolution: {integrity: sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==} + engines: {node: '>= 18'} + dev: false + /form-data@2.3.3: resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} engines: {node: '>= 0.12'} diff --git a/references/v3-catalog/.uploadable-env b/references/v3-catalog/.uploadable-env new file mode 100644 index 000000000..b61445517 --- /dev/null +++ b/references/v3-catalog/.uploadable-env @@ -0,0 +1,2 @@ +THIS_IS_MY_KEY=1234567890 +ANOTHER_KEY=0987654321 \ No newline at end of file diff --git a/references/v3-catalog/package.json b/references/v3-catalog/package.json index 40b5da4c0..a69369d62 100644 --- a/references/v3-catalog/package.json +++ b/references/v3-catalog/package.json @@ -9,6 +9,7 @@ "dependencies": { "@ffmpeg-installer/ffmpeg": "^1.1.0", "@ffprobe-installer/ffprobe": "^2.1.2", + "@infisical/sdk": "^2.1.9", "@opentelemetry/api": "1.4.1", "@react-email/components": "^0.0.17", "@react-email/render": "^0.0.7", diff --git a/references/v3-catalog/src/management.ts b/references/v3-catalog/src/management.ts index 8fdae18a0..90ee05291 100644 --- a/references/v3-catalog/src/management.ts +++ b/references/v3-catalog/src/management.ts @@ -1,11 +1,93 @@ import { tracer } from "./tracer"; -import { APIError, configure, runs, schedules } from "@trigger.dev/sdk/v3"; +import { APIError, configure, runs, schedules, envvars } from "@trigger.dev/sdk/v3"; import { simpleChildTask } from "./trigger/subtasks"; import dotenv from "dotenv"; import { firstScheduledTask } from "./trigger/scheduled"; +import { createReadStream } from "node:fs"; dotenv.config(); +async function uploadEnvVars() { + configure({ + secretKey: process.env.TRIGGER_ACCESS_TOKEN, + }); + + const response1 = await envvars.upload("yubjwjsfkxnylobaqvqz", "dev", { + variables: { + MY_ENV_VAR: "MY_ENV_VAR_VALUE", + }, + override: true, + }); + + console.log("response1", response1); + + const envVars = await envvars.list("yubjwjsfkxnylobaqvqz", "dev"); + + console.log("envVars", envVars); + + const createResponse = await envvars.create("yubjwjsfkxnylobaqvqz", "dev", { + name: "MY_ENV_VAR_CREATE", + value: "MY_ENV_VAR_VALUE_CREATE", + }); + + console.log("createResponse", createResponse); + + const retrieveResponse = await envvars.retrieve( + "yubjwjsfkxnylobaqvqz", + "dev", + "MY_ENV_VAR_CREATE" + ); + + console.log("retrieveResponse", retrieveResponse); + + const updateResponse = await envvars.update("yubjwjsfkxnylobaqvqz", "dev", "MY_ENV_VAR_CREATE", { + value: "MY_ENV_VAR_VALUE_CREATE_UPDATED", + }); + + console.log("updateResponse", updateResponse); + + const deleteResponse = await envvars.del("yubjwjsfkxnylobaqvqz", "dev", "MY_ENV_VAR_CREATE"); + + console.log("deleteResponse", deleteResponse); + + const response2 = await envvars.upload("yubjwjsfkxnylobaqvqz", "dev", { + variables: createReadStream(".uploadable-env"), + override: true, + }); + + console.log("response2", response2); + + const response3 = await envvars.upload("yubjwjsfkxnylobaqvqz", "prod", { + variables: createReadStream(".uploadable-env"), + override: true, + }); + + console.log("response3", response3); + + const response4 = await envvars.upload("yubjwjsfkxnylobaqvqz", "prod", { + variables: await fetch( + "https://gist.githubusercontent.com/ericallam/7a1001c6b03986a74d0f8aad4fd890aa/raw/fe2bc4da82f3b17178d47f58ec1458af47af5035/.env" + ), + override: true, + }); + + console.log("response4", response4); + + const response5 = await envvars.upload("yubjwjsfkxnylobaqvqz", "prod", { + variables: new File(["IM_A_FILE=GREAT_FOR_YOU"], ".env"), + override: true, + }); + + console.log("response5", response5); + + const response6 = await envvars.upload("yubjwjsfkxnylobaqvqz", "prod", { + variables: Buffer.from("IN_BUFFER=TRUE"), + override: true, + }); + + console.log("response6", response6); +} + export async function run() { await tracer.startActiveSpan("run", async (span) => { try { @@ -88,4 +170,5 @@ export async function run() { }); } -run(); +// run(); +uploadEnvVars().catch(console.error); diff --git a/references/v3-catalog/src/trigger/simple.ts b/references/v3-catalog/src/trigger/simple.ts index 6b150625d..0dcab6d5e 100644 --- a/references/v3-catalog/src/trigger/simple.ts +++ b/references/v3-catalog/src/trigger/simple.ts @@ -1,5 +1,5 @@ import "server-only"; -import { logger, task, wait } from "@trigger.dev/sdk/v3"; +import { envvars, logger, task, wait } from "@trigger.dev/sdk/v3"; import { traceAsync } from "@/telemetry"; export const simplestTask = task({ @@ -10,7 +10,7 @@ export const simplestTask = task({ body: JSON.stringify({ hello: "world", taskId: "fetch-post-task", - foo: "barrrrrrrrrrrrrrrrrrrr", + foo: "barrrrrrrrrrrrrrrrrrrrrr", }), }); @@ -31,6 +31,18 @@ export const taskWithSpecialCharacters = task({ }, }); +export const updateEnvVars = task({ + id: "update-env-vars", + run: async () => { + return await envvars.upload({ + variables: await fetch( + "https://gist.githubusercontent.com/ericallam/7a1001c6b03986a74d0f8aad4fd890aa/raw/fe2bc4da82f3b17178d47f58ec1458af47af5035/.env" + ), + override: true, + }); + }, +}); + export const createJsonHeroDoc = task({ id: "create-jsonhero-doc", run: async (payload: { title: string; content: any }, { ctx }) => { diff --git a/references/v3-catalog/trigger.config.ts b/references/v3-catalog/trigger.config.ts index 56e1c9097..fbb90b1f4 100644 --- a/references/v3-catalog/trigger.config.ts +++ b/references/v3-catalog/trigger.config.ts @@ -1,9 +1,33 @@ -import type { TriggerConfig } from "@trigger.dev/sdk/v3"; +import type { TriggerConfig, ResolveEnvironmentVariablesFunction } from "@trigger.dev/sdk/v3"; import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai"; import { AppDataSource } from "@/trigger/orm"; +import { InfisicalClient } from "@infisical/sdk"; export { handleError } from "./src/handleError"; +export const resolveEnvVars: ResolveEnvironmentVariablesFunction = async ({ + projectRef, + env, + environment, +}) => { + const client = new InfisicalClient({ + clientId: env.INFISICAL_CLIENT_ID, + clientSecret: env.INFISICAL_CLIENT_SECRET, + }); + + const secrets = await client.listSecrets({ + environment, + projectId: env.INFISICAL_PROJECT_ID!, + }); + + return { + variables: secrets.map((secret) => ({ + name: secret.secretKey, + value: secret.secretValue, + })), + }; +}; + export const config: TriggerConfig = { project: "yubjwjsfkxnylobaqvqz", retries: { From ff3cd97a3b0472f2ebfb467f42e50db276d9b0ca Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 23 May 2024 17:41:25 +0100 Subject: [PATCH 19/27] Fixed the syntax highlighting for JSON and TypeScript with the new Prisma renderer --- apps/webapp/app/components/code/CodeBlock.tsx | 13 +++++++++++-- apps/webapp/remix.config.js | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/components/code/CodeBlock.tsx b/apps/webapp/app/components/code/CodeBlock.tsx index ff0956a35..144c9ad33 100644 --- a/apps/webapp/app/components/code/CodeBlock.tsx +++ b/apps/webapp/app/components/code/CodeBlock.tsx @@ -1,14 +1,23 @@ import { Clipboard, ClipboardCheck } from "lucide-react"; import type { Language, PrismTheme } from "prism-react-renderer"; -import { Highlight } from "prism-react-renderer"; +import { Highlight, Prism } from "prism-react-renderer"; import { forwardRef, useCallback, useState } from "react"; import { cn } from "~/utils/cn"; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip"; import { Paragraph } from "../primitives/Paragraph"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip"; //This is a fork of https://github.com/mantinedev/mantine/blob/master/src/mantine-prism/src/Prism/Prism.tsx //it didn't support highlighting lines by dimming the rest of the code, or animations on the highlighting +async function setup() { + (typeof global !== "undefined" ? global : window).Prism = Prism; + //@ts-ignore + await import("prismjs/components/prism-json"); + //@ts-ignore + await import("prismjs/components/prism-typescript"); +} +setup(); + type CodeBlockProps = { /** Code which will be highlighted */ code: string; diff --git a/apps/webapp/remix.config.js b/apps/webapp/remix.config.js index 7ddae5e0e..ea4bf3037 100644 --- a/apps/webapp/remix.config.js +++ b/apps/webapp/remix.config.js @@ -20,6 +20,8 @@ module.exports = { "highlight.run", "random-words", "superjson", + "prismjs/components/prism-json", + "prismjs/components/prism-typescript", ], browserNodeBuiltinsPolyfill: { modules: { path: true, os: true, crypto: true } }, watchPaths: async () => { From dbbc7760f3cc8611e33c9f28d1870a5eec029d4b Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 23 May 2024 18:27:31 +0100 Subject: [PATCH 20/27] When logging out clear the projectId from the session if there is one --- apps/webapp/app/routes/logout.tsx | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/apps/webapp/app/routes/logout.tsx b/apps/webapp/app/routes/logout.tsx index 25e42ac1c..c0c133f2c 100644 --- a/apps/webapp/app/routes/logout.tsx +++ b/apps/webapp/app/routes/logout.tsx @@ -1,10 +1,36 @@ -import type { ActionFunction, LoaderFunction } from "@remix-run/node"; +import { redirect, type ActionFunction, type LoaderFunction } from "@remix-run/node"; import { authenticator } from "~/services/auth.server"; +import { + clearCurrentProjectId, + commitCurrentProjectSession, + getCurrentProjectId, +} from "~/services/currentProject.server"; +import { logoutPath } from "~/utils/pathBuilder"; export const action: ActionFunction = async ({ request }) => { + const projectId = await getCurrentProjectId(request); + if (projectId) { + const removeProjectIdSession = await clearCurrentProjectId(request); + return redirect(logoutPath(), { + headers: { + "Set-Cookie": await commitCurrentProjectSession(removeProjectIdSession), + }, + }); + } + return await authenticator.logout(request, { redirectTo: "/" }); }; export const loader: LoaderFunction = async ({ request }) => { + const projectId = await getCurrentProjectId(request); + if (projectId) { + const removeProjectIdSession = await clearCurrentProjectId(request); + return redirect(logoutPath(), { + headers: { + "Set-Cookie": await commitCurrentProjectSession(removeProjectIdSession), + }, + }); + } + return await authenticator.logout(request, { redirectTo: "/" }); }; From 1ca3b99ace43b4f95b043857313537919204ace1 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Fri, 24 May 2024 09:44:17 +0100 Subject: [PATCH 21/27] =?UTF-8?q?Remove=20=E2=80=9CPAUSED=E2=80=9D=20statu?= =?UTF-8?q?s=20from=20being=20filterable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/webapp/app/components/runs/v3/RunFilters.tsx | 3 ++- .../app/components/runs/v3/TaskRunStatus.tsx | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/webapp/app/components/runs/v3/RunFilters.tsx b/apps/webapp/app/components/runs/v3/RunFilters.tsx index 6ca258b4d..082815118 100644 --- a/apps/webapp/app/components/runs/v3/RunFilters.tsx +++ b/apps/webapp/app/components/runs/v3/RunFilters.tsx @@ -44,6 +44,7 @@ import { Button } from "../../primitives/Buttons"; import { TaskRunStatusCombo, allTaskRunStatuses, + filterableTaskRunStatuses, descriptionForTaskRunStatus, runStatusTitle, } from "./TaskRunStatus"; @@ -272,7 +273,7 @@ function MainMenu({ searchValue, trigger, clearSearchValue, setFilterType }: Men ); } -const statuses = allTaskRunStatuses.map((status) => ({ +const statuses = filterableTaskRunStatuses.map((status) => ({ title: runStatusTitle(status), value: status, })); diff --git a/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx b/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx index 1a49eb8ed..3e7ef6db6 100644 --- a/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx +++ b/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx @@ -30,6 +30,20 @@ export const allTaskRunStatuses = [ "SYSTEM_FAILURE", ] as const satisfies Readonly>; +export const filterableTaskRunStatuses = [ + "WAITING_FOR_DEPLOY", + "PENDING", + "EXECUTING", + "RETRYING_AFTER_FAILURE", + "WAITING_TO_RESUME", + "COMPLETED_SUCCESSFULLY", + "CANCELED", + "COMPLETED_WITH_ERRORS", + "CRASHED", + "INTERRUPTED", + "SYSTEM_FAILURE", +] as const satisfies Readonly>; + const taskRunStatusDescriptions: Record = { PENDING: "Task is waiting to be executed", WAITING_FOR_DEPLOY: "Task needs to be deployed first to start executing", From 129c6022a04bd2bf3251c0cf49c4909b64af3228 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 24 May 2024 10:26:31 +0100 Subject: [PATCH 22/27] Make the resolveEnvVars fn optional in the v3-catalog/trigger.config file --- references/v3-catalog/trigger.config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/references/v3-catalog/trigger.config.ts b/references/v3-catalog/trigger.config.ts index fbb90b1f4..649a349a6 100644 --- a/references/v3-catalog/trigger.config.ts +++ b/references/v3-catalog/trigger.config.ts @@ -10,6 +10,10 @@ export const resolveEnvVars: ResolveEnvironmentVariablesFunction = async ({ env, environment, }) => { + if (env.INFISICAL_CLIENT_ID === undefined || env.INFISICAL_CLIENT_SECRET === undefined) { + return; + } + const client = new InfisicalClient({ clientId: env.INFISICAL_CLIENT_ID, clientSecret: env.INFISICAL_CLIENT_SECRET, From 80b1d8c6b1d794695b79af955d91686e04af5631 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 24 May 2024 10:27:03 +0100 Subject: [PATCH 23/27] =?UTF-8?q?Task=20list=20page:=20if=20there=E2=80=99?= =?UTF-8?q?s=20a=20newer=20version=20then=20show=20the=20icon/type=20etc?= =?UTF-8?q?=20for=20that?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/webapp/app/presenters/v3/TaskListPresenter.server.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts b/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts index c6a26f970..9bcd006d5 100644 --- a/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts @@ -111,6 +111,14 @@ export class TaskListPresenter extends BasePresenter { acc.push(existingTask); } + //favour newer tasks + if (task.createdAt > existingTask.createdAt) { + existingTask.createdAt = task.createdAt; + existingTask.exportName = task.exportName; + existingTask.filePath = task.filePath; + existingTask.triggerSource = task.triggerSource; + } + existingTask.environments.push(displayableEnvironments(environment, userId)); //order the environments From 914a394d152c18f7cad4ac76519cfb65fc3eb1ef Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 24 May 2024 10:53:57 +0100 Subject: [PATCH 24/27] Fix for create/edit schedule form blank page when an error is thrown from the service --- .../route.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.schedules.new/route.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.schedules.new/route.tsx index deb17f3e1..35360e784 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.schedules.new/route.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.schedules.new/route.tsx @@ -34,7 +34,7 @@ import { TextLink } from "~/components/primitives/TextLink"; import { prisma } from "~/db.server"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; -import { redirectWithSuccessMessage } from "~/models/message.server"; +import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server"; import { EditableScheduleElements } from "~/presenters/v3/EditSchedulePresenter.server"; import { requireUserId } from "~/services/session.server"; import { cn } from "~/utils/cn"; @@ -92,9 +92,14 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { submission.value?.friendlyId === result.id ? "Schedule updated" : "Schedule created" ); } catch (error: any) { - submission.error.taskIdentifier = - error instanceof Error ? error.message : JSON.stringify(error); - return json(submission, { status: 400 }); + const errorMessage = `Failed: ${ + error instanceof Error ? error.message : JSON.stringify(error) + }`; + return redirectWithErrorMessage( + v3SchedulesPath({ slug: organizationSlug }, { slug: projectParam }), + request, + errorMessage + ); } }; From 2fa578007654e2d18df4e896f145c2f669657926 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 24 May 2024 12:35:33 +0100 Subject: [PATCH 25/27] At the root path, if you have no projects yet but you do have an org then go to the new project page --- .../OrganizationsPresenter.server.ts | 4 +--- apps/webapp/app/routes/_app._index/route.tsx | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/webapp/app/presenters/OrganizationsPresenter.server.ts b/apps/webapp/app/presenters/OrganizationsPresenter.server.ts index d4888ea3f..894b1774f 100644 --- a/apps/webapp/app/presenters/OrganizationsPresenter.server.ts +++ b/apps/webapp/app/presenters/OrganizationsPresenter.server.ts @@ -1,8 +1,8 @@ import { PrismaClient } from "@trigger.dev/database"; import { redirect } from "remix-typedjson"; import { prisma } from "~/db.server"; +import { redirectWithErrorMessage } from "~/models/message.server"; import { - clearCurrentProjectId, commitCurrentProjectSession, getCurrentProjectId, setCurrentProjectId, @@ -10,8 +10,6 @@ import { import { logger } from "~/services/logger.server"; import { newProjectPath } from "~/utils/pathBuilder"; import { ProjectPresenter } from "./ProjectPresenter.server"; -import { redirectWithErrorMessage } from "~/models/message.server"; -import { match } from "assert"; export class OrganizationsPresenter { #prismaClient: PrismaClient; diff --git a/apps/webapp/app/routes/_app._index/route.tsx b/apps/webapp/app/routes/_app._index/route.tsx index a3889f840..dc9a9a9dc 100644 --- a/apps/webapp/app/routes/_app._index/route.tsx +++ b/apps/webapp/app/routes/_app._index/route.tsx @@ -1,8 +1,9 @@ import { LoaderFunctionArgs, redirect } from "@remix-run/server-runtime"; +import { prisma } from "~/db.server"; import { getUsersInvites } from "~/models/member.server"; import { SelectBestProjectPresenter } from "~/presenters/SelectBestProjectPresenter.server"; import { requireUser } from "~/services/session.server"; -import { invitesPath, newOrganizationPath, projectPath } from "~/utils/pathBuilder"; +import { invitesPath, newOrganizationPath, newProjectPath, projectPath } from "~/utils/pathBuilder"; //this loader chooses the best project to redirect you to, ideally based on the cookie export const loader = async ({ request }: LoaderFunctionArgs) => { @@ -20,6 +21,24 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { //redirect them to the most appropriate project return redirect(projectPath(organization, project)); } catch (e) { + const organization = await prisma.organization.findFirst({ + where: { + members: { + some: { + userId: user.id, + }, + }, + deletedAt: null, + }, + orderBy: { + createdAt: "desc", + }, + }); + + if (organization) { + return redirect(newProjectPath(organization)); + } + //this should only happen if the user has no projects, and no invites return redirect(newOrganizationPath()); } From ea4d15536c026732b63c1a6dc123d4c35baa1c92 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 24 May 2024 14:55:09 +0100 Subject: [PATCH 26/27] chore: Update version for release (beta) (#1115) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 8 +++++++- integrations/airtable/CHANGELOG.md | 10 ++++++++++ integrations/airtable/package.json | 6 +++--- integrations/github/CHANGELOG.md | 10 ++++++++++ integrations/github/package.json | 6 +++--- integrations/linear/CHANGELOG.md | 10 ++++++++++ integrations/linear/package.json | 6 +++--- integrations/openai/CHANGELOG.md | 10 ++++++++++ integrations/openai/package.json | 6 +++--- integrations/plain/CHANGELOG.md | 10 ++++++++++ integrations/plain/package.json | 6 +++--- integrations/replicate/CHANGELOG.md | 10 ++++++++++ integrations/replicate/package.json | 6 +++--- integrations/resend/CHANGELOG.md | 10 ++++++++++ integrations/resend/package.json | 6 +++--- integrations/sendgrid/CHANGELOG.md | 10 ++++++++++ integrations/sendgrid/package.json | 6 +++--- integrations/shopify/CHANGELOG.md | 10 ++++++++++ integrations/shopify/package.json | 6 +++--- integrations/slack/CHANGELOG.md | 9 +++++++++ integrations/slack/package.json | 4 ++-- integrations/stripe/CHANGELOG.md | 10 ++++++++++ integrations/stripe/package.json | 6 +++--- integrations/supabase/CHANGELOG.md | 10 ++++++++++ integrations/supabase/package.json | 6 +++--- integrations/typeform/CHANGELOG.md | 10 ++++++++++ integrations/typeform/package.json | 6 +++--- packages/astro/CHANGELOG.md | 9 +++++++++ packages/astro/package.json | 4 ++-- packages/cli-v3/CHANGELOG.md | 16 ++++++++++++++++ packages/cli-v3/package.json | 4 ++-- packages/cli/CHANGELOG.md | 10 ++++++++++ packages/cli/package.json | 2 +- packages/core-apps/CHANGELOG.md | 2 ++ packages/core-apps/package.json | 2 +- packages/core-backend/CHANGELOG.md | 2 ++ packages/core-backend/package.json | 2 +- packages/core/CHANGELOG.md | 8 ++++++++ packages/core/package.json | 2 +- packages/eslint-plugin/CHANGELOG.md | 2 ++ packages/eslint-plugin/package.json | 2 +- packages/express/CHANGELOG.md | 9 +++++++++ packages/express/package.json | 6 +++--- packages/hono/CHANGELOG.md | 9 +++++++++ packages/hono/package.json | 4 ++-- packages/integration-kit/CHANGELOG.md | 9 +++++++++ packages/integration-kit/package.json | 2 +- packages/nestjs/CHANGELOG.md | 9 +++++++++ packages/nestjs/package.json | 6 +++--- packages/nextjs/CHANGELOG.md | 9 +++++++++ packages/nextjs/package.json | 4 ++-- packages/otlp-importer/CHANGELOG.md | 2 ++ packages/otlp-importer/package.json | 2 +- packages/react/CHANGELOG.md | 9 +++++++++ packages/react/package.json | 4 ++-- packages/remix/CHANGELOG.md | 9 +++++++++ packages/remix/package.json | 4 ++-- packages/sveltekit/CHANGELOG.md | 9 +++++++++ packages/sveltekit/package.json | 4 ++-- packages/testing/CHANGELOG.md | 10 ++++++++++ packages/testing/package.json | 2 +- packages/trigger-sdk/CHANGELOG.md | 13 +++++++++++++ packages/trigger-sdk/package.json | 6 +++--- packages/yalt/CHANGELOG.md | 2 ++ packages/yalt/package.json | 2 +- 65 files changed, 354 insertions(+), 71 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index dbb68a0c6..35cc4cb3b 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -55,6 +55,7 @@ "clever-apes-collect", "clever-carrots-travel", "clever-donkeys-hunt", + "cool-comics-burn", "cool-glasses-bake", "cuddly-feet-approve", "dry-walls-check", @@ -63,9 +64,11 @@ "eleven-paws-join", "famous-boats-tease", "few-students-share", + "five-toes-destroy", "funny-swans-destroy", "gorgeous-gorillas-compete", "green-bags-wink", + "hot-fishes-retire", "khaki-apricots-design", "khaki-poems-lay", "late-icons-lie", @@ -109,7 +112,9 @@ "smart-olives-eat", "spicy-lamps-smoke", "strange-ghosts-matter", + "strange-sheep-pull", "strong-lemons-add", + "strong-owls-know", "stupid-bulldogs-applaud", "sweet-lizards-press", "swift-dragons-peel", @@ -126,6 +131,7 @@ "tiny-elephants-scream", "tricky-bulldogs-heal", "tricky-ladybugs-unite", - "two-pumas-wait" + "two-pumas-wait", + "warm-planes-taste" ] } diff --git a/integrations/airtable/CHANGELOG.md b/integrations/airtable/CHANGELOG.md index 466ac9188..c82158e86 100644 --- a/integrations/airtable/CHANGELOG.md +++ b/integrations/airtable/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/airtable +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + - @trigger.dev/integration-kit@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/integrations/airtable/package.json b/integrations/airtable/package.json index 9ec82eb2b..6c5e3db7e 100644 --- a/integrations/airtable/package.json +++ b/integrations/airtable/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/airtable", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", - "@trigger.dev/sdk": "workspace:^3.0.0-beta.33", + "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.34", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "airtable": "^0.12.1", "zod": "3.22.3" }, diff --git a/integrations/github/CHANGELOG.md b/integrations/github/CHANGELOG.md index eb4354c24..034570f2a 100644 --- a/integrations/github/CHANGELOG.md +++ b/integrations/github/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/github +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + - @trigger.dev/integration-kit@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/integrations/github/package.json b/integrations/github/package.json index f2b38569d..dba80e836 100644 --- a/integrations/github/package.json +++ b/integrations/github/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/github", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", - "@trigger.dev/sdk": "workspace:^3.0.0-beta.33", + "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.34", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "zod": "3.22.3" }, "engines": { diff --git a/integrations/linear/CHANGELOG.md b/integrations/linear/CHANGELOG.md index db5c488e6..6b9914172 100644 --- a/integrations/linear/CHANGELOG.md +++ b/integrations/linear/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/linear +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + - @trigger.dev/integration-kit@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/integrations/linear/package.json b/integrations/linear/package.json index d4a8d04b9..bbe72fece 100644 --- a/integrations/linear/package.json +++ b/integrations/linear/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/linear", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", - "@trigger.dev/sdk": "workspace:^3.0.0-beta.33", + "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.34", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "zod": "3.22.3" }, "engines": { diff --git a/integrations/openai/CHANGELOG.md b/integrations/openai/CHANGELOG.md index c2c7b97a7..1e06147bc 100644 --- a/integrations/openai/CHANGELOG.md +++ b/integrations/openai/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/slack +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + - @trigger.dev/integration-kit@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/integrations/openai/package.json b/integrations/openai/package.json index ce448da0f..69103f097 100644 --- a/integrations/openai/package.json +++ b/integrations/openai/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/openai", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", - "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.33" + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", + "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.34" }, "engines": { "node": ">=18.0.0" diff --git a/integrations/plain/CHANGELOG.md b/integrations/plain/CHANGELOG.md index 9194e83d1..50056929c 100644 --- a/integrations/plain/CHANGELOG.md +++ b/integrations/plain/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/plain +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + - @trigger.dev/integration-kit@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/integrations/plain/package.json b/integrations/plain/package.json index c3c51b5bb..f2f3332ab 100644 --- a/integrations/plain/package.json +++ b/integrations/plain/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/plain", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", - "@trigger.dev/sdk": "workspace:^3.0.0-beta.33", + "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.34", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "@team-plain/typescript-sdk": "^2.7.0" }, "engines": { diff --git a/integrations/replicate/CHANGELOG.md b/integrations/replicate/CHANGELOG.md index 7c641a12f..8166522a6 100644 --- a/integrations/replicate/CHANGELOG.md +++ b/integrations/replicate/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/replicate +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + - @trigger.dev/integration-kit@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/integrations/replicate/package.json b/integrations/replicate/package.json index 68ba6e34f..aeedc0b4e 100644 --- a/integrations/replicate/package.json +++ b/integrations/replicate/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/replicate", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", - "@trigger.dev/sdk": "workspace:^3.0.0-beta.33", + "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.34", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "replicate": "^0.18.1", "zod": "3.22.3" }, diff --git a/integrations/resend/CHANGELOG.md b/integrations/resend/CHANGELOG.md index ef2e919f7..d01bce5cb 100644 --- a/integrations/resend/CHANGELOG.md +++ b/integrations/resend/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/resend +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + - @trigger.dev/integration-kit@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/integrations/resend/package.json b/integrations/resend/package.json index 5e1d0fe70..9aa151fb3 100644 --- a/integrations/resend/package.json +++ b/integrations/resend/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/resend", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", - "@trigger.dev/sdk": "workspace:^3.0.0-beta.33", + "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.34", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "resend": "^2.1.0" }, "engines": { diff --git a/integrations/sendgrid/CHANGELOG.md b/integrations/sendgrid/CHANGELOG.md index 2170ce650..462aeea61 100644 --- a/integrations/sendgrid/CHANGELOG.md +++ b/integrations/sendgrid/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/sendgrid +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + - @trigger.dev/integration-kit@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/integrations/sendgrid/package.json b/integrations/sendgrid/package.json index d8cdb02dd..794e90274 100644 --- a/integrations/sendgrid/package.json +++ b/integrations/sendgrid/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/sendgrid", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", - "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.33" + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", + "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.34" }, "engines": { "node": ">=16.8.0" diff --git a/integrations/shopify/CHANGELOG.md b/integrations/shopify/CHANGELOG.md index cf04bfd9e..e47957ff7 100644 --- a/integrations/shopify/CHANGELOG.md +++ b/integrations/shopify/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/shopify +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + - @trigger.dev/integration-kit@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/integrations/shopify/package.json b/integrations/shopify/package.json index b857d4e09..183622524 100644 --- a/integrations/shopify/package.json +++ b/integrations/shopify/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/shopify", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", - "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.33", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", + "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.34", "zod": "3.22.3" }, "engines": { diff --git a/integrations/slack/CHANGELOG.md b/integrations/slack/CHANGELOG.md index c7f844eed..4499de99f 100644 --- a/integrations/slack/CHANGELOG.md +++ b/integrations/slack/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/slack +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/integrations/slack/package.json b/integrations/slack/package.json index 395c4e46d..2a091689b 100644 --- a/integrations/slack/package.json +++ b/integrations/slack/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/slack", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "zod": "3.22.3" }, "engines": { diff --git a/integrations/stripe/CHANGELOG.md b/integrations/stripe/CHANGELOG.md index d28826d5b..546775700 100644 --- a/integrations/stripe/CHANGELOG.md +++ b/integrations/stripe/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/stripe +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + - @trigger.dev/integration-kit@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/integrations/stripe/package.json b/integrations/stripe/package.json index 2031bc5d8..c2a001f52 100644 --- a/integrations/stripe/package.json +++ b/integrations/stripe/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/stripe", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", - "@trigger.dev/sdk": "workspace:^3.0.0-beta.33", + "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.34", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "stripe": "^12.14.0", "zod": "3.22.3" }, diff --git a/integrations/supabase/CHANGELOG.md b/integrations/supabase/CHANGELOG.md index 418cff6ac..8a2d265a4 100644 --- a/integrations/supabase/CHANGELOG.md +++ b/integrations/supabase/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/supabase +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + - @trigger.dev/integration-kit@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/integrations/supabase/package.json b/integrations/supabase/package.json index 0d7ca33a5..f194ab1f5 100644 --- a/integrations/supabase/package.json +++ b/integrations/supabase/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/supabase", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", - "@trigger.dev/sdk": "workspace:^3.0.0-beta.33", + "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.34", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "supabase-management-js": "^1.0.0", "zod": "3.22.3" }, diff --git a/integrations/typeform/CHANGELOG.md b/integrations/typeform/CHANGELOG.md index dcdbb0da9..b6b77d7a5 100644 --- a/integrations/typeform/CHANGELOG.md +++ b/integrations/typeform/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/typeform +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + - @trigger.dev/integration-kit@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/integrations/typeform/package.json b/integrations/typeform/package.json index c247f1cba..954d507d4 100644 --- a/integrations/typeform/package.json +++ b/integrations/typeform/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/typeform", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", - "@trigger.dev/sdk": "workspace:^3.0.0-beta.33", + "@trigger.dev/integration-kit": "workspace:^3.0.0-beta.34", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "@typeform/api-client": "^1.8.0", "zod": "3.22.3" }, diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md index f17bcc8af..46417c4af 100644 --- a/packages/astro/CHANGELOG.md +++ b/packages/astro/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/astro +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/astro/package.json b/packages/astro/package.json index bcd533862..41d4980e3 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,7 +1,7 @@ { "name": "@trigger.dev/astro", "description": "An Astro-native integration for Trigger.dev background jobs platform", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33" + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34" }, "devDependencies": { "astro": "^3.0.12", diff --git a/packages/cli-v3/CHANGELOG.md b/packages/cli-v3/CHANGELOG.md index 4eaa4f5a9..139baa5cf 100644 --- a/packages/cli-v3/CHANGELOG.md +++ b/packages/cli-v3/CHANGELOG.md @@ -1,5 +1,21 @@ # trigger.dev +## 3.0.0-beta.34 + +### Patch Changes + +- 5a6e79e0c: Fixing missing logs when importing client @opentelemetry/api +- 3a1b0c486: v3: Environment variable management API and SDK, along with resolveEnvVars CLI hook +- a86f36cef: Fix TypeScript inclusion in tsconfig.json for `cli-v3 init` +- c092c0f9d: v3: Prevent legacy-peer-deps=true from breaking deploys + + When a global `.npmrc` file includes `legacy-peer-deps=true`, deploys would fail on the `npm ci` step because the package-lock.json wouldn't match the `package.json` file. This is because inside the image build, the `.npmrc` file would not be picked up and so `legacy-peer-deps` would end up being false (which is the default). This change forces the `package-lock.json` file to be created using `legacy-peer-deps=false` + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/core@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index 183eefbb0..eb0277a5f 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -1,6 +1,6 @@ { "name": "trigger.dev", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "description": "A Command-Line Interface for Trigger.dev (v3) projects", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -86,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.33", + "@trigger.dev/core": "workspace:3.0.0-beta.34", "@types/degit": "^2.8.3", "chalk": "^5.2.0", "chokidar": "^3.5.3", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 993f23975..71f421e5c 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,15 @@ # create-trigger +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/core@3.0.0-beta.34 + - @trigger.dev/yalt@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index b15e57dfb..bb07ec109 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/cli", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "description": "The Trigger.dev CLI", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/core-apps/CHANGELOG.md b/packages/core-apps/CHANGELOG.md index ea36d0845..0aebc6f53 100644 --- a/packages/core-apps/CHANGELOG.md +++ b/packages/core-apps/CHANGELOG.md @@ -1,5 +1,7 @@ # @trigger.dev/core-apps +## 3.0.0-beta.34 + ## 3.0.0-beta.33 ## 3.0.0-beta.32 diff --git a/packages/core-apps/package.json b/packages/core-apps/package.json index a8a70b7be..20120c2d6 100644 --- a/packages/core-apps/package.json +++ b/packages/core-apps/package.json @@ -1,7 +1,7 @@ { "name": "@trigger.dev/core-apps", "description": "Backend core code used across apps", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "private": true, "license": "MIT", "main": "./dist/index.js", diff --git a/packages/core-backend/CHANGELOG.md b/packages/core-backend/CHANGELOG.md index 0408e0266..7e800bdfb 100644 --- a/packages/core-backend/CHANGELOG.md +++ b/packages/core-backend/CHANGELOG.md @@ -1,5 +1,7 @@ # @trigger.dev/core-backend +## 3.0.0-beta.34 + ## 3.0.0-beta.33 ## 3.0.0-beta.32 diff --git a/packages/core-backend/package.json b/packages/core-backend/package.json index bb1135202..16716ea1e 100644 --- a/packages/core-backend/package.json +++ b/packages/core-backend/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/core-backend", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "description": "Core code used across `@trigger.dev/sdk` and Trigger.dev server", "license": "MIT", "main": "./dist/index.js", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 0b87d195a..414aac8fe 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,13 @@ # internal-platform +## 3.0.0-beta.34 + +### Patch Changes + +- 3a1b0c486: v3: Environment variable management API and SDK, along with resolveEnvVars CLI hook +- 3f8b6d8fc: v2: Better handle recovering from platform communication errors by auto-yielding back to the platform in case of temporary API failures +- 1281d40e4: When a v2 run hits the rate limit, reschedule with the reset date + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 7b6bca42d..0b49d753f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/core", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "description": "Core code used across the Trigger.dev SDK and platform", "license": "MIT", "main": "./dist/index.js", diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index a4a327cc7..3caf96f86 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,5 +1,7 @@ # @trigger.dev/eslint-plugin +## 3.0.0-beta.34 + ## 3.0.0-beta.33 ## 3.0.0-beta.32 diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index e30e5e922..239aa66b0 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/eslint-plugin", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "description": "ESLint plugin with trigger.dev best practices", "keywords": [ "eslint", diff --git a/packages/express/CHANGELOG.md b/packages/express/CHANGELOG.md index 516669b91..cb47e8ca3 100644 --- a/packages/express/CHANGELOG.md +++ b/packages/express/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/express +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/express/package.json b/packages/express/package.json index 703a71fe6..4c8c7c458 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/express", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "@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.33" + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34" }, "dependencies": { "debug": "^4.3.4", diff --git a/packages/hono/CHANGELOG.md b/packages/hono/CHANGELOG.md index a74cf31f3..bf50f5bda 100644 --- a/packages/hono/CHANGELOG.md +++ b/packages/hono/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/hono +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/hono/package.json b/packages/hono/package.json index 9f061f611..c7edc00f9 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/hono", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33" + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34" }, "devDependencies": { "@trigger.dev/tsconfig": "workspace:*", diff --git a/packages/integration-kit/CHANGELOG.md b/packages/integration-kit/CHANGELOG.md index 0980e0200..5b4866e81 100644 --- a/packages/integration-kit/CHANGELOG.md +++ b/packages/integration-kit/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/integration-kit +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/core@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/integration-kit/package.json b/packages/integration-kit/package.json index eebf73b62..b2d818286 100644 --- a/packages/integration-kit/package.json +++ b/packages/integration-kit/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/integration-kit", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "description": "Trigger.dev Integration Kit has helpers to make creating integrations easier", "license": "MIT", "main": "./dist/index.js", diff --git a/packages/nestjs/CHANGELOG.md b/packages/nestjs/CHANGELOG.md index 71d303e3f..dab7c787a 100644 --- a/packages/nestjs/CHANGELOG.md +++ b/packages/nestjs/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/nestjs +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 4d8e8b0f9..fd9e58bc9 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/nestjs", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "@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.33" + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34" }, "dependencies": { "@nestjs/common": "^10.2.4", diff --git a/packages/nextjs/CHANGELOG.md b/packages/nextjs/CHANGELOG.md index ae68c5e77..d31470dd1 100644 --- a/packages/nextjs/CHANGELOG.md +++ b/packages/nextjs/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/nextjs +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index f6ea6ffbd..2132013e8 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/nextjs", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "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.33", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "next": ">=12.0.0" }, "dependencies": { diff --git a/packages/otlp-importer/CHANGELOG.md b/packages/otlp-importer/CHANGELOG.md index 9e1063f9f..07db6c1c2 100644 --- a/packages/otlp-importer/CHANGELOG.md +++ b/packages/otlp-importer/CHANGELOG.md @@ -1,5 +1,7 @@ # @trigger.dev/otlp-importer +## 3.0.0-beta.34 + ## 3.0.0-beta.33 ## 3.0.0-beta.32 diff --git a/packages/otlp-importer/package.json b/packages/otlp-importer/package.json index b10a7b597..9c1935411 100644 --- a/packages/otlp-importer/package.json +++ b/packages/otlp-importer/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/otlp-importer", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "description": "OpenTelemetry OTLP Importer for Node.js written in TypeScript", "license": "MIT", "main": "./dist/index.js", diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 4ce4823c1..9e91f4a01 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/react +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/core@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/react/package.json b/packages/react/package.json index 0a6e9e411..234fda2a0 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/react", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "description": "Trigger.dev React SDK", "license": "MIT", "types": "dist/index.d.ts", @@ -27,7 +27,7 @@ }, "dependencies": { "@tanstack/react-query": "5.0.0-beta.2", - "@trigger.dev/core": "workspace:^3.0.0-beta.33", + "@trigger.dev/core": "workspace:^3.0.0-beta.34", "debug": "^4.3.4", "zod": "3.22.3" }, diff --git a/packages/remix/CHANGELOG.md b/packages/remix/CHANGELOG.md index 6e134e0ec..31502eb87 100644 --- a/packages/remix/CHANGELOG.md +++ b/packages/remix/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/remix +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/remix/package.json b/packages/remix/package.json index a4a9c5ce5..5ce37c9ee 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/remix", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "description": "Trigger.dev Remix integration", "license": "MIT", "main": "./dist/index.js", @@ -39,7 +39,7 @@ "build:tsup": "tsup" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^3.0.0-beta.33", + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34", "@remix-run/server-runtime": ">1.19.0" }, "dependencies": { diff --git a/packages/sveltekit/CHANGELOG.md b/packages/sveltekit/CHANGELOG.md index e5c9dc29c..be0964d79 100644 --- a/packages/sveltekit/CHANGELOG.md +++ b/packages/sveltekit/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/sveltekit +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index d9f33f963..f1b51c024 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/sveltekit", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "description": "Trigger.dev svelteKit integration", "license": "MIT", "main": "./dist/index.js", @@ -39,7 +39,7 @@ "build:tsup": "tsup" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^3.0.0-beta.33" + "@trigger.dev/sdk": "workspace:^3.0.0-beta.34" }, "dependencies": { "debug": "^4.3.4" diff --git a/packages/testing/CHANGELOG.md b/packages/testing/CHANGELOG.md index ec9d0cdd6..e1dd493cf 100644 --- a/packages/testing/CHANGELOG.md +++ b/packages/testing/CHANGELOG.md @@ -1,5 +1,15 @@ # @trigger.dev/testing +## 3.0.0-beta.34 + +### Patch Changes + +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/sdk@3.0.0-beta.34 + - @trigger.dev/core@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/testing/package.json b/packages/testing/package.json index dc799a2dc..6554dffe5 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -1,7 +1,7 @@ { "name": "@trigger.dev/testing", "description": "A collection of useful tools to write tests for Trigger.dev.", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "license": "MIT", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/trigger-sdk/CHANGELOG.md b/packages/trigger-sdk/CHANGELOG.md index 835464115..73f941c78 100644 --- a/packages/trigger-sdk/CHANGELOG.md +++ b/packages/trigger-sdk/CHANGELOG.md @@ -1,5 +1,18 @@ # @trigger.dev/sdk +## 3.0.0-beta.34 + +### Patch Changes + +- 3a1b0c486: v3: Environment variable management API and SDK, along with resolveEnvVars CLI hook +- 3f8b6d8fc: v2: Better handle recovering from platform communication errors by auto-yielding back to the platform in case of temporary API failures +- 1281d40e4: When a v2 run hits the rate limit, reschedule with the reset date +- Updated dependencies [3a1b0c486] +- Updated dependencies [3f8b6d8fc] +- Updated dependencies [1281d40e4] + - @trigger.dev/core@3.0.0-beta.34 + - @trigger.dev/core-backend@3.0.0-beta.34 + ## 3.0.0-beta.33 ### Patch Changes diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index f14f90f80..77506c8d2 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/sdk", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "description": "trigger.dev Node.JS SDK", "license": "MIT", "main": "./dist/index.js", @@ -48,8 +48,8 @@ "@opentelemetry/api": "^1.8.0", "@opentelemetry/api-logs": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@trigger.dev/core": "workspace:3.0.0-beta.33", - "@trigger.dev/core-backend": "workspace:3.0.0-beta.33", + "@trigger.dev/core": "workspace:3.0.0-beta.34", + "@trigger.dev/core-backend": "workspace:3.0.0-beta.34", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", diff --git a/packages/yalt/CHANGELOG.md b/packages/yalt/CHANGELOG.md index 23e0742a6..fb3b31fb4 100644 --- a/packages/yalt/CHANGELOG.md +++ b/packages/yalt/CHANGELOG.md @@ -1,5 +1,7 @@ # @trigger.dev/yalt +## 3.0.0-beta.34 + ## 3.0.0-beta.33 ## 3.0.0-beta.32 diff --git a/packages/yalt/package.json b/packages/yalt/package.json index a59cdfe91..29a4cb500 100644 --- a/packages/yalt/package.json +++ b/packages/yalt/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/yalt", - "version": "3.0.0-beta.33", + "version": "3.0.0-beta.34", "description": "yalt.dev client library", "license": "MIT", "main": "./dist/index.js", From cec20b96fd8e6f5d8a3a3eef0ca8370419167606 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 24 May 2024 15:32:04 +0100 Subject: [PATCH 27/27] Release 3.0.0-beta.34 --- pnpm-lock.yaml | 120 +++++++++++++++++++------------------------------ 1 file changed, 45 insertions(+), 75 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 825087364..f99275a30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -860,10 +860,10 @@ importers: integrations/airtable: dependencies: '@trigger.dev/integration-kit': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/integration-kit '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/trigger-sdk airtable: specifier: ^0.12.1 @@ -903,10 +903,10 @@ importers: specifier: ^12.0.10 version: 12.0.10 '@trigger.dev/integration-kit': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/integration-kit '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/trigger-sdk octokit: specifier: ^3.1.2 @@ -946,10 +946,10 @@ importers: specifier: ^8.0.0 version: 8.0.0 '@trigger.dev/integration-kit': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/integration-kit '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/trigger-sdk zod: specifier: 3.22.3 @@ -977,10 +977,10 @@ importers: integrations/openai: dependencies: '@trigger.dev/integration-kit': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/integration-kit '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/trigger-sdk openai: specifier: ^4.16.1 @@ -1020,10 +1020,10 @@ importers: specifier: ^2.7.0 version: 2.7.0 '@trigger.dev/integration-kit': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/integration-kit '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/trigger-sdk devDependencies: '@trigger.dev/tsconfig': @@ -1048,10 +1048,10 @@ importers: integrations/replicate: dependencies: '@trigger.dev/integration-kit': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/integration-kit '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/trigger-sdk replicate: specifier: ^0.18.1 @@ -1082,10 +1082,10 @@ importers: integrations/resend: dependencies: '@trigger.dev/integration-kit': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/integration-kit '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/trigger-sdk resend: specifier: ^2.1.0 @@ -1116,10 +1116,10 @@ importers: specifier: ^7.7.0 version: 7.7.0 '@trigger.dev/integration-kit': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/integration-kit '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/trigger-sdk devDependencies: '@trigger.dev/tsconfig': @@ -1147,10 +1147,10 @@ importers: specifier: ^8.0.2 version: 8.0.2 '@trigger.dev/integration-kit': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/integration-kit '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/trigger-sdk zod: specifier: 3.22.3 @@ -1181,7 +1181,7 @@ importers: specifier: ^6.8.1 version: 6.8.1 '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/trigger-sdk zod: specifier: 3.22.3 @@ -1209,10 +1209,10 @@ importers: integrations/stripe: dependencies: '@trigger.dev/integration-kit': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/integration-kit '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/trigger-sdk stripe: specifier: ^12.14.0 @@ -1246,10 +1246,10 @@ importers: specifier: ^2.26.0 version: 2.31.0 '@trigger.dev/integration-kit': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/integration-kit '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/trigger-sdk supabase-management-js: specifier: ^1.0.0 @@ -1280,10 +1280,10 @@ importers: integrations/typeform: dependencies: '@trigger.dev/integration-kit': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/integration-kit '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../../packages/trigger-sdk '@typeform/api-client': specifier: ^1.8.0 @@ -1311,7 +1311,7 @@ importers: packages/astro: dependencies: '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../trigger-sdk debug: specifier: ^4.3.4 @@ -1529,7 +1529,7 @@ importers: specifier: ^1.22.0 version: 1.22.0 '@trigger.dev/core': - specifier: workspace:3.0.0-beta.33 + specifier: workspace:3.0.0-beta.34 version: link:../core '@types/degit': specifier: ^2.8.3 @@ -1944,7 +1944,7 @@ importers: version: 4.18.2 devDependencies: '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../trigger-sdk '@trigger.dev/tsconfig': specifier: workspace:* @@ -2039,7 +2039,7 @@ importers: version: 4.3.4(supports-color@8.1.1) devDependencies: '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../trigger-sdk '@trigger.dev/tsconfig': specifier: workspace:* @@ -2072,7 +2072,7 @@ importers: packages/nextjs: dependencies: '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../trigger-sdk debug: specifier: ^4.3.4 @@ -2158,7 +2158,7 @@ importers: specifier: 5.0.0-beta.2 version: 5.0.0-beta.2(react@18.2.0) '@trigger.dev/core': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../core debug: specifier: ^4.3.4 @@ -2201,7 +2201,7 @@ importers: packages/remix: dependencies: '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../trigger-sdk debug: specifier: ^4.3.4 @@ -2238,7 +2238,7 @@ importers: packages/sveltekit: dependencies: '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.33 + specifier: workspace:^3.0.0-beta.34 version: link:../trigger-sdk debug: specifier: ^4.3.4 @@ -2312,10 +2312,10 @@ importers: specifier: ^1.22.0 version: 1.22.0 '@trigger.dev/core': - specifier: workspace:3.0.0-beta.33 + specifier: workspace:3.0.0-beta.34 version: link:../core '@trigger.dev/core-backend': - specifier: workspace:3.0.0-beta.33 + specifier: workspace:3.0.0-beta.34 version: link:../core-backend chalk: specifier: ^5.2.0 @@ -13730,7 +13730,7 @@ packages: eslint: 8.45.0 eslint-import-resolver-node: 0.3.7 eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.27.5)(eslint@8.45.0) - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.6)(eslint@8.45.0) + eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-typescript@3.5.5)(eslint@8.45.0) eslint-plugin-jest: 26.9.0(@typescript-eslint/eslint-plugin@5.59.6)(eslint@8.45.0)(typescript@4.9.5) eslint-plugin-jest-dom: 4.0.3(eslint@8.45.0) eslint-plugin-jsx-a11y: 6.7.1(eslint@8.45.0) @@ -16424,7 +16424,6 @@ packages: typescript: 4.9.5 transitivePeerDependencies: - supports-color - dev: true /@typescript-eslint/parser@5.59.6(eslint@8.45.0)(typescript@5.1.6): resolution: {integrity: sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==} @@ -16444,6 +16443,7 @@ packages: typescript: 5.1.6 transitivePeerDependencies: - supports-color + dev: false /@typescript-eslint/parser@5.59.6(eslint@8.45.0)(typescript@5.2.2): resolution: {integrity: sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==} @@ -16609,7 +16609,6 @@ packages: typescript: 4.9.5 transitivePeerDependencies: - supports-color - dev: true /@typescript-eslint/typescript-estree@5.59.6(typescript@5.0.4): resolution: {integrity: sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==} @@ -16651,6 +16650,7 @@ packages: typescript: 5.1.6 transitivePeerDependencies: - supports-color + dev: false /@typescript-eslint/typescript-estree@5.59.6(typescript@5.2.2): resolution: {integrity: sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==} @@ -20991,7 +20991,7 @@ packages: eslint: 8.45.0 eslint-import-resolver-node: 0.3.7 eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.27.5)(eslint@8.45.0) - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-typescript@3.5.5)(eslint@8.45.0) + eslint-plugin-import: 2.27.5(eslint@8.45.0) eslint-plugin-jsx-a11y: 6.7.1(eslint@8.45.0) eslint-plugin-react: 7.32.2(eslint@8.45.0) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.45.0) @@ -21281,7 +21281,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.59.6(eslint@8.45.0)(typescript@5.1.6) + '@typescript-eslint/parser': 5.59.6(eslint@8.45.0)(typescript@4.9.5) debug: 3.2.7(supports-color@5.5.0) eslint: 8.45.0 eslint-import-resolver-node: 0.3.7 @@ -21289,35 +21289,6 @@ packages: transitivePeerDependencies: - supports-color - /eslint-module-utils@2.7.4(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint@8.45.0): - resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - dependencies: - '@typescript-eslint/parser': 5.59.6(eslint@8.45.0)(typescript@4.9.5) - debug: 3.2.7(supports-color@5.5.0) - eslint: 8.45.0 - eslint-import-resolver-node: 0.3.7 - transitivePeerDependencies: - - supports-color - dev: true - /eslint-plugin-es@3.0.1(eslint@8.31.0): resolution: {integrity: sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==} engines: {node: '>=8.10.0'} @@ -21427,7 +21398,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.59.6(eslint@8.45.0)(typescript@5.1.6) + '@typescript-eslint/parser': 5.59.6(eslint@8.45.0)(typescript@4.9.5) array-includes: 3.1.6 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 @@ -21449,7 +21420,7 @@ packages: - eslint-import-resolver-webpack - supports-color - /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.6)(eslint@8.45.0): + /eslint-plugin-import@2.27.5(eslint@8.45.0): resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} engines: {node: '>=4'} peerDependencies: @@ -21459,7 +21430,6 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.59.6(eslint@8.45.0)(typescript@4.9.5) array-includes: 3.1.6 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 @@ -21467,7 +21437,7 @@ packages: doctrine: 2.1.0 eslint: 8.45.0 eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint@8.45.0) + eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.45.0) has: 1.0.3 is-core-module: 2.13.0 is-glob: 4.0.3 @@ -21480,7 +21450,7 @@ packages: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - dev: true + dev: false /eslint-plugin-jest-dom@4.0.3(eslint@8.31.0): resolution: {integrity: sha512-9j+n8uj0+V0tmsoS7bYC7fLhQmIvjRqRYEcbDSi+TKPsTThLLXCyj5swMSSf/hTleeMktACnn+HFqXBr5gbcbA==} @@ -34064,7 +34034,6 @@ packages: dependencies: tslib: 1.14.1 typescript: 4.9.5 - dev: true /tsutils@3.21.0(typescript@5.0.4): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} @@ -34084,6 +34053,7 @@ packages: dependencies: tslib: 1.14.1 typescript: 5.1.6 + dev: false /tsutils@3.21.0(typescript@5.2.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}