WIP new stuff

This commit is contained in:
Eric Allam
2023-05-12 13:17:40 +01:00
parent e851cefb39
commit 214546d17a
29 changed files with 2058 additions and 1484 deletions
@@ -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 } } },
+3 -3
View File
@@ -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) {
@@ -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", {
@@ -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,
},
@@ -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<JobInstance> {
metadata: JobMetadata
): Promise<JobVersion> {
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<string, ApiConnection>();
const apiConnectionClients = new Map<string, ApiConnectionClient>();
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<string, ApiConnection>,
apiConnectionClients: Map<string, ApiConnectionClient>,
key: string
): Promise<JobConnection> {
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<JobConnection> {
// 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,
},
});
}
+6 -6
View File
@@ -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);
},
@@ -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;
File diff suppressed because it is too large Load Diff
+241 -1
View File
@@ -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) => {
+105 -13
View File
@@ -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",
+98 -56
View File
@@ -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<Octokit, typeof tasks>) {
return {
onIssue: buildRepoWebhookTrigger<IssuesEvent>(
"On Issue",
"issues",
connection
),
onIssueOpened: buildRepoWebhookTrigger<IssuesOpenedEvent>(
"On Issue Opened",
"issues",
connection,
{
action: ["opened"],
}
),
onStar: buildRepoWebhookTrigger<StarEvent>("On Star", "star", connection),
};
}
const onIssueOpened: EventSpecification<IssuesOpenedEvent> = {
name: "issues",
title: "On issue opened",
source: "github.com",
filter: {
action: ["opened"],
},
parsePayload: (payload) => payload as IssuesOpenedEvent,
};
function buildRepoWebhookTrigger<TEvent>(
title: string,
event: string,
connection: Connection<Octokit, typeof tasks>,
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<IssuesEvent> = {
name: "issues",
title: "On issue",
source: "github.com",
parsePayload: (payload) => payload as IssuesEvent,
};
const onStar: EventSpecification<StarEvent> = {
name: "star",
title: "On star",
source: "github.com",
parsePayload: (payload) => payload as StarEvent,
};
const onNewStar: EventSpecification<StarCreatedEvent> = {
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<typeof createRepoEventSource>) {
return <TEventSpecification extends GitHubEvents>({
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<typeof createOrgEventSource>) {
return <TEventSpecification extends GitHubEvents>({
event,
org,
}: {
event: TEventSpecification;
org: string;
}) => {
return new ExternalSourceTrigger({
event,
params: { org },
source,
filter: {
payload: {
organization: {
login: [org],
},
},
},
});
};
}
+162 -17
View File
@@ -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<TEventType>(
params: {
repo: string;
events: string[];
secret?: string;
},
connection: Connection<Octokit, typeof tasks>,
parsePayload: (payload: any) => TEventType
export function createRepoEventSource(
connection: Connection<Octokit, typeof tasks>
) {
// 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<TEventType>(
) {
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<TEventType>(
(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<TEventType>(
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<Octokit, typeof tasks>
) {
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,
});
+116
View File
@@ -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<typeof Octokit>,
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<typeof Octokit>,
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<typeof Octokit>,
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,
};
+2 -1
View File
@@ -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"
}
}
}
+1
View File
@@ -1,3 +1,4 @@
export * from "./logger";
export * from "./schemas";
export * from "./types";
export * from "./utils";
+17 -11
View File
@@ -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<typeof QueueOptionsSchema>;
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<typeof JobSchema>;
export type JobMetadata = z.infer<typeof JobMetadataSchema>;
export const GetJobResponseSchema = JobSchema;
export type GetJobResponse = z.infer<typeof GetJobResponseSchema>;
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<typeof RunJobResponseSchema>;
export const CreateRunBodySchema = z.object({
client: z.string(),
job: JobSchema,
job: JobMetadataSchema,
event: ApiEventLogSchema,
elements: z.array(DisplayElementSchema).optional(),
});
+2 -21
View File
@@ -17,28 +17,9 @@ export const ConnectionAuthSchema = z.object({
export type ConnectionAuth = z.infer<typeof ConnectionAuthSchema>;
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<typeof ConnectionConfigSchema>;
export type LocalAuthConnectionConfig = z.infer<
typeof LocalAuthConnectionConfigSchema
>;
export type HostedAuthConnectionConfig = z.infer<
typeof HostedAuthConnectionConfigSchema
>;
+26 -19
View File
@@ -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<typeof TriggerMetadataSchema>;
export const TriggerVariantConfigSchema = z.object({
id: z.string(),
trigger: TriggerMetadataSchema,
});
export type TriggerVariantConfig = z.infer<typeof TriggerVariantConfigSchema>;
+39
View File
@@ -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;
}
-53
View File
@@ -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<TriggerVariantResponseBody> {
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
+3 -1
View File
@@ -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";
+11 -11
View File
@@ -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<any>,
TTrigger extends Trigger<EventSpecification<any>>,
TConnections extends Record<string, Connection<any, any>> = {}
> = {
id: string;
@@ -28,7 +33,7 @@ export type JobOptions<
};
export class Job<
TTrigger extends Trigger<any>,
TTrigger extends Trigger<EventSpecification<any>>,
TConnections extends Record<string, Connection<any, any>>
> {
readonly options: JobOptions<TTrigger, TConnections>;
@@ -67,14 +72,8 @@ export class Job<
(acc: Record<string, ConnectionConfig>, 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,
+12 -7
View File
@@ -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<string, Job<Trigger<any>, any>> = {};
#registeredJobs: Record<string, Job<Trigger<EventSpecification<any>>, 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<Trigger<any>, 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)
);
@@ -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<any>,
TTriggers extends Array<Trigger<TEventSpecification>>
> = {
event: TEventSpecification;
triggers: TTriggers;
};
class ComboTrigger<
TEventSpecification extends EventSpecification<any>,
TTriggers extends Array<Trigger<TEventSpecification>>
> implements Trigger<TEventSpecification>
{
#options: ComboTriggerOptions<TEventSpecification, TTriggers>;
constructor(options: ComboTriggerOptions<TEventSpecification, TTriggers>) {
this.#options = options;
}
toJSON(): Array<TriggerMetadata> {
return this.#options.triggers.flatMap((trigger) => trigger.toJSON());
}
get event() {
return this.#options.event;
}
attachToJob(
triggerClient: TriggerClient,
job: Job<Trigger<TEventSpecification>, any>
): void {}
}
export function comboTrigger<
TEventSpecification extends EventSpecification<any>,
TTriggers extends Array<Trigger<TEventSpecification>>
>(
options: ComboTriggerOptions<TEventSpecification, TTriggers>
): Trigger<TEventSpecification> {
return new ComboTrigger(options);
}
@@ -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<TSchema extends z.ZodTypeAny> = {
name: string;
source?: string;
schema?: TSchema;
filter?: EventFilter;
};
class CustomEventTrigger<TSchema extends z.ZodTypeAny>
implements Trigger<z.infer<TSchema>>
{
#options: CustomEventTriggerOptions<TSchema>;
constructor(options: CustomEventTriggerOptions<TSchema>) {
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<TSchema> {
if (!this.#options.schema) {
return payload;
}
return this.#options.schema.parse(payload);
}
attach(
triggerClient: TriggerClient,
job: Job<Trigger<z.infer<TSchema>>, any>,
variantId?: string
): void {}
}
export function customEvent<TSchema extends z.ZodTypeAny>(
options: CustomEventTriggerOptions<TSchema>
): Trigger<z.infer<TSchema>> {
return new CustomEventTrigger(options);
}
@@ -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<TEventSpecification extends EventSpecification<any>> =
{
name: string;
event: TEventSpecification;
source?: string;
filter?: EventFilter;
};
class CustomTrigger<TEventSpecification extends EventSpecification<any>>
implements Trigger<TEventSpecification>
{
#options: CustomTriggerOptions<TEventSpecification>;
constructor(options: CustomTriggerOptions<TEventSpecification>) {
this.#options = options;
}
toJSON(): Array<TriggerMetadata> {
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<Trigger<TEventSpecification>, any>
): void {}
}
export function customTrigger<
TEventSpecification extends EventSpecification<any>
>(
options: CustomTriggerOptions<TEventSpecification>
): Trigger<TEventSpecification> {
return new CustomTrigger(options);
}
export function customEvent<TEvent>({
schema,
source,
}: {
schema: z.Schema<TEvent>;
source?: string;
}): EventSpecification<TEvent> {
return {
name: "custom",
title: "Custom Event",
source: source ?? "trigger.dev",
parsePayload: (payload: any) => {
return schema.parse(payload);
},
};
}
@@ -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<any>,
TExternalSource extends ExternalSource<any, any, any>
> = {
id: string;
event: TEventSpec;
source?: TExternalSource;
};
export class DynamicTrigger<
TEventSpec extends EventSpecification<any>,
TExternalSource extends ExternalSource<any, any, any>
> implements Trigger<TEventSpec>
{
#client: TriggerClient;
#options: DynamicTriggerOptions<TEventSpec, TExternalSource>;
constructor(
client: TriggerClient,
options: DynamicTriggerOptions<TEventSpec, TExternalSource>
) {
this.#client = client;
this.#options = options;
}
toJSON(): Array<TriggerMetadata> {
return [
{
type: "dynamic",
id: this.#options.id,
},
];
}
get event() {
return this.#options.event;
}
// Just an example for the types
register(params: ExternalSourceParams<TExternalSource>): void {}
attachToJob(
triggerClient: TriggerClient,
job: Job<Trigger<TEventSpec>, any>
): void {}
}
@@ -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<TConnection extends Connection<any, any>> = (
type RegisterFunction<
TConnection extends Connection<any, any>,
TParams extends any
> = (
params: TParams,
eventSpecification: EventSpecification<any>,
io: IOWithConnections<{ client: TConnection }>,
ctx: TriggerContext
) => Promise<void>;
@@ -59,49 +64,56 @@ type HandlerFunction<
) => Promise<{ events: SendEvent[] }>;
type ExternalSourceOptions<
TEvent extends any,
TChannel extends ChannelNames,
TConnection extends Connection<any, any>
TConnection extends Connection<any, any>,
TParams extends any
> = {
schema: z.Schema<TParams>;
connection: TConnection;
register: RegisterFunction<TConnection>;
register: RegisterFunction<TConnection, TParams>;
handler: HandlerFunction<TChannel, TConnection>;
parsePayload: (payload: unknown) => TEvent;
};
export interface AnExternalSource {
connection: Connection<any, any>;
register: (
params: any,
spec: EventSpecification<any>,
io: IO,
ctx: TriggerContext
) => Promise<any>;
}
export class ExternalSource<
TEvent extends any,
TChannel extends ChannelNames,
TConnection extends Connection<any, any>
> {
TConnection extends Connection<any, any>,
TParams extends any
> implements AnExternalSource
{
channel: TChannel;
key: string;
version: string;
constructor(
channel: TChannel,
key: string,
version: string,
private options: ExternalSourceOptions<TEvent, TChannel, TConnection>
private options: ExternalSourceOptions<TChannel, TConnection, TParams>
) {
this.key = key;
this.channel = channel;
this.version = version;
}
async register(
io: IOWithConnections<{ client: TConnection }>,
params: TParams,
spec: EventSpecification<any>,
io: IO,
ctx: TriggerContext
) {
return await this.options.register(io, ctx);
}
async handle(
event: RawSourceTriggerEvent<TChannel>,
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<any, any>
export type ExternalSourceParams<
TExternalSource extends ExternalSource<any, any, any>
> = TExternalSource extends ExternalSource<any, any, infer TParams>
? TParams
: never;
export type ExternalSourceTriggerOptions<
TEventSpecification extends EventSpecification<any>,
TEventSource extends ExternalSource<any, any, any>
> = {
title: string;
eventRule: EventRule;
elements: DisplayElement[];
source: ExternalSource<TEvent, TChannel, TConnection>;
event: TEventSpecification;
source: TEventSource;
params: ExternalSourceParams<TEventSource>;
filter?: EventFilter;
};
export class ExternalSourceEventTrigger<
TEventType extends any,
TChannel extends ChannelNames,
TConnection extends Connection<any, any>
> implements Trigger<TEventType>
export class ExternalSourceTrigger<
TEventSpecification extends EventSpecification<any>,
TEventSource extends ExternalSource<any, any, any>
> implements Trigger<TEventSpecification>
{
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<TriggerMetadata> {
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<Trigger<TEventType>, 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<Trigger<TEventSpecification>, any>
) {}
}
type RawSourceTriggerEvent<TChannel extends ChannelNames> = {
@@ -183,98 +181,98 @@ type RawSourceTriggerEvent<TChannel extends ChannelNames> = {
source: { key: string; secret: string; data: any };
};
function rawSourceTrigger<TChannel extends ChannelNames>(
channel: TChannel,
key: string
): Trigger<RawSourceTriggerEvent<TChannel>> {
return new RawSourceEventTrigger(channel, key);
}
// function rawSourceTrigger<TChannel extends ChannelNames>(
// channel: TChannel,
// key: string
// ): Trigger<RawSourceTriggerEvent<TChannel>> {
// return new RawSourceEventTrigger(channel, key);
// }
class RawSourceEventTrigger<TChannel extends ChannelNames>
implements Trigger<RawSourceTriggerEvent<TChannel>>
{
constructor(private channel: TChannel, private key: string) {}
// class RawSourceEventTrigger<TChannel extends ChannelNames>
// implements Trigger<RawSourceTriggerEvent<TChannel>>
// {
// 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<TChannel> {
return payload as RawSourceTriggerEvent<TChannel>;
}
// parsePayload(payload: unknown): RawSourceTriggerEvent<TChannel> {
// return payload as RawSourceTriggerEvent<TChannel>;
// }
attach(
triggerClient: TriggerClient,
job: Job<Trigger<RawSourceTriggerEvent<TChannel>>, any>,
variantId?: string
): void {}
}
// attach(
// triggerClient: TriggerClient,
// job: Job<Trigger<RawSourceTriggerEvent<TChannel>>, 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<typeof PrepareTriggerEventSchema>;
// type PrepareTriggerEvent = z.infer<typeof PrepareTriggerEventSchema>;
class PrepareTriggerInternalTrigger implements Trigger<PrepareTriggerEvent> {
constructor(
private job: Job<Trigger<any>, any>,
private variantId?: string
) {}
// class PrepareTriggerInternalTrigger implements Trigger<PrepareTriggerEvent> {
// constructor(
// private job: Job<Trigger<any>, 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<Trigger<PrepareTriggerEvent>, any>,
variantId?: string
): void {}
}
// attach(
// triggerClient: TriggerClient,
// job: Job<Trigger<PrepareTriggerEvent>, any>,
// variantId?: string
// ): void {}
// }
export function internalPrepareTrigger(
job: Job<Trigger<any>, any>,
variantId?: string
): Trigger<PrepareTriggerEvent> {
return new PrepareTriggerInternalTrigger(job, variantId);
}
// export function internalPrepareTrigger(
// job: Job<Trigger<any>, any>,
// variantId?: string
// ): Trigger<PrepareTriggerEvent> {
// return new PrepareTriggerInternalTrigger(job, variantId);
// }
+26 -9
View File
@@ -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<any>> =
TTrigger extends Trigger<infer TEventType> ? TEventType : never;
export interface Trigger<TEventType = any> {
eventElements(event: ApiEventLog): DisplayElement[];
toJSON(): TriggerMetadata;
parsePayload(payload: unknown): TEventType;
TTrigger extends Trigger<infer TEventSpec>
? ReturnType<TEventSpec["parsePayload"]>
: never;
export interface Trigger<TEventSpec extends EventSpecification<any>> {
event: TEventSpec;
toJSON(): Array<TriggerMetadata>;
// 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<Trigger<TEventType>, any>,
variantId?: string
job: Job<Trigger<TEventSpec>, any>
): void;
}
export interface EventSpecification<TEvent extends any> {
name: string;
title: string;
source: string;
elements?: DisplayElement[];
schema?: any;
examples?: Array<TEvent>;
filter?: EventFilter;
parsePayload: (payload: unknown) => TEvent;
}
export type EventTypeFromSpecification<
TEventSpec extends EventSpecification<any>
> = TEventSpec extends EventSpecification<infer TEvent> ? TEvent : never;