Files
2026-07-13 13:32:57 +08:00

80 lines
3.1 KiB
TypeScript

import type { CreateBackgroundWorkerRequestBody } from "@trigger.dev/core/v3";
import { BackgroundWorkerId } from "@trigger.dev/core/v3/isomorphic";
import {
isUniqueConstraintError,
type BackgroundWorker,
type PrismaClientOrTransaction,
type WorkerDeployment,
} from "@trigger.dev/database";
import type { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { ServiceValidationError } from "../common.server";
import { stripBackgroundWorkerMetadataForStorage } from "../stripBackgroundWorkerMetadataForStorage.server";
/**
* Idempotent on `(project, environment, version)` for sequential calls, not concurrent calls.
*
* Failure shapes the caller distinguishes:
* - `ServiceValidationError` (409): definitive — contentHash drift means a different build
* is being pushed under the same deployment version, so the caller should fail the
* deployment instead of waiting for a timeout.
* - Plain `Error`: transient — two attempts raced the `create()` call and the loser caught
* the unique-index violation. The caller should propagate this as 5xx so the CLI's
* retry/backoff hits findFirst on the next attempt and returns the winner's row.
*/
export async function findOrCreateBackgroundWorker(
environment: AuthenticatedEnvironment,
deployment: WorkerDeployment,
body: CreateBackgroundWorkerRequestBody,
prisma: PrismaClientOrTransaction
): Promise<BackgroundWorker> {
const existing = await prisma.backgroundWorker.findFirst({
where: {
projectId: environment.projectId,
runtimeEnvironmentId: environment.id,
version: deployment.version,
},
});
if (existing && existing.contentHash === body.metadata.contentHash) {
return existing;
}
if (existing) {
throw new ServiceValidationError(
"A background worker for this deployment version already exists with a different content hash",
409
);
}
try {
return await prisma.backgroundWorker.create({
data: {
...BackgroundWorkerId.generate(),
version: deployment.version,
runtimeEnvironmentId: environment.id,
projectId: environment.projectId,
metadata: stripBackgroundWorkerMetadataForStorage(body.metadata),
contentHash: body.metadata.contentHash,
cliVersion: body.metadata.cliPackageVersion,
sdkVersion: body.metadata.packageVersion,
supportsLazyAttempts: body.supportsLazyAttempts,
engine: body.engine,
runtime: body.metadata.runtime,
runtimeVersion: body.metadata.runtimeVersion,
},
});
} catch (error) {
// Concurrent attempts raced past `findFirst` and both reached `create`. Surface
// a clear, non-Prisma error so the 5xx the caller returns isn't an opaque
// P2002 — the CLI's retry will then hit `findFirst` and find the winner's row.
// Intentionally NOT a ServiceValidationError so the caller doesn't fail-deploy
// on a transient race.
if (isUniqueConstraintError(error, ["projectId", "runtimeEnvironmentId", "version"])) {
throw new Error(
"Concurrent background worker registration detected for this deployment version; please retry"
);
}
throw error;
}
}