Files
triggerdotdev--trigger.dev/apps/webapp/app/v3/mollifier/mollifierMollify.server.ts
T
2026-07-13 13:32:57 +08:00

98 lines
3.8 KiB
TypeScript

import { RunId } from "@trigger.dev/core/v3/isomorphic";
import type { MollifierBuffer } from "@trigger.dev/redis-worker";
import { serialiseMollifierSnapshot, type MollifierSnapshot } from "./mollifierSnapshot.server";
import type { TripDecision } from "./mollifierGate.server";
export type MollifyNotice = {
code: "mollifier.queued";
message: string;
docs: string;
};
export type MollifySyntheticResult = {
// `id` is the canonical TaskRun primary key derived from `friendlyId`
// via `RunId.fromFriendlyId`. Downstream consumers in the trigger
// route — notably `saveRequestIdempotency` — index the request-
// idempotency cache by this id; without it the cache stores
// `undefined` and Prisma's `findFirst({ where: { id: undefined } })`
// on retry strips the predicate and returns an arbitrary TaskRun
// (potential cross-tenant leak). Always populated.
//
// `spanId` is the root-span id allocated at gate-accept time and
// stored in the snapshot. Callers like the dashboard's Test action
// use it to build a `v3RunSpanPath` URL that auto-opens the right
// details panel — without it, the buffered run lands on the
// run-detail page with no span selected (parity gap with PG runs).
run: { id: string; friendlyId: string; spanId: string };
error: undefined;
// The race-loser path: if accept's SETNX hit an existing
// buffered run with the same (env, task, idempotencyKey), the
// response echoes the winner's runId with isCached=true. The
// mollifier-queued notice is only attached for the happy accept.
isCached: boolean;
notice?: MollifyNotice;
};
const NOTICE: MollifyNotice = {
code: "mollifier.queued",
message: "Trigger accepted into burst buffer. Consider batchTrigger for fan-outs of 100+.",
docs: "https://trigger.dev/docs/management/tasks/batch-trigger",
};
export async function mollifyTrigger(args: {
runFriendlyId: string;
environmentId: string;
organizationId: string;
engineTriggerInput: MollifierSnapshot;
decision: Extract<TripDecision, { divert: true }>;
buffer: MollifierBuffer;
// Optional idempotency context. When both are passed, accept SETNXes
// the lookup so the buffered window participates in trigger-time
// dedup symmetrically with PG.
idempotencyKey?: string;
taskIdentifier?: string;
}): Promise<MollifySyntheticResult> {
const result = await args.buffer.accept({
runId: args.runFriendlyId,
envId: args.environmentId,
orgId: args.organizationId,
payload: serialiseMollifierSnapshot(args.engineTriggerInput),
idempotencyKey: args.idempotencyKey,
taskIdentifier: args.taskIdentifier,
});
if (result.kind === "duplicate_idempotency") {
// Race loser. Echo the winner's runId so the SDK's response shape
// matches PG-side idempotency cache hits. The winner's spanId isn't
// readily available without a second buffer fetch; an empty string
// causes `v3RunSpanPath` to omit the `?span=` param, which matches
// current behaviour for cached PG responses.
return {
run: {
id: RunId.fromFriendlyId(result.existingRunId),
friendlyId: result.existingRunId,
spanId: "",
},
error: undefined,
isCached: true,
};
}
// Both "accepted" and "duplicate_run_id" produce the same customer-
// visible response: a buffered-trigger acknowledgement. The duplicate
// runId case is unreachable in practice (runIds are server-generated
// and unique) but is silently idempotent at the buffer layer either way.
const rawSpanId = args.engineTriggerInput.spanId;
const spanId = typeof rawSpanId === "string" ? rawSpanId : "";
return {
run: {
id: RunId.fromFriendlyId(args.runFriendlyId),
friendlyId: args.runFriendlyId,
spanId,
},
error: undefined,
isCached: false,
notice: NOTICE,
};
}