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

125 lines
4.8 KiB
TypeScript

import { logger, type RuntimeEnvironmentType } from "@trigger.dev/core/v3";
import { type RunEngineVersion } from "@trigger.dev/database";
import { ServiceValidationError } from "~/v3/services/baseService.server";
import { BasePresenter } from "./basePresenter.server";
import { waitpointStatusToApiStatus } from "./WaitpointListPresenter.server";
import { generateHttpCallbackUrl } from "~/services/httpCallback.server";
import type { PrismaClientOrTransaction, PrismaReplicaClient } from "~/db.server";
import { readThroughRun } from "~/v3/runOpsMigration/readThrough.server";
// When omitted, clients default to the inherited _replica handle => passthrough reads the
// replica exactly as today. isPastRetention is injectable for tests. Typed PrismaReplicaClient
// to match readThroughRun's readNew/readLegacy + deps.
type ApiWaitpointPresenterReadThroughDeps = {
newClient?: PrismaReplicaClient;
legacyReplica?: PrismaReplicaClient;
splitEnabled?: boolean;
isPastRetention?: (id: string) => boolean;
};
export class ApiWaitpointPresenter extends BasePresenter {
constructor(
prismaClient?: PrismaClientOrTransaction,
replicaClient?: PrismaClientOrTransaction,
private readonly readThroughDeps?: ApiWaitpointPresenterReadThroughDeps
) {
super(prismaClient, replicaClient);
}
public async call(
environment: {
id: string;
type: RuntimeEnvironmentType;
project: {
id: string;
engine: RunEngineVersion;
};
apiKey: string;
},
waitpointId: string
) {
return this.trace("call", async (span) => {
// Public waitpoint retrieve. Split on: new run-ops client first, then the LEGACY
// RUN-OPS READ REPLICA ONLY on a new-probe miss — never the legacy primary.
// Split off (single-DB / self-host): one plain waitpoint.findFirst against the replica
// (passthrough). The waitpointId is the residency-classifiable run-ops id (the route
// pre-decodes the friendlyId via WaitpointId.toId).
const hydrate = (client: PrismaReplicaClient) =>
client.waitpoint.findFirst({
where: {
id: waitpointId,
environmentId: environment.id,
},
select: {
id: true,
friendlyId: true,
type: true,
status: true,
idempotencyKey: true,
userProvidedIdempotencyKey: true,
idempotencyKeyExpiresAt: true,
inactiveIdempotencyKey: true,
output: true,
outputType: true,
outputIsError: true,
completedAfter: true,
completedAt: true,
createdAt: true,
tags: true,
},
});
const result = await readThroughRun({
runId: waitpointId,
environmentId: environment.id,
readNew: (client) => hydrate(client),
readLegacy: (replica) => hydrate(replica),
deps: {
splitEnabled: this.readThroughDeps?.splitEnabled,
// Default both clients to the inherited _replica handle (declared
// PrismaClientOrTransaction but $replica at runtime) so passthrough reads the replica
// as today; split mode injects a distinct newClient.
newClient: this.readThroughDeps?.newClient ?? (this._replica as PrismaReplicaClient),
legacyReplica:
this.readThroughDeps?.legacyReplica ?? (this._replica as PrismaReplicaClient),
isPastRetention: this.readThroughDeps?.isPastRetention,
},
});
const waitpoint =
result.source === "new" || result.source === "legacy-replica" ? result.value : null;
if (!waitpoint) {
logger.error(`WaitpointPresenter: Waitpoint not found`, {
id: waitpointId,
});
throw new ServiceValidationError("Waitpoint not found");
}
let _isTimeout = false;
if (waitpoint.outputIsError && waitpoint.output) {
_isTimeout = true;
}
return {
id: waitpoint.friendlyId,
type: waitpoint.type,
url: generateHttpCallbackUrl(waitpoint.id, environment.apiKey),
status: waitpointStatusToApiStatus(waitpoint.status, waitpoint.outputIsError),
idempotencyKey: waitpoint.idempotencyKey,
userProvidedIdempotencyKey: waitpoint.userProvidedIdempotencyKey,
idempotencyKeyExpiresAt: waitpoint.idempotencyKeyExpiresAt ?? undefined,
inactiveIdempotencyKey: waitpoint.inactiveIdempotencyKey ?? undefined,
output: waitpoint.output ?? undefined,
outputType: waitpoint.outputType,
outputIsError: waitpoint.outputIsError,
timeoutAt: waitpoint.completedAfter ?? undefined,
completedAfter: waitpoint.completedAfter ?? undefined,
completedAt: waitpoint.completedAt ?? undefined,
createdAt: waitpoint.createdAt,
tags: waitpoint.tags,
};
});
}
}