ecd050bece
* CLI create-integration command now accepts an Open AI api key * Create integration docs separated into multiple pages * Initial Airtable integration commit, with OpenAI generated code * OAuth page coming soon * Export DisplayProperty from the SDK * TSConfig made to match GitHub’s with paths * getRecords * Removed duplicate Stripe job from the catalog * Renamed Airtable apiKey option to token * First Airtable job * Export Collaborator and Attachment field types * A typesafe example that uses runTask * WIP on new integration tasks… not working yet * Attempt with class * Revert "Attempt with class" This reverts commit 93a48330019f754c3216c5b49964fa4b0218bd3f. * WIP changing how tasks work * Mock of async local storage * Moved client creation from constructor * New approach with a clone method on TriggerIntegration * Added runTask to Airtable which is used by integration tasks * Added the Airtable icon and connection when using runTask * base().table() is working * runTask options moved to the 3rd param, made optional with optional name * Added some generic arguments * Added generic type to table * Removed old comment * We don’t need to repeat the icon * getRecords and getRecord now returning the right data and types * Creating records * Update records * Delete records * The internal properties of integrations are now hidden by the TypeScript types * Sprinkled a Prettify in there * Improved the types * Added Airtable to the integration catalog * Early work on Airtable webhook registration * More progress with webhooks * connectionKey needs to be cloned for webhooks to work * connectionKey needs to be cloned for webhooks to work * It was unclear that the ActivateSourceService was using a graphileJob id * ActivateSourceService optionally takes a jobId, if missing it generate a unique id * When retrying trigger registration, don’t pass an id so it is generated * Removed Airtable webhooks tasks from the job-catalog example * Added TriggerSourceOption, removed TriggerSourceEvent * WIP with new ExternalSource options * ExternalSourceTrigger setup * DynamicTrigger changed to options, will need some more work * filter gets options passed to it * SourceMetadata v2 renamed to SourceMetadataV2, kept original * Started versioning the backend * Moved param order on io.getEvent and io.cancelEvent * The runTask stuff that allows unknown to work is back * Indexing for v1 and v2, with version on “activateSource” schema * Added todos, to deal with Airtable SDK calls inside the webhook handler * “deliverHttpSourceRequest” queueName changed to the source id so they process in order * ActivateSource changes to deal with old and new data formats * Update existing TriggerSources to v2 * Fix for dynamic.ts typescript errors, need to revisit this later * UpdateSourceService v1 and v2, with new v2 API endpoint * Removed unused imports * More progress on v1 and v2 * Airtable webhooks are now triggering a job * Moved webhooks to a new file * You can do API calls in the webhook handler now, Airtable webhook data is being processed * Airtable events coming through * Defined the Airtable table payload type * TriggerSource metadata is being stored and used * Removed some logs * Added filtering and don’t allow any webhooks that use automated sources * Resend switched to new integration * Moved Resend test jobs to the catalog, and tested it worked * WIP on Slack, there are compile errors * Created a generic type that strips out indexes * Slack updated to use new integration * SendGrid migrated over * Integration runTask is now allowing regular types * Changed io.runTask types so it only allows Json-able types * OpenAI models tasks working * Added Airtable changes to runTask * Removed the index signature crap from the Slack integration * Don’t need to cast the callback result * Updated Resend * Re-ordered runTask params * WIP on openai * onAccountUpdated is Connect only * Removed RunTaskResult * Handle Resend errors, the official SDK doesn’t expose them properly at the moment * Removed OmitIndexSignature * OpenAI converted to new integration, with backwards compatible functions * Put the openai catalog back to what it was originally * Export a standard retry with backoff, to be used * Use the standard exponential backoff in the integrations * Retry options moved earlier so they can be overriden by a task * GitHub tasks migrated * Added sources, fixed one bundling issue * Added GitHub jobs to catalog * Remove duplicate options * Deduplicate events * Removed duplicate Job * Switched Plain over * Set the Plain icon * Converted Stripe over * Supabase adapted * Typeform working * Added dynamic-schedule to catalog * Added background-fetch job catalog * Created dynamic-triggers catalog file * Fixed old general file with runTask param order * Dynamic triggers working * SendGrid updated to use the same tsconfig as other integrations * Removed Airtable webhook, until we have batch support * Added OAuth airtable auth example * Created beta changeset tag * Beta changesets for most packages --------- Co-authored-by: Eric Allam <eallam@icloud.com>
336 lines
11 KiB
TypeScript
336 lines
11 KiB
TypeScript
import { DeliverEmailSchema } from "@/../../packages/emails/src";
|
|
import { ScheduledPayloadSchema } from "@trigger.dev/core";
|
|
import { z } from "zod";
|
|
import { prisma } from "~/db.server";
|
|
import { env } from "~/env.server";
|
|
import { ZodWorker } from "~/platform/zodWorker.server";
|
|
import { sendEmail } from "./email.server";
|
|
import { IndexEndpointService } from "./endpoints/indexEndpoint.server";
|
|
import { RecurringEndpointIndexService } from "./endpoints/recurringEndpointIndex.server";
|
|
import { DeliverEventService } from "./events/deliverEvent.server";
|
|
import { InvokeDispatcherService } from "./events/invokeDispatcher.server";
|
|
import { integrationAuthRepository } from "./externalApis/integrationAuthRepository.server";
|
|
import { IntegrationConnectionCreatedService } from "./externalApis/integrationConnectionCreated.server";
|
|
import { MissingConnectionCreatedService } from "./runs/missingConnectionCreated.server";
|
|
import { PerformRunExecutionV1Service } from "./runs/performRunExecutionV1.server";
|
|
import { PerformRunExecutionV2Service } from "./runs/performRunExecutionV2.server";
|
|
import { StartRunService } from "./runs/startRun.server";
|
|
import { DeliverScheduledEventService } from "./schedules/deliverScheduledEvent.server";
|
|
import { ActivateSourceService } from "./sources/activateSource.server";
|
|
import { DeliverHttpSourceRequestService } from "./sources/deliverHttpSourceRequest.server";
|
|
import { PerformTaskOperationService } from "./tasks/performTaskOperation.server";
|
|
import { addMissingVersionField } from "@trigger.dev/core";
|
|
|
|
const workerCatalog = {
|
|
indexEndpoint: z.object({
|
|
id: z.string(),
|
|
source: z.enum(["MANUAL", "API", "INTERNAL", "HOOK"]).optional(),
|
|
sourceData: z.any().optional(),
|
|
reason: z.string().optional(),
|
|
}),
|
|
scheduleEmail: DeliverEmailSchema,
|
|
startRun: z.object({ id: z.string() }),
|
|
performTaskOperation: z.object({
|
|
id: z.string(),
|
|
}),
|
|
deliverHttpSourceRequest: z.object({ id: z.string() }),
|
|
refreshOAuthToken: z.object({
|
|
organizationId: z.string(),
|
|
connectionId: z.string(),
|
|
}),
|
|
activateSource: z.preprocess(
|
|
addMissingVersionField,
|
|
z.discriminatedUnion("version", [
|
|
z.object({
|
|
version: z.literal("1"),
|
|
id: z.string(),
|
|
orphanedEvents: z.array(z.string()).optional(),
|
|
}),
|
|
z.object({
|
|
version: z.literal("2"),
|
|
id: z.string(),
|
|
orphanedOptions: z.record(z.string(), z.array(z.string())).optional(),
|
|
}),
|
|
])
|
|
),
|
|
deliverEvent: z.object({ id: z.string() }),
|
|
"events.invokeDispatcher": z.object({
|
|
id: z.string(),
|
|
eventRecordId: z.string(),
|
|
}),
|
|
"events.deliverScheduled": z.object({
|
|
id: z.string(),
|
|
payload: ScheduledPayloadSchema,
|
|
}),
|
|
missingConnectionCreated: z.object({
|
|
id: z.string(),
|
|
}),
|
|
connectionCreated: z.object({
|
|
id: z.string(),
|
|
}),
|
|
};
|
|
|
|
const executionWorkerCatalog = {
|
|
performRunExecution: z.object({
|
|
id: z.string(),
|
|
}),
|
|
performRunExecutionV2: z.object({
|
|
id: z.string(),
|
|
reason: z.enum(["EXECUTE_JOB", "PREPROCESS"]),
|
|
resumeTaskId: z.string().optional(),
|
|
isRetry: z.boolean(),
|
|
}),
|
|
};
|
|
|
|
let workerQueue: ZodWorker<typeof workerCatalog>;
|
|
let executionWorker: ZodWorker<typeof executionWorkerCatalog>;
|
|
|
|
declare global {
|
|
var __worker__: ZodWorker<typeof workerCatalog>;
|
|
var __executionWorker__: ZodWorker<typeof executionWorkerCatalog>;
|
|
}
|
|
|
|
// this is needed because in development we don't want to restart
|
|
// the server with every change, but we want to make sure we don't
|
|
// create a new connection to the DB with every change either.
|
|
// in production we'll have a single connection to the DB.
|
|
if (env.NODE_ENV === "production") {
|
|
workerQueue = getWorkerQueue();
|
|
executionWorker = getExecutionWorkerQueue();
|
|
} else {
|
|
if (!global.__worker__) {
|
|
global.__worker__ = getWorkerQueue();
|
|
}
|
|
workerQueue = global.__worker__;
|
|
|
|
if (!global.__executionWorker__) {
|
|
global.__executionWorker__ = getExecutionWorkerQueue();
|
|
}
|
|
|
|
executionWorker = global.__executionWorker__;
|
|
}
|
|
|
|
export async function init() {
|
|
if (env.WORKER_ENABLED === "true") {
|
|
await workerQueue.initialize();
|
|
}
|
|
|
|
if (env.EXECUTION_WORKER_ENABLED === "true") {
|
|
await executionWorker.initialize();
|
|
}
|
|
}
|
|
|
|
function getWorkerQueue() {
|
|
return new ZodWorker({
|
|
name: "workerQueue",
|
|
prisma,
|
|
runnerOptions: {
|
|
connectionString: env.DATABASE_URL,
|
|
concurrency: env.WORKER_CONCURRENCY,
|
|
pollInterval: env.WORKER_POLL_INTERVAL,
|
|
noPreparedStatements: env.DATABASE_URL !== env.DIRECT_URL,
|
|
schema: env.WORKER_SCHEMA,
|
|
maxPoolSize: env.WORKER_CONCURRENCY,
|
|
},
|
|
schema: workerCatalog,
|
|
recurringTasks: {
|
|
// Run this every 5 minutes
|
|
autoIndexProductionEndpoints: {
|
|
pattern: "*/5 * * * *",
|
|
handler: async (payload, job) => {
|
|
const service = new RecurringEndpointIndexService();
|
|
|
|
await service.call(payload.ts);
|
|
},
|
|
},
|
|
// Run this every hour
|
|
purgeOldIndexings: {
|
|
pattern: "0 * * * *",
|
|
handler: async (payload, job) => {
|
|
// Delete indexings that are older than 7 days
|
|
await prisma.endpointIndex.deleteMany({
|
|
where: {
|
|
createdAt: {
|
|
lt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
|
|
},
|
|
},
|
|
});
|
|
},
|
|
},
|
|
},
|
|
tasks: {
|
|
"events.invokeDispatcher": {
|
|
priority: 0, // smaller number = higher priority
|
|
maxAttempts: 3,
|
|
handler: async (payload, job) => {
|
|
const service = new InvokeDispatcherService();
|
|
|
|
await service.call(payload.id, payload.eventRecordId);
|
|
},
|
|
},
|
|
"events.deliverScheduled": {
|
|
priority: 0, // smaller number = higher priority
|
|
maxAttempts: 5,
|
|
handler: async ({ id, payload }, job) => {
|
|
const service = new DeliverScheduledEventService();
|
|
|
|
await service.call(id, payload);
|
|
},
|
|
},
|
|
connectionCreated: {
|
|
priority: 10, // smaller number = higher priority
|
|
maxAttempts: 3,
|
|
handler: async (payload, job) => {
|
|
const service = new IntegrationConnectionCreatedService();
|
|
|
|
await service.call(payload.id);
|
|
},
|
|
},
|
|
missingConnectionCreated: {
|
|
priority: 10, // smaller number = higher priority
|
|
maxAttempts: 3,
|
|
handler: async (payload, job) => {
|
|
const service = new MissingConnectionCreatedService();
|
|
|
|
await service.call(payload.id);
|
|
},
|
|
},
|
|
activateSource: {
|
|
priority: 10, // smaller number = higher priority
|
|
maxAttempts: 3,
|
|
handler: async (payload, graphileJob) => {
|
|
const service = new ActivateSourceService();
|
|
switch (payload.version) {
|
|
case "1": {
|
|
//change the input data to match the new schema
|
|
await service.call(
|
|
payload.id,
|
|
graphileJob.id,
|
|
payload.orphanedEvents
|
|
? {
|
|
event: payload.orphanedEvents,
|
|
}
|
|
: undefined
|
|
);
|
|
break;
|
|
}
|
|
case "2": {
|
|
await service.call(payload.id, graphileJob.id, payload.orphanedOptions);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
},
|
|
deliverHttpSourceRequest: {
|
|
priority: 1, // smaller number = higher priority
|
|
maxAttempts: 14,
|
|
handler: async (payload, job) => {
|
|
const service = new DeliverHttpSourceRequestService();
|
|
|
|
await service.call(payload.id);
|
|
},
|
|
},
|
|
startRun: {
|
|
priority: 0, // smaller number = higher priority
|
|
maxAttempts: 4,
|
|
handler: async (payload, job) => {
|
|
const service = new StartRunService();
|
|
|
|
await service.call(payload.id);
|
|
},
|
|
},
|
|
performTaskOperation: {
|
|
priority: 0, // smaller number = higher priority
|
|
queueName: (payload) => `tasks:${payload.id}`,
|
|
maxAttempts: 3,
|
|
handler: async (payload, job) => {
|
|
const service = new PerformTaskOperationService();
|
|
|
|
await service.call(payload.id);
|
|
},
|
|
},
|
|
scheduleEmail: {
|
|
queueName: "internal-queue",
|
|
priority: 100,
|
|
maxAttempts: 3,
|
|
handler: async (payload, job) => {
|
|
await sendEmail(payload);
|
|
},
|
|
},
|
|
indexEndpoint: {
|
|
priority: 1, // smaller number = higher priority
|
|
maxAttempts: 7,
|
|
handler: async (payload, job) => {
|
|
const service = new IndexEndpointService();
|
|
|
|
await service.call(payload.id, payload.source, payload.reason, payload.sourceData);
|
|
},
|
|
},
|
|
deliverEvent: {
|
|
priority: 0, // smaller number = higher priority
|
|
maxAttempts: 5,
|
|
handler: async (payload, job) => {
|
|
const service = new DeliverEventService();
|
|
|
|
await service.call(payload.id);
|
|
},
|
|
},
|
|
refreshOAuthToken: {
|
|
priority: 8, // smaller number = higher priority
|
|
queueName: "internal-queue",
|
|
maxAttempts: 7,
|
|
handler: async (payload, job) => {
|
|
await integrationAuthRepository.refreshConnection({
|
|
connectionId: payload.connectionId,
|
|
});
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
function getExecutionWorkerQueue() {
|
|
return new ZodWorker({
|
|
name: "executionWorker",
|
|
prisma,
|
|
runnerOptions: {
|
|
connectionString: env.DATABASE_URL,
|
|
concurrency: env.EXECUTION_WORKER_CONCURRENCY,
|
|
pollInterval: env.EXECUTION_WORKER_POLL_INTERVAL,
|
|
noPreparedStatements: env.DATABASE_URL !== env.DIRECT_URL,
|
|
schema: env.WORKER_SCHEMA,
|
|
maxPoolSize: env.EXECUTION_WORKER_CONCURRENCY,
|
|
},
|
|
schema: executionWorkerCatalog,
|
|
tasks: {
|
|
performRunExecution: {
|
|
priority: 0, // smaller number = higher priority
|
|
maxAttempts: 1,
|
|
handler: async (payload, job) => {
|
|
// This is a legacy task that we don't use anymore, but needs to be here for backwards compatibility
|
|
// TODO: remove this once all performRunExecution tasks have been processed
|
|
const service = new PerformRunExecutionV1Service();
|
|
|
|
await service.call(payload.id);
|
|
},
|
|
},
|
|
performRunExecutionV2: {
|
|
priority: 0, // smaller number = higher priority
|
|
maxAttempts: 12,
|
|
handler: async (payload, job) => {
|
|
const service = new PerformRunExecutionV2Service();
|
|
|
|
await service.call({
|
|
id: payload.id,
|
|
reason: payload.reason,
|
|
resumeTaskId: payload.resumeTaskId,
|
|
isRetry: payload.isRetry,
|
|
});
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
export { executionWorker, workerQueue };
|