Merge branch 'main' into v3/worker-attempt-creation
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
---
|
||||
|
||||
Fixing missing logs when importing client @opentelemetry/api
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
trigger.dev: patch
|
||||
---
|
||||
|
||||
Fix TypeScript inclusion in tsconfig.json for `cli-v3 init`
|
||||
+7
-1
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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`
|
||||
Vendored
+8
@@ -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",
|
||||
|
||||
@@ -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 `<root>/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
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.861435" y="0.861435" width="30.2771" height="30.2771" rx="15.1386" stroke="#D7D9DD" stroke-width="1.72287"/>
|
||||
<path d="M14.9669 12.642L11.6417 21.7732H9.8155L6.49036 12.642H8.04094L10.7286 20.2571L13.4163 12.642H14.9669ZM20.534 14.5544C21.6884 14.5716 23.0666 14.0375 23.0666 12.4525C23.0666 11.3843 22.1018 10.7124 20.534 10.7124C19.2591 10.7124 18.3632 11.3326 18.2082 12.3146L16.6576 12.2113C16.8815 10.4884 18.4838 9.26516 20.534 9.26516C22.946 9.26516 24.6345 10.5745 24.6345 12.4525C24.6345 13.8135 23.8592 14.7267 22.3775 15.1574C24.0831 15.657 25.0479 16.8286 25.0479 18.4136C25.0479 20.55 23.1872 22.0489 20.534 22.0489C18.0876 22.0489 16.313 20.6361 16.2096 18.6203L17.743 18.517C17.8808 20.0159 19.3108 20.6016 20.534 20.6016C21.9812 20.6016 23.4974 19.947 23.4974 18.293C23.4974 16.6563 21.9812 15.9499 20.534 15.9844L19.5865 16.0016V14.5371L20.534 14.5544Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 999 B |
@@ -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<HTMLDivElement, CodeBlockProps>(
|
||||
)}
|
||||
|
||||
{shouldHighlight ? (
|
||||
<Highlight {...defaultProps} theme={theme} code={code} language={language}>
|
||||
<Highlight theme={theme} code={code} language={language}>
|
||||
{({
|
||||
className: inheritedClassName,
|
||||
style: inheritedStyle,
|
||||
@@ -283,7 +292,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||
|
||||
return (
|
||||
<div
|
||||
key={lineProps.key}
|
||||
key={lineNumber}
|
||||
{...lineProps}
|
||||
className={cn(
|
||||
"flex w-full justify-start transition-opacity duration-500",
|
||||
@@ -312,7 +321,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||
const tokenProps = getTokenProps({ token, key });
|
||||
return (
|
||||
<span
|
||||
key={tokenProps.key}
|
||||
key={key}
|
||||
{...tokenProps}
|
||||
style={{
|
||||
color: tokenProps?.style?.color as string,
|
||||
|
||||
@@ -7,14 +7,15 @@ import {
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/20/solid";
|
||||
import { Form } from "@remix-run/react";
|
||||
import {
|
||||
BulkActionType,
|
||||
import type {
|
||||
RuntimeEnvironment,
|
||||
TaskRunStatus,
|
||||
TaskTriggerSource,
|
||||
TaskRunStatus,
|
||||
BulkActionType,
|
||||
} from "@trigger.dev/database";
|
||||
import { ListFilterIcon } from "lucide-react";
|
||||
import { ReactNode, startTransition, useCallback, useMemo, useState } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { startTransition, useCallback, useMemo, useState } from "react";
|
||||
import { z } from "zod";
|
||||
import { TaskIcon } from "~/assets/icons/TaskIcon";
|
||||
import { EnvironmentLabel, environmentTitle } from "~/components/environments/EnvironmentLabel";
|
||||
@@ -43,6 +44,7 @@ import { Button } from "../../primitives/Buttons";
|
||||
import {
|
||||
TaskRunStatusCombo,
|
||||
allTaskRunStatuses,
|
||||
filterableTaskRunStatuses,
|
||||
descriptionForTaskRunStatus,
|
||||
runStatusTitle,
|
||||
} from "./TaskRunStatus";
|
||||
@@ -50,7 +52,7 @@ import { TaskTriggerSourceIcon } from "./TaskTriggerSource";
|
||||
import { DateTime } from "~/components/primitives/DateTime";
|
||||
import { BulkActionStatusCombo } from "./BulkAction";
|
||||
|
||||
export const TaskAttemptStatus = z.nativeEnum(TaskRunStatus);
|
||||
export const TaskAttemptStatus = z.enum(allTaskRunStatuses);
|
||||
|
||||
export const TaskRunListSearchFilters = z.object({
|
||||
cursor: z.string().optional(),
|
||||
@@ -271,7 +273,7 @@ function MainMenu({ searchValue, trigger, clearSearchValue, setFilterType }: Men
|
||||
);
|
||||
}
|
||||
|
||||
const statuses = allTaskRunStatuses.map((status) => ({
|
||||
const statuses = filterableTaskRunStatuses.map((status) => ({
|
||||
title: runStatusTitle(status),
|
||||
value: status,
|
||||
}));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -25,9 +25,24 @@ export const allTaskRunStatuses = [
|
||||
"CANCELED",
|
||||
"COMPLETED_WITH_ERRORS",
|
||||
"CRASHED",
|
||||
"PAUSED",
|
||||
"INTERRUPTED",
|
||||
"SYSTEM_FAILURE",
|
||||
] as TaskRunStatus[];
|
||||
] as const satisfies Readonly<Array<TaskRunStatus>>;
|
||||
|
||||
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<Array<TaskRunStatus>>;
|
||||
|
||||
const taskRunStatusDescriptions: Record<TaskRunStatus, string> = {
|
||||
PENDING: "Task is waiting to be executed",
|
||||
|
||||
@@ -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<BatchTaskRunItemStatusType, BatchTaskRunItemStatusType>;
|
||||
|
||||
export const TaskRunAttemptStatus = {
|
||||
PENDING: "PENDING",
|
||||
EXECUTING: "EXECUTING",
|
||||
PAUSED: "PAUSED",
|
||||
FAILED: "FAILED",
|
||||
CANCELED: "CANCELED",
|
||||
COMPLETED: "COMPLETED",
|
||||
} as const satisfies Record<TaskRunAttemptStatusType, TaskRunAttemptStatusType>;
|
||||
|
||||
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<TaskRunStatusType, TaskRunStatusType>;
|
||||
|
||||
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<JobRunStatusType, JobRunStatusType>;
|
||||
|
||||
export const RuntimeEnvironmentType = {
|
||||
PRODUCTION: "PRODUCTION",
|
||||
STAGING: "STAGING",
|
||||
DEVELOPMENT: "DEVELOPMENT",
|
||||
PREVIEW: "PREVIEW",
|
||||
} as const satisfies Record<RuntimeEnvironmentTypeType, RuntimeEnvironmentTypeType>;
|
||||
@@ -70,7 +70,9 @@ export { Prisma };
|
||||
|
||||
export const prisma = singleton("prisma", getClient);
|
||||
|
||||
export const $replica: Omit<PrismaClient, "$transaction"> = singleton(
|
||||
export type PrismaReplicaClient = Omit<PrismaClient, "$transaction">;
|
||||
|
||||
export const $replica: PrismaReplicaClient = singleton(
|
||||
"replica",
|
||||
() => getReplicaClient() ?? prisma
|
||||
);
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -21,16 +21,12 @@ export async function createOrganization(
|
||||
{
|
||||
title,
|
||||
userId,
|
||||
projectName,
|
||||
companySize,
|
||||
projectVersion,
|
||||
}: Pick<Organization, "title" | "companySize"> & {
|
||||
userId: User["id"];
|
||||
projectName: string;
|
||||
projectVersion: "v2" | "v3";
|
||||
},
|
||||
attemptCount = 0
|
||||
): Promise<Organization & { projects: Project[] }> {
|
||||
): Promise<Organization> {
|
||||
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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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<ReturnType<SpanPresenter["call"]>>;
|
||||
export type Span = NonNullable<Result>["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;
|
||||
|
||||
@@ -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<TaskRunStatus, number>);
|
||||
} as { day: string } & Record<TaskRunStatusType, number>);
|
||||
}
|
||||
|
||||
acc[a.taskIdentifier] = existingTask;
|
||||
@@ -214,7 +223,7 @@ export class TaskListPresenter extends BasePresenter {
|
||||
day[a.status] = Number(a.count);
|
||||
|
||||
return acc;
|
||||
}, {} as Record<string, ({ day: string } & Record<TaskRunStatus, number>)[]>);
|
||||
}, {} as Record<string, ({ day: string } & Record<TaskRunStatusType, number>)[]>);
|
||||
}
|
||||
|
||||
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;
|
||||
}[]
|
||||
>`
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
+12
-5
@@ -70,7 +70,7 @@ const Variable = z.object({
|
||||
type Variable = z.infer<typeof Variable>;
|
||||
|
||||
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() {
|
||||
<Button
|
||||
variant="secondary/small"
|
||||
disabled={isLoading}
|
||||
name="overwrite"
|
||||
name="override"
|
||||
value="true"
|
||||
>
|
||||
{isLoading ? "Overwriting" : "Overwrite"}
|
||||
{isLoading ? "Overriding" : "Override"}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
|
||||
+10
-2
@@ -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"
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -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<typeof loader>();
|
||||
const { organization } = useTypedLoaderData<typeof loader>();
|
||||
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 (
|
||||
<RequestV3Access
|
||||
hasRequestedV3={organization.hasRequestedV3}
|
||||
organizationSlug={organization.slug}
|
||||
projectsCount={organization.projectsCount}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const [form, { projectName, projectVersion }] = useForm({
|
||||
id: "create-project",
|
||||
@@ -119,7 +141,7 @@ export default function NewOrganizationPage() {
|
||||
<FormTitle
|
||||
LeadingIcon="folder"
|
||||
title="Create a new project"
|
||||
description={`This will create a new project in your "${organization.title}" organization. `}
|
||||
description={`This will create a new project in your "${organization.title}" organization.`}
|
||||
/>
|
||||
<Form method="post" {...form.props}>
|
||||
{organization.projectsCount === 0 && (
|
||||
@@ -138,7 +160,7 @@ export default function NewOrganizationPage() {
|
||||
/>
|
||||
<FormError id={projectName.errorId}>{projectName.error}</FormError>
|
||||
</InputGroup>
|
||||
{canCreateV3Projects ? (
|
||||
{canCreateV2Projects && canCreateV3Projects ? (
|
||||
<InputGroup>
|
||||
<Label htmlFor={projectVersion.id}>Project version</Label>
|
||||
<Select
|
||||
@@ -161,8 +183,16 @@ export default function NewOrganizationPage() {
|
||||
</Select>
|
||||
<FormError id={projectVersion.errorId}>{projectVersion.error}</FormError>
|
||||
</InputGroup>
|
||||
) : canCreateV3Projects ? (
|
||||
<>
|
||||
<Callout variant="info">This will be a v3 project</Callout>
|
||||
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v3"} />
|
||||
</>
|
||||
) : (
|
||||
<input {...conform.input(projectVersion, { type: "hidden" })} value="v2" />
|
||||
<>
|
||||
<Callout variant="info">This will be a v2 project</Callout>
|
||||
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v2"} />
|
||||
</>
|
||||
)}
|
||||
<FormButtons
|
||||
confirmButton={
|
||||
|
||||
@@ -17,19 +17,14 @@ import { Input } from "~/components/primitives/Input";
|
||||
import { InputGroup } from "~/components/primitives/InputGroup";
|
||||
import { Label } from "~/components/primitives/Label";
|
||||
import { RadioGroupItem } from "~/components/primitives/RadioButton";
|
||||
import { Select, SelectItem } from "~/components/primitives/Select";
|
||||
import { featuresForRequest } from "~/features.server";
|
||||
import { useFeatures } from "~/hooks/useFeatures";
|
||||
import { createOrganization } from "~/models/organization.server";
|
||||
import { NewOrganizationPresenter } from "~/presenters/NewOrganizationPresenter.server";
|
||||
import { commitCurrentProjectSession, setCurrentProjectId } from "~/services/currentProject.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { projectPath, rootPath, selectPlanPath } from "~/utils/pathBuilder";
|
||||
import { organizationPath, rootPath } from "~/utils/pathBuilder";
|
||||
|
||||
const schema = z.object({
|
||||
orgName: z.string().min(3).max(50),
|
||||
projectName: z.string().min(3).max(50),
|
||||
projectVersion: z.enum(["v2", "v3"]),
|
||||
companySize: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -57,29 +52,10 @@ export const action: ActionFunction = async ({ request }) => {
|
||||
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() {
|
||||
<Hint>E.g. your company name or your workspace name.</Hint>
|
||||
<FormError id={orgName.errorId}>{orgName.error}</FormError>
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<Label htmlFor={projectName.id}>Project name</Label>
|
||||
<Input
|
||||
{...conform.input(projectName, { type: "text" })}
|
||||
placeholder="Your Project name"
|
||||
icon="folder"
|
||||
/>
|
||||
<Hint>Your Jobs will live inside this Project.</Hint>
|
||||
<FormError id={projectName.errorId}>{projectName.error}</FormError>
|
||||
</InputGroup>
|
||||
{v3Enabled ? (
|
||||
<InputGroup>
|
||||
<Label htmlFor={projectVersion.id}>Project version</Label>
|
||||
<Select
|
||||
{...conform.select(projectVersion)}
|
||||
defaultValue={undefined}
|
||||
variant="tertiary/medium"
|
||||
placeholder="Select version"
|
||||
dropdownIcon
|
||||
text={(value) => {
|
||||
switch (value) {
|
||||
case "v2":
|
||||
return "Version 2";
|
||||
case "v3":
|
||||
return "Version 3";
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectItem value="v2">Version 2</SelectItem>
|
||||
<SelectItem value="v3">Version 3 (Developer Preview)</SelectItem>
|
||||
</Select>
|
||||
<FormError id={projectVersion.errorId}>{projectVersion.error}</FormError>
|
||||
</InputGroup>
|
||||
) : (
|
||||
<input {...conform.input(projectVersion, { type: "hidden" })} value="v2" />
|
||||
)}
|
||||
{isManagedCloud && (
|
||||
<InputGroup>
|
||||
<Label htmlFor={projectName.id}>Number of employees</Label>
|
||||
<Label htmlFor={"companySize"}>Number of employees</Label>
|
||||
<RadioGroup name="companySize" className="flex items-center justify-between gap-2">
|
||||
<RadioGroupItem
|
||||
id="employees-1-5"
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { CreateExternalConnectionBody } from "@trigger.dev/core";
|
||||
import { PrismaClientOrTransaction, prisma } from "~/db.server";
|
||||
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
|
||||
import { integrationAuthRepository } from "~/services/externalApis/integrationAuthRepository.server";
|
||||
|
||||
export class CreateExternalConnectionService {
|
||||
#prismaClient: PrismaClientOrTransaction;
|
||||
|
||||
constructor(prismaClient: PrismaClientOrTransaction = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(
|
||||
accountIdentifier: string,
|
||||
clientSlug: string,
|
||||
environment: AuthenticatedEnvironment,
|
||||
payload: CreateExternalConnectionBody
|
||||
) {
|
||||
const externalAccount = await this.#prismaClient.externalAccount.upsert({
|
||||
where: {
|
||||
environmentId_identifier: {
|
||||
environmentId: environment.id,
|
||||
identifier: accountIdentifier,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
environmentId: environment.id,
|
||||
organizationId: environment.organizationId,
|
||||
identifier: accountIdentifier,
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
|
||||
const integration = await this.#prismaClient.integration.findUniqueOrThrow({
|
||||
where: {
|
||||
organizationId_slug: {
|
||||
organizationId: environment.organizationId,
|
||||
slug: clientSlug,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return await integrationAuthRepository.createConnectionFromToken({
|
||||
externalAccount: externalAccount,
|
||||
integration,
|
||||
token: payload,
|
||||
});
|
||||
}
|
||||
}
|
||||
+3
-53
@@ -1,15 +1,10 @@
|
||||
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import {
|
||||
CreateExternalConnectionBody,
|
||||
CreateExternalConnectionBodySchema,
|
||||
ErrorWithStackSchema,
|
||||
} from "@trigger.dev/core";
|
||||
import { CreateExternalConnectionBodySchema, ErrorWithStackSchema } from "@trigger.dev/core";
|
||||
import { z } from "zod";
|
||||
import { generateErrorMessage } from "zod-error";
|
||||
import { PrismaClientOrTransaction, prisma } from "~/db.server";
|
||||
import { AuthenticatedEnvironment, authenticateApiRequest } from "~/services/apiAuth.server";
|
||||
import { integrationAuthRepository } from "~/services/externalApis/integrationAuthRepository.server";
|
||||
import { authenticateApiRequest } from "~/services/apiAuth.server";
|
||||
import { CreateExternalConnectionService } from "./CreateExternalConnectionService.server";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
accountId: z.string(),
|
||||
@@ -67,48 +62,3 @@ export async function action({ request, params }: ActionFunctionArgs) {
|
||||
return json({ message: parsedError.data.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
class CreateExternalConnectionService {
|
||||
#prismaClient: PrismaClientOrTransaction;
|
||||
|
||||
constructor(prismaClient: PrismaClientOrTransaction = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(
|
||||
accountIdentifier: string,
|
||||
clientSlug: string,
|
||||
environment: AuthenticatedEnvironment,
|
||||
payload: CreateExternalConnectionBody
|
||||
) {
|
||||
const externalAccount = await this.#prismaClient.externalAccount.upsert({
|
||||
where: {
|
||||
environmentId_identifier: {
|
||||
environmentId: environment.id,
|
||||
identifier: accountIdentifier,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
environmentId: environment.id,
|
||||
organizationId: environment.organizationId,
|
||||
identifier: accountIdentifier,
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
|
||||
const integration = await this.#prismaClient.integration.findUniqueOrThrow({
|
||||
where: {
|
||||
organizationId_slug: {
|
||||
organizationId: environment.organizationId,
|
||||
slug: clientSlug,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return await integrationAuthRepository.createConnectionFromToken({
|
||||
externalAccount: externalAccount,
|
||||
integration,
|
||||
token: payload,
|
||||
});
|
||||
}
|
||||
}
|
||||
+4
-67
@@ -1,73 +1,10 @@
|
||||
import { ActionFunctionArgs, LoaderFunctionArgs, json } from "@remix-run/server-runtime";
|
||||
import { RuntimeEnvironmentType } from "@trigger.dev/database";
|
||||
import { z } from "zod";
|
||||
import { $transaction, PrismaClient, prisma } from "~/db.server";
|
||||
import type { PrismaClient } from "~/db.server";
|
||||
import { $transaction, prisma } from "~/db.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { workerQueue } from "~/services/worker.server";
|
||||
import { safeJsonParse } from "~/utils/json";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
environmentId: z.string(),
|
||||
endpointSlug: z.string(),
|
||||
indexHookIdentifier: z.string(),
|
||||
});
|
||||
|
||||
export async function loader({ params }: LoaderFunctionArgs) {
|
||||
const parsedParams = ParamsSchema.safeParse(params);
|
||||
|
||||
if (!parsedParams.success) {
|
||||
return {
|
||||
status: 400,
|
||||
json: {
|
||||
error: "Invalid params",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const { environmentId, endpointSlug, indexHookIdentifier } = parsedParams.data;
|
||||
|
||||
const service = new TriggerEndpointIndexHookService();
|
||||
|
||||
await service.call({
|
||||
environmentId,
|
||||
endpointSlug,
|
||||
indexHookIdentifier,
|
||||
});
|
||||
|
||||
return json({
|
||||
ok: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function action({ request, params }: ActionFunctionArgs) {
|
||||
const parsedParams = ParamsSchema.safeParse(params);
|
||||
|
||||
if (!parsedParams.success) {
|
||||
return {
|
||||
status: 400,
|
||||
json: {
|
||||
error: "Invalid params",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const { environmentId, endpointSlug, indexHookIdentifier } = parsedParams.data;
|
||||
|
||||
const body = await request.text();
|
||||
|
||||
const service = new TriggerEndpointIndexHookService();
|
||||
|
||||
await service.call({
|
||||
environmentId,
|
||||
endpointSlug,
|
||||
indexHookIdentifier,
|
||||
body: body ? safeJsonParse(body) : undefined,
|
||||
});
|
||||
|
||||
return json({
|
||||
ok: true,
|
||||
});
|
||||
}
|
||||
import { RuntimeEnvironmentType } from "~/database-types";
|
||||
import type { ParamsSchema } from "./route";
|
||||
|
||||
type TriggerEndpointDeployHookOptions = z.infer<typeof ParamsSchema> & {
|
||||
body?: any;
|
||||
+67
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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<ImportEnvironmentVariablesRequestBody> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 })));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
-21
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
-72
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
|
||||
+92
@@ -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<ServerTask | undefined> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
+2
-90
@@ -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<ServerTask | undefined> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
+3
-68
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
+49
@@ -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<RunTaskResponseWithCachedTasksBody> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-53
@@ -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<RunTaskResponseWithCachedTasksBody> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
-36
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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: "/" });
|
||||
};
|
||||
|
||||
-67
@@ -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,
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
+9
-4
@@ -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
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+3
-2
@@ -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) {
|
||||
|
||||
@@ -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 (
|
||||
<MainCenteredContainer>
|
||||
<div>
|
||||
<div className="relative mb-4 flex size-9">
|
||||
<img src={v3Icon} alt="v3" width={32} height={32} />
|
||||
<div className="absolute right-0 top-0 size-4 rounded-full bg-background-dimmed">
|
||||
<CheckCircleIcon className="size-4 text-success" />
|
||||
</div>
|
||||
</div>
|
||||
<Paragraph spacing variant="base/bright">
|
||||
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.
|
||||
</Paragraph>
|
||||
<Paragraph spacing variant="base/bright">
|
||||
Right now v3 is completely free to use but{" "}
|
||||
<TextLink href="https://trigger.dev/blog/v3-developer-preview-launch/#cloud-pricing">
|
||||
paid tiers
|
||||
</TextLink>{" "}
|
||||
will be introduced soon.
|
||||
</Paragraph>
|
||||
<Paragraph spacing variant="base/bright">
|
||||
In the meantime, check out the{" "}
|
||||
<TextLink href="https://trigger.dev/docs">v3 docs</TextLink>, the{" "}
|
||||
<TextLink href="https://trigger.dev/blog/v3-developer-preview-launch/">
|
||||
v3 blog post
|
||||
</TextLink>{" "}
|
||||
and <TextLink href="https://trigger.dev/discord">join our Discord</TextLink>.
|
||||
</Paragraph>
|
||||
</div>
|
||||
</MainCenteredContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MainCenteredContainer>
|
||||
<img src={v3Icon} alt="v3" width={32} height={32} className="mb-4" />
|
||||
<Form action={`/resources/orgs/${organizationSlug}/v3-access`} method="post">
|
||||
{projectsCount > 0 ? (
|
||||
<Paragraph spacing variant="base/bright">
|
||||
You can no longer create v2 projects and your organization doesn't have access to v3
|
||||
yet. We are approving access requests daily.
|
||||
</Paragraph>
|
||||
) : (
|
||||
<Paragraph spacing variant="base/bright">
|
||||
Trigger.dev v3 is currently in Developer Preview and we’re operating a waitlist as we
|
||||
focus on the platform’s reliability and scaleability.
|
||||
</Paragraph>
|
||||
)}
|
||||
<Paragraph spacing variant="base/bright">
|
||||
For more info, check out our{" "}
|
||||
<TextLink href="https://trigger.dev/blog/v3-developer-preview-launch/">
|
||||
v3 blog post
|
||||
</TextLink>
|
||||
.
|
||||
</Paragraph>
|
||||
<div className="mt-2 flex items-center justify-between gap-3">
|
||||
{projectsCount > 0 ? (
|
||||
<LinkButton variant="tertiary/small" to={organizationPath({ slug: organizationSlug })}>
|
||||
Cancel
|
||||
</LinkButton>
|
||||
) : null}
|
||||
<Button variant="primary/small" type="submit">
|
||||
Request access
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</MainCenteredContainer>
|
||||
);
|
||||
}
|
||||
@@ -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<T, K extends keyof T> = Prettify<Omit<T, K> & Partial<Pick<T, K>>>;
|
||||
|
||||
@@ -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<DualAuthenticationResult | undefined> {
|
||||
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<AuthenticatedEnvironment> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<JobRunStatusType> = [
|
||||
JobRunStatus.PENDING,
|
||||
JobRunStatus.QUEUED,
|
||||
JobRunStatus.WAITING_ON_CONNECTIONS,
|
||||
|
||||
@@ -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<keyof typeof JobRunStatus> = [
|
||||
JobRunStatus.PENDING,
|
||||
JobRunStatus.QUEUED,
|
||||
JobRunStatus.WAITING_ON_CONNECTIONS,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<typeof SecretStoreOptionsSchema>;
|
||||
import type { SecretStoreOptions } from "./secretStoreOptionsSchema.server";
|
||||
|
||||
type ProviderInitializationOptions = {
|
||||
DATABASE: {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const SecretStoreOptionsSchema = z.enum(["DATABASE", "AWS_PARAM_STORE"]);
|
||||
export type SecretStoreOptions = z.infer<typeof SecretStoreOptionsSchema>;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Result> {
|
||||
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<ProjectEnvironmentVariable[]> {
|
||||
async getProject(projectId: string): Promise<ProjectEnvironmentVariable[]> {
|
||||
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<EnvironmentVariable[]> {
|
||||
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<EnvironmentVariable[]> {
|
||||
@@ -621,25 +597,24 @@ export class EnvironmentVariablesRepository implements Repository {
|
||||
|
||||
async getEnvironmentVariables(
|
||||
projectId: string,
|
||||
environmentId: string
|
||||
environmentId: string,
|
||||
excludeInternalVariables?: boolean
|
||||
): Promise<EnvironmentVariable[]> {
|
||||
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<Result> {
|
||||
async delete(projectId: string, options: DeleteEnvironmentVariable): Promise<Result> {
|
||||
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<Result> {
|
||||
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",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,14 +31,22 @@ export const EditEnvironmentVariable = z.object({
|
||||
value: z.string(),
|
||||
})
|
||||
),
|
||||
keepEmptyValues: z.boolean().optional(),
|
||||
});
|
||||
export type EditEnvironmentVariable = z.infer<typeof EditEnvironmentVariable>;
|
||||
|
||||
export const DeleteEnvironmentVariable = z.object({
|
||||
id: z.string(),
|
||||
environmentId: z.string().optional(),
|
||||
});
|
||||
export type DeleteEnvironmentVariable = z.infer<typeof DeleteEnvironmentVariable>;
|
||||
|
||||
export const DeleteEnvironmentVariableValue = z.object({
|
||||
id: z.string(),
|
||||
environmentId: z.string(),
|
||||
});
|
||||
export type DeleteEnvironmentVariableValue = z.infer<typeof DeleteEnvironmentVariableValue>;
|
||||
|
||||
export type Result =
|
||||
| {
|
||||
success: true;
|
||||
@@ -65,18 +73,19 @@ export type EnvironmentVariable = {
|
||||
};
|
||||
|
||||
export interface Repository {
|
||||
create(
|
||||
projectId: string,
|
||||
userId: string,
|
||||
options: CreateEnvironmentVariables
|
||||
): Promise<CreateResult>;
|
||||
edit(projectId: string, userId: string, options: EditEnvironmentVariable): Promise<Result>;
|
||||
getProject(projectId: string, userId: string): Promise<ProjectEnvironmentVariable[]>;
|
||||
create(projectId: string, options: CreateEnvironmentVariables): Promise<CreateResult>;
|
||||
edit(projectId: string, options: EditEnvironmentVariable): Promise<Result>;
|
||||
getProject(projectId: string): Promise<ProjectEnvironmentVariable[]>;
|
||||
getEnvironment(
|
||||
projectId: string,
|
||||
userId: string,
|
||||
environmentId: string
|
||||
environmentId: string,
|
||||
excludeInternalVariables?: boolean
|
||||
): Promise<EnvironmentVariable[]>;
|
||||
getEnvironmentVariables(projectId: string, environmentId: string): Promise<EnvironmentVariable[]>;
|
||||
delete(projectId: string, userId: string, options: DeleteEnvironmentVariable): Promise<Result>;
|
||||
getEnvironmentVariables(
|
||||
projectId: string,
|
||||
environmentId: string,
|
||||
excludeInternalVariables?: boolean
|
||||
): Promise<EnvironmentVariable[]>;
|
||||
delete(projectId: string, options: DeleteEnvironmentVariable): Promise<Result>;
|
||||
deleteValue(projectId: string, options: DeleteEnvironmentVariableValue): Promise<Result>;
|
||||
}
|
||||
|
||||
@@ -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<TaskEventRecord, "events" | "style" | "duration"> & {
|
||||
export type PreparedEvent = Omit<QueriedEvent, "events" | "style" | "duration"> & {
|
||||
duration: number;
|
||||
events: SpanEvents;
|
||||
style: TaskEventStyle;
|
||||
@@ -140,6 +158,7 @@ export type SpanSummary = {
|
||||
isPartial: boolean;
|
||||
isCancelled: boolean;
|
||||
level: NonNullable<CreatableEvent["level"]>;
|
||||
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<TraceSummary | undefined> {
|
||||
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,
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 () => {
|
||||
|
||||
+93
-20
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <token>' \\\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 <token>'"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/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 <token>'"
|
||||
}
|
||||
]
|
||||
},
|
||||
"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 <token>' \\\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 <token>'"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/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 <token>'"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/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 <token>'"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/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 <token>'"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/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 <token>'"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/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 <token>'"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"bearerAuth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"description": "Use your Secret API key in the form 'Bearer <SECRET KEY>' (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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Create Env Var"
|
||||
openapi: "v3-openapi POST /api/v1/projects/{projectRef}/envvars/{env}"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Delete Env Var"
|
||||
openapi: "v3-openapi DELETE /api/v1/projects/{projectRef}/envvars/{env}/{name}"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Import Env Vars"
|
||||
openapi: "v3-openapi POST /api/v1/projects/{projectRef}/envvars/{env}/import"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "List Env Vars"
|
||||
openapi: "v3-openapi GET /api/v1/projects/{projectRef}/envvars/{env}"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Retrieve Env Var"
|
||||
openapi: "v3-openapi GET /api/v1/projects/{projectRef}/envvars/{env}/{name}"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Update Env Var"
|
||||
openapi: "v3-openapi PUT /api/v1/projects/{projectRef}/envvars/{env}/{name}"
|
||||
---
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user