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/.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/.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/.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/.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/.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/.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/.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/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/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/components/code/CodeBlock.tsx b/apps/webapp/app/components/code/CodeBlock.tsx index a1a496ad5..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, { defaultProps } 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; @@ -238,7 +247,7 @@ export const CodeBlock = forwardRef( )} {shouldHighlight ? ( - + {({ className: inheritedClassName, style: inheritedStyle, @@ -283,7 +292,7 @@ export const CodeBlock = forwardRef( return (
( const tokenProps = getTokenProps({ token, key }); return ( ({ +const statuses = filterableTaskRunStatuses.map((status) => ({ title: runStatusTitle(status), value: status, })); diff --git a/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx b/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx index 557b62e08..de0fa038b 100644 --- a/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx +++ b/apps/webapp/app/components/runs/v3/TaskRunAttemptStatus.tsx @@ -7,7 +7,7 @@ import { XCircleIcon, } from "@heroicons/react/20/solid"; import type { TaskRunAttemptStatus as TaskRunAttemptStatusType } from "@trigger.dev/database"; -import { TaskRunAttemptStatus } 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"; @@ -17,7 +17,7 @@ export const allTaskRunAttemptStatuses = Object.values( TaskRunAttemptStatus ) as 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..3e7ef6db6 100644 --- a/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx +++ b/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx @@ -25,9 +25,24 @@ export const allTaskRunStatuses = [ "CANCELED", "COMPLETED_WITH_ERRORS", "CRASHED", + "PAUSED", "INTERRUPTED", "SYSTEM_FAILURE", -] as TaskRunStatus[]; +] 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", 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/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/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/models/api-key.server.ts b/apps/webapp/app/models/api-key.server.ts index a54fdae9a..0389782d8 100644 --- a/apps/webapp/app/models/api-key.server.ts +++ b/apps/webapp/app/models/api-key.server.ts @@ -1,6 +1,7 @@ -import { RuntimeEnvironmentType, type RuntimeEnvironment } from "@trigger.dev/database"; +import type { RuntimeEnvironment } from "@trigger.dev/database"; import { prisma } from "~/db.server"; import { customAlphabet } from "nanoid"; +import { RuntimeEnvironmentType } from "~/database-types"; const apiKeyId = customAlphabet( "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 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/models/taskRun.server.ts b/apps/webapp/app/models/taskRun.server.ts index 0e2eccbf1..7922c1aed 100644 --- a/apps/webapp/app/models/taskRun.server.ts +++ b/apps/webapp/app/models/taskRun.server.ts @@ -1,17 +1,20 @@ -import { - TaskRunError, +import type { TaskRunExecutionResult, TaskRunFailedExecutionResult, TaskRunSuccessfulExecutionResult, } from "@trigger.dev/core/v3"; -import { - BatchTaskRunItemStatus, +import { TaskRunError } from "@trigger.dev/core/v3"; + +import type { TaskRun, TaskRunAttempt, - TaskRunAttemptStatus, - TaskRunStatus, + 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 SUCCESSFUL_STATUSES = [TaskRunStatus.COMPLETED_SUCCESSFULLY]; @@ -104,7 +107,9 @@ export function executionResultForTaskRun( } } -export function batchTaskRunItemStatusForRunStatus(status: TaskRunStatus): BatchTaskRunItemStatus { +export function batchTaskRunItemStatusForRunStatus( + status: TaskRunStatusType +): BatchTaskRunItemStatusType { switch (status) { case TaskRunStatus.COMPLETED_SUCCESSFULLY: return BatchTaskRunItemStatus.COMPLETED; @@ -113,7 +118,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/OrganizationsPresenter.server.ts b/apps/webapp/app/presenters/OrganizationsPresenter.server.ts index becd33013..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; @@ -64,6 +62,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/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/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/presenters/v3/TaskListPresenter.server.ts b/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts index c0a8935ba..9bcd006d5 100644 --- a/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts @@ -1,18 +1,19 @@ -import { - Prisma, +import type { RuntimeEnvironmentType, - TaskRunStatus, 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 { 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"; +import { TaskRunStatus } from "~/database-types"; export type Task = { slug: string; @@ -110,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 @@ -150,7 +159,7 @@ export class TaskListPresenter extends BasePresenter { const activity = await this._replica.$queryRaw< { taskIdentifier: string; - status: TaskRunStatus; + status: TaskRunStatusType; day: Date; count: BigInt; }[] @@ -193,7 +202,7 @@ export class TaskListPresenter extends BasePresenter { existingTask.push({ day: day.toISOString(), [TaskRunStatus.COMPLETED_SUCCESSFULLY]: 0, - } as { day: string } & Record); + } as { day: string } & Record); } acc[a.taskIdentifier] = existingTask; @@ -214,7 +223,7 @@ export class TaskListPresenter extends BasePresenter { day[a.status] = Number(a.count); return acc; - }, {} as Record)[]>); + }, {} as Record)[]>); } async #getRunningStats(tasks: string[], projectId: string) { @@ -225,7 +234,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/_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()); } 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/_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 && ( - + & { 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.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/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/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: "/" }); }; 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 77% 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 b95b058a6..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,76 +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 user = await prisma.user.findUnique({ - where: { - id: authenticationResult.userId, - }, - }); - - if (!user) { - return json({ error: "Invalid or Missing Access Token" }, { status: 401 }); - } - - const project = user.admin - ? await prisma.project.findFirst({ - where: { - externalRef: validatedParams.projectRef, - }, - include: { - organization: true, - }, - }) - : 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/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 + ); } }; 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/routes/resources.orgs.$organizationSlug.v3-access.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.v3-access.tsx new file mode 100644 index 000000000..a16f691dc --- /dev/null +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.v3-access.tsx @@ -0,0 +1,210 @@ +import { Form } from "@remix-run/react"; +import { ActionFunctionArgs } from "@remix-run/server-runtime"; +import { PlainClient } from "@team-plain/typescript-sdk"; +import { z } from "zod"; +import { MainCenteredContainer } from "~/components/layout/AppLayout"; +import { Button, LinkButton } from "~/components/primitives/Buttons"; +import { Paragraph } from "~/components/primitives/Paragraph"; +import { TextLink } from "~/components/primitives/TextLink"; +import { prisma } from "~/db.server"; +import { env } from "~/env.server"; +import { useUser } from "~/hooks/useUser"; +import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server"; +import { logger } from "~/services/logger.server"; +import { requireUser } from "~/services/session.server"; +import { organizationPath } from "~/utils/pathBuilder"; +import v3Icon from "~/assets/icons/v3.svg"; +import { CheckCircleIcon } from "@heroicons/react/20/solid"; + +const ParamSchema = z.object({ + organizationSlug: z.string(), +}); + +export const action = async ({ request, params }: ActionFunctionArgs) => { + 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/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/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/endpoints/createEndpoint.server.ts b/apps/webapp/app/services/endpoints/createEndpoint.server.ts index a77bb844f..76a4418fa 100644 --- a/apps/webapp/app/services/endpoints/createEndpoint.server.ts +++ b/apps/webapp/app/services/endpoints/createEndpoint.server.ts @@ -4,7 +4,7 @@ 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"; +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 84baa4c22..f5d205312 100644 --- a/apps/webapp/app/services/endpoints/validateCreateEndpoint.server.ts +++ b/apps/webapp/app/services/endpoints/validateCreateEndpoint.server.ts @@ -5,7 +5,7 @@ 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"; +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 dca8b3228..a392c3e8f 100644 --- a/apps/webapp/app/services/events/cancelRunsForEvent.server.ts +++ b/apps/webapp/app/services/events/cancelRunsForEvent.server.ts @@ -1,11 +1,14 @@ -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"; +import type { JobRunStatus as JobRunStatusType } from "@trigger.dev/database"; +import { JobRunStatus } from "~/database-types"; -const CANCELLABLE_JOB_RUN_STATUS: JobRunStatus[] = [ + +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..0ad57ac46 100644 --- a/apps/webapp/app/services/jobs/cancelRunsForJob.server.ts +++ b/apps/webapp/app/services/jobs/cancelRunsForJob.server.ts @@ -1,11 +1,11 @@ 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"; +import { JobRunStatus } from "~/database-types"; -const CANCELLABLE_JOB_RUN_STATUS: JobRunStatus[] = [ +const CANCELLABLE_JOB_RUN_STATUS: Array = [ JobRunStatus.PENDING, JobRunStatus.QUEUED, JobRunStatus.WAITING_ON_CONNECTIONS, 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/services/runs/performRunExecutionV3.server.ts b/apps/webapp/app/services/runs/performRunExecutionV3.server.ts index ad1c28217..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"; @@ -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/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; diff --git a/apps/webapp/app/services/sources/handleHttpSource.server.ts b/apps/webapp/app/services/sources/handleHttpSource.server.ts index 518c65eaf..ed96c90b2 100644 --- a/apps/webapp/app/services/sources/handleHttpSource.server.ts +++ b/apps/webapp/app/services/sources/handleHttpSource.server.ts @@ -1,10 +1,8 @@ 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"; +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 c0c3ddf0e..166d8025e 100644 --- a/apps/webapp/app/services/sources/handleWebhookRequest.server.ts +++ b/apps/webapp/app/services/sources/handleWebhookRequest.server.ts @@ -1,10 +1,10 @@ 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"; +import { RuntimeEnvironmentType } from "~/database-types"; export class HandleWebhookRequestService { #prismaClient: PrismaClient; 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/apps/webapp/app/v3/eventRepository.server.ts b/apps/webapp/app/v3/eventRepository.server.ts index 991c52d82..4a25d2dbd 100644 --- a/apps/webapp/app/v3/eventRepository.server.ts +++ b/apps/webapp/app/v3/eventRepository.server.ts @@ -23,7 +23,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"; @@ -102,9 +102,27 @@ 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; + environmentType: true; + }; +}>; -export type PreparedEvent = Omit & { +export type PreparedEvent = Omit & { duration: number; events: SpanEvents; style: TaskEventStyle; @@ -140,6 +158,7 @@ export type SpanSummary = { isPartial: boolean; isCancelled: boolean; level: NonNullable; + environmentType: CreatableEventEnvironmentType; }; }; @@ -162,7 +181,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, @@ -351,7 +374,24 @@ 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, + environmentType: true, + }, where: { traceId, }, @@ -386,6 +426,7 @@ export class EventRepository { startTime: getDateFromNanoseconds(event.startTime), level: event.level, events: event.events, + environmentType: event.environmentType, }, }; }); @@ -409,22 +450,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, - environmentType: 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); @@ -432,7 +459,7 @@ export class EventRepository { return; } - const fullEvent = await this.db.taskEvent.findUnique({ + const fullEvent = await this.readReplica.taskEvent.findUnique({ where: { id: span.recordId, }, @@ -487,7 +514,7 @@ export class EventRepository { const events = transformEvents( span.data.events, fullEvent.metadata as Attributes, - traceSearch.environmentType === "DEVELOPMENT" + traceSummary?.rootSpan.data.environmentType === "DEVELOPMENT" ); return { @@ -821,7 +848,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/apps/webapp/app/v3/services/triggerTask.server.ts b/apps/webapp/app/v3/services/triggerTask.server.ts index aee4c5c31..1d6a59443 100644 --- a/apps/webapp/app/v3/services/triggerTask.server.ts +++ b/apps/webapp/app/v3/services/triggerTask.server.ts @@ -99,10 +99,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/apps/webapp/package.json b/apps/webapp/package.json index 5e335b8f3..fb6f8d3db 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", @@ -136,7 +137,7 @@ "parse-duration": "^1.1.0", "posthog-js": "^1.93.3", "posthog-node": "^3.1.3", - "prism-react-renderer": "^1.3.5", + "prism-react-renderer": "^2.3.1", "prismjs": "^1.29.0", "prom-client": "^15.1.0", "random-words": "^2.0.0", @@ -237,4 +238,4 @@ "engines": { "node": ">=16.0.0" } -} +} \ No newline at end of file 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 () => { 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/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-v3/src/apiClient.ts b/packages/cli-v3/src/apiClient.ts index 20fd16d4d..3702ef972 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, TaskRunExecution, APIError, } from "@trigger.dev/core/v3"; @@ -160,6 +162,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 909f564e7..37a9157d4 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), @@ -260,6 +261,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, @@ -1119,7 +1123,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) { @@ -1391,6 +1397,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 @@ -1452,10 +1544,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/packages/cli-v3/src/commands/dev.tsx b/packages/cli-v3/src/commands/dev.tsx index 51b537431..6a6a3e003 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; @@ -166,7 +167,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; @@ -205,6 +207,7 @@ async function startDev( debuggerOn={options.debugger} debugOtel={options.debugOtel} configPath={configPath} + configModule={configModule} /> ); } @@ -213,7 +216,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 ) ); @@ -240,6 +244,7 @@ type DevProps = { debuggerOn: boolean; debugOtel: boolean; configPath?: string; + configModule?: any; }; function useDev({ @@ -252,6 +257,7 @@ function useDev({ debuggerOn, debugOtel, configPath, + configModule, }: DevProps) { useEffect(() => { const websocketUrl = new URL(apiUrl); @@ -376,6 +382,8 @@ function useDev({ let ctx: BuildContext | undefined; + let firstBuild = true; + async function runBuild() { if (ctx) { // This will stop the watching @@ -395,7 +403,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) { @@ -415,8 +425,6 @@ function useDev({ ); } - let firstBuild = true; - logger.log(chalkGrey("○ Building background worker…")); ctx = await context({ @@ -528,24 +536,21 @@ function useDev({ const processEnv = await gatherProcessEnv(); - const backgroundWorker = new BackgroundWorker( - fullPath, - { - projectConfig: config, - dependencies, - env: { - ...processEnv, - TRIGGER_API_URL: apiUrl, - TRIGGER_SECRET_KEY: apiKey, - ...(environmentVariablesResponse.success - ? environmentVariablesResponse.data.variables - : {}), - }, - debuggerOn, - debugOtel, + const backgroundWorker = new BackgroundWorker(fullPath, { + projectConfig: config, + dependencies, + env: { + ...processEnv, + TRIGGER_API_URL: apiUrl, + TRIGGER_SECRET_KEY: apiKey, + ...(environmentVariablesResponse.success + ? environmentVariablesResponse.data.variables + : {}), }, - environmentClient - ); + debuggerOn, + debugOtel, + resolveEnvVariables: createResolveEnvironmentVariablesFunction(configModule), + }); try { await backgroundWorker.initialize(); @@ -895,3 +900,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/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, 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 b1867355b..bfb95cda2 100644 --- a/packages/cli-v3/src/workers/dev/backgroundWorker.ts +++ b/packages/cli-v3/src/workers/dev/backgroundWorker.ts @@ -46,6 +46,7 @@ import { UncaughtExceptionError, UnexpectedExitError, } from "../common/errors.js"; +import { env } from "node:process"; import { CliApiClient } from "../../apiClient.js"; export type CurrentWorkers = BackgroundWorkerCoordinator["currentWorkers"]; @@ -258,6 +259,10 @@ export type BackgroundWorkerParams = { projectConfig: ResolvedConfig; debuggerOn: boolean; debugOtel?: boolean; + resolveEnvVariables?: ( + env: Record, + worker: BackgroundWorker + ) => Promise | undefined>; }; export class BackgroundWorker { @@ -281,10 +286,11 @@ export class BackgroundWorker { private _closed: boolean = false; + private _fullEnv: Record = {}; + constructor( public path: string, - private params: BackgroundWorkerParams, - private apiClient: CliApiClient + public params: BackgroundWorkerParams ) {} close() { @@ -327,18 +333,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 @@ -418,7 +440,7 @@ export class BackgroundWorker { payload.execution.run.isTest, this.path, { - ...this.params.env, + ...this._fullEnv, ...(payload.environment ?? {}), ...this.#readEnvVars(), }, diff --git a/packages/cli-v3/src/workers/dev/worker-facade.ts b/packages/cli-v3/src/workers/dev/worker-facade.ts index cb38dcf07..703ea93a1 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 e16496d3a..516f70353 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/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 b248b6658..222191a7a 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", @@ -142,13 +142,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:*", @@ -158,10 +159,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/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/core/src/v3/apiClient/index.ts b/packages/core/src/v3/apiClient/index.ts index b98b67613..00449eb40 100644 --- a/packages/core/src/v3/apiClient/index.ts +++ b/packages/core/src/v3/apiClient/index.ts @@ -1,13 +1,18 @@ import { context, propagation } from "@opentelemetry/api"; -import { ZodFetchOptions, zodfetch } from "../zodfetch"; +import { version } from "../../../package.json"; +import { APIError } from "../apiErrors"; import { BatchTaskRunExecutionResult, BatchTriggerTaskRequestBody, BatchTriggerTaskResponse, CanceledRunResponse, + CreateEnvironmentVariableRequestBody, CreateScheduleOptions, CreateUploadPayloadUrlResponseBody, DeletedScheduleObject, + EnvironmentVariableResponseBody, + EnvironmentVariableValue, + EnvironmentVariables, ListScheduleOptions, ListSchedulesResult, ReplayRunResponse, @@ -16,13 +21,22 @@ import { TaskRunExecutionResult, TriggerTaskRequestBody, TriggerTaskResponse, + UpdateEnvironmentVariableRequestBody, 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, isRecordLike, zodfetch, zodupload } from "../zodfetch"; +import { + ImportEnvironmentVariablesParams, + CreateEnvironmentVariableParams, + UpdateEnvironmentVariableParams, +} from "./types"; + +export type { + ImportEnvironmentVariablesParams, + CreateEnvironmentVariableParams, + UpdateEnvironmentVariableParams, +}; export type TriggerOptions = { spanParentAsLink?: boolean; @@ -236,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/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/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 9f9b807d3..0a14bb2f7 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -364,3 +364,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 b57f3d197..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: T, +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/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/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/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 ae520c043..2052f5b82 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[] @@ -404,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 +1733,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 @@ -1883,6 +1900,9 @@ model TaskEvent { payloadType String? createdAt DateTime @default(now()) + + /// Used on the run page + @@index([traceId]) } enum TaskEventLevel { 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/trigger-sdk/src/apiClient.ts b/packages/trigger-sdk/src/apiClient.ts index c487691f7..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; @@ -74,6 +75,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 +142,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 +783,7 @@ async function zodfetchWithVersions< TUnversionedResponseBodySchema extends z.ZodTypeAny, TOptional extends boolean = false, >( + logger: Logger, versionedSchemaMap: TVersionedResponseBodyMap, unversionedSchema: TUnversionedResponseBodySchema, url: string, @@ -785,66 +798,141 @@ 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(); + //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)); + } + } - const version = response.headers.get("trigger-version"); + if (response.status >= 400 && response.status < 500) { + const rawBody = await safeResponseText(response); + const body = safeJsonParse(rawBody); + + 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 || error instanceof AutoYieldRateLimitError) { + 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 +961,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 +985,93 @@ 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; + } + + //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 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 (error instanceof AutoYieldRateLimitError) { + throw 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/errors.ts b/packages/trigger-sdk/src/errors.ts index dbc9e1d81..25e959ed9 100644 --- a/packages/trigger-sdk/src/errors.ts +++ b/packages/trigger-sdk/src/errors.ts @@ -45,6 +45,10 @@ export class AutoYieldWithCompletedTaskExecutionError { ) {} } +export class AutoYieldRateLimitError { + constructor(public resetAtTimestamp: number) {} +} + export class ParsedPayloadSchemaError { constructor(public schemaErrors: SchemaError[]) {} } @@ -56,6 +60,7 @@ export type TriggerInternalError = | YieldExecutionError | AutoYieldExecutionError | AutoYieldWithCompletedTaskExecutionError + | AutoYieldRateLimitError | ResumeWithParallelTaskError; /** Use this function if you're using a `try/catch` block to catch errors. @@ -72,6 +77,7 @@ export function isTriggerError(err: unknown): err is TriggerInternalError { err instanceof YieldExecutionError || err instanceof AutoYieldExecutionError || err instanceof AutoYieldWithCompletedTaskExecutionError || + err instanceof AutoYieldRateLimitError || err instanceof ResumeWithParallelTaskError ); } diff --git a/packages/trigger-sdk/src/io.ts b/packages/trigger-sdk/src/io.ts index 47c4d0685..eda9d0887 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, @@ -26,6 +28,7 @@ import { webcrypto } from "node:crypto"; import { ApiClient } from "./apiClient"; import { AutoYieldExecutionError, + AutoYieldRateLimitError, AutoYieldWithCompletedTaskExecutionError, CanceledWithTaskError, ErrorWithTask, @@ -1161,19 +1164,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 +1269,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 +1455,32 @@ 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) { + if (error instanceof AutoYieldRateLimitError) { + this._logger.debug("AutoYieldRateLimitError", { + error, + }); + + throw 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 +1502,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..3e132d7b2 100644 --- a/packages/trigger-sdk/src/triggerClient.ts +++ b/packages/trigger-sdk/src/triggerClient.ts @@ -52,6 +52,7 @@ import { ApiClient } from "./apiClient"; import { ConcurrencyLimit, ConcurrencyLimitOptions } from "./concurrencyLimit"; import { AutoYieldExecutionError, + AutoYieldRateLimitError, AutoYieldWithCompletedTaskExecutionError, CanceledWithTaskError, ErrorWithTask, @@ -198,11 +199,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); } @@ -1231,6 +1236,13 @@ export class TriggerClient { }; } + if (error instanceof AutoYieldRateLimitError) { + return { + status: "AUTO_YIELD_RATE_LIMIT", + reset: error.resetAtTimestamp, + }; + } + if (error instanceof YieldExecutionError) { return { status: "YIELD_EXECUTION", key: error.key }; } 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/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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e4333e77..f99275a30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -520,8 +520,8 @@ importers: specifier: ^3.1.3 version: 3.1.3 prism-react-renderer: - specifier: ^1.3.5 - version: 1.3.5(react@18.2.0) + specifier: ^2.3.1 + version: 2.3.1(react@18.2.0) prismjs: specifier: ^1.29.0 version: 1.29.0 @@ -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 @@ -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 @@ -1941,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:* @@ -2036,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:* @@ -2069,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 @@ -2155,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 @@ -2198,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 @@ -2235,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 @@ -2309,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 @@ -3065,9 +3068,12 @@ 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.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 +3085,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 @@ -3096,8 +3102,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 @@ -3125,43 +3131,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 @@ -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'} @@ -9290,10 +9422,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 +9446,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 +9465,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 +9493,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 +9523,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 +9551,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 +9581,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 +9607,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 +9635,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 +9665,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 +9695,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 +9726,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 +9758,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 +9777,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 +9795,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 +9821,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 +9845,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 +9875,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 +9895,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 +9915,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 +9937,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 +9952,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 +9976,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 +10000,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 +10048,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 +10072,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 +10102,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 +10126,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==} @@ -13251,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) @@ -14878,12 +15357,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: @@ -15945,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==} @@ -15965,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==} @@ -16130,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==} @@ -16172,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==} @@ -20512,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) @@ -20802,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 @@ -20810,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'} @@ -20948,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 @@ -20970,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: @@ -20980,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 @@ -20988,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 @@ -21001,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==} @@ -22381,6 +22830,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'} @@ -27817,15 +28271,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 @@ -27834,8 +28287,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 @@ -29370,14 +29823,6 @@ packages: resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} dev: true - /prism-react-renderer@1.3.5(react@18.2.0): - resolution: {integrity: sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==} - peerDependencies: - react: '>=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 +29833,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'} @@ -33579,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==} @@ -33599,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==} diff --git a/references/job-catalog/src/stressTest.ts b/references/job-catalog/src/stressTest.ts index 305dc152d..46689bf26 100644 --- a/references/job-catalog/src/stressTest.ts +++ b/references/job-catalog/src/stressTest.ts @@ -22,25 +22,16 @@ client.defineJob({ await io.runTask( `task-${i}`, async (task) => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + 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` } - ); - // Now do a wait for 5 seconds await io.wait("wait", 5); }, 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 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 9bbc8631c..a69369d62 100644 --- a/references/v3-catalog/package.json +++ b/references/v3-catalog/package.json @@ -9,7 +9,8 @@ "dependencies": { "@ffmpeg-installer/ffmpeg": "^1.1.0", "@ffprobe-installer/ffprobe": "^2.1.2", - "@opentelemetry/api": "^1.8.0", + "@infisical/sdk": "^2.1.9", + "@opentelemetry/api": "1.4.1", "@react-email/components": "^0.0.17", "@react-email/render": "^0.0.7", "@sindresorhus/slugify": "^2.2.1", @@ -19,7 +20,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", @@ -30,27 +31,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/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/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..0dcab6d5e 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 { envvars, logger, task, wait } from "@trigger.dev/sdk/v3"; +import { traceAsync } from "@/telemetry"; export const simplestTask = task({ id: "fetch-post-task", @@ -9,7 +10,7 @@ export const simplestTask = task({ body: JSON.stringify({ hello: "world", taskId: "fetch-post-task", - foo: "barrrrrrrrrrrrrrrrrrrr", + foo: "barrrrrrrrrrrrrrrrrrrrrr", }), }); @@ -20,12 +21,28 @@ 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", }; }, }); +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..649a349a6 100644 --- a/references/v3-catalog/trigger.config.ts +++ b/references/v3-catalog/trigger.config.ts @@ -1,9 +1,37 @@ -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, +}) => { + 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, + }); + + 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: {