import type { MollifierBuffer } from "@trigger.dev/redis-worker"; import type { PrismaClientOrTransaction, PrismaReplicaClient } from "~/db.server"; import { $replica as defaultReplica, prisma as defaultWriter } from "~/db.server"; import { runStore } from "~/v3/runStore.server"; import { getMollifierBuffer as defaultGetBuffer } from "./mollifierBuffer.server"; // Discriminated-union resolver used by mutation routes' `findResource`. // The route builder treats a null return from `findResource` as a 404 // BEFORE the action handler runs (`apiBuilder.server.ts:321`), so we // must check BOTH the PG canonical store and the mollifier buffer here // — otherwise a buffered run can't be cancelled / mutated even though // the underlying mutateWithFallback flow would handle it correctly. // // (Regression: before extracting this helper the cancel route had // `findResource: async () => null`, which made every cancel 404 before // the action ran. The helper makes the lookup unit-testable.) export type ResolvedRunForMutation = | { source: "pg"; friendlyId: string } | { source: "buffer"; friendlyId: string }; export type ResolveRunForMutationDeps = { prismaReplica?: PrismaReplicaClient; prismaWriter?: PrismaClientOrTransaction; getBuffer?: () => MollifierBuffer | null; }; export async function resolveRunForMutation(input: { runParam: string; environmentId: string; organizationId: string; deps?: ResolveRunForMutationDeps; }): Promise { const replica = input.deps?.prismaReplica ?? defaultReplica; const writer = input.deps?.prismaWriter ?? defaultWriter; const getBuffer = input.deps?.getBuffer ?? defaultGetBuffer; const pgRun = await runStore.findRun( { friendlyId: input.runParam, runtimeEnvironmentId: input.environmentId }, { select: { friendlyId: true } }, replica ); if (pgRun) return { source: "pg", friendlyId: pgRun.friendlyId }; const buffer = getBuffer(); if (buffer) { const entry = await buffer.getEntry(input.runParam); if (entry && entry.envId === input.environmentId && entry.orgId === input.organizationId) { return { source: "buffer", friendlyId: input.runParam }; } } // Replica + buffer both missed. Before declaring "not found" (which the // route builder converts to a hard 404 *before* the action handler runs, // so the downstream `mutateWithFallback` writer-recovery never gets a // chance to fire), do one final probe against the writer. This catches // two cases: // 1. Replica lag on a freshly-created PG row. // 2. A buffered run that materialised in the window between the // replica read and our buffer check (the entry was ack'd and the // hash is mid-grace-TTL but our getEntry returned null due to // lookup-by-friendlyId timing). // Without this, the resolver returns null in degraded states that the // downstream mutateWithFallback flow would otherwise handle correctly. const writerRun = await runStore.findRun( { friendlyId: input.runParam, runtimeEnvironmentId: input.environmentId }, { select: { friendlyId: true } }, writer ); if (writerRun) return { source: "pg", friendlyId: writerRun.friendlyId }; return null; }