125 lines
4.8 KiB
TypeScript
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,
|
|
};
|
|
});
|
|
}
|
|
}
|