Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef1576c73f |
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "core-functions",
|
||||
"version": "1.0.0",
|
||||
"sideEffects": false,
|
||||
"scripts": {},
|
||||
"eslintIgnore": [
|
||||
"/node_modules",
|
||||
"/build",
|
||||
"/public/build"
|
||||
],
|
||||
"dependencies": {
|
||||
"@trigger.dev/functions": "workspace:*",
|
||||
"@trigger.dev/functions-worker": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^5.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"include": ["**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2019"],
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"target": "ES2019",
|
||||
"strict": true,
|
||||
"allowJs": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./app/*"],
|
||||
"@/*": ["./*"],
|
||||
"@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"],
|
||||
"@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"],
|
||||
"@trigger.dev/core": ["../../packages/core/src/index"],
|
||||
"@trigger.dev/core/*": ["../../packages/core/src/*"],
|
||||
"@trigger.dev/database": ["../../packages/database/src/index"],
|
||||
"@trigger.dev/database/*": ["../../packages/database/src/*"],
|
||||
"emails": ["../../packages/emails/src/index"],
|
||||
"emails/*": ["../../packages/emails/src/*"]
|
||||
},
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Task, TaskAttempt } from "@trigger.dev/database";
|
||||
import { ServerTask } from "@trigger.dev/core";
|
||||
import { PrismaClientOrTransaction } from "~/db.server";
|
||||
|
||||
export type TaskWithAttempts = Task & { attempts: TaskAttempt[] };
|
||||
|
||||
@@ -25,3 +26,20 @@ export function taskWithAttemptsToServerTask(task: TaskWithAttempts): ServerTask
|
||||
operation: task.operation,
|
||||
};
|
||||
}
|
||||
|
||||
export type KitchenSinkTask = NonNullable<Awaited<ReturnType<typeof findKitchenSinkTask>>>;
|
||||
|
||||
export async function findKitchenSinkTask(prisma: PrismaClientOrTransaction, id: string) {
|
||||
return prisma.task.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
attempts: true,
|
||||
run: {
|
||||
include: {
|
||||
environment: true,
|
||||
queue: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { ActionArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import { CreateBackgroundFunctionWorkerImageRequestBodySchema } from "@trigger.dev/core";
|
||||
import { z } from "zod";
|
||||
import { authenticateApiRequest } from "~/services/apiAuth.server";
|
||||
import { CreateBackgroundFunctionImageService } from "~/services/backgroundFunctions/createBackgroundFunctionImage.server";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export async function action({ request, params }: ActionArgs) {
|
||||
// Ensure this is a POST request
|
||||
if (request.method.toUpperCase() !== "POST") {
|
||||
return { status: 405, body: "Method Not Allowed" };
|
||||
}
|
||||
|
||||
// Next authenticate the request
|
||||
const authenticationResult = await authenticateApiRequest(request);
|
||||
|
||||
if (!authenticationResult) {
|
||||
return json({ error: "Invalid or Missing API key" }, { status: 401 });
|
||||
}
|
||||
|
||||
const parsedParams = ParamsSchema.safeParse(params);
|
||||
|
||||
if (!parsedParams.success) {
|
||||
return json({ error: "Invalid request params" }, { status: 400 });
|
||||
}
|
||||
|
||||
// Now parse the request body
|
||||
const anyBody = await request.json();
|
||||
|
||||
const body = CreateBackgroundFunctionWorkerImageRequestBodySchema.safeParse(anyBody);
|
||||
|
||||
if (!body.success) {
|
||||
return json({ error: "Invalid request body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const service = new CreateBackgroundFunctionImageService();
|
||||
|
||||
try {
|
||||
const image = await service.call(
|
||||
authenticationResult.environment,
|
||||
parsedParams.data.id,
|
||||
body.data
|
||||
);
|
||||
|
||||
if (!image) {
|
||||
return json(
|
||||
{
|
||||
error: `Unable to create background function worker image`,
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
return json(image);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return json({ error: error.message }, { status: 400 });
|
||||
}
|
||||
|
||||
return json({ error: "Something went wrong" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import { Response } from "@remix-run/node";
|
||||
import { LoaderArgs, json } from "@remix-run/server-runtime";
|
||||
import {
|
||||
BackgroundFunction,
|
||||
BackgroundFunctionArtifact,
|
||||
PrismaClient,
|
||||
} from "@trigger.dev/database";
|
||||
import archiver from "archiver";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "~/db.server";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export async function loader({ params }: LoaderArgs) {
|
||||
const parsedParams = ParamsSchema.safeParse(params);
|
||||
|
||||
if (!parsedParams.success) {
|
||||
return json({ error: "Invalid request params" }, { status: 400 });
|
||||
}
|
||||
|
||||
const service = new GenerateBackgroundFunctionArtifactArchiveService();
|
||||
|
||||
const results = await service.call(parsedParams.data.id);
|
||||
|
||||
if (!results) {
|
||||
return json({ error: "Something went wrong" }, { status: 500 });
|
||||
}
|
||||
|
||||
return new Response(results.archive, {
|
||||
headers: {
|
||||
"Content-Disposition": `attachment; filename="${results.name}"`,
|
||||
"Content-Type": "application/gzip",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
class GenerateBackgroundFunctionArtifactArchiveService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(id: string) {
|
||||
const artifact = await this.#prismaClient.backgroundFunctionArtifact.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
include: {
|
||||
backgroundFunction: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!artifact) {
|
||||
return;
|
||||
}
|
||||
|
||||
const archive = archiver("tar", {
|
||||
gzip: true,
|
||||
zlib: { level: 9 }, // Sets the compression level
|
||||
});
|
||||
|
||||
function addFileToContext(contents: string, path: string) {
|
||||
// Append files to the archive
|
||||
archive.append(contents, { name: `ctx/${path}` });
|
||||
}
|
||||
|
||||
// Good practice to catch warnings (ie stat failures and other non-blocking errors)
|
||||
archive.on("warning", function (err) {
|
||||
if (err.code === "ENOENT") {
|
||||
// log warning
|
||||
console.warn(err);
|
||||
} else {
|
||||
// throw error
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
// Good practice to catch this error explicitly
|
||||
archive.on("error", function (err) {
|
||||
throw err;
|
||||
});
|
||||
|
||||
addFileToContext(artifact.bundle, `src/${artifact.fileName}`);
|
||||
addFileToContext(
|
||||
JSON.stringify(this.#generatePackageJson(artifact, artifact.backgroundFunction)),
|
||||
"package.json"
|
||||
);
|
||||
addFileToContext(this.#generateDockerfile(artifact, artifact.backgroundFunction), "Dockerfile");
|
||||
addFileToContext(this.#generateIndexJs(artifact, artifact.backgroundFunction), "src/index.js");
|
||||
|
||||
// Finalize the archive
|
||||
archive.finalize();
|
||||
|
||||
return {
|
||||
archive,
|
||||
name: `${artifact.id}.tar.gz`,
|
||||
};
|
||||
}
|
||||
|
||||
#generatePackageJson(artifact: BackgroundFunctionArtifact, task: BackgroundFunction) {
|
||||
return {
|
||||
name: task.slug,
|
||||
version: artifact.version,
|
||||
description: `Trigger background function ${task.slug}`,
|
||||
main: "src/index.js",
|
||||
scripts: {
|
||||
start: "node src/index.js",
|
||||
},
|
||||
dependencies: artifact.dependencies,
|
||||
engines: {
|
||||
node: this.#getNodeVersion(artifact.nodeVersion),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#generateDockerfile(artifact: BackgroundFunctionArtifact, func: BackgroundFunction) {
|
||||
return `FROM node:${this.#getNodeVersion(artifact.nodeVersion)}-bullseye-slim
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY ctx/package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY ctx/. .
|
||||
|
||||
CMD [ "npm", "start" ]
|
||||
`;
|
||||
}
|
||||
|
||||
#generateIndexJs(artifact: BackgroundFunctionArtifact, func: BackgroundFunction) {
|
||||
return `
|
||||
const func = require("./${artifact.fileName}").default;
|
||||
console.log(JSON.stringify(func.toJSON()));
|
||||
console.log(process.env);
|
||||
`;
|
||||
}
|
||||
|
||||
// replace the v if it exists
|
||||
#getNodeVersion(version: string) {
|
||||
return version.replace("v", "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { ActionArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import { UploadBackgroundFunctionRequestBodySchema } from "@trigger.dev/core";
|
||||
import { authenticateApiRequest } from "~/services/apiAuth.server";
|
||||
import { UploadBackgroundFunctionService } from "~/services/backgroundFunctions/uploadBackgroundFunction.server";
|
||||
|
||||
export async function action({ request, params }: ActionArgs) {
|
||||
// Ensure this is a POST request
|
||||
if (request.method.toUpperCase() !== "POST") {
|
||||
return { status: 405, body: "Method Not Allowed" };
|
||||
}
|
||||
|
||||
// Next authenticate the request
|
||||
const authenticationResult = await authenticateApiRequest(request);
|
||||
|
||||
if (!authenticationResult) {
|
||||
return json({ error: "Invalid or Missing API key" }, { status: 401 });
|
||||
}
|
||||
|
||||
// Now parse the request body
|
||||
const anyBody = await request.json();
|
||||
|
||||
const body = UploadBackgroundFunctionRequestBodySchema.safeParse(anyBody);
|
||||
|
||||
if (!body.success) {
|
||||
return json({ error: "Invalid request body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const service = new UploadBackgroundFunctionService();
|
||||
|
||||
try {
|
||||
const artifact = await service.call(authenticationResult.environment, body.data);
|
||||
|
||||
if (!artifact) {
|
||||
return json(
|
||||
{
|
||||
error: `Unable to upload background function, function with ID = ${body.data.id} not found`,
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
return json({
|
||||
id: artifact.id,
|
||||
hash: artifact.hash,
|
||||
createdAt: artifact.createdAt,
|
||||
updatedAt: artifact.updatedAt,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return json({ error: error.message }, { status: 400 });
|
||||
}
|
||||
|
||||
return json({ error: "Something went wrong" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,10 @@
|
||||
import type { ActionArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import { TaskStatus } from "@trigger.dev/database";
|
||||
import { RunTaskBodyOutput, RunTaskBodyOutputSchema, ServerTask } from "@trigger.dev/core";
|
||||
import { RunTaskBodyOutputSchema } from "@trigger.dev/core";
|
||||
import { z } from "zod";
|
||||
import { $transaction, PrismaClient, prisma } from "~/db.server";
|
||||
import { taskWithAttemptsToServerTask } from "~/models/task.server";
|
||||
import { authenticateApiRequest } from "~/services/apiAuth.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { ulid } from "~/services/ulid.server";
|
||||
import { workerQueue } from "~/services/worker.server";
|
||||
import { RunTaskService } from "~/services/tasks/runTask.server";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
runId: z.string(),
|
||||
@@ -80,148 +76,3 @@ export async function action({ request, params }: ActionArgs) {
|
||||
return json({ error: "Something went wrong" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export class RunTaskService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(
|
||||
runId: string,
|
||||
idempotencyKey: string,
|
||||
taskBody: RunTaskBodyOutput
|
||||
): Promise<ServerTask | undefined> {
|
||||
const task = await $transaction(this.#prismaClient, async (tx) => {
|
||||
const existingTask = await tx.task.findUnique({
|
||||
where: {
|
||||
runId_idempotencyKey: {
|
||||
runId,
|
||||
idempotencyKey,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
attempts: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingTask) {
|
||||
if (existingTask.status === "CANCELED") {
|
||||
const existingTaskStatus =
|
||||
(taskBody.delayUntil && taskBody.delayUntil.getTime() > Date.now()) || taskBody.trigger
|
||||
? "WAITING"
|
||||
: taskBody.noop
|
||||
? "COMPLETED"
|
||||
: "RUNNING";
|
||||
|
||||
const resumedExistingTask = await tx.task.update({
|
||||
where: {
|
||||
id: existingTask.id,
|
||||
},
|
||||
data: {
|
||||
status: existingTaskStatus,
|
||||
startedAt: new Date(),
|
||||
completedAt: existingTaskStatus === "COMPLETED" ? new Date() : undefined,
|
||||
},
|
||||
include: {
|
||||
run: true,
|
||||
attempts: true,
|
||||
},
|
||||
});
|
||||
|
||||
return resumedExistingTask;
|
||||
}
|
||||
|
||||
return existingTask;
|
||||
}
|
||||
|
||||
const run = await tx.jobRun.findUnique({
|
||||
where: {
|
||||
id: runId,
|
||||
},
|
||||
select: {
|
||||
status: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!run) throw new Error("Run not found");
|
||||
|
||||
// If task.delayUntil is set and is in the future, we'll set the task's status to "WAITING", else set it to RUNNING
|
||||
let status: TaskStatus;
|
||||
|
||||
if (run.status === "CANCELED") {
|
||||
status = "CANCELED";
|
||||
} else {
|
||||
status =
|
||||
(taskBody.delayUntil && taskBody.delayUntil.getTime() > Date.now()) || taskBody.trigger
|
||||
? "WAITING"
|
||||
: taskBody.noop
|
||||
? "COMPLETED"
|
||||
: "RUNNING";
|
||||
}
|
||||
|
||||
const task = await tx.task.create({
|
||||
data: {
|
||||
id: ulid(),
|
||||
idempotencyKey,
|
||||
displayKey: taskBody.displayKey,
|
||||
runConnection: taskBody.connectionKey
|
||||
? {
|
||||
connect: {
|
||||
runId_key: {
|
||||
runId,
|
||||
key: taskBody.connectionKey,
|
||||
},
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
icon: taskBody.icon,
|
||||
run: {
|
||||
connect: {
|
||||
id: runId,
|
||||
},
|
||||
},
|
||||
parent: taskBody.parentId ? { connect: { id: taskBody.parentId } } : undefined,
|
||||
name: taskBody.name ?? "Task",
|
||||
description: taskBody.description,
|
||||
status,
|
||||
startedAt: new Date(),
|
||||
completedAt: status === "COMPLETED" || status === "CANCELED" ? new Date() : undefined,
|
||||
noop: taskBody.noop,
|
||||
delayUntil: taskBody.delayUntil,
|
||||
params: taskBody.params ?? undefined,
|
||||
properties: taskBody.properties ?? undefined,
|
||||
redact: taskBody.redact ?? undefined,
|
||||
operation: taskBody.operation,
|
||||
style: taskBody.style ?? { style: "normal" },
|
||||
attempts: {
|
||||
create: {
|
||||
number: 1,
|
||||
status: "PENDING",
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
run: true,
|
||||
attempts: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (task.status === "RUNNING" && typeof taskBody.operation === "string") {
|
||||
// We need to schedule the operation
|
||||
await workerQueue.enqueue(
|
||||
"performTaskOperation",
|
||||
{
|
||||
id: task.id,
|
||||
},
|
||||
{ tx, runAt: task.delayUntil ?? undefined }
|
||||
);
|
||||
}
|
||||
|
||||
return task;
|
||||
});
|
||||
|
||||
return task ? taskWithAttemptsToServerTask(task) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { CreateBackgroundFunctionWorkerImageRequestBody } from "@trigger.dev/core";
|
||||
import { PrismaClient } from "@trigger.dev/database";
|
||||
import { prisma } from "~/db.server";
|
||||
import { AuthenticatedEnvironment } from "../apiAuth.server";
|
||||
|
||||
export class CreateBackgroundFunctionImageService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(
|
||||
environment: AuthenticatedEnvironment,
|
||||
id: string,
|
||||
payload: CreateBackgroundFunctionWorkerImageRequestBody
|
||||
) {
|
||||
// Find the artifact
|
||||
const artifact = await this.#prismaClient.backgroundFunctionArtifact.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
include: {
|
||||
backgroundFunction: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!artifact) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (artifact.backgroundFunction.projectId !== environment.projectId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const image = await this.#prismaClient.backgroundFunctionImage.upsert({
|
||||
where: {
|
||||
backgroundFunctionArtifactId_digest: {
|
||||
backgroundFunctionArtifactId: artifact.id,
|
||||
digest: payload.digest,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
backgroundFunctionArtifactId: artifact.id,
|
||||
backgroundFunctionId: artifact.backgroundFunctionId,
|
||||
digest: payload.digest,
|
||||
name: payload.name,
|
||||
tag: payload.tag,
|
||||
size: payload.size,
|
||||
registry: payload.registry,
|
||||
},
|
||||
update: {
|
||||
name: payload.name,
|
||||
tag: payload.tag,
|
||||
size: payload.size,
|
||||
registry: payload.registry,
|
||||
},
|
||||
});
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { BackgroundFunctionVersion } from "@trigger.dev/database";
|
||||
import type { PrismaClient } from "~/db.server";
|
||||
import { prisma } from "~/db.server";
|
||||
import { ExtendedEndpoint, findEndpoint } from "~/models/endpoint.server";
|
||||
import type { AuthenticatedEnvironment } from "../apiAuth.server";
|
||||
|
||||
export type DisableBackgroundFunctionServiceOptions = {
|
||||
slug: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export class DisableBackgroundFunctionService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(
|
||||
endpointIdOrEndpoint: string | ExtendedEndpoint,
|
||||
options: DisableBackgroundFunctionServiceOptions
|
||||
) {
|
||||
const endpoint =
|
||||
typeof endpointIdOrEndpoint === "string"
|
||||
? await findEndpoint(endpointIdOrEndpoint)
|
||||
: endpointIdOrEndpoint;
|
||||
|
||||
return this.#disableBackgroundFunction(endpoint.environment, options);
|
||||
}
|
||||
|
||||
async #disableBackgroundFunction(
|
||||
environment: AuthenticatedEnvironment,
|
||||
options: DisableBackgroundFunctionServiceOptions
|
||||
): Promise<BackgroundFunctionVersion | undefined> {
|
||||
const backgroundFunction = await this.#prismaClient.backgroundFunction.findUnique({
|
||||
where: {
|
||||
projectId_slug: {
|
||||
projectId: environment.projectId,
|
||||
slug: options.slug,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!backgroundFunction) {
|
||||
return;
|
||||
}
|
||||
|
||||
const backgroundFunctionVersion = await this.#prismaClient.backgroundFunctionVersion.findUnique(
|
||||
{
|
||||
where: {
|
||||
backgroundFunctionId_version_environmentId: {
|
||||
backgroundFunctionId: backgroundFunction.id,
|
||||
version: options.version,
|
||||
environmentId: environment.id,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!backgroundFunctionVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Disable background task
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
import { BackgroundFunctionTaskParamsSchema } from "@trigger.dev/core";
|
||||
import { PrismaClient, RuntimeEnvironmentType, Task } from "@trigger.dev/database";
|
||||
import { $transaction, prisma } from "~/db.server";
|
||||
import { enqueueRunExecutionV2 } from "~/models/jobRunExecution.server";
|
||||
import { KitchenSinkTask } from "~/models/task.server";
|
||||
import { workerQueue } from "../worker.server";
|
||||
|
||||
export class InitializeBackgroundFunctionTaskService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(task: KitchenSinkTask) {
|
||||
const params = BackgroundFunctionTaskParamsSchema.safeParse(task.params);
|
||||
// We need to create a new background task operation
|
||||
|
||||
if (!params.success) {
|
||||
await this.#resumeTaskWithError(task, params.error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const backgroundFunction = await this.#prismaClient.backgroundFunction.findUnique({
|
||||
where: {
|
||||
projectId_slug: {
|
||||
projectId: task.run.projectId,
|
||||
slug: params.data.id,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
versions: {
|
||||
where: {
|
||||
version: params.data.version,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!backgroundFunction) {
|
||||
await this.#resumeTaskWithError(task, `Background function ${params.data.id} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const version = backgroundFunction.versions[0];
|
||||
|
||||
if (!version) {
|
||||
await this.#resumeTaskWithError(
|
||||
task,
|
||||
`Background task ${params.data.id} version ${params.data.version} not found`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await $transaction(this.#prismaClient, async (tx) => {
|
||||
const functionTask = await tx.backgroundFunctionTask.create({
|
||||
data: {
|
||||
backgroundFunctionId: backgroundFunction.id,
|
||||
backgroundFunctionVersionId: version.id,
|
||||
taskId: task.id,
|
||||
payload: params.data.payload,
|
||||
},
|
||||
});
|
||||
|
||||
await workerQueue.enqueue(
|
||||
"executeBackgroundFunctionTask",
|
||||
{
|
||||
id: functionTask.id,
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
|
||||
return functionTask;
|
||||
});
|
||||
}
|
||||
|
||||
async #resumeTaskWithError(task: KitchenSinkTask, message: string) {
|
||||
await $transaction(this.#prismaClient, async (tx) => {
|
||||
await tx.task.update({
|
||||
where: { id: task.id },
|
||||
data: {
|
||||
status: "ERRORED",
|
||||
completedAt: new Date(),
|
||||
output: { message },
|
||||
},
|
||||
});
|
||||
|
||||
await tx.taskAttempt.updateMany({
|
||||
where: {
|
||||
taskId: task.id,
|
||||
status: "PENDING",
|
||||
},
|
||||
data: {
|
||||
status: "ERRORED",
|
||||
error: message,
|
||||
},
|
||||
});
|
||||
|
||||
await enqueueRunExecutionV2(task.run, prisma, {
|
||||
skipRetrying: task.run.environment.type === RuntimeEnvironmentType.DEVELOPMENT,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import { BackgroundFunctionMetadata } from "@trigger.dev/core";
|
||||
import type { BackgroundFunctionVersion, Endpoint } from "@trigger.dev/database";
|
||||
import type { PrismaClient } from "~/db.server";
|
||||
import { prisma } from "~/db.server";
|
||||
import { ExtendedEndpoint, findEndpoint } from "~/models/endpoint.server";
|
||||
import type { AuthenticatedEnvironment } from "../apiAuth.server";
|
||||
|
||||
export class RegisterBackgroundFunctionService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(
|
||||
endpointIdOrEndpoint: string | ExtendedEndpoint,
|
||||
metadata: BackgroundFunctionMetadata
|
||||
) {
|
||||
const endpoint =
|
||||
typeof endpointIdOrEndpoint === "string"
|
||||
? await findEndpoint(endpointIdOrEndpoint)
|
||||
: endpointIdOrEndpoint;
|
||||
|
||||
return this.#upsertBackgroundFunction(endpoint, endpoint.environment, metadata);
|
||||
}
|
||||
|
||||
async #upsertBackgroundFunction(
|
||||
endpoint: Endpoint,
|
||||
environment: AuthenticatedEnvironment,
|
||||
metadata: BackgroundFunctionMetadata
|
||||
): Promise<BackgroundFunctionVersion | undefined> {
|
||||
// Check the background task doesn't already exist and is deleted
|
||||
const existingBackgroundFunction = await this.#prismaClient.backgroundFunction.findUnique({
|
||||
where: {
|
||||
projectId_slug: {
|
||||
projectId: environment.projectId,
|
||||
slug: metadata.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (existingBackgroundFunction && existingBackgroundFunction.deletedAt && !metadata.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const backgroundFunction = await this.#prismaClient.backgroundFunction.upsert({
|
||||
where: {
|
||||
projectId_slug: {
|
||||
projectId: environment.projectId,
|
||||
slug: metadata.id,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
organization: {
|
||||
connect: {
|
||||
id: environment.organizationId,
|
||||
},
|
||||
},
|
||||
project: {
|
||||
connect: {
|
||||
id: environment.projectId,
|
||||
},
|
||||
},
|
||||
slug: metadata.id,
|
||||
title: metadata.name,
|
||||
},
|
||||
update: {
|
||||
title: metadata.name,
|
||||
deletedAt: metadata.enabled ? null : undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const backgroundFunctionVersion = await this.#prismaClient.backgroundFunctionVersion.upsert({
|
||||
where: {
|
||||
backgroundFunctionId_version_environmentId: {
|
||||
backgroundFunctionId: backgroundFunction.id,
|
||||
version: metadata.version,
|
||||
environmentId: environment.id,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
backgroundFunction: {
|
||||
connect: {
|
||||
id: backgroundFunction.id,
|
||||
},
|
||||
},
|
||||
endpoint: {
|
||||
connect: {
|
||||
id: endpoint.id,
|
||||
},
|
||||
},
|
||||
environment: {
|
||||
connect: {
|
||||
id: environment.id,
|
||||
},
|
||||
},
|
||||
organization: {
|
||||
connect: {
|
||||
id: environment.organizationId,
|
||||
},
|
||||
},
|
||||
project: {
|
||||
connect: {
|
||||
id: environment.projectId,
|
||||
},
|
||||
},
|
||||
version: metadata.version,
|
||||
},
|
||||
update: {
|
||||
endpoint: {
|
||||
connect: {
|
||||
id: endpoint.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Count the number of job instances that have higher version numbers
|
||||
const laterVersionCount = await this.#prismaClient.backgroundFunctionVersion.count({
|
||||
where: {
|
||||
backgroundFunctionId: backgroundFunction.id,
|
||||
version: {
|
||||
gt: metadata.version,
|
||||
},
|
||||
environmentId: environment.id,
|
||||
},
|
||||
});
|
||||
|
||||
// If there are no later versions, then we can upsert the latest BackgroundFunctionAlias
|
||||
if (laterVersionCount === 0) {
|
||||
await this.#prismaClient.backgroundFunctionAlias.upsert({
|
||||
where: {
|
||||
backgroundFunctionId_environmentId_name: {
|
||||
backgroundFunctionId: backgroundFunction.id,
|
||||
environmentId: environment.id,
|
||||
name: "latest",
|
||||
},
|
||||
},
|
||||
create: {
|
||||
backgroundFunctionId: backgroundFunction.id,
|
||||
versionId: backgroundFunctionVersion.id,
|
||||
environmentId: environment.id,
|
||||
name: "latest",
|
||||
value: backgroundFunctionVersion.version,
|
||||
},
|
||||
update: {
|
||||
versionId: backgroundFunctionVersion.id,
|
||||
value: backgroundFunctionVersion.version,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return backgroundFunctionVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { UploadBackgroundFunctionRequestBody } from "@trigger.dev/core";
|
||||
import { PrismaClient } from "@trigger.dev/database";
|
||||
import nodeCrypto from "node:crypto";
|
||||
import { prisma } from "~/db.server";
|
||||
import { AuthenticatedEnvironment } from "../apiAuth.server";
|
||||
|
||||
export class UploadBackgroundFunctionService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(
|
||||
environment: AuthenticatedEnvironment,
|
||||
payload: UploadBackgroundFunctionRequestBody
|
||||
) {
|
||||
const hash = this.#hashPayload(payload);
|
||||
|
||||
const backgroundFunction = await this.#prismaClient.backgroundFunction.findUnique({
|
||||
where: {
|
||||
projectId_slug: {
|
||||
projectId: environment.projectId,
|
||||
slug: payload.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!backgroundFunction) {
|
||||
return;
|
||||
}
|
||||
|
||||
const artifact = await this.#prismaClient.backgroundFunctionArtifact.upsert({
|
||||
where: {
|
||||
backgroundFunctionId_version_hash: {
|
||||
backgroundFunctionId: backgroundFunction.id,
|
||||
version: payload.version,
|
||||
hash,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
backgroundFunctionId: backgroundFunction.id,
|
||||
fileName: payload.fileName,
|
||||
version: payload.version,
|
||||
hash,
|
||||
bundle: payload.bundle,
|
||||
nodeVersion: payload.nodeVersion,
|
||||
dependencies: payload.dependencies,
|
||||
sourcemap: payload.sourcemap,
|
||||
},
|
||||
update: {
|
||||
fileName: payload.fileName,
|
||||
bundle: payload.bundle,
|
||||
nodeVersion: payload.nodeVersion,
|
||||
dependencies: payload.dependencies,
|
||||
sourcemap: payload.sourcemap,
|
||||
},
|
||||
});
|
||||
|
||||
return artifact;
|
||||
}
|
||||
|
||||
#hashPayload(payload: UploadBackgroundFunctionRequestBody) {
|
||||
// Create a hash out of the bundle, the nodeVersion, and a determinstically list of dependencies
|
||||
// This will allow us to determine if the bundle has changed
|
||||
const hash = nodeCrypto.createHash("sha256");
|
||||
|
||||
hash.update(payload.bundle);
|
||||
hash.update(payload.nodeVersion);
|
||||
|
||||
const dependencies = Object.keys(payload.dependencies).sort();
|
||||
|
||||
for (const dependency of dependencies) {
|
||||
hash.update(dependency);
|
||||
hash.update(payload.dependencies[dependency]);
|
||||
}
|
||||
|
||||
return hash.digest("hex");
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import { RegisterDynamicScheduleService } from "../triggers/registerDynamicSched
|
||||
import { RegisterDynamicTriggerService } from "../triggers/registerDynamicTrigger.server";
|
||||
import { DisableJobService } from "../jobs/disableJob.server";
|
||||
import { RegisterSourceServiceV2 } from "../sources/registerSourceV2.server";
|
||||
import { DisableBackgroundFunctionService } from "../backgroundFunctions/disableBackgroundFunction.server";
|
||||
import { RegisterBackgroundFunctionService } from "../backgroundFunctions/registerBackgroundFunction.server";
|
||||
|
||||
export class IndexEndpointService {
|
||||
#prismaClient: PrismaClient;
|
||||
@@ -18,6 +20,8 @@ export class IndexEndpointService {
|
||||
#registerSourceServiceV2 = new RegisterSourceServiceV2();
|
||||
#registerDynamicTriggerService = new RegisterDynamicTriggerService();
|
||||
#registerDynamicScheduleService = new RegisterDynamicScheduleService();
|
||||
#registerBackgroundFunctionService = new RegisterBackgroundFunctionService();
|
||||
#disableBackgroundFunctionService = new DisableBackgroundFunctionService();
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
@@ -40,7 +44,13 @@ export class IndexEndpointService {
|
||||
throw new Error(indexResponse.error);
|
||||
}
|
||||
|
||||
const { jobs, sources, dynamicTriggers, dynamicSchedules } = indexResponse.data;
|
||||
const {
|
||||
jobs,
|
||||
sources,
|
||||
dynamicTriggers,
|
||||
dynamicSchedules,
|
||||
backgroundFunctions = [],
|
||||
} = indexResponse.data;
|
||||
|
||||
logger.debug("Indexing endpoint", {
|
||||
endpointId: endpoint.id,
|
||||
@@ -62,6 +72,8 @@ export class IndexEndpointService {
|
||||
dynamicTriggers: 0,
|
||||
dynamicSchedules: 0,
|
||||
disabledJobs: 0,
|
||||
backgroundFunctions: 0,
|
||||
disabledBackgroundFunctions: 0,
|
||||
};
|
||||
|
||||
const existingJobs = await this.#prismaClient.job.findMany({
|
||||
@@ -208,6 +220,100 @@ export class IndexEndpointService {
|
||||
}
|
||||
}
|
||||
|
||||
const existingBackgroundFunctions = await this.#prismaClient.backgroundFunction.findMany({
|
||||
where: {
|
||||
projectId: endpoint.projectId,
|
||||
deletedAt: null,
|
||||
},
|
||||
include: {
|
||||
aliases: {
|
||||
where: {
|
||||
name: "latest",
|
||||
environmentId: endpoint.environmentId,
|
||||
},
|
||||
include: {
|
||||
version: true,
|
||||
},
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const backgroundFunction of backgroundFunctions) {
|
||||
if (!backgroundFunction.enabled) {
|
||||
const disabledBackgroundFunction = await this.#disableBackgroundFunctionService
|
||||
.call(endpoint, { slug: backgroundFunction.id, version: backgroundFunction.version })
|
||||
.catch((error) => {
|
||||
logger.error("Failed to disable background task", {
|
||||
endpointId: endpoint.id,
|
||||
backgroundFunction,
|
||||
error,
|
||||
});
|
||||
|
||||
return;
|
||||
});
|
||||
|
||||
if (disabledBackgroundFunction) {
|
||||
indexStats.disabledJobs++;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const registeredVersion = await this.#registerBackgroundFunctionService.call(
|
||||
endpoint,
|
||||
backgroundFunction
|
||||
);
|
||||
|
||||
if (registeredVersion) {
|
||||
indexStats.backgroundFunctions++;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Failed to register background task", {
|
||||
endpointId: endpoint.id,
|
||||
backgroundFunction,
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const missingBackgroundFunctions = existingBackgroundFunctions.filter((backgroundFunction) => {
|
||||
return !backgroundFunctions.find((b) => b.id === backgroundFunction.slug);
|
||||
});
|
||||
|
||||
if (missingBackgroundFunctions.length > 0) {
|
||||
logger.debug("Disabling missing background tasks", {
|
||||
endpointId: endpoint.id,
|
||||
missingIds: missingBackgroundFunctions.map((job) => job.slug),
|
||||
});
|
||||
|
||||
for (const backgroundFunction of missingBackgroundFunctions) {
|
||||
const latestVersion = backgroundFunction.aliases[0]?.version;
|
||||
|
||||
if (!latestVersion) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const disabledBackgroundFunction = await this.#disableBackgroundFunctionService
|
||||
.call(endpoint, {
|
||||
slug: backgroundFunction.slug,
|
||||
version: latestVersion.version,
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error("Failed to disable background task", {
|
||||
endpointId: endpoint.id,
|
||||
backgroundFunction,
|
||||
error,
|
||||
});
|
||||
|
||||
return;
|
||||
});
|
||||
|
||||
if (disabledBackgroundFunction) {
|
||||
indexStats.disabledJobs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Endpoint indexing complete", {
|
||||
endpointId: endpoint.id,
|
||||
indexStats,
|
||||
|
||||
@@ -15,8 +15,7 @@ import { formatUnknownError } from "~/utils/formatErrors.server";
|
||||
import { safeJsonFromResponse } from "~/utils/json";
|
||||
import { logger } from "../logger.server";
|
||||
import { workerQueue } from "../worker.server";
|
||||
|
||||
type FoundTask = Awaited<ReturnType<typeof findTask>>;
|
||||
import { KitchenSinkTask, findKitchenSinkTask } from "~/models/task.server";
|
||||
|
||||
export class PerformTaskOperationService {
|
||||
#prismaClient: PrismaClient;
|
||||
@@ -26,7 +25,7 @@ export class PerformTaskOperationService {
|
||||
}
|
||||
|
||||
public async call(id: string) {
|
||||
const task = await findTask(this.#prismaClient, id);
|
||||
const task = await findKitchenSinkTask(this.#prismaClient, id);
|
||||
|
||||
if (!task) {
|
||||
return;
|
||||
@@ -104,7 +103,7 @@ export class PerformTaskOperationService {
|
||||
}
|
||||
|
||||
#calculateRetryForResponse(
|
||||
task: NonNullable<FoundTask>,
|
||||
task: KitchenSinkTask,
|
||||
retry: FetchRetryOptions | undefined,
|
||||
response: Response
|
||||
): Date | undefined {
|
||||
@@ -194,7 +193,7 @@ export class PerformTaskOperationService {
|
||||
});
|
||||
}
|
||||
|
||||
async #resumeTaskWithError(task: NonNullable<FoundTask>, output: any) {
|
||||
async #resumeTaskWithError(task: KitchenSinkTask, output: any) {
|
||||
await $transaction(this.#prismaClient, async (tx) => {
|
||||
await tx.task.update({
|
||||
where: { id: task.id },
|
||||
@@ -220,7 +219,7 @@ export class PerformTaskOperationService {
|
||||
});
|
||||
}
|
||||
|
||||
async #resumeTask(task: NonNullable<FoundTask>, output: any) {
|
||||
async #resumeTask(task: KitchenSinkTask, output: any) {
|
||||
await $transaction(this.#prismaClient, async (tx) => {
|
||||
await tx.taskAttempt.updateMany({
|
||||
where: {
|
||||
@@ -245,7 +244,7 @@ export class PerformTaskOperationService {
|
||||
});
|
||||
}
|
||||
|
||||
async #resumeRunExecution(task: NonNullable<FoundTask>, prisma: PrismaClientOrTransaction) {
|
||||
async #resumeRunExecution(task: KitchenSinkTask, prisma: PrismaClientOrTransaction) {
|
||||
await enqueueRunExecutionV2(task.run, prisma, {
|
||||
skipRetrying: task.run.environment.type === RuntimeEnvironmentType.DEVELOPMENT,
|
||||
});
|
||||
@@ -278,21 +277,6 @@ function hydrateRedactedString(value: RedactString): string {
|
||||
return result;
|
||||
}
|
||||
|
||||
async function findTask(prisma: PrismaClient, id: string) {
|
||||
return prisma.task.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
attempts: true,
|
||||
run: {
|
||||
include: {
|
||||
environment: true,
|
||||
queue: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Add a random number of ms between 0ms and 5000ms
|
||||
function addJitterInMs() {
|
||||
return Math.floor(Math.random() * 5000);
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import { RunTaskBodyOutput, ServerTask } from "@trigger.dev/core";
|
||||
import { TaskStatus } from "@trigger.dev/database";
|
||||
import { $transaction, PrismaClient, prisma } from "~/db.server";
|
||||
import { taskWithAttemptsToServerTask } from "~/models/task.server";
|
||||
import { ulid } from "~/services/ulid.server";
|
||||
import { workerQueue } from "~/services/worker.server";
|
||||
|
||||
export class RunTaskService {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call(
|
||||
runId: string,
|
||||
idempotencyKey: string,
|
||||
taskBody: RunTaskBodyOutput
|
||||
): Promise<ServerTask | undefined> {
|
||||
const task = await $transaction(this.#prismaClient, async (tx) => {
|
||||
const existingTask = await tx.task.findUnique({
|
||||
where: {
|
||||
runId_idempotencyKey: {
|
||||
runId,
|
||||
idempotencyKey,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
attempts: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingTask) {
|
||||
if (existingTask.status === "CANCELED") {
|
||||
const existingTaskStatus =
|
||||
(taskBody.delayUntil && taskBody.delayUntil.getTime() > Date.now()) || taskBody.trigger
|
||||
? "WAITING"
|
||||
: taskBody.noop
|
||||
? "COMPLETED"
|
||||
: "RUNNING";
|
||||
|
||||
const resumedExistingTask = await tx.task.update({
|
||||
where: {
|
||||
id: existingTask.id,
|
||||
},
|
||||
data: {
|
||||
status: existingTaskStatus,
|
||||
startedAt: new Date(),
|
||||
completedAt: existingTaskStatus === "COMPLETED" ? new Date() : undefined,
|
||||
},
|
||||
include: {
|
||||
run: true,
|
||||
attempts: true,
|
||||
},
|
||||
});
|
||||
|
||||
return resumedExistingTask;
|
||||
}
|
||||
|
||||
return existingTask;
|
||||
}
|
||||
|
||||
const run = await tx.jobRun.findUnique({
|
||||
where: {
|
||||
id: runId,
|
||||
},
|
||||
select: {
|
||||
status: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!run) throw new Error("Run not found");
|
||||
|
||||
// If task.delayUntil is set and is in the future, we'll set the task's status to "WAITING", else set it to RUNNING
|
||||
let status: TaskStatus;
|
||||
|
||||
if (run.status === "CANCELED") {
|
||||
status = "CANCELED";
|
||||
} else {
|
||||
status =
|
||||
(taskBody.delayUntil && taskBody.delayUntil.getTime() > Date.now()) || taskBody.trigger
|
||||
? "WAITING"
|
||||
: taskBody.noop
|
||||
? "COMPLETED"
|
||||
: "RUNNING";
|
||||
}
|
||||
|
||||
const task = await tx.task.create({
|
||||
data: {
|
||||
id: ulid(),
|
||||
idempotencyKey,
|
||||
displayKey: taskBody.displayKey,
|
||||
runConnection: taskBody.connectionKey
|
||||
? {
|
||||
connect: {
|
||||
runId_key: {
|
||||
runId,
|
||||
key: taskBody.connectionKey,
|
||||
},
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
icon: taskBody.icon,
|
||||
run: {
|
||||
connect: {
|
||||
id: runId,
|
||||
},
|
||||
},
|
||||
parent: taskBody.parentId ? { connect: { id: taskBody.parentId } } : undefined,
|
||||
name: taskBody.name ?? "Task",
|
||||
description: taskBody.description,
|
||||
status,
|
||||
startedAt: new Date(),
|
||||
completedAt: status === "COMPLETED" || status === "CANCELED" ? new Date() : undefined,
|
||||
noop: taskBody.noop,
|
||||
delayUntil: taskBody.delayUntil,
|
||||
params: taskBody.params ?? undefined,
|
||||
properties: taskBody.properties ?? undefined,
|
||||
redact: taskBody.redact ?? undefined,
|
||||
operation: taskBody.operation,
|
||||
style: taskBody.style ?? { style: "normal" },
|
||||
attempts: {
|
||||
create: {
|
||||
number: 1,
|
||||
status: "PENDING",
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
run: true,
|
||||
attempts: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (task.status === "RUNNING" && typeof taskBody.operation === "string") {
|
||||
// We need to schedule the operation
|
||||
await workerQueue.enqueue(
|
||||
"performTaskOperation",
|
||||
{
|
||||
id: task.id,
|
||||
},
|
||||
{ tx, runAt: task.delayUntil ?? undefined }
|
||||
);
|
||||
}
|
||||
|
||||
return task;
|
||||
});
|
||||
|
||||
return task ? taskWithAttemptsToServerTask(task) : undefined;
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,9 @@ const workerCatalog = {
|
||||
connectionCreated: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
executeBackgroundFunctionTask: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
};
|
||||
|
||||
const executionWorkerCatalog = {
|
||||
@@ -285,6 +288,11 @@ function getWorkerQueue() {
|
||||
});
|
||||
},
|
||||
},
|
||||
executeBackgroundFunctionTask: {
|
||||
priority: 0, // smaller number = higher priority
|
||||
maxAttempts: 3,
|
||||
handler: async (payload, job) => {},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,10 +61,11 @@
|
||||
"@remix-run/server-runtime": "1.19.2-pre.0",
|
||||
"@team-plain/typescript-sdk": "^2.2.0",
|
||||
"@trigger.dev/companyicons": "^1.5.14",
|
||||
"@trigger.dev/database": "workspace:*",
|
||||
"@trigger.dev/core": "workspace:*",
|
||||
"@trigger.dev/database": "workspace:*",
|
||||
"@trigger.dev/sdk": "workspace:*",
|
||||
"@uiw/react-codemirror": "^4.19.5",
|
||||
"archiver": "^6.0.1",
|
||||
"class-variance-authority": "^0.5.2",
|
||||
"clsx": "^1.2.1",
|
||||
"compression": "^1.7.4",
|
||||
@@ -134,6 +135,7 @@
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@total-typescript/ts-reset": "^0.4.2",
|
||||
"@trigger.dev/tailwind-config": "workspace:*",
|
||||
"@types/archiver": "^5.3.2",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/compression": "^1.7.2",
|
||||
"@types/eslint": "^8.4.6",
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
@@ -0,0 +1,34 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
@@ -0,0 +1,4 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {}
|
||||
|
||||
module.exports = nextConfig
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@examples/background-functions",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/nextjs": "workspace:*",
|
||||
"@trigger.dev/sdk": "workspace:*",
|
||||
"@trigger.dev/functions": "workspace:*",
|
||||
"@types/node": "20.5.9",
|
||||
"@types/react": "18.2.17",
|
||||
"@types/react-dom": "18.2.7",
|
||||
"autoprefixer": "10.4.15",
|
||||
"next": "13.4.19",
|
||||
"postcss": "8.4.29",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"tailwindcss": "3.3.3",
|
||||
"typescript": "5.2.2",
|
||||
"zod": "3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@trigger.dev/cli": "workspace:*"
|
||||
},
|
||||
"trigger.dev": {
|
||||
"endpointId": "background-tasks-z2kp"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
||||
|
After Width: | Height: | Size: 629 B |
@@ -0,0 +1,9 @@
|
||||
|
||||
import { createAppRoute } from "@trigger.dev/nextjs";
|
||||
import { client } from "@/trigger";
|
||||
|
||||
|
||||
import "@/jobs";
|
||||
|
||||
//this route is used to send and receive data with Trigger.dev
|
||||
export const { POST, dynamic } = createAppRoute(client);
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,27 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import './globals.css'
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Create Next App',
|
||||
description: 'Generated by create next app',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import Image from 'next/image'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-between p-24">
|
||||
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
|
||||
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
|
||||
Get started by editing
|
||||
<code className="font-mono font-bold">src/app/page.tsx</code>
|
||||
</p>
|
||||
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
|
||||
<a
|
||||
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
|
||||
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
By{' '}
|
||||
<Image
|
||||
src="/vercel.svg"
|
||||
alt="Vercel Logo"
|
||||
className="dark:invert"
|
||||
width={100}
|
||||
height={24}
|
||||
priority
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative flex place-items-center before:absolute before:h-[300px] before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]">
|
||||
<Image
|
||||
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js Logo"
|
||||
width={180}
|
||||
height={37}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
|
||||
<a
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Docs{' '}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Find in-depth information about Next.js features and API.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Learn{' '}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Learn about Next.js in an interactive course with quizzes!
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Templates{' '}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Explore the Next.js 13 playground.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Deploy{' '}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Instantly deploy your Next.js site to a shareable URL with Vercel.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// tasks.background.ts
|
||||
import { client } from "@/trigger";
|
||||
import { z } from "zod";
|
||||
|
||||
export default client.defineBackgroundFunction({
|
||||
id: "function-1",
|
||||
name: "Function 1",
|
||||
version: "1.0.2",
|
||||
schema: z.object({
|
||||
userName: z.string(),
|
||||
}),
|
||||
run: async (payload) => {
|
||||
// This code will run in the background, as it will be bundled and shipped to a trigger.dev background worker
|
||||
await new Promise((resolve) => setTimeout(resolve, 100000));
|
||||
|
||||
return {
|
||||
username: payload.userName,
|
||||
foo: "bar",
|
||||
message: `Function Response for user ${payload.userName}`,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { eventTrigger } from "@trigger.dev/sdk";
|
||||
import { client } from "@/trigger";
|
||||
import { fetchFunction } from "@trigger.dev/functions";
|
||||
|
||||
client.defineJob({
|
||||
id: "function-usage-1",
|
||||
name: "Background Function Usage",
|
||||
version: "0.0.1",
|
||||
trigger: eventTrigger({
|
||||
name: "example.event",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
const output = await fetchFunction.invoke("fetch-1", {
|
||||
userName: "ericallam",
|
||||
});
|
||||
|
||||
return { output };
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
// import all your job files here
|
||||
|
||||
export * from "./examples"
|
||||
@@ -0,0 +1,7 @@
|
||||
import { TriggerClient } from "@trigger.dev/sdk";
|
||||
|
||||
export const client = new TriggerClient({
|
||||
id: "background-tasks-z2kp",
|
||||
apiKey: process.env.TRIGGER_API_KEY,
|
||||
apiUrl: process.env.TRIGGER_API_URL,
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { Config } from 'tailwindcss'
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
||||
'gradient-conic':
|
||||
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
export default config
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"],
|
||||
"@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"],
|
||||
"@trigger.dev/nextjs": ["../../packages/nextjs/src/index"],
|
||||
"@trigger.dev/nextjs/*": ["../../packages/nextjs/src/*"],
|
||||
"@trigger.dev/core": ["../../packages/core/src/index"],
|
||||
"@trigger.dev/core/*": ["../../packages/core/src/*"],
|
||||
"@trigger.dev/integration-kit": ["../../packages/integration-kit/src/index"],
|
||||
"@trigger.dev/integration-kit/*": ["../../packages/integration-kit/src/*"],
|
||||
"@trigger.dev/functions": ["../../packages/functions/src/index"],
|
||||
"@trigger.dev/functions/*": ["../../packages/functions/src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -281,11 +281,21 @@ export const DynamicTriggerEndpointMetadataSchema = z.object({
|
||||
|
||||
export type DynamicTriggerEndpointMetadata = z.infer<typeof DynamicTriggerEndpointMetadataSchema>;
|
||||
|
||||
export const BackgroundFunctionMetadataSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
version: z.string(),
|
||||
enabled: z.boolean(),
|
||||
});
|
||||
|
||||
export type BackgroundFunctionMetadata = z.infer<typeof BackgroundFunctionMetadataSchema>;
|
||||
|
||||
export const IndexEndpointResponseSchema = z.object({
|
||||
jobs: z.array(JobMetadataSchema),
|
||||
sources: z.array(SourceMetadataSchema),
|
||||
dynamicTriggers: z.array(DynamicTriggerEndpointMetadataSchema),
|
||||
dynamicSchedules: z.array(RegisterDynamicSchedulePayloadSchema),
|
||||
backgroundFunctions: z.array(BackgroundFunctionMetadataSchema).optional(),
|
||||
});
|
||||
|
||||
export type IndexEndpointResponse = z.infer<typeof IndexEndpointResponseSchema>;
|
||||
@@ -593,7 +603,7 @@ export const RunTaskOptionsSchema = z.object({
|
||||
/** Allows you to link the Integration connection in the logs. This is handled automatically in integrations. */
|
||||
connectionKey: z.string().optional(),
|
||||
/** An operation you want to perform on the Trigger.dev platform, current only "fetch" is supported. If you wish to `fetch` use [`io.backgroundFetch()`](https://trigger.dev/docs/sdk/io/backgroundfetch) instead. */
|
||||
operation: z.enum(["fetch"]).optional(),
|
||||
operation: z.enum(["fetch", "invokeBackgroundFunction"]).optional(),
|
||||
/** A No Operation means that the code won't be executed. This is used internally to implement features like [io.wait()](https://trigger.dev/docs/sdk/io/wait). */
|
||||
noop: z.boolean().default(false),
|
||||
redact: RedactSchema.optional(),
|
||||
@@ -729,3 +739,57 @@ export const CreateExternalConnectionBodySchema = z.object({
|
||||
});
|
||||
|
||||
export type CreateExternalConnectionBody = z.infer<typeof CreateExternalConnectionBodySchema>;
|
||||
|
||||
export const UploadBackgroundFunctionRequestBodySchema = z.object({
|
||||
id: z.string(),
|
||||
version: z.string(),
|
||||
fileName: z.string(),
|
||||
bundle: z.string(),
|
||||
sourcemap: z.string(),
|
||||
dependencies: z.record(z.string()),
|
||||
nodeVersion: z.string(),
|
||||
});
|
||||
|
||||
export type UploadBackgroundFunctionRequestBody = z.infer<
|
||||
typeof UploadBackgroundFunctionRequestBodySchema
|
||||
>;
|
||||
|
||||
export const UploadBackgroundFunctionResponseBodySchema = z.object({
|
||||
id: z.string(),
|
||||
hash: z.string(),
|
||||
createdAt: z.coerce.date(),
|
||||
updatedAt: z.coerce.date(),
|
||||
});
|
||||
|
||||
export type UploadBackgroundFunctionResponseBody = z.infer<
|
||||
typeof UploadBackgroundFunctionResponseBodySchema
|
||||
>;
|
||||
|
||||
export const CreateBackgroundFunctionWorkerImageRequestBodySchema = z.object({
|
||||
registry: z.string(),
|
||||
name: z.string(),
|
||||
tag: z.string(),
|
||||
digest: z.string(),
|
||||
size: z.number(),
|
||||
});
|
||||
|
||||
export type CreateBackgroundFunctionWorkerImageRequestBody = z.infer<
|
||||
typeof CreateBackgroundFunctionWorkerImageRequestBodySchema
|
||||
>;
|
||||
|
||||
export const CreateBackgroundFunctionWorkerImageResponseBodySchema = z.object({
|
||||
id: z.string(),
|
||||
backgroundTaskArtifactId: z.string(),
|
||||
backgroundTaskId: z.string(),
|
||||
registry: z.string(),
|
||||
name: z.string(),
|
||||
tag: z.string(),
|
||||
digest: z.string(),
|
||||
size: z.number(),
|
||||
createdAt: z.coerce.date(),
|
||||
updatedAt: z.coerce.date(),
|
||||
});
|
||||
|
||||
export type CreateBackgroundFunctionWorkerImageResponseBody = z.infer<
|
||||
typeof CreateBackgroundFunctionWorkerImageResponseBodySchema
|
||||
>;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const BackgroundFunctionTaskParamsSchema = z.object({
|
||||
id: z.string(),
|
||||
version: z.string(),
|
||||
payload: z.any(),
|
||||
});
|
||||
@@ -12,3 +12,4 @@ export * from "./fetch";
|
||||
export * from "./events";
|
||||
export * from "./runs";
|
||||
export * from "./addMissingVersionField";
|
||||
export * from "./backgroundFunctions";
|
||||
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "BackgroundFunctionTaskStatus" AS ENUM ('PENDING', 'STARTED', 'SUCCESS', 'FAILURE');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "BackgroundFunction" (
|
||||
"id" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"organizationId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "BackgroundFunction_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "BackgroundFunctionVersion" (
|
||||
"id" TEXT NOT NULL,
|
||||
"version" TEXT NOT NULL,
|
||||
"backgroundFunctionId" TEXT NOT NULL,
|
||||
"endpointId" TEXT NOT NULL,
|
||||
"environmentId" TEXT NOT NULL,
|
||||
"organizationId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "BackgroundFunctionVersion_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "BackgroundFunctionAlias" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL DEFAULT 'latest',
|
||||
"value" TEXT NOT NULL,
|
||||
"versionId" TEXT NOT NULL,
|
||||
"backgroundFunctionId" TEXT NOT NULL,
|
||||
"environmentId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "BackgroundFunctionAlias_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "BackgroundFunctionArtifact" (
|
||||
"id" TEXT NOT NULL,
|
||||
"version" TEXT NOT NULL,
|
||||
"fileName" TEXT NOT NULL,
|
||||
"hash" TEXT NOT NULL,
|
||||
"bundle" TEXT NOT NULL,
|
||||
"sourcemap" JSONB NOT NULL,
|
||||
"nodeVersion" TEXT NOT NULL,
|
||||
"dependencies" JSONB NOT NULL,
|
||||
"backgroundFunctionId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "BackgroundFunctionArtifact_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "BackgroundFunctionImage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"registry" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"tag" TEXT NOT NULL,
|
||||
"digest" TEXT NOT NULL,
|
||||
"size" INTEGER NOT NULL,
|
||||
"backgroundFunctionId" TEXT NOT NULL,
|
||||
"backgroundFunctionArtifactId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "BackgroundFunctionImage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "BackgroundFunctionTask" (
|
||||
"id" TEXT NOT NULL,
|
||||
"backgroundFunctionId" TEXT NOT NULL,
|
||||
"backgroundFunctionVersionId" TEXT NOT NULL,
|
||||
"taskId" TEXT NOT NULL,
|
||||
"payload" JSONB NOT NULL,
|
||||
"output" JSONB,
|
||||
"error" JSONB,
|
||||
"status" "BackgroundFunctionTaskStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"startedAt" TIMESTAMP(3),
|
||||
"endedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "BackgroundFunctionTask_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "BackgroundFunction_projectId_slug_key" ON "BackgroundFunction"("projectId", "slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "BackgroundFunctionVersion_backgroundFunctionId_version_envi_key" ON "BackgroundFunctionVersion"("backgroundFunctionId", "version", "environmentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "BackgroundFunctionAlias_backgroundFunctionId_environmentId__key" ON "BackgroundFunctionAlias"("backgroundFunctionId", "environmentId", "name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "BackgroundFunctionArtifact_backgroundFunctionId_version_has_key" ON "BackgroundFunctionArtifact"("backgroundFunctionId", "version", "hash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "BackgroundFunctionImage_backgroundFunctionArtifactId_digest_key" ON "BackgroundFunctionImage"("backgroundFunctionArtifactId", "digest");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "BackgroundFunctionTask_taskId_key" ON "BackgroundFunctionTask"("taskId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunction" ADD CONSTRAINT "BackgroundFunction_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunction" ADD CONSTRAINT "BackgroundFunction_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionVersion" ADD CONSTRAINT "BackgroundFunctionVersion_backgroundFunctionId_fkey" FOREIGN KEY ("backgroundFunctionId") REFERENCES "BackgroundFunction"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionVersion" ADD CONSTRAINT "BackgroundFunctionVersion_endpointId_fkey" FOREIGN KEY ("endpointId") REFERENCES "Endpoint"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionVersion" ADD CONSTRAINT "BackgroundFunctionVersion_environmentId_fkey" FOREIGN KEY ("environmentId") REFERENCES "RuntimeEnvironment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionVersion" ADD CONSTRAINT "BackgroundFunctionVersion_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionVersion" ADD CONSTRAINT "BackgroundFunctionVersion_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionAlias" ADD CONSTRAINT "BackgroundFunctionAlias_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "BackgroundFunctionVersion"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionAlias" ADD CONSTRAINT "BackgroundFunctionAlias_backgroundFunctionId_fkey" FOREIGN KEY ("backgroundFunctionId") REFERENCES "BackgroundFunction"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionAlias" ADD CONSTRAINT "BackgroundFunctionAlias_environmentId_fkey" FOREIGN KEY ("environmentId") REFERENCES "RuntimeEnvironment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionArtifact" ADD CONSTRAINT "BackgroundFunctionArtifact_backgroundFunctionId_fkey" FOREIGN KEY ("backgroundFunctionId") REFERENCES "BackgroundFunction"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionImage" ADD CONSTRAINT "BackgroundFunctionImage_backgroundFunctionId_fkey" FOREIGN KEY ("backgroundFunctionId") REFERENCES "BackgroundFunction"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionImage" ADD CONSTRAINT "BackgroundFunctionImage_backgroundFunctionArtifactId_fkey" FOREIGN KEY ("backgroundFunctionArtifactId") REFERENCES "BackgroundFunctionArtifact"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionTask" ADD CONSTRAINT "BackgroundFunctionTask_backgroundFunctionId_fkey" FOREIGN KEY ("backgroundFunctionId") REFERENCES "BackgroundFunction"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionTask" ADD CONSTRAINT "BackgroundFunctionTask_backgroundFunctionVersionId_fkey" FOREIGN KEY ("backgroundFunctionVersionId") REFERENCES "BackgroundFunctionVersion"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BackgroundFunctionTask" ADD CONSTRAINT "BackgroundFunctionTask_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "Task"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -76,12 +76,14 @@ model Organization {
|
||||
events EventRecord[]
|
||||
jobRuns JobRun[]
|
||||
|
||||
projects Project[]
|
||||
members OrgMember[]
|
||||
invites OrgMemberInvite[]
|
||||
externalAccounts ExternalAccount[]
|
||||
integrations Integration[]
|
||||
sources TriggerSource[]
|
||||
projects Project[]
|
||||
members OrgMember[]
|
||||
invites OrgMemberInvite[]
|
||||
externalAccounts ExternalAccount[]
|
||||
integrations Integration[]
|
||||
sources TriggerSource[]
|
||||
backgroundFunctions BackgroundFunction[]
|
||||
backgroundFunctionVersions BackgroundFunctionVersion[]
|
||||
}
|
||||
|
||||
model ExternalAccount {
|
||||
@@ -306,17 +308,19 @@ model RuntimeEnvironment {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
endpoints Endpoint[]
|
||||
jobVersions JobVersion[]
|
||||
events EventRecord[]
|
||||
jobRuns JobRun[]
|
||||
requestDeliveries HttpSourceRequestDelivery[]
|
||||
jobAliases JobAlias[]
|
||||
JobQueue JobQueue[]
|
||||
sources TriggerSource[]
|
||||
eventDispatchers EventDispatcher[]
|
||||
scheduleSources ScheduleSource[]
|
||||
ExternalAccount ExternalAccount[]
|
||||
endpoints Endpoint[]
|
||||
jobVersions JobVersion[]
|
||||
events EventRecord[]
|
||||
jobRuns JobRun[]
|
||||
requestDeliveries HttpSourceRequestDelivery[]
|
||||
jobAliases JobAlias[]
|
||||
JobQueue JobQueue[]
|
||||
sources TriggerSource[]
|
||||
eventDispatchers EventDispatcher[]
|
||||
scheduleSources ScheduleSource[]
|
||||
ExternalAccount ExternalAccount[]
|
||||
backgroundFunctionVersions BackgroundFunctionVersion[]
|
||||
backgroundFunctionAliases BackgroundFunctionAlias[]
|
||||
|
||||
@@unique([projectId, slug, orgMemberId])
|
||||
}
|
||||
@@ -339,13 +343,15 @@ model Project {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
environments RuntimeEnvironment[]
|
||||
endpoints Endpoint[]
|
||||
jobs Job[]
|
||||
jobVersion JobVersion[]
|
||||
events EventRecord[]
|
||||
runs JobRun[]
|
||||
sources TriggerSource[]
|
||||
environments RuntimeEnvironment[]
|
||||
endpoints Endpoint[]
|
||||
jobs Job[]
|
||||
jobVersion JobVersion[]
|
||||
events EventRecord[]
|
||||
runs JobRun[]
|
||||
sources TriggerSource[]
|
||||
backgroundFunctions BackgroundFunction[]
|
||||
backgroundFunctionVersions BackgroundFunctionVersion[]
|
||||
}
|
||||
|
||||
model Endpoint {
|
||||
@@ -367,12 +373,13 @@ model Endpoint {
|
||||
|
||||
indexingHookIdentifier String?
|
||||
|
||||
jobVersions JobVersion[]
|
||||
jobRuns JobRun[]
|
||||
httpRequestDeliveries HttpSourceRequestDelivery[]
|
||||
dynamictriggers DynamicTrigger[]
|
||||
sources TriggerSource[]
|
||||
indexings EndpointIndex[]
|
||||
jobVersions JobVersion[]
|
||||
jobRuns JobRun[]
|
||||
httpRequestDeliveries HttpSourceRequestDelivery[]
|
||||
dynamictriggers DynamicTrigger[]
|
||||
sources TriggerSource[]
|
||||
indexings EndpointIndex[]
|
||||
backgroundFunctionVersions BackgroundFunctionVersion[]
|
||||
|
||||
@@unique([environmentId, slug])
|
||||
}
|
||||
@@ -807,9 +814,10 @@ model Task {
|
||||
runConnection RunConnection? @relation(fields: [runConnectionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
runConnectionId String?
|
||||
|
||||
children Task[] @relation("TaskParent")
|
||||
executions JobRunExecution[]
|
||||
attempts TaskAttempt[]
|
||||
children Task[] @relation("TaskParent")
|
||||
executions JobRunExecution[]
|
||||
attempts TaskAttempt[]
|
||||
functionTask BackgroundFunctionTask?
|
||||
|
||||
@@unique([runId, idempotencyKey])
|
||||
}
|
||||
@@ -1068,3 +1076,147 @@ model ApiIntegrationVote {
|
||||
|
||||
@@unique([apiIdentifier, userId])
|
||||
}
|
||||
|
||||
model BackgroundFunction {
|
||||
id String @id @default(cuid())
|
||||
slug String
|
||||
title String
|
||||
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
organizationId String
|
||||
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
projectId String
|
||||
|
||||
versions BackgroundFunctionVersion[]
|
||||
aliases BackgroundFunctionAlias[]
|
||||
artifacts BackgroundFunctionArtifact[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
deletedAt DateTime?
|
||||
|
||||
operations BackgroundFunctionTask[]
|
||||
images BackgroundFunctionImage[]
|
||||
|
||||
@@unique([projectId, slug])
|
||||
}
|
||||
|
||||
model BackgroundFunctionVersion {
|
||||
id String @id @default(cuid())
|
||||
version String
|
||||
|
||||
backgroundFunction BackgroundFunction @relation(fields: [backgroundFunctionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
backgroundFunctionId String
|
||||
|
||||
endpoint Endpoint @relation(fields: [endpointId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
endpointId String
|
||||
|
||||
environment RuntimeEnvironment @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
environmentId String
|
||||
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
organizationId String
|
||||
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
projectId String
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
aliases BackgroundFunctionAlias[]
|
||||
tasks BackgroundFunctionTask[]
|
||||
|
||||
@@unique([backgroundFunctionId, version, environmentId])
|
||||
}
|
||||
|
||||
model BackgroundFunctionAlias {
|
||||
id String @id @default(cuid())
|
||||
name String @default("latest")
|
||||
value String
|
||||
|
||||
version BackgroundFunctionVersion @relation(fields: [versionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
versionId String
|
||||
|
||||
backgroundFunction BackgroundFunction @relation(fields: [backgroundFunctionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
backgroundFunctionId String
|
||||
|
||||
environment RuntimeEnvironment @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
environmentId String
|
||||
|
||||
@@unique([backgroundFunctionId, environmentId, name])
|
||||
}
|
||||
|
||||
model BackgroundFunctionArtifact {
|
||||
id String @id @default(cuid())
|
||||
version String
|
||||
|
||||
fileName String
|
||||
hash String
|
||||
bundle String
|
||||
sourcemap Json
|
||||
nodeVersion String
|
||||
dependencies Json
|
||||
|
||||
backgroundFunction BackgroundFunction @relation(fields: [backgroundFunctionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
backgroundFunctionId String
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
images BackgroundFunctionImage[]
|
||||
|
||||
@@unique([backgroundFunctionId, version, hash])
|
||||
}
|
||||
|
||||
model BackgroundFunctionImage {
|
||||
id String @id @default(cuid())
|
||||
registry String
|
||||
name String
|
||||
tag String
|
||||
digest String
|
||||
size Int
|
||||
|
||||
backgroundFunction BackgroundFunction @relation(fields: [backgroundFunctionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
backgroundFunctionId String
|
||||
|
||||
backgroundFunctionArtifact BackgroundFunctionArtifact @relation(fields: [backgroundFunctionArtifactId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
backgroundFunctionArtifactId String
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([backgroundFunctionArtifactId, digest])
|
||||
}
|
||||
|
||||
model BackgroundFunctionTask {
|
||||
id String @id @default(cuid())
|
||||
|
||||
backgroundFunction BackgroundFunction @relation(fields: [backgroundFunctionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
backgroundFunctionId String
|
||||
|
||||
backgroundFunctionVersion BackgroundFunctionVersion @relation(fields: [backgroundFunctionVersionId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
backgroundFunctionVersionId String
|
||||
|
||||
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
taskId String @unique
|
||||
|
||||
payload Json
|
||||
output Json?
|
||||
error Json?
|
||||
|
||||
status BackgroundFunctionTaskStatus @default(PENDING)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
startedAt DateTime?
|
||||
endedAt DateTime?
|
||||
}
|
||||
|
||||
enum BackgroundFunctionTaskStatus {
|
||||
PENDING
|
||||
STARTED
|
||||
SUCCESS
|
||||
FAILURE
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "../src",
|
||||
"target": "es2022",
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./build.json",
|
||||
"include": [
|
||||
"../src/**/*.ts",
|
||||
"../src/**/*.cts",
|
||||
"../src/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
".../src/**/*.mts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outDir": "../.tshy-build-tmp/commonjs"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "./build.json",
|
||||
"include": [
|
||||
"../src/**/*.ts",
|
||||
"../src/**/*.mts",
|
||||
"../src/**/*.tsx"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outDir": "../.tshy-build-tmp/esm"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@trigger.dev/functions",
|
||||
"version": "2.1.2",
|
||||
"description": "Trigger.dev Common Background Functions",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"prepare": "tshy"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tshy": "^1.0.0",
|
||||
"typescript": "^5.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"tshy": {
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"default": "./dist/commonjs/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type Foo = string;
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"paths": {
|
||||
"@trigger.dev/core/*": ["../core/src/*"],
|
||||
"@trigger.dev/core": ["../core/src/index"]
|
||||
},
|
||||
"lib": ["DOM", "DOM.Iterable"],
|
||||
"declaration": true,
|
||||
"declarationMap": true
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { BackgroundFunctionMetadata, LogLevel } from "@trigger.dev/core";
|
||||
import { z } from "zod";
|
||||
import { TriggerClient } from "./triggerClient";
|
||||
import { slugifyId } from "./utils";
|
||||
import { runLocalStorage } from "./runLocalStorage";
|
||||
|
||||
export type BackgroundFunctionOptions<TPayload = any, TRunResult = any> = {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
schema?: z.Schema<TPayload>;
|
||||
logLevel?: LogLevel;
|
||||
enabled?: boolean;
|
||||
run: (payload: TPayload) => Promise<TRunResult>;
|
||||
};
|
||||
|
||||
export class BackgroundFunction<TPayload = any, TRunResult = any> {
|
||||
readonly options: BackgroundFunctionOptions<TPayload, TRunResult>;
|
||||
|
||||
client: TriggerClient;
|
||||
|
||||
constructor(client: TriggerClient, options: BackgroundFunctionOptions<TPayload, TRunResult>) {
|
||||
this.client = client;
|
||||
this.options = options;
|
||||
this.#validate();
|
||||
|
||||
client.attachBackgroundFunction(this);
|
||||
}
|
||||
|
||||
get id() {
|
||||
return slugifyId(this.options.id);
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
return typeof this.options.enabled === "boolean" ? this.options.enabled : true;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.options.name;
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return this.options.schema;
|
||||
}
|
||||
|
||||
get version() {
|
||||
return this.options.version;
|
||||
}
|
||||
|
||||
get logLevel() {
|
||||
return this.options.logLevel;
|
||||
}
|
||||
|
||||
public async invoke(key: string | string[], payload: TPayload): Promise<TRunResult> {
|
||||
if (!this.enabled) {
|
||||
throw new Error(`Cannot invoke a disabled background task: ${this.id}`);
|
||||
}
|
||||
|
||||
const runStore = runLocalStorage.getStore();
|
||||
|
||||
if (!runStore) {
|
||||
throw new Error("Cannot invoke a background task outside of a job run");
|
||||
}
|
||||
|
||||
const { io, ctx } = runStore;
|
||||
|
||||
return await io.invokeBackgroundFunction(key, this.id, this.version, payload);
|
||||
}
|
||||
|
||||
toJSON(): BackgroundFunctionMetadata {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
version: this.version,
|
||||
enabled: this.enabled,
|
||||
};
|
||||
}
|
||||
|
||||
// Make sure the id is valid (must only contain alphanumeric characters and dashes)
|
||||
// Make sure the version is valid (must be a valid semver version)
|
||||
#validate() {
|
||||
if (!this.version.match(/^(\d+)\.(\d+)\.(\d+)$/)) {
|
||||
throw new Error(
|
||||
`Invalid job version: "${this.version}". BackgroundFunction versions must be valid semver versions.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
RunTaskOptions,
|
||||
SendEvent,
|
||||
SendEventOptions,
|
||||
SerializableJson,
|
||||
SerializableJsonSchema,
|
||||
ServerTask,
|
||||
UpdateTriggerSourceBodyV2,
|
||||
@@ -25,7 +24,6 @@ import {
|
||||
RetryWithTaskError,
|
||||
isTriggerError,
|
||||
} from "./errors";
|
||||
import { createIOWithIntegrations } from "./ioWithIntegrations";
|
||||
import { calculateRetryAt } from "./retry";
|
||||
import { TriggerClient } from "./triggerClient";
|
||||
import { DynamicTrigger } from "./triggers/dynamic";
|
||||
@@ -199,6 +197,43 @@ export class IO {
|
||||
)) as TResponseData;
|
||||
}
|
||||
|
||||
/** `io.invokeBackgroundFunction()` invokes a background function by id and version.
|
||||
* @param key Should be a stable and unique key inside the `run()`. See [resumability](https://trigger.dev/docs/documentation/concepts/resumability) for more information.
|
||||
* @param id The id of the background function to invoke.
|
||||
* @param version The version of the background function to invoke.
|
||||
* @param payload The payload to send to the background function.
|
||||
*/
|
||||
async invokeBackgroundFunction<TResultData>(
|
||||
key: string | any[],
|
||||
id: string,
|
||||
version: string,
|
||||
payload: any
|
||||
): Promise<TResultData> {
|
||||
return (await this.runTask(
|
||||
key,
|
||||
async (task) => {
|
||||
return task.output;
|
||||
},
|
||||
{
|
||||
name: `invoke ${id}`,
|
||||
params: { payload, id, version },
|
||||
operation: "invokeBackgroundFunction",
|
||||
icon: "background",
|
||||
noop: false,
|
||||
properties: [
|
||||
{
|
||||
label: "Function ID",
|
||||
text: id,
|
||||
},
|
||||
{
|
||||
label: "Version",
|
||||
text: version,
|
||||
},
|
||||
],
|
||||
}
|
||||
)) as TResultData;
|
||||
}
|
||||
|
||||
/** `io.sendEvent()` allows you to send an event from inside a Job run. The sent even will trigger any Jobs that are listening for that event (based on the name).
|
||||
* @param key Should be a stable and unique key inside the `run()`. See [resumability](https://trigger.dev/docs/documentation/concepts/resumability) for more information.
|
||||
* @param event The event to send. The event name must match the name of the event that your Jobs are listening for.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { IO } from "./io";
|
||||
import { TriggerContext } from "./types";
|
||||
import { TypedAsyncLocalStorage } from "./utils/typedAsyncLocalStorage";
|
||||
|
||||
export type RunStore = {
|
||||
io: IO;
|
||||
ctx: TriggerContext;
|
||||
};
|
||||
|
||||
export const runLocalStorage = new TypedAsyncLocalStorage<RunStore>();
|
||||
@@ -42,6 +42,8 @@ import type {
|
||||
TriggerContext,
|
||||
TriggerPreprocessContext,
|
||||
} from "./types";
|
||||
import { BackgroundFunction, BackgroundFunctionOptions } from "./backgroundFunction";
|
||||
import { runLocalStorage } from "./runLocalStorage";
|
||||
|
||||
const registerSourceEvent: EventSpecification<RegisterSourceEventV2> = {
|
||||
name: REGISTER_SOURCE_EVENT_V2,
|
||||
@@ -93,6 +95,7 @@ export class TriggerClient {
|
||||
> = {};
|
||||
#jobMetadataByDynamicTriggers: Record<string, Array<{ id: string; version: string }>> = {};
|
||||
#registeredSchedules: Record<string, Array<{ id: string; version: string }>> = {};
|
||||
#registeredBackgroundFunctions: Record<string, BackgroundFunction<any>> = {};
|
||||
|
||||
#client: ApiClient;
|
||||
#internalLogger: Logger;
|
||||
@@ -234,6 +237,9 @@ export class TriggerClient {
|
||||
id,
|
||||
jobs,
|
||||
})),
|
||||
backgroundFunctions: Object.values(this.#registeredBackgroundFunctions).map((func) =>
|
||||
func.toJSON()
|
||||
),
|
||||
};
|
||||
|
||||
// if the x-trigger-job-id header is not set, we return all jobs
|
||||
@@ -426,6 +432,10 @@ export class TriggerClient {
|
||||
job.trigger.attachToJob(this, job);
|
||||
}
|
||||
|
||||
attachBackgroundFunction(func: BackgroundFunction<any>): void {
|
||||
this.#registeredBackgroundFunctions[func.id] = func;
|
||||
}
|
||||
|
||||
attachDynamicTrigger(trigger: DynamicTrigger<any, any>): void {
|
||||
this.#registeredDynamicTriggers[trigger.id] = trigger;
|
||||
|
||||
@@ -648,11 +658,13 @@ export class TriggerClient {
|
||||
);
|
||||
|
||||
try {
|
||||
const output = await job.options.run(
|
||||
job.trigger.event.parsePayload(body.event.payload ?? {}),
|
||||
ioWithConnections,
|
||||
context
|
||||
);
|
||||
const output = await runLocalStorage.runWith({ io, ctx: context }, () => {
|
||||
return job.options.run(
|
||||
job.trigger.event.parsePayload(body.event.payload ?? {}),
|
||||
ioWithConnections,
|
||||
context
|
||||
);
|
||||
});
|
||||
|
||||
return { status: "SUCCESS", output };
|
||||
} catch (error) {
|
||||
@@ -863,6 +875,12 @@ export class TriggerClient {
|
||||
>(options: JobOptions<TTrigger, TIntegrations>) {
|
||||
return new Job<TTrigger, TIntegrations>(this, options);
|
||||
}
|
||||
|
||||
defineBackgroundFunction<TPayload = any, TRunResult = any>(
|
||||
options: BackgroundFunctionOptions<TPayload, TRunResult>
|
||||
) {
|
||||
return new BackgroundFunction<TPayload, TRunResult>(this, options);
|
||||
}
|
||||
}
|
||||
|
||||
function dynamicTriggerRegisterSourceJobId(id: string) {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
|
||||
export class TypedAsyncLocalStorage<T> {
|
||||
private storage: AsyncLocalStorage<T>;
|
||||
|
||||
constructor() {
|
||||
this.storage = new AsyncLocalStorage<T>();
|
||||
}
|
||||
|
||||
runWith<R extends (...args: any[]) => Promise<any>>(context: T, fn: R): Promise<ReturnType<R>> {
|
||||
return this.storage.run(context, fn);
|
||||
}
|
||||
|
||||
getStore(): T | undefined {
|
||||
return this.storage.getStore();
|
||||
}
|
||||
}
|
||||
Generated
+370
-78
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user