|
1 | 1 | import { type LoaderFunctionArgs } from "@remix-run/server-runtime"; |
2 | 2 | import { json } from "@remix-run/node"; |
| 3 | +import { z } from "zod"; |
| 4 | +import { MachinePresetName } from "@trigger.dev/core/v3"; |
3 | 5 | import { requireUserId } from "~/services/session.server"; |
4 | 6 | import { EnvironmentParamSchema } from "~/utils/pathBuilder"; |
5 | 7 | import { findProjectBySlug } from "~/models/project.server"; |
6 | 8 | import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server"; |
7 | 9 | import { $replica } from "~/db.server"; |
| 10 | +import type { TaskRunStatus } from "@trigger.dev/database"; |
| 11 | + |
| 12 | +// Valid TaskRunStatus values |
| 13 | +const VALID_TASK_RUN_STATUSES = [ |
| 14 | + "PENDING", |
| 15 | + "QUEUED", |
| 16 | + "EXECUTING", |
| 17 | + "WAITING_FOR_EXECUTION", |
| 18 | + "WAITING", |
| 19 | + "COMPLETED_SUCCESSFULLY", |
| 20 | + "COMPLETED_WITH_ERRORS", |
| 21 | + "SYSTEM_FAILURE", |
| 22 | + "FAILURE", |
| 23 | + "CANCELED", |
| 24 | +] as const; |
| 25 | + |
| 26 | +// Schema for validating run context data |
| 27 | +export const RunContextSchema = z.object({ |
| 28 | + id: z.string(), |
| 29 | + friendlyId: z.string(), |
| 30 | + taskIdentifier: z.string(), |
| 31 | + status: z.enum(VALID_TASK_RUN_STATUSES).catch((ctx) => { |
| 32 | + throw new Error(`Invalid TaskRunStatus: ${ctx.input}`); |
| 33 | + }), |
| 34 | + createdAt: z.string().datetime(), |
| 35 | + startedAt: z.string().datetime().optional(), |
| 36 | + completedAt: z.string().datetime().optional(), |
| 37 | + isTest: z.boolean(), |
| 38 | + tags: z.array(z.string()), |
| 39 | + queue: z.string(), |
| 40 | + concurrencyKey: z.string().nullable(), |
| 41 | + usageDurationMs: z.number(), |
| 42 | + costInCents: z.number(), |
| 43 | + baseCostInCents: z.number(), |
| 44 | + machinePreset: MachinePresetName.nullable(), |
| 45 | + version: z.string().optional(), |
| 46 | + rootRun: z |
| 47 | + .object({ |
| 48 | + friendlyId: z.string(), |
| 49 | + taskIdentifier: z.string(), |
| 50 | + }) |
| 51 | + .nullable(), |
| 52 | + parentRun: z |
| 53 | + .object({ |
| 54 | + friendlyId: z.string(), |
| 55 | + taskIdentifier: z.string(), |
| 56 | + }) |
| 57 | + .nullable(), |
| 58 | + batch: z |
| 59 | + .object({ |
| 60 | + friendlyId: z.string(), |
| 61 | + }) |
| 62 | + .nullable(), |
| 63 | + schedule: z |
| 64 | + .object({ |
| 65 | + friendlyId: z.string(), |
| 66 | + }) |
| 67 | + .nullable(), |
| 68 | +}); |
| 69 | + |
| 70 | +export type RunContext = z.infer<typeof RunContextSchema>; |
8 | 71 |
|
9 | 72 | // Fetch run context for a log entry |
10 | 73 | export const loader = async ({ request, params }: LoaderFunctionArgs) => { |
@@ -99,38 +162,43 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { |
99 | 162 | schedule = scheduleData; |
100 | 163 | } |
101 | 164 |
|
| 165 | + const runData = { |
| 166 | + id: run.id, |
| 167 | + friendlyId: run.friendlyId, |
| 168 | + taskIdentifier: run.taskIdentifier, |
| 169 | + status: run.status, |
| 170 | + createdAt: run.createdAt.toISOString(), |
| 171 | + startedAt: run.startedAt?.toISOString(), |
| 172 | + completedAt: run.completedAt?.toISOString(), |
| 173 | + isTest: run.isTest, |
| 174 | + tags: run.runTags, |
| 175 | + queue: run.queue, |
| 176 | + concurrencyKey: run.concurrencyKey, |
| 177 | + usageDurationMs: run.usageDurationMs, |
| 178 | + costInCents: run.costInCents, |
| 179 | + baseCostInCents: run.baseCostInCents, |
| 180 | + machinePreset: run.machinePreset, |
| 181 | + version: run.lockedToVersion?.version, |
| 182 | + rootRun: run.rootTaskRun |
| 183 | + ? { |
| 184 | + friendlyId: run.rootTaskRun.friendlyId, |
| 185 | + taskIdentifier: run.rootTaskRun.taskIdentifier, |
| 186 | + } |
| 187 | + : null, |
| 188 | + parentRun: run.parentTaskRun |
| 189 | + ? { |
| 190 | + friendlyId: run.parentTaskRun.friendlyId, |
| 191 | + taskIdentifier: run.parentTaskRun.taskIdentifier, |
| 192 | + } |
| 193 | + : null, |
| 194 | + batch: run.batch ? { friendlyId: run.batch.friendlyId } : null, |
| 195 | + schedule: schedule, |
| 196 | + }; |
| 197 | + |
| 198 | + // Validate the run data |
| 199 | + const validatedRun = RunContextSchema.parse(runData); |
| 200 | + |
102 | 201 | return json({ |
103 | | - run: { |
104 | | - id: run.id, |
105 | | - friendlyId: run.friendlyId, |
106 | | - taskIdentifier: run.taskIdentifier, |
107 | | - status: run.status, |
108 | | - createdAt: run.createdAt.toISOString(), |
109 | | - startedAt: run.startedAt?.toISOString(), |
110 | | - completedAt: run.completedAt?.toISOString(), |
111 | | - isTest: run.isTest, |
112 | | - tags: run.runTags, |
113 | | - queue: run.queue, |
114 | | - concurrencyKey: run.concurrencyKey, |
115 | | - usageDurationMs: run.usageDurationMs, |
116 | | - costInCents: run.costInCents, |
117 | | - baseCostInCents: run.baseCostInCents, |
118 | | - machinePreset: run.machinePreset, |
119 | | - version: run.lockedToVersion?.version, |
120 | | - rootRun: run.rootTaskRun |
121 | | - ? { |
122 | | - friendlyId: run.rootTaskRun.friendlyId, |
123 | | - taskIdentifier: run.rootTaskRun.taskIdentifier, |
124 | | - } |
125 | | - : null, |
126 | | - parentRun: run.parentTaskRun |
127 | | - ? { |
128 | | - friendlyId: run.parentTaskRun.friendlyId, |
129 | | - taskIdentifier: run.parentTaskRun.taskIdentifier, |
130 | | - } |
131 | | - : null, |
132 | | - batch: run.batch ? { friendlyId: run.batch.friendlyId } : null, |
133 | | - schedule: schedule, |
134 | | - }, |
| 202 | + run: validatedRun, |
135 | 203 | }); |
136 | 204 | }; |
0 commit comments