diff --git a/apps/webapp/app/models/organization.server.ts b/apps/webapp/app/models/organization.server.ts index 66cc0a0c1..8bfc912af 100644 --- a/apps/webapp/app/models/organization.server.ts +++ b/apps/webapp/app/models/organization.server.ts @@ -27,20 +27,6 @@ export function getOrganizationFromSlug({ }) { return prisma.organization.findFirst({ include: { - workflows: { - include: { - externalServices: { - select: { - service: true, - }, - }, - }, - where: { isArchived: false }, - orderBy: [ - { disabledAt: { sort: "asc", nulls: "first" } }, - { title: "asc" }, - ], - }, environments: true, }, where: { slug, members: { some: { userId } } }, diff --git a/apps/webapp/app/services/clientApi.server.ts b/apps/webapp/app/services/clientApi.server.ts index eef2a7286..929758126 100644 --- a/apps/webapp/app/services/clientApi.server.ts +++ b/apps/webapp/app/services/clientApi.server.ts @@ -9,7 +9,7 @@ import { DeliverEventResponseSchema, ErrorWithStackSchema, RunJobResponseSchema, - GetJobsResponseSchema, + GetEndpointDataResponseSchema, HttpSourceResponseSchema, PongResponseSchema, PrepareForJobExecutionResponseSchema, @@ -64,7 +64,7 @@ export class ClientApi { return PongResponseSchema.parse(anyBody); } - async getJobs() { + async getEndpointData() { const response = await safeFetch(this.#url, { method: "GET", headers: { @@ -91,7 +91,7 @@ export class ClientApi { body: anyBody, }); - return GetJobsResponseSchema.parse(anyBody); + return GetEndpointDataResponseSchema.parse(anyBody); } async deliverEvent(event: ApiEventLog) { diff --git a/apps/webapp/app/services/endpoints/endpointRegistered.server.ts b/apps/webapp/app/services/endpoints/endpointRegistered.server.ts index 4e41c316d..489c68cc3 100644 --- a/apps/webapp/app/services/endpoints/endpointRegistered.server.ts +++ b/apps/webapp/app/services/endpoints/endpointRegistered.server.ts @@ -23,7 +23,7 @@ export class EndpointRegisteredService { // Make a request to the endpoint to fetch a list of jobs const client = new ClientApi(endpoint.environment.apiKey, endpoint.url); - const { jobs } = await client.getJobs(); + const { jobs, dynamicTriggers } = await client.getEndpointData(); for (const job of jobs) { await workerQueue.enqueue("registerJob", { diff --git a/apps/webapp/app/services/endpoints/prepareJobInstance.server.ts b/apps/webapp/app/services/endpoints/prepareJobVersion.server.ts similarity index 91% rename from apps/webapp/app/services/endpoints/prepareJobInstance.server.ts rename to apps/webapp/app/services/endpoints/prepareJobVersion.server.ts index 5445b6d34..f78d12966 100644 --- a/apps/webapp/app/services/endpoints/prepareJobInstance.server.ts +++ b/apps/webapp/app/services/endpoints/prepareJobVersion.server.ts @@ -3,7 +3,7 @@ import { prisma } from "~/db.server"; import { IngestSendEvent } from "~/routes/api.v3.events"; import semver from "semver"; -export class PrepareJobInstanceService { +export class PrepareJobVersionService { #prismaClient: PrismaClient; constructor(prismaClient: PrismaClient = prisma) { @@ -11,7 +11,7 @@ export class PrepareJobInstanceService { } public async call(id: string) { - const jobInstance = await this.#prismaClient.jobInstance.findUniqueOrThrow({ + const jobInstance = await this.#prismaClient.jobVersion.findUniqueOrThrow({ where: { id, }, diff --git a/apps/webapp/app/services/jobs/registerJob.server.ts b/apps/webapp/app/services/jobs/registerJob.server.ts index 4036fcd31..2649396c2 100644 --- a/apps/webapp/app/services/jobs/registerJob.server.ts +++ b/apps/webapp/app/services/jobs/registerJob.server.ts @@ -1,15 +1,11 @@ import type { - ApiConnection, Endpoint, Job, JobConnection, - JobInstance, + JobVersion, + ApiConnectionClient, } from ".prisma/client"; -import type { - ConnectionConfig, - GetJobResponse, - LocalAuthConnectionConfig, -} from "@trigger.dev/internal"; +import type { ConnectionConfig, JobMetadata } from "@trigger.dev/internal"; import { DEFAULT_MAX_CONCURRENT_RUNS } from "~/consts"; import type { PrismaClient } from "~/db.server"; import { prisma } from "~/db.server"; @@ -24,7 +20,7 @@ export class RegisterJobService { this.#prismaClient = prismaClient; } - public async call(endpointId: string, jobResponse: GetJobResponse) { + public async call(endpointId: string, jobResponse: JobMetadata) { const endpoint = await this.#prismaClient.endpoint.findUniqueOrThrow({ where: { id: endpointId, @@ -39,15 +35,15 @@ export class RegisterJobService { }, }); - const jobInstance = await this.#upsertJob( + const jobVersion = await this.#upsertJob( endpoint, endpoint.environment, jobResponse ); await workerQueue.enqueue( - "prepareJobInstance", - { id: jobInstance.id }, + "prepareJobVersion", + { id: jobVersion.id }, { queueName: `endpoint-${endpoint.id}` } ); } @@ -55,8 +51,8 @@ export class RegisterJobService { async #upsertJob( endpoint: Endpoint, environment: AuthenticatedEnvironment, - metadata: GetJobResponse - ): Promise { + metadata: JobMetadata + ): Promise { logger.debug("Upserting job", { endpoint, organizationId: environment.organizationId, @@ -72,32 +68,31 @@ export class RegisterJobService { if (metadata.connections) { for (const connection of Object.values(metadata.connections)) { - if (connection.auth === "hosted") { - connectionSlugs.add(connection.id); - } + connectionSlugs.add(connection.id); } } - const apiConnections = new Map(); + const apiConnectionClients = new Map(); for (const connectionSlug of connectionSlugs) { - const apiConnection = await this.#prismaClient.apiConnection.findUnique({ - where: { - organizationId_slug: { - organizationId: environment.organizationId, - slug: connectionSlug, + const apiConnectionClient = + await this.#prismaClient.apiConnectionClient.findUnique({ + where: { + organizationId_slug: { + organizationId: environment.organizationId, + slug: connectionSlug, + }, }, - }, - }); + }); - if (!apiConnection) { + if (!apiConnectionClient) { // TODO: find a better way to handle and message the user about this issue throw new Error( - `Could not find ApiConnection with slug ${connectionSlug}` + `Could not find ApiConnectionClient with slug ${connectionSlug}` ); } - apiConnections.set(connectionSlug, apiConnection); + apiConnectionClients.set(connectionSlug, apiConnectionClient); } // Upsert the Job @@ -129,7 +124,7 @@ export class RegisterJobService { include: { connections: { include: { - apiConnection: true, + apiConnectionClient: true, }, }, }, @@ -170,8 +165,8 @@ export class RegisterJobService { }, }); - // Upsert the JobInstance - const jobInstance = await this.#prismaClient.jobInstance.upsert({ + // Upsert the JobVersion + const jobVersion = await this.#prismaClient.jobVersion.upsert({ where: { jobId_version_endpointId: { jobId: job.id, @@ -211,10 +206,10 @@ export class RegisterJobService { }, }, version: metadata.version, - trigger: metadata.trigger, + eventSpecification: metadata.event, }, update: { - trigger: metadata.trigger, + eventSpecification: metadata.event, queue: { connect: { id: jobQueue.id, @@ -224,7 +219,7 @@ export class RegisterJobService { include: { connections: { include: { - apiConnection: true, + apiConnectionClient: true, }, }, }, @@ -236,9 +231,9 @@ export class RegisterJobService { for (const [key, connection] of Object.entries(metadata.connections)) { const jobConnection = await this.#upsertJobConnection( job, - jobInstance, + jobVersion, connection, - apiConnections, + apiConnectionClients, key ); @@ -246,7 +241,7 @@ export class RegisterJobService { } // Count the number of job instances that have higher version numbers - const laterJobInstanceCount = await this.#prismaClient.jobInstance.count({ + const laterJobVersionCount = await this.#prismaClient.jobVersion.count({ where: { jobId: job.id, version: { @@ -257,7 +252,7 @@ export class RegisterJobService { }); // If there are no later job instances, then we can upsert the latest jobalias - if (laterJobInstanceCount === 0) { + if (laterJobVersionCount === 0) { // upsert the latest jobalias await this.#prismaClient.jobAlias.upsert({ where: { @@ -269,14 +264,14 @@ export class RegisterJobService { }, create: { jobId: job.id, - jobInstanceId: jobInstance.id, + versionId: jobVersion.id, environmentId: environment.id, name: "latest", - version: jobInstance.version, + value: jobVersion.version, }, update: { - jobInstanceId: jobInstance.id, - version: jobInstance.version, + versionId: jobVersion.id, + value: jobVersion.version, }, }); } @@ -285,75 +280,72 @@ export class RegisterJobService { // It's import this runs after the trigger variant upserts await this.#prismaClient.jobConnection.deleteMany({ where: { - jobInstanceId: jobInstance.id, + versionId: jobVersion.id, id: { notIn: Array.from(jobConnections), }, }, }); - // upsert the eventrule - // The event rule should only be enabled if all the external connections are ready - await this.#prismaClient.jobEventRule.upsert({ - where: { - jobInstanceId_actionIdentifier: { - jobInstanceId: jobInstance.id, - actionIdentifier: "__trigger", - }, - }, - create: { - event: metadata.trigger.eventRule.event, - source: metadata.trigger.eventRule.source, - payloadFilter: metadata.trigger.eventRule.payload, - contextFilter: metadata.trigger.eventRule.context, - jobId: job.id, - jobInstanceId: jobInstance.id, - environmentId: environment.id, - organizationId: environment.organizationId, - projectId: environment.projectId, - enabled: true, - actionIdentifier: "__trigger", - }, - update: { - event: metadata.trigger.eventRule.event, - source: metadata.trigger.eventRule.source, - payloadFilter: metadata.trigger.eventRule.payload, - contextFilter: metadata.trigger.eventRule.context, - }, - }); + // This is where we upsert the triggers if there are any + // // upsert the eventrule + // // The event rule should only be enabled if all the external connections are ready + // await this.#prismaClient.jobEventRule.upsert({ + // where: { + // jobVersionId_actionIdentifier: { + // jobVersionId: jobVersion.id, + // actionIdentifier: "__trigger", + // }, + // }, + // create: { + // event: metadata.trigger.eventRule.event, + // source: metadata.trigger.eventRule.source, + // payloadFilter: metadata.trigger.eventRule.payload, + // contextFilter: metadata.trigger.eventRule.context, + // jobId: job.id, + // jobVersionId: jobVersion.id, + // environmentId: environment.id, + // organizationId: environment.organizationId, + // projectId: environment.projectId, + // enabled: true, + // actionIdentifier: "__trigger", + // }, + // update: { + // event: metadata.trigger.eventRule.event, + // source: metadata.trigger.eventRule.source, + // payloadFilter: metadata.trigger.eventRule.payload, + // contextFilter: metadata.trigger.eventRule.context, + // }, + // }); - return jobInstance; + return jobVersion; } async #upsertJobConnection( job: Job & { connections: Array< - JobConnection & { apiConnection: ApiConnection | null } + JobConnection & { apiConnectionClient: ApiConnectionClient | null } >; }, - jobInstance: JobInstance & { + jobVersion: JobVersion & { connections: Array< - JobConnection & { apiConnection: ApiConnection | null } + JobConnection & { apiConnectionClient: ApiConnectionClient | null } >; }, config: ConnectionConfig, - apiConnections: Map, + apiConnectionClients: Map, key: string ): Promise { - if (config.auth === "local") { - return this.#upsertLocalAuthConnection(job, jobInstance, config, key); - } + const apiConnectionClient = apiConnectionClients.get(config.id); - const apiConnection = apiConnections.get(config.id); - - if (!apiConnection) { + if (!apiConnectionClient) { throw new Error( - `Could not find api connection with id ${config.id} for job ${job.id}` + `Could not find api connection client with id ${config.id} for job ${job.id}` ); } // Find existing connection in the job instance - const existingInstanceConnection = jobInstance.connections.find( + const existingInstanceConnection = jobVersion.connections.find( (connection) => connection.key === key ); @@ -363,8 +355,7 @@ export class RegisterJobService { id: existingInstanceConnection.id, }, data: { - apiConnectionId: apiConnection.id, - usesLocalAuth: false, + apiConnectionClientId: apiConnectionClient.id, }, }); } @@ -378,14 +369,14 @@ export class RegisterJobService { logger.debug("Creating new job connection from existing", { existingJobConnection, key, - jobInstanceId: jobInstance.id, + jobVersionId: jobVersion.id, }); return this.#prismaClient.jobConnection.create({ data: { - jobInstance: { + version: { connect: { - id: jobInstance.id, + id: jobVersion.id, }, }, job: { @@ -395,27 +386,26 @@ export class RegisterJobService { }, key, connectionMetadata: existingJobConnection.connectionMetadata ?? {}, - apiConnection: { + apiConnectionClient: { connect: { - id: apiConnection.id, + id: apiConnectionClient.id, }, }, - usesLocalAuth: false, }, }); } logger.debug("Creating new job connection", { key, - jobInstanceId: jobInstance.id, + jobVersionId: jobVersion.id, config, }); return this.#prismaClient.jobConnection.create({ data: { - jobInstance: { + version: { connect: { - id: jobInstance.id, + id: jobVersion.id, }, }, job: { @@ -425,94 +415,11 @@ export class RegisterJobService { }, key, connectionMetadata: config.metadata, - apiConnection: { + apiConnectionClient: { connect: { - id: apiConnection.id, + id: apiConnectionClient.id, }, }, - usesLocalAuth: false, - }, - }); - } - - async #upsertLocalAuthConnection( - job: Job & { - connections: Array< - JobConnection & { apiConnection: ApiConnection | null } - >; - }, - jobInstance: JobInstance & { - connections: Array< - JobConnection & { apiConnection: ApiConnection | null } - >; - }, - config: LocalAuthConnectionConfig, - key: string - ): Promise { - // Find existing connection in the job instance - const existingInstanceConnection = jobInstance.connections.find( - (connection) => connection.key === key - ); - - if ( - existingInstanceConnection && - existingInstanceConnection.apiConnectionId - ) { - return await this.#prismaClient.jobConnection.update({ - where: { - id: existingInstanceConnection.id, - }, - data: { - apiConnectionId: null, - usesLocalAuth: true, - }, - }); - } - - if (existingInstanceConnection) { - return existingInstanceConnection; - } - - // Find existing connection in the job - const existingJobConnection = job.connections.find( - (connection) => connection.key === key - ); - - if (existingJobConnection) { - return this.#prismaClient.jobConnection.create({ - data: { - jobInstance: { - connect: { - id: jobInstance.id, - }, - }, - job: { - connect: { - id: job.id, - }, - }, - key, - connectionMetadata: existingJobConnection.connectionMetadata ?? {}, - usesLocalAuth: true, - }, - }); - } - - return this.#prismaClient.jobConnection.create({ - data: { - jobInstance: { - connect: { - id: jobInstance.id, - }, - }, - job: { - connect: { - id: job.id, - }, - }, - key, - connectionMetadata: config.metadata, - usesLocalAuth: true, }, }); } diff --git a/apps/webapp/app/services/worker.server.ts b/apps/webapp/app/services/worker.server.ts index 25ce344d6..f08cda30b 100644 --- a/apps/webapp/app/services/worker.server.ts +++ b/apps/webapp/app/services/worker.server.ts @@ -1,9 +1,8 @@ -import { GetJobResponseSchema } from "@/../../packages/internal/src"; import { z } from "zod"; import { env } from "~/env.server"; import { ZodWorker } from "~/platform/zodWorker.server"; import { EndpointRegisteredService } from "./endpoints/endpointRegistered.server"; -import { PrepareJobInstanceService } from "./endpoints/prepareJobInstance.server"; +import { PrepareJobVersionService } from "./endpoints/prepareJobVersion.server"; import { DeliverEventService } from "./events/deliverEvent.server"; import { apiConnectionRepository } from "./externalApis/apiAuthenticationRepository.server"; import { RegisterJobService } from "./jobs/registerJob.server"; @@ -12,6 +11,7 @@ import { StartRunService } from "./runs/startRun.server"; import { DeliverHttpSourceRequestService } from "./sources/deliverHttpSourceRequest.server"; import { StartQueuedRunsService } from "./runs/startQueuedRuns.server"; import { RunFinishedService } from "./runs/runFinished.server"; +import { JobMetadataSchema } from "@trigger.dev/internal"; const workerCatalog = { organizationCreated: z.object({ id: z.string() }), @@ -33,7 +33,7 @@ const workerCatalog = { startRun: z.object({ id: z.string() }), runFinished: z.object({ id: z.string() }), resumeTask: z.object({ id: z.string() }), - prepareJobInstance: z.object({ id: z.string() }), + prepareJobVersion: z.object({ id: z.string() }), deliverHttpSourceRequest: z.object({ id: z.string() }), refreshOAuthToken: z.object({ organizationId: z.string(), @@ -41,7 +41,7 @@ const workerCatalog = { }), registerJob: z.object({ endpointId: z.string(), - job: GetJobResponseSchema, + job: JobMetadataSchema, }), startQueuedRuns: z.object({ id: z.string() }), }; @@ -112,10 +112,10 @@ function getWorkerQueue() { await service.call(payload.id); }, }, - prepareJobInstance: { + prepareJobVersion: { maxAttempts: 3, handler: async (payload, job) => { - const service = new PrepareJobInstanceService(); + const service = new PrepareJobVersionService(); await service.call(payload.id); }, diff --git a/apps/webapp/prisma/migrations/20230512085413_schema_redesign_for_new_system/migration.sql b/apps/webapp/prisma/migrations/20230512085413_schema_redesign_for_new_system/migration.sql new file mode 100644 index 000000000..82e84c3f4 --- /dev/null +++ b/apps/webapp/prisma/migrations/20230512085413_schema_redesign_for_new_system/migration.sql @@ -0,0 +1,640 @@ +/* + Warnings: + + - You are about to drop the column `apiIdentifier` on the `ApiConnection` table. All the data in the column will be lost. + - You are about to drop the column `authenticationMethodKey` on the `ApiConnection` table. All the data in the column will be lost. + - You are about to drop the column `scopes` on the `ApiConnection` table. All the data in the column will be lost. + - You are about to drop the column `slug` on the `ApiConnection` table. All the data in the column will be lost. + - You are about to drop the column `title` on the `ApiConnection` table. All the data in the column will be lost. + - You are about to drop the column `apiConnectionId` on the `ApiConnectionAttempt` table. All the data in the column will be lost. + - You are about to drop the column `apiIdentifier` on the `ApiConnectionAttempt` table. All the data in the column will be lost. + - You are about to drop the column `authenticationMethodKey` on the `ApiConnectionAttempt` table. All the data in the column will be lost. + - You are about to drop the column `organizationId` on the `ApiConnectionAttempt` table. All the data in the column will be lost. + - You are about to drop the column `scopes` on the `ApiConnectionAttempt` table. All the data in the column will be lost. + - You are about to drop the column `title` on the `ApiConnectionAttempt` table. All the data in the column will be lost. + - You are about to drop the column `jobInstanceId` on the `JobAlias` table. All the data in the column will be lost. + - You are about to drop the column `version` on the `JobAlias` table. All the data in the column will be lost. + - You are about to drop the column `apiConnectionId` on the `JobConnection` table. All the data in the column will be lost. + - You are about to drop the column `jobInstanceId` on the `JobConnection` table. All the data in the column will be lost. + - You are about to drop the column `usesLocalAuth` on the `JobConnection` table. All the data in the column will be lost. + - You are about to drop the column `eventLogId` on the `JobRun` table. All the data in the column will be lost. + - You are about to drop the column `jobInstanceId` on the `JobRun` table. All the data in the column will be lost. + - You are about to drop the `CurrentEnvironment` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `DeploymentLog` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `DeploymentLogPoll` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `DurableDelay` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `EventLog` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `EventRule` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `ExternalService` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `ExternalSource` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `FetchRequest` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `FetchResponse` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `GitHubAppAuthorization` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `GitHubAppAuthorizationAttempt` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `IntegrationRequest` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `IntegrationResponse` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `InternalSource` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `JobEventRule` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `JobInstance` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `KeyValueItem` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `OrganizationTemplate` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `ProjectDeployment` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `RepositoryProject` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `SchedulerSource` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `Template` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `TriggerEvent` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `Workflow` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `WorkflowRun` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `WorkflowRunStep` table. If the table is not empty, all the data it contains will be lost. + - A unique constraint covering the columns `[versionId,key]` on the table `JobConnection` will be added. If there are existing duplicate values, this will fail. + - Added the required column `clientId` to the `ApiConnection` table without a default value. This is not possible if the table is not empty. + - Added the required column `clientId` to the `ApiConnectionAttempt` table without a default value. This is not possible if the table is not empty. + - Added the required column `value` to the `JobAlias` table without a default value. This is not possible if the table is not empty. + - Added the required column `versionId` to the `JobAlias` table without a default value. This is not possible if the table is not empty. + - Added the required column `apiConnectionClientId` to the `JobConnection` table without a default value. This is not possible if the table is not empty. + - Added the required column `versionId` to the `JobConnection` table without a default value. This is not possible if the table is not empty. + - Added the required column `eventId` to the `JobRun` table without a default value. This is not possible if the table is not empty. + - Added the required column `versionId` to the `JobRun` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "ApiConnectionType" AS ENUM ('EXTERNAL', 'DEVELOPER'); + +-- CreateEnum +CREATE TYPE "JobTriggerAction" AS ENUM ('CREATE_RUN', 'RESUME_TASK'); + +-- DropForeignKey +ALTER TABLE "ApiConnectionAttempt" DROP CONSTRAINT "ApiConnectionAttempt_apiConnectionId_fkey"; + +-- DropForeignKey +ALTER TABLE "CurrentEnvironment" DROP CONSTRAINT "CurrentEnvironment_environmentId_fkey"; + +-- DropForeignKey +ALTER TABLE "CurrentEnvironment" DROP CONSTRAINT "CurrentEnvironment_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "CurrentEnvironment" DROP CONSTRAINT "CurrentEnvironment_workflowId_fkey"; + +-- DropForeignKey +ALTER TABLE "DeploymentLog" DROP CONSTRAINT "DeploymentLog_deploymentId_fkey"; + +-- DropForeignKey +ALTER TABLE "DeploymentLogPoll" DROP CONSTRAINT "DeploymentLogPoll_deploymentId_fkey"; + +-- DropForeignKey +ALTER TABLE "DurableDelay" DROP CONSTRAINT "DurableDelay_runId_fkey"; + +-- DropForeignKey +ALTER TABLE "DurableDelay" DROP CONSTRAINT "DurableDelay_stepId_fkey"; + +-- DropForeignKey +ALTER TABLE "EventLog" DROP CONSTRAINT "EventLog_environmentId_fkey"; + +-- DropForeignKey +ALTER TABLE "EventLog" DROP CONSTRAINT "EventLog_organizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "EventLog" DROP CONSTRAINT "EventLog_projectId_fkey"; + +-- DropForeignKey +ALTER TABLE "EventRule" DROP CONSTRAINT "EventRule_environmentId_fkey"; + +-- DropForeignKey +ALTER TABLE "EventRule" DROP CONSTRAINT "EventRule_organizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "EventRule" DROP CONSTRAINT "EventRule_workflowId_fkey"; + +-- DropForeignKey +ALTER TABLE "ExternalService" DROP CONSTRAINT "ExternalService_connectionId_fkey"; + +-- DropForeignKey +ALTER TABLE "ExternalService" DROP CONSTRAINT "ExternalService_workflowId_fkey"; + +-- DropForeignKey +ALTER TABLE "ExternalSource" DROP CONSTRAINT "ExternalSource_connectionId_fkey"; + +-- DropForeignKey +ALTER TABLE "ExternalSource" DROP CONSTRAINT "ExternalSource_organizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "FetchRequest" DROP CONSTRAINT "FetchRequest_runId_fkey"; + +-- DropForeignKey +ALTER TABLE "FetchRequest" DROP CONSTRAINT "FetchRequest_stepId_fkey"; + +-- DropForeignKey +ALTER TABLE "FetchResponse" DROP CONSTRAINT "FetchResponse_requestId_fkey"; + +-- DropForeignKey +ALTER TABLE "GitHubAppAuthorization" DROP CONSTRAINT "GitHubAppAuthorization_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "GitHubAppAuthorizationAttempt" DROP CONSTRAINT "GitHubAppAuthorizationAttempt_authorizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "IntegrationRequest" DROP CONSTRAINT "IntegrationRequest_externalServiceId_fkey"; + +-- DropForeignKey +ALTER TABLE "IntegrationRequest" DROP CONSTRAINT "IntegrationRequest_runId_fkey"; + +-- DropForeignKey +ALTER TABLE "IntegrationRequest" DROP CONSTRAINT "IntegrationRequest_stepId_fkey"; + +-- DropForeignKey +ALTER TABLE "IntegrationResponse" DROP CONSTRAINT "IntegrationResponse_requestId_fkey"; + +-- DropForeignKey +ALTER TABLE "InternalSource" DROP CONSTRAINT "InternalSource_environmentId_fkey"; + +-- DropForeignKey +ALTER TABLE "InternalSource" DROP CONSTRAINT "InternalSource_organizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "InternalSource" DROP CONSTRAINT "InternalSource_workflowId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobAlias" DROP CONSTRAINT "JobAlias_jobInstanceId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobConnection" DROP CONSTRAINT "JobConnection_apiConnectionId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobConnection" DROP CONSTRAINT "JobConnection_jobInstanceId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobEventRule" DROP CONSTRAINT "JobEventRule_environmentId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobEventRule" DROP CONSTRAINT "JobEventRule_jobId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobEventRule" DROP CONSTRAINT "JobEventRule_jobInstanceId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobEventRule" DROP CONSTRAINT "JobEventRule_organizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobEventRule" DROP CONSTRAINT "JobEventRule_projectId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobInstance" DROP CONSTRAINT "JobInstance_endpointId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobInstance" DROP CONSTRAINT "JobInstance_environmentId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobInstance" DROP CONSTRAINT "JobInstance_jobId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobInstance" DROP CONSTRAINT "JobInstance_organizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobInstance" DROP CONSTRAINT "JobInstance_projectId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobInstance" DROP CONSTRAINT "JobInstance_queueId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobRun" DROP CONSTRAINT "JobRun_eventLogId_fkey"; + +-- DropForeignKey +ALTER TABLE "JobRun" DROP CONSTRAINT "JobRun_jobInstanceId_fkey"; + +-- DropForeignKey +ALTER TABLE "KeyValueItem" DROP CONSTRAINT "KeyValueItem_environmentId_fkey"; + +-- DropForeignKey +ALTER TABLE "OrganizationTemplate" DROP CONSTRAINT "OrganizationTemplate_authorizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "OrganizationTemplate" DROP CONSTRAINT "OrganizationTemplate_organizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "OrganizationTemplate" DROP CONSTRAINT "OrganizationTemplate_templateId_fkey"; + +-- DropForeignKey +ALTER TABLE "ProjectDeployment" DROP CONSTRAINT "ProjectDeployment_environmentId_fkey"; + +-- DropForeignKey +ALTER TABLE "ProjectDeployment" DROP CONSTRAINT "ProjectDeployment_projectId_fkey"; + +-- DropForeignKey +ALTER TABLE "RepositoryProject" DROP CONSTRAINT "RepositoryProject_authorizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "RepositoryProject" DROP CONSTRAINT "RepositoryProject_currentDeploymentId_fkey"; + +-- DropForeignKey +ALTER TABLE "RepositoryProject" DROP CONSTRAINT "RepositoryProject_organizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "SchedulerSource" DROP CONSTRAINT "SchedulerSource_environmentId_fkey"; + +-- DropForeignKey +ALTER TABLE "SchedulerSource" DROP CONSTRAINT "SchedulerSource_organizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "SchedulerSource" DROP CONSTRAINT "SchedulerSource_workflowId_fkey"; + +-- DropForeignKey +ALTER TABLE "TriggerEvent" DROP CONSTRAINT "TriggerEvent_environmentId_fkey"; + +-- DropForeignKey +ALTER TABLE "TriggerEvent" DROP CONSTRAINT "TriggerEvent_organizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "Workflow" DROP CONSTRAINT "Workflow_externalSourceId_fkey"; + +-- DropForeignKey +ALTER TABLE "Workflow" DROP CONSTRAINT "Workflow_organizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "Workflow" DROP CONSTRAINT "Workflow_organizationTemplateId_fkey"; + +-- DropForeignKey +ALTER TABLE "Workflow" DROP CONSTRAINT "Workflow_repositoryProjectId_fkey"; + +-- DropForeignKey +ALTER TABLE "WorkflowRun" DROP CONSTRAINT "WorkflowRun_environmentId_fkey"; + +-- DropForeignKey +ALTER TABLE "WorkflowRun" DROP CONSTRAINT "WorkflowRun_eventId_fkey"; + +-- DropForeignKey +ALTER TABLE "WorkflowRun" DROP CONSTRAINT "WorkflowRun_eventRuleId_fkey"; + +-- DropForeignKey +ALTER TABLE "WorkflowRun" DROP CONSTRAINT "WorkflowRun_workflowId_fkey"; + +-- DropForeignKey +ALTER TABLE "WorkflowRunStep" DROP CONSTRAINT "WorkflowRunStep_runId_fkey"; + +-- DropIndex +DROP INDEX "ApiConnection_organizationId_slug_key"; + +-- DropIndex +DROP INDEX "JobConnection_jobInstanceId_key_key"; + +-- AlterTable +ALTER TABLE "ApiConnection" DROP COLUMN "apiIdentifier", +DROP COLUMN "authenticationMethodKey", +DROP COLUMN "scopes", +DROP COLUMN "slug", +DROP COLUMN "title", +ADD COLUMN "clientId" TEXT NOT NULL, +ADD COLUMN "connectionType" "ApiConnectionType" NOT NULL DEFAULT 'DEVELOPER', +ADD COLUMN "externalAccountId" TEXT; + +-- AlterTable +ALTER TABLE "ApiConnectionAttempt" DROP COLUMN "apiConnectionId", +DROP COLUMN "apiIdentifier", +DROP COLUMN "authenticationMethodKey", +DROP COLUMN "organizationId", +DROP COLUMN "scopes", +DROP COLUMN "title", +ADD COLUMN "clientId" TEXT NOT NULL; + +-- AlterTable +ALTER TABLE "JobAlias" DROP COLUMN "jobInstanceId", +DROP COLUMN "version", +ADD COLUMN "value" TEXT NOT NULL, +ADD COLUMN "versionId" TEXT NOT NULL; + +-- AlterTable +ALTER TABLE "JobConnection" DROP COLUMN "apiConnectionId", +DROP COLUMN "jobInstanceId", +DROP COLUMN "usesLocalAuth", +ADD COLUMN "apiConnectionClientId" TEXT NOT NULL, +ADD COLUMN "versionId" TEXT NOT NULL; + +-- AlterTable +ALTER TABLE "JobRun" DROP COLUMN "eventLogId", +DROP COLUMN "jobInstanceId", +ADD COLUMN "eventId" TEXT NOT NULL, +ADD COLUMN "versionId" TEXT NOT NULL; + +-- DropTable +DROP TABLE "CurrentEnvironment"; + +-- DropTable +DROP TABLE "DeploymentLog"; + +-- DropTable +DROP TABLE "DeploymentLogPoll"; + +-- DropTable +DROP TABLE "DurableDelay"; + +-- DropTable +DROP TABLE "EventLog"; + +-- DropTable +DROP TABLE "EventRule"; + +-- DropTable +DROP TABLE "ExternalService"; + +-- DropTable +DROP TABLE "ExternalSource"; + +-- DropTable +DROP TABLE "FetchRequest"; + +-- DropTable +DROP TABLE "FetchResponse"; + +-- DropTable +DROP TABLE "GitHubAppAuthorization"; + +-- DropTable +DROP TABLE "GitHubAppAuthorizationAttempt"; + +-- DropTable +DROP TABLE "IntegrationRequest"; + +-- DropTable +DROP TABLE "IntegrationResponse"; + +-- DropTable +DROP TABLE "InternalSource"; + +-- DropTable +DROP TABLE "JobEventRule"; + +-- DropTable +DROP TABLE "JobInstance"; + +-- DropTable +DROP TABLE "KeyValueItem"; + +-- DropTable +DROP TABLE "OrganizationTemplate"; + +-- DropTable +DROP TABLE "ProjectDeployment"; + +-- DropTable +DROP TABLE "RepositoryProject"; + +-- DropTable +DROP TABLE "SchedulerSource"; + +-- DropTable +DROP TABLE "Template"; + +-- DropTable +DROP TABLE "TriggerEvent"; + +-- DropTable +DROP TABLE "Workflow"; + +-- DropTable +DROP TABLE "WorkflowRun"; + +-- DropTable +DROP TABLE "WorkflowRunStep"; + +-- DropEnum +DROP TYPE "DeploymentLogType"; + +-- DropEnum +DROP TYPE "ExternalServiceStatus"; + +-- DropEnum +DROP TYPE "ExternalServiceType"; + +-- DropEnum +DROP TYPE "ExternalSourceStatus"; + +-- DropEnum +DROP TYPE "ExternalSourceType"; + +-- DropEnum +DROP TYPE "FetchRequestStatus"; + +-- DropEnum +DROP TYPE "GitHubAccountType"; + +-- DropEnum +DROP TYPE "IntegrationRequestStatus"; + +-- DropEnum +DROP TYPE "InternalSourceStatus"; + +-- DropEnum +DROP TYPE "InternalSourceType"; + +-- DropEnum +DROP TYPE "JobEventAction"; + +-- DropEnum +DROP TYPE "OrganizationTemplateStatus"; + +-- DropEnum +DROP TYPE "ProjectDeploymentStatus"; + +-- DropEnum +DROP TYPE "RepositoryProjectStatus"; + +-- DropEnum +DROP TYPE "SchedulerSourceStatus"; + +-- DropEnum +DROP TYPE "TriggerEventStatus"; + +-- DropEnum +DROP TYPE "TriggerType"; + +-- DropEnum +DROP TYPE "WorkflowRunStatus"; + +-- DropEnum +DROP TYPE "WorkflowRunStepStatus"; + +-- DropEnum +DROP TYPE "WorkflowRunStepType"; + +-- DropEnum +DROP TYPE "WorkflowStatus"; + +-- CreateTable +CREATE TABLE "ExternalAccount" ( + "id" TEXT NOT NULL, + "identifier" TEXT NOT NULL, + "metadata" JSONB, + "organizationId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ExternalAccount_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ApiConnectionClient" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "slug" TEXT NOT NULL, + "schema" JSONB NOT NULL, + "scopes" TEXT[], + "credentialsReferenceId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "organizationId" TEXT NOT NULL, + + CONSTRAINT "ApiConnectionClient_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "JobVersion" ( + "id" TEXT NOT NULL, + "version" TEXT NOT NULL, + "eventSpecification" JSONB NOT NULL, + "jobId" TEXT NOT NULL, + "endpointId" TEXT NOT NULL, + "environmentId" TEXT NOT NULL, + "organizationId" TEXT NOT NULL, + "projectId" TEXT NOT NULL, + "queueId" TEXT NOT NULL, + "ready" BOOLEAN NOT NULL DEFAULT false, + "latest" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "JobVersion_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "JobTrigger" ( + "id" TEXT NOT NULL, + "event" TEXT NOT NULL, + "source" TEXT NOT NULL, + "payloadFilter" JSONB, + "contextFilter" JSONB, + "action" "JobTriggerAction" NOT NULL DEFAULT 'CREATE_RUN', + "actionIdentifier" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "enabled" BOOLEAN NOT NULL DEFAULT true, + "jobId" TEXT NOT NULL, + "versionId" TEXT NOT NULL, + "organizationId" TEXT NOT NULL, + "environmentId" TEXT NOT NULL, + "projectId" TEXT NOT NULL, + "externalAccountId" TEXT, + + CONSTRAINT "JobTrigger_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "EventRecord" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "payload" JSONB NOT NULL, + "context" JSONB, + "source" TEXT NOT NULL DEFAULT 'trigger.dev', + "organizationId" TEXT NOT NULL, + "environmentId" TEXT NOT NULL, + "projectId" TEXT NOT NULL, + "deliverAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deliveredAt" TIMESTAMP(3), + "isTest" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "EventRecord_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "ExternalAccount_organizationId_identifier_key" ON "ExternalAccount"("organizationId", "identifier"); + +-- CreateIndex +CREATE UNIQUE INDEX "ApiConnectionClient_organizationId_slug_key" ON "ApiConnectionClient"("organizationId", "slug"); + +-- CreateIndex +CREATE UNIQUE INDEX "JobVersion_jobId_version_endpointId_key" ON "JobVersion"("jobId", "version", "endpointId"); + +-- CreateIndex +CREATE UNIQUE INDEX "JobTrigger_versionId_actionIdentifier_key" ON "JobTrigger"("versionId", "actionIdentifier"); + +-- CreateIndex +CREATE UNIQUE INDEX "JobConnection_versionId_key_key" ON "JobConnection"("versionId", "key"); + +-- AddForeignKey +ALTER TABLE "ExternalAccount" ADD CONSTRAINT "ExternalAccount_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ApiConnectionClient" ADD CONSTRAINT "ApiConnectionClient_credentialsReferenceId_fkey" FOREIGN KEY ("credentialsReferenceId") REFERENCES "SecretReference"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ApiConnectionClient" ADD CONSTRAINT "ApiConnectionClient_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ApiConnection" ADD CONSTRAINT "ApiConnection_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "ApiConnectionClient"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ApiConnection" ADD CONSTRAINT "ApiConnection_externalAccountId_fkey" FOREIGN KEY ("externalAccountId") REFERENCES "ExternalAccount"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ApiConnectionAttempt" ADD CONSTRAINT "ApiConnectionAttempt_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "ApiConnectionClient"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobVersion" ADD CONSTRAINT "JobVersion_jobId_fkey" FOREIGN KEY ("jobId") REFERENCES "Job"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobVersion" ADD CONSTRAINT "JobVersion_endpointId_fkey" FOREIGN KEY ("endpointId") REFERENCES "Endpoint"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobVersion" ADD CONSTRAINT "JobVersion_environmentId_fkey" FOREIGN KEY ("environmentId") REFERENCES "RuntimeEnvironment"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobVersion" ADD CONSTRAINT "JobVersion_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobVersion" ADD CONSTRAINT "JobVersion_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobVersion" ADD CONSTRAINT "JobVersion_queueId_fkey" FOREIGN KEY ("queueId") REFERENCES "JobQueue"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobAlias" ADD CONSTRAINT "JobAlias_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "JobVersion"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobConnection" ADD CONSTRAINT "JobConnection_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "JobVersion"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobConnection" ADD CONSTRAINT "JobConnection_apiConnectionClientId_fkey" FOREIGN KEY ("apiConnectionClientId") REFERENCES "ApiConnectionClient"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobTrigger" ADD CONSTRAINT "JobTrigger_jobId_fkey" FOREIGN KEY ("jobId") REFERENCES "Job"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobTrigger" ADD CONSTRAINT "JobTrigger_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "JobVersion"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobTrigger" ADD CONSTRAINT "JobTrigger_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobTrigger" ADD CONSTRAINT "JobTrigger_environmentId_fkey" FOREIGN KEY ("environmentId") REFERENCES "RuntimeEnvironment"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobTrigger" ADD CONSTRAINT "JobTrigger_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobTrigger" ADD CONSTRAINT "JobTrigger_externalAccountId_fkey" FOREIGN KEY ("externalAccountId") REFERENCES "ExternalAccount"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EventRecord" ADD CONSTRAINT "EventRecord_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EventRecord" ADD CONSTRAINT "EventRecord_environmentId_fkey" FOREIGN KEY ("environmentId") REFERENCES "RuntimeEnvironment"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EventRecord" ADD CONSTRAINT "EventRecord_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobRun" ADD CONSTRAINT "JobRun_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "JobVersion"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "JobRun" ADD CONSTRAINT "JobRun_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "EventRecord"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/webapp/prisma/schema.prisma b/apps/webapp/prisma/schema.prisma index fd3123e11..221ed02ac 100644 --- a/apps/webapp/prisma/schema.prisma +++ b/apps/webapp/prisma/schema.prisma @@ -29,13 +29,10 @@ model User { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - gitHubAppAuthorizations GitHubAppAuthorization[] - featureCloud Boolean @default(false) isOnHostedRepoWaitlist Boolean @default(false) - currentEnvironments CurrentEnvironment[] - orgMemberships OrgMember[] + orgMemberships OrgMember[] } enum AuthenticationMethod { @@ -51,41 +48,50 @@ model Organization { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - workflows Workflow[] - environments RuntimeEnvironment[] - apiConnections ApiConnection[] - events TriggerEvent[] - externalSources ExternalSource[] - eventRules EventRule[] - schedulerSources SchedulerSource[] - internalSources InternalSource[] - templates OrganizationTemplate[] - repositoryProjects RepositoryProject[] - endpoints Endpoint[] - jobs Job[] - jobInstances JobInstance[] - eventLogs EventLog[] - jobRuns JobRun[] - httpSources HttpSource[] - jobEventRule JobEventRule[] - projects Project[] - members OrgMember[] + environments RuntimeEnvironment[] + apiConnections ApiConnection[] + endpoints Endpoint[] + jobs Job[] + jobVersions JobVersion[] + events EventRecord[] + jobRuns JobRun[] + httpSources HttpSource[] + jobEventRule JobTrigger[] + projects Project[] + members OrgMember[] + externalAccounts ExternalAccount[] + connectionClients ApiConnectionClient[] } -model ApiConnection { +model ExternalAccount { + id String @id @default(cuid()) + identifier String + metadata Json? + + organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) + organizationId String + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + connections ApiConnection[] + triggers JobTrigger[] + + @@unique([organizationId, identifier]) +} + +model ApiConnectionClient { id String @id @default(cuid()) - slug String - title String - apiIdentifier String + title String + slug String - authenticationMethodKey String - scopes String[] - expiresAt DateTime? - metadata Json + schema Json - dataReference SecretReference @relation(fields: [dataReferenceId], references: [id], onDelete: Cascade, onUpdate: Cascade) - dataReferenceId String + scopes String[] + + credentialsReference SecretReference? @relation(fields: [credentialsReferenceId], references: [id], onDelete: Cascade, onUpdate: Cascade) + credentialsReferenceId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -93,33 +99,56 @@ model ApiConnection { organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) organizationId String - attempts ApiConnectionAttempt[] - externalSources ExternalSource[] - externalServices ExternalService[] - + attempts ApiConnectionAttempt[] + connections ApiConnection[] jobConnections JobConnection[] - httpSources HttpSource[] @@unique([organizationId, slug]) } +model ApiConnection { + id String @id @default(cuid()) + + expiresAt DateTime? + metadata Json + + dataReference SecretReference @relation(fields: [dataReferenceId], references: [id], onDelete: Cascade, onUpdate: Cascade) + dataReferenceId String + + client ApiConnectionClient @relation(fields: [clientId], references: [id], onDelete: Cascade, onUpdate: Cascade) + clientId String + + organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) + organizationId String + + connectionType ApiConnectionType @default(DEVELOPER) + + externalAccount ExternalAccount? @relation(fields: [externalAccountId], references: [id], onDelete: Cascade, onUpdate: Cascade) + externalAccountId String? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + httpSources HttpSource[] +} + +enum ApiConnectionType { + EXTERNAL + DEVELOPER +} + model ApiConnectionAttempt { id String @id @default(cuid()) - title String - organizationId String - apiIdentifier String - authenticationMethodKey String - scopes String[] - securityCode String? + securityCode String? redirectTo String @default("/") - apiConnection ApiConnection? @relation(fields: [apiConnectionId], references: [id], onDelete: Cascade, onUpdate: Cascade) - apiConnectionId String? - createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt + + client ApiConnectionClient @relation(fields: [clientId], references: [id]) + clientId String } model OrgMember { @@ -164,22 +193,13 @@ model RuntimeEnvironment { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - events TriggerEvent[] - runs WorkflowRun[] - eventRules EventRule[] - schedulerSources SchedulerSource[] - internalSources InternalSource[] - deployments ProjectDeployment[] - keyValueItems KeyValueItem[] - currentEnvironments CurrentEnvironment[] - endpoints Endpoint[] - jobInstances JobInstance[] - eventLogs EventLog[] + jobVersions JobVersion[] + events EventRecord[] jobRuns JobRun[] httpSources HttpSource[] requestDeliveries HttpSourceRequestDelivery[] - jobEventRules JobEventRule[] + jobEventRules JobTrigger[] jobAliases JobAlias[] JobQueue JobQueue[] @@ -193,735 +213,6 @@ enum RuntimeEnvironmentType { PREVIEW } -model Workflow { - id String @id @default(cuid()) - slug String - title String - - packageJson Json? - jsonSchema Json? - metadata Json? - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) - organizationId String - - type TriggerType - status WorkflowStatus @default(CREATED) - - externalSource ExternalSource? @relation(fields: [externalSourceId], references: [id], onDelete: Cascade, onUpdate: Cascade) - externalSourceId String? - - runs WorkflowRun[] - rules EventRule[] - externalServices ExternalService[] - schedulerSources SchedulerSource[] - internalSources InternalSource[] - - service String @default("trigger") - eventNames String[] - - disabledAt DateTime? - archivedAt DateTime? - isArchived Boolean @default(false) - - triggerTtlInSeconds Int @default(3600) - - organizationTemplate OrganizationTemplate? @relation(fields: [organizationTemplateId], references: [id], onDelete: Cascade, onUpdate: Cascade) - organizationTemplateId String? - - repositoryProject RepositoryProject? @relation(fields: [repositoryProjectId], references: [id], onDelete: Cascade, onUpdate: Cascade) - repositoryProjectId String? - - currentEnvironments CurrentEnvironment[] - - @@unique([organizationId, slug]) -} - -model EventRule { - id String @id @default(cuid()) - - type TriggerType - - workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade, onUpdate: Cascade) - workflowId String - - organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) - organizationId String - - environment RuntimeEnvironment @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade) - environmentId String - - filter Json - trigger Json - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - enabled Boolean @default(true) - - runs WorkflowRun[] - - @@unique([workflowId, environmentId]) -} - -enum TriggerType { - WEBHOOK - SCHEDULE - CUSTOM_EVENT - HTTP_ENDPOINT - EVENT_BRIDGE - HTTP_POLLING - SLACK_INTERACTION -} - -enum WorkflowStatus { - CREATED - READY - DISABLED -} - -model ExternalSource { - id String @id @default(cuid()) - - organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) - organizationId String - - service String - - workflows Workflow[] - type ExternalSourceType - key String - source Json - status ExternalSourceStatus @default(CREATED) - externalData Json? - secret String? - manualRegistration Boolean @default(false) - - readyAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - connection ApiConnection? @relation(fields: [connectionId], references: [id], onDelete: SetNull) - connectionId String? - - @@unique([organizationId, key]) -} - -enum ExternalSourceStatus { - CREATED - READY - CANCELLED -} - -enum ExternalSourceType { - WEBHOOK - EVENT_BRIDGE - HTTP_POLLING -} - -model SchedulerSource { - id String @id @default(cuid()) - - organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) - organizationId String - - workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade, onUpdate: Cascade) - workflowId String - - environment RuntimeEnvironment @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade) - environmentId String - - schedule Json - - status SchedulerSourceStatus @default(CREATED) - - readyAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@unique([workflowId, environmentId]) -} - -enum SchedulerSourceStatus { - CREATED - READY - CANCELLED -} - -// We should eventually move SchedulerSource to this as it's more generic -model InternalSource { - id String @id @default(cuid()) - - organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) - organizationId String - - workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade, onUpdate: Cascade) - workflowId String - - environment RuntimeEnvironment @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade) - environmentId String - - type InternalSourceType - source Json - - status InternalSourceStatus @default(CREATED) - - readyAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@unique([workflowId, environmentId]) -} - -enum InternalSourceType { - SLACK -} - -enum InternalSourceStatus { - CREATED - READY - CANCELLED -} - -model ExternalService { - id String @id @default(cuid()) - slug String - service String - - workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade, onUpdate: Cascade) - workflowId String - - connection ApiConnection? @relation(fields: [connectionId], references: [id], onDelete: SetNull) - connectionId String? - - type ExternalServiceType - status ExternalServiceStatus @default(CREATED) - - readyAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - requests IntegrationRequest[] - - @@unique([workflowId, slug]) -} - -enum ExternalServiceType { - HTTP_API -} - -enum ExternalServiceStatus { - CREATED - READY -} - -model IntegrationRequest { - id String @id @default(cuid()) - - params Json - endpoint String - version String @default("1") - - externalService ExternalService @relation(fields: [externalServiceId], references: [id], onDelete: Cascade, onUpdate: Cascade) - externalServiceId String - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - status IntegrationRequestStatus @default(PENDING) - - run WorkflowRun @relation(fields: [runId], references: [id], onDelete: Cascade, onUpdate: Cascade) - runId String - - step WorkflowRunStep @relation(fields: [stepId], references: [id], onDelete: Cascade, onUpdate: Cascade) - stepId String @unique - - retryCount Int @default(0) - error Json? - response Json? - responses IntegrationResponse[] -} - -enum IntegrationRequestStatus { - PENDING - WAITING_FOR_CONNECTION - FETCHING - RETRYING - SUCCESS - ERROR -} - -model IntegrationResponse { - id String @id @default(cuid()) - - request IntegrationRequest @relation(fields: [requestId], references: [id], onDelete: Cascade, onUpdate: Cascade) - requestId String - - output Json - context Json - - createdAt DateTime @default(now()) -} - -model DurableDelay { - id String @id @default(cuid()) - - run WorkflowRun @relation(fields: [runId], references: [id], onDelete: Cascade, onUpdate: Cascade) - runId String - - step WorkflowRunStep @relation(fields: [stepId], references: [id], onDelete: Cascade, onUpdate: Cascade) - stepId String @unique - - delayUntil DateTime - - createdAt DateTime @default(now()) - resolvedAt DateTime? -} - -model TriggerEvent { - id String @id @default(cuid()) - service String - name String - type TriggerType - timestamp DateTime @default(now()) - payload Json - context Json? - - organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) - organizationId String? - - environment RuntimeEnvironment? @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade) - environmentId String? - - status TriggerEventStatus @default(PENDING) - WorkflowRun WorkflowRun[] - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - dispatchedAt DateTime? - - isTest Boolean @default(false) -} - -enum TriggerEventStatus { - PENDING - DISPATCHED -} - -model WorkflowRun { - id String @id @default(cuid()) - - workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade, onUpdate: Cascade) - workflowId String - - environment RuntimeEnvironment @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade) - environmentId String - - eventRule EventRule @relation(fields: [eventRuleId], references: [id], onDelete: Cascade, onUpdate: Cascade) - eventRuleId String - - tasks WorkflowRunStep[] - - event TriggerEvent @relation(fields: [eventId], references: [id], onDelete: Cascade, onUpdate: Cascade) - eventId String - - error Json? - - status WorkflowRunStatus @default(PENDING) - - attemptCount Int @default(0) - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - startedAt DateTime? - finishedAt DateTime? - - timedOutAt DateTime? - timedOutReason String? - - isTest Boolean @default(false) - requests IntegrationRequest[] - delays DurableDelay[] - fetchRequests FetchRequest[] -} - -enum WorkflowRunStatus { - PENDING - RUNNING - DISCONNECTED - SUCCESS - ERROR - TIMED_OUT -} - -model WorkflowRunStep { - id String @id @default(cuid()) - - run WorkflowRun @relation(fields: [runId], references: [id], onDelete: Cascade, onUpdate: Cascade) - runId String - - idempotencyKey String - ts String - - type WorkflowRunStepType - input Json? - output Json? - context Json - displayProperties Json? - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - startedAt DateTime? - finishedAt DateTime? - - status WorkflowRunStepStatus @default(PENDING) - - integrationRequest IntegrationRequest? - delay DurableDelay? - fetchRequest FetchRequest? - - @@unique([runId, idempotencyKey]) -} - -enum WorkflowRunStepStatus { - PENDING - RUNNING - SUCCESS - ERROR -} - -enum WorkflowRunStepType { - OUTPUT - LOG_MESSAGE - DURABLE_DELAY - CUSTOM_EVENT - INTEGRATION_REQUEST - DISCONNECTION - FETCH_REQUEST - RUN_ONCE - KV_GET - KV_SET - KV_DELETE -} - -model KeyValueItem { - id String @id @default(cuid()) - - key String - value Json - - environment RuntimeEnvironment @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade) - environmentId String - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@unique([environmentId, key]) -} - -model FetchRequest { - id String @id @default(cuid()) - - fetch Json - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - status FetchRequestStatus @default(PENDING) - - run WorkflowRun @relation(fields: [runId], references: [id], onDelete: Cascade, onUpdate: Cascade) - runId String - - step WorkflowRunStep @relation(fields: [stepId], references: [id], onDelete: Cascade, onUpdate: Cascade) - stepId String @unique - - retryCount Int @default(0) - error Json? - response Json? - responses FetchResponse[] -} - -enum FetchRequestStatus { - PENDING - FETCHING - RETRYING - SUCCESS - ERROR -} - -model FetchResponse { - id String @id @default(cuid()) - - request FetchRequest @relation(fields: [requestId], references: [id], onDelete: Cascade, onUpdate: Cascade) - requestId String - - output Json - context Json - - createdAt DateTime @default(now()) -} - -model GitHubAppAuthorizationAttempt { - id String @id @default(cuid()) - userId String - redirectTo String @default("/") - - authorization GitHubAppAuthorization? @relation(fields: [authorizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) - authorizationId String? - - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt -} - -model GitHubAppAuthorization { - id String @id @default(cuid()) - - user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade) - userId String - - accountType GitHubAccountType @default(USER) - accountName String - - installationId Int @unique - account Json - permissions Json - repositorySelection String - accessTokensUrl String - repositoriesUrl String - htmlUrl String - events String[] - - installationAccessToken String? - installationAccessTokenExpiresAt DateTime? - - organizationTemplates OrganizationTemplate[] - - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - GitHubAppAuthorizationAttempt GitHubAppAuthorizationAttempt[] - repositoryProjects RepositoryProject[] -} - -enum GitHubAccountType { - USER - ORGANIZATION -} - -model RepositoryProject { - id String @id @default(cuid()) - - name String @unique - url String - branch String @default("main") - - authorization GitHubAppAuthorization @relation(fields: [authorizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) - authorizationId String - - organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) - organizationId String - - buildCommand String - startCommand String - autoDeploy Boolean @default(true) - envVars Json - - status RepositoryProjectStatus @default(PENDING) - statusText String? - - currentVMIdentifier String? - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - deployments ProjectDeployment[] @relation("repoProject") - - latestCommit Json? - - currentDeployment ProjectDeployment? @relation(fields: [currentDeploymentId], references: [id], onDelete: Cascade, onUpdate: Cascade) - currentDeploymentId String? - - workflows Workflow[] -} - -enum RepositoryProjectStatus { - PENDING - PREPARING - BUILDING - DEPLOYING - DEPLOYED - ERROR - DISABLED -} - -model ProjectDeployment { - id String @id @default(cuid()) - - project RepositoryProject @relation("repoProject", fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade) - projectId String - - environment RuntimeEnvironment @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade) - environmentId String - - version String - - status ProjectDeploymentStatus @default(PENDING) - - buildId String @unique - imageId String? - vmIdentifier String? - buildStartedAt DateTime? - buildFinishedAt DateTime? - stoppedAt DateTime? - - dockerfile String - dockerIgnore String - - branch String - commitHash String - commitMessage String - committer String - - error Json? - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - projects RepositoryProject[] - - logs DeploymentLog[] - polls DeploymentLogPoll[] - - @@unique([projectId, version]) -} - -enum ProjectDeploymentStatus { - PENDING - BUILDING - DEPLOYING - DEPLOYED - CANCELLED - ERROR - STOPPING - STOPPED -} - -model DeploymentLog { - id String @id @default(cuid()) - - deployment ProjectDeployment @relation(fields: [deploymentId], references: [id], onDelete: Cascade, onUpdate: Cascade) - deploymentId String - - logType DeploymentLogType @default(BUILD) - logNumber Int @default(0) - - log String - level String - createdAt DateTime @default(now()) -} - -enum DeploymentLogType { - BUILD - MACHINE -} - -model DeploymentLogPoll { - id String @id @default(cuid()) - - deployment ProjectDeployment @relation(fields: [deploymentId], references: [id], onDelete: Cascade, onUpdate: Cascade) - deploymentId String - - logType DeploymentLogType @default(BUILD) - - from DateTime - to DateTime - - totalLogsCount Int - filteredLogsCount Int - - nextPollScheduledAt DateTime? - - pollNumber Int - - createdAt DateTime @default(now()) -} - -model Template { - id String @id @default(cuid()) - slug String @unique - title String - shortTitle String - description String - imageUrl String - repositoryUrl String - markdownDocs String @default("Documentation\n\nThis template does not have any documentation yet.") - runLocalDocs String @default("Documentation\n\nThis template does not have any documentation yet.") - - priority Int @default(0) - - services String[] - workflowIds String[] - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - isLive Boolean @default(true) - - organizationTemplates OrganizationTemplate[] -} - -model OrganizationTemplate { - id String @id @default(cuid()) - organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) - organizationId String - template Template @relation(fields: [templateId], references: [id], onDelete: Cascade, onUpdate: Cascade) - templateId String - - authorization GitHubAppAuthorization @relation(fields: [authorizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) - authorizationId String - - name String - repositoryId Int @unique - repositoryUrl String - private Boolean - - status OrganizationTemplateStatus @default(PENDING) - - repositoryData Json - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - workflows Workflow[] -} - -enum OrganizationTemplateStatus { - PENDING - CREATED - READY_TO_DEPLOY - DEPLOYED -} - -model CurrentEnvironment { - id String @id @default(cuid()) - workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade, onUpdate: Cascade) - workflowId String - - environment RuntimeEnvironment @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade) - environmentId String - - user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade) - userId String - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@unique([workflowId, userId]) -} - -// ========= SERVERLESS =========== model Project { id String @id @default(cuid()) name String @@ -935,11 +226,11 @@ model Project { environments RuntimeEnvironment[] endpoints Endpoint[] jobs Job[] - jobInstances JobInstance[] - events EventLog[] + jobVersion JobVersion[] + events EventRecord[] runs JobRun[] httpSources HttpSource[] - eventRules JobEventRule[] + triggers JobTrigger[] } model Endpoint { @@ -959,7 +250,7 @@ model Endpoint { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - jobInstances JobInstance[] + jobVersions JobVersion[] jobRuns JobRun[] httpSources HttpSource[] HttpSourceRequestDelivery HttpSourceRequestDelivery[] @@ -979,19 +270,19 @@ model Job { project Project @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade) projectId String - instances JobInstance[] + versions JobVersion[] runs JobRun[] connections JobConnection[] - eventRules JobEventRule[] + triggers JobTrigger[] aliases JobAlias[] @@unique([projectId, slug]) } -model JobInstance { - id String @id @default(cuid()) - version String - trigger Json +model JobVersion { + id String @id @default(cuid()) + version String + eventSpecification Json job Job @relation(fields: [jobId], references: [id], onDelete: Cascade, onUpdate: Cascade) jobId String @@ -1019,7 +310,7 @@ model JobInstance { runs JobRun[] connections JobConnection[] - eventRules JobEventRule[] + triggers JobTrigger[] aliases JobAlias[] @@unique([jobId, version, endpointId]) @@ -1038,19 +329,19 @@ model JobQueue { jobCount Int @default(0) maxJobs Int @default(100) - runs JobRun[] - instances JobInstance[] + runs JobRun[] + jobVersion JobVersion[] @@unique([environmentId, name]) } model JobAlias { - id String @id @default(cuid()) - name String @default("latest") - version String + id String @id @default(cuid()) + name String @default("latest") + value String - jobInstance JobInstance @relation(fields: [jobInstanceId], references: [id], onDelete: Cascade, onUpdate: Cascade) - jobInstanceId String + version JobVersion @relation(fields: [versionId], references: [id], onDelete: Cascade, onUpdate: Cascade) + versionId String job Job @relation(fields: [jobId], references: [id], onDelete: Cascade, onUpdate: Cascade) jobId String @@ -1065,33 +356,31 @@ model JobConnection { id String @id @default(cuid()) key String - jobInstance JobInstance @relation(fields: [jobInstanceId], references: [id], onDelete: Cascade, onUpdate: Cascade) - jobInstanceId String + version JobVersion @relation(fields: [versionId], references: [id], onDelete: Cascade, onUpdate: Cascade) + versionId String job Job @relation(fields: [jobId], references: [id], onDelete: Cascade, onUpdate: Cascade) jobId String connectionMetadata Json - usesLocalAuth Boolean @default(false) - - apiConnection ApiConnection? @relation(fields: [apiConnectionId], references: [id], onDelete: Cascade, onUpdate: Cascade) - apiConnectionId String? + apiConnectionClient ApiConnectionClient @relation(fields: [apiConnectionClientId], references: [id], onDelete: Cascade, onUpdate: Cascade) + apiConnectionClientId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - @@unique([jobInstanceId, key]) + @@unique([versionId, key]) } -model JobEventRule { +model JobTrigger { id String @id @default(cuid()) event String source String payloadFilter Json? contextFilter Json? - action JobEventAction @default(CREATE_RUN) + action JobTriggerAction @default(CREATE_RUN) actionIdentifier String createdAt DateTime @default(now()) @@ -1102,8 +391,8 @@ model JobEventRule { job Job @relation(fields: [jobId], references: [id], onDelete: Cascade, onUpdate: Cascade) jobId String - jobInstance JobInstance @relation(fields: [jobInstanceId], references: [id], onDelete: Cascade, onUpdate: Cascade) - jobInstanceId String + version JobVersion @relation(fields: [versionId], references: [id], onDelete: Cascade, onUpdate: Cascade) + versionId String organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) organizationId String @@ -1114,15 +403,18 @@ model JobEventRule { project Project @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade) projectId String - @@unique([jobInstanceId, actionIdentifier]) + externalAccount ExternalAccount? @relation(fields: [externalAccountId], references: [id], onDelete: Cascade, onUpdate: Cascade) + externalAccountId String? + + @@unique([versionId, actionIdentifier]) } -enum JobEventAction { +enum JobTriggerAction { CREATE_RUN RESUME_TASK } -model EventLog { +model EventRecord { id String @id @default(cuid()) name String timestamp DateTime @default(now()) @@ -1156,11 +448,11 @@ model JobRun { job Job @relation(fields: [jobId], references: [id], onDelete: Cascade, onUpdate: Cascade) jobId String - jobInstance JobInstance @relation(fields: [jobInstanceId], references: [id], onDelete: Cascade, onUpdate: Cascade) - jobInstanceId String + version JobVersion @relation(fields: [versionId], references: [id], onDelete: Cascade, onUpdate: Cascade) + versionId String - eventLog EventLog @relation(fields: [eventLogId], references: [id], onDelete: Cascade, onUpdate: Cascade) - eventLogId String + event EventRecord @relation(fields: [eventId], references: [id], onDelete: Cascade, onUpdate: Cascade) + eventId String organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade) organizationId String @@ -1253,7 +545,8 @@ model SecretReference { key String @unique provider String - apiConnection ApiConnection[] + apiConnections ApiConnection[] + apiConnectionClients ApiConnectionClient[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/apps/webapp/prisma/seed.ts b/apps/webapp/prisma/seed.ts index 8c4a4615f..3f31715cd 100644 --- a/apps/webapp/prisma/seed.ts +++ b/apps/webapp/prisma/seed.ts @@ -1,8 +1,248 @@ +/* eslint-disable turbo/no-undeclared-env-vars */ import { PrismaClient } from ".prisma/client"; const prisma = new PrismaClient(); -async function seed() {} +const GITHUB_CONNECTION_KEY = "github-seed-key"; +const SLACK_CONNECTION_KEY = "slack-seed-key"; + +async function seed() { + // Create a user, organization, and project + const user = await prisma.user.upsert({ + where: { + email: "eric@trigger.dev", + }, + create: { + email: "eric@trigger.dev", + name: "Eric", + authenticationMethod: "MAGIC_LINK", + }, + update: {}, + }); + + const organization = await prisma.organization.upsert({ + where: { + slug: "seed-org-123", + }, + create: { + title: "Personal Workspace", + slug: "seed-org-123", + members: { + create: { + userId: user.id, + role: "ADMIN", + }, + }, + projects: { + create: { + name: "My Project", + }, + }, + }, + update: {}, + include: { + members: true, + projects: true, + }, + }); + + const adminMember = organization.members[0]; + const defaultProject = organization.projects[0]; + + await prisma.runtimeEnvironment.upsert({ + where: { + apiKey: "tr_dev_bNaLxayOXqoj", + }, + create: { + apiKey: "tr_dev_bNaLxayOXqoj", + slug: "dev", + type: "DEVELOPMENT", + project: { + connect: { + id: defaultProject.id, + }, + }, + organization: { + connect: { + id: organization.id, + }, + }, + orgMember: { + connect: { + id: adminMember.id, + }, + }, + }, + update: {}, + }); + + await prisma.runtimeEnvironment.upsert({ + where: { + apiKey: "tr_prod_bNaLxayOXqoj", + }, + create: { + apiKey: "tr_prod_bNaLxayOXqoj", + slug: "prod", + type: "PRODUCTION", + project: { + connect: { + id: defaultProject.id, + }, + }, + organization: { + connect: { + id: organization.id, + }, + }, + }, + update: {}, + }); + + // Now we need to create a couple of ApiConnectionClients + const slackClient = await prisma.apiConnectionClient.upsert({ + where: { + organizationId_slug: { + organizationId: organization.id, + slug: "my-slack-new", + }, + }, + create: { + organizationId: organization.id, + slug: "my-slack-new", + schema: {}, + title: "My Slack", + scopes: ["chat:write"], + }, + update: {}, + }); + + const githubClient = await prisma.apiConnectionClient.upsert({ + where: { + organizationId_slug: { + organizationId: organization.id, + slug: "github", + }, + }, + create: { + organizationId: organization.id, + slug: "github", + schema: {}, + title: "GitHub", + scopes: ["admin:repo_hook", "public_repo"], + }, + update: {}, + }); + + await prisma.apiConnection.upsert({ + where: { + id: "clhkhsvx20000rmdy9u9d25e7", + }, + create: { + metadata: { id: "github" }, + client: { + connect: { + id: githubClient.id, + }, + }, + organization: { + connect: { + id: organization.id, + }, + }, + connectionType: "DEVELOPER", + dataReference: { + create: { + key: GITHUB_CONNECTION_KEY, + provider: "database", + }, + }, + }, + update: {}, + }); + + await prisma.apiConnection.upsert({ + where: { + id: "clhkigzf90000rmdyfuiec6ew", + }, + create: { + metadata: { id: "slack" }, + client: { + connect: { + id: slackClient.id, + }, + }, + organization: { + connect: { + id: organization.id, + }, + }, + connectionType: "DEVELOPER", + dataReference: { + create: { + key: SLACK_CONNECTION_KEY, + provider: "database", + }, + }, + }, + update: {}, + }); + + await prisma.secretStore.upsert({ + where: { + key: GITHUB_CONNECTION_KEY, + }, + create: { + key: GITHUB_CONNECTION_KEY, + value: { + raw: { + scope: "admin:repo_hook,public_repo", + token_type: "bearer", + access_token: process.env.SEED_GITHUB_ACCESS_TOKEN, + }, + type: "oauth2", + scopes: ["admin:repo_hook,public_repo"], + accessToken: process.env.SEED_GITHUB_ACCESS_TOKEN, + }, + }, + update: {}, + }); + + await prisma.secretStore.upsert({ + where: { + key: SLACK_CONNECTION_KEY, + }, + create: { + key: SLACK_CONNECTION_KEY, + value: { + raw: { + ok: true, + team: { id: "T84AW8RBP", name: "Trigger.dev" }, + scope: + "chat:write,channels:read,channels:manage,im:write,channels:join,chat:write.customize,bookmarks:read", + app_id: "A04H149K884", + enterprise: null, + token_type: "bot", + authed_user: { id: "U8590FPB9" }, + bot_user_id: "U04H0UUQPHR", + access_token: process.env.SEED_SLACK_ACCESS_TOKEN, + is_enterprise_install: false, + }, + type: "oauth2", + scopes: [ + "chat:write", + "channels:read", + "channels:manage", + "im:write", + "channels:join", + "chat:write.customize", + "bookmarks:read", + ], + accessToken: process.env.SEED_SLACK_ACCESS_TOKEN, + }, + }, + update: {}, + }); +} seed() .catch((e) => { diff --git a/examples/nextjs-example/src/pages/api/trigger.ts b/examples/nextjs-example/src/pages/api/trigger.ts index 3c9742558..339c7387d 100644 --- a/examples/nextjs-example/src/pages/api/trigger.ts +++ b/examples/nextjs-example/src/pages/api/trigger.ts @@ -1,10 +1,13 @@ import { + comboTrigger, customEvent, + customTrigger, + DynamicTrigger, Job, NormalizedRequest, TriggerClient, } from "@trigger.dev/sdk"; -import { github } from "@trigger.dev/github"; +import { github, events } from "@trigger.dev/github"; import { slack as slackConnection } from "@trigger.dev/slack"; import type { NextApiRequest, NextApiResponse } from "next"; import { z } from "zod"; @@ -19,11 +22,25 @@ const client = new TriggerClient("nextjs", { logLevel: "debug", }); -// const dynamicOnIssueOpenedTrigger = new DynamicTrigger(client, { -// id: "github-issue-opened", -// event: events.onIssueOpened, -// connection: gh, -// }); +const dynamicOnIssueOpenedTrigger = new DynamicTrigger(client, { + id: "github-issue-opened", + event: events.onIssueOpened, + source: gh.sources.repo, +}); + +const dynamicOnIssueOpenedTriggerOrg = new DynamicTrigger(client, { + id: "github-issue-opened-org", + event: events.onIssueOpened, + source: gh.sources.org, +}); + +dynamicOnIssueOpenedTrigger.register({ + repo: "ericallam/basic-starter-100k", +}); + +dynamicOnIssueOpenedTriggerOrg.register({ + org: "triggerdotdev", +}); new Job(client, { id: "alert-on-new-github-issues", @@ -32,7 +49,8 @@ new Job(client, { connections: { slack, }, - trigger: gh.triggers.onIssueOpened({ + trigger: gh.triggers.repo({ + event: events.onIssueOpened, repo: "ericallam/basic-starter-100k", }), run: async (event, io, ctx) => { @@ -44,12 +62,10 @@ new Job(client, { }); new Job(client, { - id: "alert-on-new-github-issues-2", - name: "Alert on new GitHub issues 2", + id: "alert-on-new-github-issues-dynamic", + name: "Alert on new GitHub issues Dynamic", version: "0.1.1", - trigger: gh.triggers.onIssueOpened({ - repo: "ericallam/basic-starter-100k", - }), + trigger: dynamicOnIssueOpenedTrigger, run: async (event, io, ctx) => {}, }); @@ -57,12 +73,88 @@ new Job(client, { id: "alert-on-new-github-stars", name: "Alert on new GitHub stars", version: "0.1.1", - trigger: gh.triggers.onStar({ + trigger: gh.triggers.repo({ + event: events.onNewStar, repo: "ericallam/basic-starter-100k", }), run: async (event, io, ctx) => {}, }); +new Job(client, { + id: "alert-on-new-github-stars-in-org", + name: "Alert on new GitHub stars in Org", + version: "0.1.1", + trigger: gh.triggers.org({ + event: events.onNewStar, + org: "triggerdotdev", + }), + run: async (event, io, ctx) => {}, +}); + +new Job(client, { + id: "alert-on-new-github-stars-in-org", + name: "Alert on new GitHub stars in Org", + version: "0.1.1", + trigger: comboTrigger({ + event: events.onNewStar, + triggers: [ + gh.triggers.org({ + event: events.onNewStar, + org: "triggerdotdev", + }), + gh.triggers.org({ + event: events.onNewStar, + org: "jsonheroio", + }), + ], + }), + run: async (event, io, ctx) => {}, +}); + +new Job(client, { + id: "custom-event-example", + name: "Custom Event Example", + version: "0.1.1", + trigger: customTrigger({ + name: "my.custom.trigger", + event: customEvent({ schema: z.object({ id: z.string() }) }), + }), + run: async (event, io, ctx) => {}, +}); + +new Job(client, { + id: "custom-github-event-example", + name: "Custom Github Event Example", + version: "0.1.1", + trigger: customTrigger({ + name: "my.custom.trigger", + event: events.onNewStar, + }), + run: async (event, io, ctx) => {}, +}); + +// new Job(client, { +// id: "alert-on-new-github-stars", +// name: "Alert on new GitHub stars", +// version: "0.1.1", +// trigger: customTrigger({ +// name: "my.custom.trigger", +// event: events.onNewStar, +// }), +// run: async (event, io, ctx) => {}, +// }); + +// new Job(client, { +// id: "alert-on-new-github-stars", +// name: "Alert on new GitHub stars", +// version: "0.1.1", +// trigger: customTrigger({ +// name: "other.custom.trigger", +// event: eventFromZodSchema(z.object({ id: z.string() })), +// }), +// run: async (event, io, ctx) => {}, +// }); + // const notifySlackONNewCommentsJob = new Job({ // id: "notify-slack-on-new-comments", // name: "Notify Slack on new GitHub comments", diff --git a/integrations/github/src/index.ts b/integrations/github/src/index.ts index c8d48f835..0d1a36759 100644 --- a/integrations/github/src/index.ts +++ b/integrations/github/src/index.ts @@ -1,18 +1,18 @@ import { - IssueCommentEvent, IssuesEvent, IssuesOpenedEvent, + StarCreatedEvent, StarEvent, } from "@octokit/webhooks-types"; import { Connection, - EventFilter, - ExternalSourceEventTrigger, + EventSpecification, + ExternalSourceTrigger, } from "@trigger.dev/sdk"; import { Octokit } from "octokit"; import { clientFactory } from "./clientFactory"; import { metadata } from "./metadata"; -import { repositoryWebhookSource } from "./sources"; +import { createOrgEventSource, createRepoEventSource } from "./sources"; import { tasks } from "./tasks"; export type GitHubConnectionOptions = @@ -26,9 +26,21 @@ export type GitHubConnectionOptions = export const github = (options: GitHubConnectionOptions) => { const connection = createConnectionFromOptions(options); + const repoSource = createRepoEventSource(connection); + const orgSource = createOrgEventSource(connection); + const repoTrigger = createRepoTrigger(repoSource); + const orgTrigger = createOrgTrigger(orgSource); + return { ...connection, - triggers: createTriggers(connection), + sources: { + repo: repoSource, + org: orgSource, + }, + triggers: { + repo: repoTrigger, + org: orgTrigger, + }, }; }; @@ -57,62 +69,92 @@ function createConnectionFromOptions( }; } -function createTriggers(connection: Connection) { - return { - onIssue: buildRepoWebhookTrigger( - "On Issue", - "issues", - connection - ), - onIssueOpened: buildRepoWebhookTrigger( - "On Issue Opened", - "issues", - connection, - { - action: ["opened"], - } - ), - onStar: buildRepoWebhookTrigger("On Star", "star", connection), - }; -} +const onIssueOpened: EventSpecification = { + name: "issues", + title: "On issue opened", + source: "github.com", + filter: { + action: ["opened"], + }, + parsePayload: (payload) => payload as IssuesOpenedEvent, +}; -function buildRepoWebhookTrigger( - title: string, - event: string, - connection: Connection, - filter?: EventFilter -) { - return (params: { repo: string }) => - new ExternalSourceEventTrigger({ - title, - elements: [ - { - label: "Repo", - text: params.repo, - }, - { - label: "Event", - text: event, - }, - ], - source: repositoryWebhookSource( - { - repo: params.repo, - events: [event], - }, - connection, - (payload) => payload as TEvent - ), - eventRule: { - event, - source: "github.com", +const onIssue: EventSpecification = { + name: "issues", + title: "On issue", + source: "github.com", + parsePayload: (payload) => payload as IssuesEvent, +}; + +const onStar: EventSpecification = { + name: "star", + title: "On star", + source: "github.com", + parsePayload: (payload) => payload as StarEvent, +}; + +const onNewStar: EventSpecification = { + name: "star", + title: "On new star", + source: "github.com", + filter: { + action: ["created"], + }, + parsePayload: (payload) => payload as StarCreatedEvent, +}; + +export const events = { + onIssueOpened, + onIssue, + onStar, + onNewStar, +}; + +// params.event has to be a union of all the values of the exports events object +type GitHubEvents = (typeof events)[keyof typeof events]; + +function createRepoTrigger(source: ReturnType) { + return ({ + event, + repo, + }: { + event: TEventSpecification; + repo: string; + }) => { + return new ExternalSourceTrigger({ + event, + params: { repo }, + source, + filter: { payload: { - ...(filter ?? {}), repository: { - ...filter?.repository, - full_name: [params.repo], + full_name: [repo], }, }, }, }); + }; +} + +function createOrgTrigger(source: ReturnType) { + return ({ + event, + org, + }: { + event: TEventSpecification; + org: string; + }) => { + return new ExternalSourceTrigger({ + event, + params: { org }, + source, + filter: { + payload: { + organization: { + login: [org], + }, + }, + }, + }); + }; } diff --git a/integrations/github/src/sources.ts b/integrations/github/src/sources.ts index bccb222bf..06b91f8be 100644 --- a/integrations/github/src/sources.ts +++ b/integrations/github/src/sources.ts @@ -1,6 +1,7 @@ import { Webhooks } from "@octokit/webhooks"; import { Connection, ExternalSource } from "@trigger.dev/sdk"; import { Octokit } from "octokit"; +import { z } from "zod"; import { tasks } from "./tasks"; type WebhookData = { @@ -21,22 +22,15 @@ function webhookData(data: any): data is WebhookData { ); } -export function repositoryWebhookSource( - params: { - repo: string; - events: string[]; - secret?: string; - }, - connection: Connection, - parsePayload: (payload: any) => TEventType +export function createRepoEventSource( + connection: Connection ) { - // Create a stable key for this source so we only register it once - const key = `github.repo.${params.repo}.webhook`; - - return new ExternalSource("http", key, "0.1.1", { - parsePayload, + return new ExternalSource("http", "0.1.1", { + schema: z.object({ repo: z.string() }), connection, - register: async (io, ctx) => { + register: async (params, spec, io, ctx) => { + const key = `github.repo.${params.repo}.webhook`; + const httpSource = await io.registerHttpSource("register-http-source", { key, }); @@ -48,7 +42,7 @@ export function repositoryWebhookSource( ) { const existingData = httpSource.data; - const sourceEvents = new Set(params.events); + const sourceEvents = new Set([spec.name]); const existingEvents = new Set(existingData.events); const missingEvents = Array.from( @@ -87,7 +81,7 @@ export function repositoryWebhookSource( (w) => w.config.url === httpSource.url ); - const secret = params.secret || Math.random().toString(36).slice(2); + const secret = Math.random().toString(36).slice(2); if (existingWebhook && existingWebhook.active) { await io.client.updateWebhook("update-webhook", { @@ -109,7 +103,158 @@ export function repositoryWebhookSource( const webhook = await io.client.createWebhook("create-webhook", { repo: params.repo, - events: params.events, + events: [spec.name], + url: httpSource.url, + secret, + }); + + await io.updateHttpSource("update-http-source", { + id: httpSource.id, + secret, + data: webhook, + active: true, + }); + }, + handler: async ({ rawEvent: request, source }, io, ctx) => { + if (!request.rawBody) { + return { events: [] }; + } + + const deliveryId = request.headers["x-github-delivery"]; + const hookId = request.headers["x-github-hook-id"]; + const signature = request.headers["x-hub-signature-256"]; + + if (source.secret && signature) { + const githubWebhooks = new Webhooks({ + secret: source.secret, + }); + + if (!githubWebhooks.verify(request.rawBody, signature)) { + return { + events: [], + }; + } + } + + const name = request.headers["x-github-event"]; + + const context = omit(request.headers, [ + "x-github-event", + "x-github-delivery", + "x-hub-signature-256", + "x-hub-signature", + "content-type", + "content-length", + "accept", + "accept-encoding", + "x-forwarded-proto", + ]); + + const payload = parseBody(request.rawBody); + + if (!payload) { + return { + events: [], + }; + } + + return { + events: [ + { + id: [hookId, deliveryId].join(":"), + source: "github.com", + payload, + name, + context, + }, + ], + }; + }, + }); +} + +export function createOrgEventSource( + connection: Connection +) { + return new ExternalSource("http", "0.1.1", { + schema: z.object({ org: z.string() }), + connection, + register: async (params, spec, io, ctx) => { + const key = `github.org.${params.org}.webhook`; + + const httpSource = await io.registerHttpSource("register-http-source", { + key, + }); + + if ( + httpSource.active && + webhookData(httpSource.data) && + httpSource.secret + ) { + const existingData = httpSource.data; + + const sourceEvents = new Set([spec.name]); + const existingEvents = new Set(existingData.events); + + const missingEvents = Array.from( + new Set( + Array.from(sourceEvents).filter((x) => !existingEvents.has(x)) + ) + ); + + if (missingEvents.length > 0) { + // We need to update the webhook to add the new events and then return + const newWebhookData = await io.client.updateOrgWebhook( + "update-webhook", + { + org: params.org, + hookId: existingData.id, + url: httpSource.url, + secret: httpSource.secret, + addEvents: missingEvents, + } + ); + + await io.updateHttpSource("update-http-source", { + id: httpSource.id, + data: newWebhookData, + }); + } + + return; + } + + const webhooks = await io.client.listOrgWebhooks("list-webhooks", { + org: params.org, + }); + + const existingWebhook = webhooks.find( + (w) => w.config.url === httpSource.url + ); + + const secret = Math.random().toString(36).slice(2); + + if (existingWebhook && existingWebhook.active) { + await io.client.updateOrgWebhook("update-webhook", { + org: params.org, + hookId: existingWebhook.id, + url: httpSource.url, + secret, + }); + + await io.updateHttpSource("update-http-source", { + id: httpSource.id, + secret, + data: existingWebhook, + active: true, + }); + + return; + } + + const webhook = await io.client.createOrgWebhook("create-webhook", { + org: params.org, + events: [spec.name], url: httpSource.url, secret, }); diff --git a/integrations/github/src/tasks.ts b/integrations/github/src/tasks.ts index 9177019f3..8cc7c1a44 100644 --- a/integrations/github/src/tasks.ts +++ b/integrations/github/src/tasks.ts @@ -285,6 +285,49 @@ export const updateWebhook = authenticatedTask({ }, }); +export const updateOrgWebhook = authenticatedTask({ + run: async ( + params: { + org: string; + hookId: number; + url: string; + secret: string; + addEvents?: string[]; + }, + client: InstanceType, + task + ) => { + return client.rest.orgs + .updateWebhook({ + org: params.org, + hook_id: params.hookId, + config: { + content_type: "json", + url: params.url, + secret: params.secret, + }, + add_events: params.addEvents, + }) + .then((response) => response.data); + }, + init: (params) => { + return { + name: "Update Org Webhook", + params, + elements: [ + { + label: "Org", + text: params.org, + }, + { + label: "Hook ID", + text: String(params.hookId), + }, + ], + }; + }, +}); + export const createWebhook = authenticatedTask({ run: async ( params: { @@ -329,6 +372,48 @@ export const createWebhook = authenticatedTask({ }, }); +export const createOrgWebhook = authenticatedTask({ + run: async ( + params: { + org: string; + url: string; + secret: string; + events: string[]; + }, + client: InstanceType, + task + ) => { + return client.rest.orgs + .createWebhook({ + org: params.org, + name: "web", + config: { + content_type: "json", + url: params.url, + secret: params.secret, + }, + events: params.events, + }) + .then((response) => response.data); + }, + init: (params) => { + return { + name: "Create Org Webhook", + params, + elements: [ + { + label: "Org", + text: params.org, + }, + { + label: "Events", + text: params.events.join(", "), + }, + ], + }; + }, +}); + export const listWebhooks = authenticatedTask({ run: async ( params: { @@ -360,6 +445,34 @@ export const listWebhooks = authenticatedTask({ }, }); +export const listOrgWebhooks = authenticatedTask({ + run: async ( + params: { + org: string; + }, + client: InstanceType, + task + ) => { + return client.rest.orgs + .listWebhooks({ + org: params.org, + }) + .then((response) => response.data); + }, + init: (params) => { + return { + name: "List Org Webhooks", + params, + elements: [ + { + label: "Org", + text: params.org, + }, + ], + }; + }, +}); + export const tasks = { createIssue, createIssueComment, @@ -369,4 +482,7 @@ export const tasks = { updateWebhook, createWebhook, listWebhooks, + updateOrgWebhook, + createOrgWebhook, + listOrgWebhooks, }; diff --git a/package.json b/package.json index 020fe6408..812988c6b 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "db:migrate:dev": "turbo run db:migrate:dev", "db:push": "turbo run db:push", "db:seed": "turbo run db:seed --no-cache", + "i-db:seed": "infisical run -- turbo run db:seed --no-cache", "db:migrate:force": "turbo run db:migrate:force --no-cache", "dev": "turbo run dev --parallel", "i-dev": "infisical run -- turbo run dev --parallel", @@ -66,4 +67,4 @@ "@changesets/cli": "^2.26.0", "node-fetch": "2.6.x" } -} +} \ No newline at end of file diff --git a/packages/internal/src/index.ts b/packages/internal/src/index.ts index 3d72e0686..086b60e41 100644 --- a/packages/internal/src/index.ts +++ b/packages/internal/src/index.ts @@ -1,3 +1,4 @@ export * from "./logger"; export * from "./schemas"; export * from "./types"; +export * from "./utils"; diff --git a/packages/internal/src/schemas/api.ts b/packages/internal/src/schemas/api.ts index 75f0cc6ec..ac573d9c4 100644 --- a/packages/internal/src/schemas/api.ts +++ b/packages/internal/src/schemas/api.ts @@ -4,7 +4,11 @@ import { ConnectionAuthSchema, ConnectionConfigSchema } from "./connections"; import { DisplayElementSchema } from "./elements"; import { DeserializedJsonSchema, SerializableJsonSchema } from "./json"; import { CachedTaskSchema, ServerTaskSchema, TaskSchema } from "./tasks"; -import { TriggerMetadataSchema } from "./triggers"; +import { + DynamicTriggerMetadataSchema, + EventSpecificationSchema, + TriggerMetadataSchema, +} from "./triggers"; export const RegisterHttpEventSourceBodySchema = z.object({ key: z.string(), @@ -78,26 +82,28 @@ export const QueueOptionsSchema = z.object({ export type QueueOptions = z.infer; -export const JobSchema = z.object({ +export const JobMetadataSchema = z.object({ id: z.string(), name: z.string(), version: z.string(), - trigger: TriggerMetadataSchema, + event: EventSpecificationSchema, + triggers: z.array(TriggerMetadataSchema), connections: z.record(ConnectionConfigSchema), internal: z.boolean().default(false), queue: z.union([QueueOptionsSchema, z.string()]).optional(), }); -export type JobMetadata = z.infer; +export type JobMetadata = z.infer; -export const GetJobResponseSchema = JobSchema; - -export type GetJobResponse = z.infer; - -export const GetJobsResponseSchema = z.object({ - jobs: z.array(GetJobResponseSchema), +export const GetEndpointDataResponseSchema = z.object({ + jobs: z.array(JobMetadataSchema), + dynamicTriggers: z.array(DynamicTriggerMetadataSchema), }); +export type GetEndpointDataResponse = z.infer< + typeof GetEndpointDataResponseSchema +>; + export const RawEventSchema = z.object({ id: z.string().default(() => ulid()), name: z.string(), @@ -172,7 +178,7 @@ export type RunJobResponse = z.infer; export const CreateRunBodySchema = z.object({ client: z.string(), - job: JobSchema, + job: JobMetadataSchema, event: ApiEventLogSchema, elements: z.array(DisplayElementSchema).optional(), }); diff --git a/packages/internal/src/schemas/connections.ts b/packages/internal/src/schemas/connections.ts index 33ec6b7f4..496405f62 100644 --- a/packages/internal/src/schemas/connections.ts +++ b/packages/internal/src/schemas/connections.ts @@ -17,28 +17,9 @@ export const ConnectionAuthSchema = z.object({ export type ConnectionAuth = z.infer; -const CommonConnectionConfigSchema = z.object({ +export const ConnectionConfigSchema = z.object({ + id: z.string(), metadata: ConnectionMetadataSchema, }); -const LocalAuthConnectionConfigSchema = CommonConnectionConfigSchema.extend({ - auth: z.literal("local"), -}); - -const HostedAuthConnectionConfigSchema = CommonConnectionConfigSchema.extend({ - auth: z.literal("hosted"), - id: z.string(), -}); - -export const ConnectionConfigSchema = z.discriminatedUnion("auth", [ - LocalAuthConnectionConfigSchema, - HostedAuthConnectionConfigSchema, -]); - export type ConnectionConfig = z.infer; -export type LocalAuthConnectionConfig = z.infer< - typeof LocalAuthConnectionConfigSchema ->; -export type HostedAuthConnectionConfig = z.infer< - typeof HostedAuthConnectionConfigSchema ->; diff --git a/packages/internal/src/schemas/triggers.ts b/packages/internal/src/schemas/triggers.ts index 0024b6e60..f34e36945 100644 --- a/packages/internal/src/schemas/triggers.ts +++ b/packages/internal/src/schemas/triggers.ts @@ -1,25 +1,32 @@ import { z } from "zod"; -import { EventRuleSchema } from "./eventFilter"; -import { DeserializedJsonSchema } from "./json"; +import { EventFilterSchema, EventRuleSchema } from "./eventFilter"; +import { DisplayElementSchema } from "./elements"; -export const TriggerMetadataSchema = z.object({ +export const EventSpecificationSchema = z.object({ + name: z.string(), title: z.string(), - elements: z.array( - z.object({ - label: z.string(), - text: z.string(), - url: z.string().optional(), - }) - ), - eventRule: EventRuleSchema, - schema: DeserializedJsonSchema.optional(), + source: z.string(), + filter: EventFilterSchema.optional(), + elements: z.array(DisplayElementSchema).optional(), + schema: z.any().optional(), + examples: z.array(z.any()).optional(), }); +export const DynamicTriggerMetadataSchema = z.object({ + type: z.literal("dynamic"), + id: z.string(), +}); + +export const StaticTriggerMetadataSchema = z.object({ + type: z.literal("static"), + title: z.string(), + elements: z.array(DisplayElementSchema).optional(), + rule: EventRuleSchema, +}); + +export const TriggerMetadataSchema = z.discriminatedUnion("type", [ + DynamicTriggerMetadataSchema, + StaticTriggerMetadataSchema, +]); + export type TriggerMetadata = z.infer; - -export const TriggerVariantConfigSchema = z.object({ - id: z.string(), - trigger: TriggerMetadataSchema, -}); - -export type TriggerVariantConfig = z.infer; diff --git a/packages/internal/src/utils.ts b/packages/internal/src/utils.ts new file mode 100644 index 000000000..a258a33a8 --- /dev/null +++ b/packages/internal/src/utils.ts @@ -0,0 +1,39 @@ +// EventFilter is typed as type EventFilter = { [key: string]: EventFilter | string[] | number[] | boolean[] } + +import { EventFilter } from "./schemas"; + +// This function should take two EventFilters and return a new EventFilter that is the result of merging the two. +export function deepMergeFilters( + filter: EventFilter, + other: EventFilter +): EventFilter { + const result: EventFilter = { ...filter }; + + for (const key in other) { + if (other.hasOwnProperty(key)) { + const otherValue = other[key]; + + if ( + typeof otherValue === "object" && + !Array.isArray(otherValue) && + otherValue !== null + ) { + const filterValue = filter[key]; + + if ( + filterValue && + typeof filterValue === "object" && + !Array.isArray(filterValue) + ) { + result[key] = deepMergeFilters(filterValue, otherValue); + } else { + result[key] = { ...other[key] }; + } + } else { + result[key] = other[key]; + } + } + } + + return result; +} diff --git a/packages/trigger-sdk/src/apiClient.ts b/packages/trigger-sdk/src/apiClient.ts index d97e3234c..1dfe3e971 100644 --- a/packages/trigger-sdk/src/apiClient.ts +++ b/packages/trigger-sdk/src/apiClient.ts @@ -12,9 +12,6 @@ import { SendEvent, SendEventOptions, ServerTask, - TriggerVariantResponseBody, - TriggerVariantConfig, - TriggerVariantResponseBodySchema, UpdateHttpEventSourceBody, } from "@trigger.dev/internal"; @@ -267,56 +264,6 @@ export class ApiClient { return await response.json(); } - async addTriggerVariant( - client: string, - jobId: string, - jobVersion: string, - config: TriggerVariantConfig - ): Promise { - const apiKey = await this.#apiKey(); - - this.#logger.debug("Adding Trigger Variant", { - client, - jobId, - jobVersion, - config, - }); - - const response = await fetch( - `${this.#apiUrl}/api/v3/${client}/jobs/${jobId}/${jobVersion}/variants`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${apiKey}`, - }, - body: JSON.stringify(config), - } - ); - - if (response.status === 404) { - throw new Error( - `Failed to add trigger variant, got status code ${response.status}` - ); - } - - if (response.status >= 400 && response.status < 500) { - const body = await response.json(); - - throw new Error(body.error); - } - - if (response.status !== 200) { - throw new Error( - `Failed to add trigger variant, got status code ${response.status}` - ); - } - - const anyBody = await response.json(); - - return TriggerVariantResponseBodySchema.parse(anyBody); - } - async registerHttpSource( client: string, source: RegisterHttpEventSourceBody diff --git a/packages/trigger-sdk/src/index.ts b/packages/trigger-sdk/src/index.ts index 12ceca21e..8f1169d15 100644 --- a/packages/trigger-sdk/src/index.ts +++ b/packages/trigger-sdk/src/index.ts @@ -1,8 +1,10 @@ export * from "./job"; export * from "./triggerClient"; export * from "./connections"; -export * from "./triggers/customEvent"; +export * from "./triggers/customTrigger"; +export * from "./triggers/comboTrigger"; export * from "./triggers/externalSource"; +export * from "./triggers/dynamic"; export * from "./io"; export * from "./types"; diff --git a/packages/trigger-sdk/src/job.ts b/packages/trigger-sdk/src/job.ts index 3e9365d3e..16bd46393 100644 --- a/packages/trigger-sdk/src/job.ts +++ b/packages/trigger-sdk/src/job.ts @@ -6,10 +6,15 @@ import { } from "@trigger.dev/internal"; import { Connection, IOWithConnections } from "./connections"; import { TriggerClient } from "./triggerClient"; -import type { TriggerContext, Trigger, TriggerEventType } from "./types"; +import type { + TriggerContext, + Trigger, + TriggerEventType, + EventSpecification, +} from "./types"; export type JobOptions< - TTrigger extends Trigger, + TTrigger extends Trigger>, TConnections extends Record> = {} > = { id: string; @@ -28,7 +33,7 @@ export type JobOptions< }; export class Job< - TTrigger extends Trigger, + TTrigger extends Trigger>, TConnections extends Record> > { readonly options: JobOptions; @@ -67,14 +72,8 @@ export class Job< (acc: Record, key) => { const connection = this.options.connections![key]; - if (connection.usesLocalAuth) { + if (!connection.usesLocalAuth) { acc[key] = { - auth: "local", - metadata: connection.metadata, - }; - } else { - acc[key] = { - auth: "hosted", metadata: connection.metadata, id: connection.id!, }; @@ -94,7 +93,8 @@ export class Job< id: this.id, name: this.name, version: this.version, - trigger: this.trigger.toJSON(), + event: this.trigger.event, + triggers: this.trigger.toJSON(), connections: this.connections, queue: this.options.queue, internal, diff --git a/packages/trigger-sdk/src/triggerClient.ts b/packages/trigger-sdk/src/triggerClient.ts index 0e221e1ba..4b9e186d1 100644 --- a/packages/trigger-sdk/src/triggerClient.ts +++ b/packages/trigger-sdk/src/triggerClient.ts @@ -1,6 +1,7 @@ import { ErrorWithMessage, ErrorWithStackSchema, + GetEndpointDataResponse, LogLevel, Logger, NormalizedRequest, @@ -17,7 +18,7 @@ import { import { IO, ResumeWithTask } from "./io"; import { Job } from "./job"; import { ContextLogger } from "./logger"; -import type { Trigger, TriggerContext } from "./types"; +import type { EventSpecification, Trigger, TriggerContext } from "./types"; export type TriggerClientOptions = { apiKey?: string; @@ -33,7 +34,8 @@ export type ListenOptions = { export class TriggerClient { #options: TriggerClientOptions; - #registeredJobs: Record, any>> = {}; + #registeredJobs: Record>, any>> = + {}; #client: ApiClient; #logger: Logger; name: string; @@ -92,12 +94,15 @@ export class TriggerClient { }; } + const body: GetEndpointDataResponse = { + jobs: Object.values(this.#registeredJobs).map((job) => job.toJSON()), + dynamicTriggers: [], + }; + // if the x-trigger-job-id header is not set, we return all jobs return { status: 200, - body: { - jobs: Object.values(this.#registeredJobs).map((job) => job.toJSON()), - }, + body, }; } @@ -172,7 +177,7 @@ export class TriggerClient { attach(job: Job, any>): void { this.#registeredJobs[job.id] = job; - job.trigger.attach(this, job); + job.trigger.attachToJob(this, job); } authorized(apiKey: string) { @@ -214,7 +219,7 @@ export class TriggerClient { try { const output = await job.options.run( - job.trigger.parsePayload(execution.event.payload ?? {}), + job.trigger.event.parsePayload(execution.event.payload ?? {}), ioWithConnections, this.#createJobContext(execution, io, abortController.signal) ); diff --git a/packages/trigger-sdk/src/triggers/comboTrigger.ts b/packages/trigger-sdk/src/triggers/comboTrigger.ts new file mode 100644 index 000000000..0b7b883e6 --- /dev/null +++ b/packages/trigger-sdk/src/triggers/comboTrigger.ts @@ -0,0 +1,46 @@ +import { TriggerMetadata } from "@trigger.dev/internal"; +import { Job } from "../job"; +import { TriggerClient } from "../triggerClient"; +import { EventSpecification, Trigger } from "../types"; + +type ComboTriggerOptions< + TEventSpecification extends EventSpecification, + TTriggers extends Array> +> = { + event: TEventSpecification; + triggers: TTriggers; +}; + +class ComboTrigger< + TEventSpecification extends EventSpecification, + TTriggers extends Array> +> implements Trigger +{ + #options: ComboTriggerOptions; + + constructor(options: ComboTriggerOptions) { + this.#options = options; + } + + toJSON(): Array { + return this.#options.triggers.flatMap((trigger) => trigger.toJSON()); + } + + get event() { + return this.#options.event; + } + + attachToJob( + triggerClient: TriggerClient, + job: Job, any> + ): void {} +} + +export function comboTrigger< + TEventSpecification extends EventSpecification, + TTriggers extends Array> +>( + options: ComboTriggerOptions +): Trigger { + return new ComboTrigger(options); +} diff --git a/packages/trigger-sdk/src/triggers/customEvent.ts b/packages/trigger-sdk/src/triggers/customEvent.ts deleted file mode 100644 index 9be6ef8bc..000000000 --- a/packages/trigger-sdk/src/triggers/customEvent.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { - ApiEventLog, - EventFilter, - TriggerMetadata, -} from "@trigger.dev/internal"; -import { DisplayElement } from "@trigger.dev/internal"; -import { z } from "zod"; -import zodToJsonSchema from "zod-to-json-schema"; -import { Trigger } from "../types"; -import { TriggerClient } from "../triggerClient"; -import { Job } from "../job"; - -type CustomEventTriggerOptions = { - name: string; - source?: string; - schema?: TSchema; - filter?: EventFilter; -}; - -class CustomEventTrigger - implements Trigger> -{ - #options: CustomEventTriggerOptions; - - constructor(options: CustomEventTriggerOptions) { - this.#options = options; - } - - eventElements(event: ApiEventLog): DisplayElement[] { - return []; - } - - toJSON(): TriggerMetadata { - return { - title: "Custom Event", - elements: [{ label: "on", text: this.#options.name }], - schema: this.#options.schema - ? zodToJsonSchema(this.#options.schema) - : undefined, - eventRule: { - event: this.#options.name, - source: this.#options.source ?? "trigger.dev", - payload: this.#options.filter ?? {}, - }, - }; - } - - parsePayload(payload: unknown): z.infer { - if (!this.#options.schema) { - return payload; - } - - return this.#options.schema.parse(payload); - } - - attach( - triggerClient: TriggerClient, - job: Job>, any>, - variantId?: string - ): void {} -} - -export function customEvent( - options: CustomEventTriggerOptions -): Trigger> { - return new CustomEventTrigger(options); -} diff --git a/packages/trigger-sdk/src/triggers/customTrigger.ts b/packages/trigger-sdk/src/triggers/customTrigger.ts new file mode 100644 index 000000000..2fc3bf579 --- /dev/null +++ b/packages/trigger-sdk/src/triggers/customTrigger.ts @@ -0,0 +1,78 @@ +import { z } from "zod"; +import { Job } from "../job"; +import { TriggerClient } from "../triggerClient"; +import { EventSpecification, Trigger } from "../types"; +import { + EventFilter, + TriggerMetadata, + deepMergeFilters, +} from "@trigger.dev/internal"; + +type CustomTriggerOptions> = + { + name: string; + event: TEventSpecification; + source?: string; + filter?: EventFilter; + }; + +class CustomTrigger> + implements Trigger +{ + #options: CustomTriggerOptions; + + constructor(options: CustomTriggerOptions) { + this.#options = options; + } + + toJSON(): Array { + return [ + { + type: "static", + title: this.#options.name, + rule: { + event: this.#options.name, + source: this.#options.source ?? "trigger.dev", + payload: deepMergeFilters( + this.#options.filter ?? {}, + this.#options.event.filter ?? {} + ), + }, + }, + ]; + } + + get event() { + return this.#options.event; + } + + attachToJob( + triggerClient: TriggerClient, + job: Job, any> + ): void {} +} + +export function customTrigger< + TEventSpecification extends EventSpecification +>( + options: CustomTriggerOptions +): Trigger { + return new CustomTrigger(options); +} + +export function customEvent({ + schema, + source, +}: { + schema: z.Schema; + source?: string; +}): EventSpecification { + return { + name: "custom", + title: "Custom Event", + source: source ?? "trigger.dev", + parsePayload: (payload: any) => { + return schema.parse(payload); + }, + }; +} diff --git a/packages/trigger-sdk/src/triggers/dynamic.ts b/packages/trigger-sdk/src/triggers/dynamic.ts new file mode 100644 index 000000000..8f57836a4 --- /dev/null +++ b/packages/trigger-sdk/src/triggers/dynamic.ts @@ -0,0 +1,52 @@ +import { TriggerMetadata } from "@trigger.dev/internal"; +import { Job } from "../job"; +import { TriggerClient } from "../triggerClient"; +import { EventSpecification, Trigger } from "../types"; +import { ExternalSource, ExternalSourceParams } from "./externalSource"; + +export type DynamicTriggerOptions< + TEventSpec extends EventSpecification, + TExternalSource extends ExternalSource +> = { + id: string; + event: TEventSpec; + source?: TExternalSource; +}; + +export class DynamicTrigger< + TEventSpec extends EventSpecification, + TExternalSource extends ExternalSource +> implements Trigger +{ + #client: TriggerClient; + #options: DynamicTriggerOptions; + + constructor( + client: TriggerClient, + options: DynamicTriggerOptions + ) { + this.#client = client; + this.#options = options; + } + + toJSON(): Array { + return [ + { + type: "dynamic", + id: this.#options.id, + }, + ]; + } + + get event() { + return this.#options.event; + } + + // Just an example for the types + register(params: ExternalSourceParams): void {} + + attachToJob( + triggerClient: TriggerClient, + job: Job, any> + ): void {} +} diff --git a/packages/trigger-sdk/src/triggers/externalSource.ts b/packages/trigger-sdk/src/triggers/externalSource.ts index 055d6b359..1ca9c88d5 100644 --- a/packages/trigger-sdk/src/triggers/externalSource.ts +++ b/packages/trigger-sdk/src/triggers/externalSource.ts @@ -1,16 +1,16 @@ -import type { - ApiEventLog, - EventRule, - TriggerMetadata, -} from "@trigger.dev/internal"; -import { DisplayElement } from "@trigger.dev/internal"; import { z } from "zod"; -import { SendEvent } from "@trigger.dev/internal"; +import { + EventFilter, + SendEvent, + TriggerMetadata, + deepMergeFilters, +} from "@trigger.dev/internal"; import { Connection, IOWithConnections } from "../connections"; +import { IO } from "../io"; import { Job } from "../job"; import { TriggerClient } from "../triggerClient"; -import type { Trigger, TriggerContext } from "../types"; +import type { EventSpecification, Trigger, TriggerContext } from "../types"; type HttpSourceEvent = { url: string; @@ -44,7 +44,12 @@ type ExternalSourceChannelMap = { type ChannelNames = keyof ExternalSourceChannelMap; -type RegisterFunction> = ( +type RegisterFunction< + TConnection extends Connection, + TParams extends any +> = ( + params: TParams, + eventSpecification: EventSpecification, io: IOWithConnections<{ client: TConnection }>, ctx: TriggerContext ) => Promise; @@ -59,49 +64,56 @@ type HandlerFunction< ) => Promise<{ events: SendEvent[] }>; type ExternalSourceOptions< - TEvent extends any, TChannel extends ChannelNames, - TConnection extends Connection + TConnection extends Connection, + TParams extends any > = { + schema: z.Schema; connection: TConnection; - register: RegisterFunction; + register: RegisterFunction; handler: HandlerFunction; - parsePayload: (payload: unknown) => TEvent; }; +export interface AnExternalSource { + connection: Connection; + register: ( + params: any, + spec: EventSpecification, + io: IO, + ctx: TriggerContext + ) => Promise; +} + export class ExternalSource< - TEvent extends any, TChannel extends ChannelNames, - TConnection extends Connection -> { + TConnection extends Connection, + TParams extends any +> implements AnExternalSource +{ channel: TChannel; - key: string; version: string; constructor( channel: TChannel, - key: string, version: string, - private options: ExternalSourceOptions + private options: ExternalSourceOptions ) { - this.key = key; this.channel = channel; this.version = version; } async register( - io: IOWithConnections<{ client: TConnection }>, + params: TParams, + spec: EventSpecification, + io: IO, ctx: TriggerContext ) { - return await this.options.register(io, ctx); - } - - async handle( - event: RawSourceTriggerEvent, - io: IOWithConnections<{ client: TConnection }>, - ctx: TriggerContext - ) { - return await this.options.handler(event, io, ctx); + return await this.options.register( + params, + spec, + io as IOWithConnections<{ client: TConnection }>, + ctx + ); } get connection() { @@ -109,73 +121,59 @@ export class ExternalSource< } } -export type ExternalSourceEventTriggerOptions< - TEvent extends any, - TChannel extends ChannelNames, - TConnection extends Connection +export type ExternalSourceParams< + TExternalSource extends ExternalSource +> = TExternalSource extends ExternalSource + ? TParams + : never; + +export type ExternalSourceTriggerOptions< + TEventSpecification extends EventSpecification, + TEventSource extends ExternalSource > = { - title: string; - eventRule: EventRule; - elements: DisplayElement[]; - source: ExternalSource; + event: TEventSpecification; + source: TEventSource; + params: ExternalSourceParams; + filter?: EventFilter; }; -export class ExternalSourceEventTrigger< - TEventType extends any, - TChannel extends ChannelNames, - TConnection extends Connection -> implements Trigger +export class ExternalSourceTrigger< + TEventSpecification extends EventSpecification, + TEventSource extends ExternalSource +> implements Trigger { constructor( - private options: ExternalSourceEventTriggerOptions< - TEventType, - TChannel, - TConnection + private options: ExternalSourceTriggerOptions< + TEventSpecification, + TEventSource > ) {} - eventElements(event: ApiEventLog): DisplayElement[] { - return []; + get event() { + return this.options.event; } - parsePayload(payload: unknown): TEventType { - return payload as TEventType; + toJSON(): Array { + return [ + { + type: "static", + title: "External Source", + rule: { + event: this.event.name, + payload: deepMergeFilters( + this.options.filter ?? {}, + this.event.filter ?? {} + ), + source: this.event.source, + }, + }, + ]; } - toJSON(): TriggerMetadata { - return { - title: this.options.title, - elements: this.options.elements, - eventRule: this.options.eventRule, - }; - } - - attach( + attachToJob( triggerClient: TriggerClient, - job: Job, any>, - variantId?: string - ): void { - new Job(triggerClient, { - id: `${job.id}-prepare-external-trigger${ - variantId ? `-${variantId}` : "" - }`, - name: `Prepare ${this.options.title}`, - version: job.version, - trigger: internalPrepareTrigger(job, variantId), - connections: { - client: this.options.source.connection, - }, - queue: { - name: `internal:${triggerClient.name}`, - maxConcurrent: 1, - }, - run: async (event, io, ctx) => { - return await this.options.source.register(io, ctx); - }, - // @ts-ignore - __internal: true, - }); - } + job: Job, any> + ) {} } type RawSourceTriggerEvent = { @@ -183,98 +181,98 @@ type RawSourceTriggerEvent = { source: { key: string; secret: string; data: any }; }; -function rawSourceTrigger( - channel: TChannel, - key: string -): Trigger> { - return new RawSourceEventTrigger(channel, key); -} +// function rawSourceTrigger( +// channel: TChannel, +// key: string +// ): Trigger> { +// return new RawSourceEventTrigger(channel, key); +// } -class RawSourceEventTrigger - implements Trigger> -{ - constructor(private channel: TChannel, private key: string) {} +// class RawSourceEventTrigger +// implements Trigger> +// { +// constructor(private channel: TChannel, private key: string) {} - eventElements(event: ApiEventLog): DisplayElement[] { - return []; - } +// eventElements(event: ApiEventLog): DisplayElement[] { +// return []; +// } - toJSON(): TriggerMetadata { - return { - title: "Handle Raw Source Event", - elements: [{ label: "sourceKey", text: this.key }], - eventRule: { - event: "internal.trigger.handle-raw-source-event", - source: "trigger.dev", - payload: { - source: { key: [this.key] }, - }, - }, - }; - } +// toJSON(): TriggerMetadata { +// return { +// title: "Handle Raw Source Event", +// elements: [{ label: "sourceKey", text: this.key }], +// eventRule: { +// event: "internal.trigger.handle-raw-source-event", +// source: "trigger.dev", +// payload: { +// source: { key: [this.key] }, +// }, +// }, +// }; +// } - parsePayload(payload: unknown): RawSourceTriggerEvent { - return payload as RawSourceTriggerEvent; - } +// parsePayload(payload: unknown): RawSourceTriggerEvent { +// return payload as RawSourceTriggerEvent; +// } - attach( - triggerClient: TriggerClient, - job: Job>, any>, - variantId?: string - ): void {} -} +// attach( +// triggerClient: TriggerClient, +// job: Job>, any>, +// variantId?: string +// ): void {} +// } -const PrepareTriggerEventSchema = z.object({ - jobId: z.string(), - jobVersion: z.string(), - variantId: z.string().optional(), -}); +// const PrepareTriggerEventSchema = z.object({ +// jobId: z.string(), +// jobVersion: z.string(), +// variantId: z.string().optional(), +// }); -type PrepareTriggerEvent = z.infer; +// type PrepareTriggerEvent = z.infer; -class PrepareTriggerInternalTrigger implements Trigger { - constructor( - private job: Job, any>, - private variantId?: string - ) {} +// class PrepareTriggerInternalTrigger implements Trigger { +// constructor( +// private job: Job, any>, +// private variantId?: string +// ) {} - eventElements(event: ApiEventLog): DisplayElement[] { - return []; - } +// eventElements(event: ApiEventLog): DisplayElement[] { +// return []; +// } - toJSON(): TriggerMetadata { - return { - title: "Prepare Trigger", - elements: [ - { label: "id", text: this.job.id }, - { label: "version", text: this.job.version }, - ], - eventRule: { - event: "internal.trigger.prepare", - source: "trigger.dev", - payload: { - jobId: [this.job.id], - jobVersion: [this.job.version], - variantId: this.variantId ? [this.variantId] : [], - }, - }, - }; - } +// toJSON(): TriggerMetadata { +// return { +// title: "Prepare Trigger", +// elements: [ +// { label: "id", text: this.job.id }, +// { label: "version", text: this.job.version }, +// ], +// eventRule: { +// event: "internal.trigger.prepare", +// source: "trigger.dev", +// payload: { +// jobId: [this.job.id], +// jobVersion: [this.job.version], +// variantId: this.variantId ? [this.variantId] : [], +// }, +// }, +// }; +// } - parsePayload(payload: unknown): PrepareTriggerEvent { - return PrepareTriggerEventSchema.parse(payload); - } +// parsePayload(payload: unknown): PrepareTriggerEvent { +// return PrepareTriggerEventSchema.parse(payload); +// } - attach( - triggerClient: TriggerClient, - job: Job, any>, - variantId?: string - ): void {} -} +// attach( +// triggerClient: TriggerClient, +// job: Job, any>, +// variantId?: string +// ): void {} +// } -export function internalPrepareTrigger( - job: Job, any>, - variantId?: string -): Trigger { - return new PrepareTriggerInternalTrigger(job, variantId); -} +// export function internalPrepareTrigger( +// job: Job, any>, +// variantId?: string +// ): Trigger { +// return new PrepareTriggerInternalTrigger(job, variantId); +// } diff --git a/packages/trigger-sdk/src/types.ts b/packages/trigger-sdk/src/types.ts index 02b6109f2..2260dae72 100644 --- a/packages/trigger-sdk/src/types.ts +++ b/packages/trigger-sdk/src/types.ts @@ -1,5 +1,6 @@ import type { ApiEventLog, + EventFilter, RawEvent, SecureString, SendEvent, @@ -9,6 +10,8 @@ import type { import { DisplayElement } from "@trigger.dev/internal"; import { Job } from "./job"; import { TriggerClient } from "./triggerClient"; +import { AnExternalSource } from "./triggers/externalSource"; +import { Connection } from "./connections"; export type { SecureString }; @@ -40,18 +43,32 @@ export interface TaskLogger { } export type TriggerEventType> = - TTrigger extends Trigger ? TEventType : never; - -export interface Trigger { - eventElements(event: ApiEventLog): DisplayElement[]; - toJSON(): TriggerMetadata; - parsePayload(payload: unknown): TEventType; + TTrigger extends Trigger + ? ReturnType + : never; +export interface Trigger> { + event: TEventSpec; + toJSON(): Array; // Attach this trigger to the job and the trigger client // Gives different triggers the ability to do things like register internal jobs - attach( + attachToJob( triggerClient: TriggerClient, - job: Job, any>, - variantId?: string + job: Job, any> ): void; } + +export interface EventSpecification { + name: string; + title: string; + source: string; + elements?: DisplayElement[]; + schema?: any; + examples?: Array; + filter?: EventFilter; + parsePayload: (payload: unknown) => TEvent; +} + +export type EventTypeFromSpecification< + TEventSpec extends EventSpecification +> = TEventSpec extends EventSpecification ? TEvent : never;