80 lines
3.1 KiB
TypeScript
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;
|
|
}
|
|
}
|