Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/define-runevent-schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@trigger.dev/core": patch
---

Define RunEvent schema and update ApiClient to use it
4 changes: 3 additions & 1 deletion packages/core/src/v3/apiClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
EnvironmentVariableResponseBody,
EnvironmentVariableWithSecret,
ListQueueOptions,
ListRunEventsResponse,
ListRunResponseItem,
ListScheduleOptions,
QueueItem,
Expand All @@ -42,6 +43,7 @@ import {
RetrieveQueueParam,
RetrieveRunResponse,
RetrieveRunTraceResponseBody,
RunEvent,
ScheduleObject,
SendInputStreamResponseBody,
StreamBatchItemsResponse,
Expand Down Expand Up @@ -700,7 +702,7 @@ export class ApiClient {

listRunEvents(runId: string, requestOptions?: ZodFetchOptions) {
return zodfetch(
z.any(), // TODO: define a proper schema for this
ListRunEventsResponse,
`${this.baseUrl}/api/v1/runs/${runId}/events`,
{
method: "GET",
Expand Down
95 changes: 94 additions & 1 deletion packages/core/src/v3/schemas/api-type.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import { InitializeDeploymentRequestBody } from "./api.js";
import { InitializeDeploymentRequestBody, RunEvent, ListRunEventsResponse } from "./api.js";
import type { InitializeDeploymentRequestBody as InitializeDeploymentRequestBodyType } from "./api.js";

describe("InitializeDeploymentRequestBody", () => {
Expand Down Expand Up @@ -139,3 +139,96 @@ describe("InitializeDeploymentRequestBody", () => {
});
});
});

describe("RunEvent Schema", () => {
const validEvent = {
spanId: "span_123",
parentId: "span_root",
runId: "run_abc",
message: "Test event",
style: {
icon: "task",
variant: "primary",
},
startTime: "2024-03-14T00:00:00Z",
duration: 1234,
isError: false,
isPartial: false,
isCancelled: false,
level: "INFO",
kind: "TASK",
attemptNumber: 1,
};

it("parses a valid event correctly", () => {
const result = RunEvent.safeParse(validEvent);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.spanId).toBe("span_123");
expect(result.data.startTime).toBeInstanceOf(Date);
expect(result.data.level).toBe("INFO");
}
});

it("fails on missing required fields", () => {
const invalidEvent = { ...validEvent };
delete (invalidEvent as any).spanId;
const result = RunEvent.safeParse(invalidEvent);
expect(result.success).toBe(false);
});

it("fails on invalid level", () => {
const invalidEvent = { ...validEvent, level: "INVALID_LEVEL" };
const result = RunEvent.safeParse(invalidEvent);
expect(result.success).toBe(false);
});

it("coerces startTime to Date", () => {
const result = RunEvent.parse(validEvent);
expect(result.startTime).toBeInstanceOf(Date);
expect(result.startTime.toISOString()).toBe("2024-03-14T00:00:00.000Z");
});

it("allows optional/null parentId", () => {
const eventWithoutParent = { ...validEvent };
delete (eventWithoutParent as any).parentId;
expect(RunEvent.safeParse(eventWithoutParent).success).toBe(true);

const eventWithNullParent = { ...validEvent, parentId: null };
expect(RunEvent.safeParse(eventWithNullParent).success).toBe(true);
});
});

describe("ListRunEventsResponse Schema", () => {
it("parses a valid wrapped response", () => {
const response = {
events: [
{
spanId: "span_1",
runId: "run_1",
message: "Event 1",
style: {},
startTime: "2024-03-14T00:00:00Z",
duration: 100,
isError: false,
isPartial: false,
isCancelled: false,
level: "INFO",
kind: "TASK",
},
],
};

const result = ListRunEventsResponse.safeParse(response);
expect(result.success).toBe(true);
if (result.success && result.data) {
expect(result.data.events[0]!.spanId).toBe("span_1");
}
});

it("fails on plain array", () => {
const response = [{ spanId: "span_1" }];
const result = ListRunEventsResponse.safeParse(response);
expect(result.success).toBe(false);
});
});
29 changes: 29 additions & 0 deletions packages/core/src/v3/schemas/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
} from "./common.js";
import { BackgroundWorkerMetadata } from "./resources.js";
import { DequeuedMessage, MachineResources } from "./runEngine.js";
import { TaskEventStyle } from "./style.js";
import { SpanEvents } from "./openTelemetry.js";

export const RunEngineVersion = z.union([z.literal("V1"), z.literal("V2")]);

Expand Down Expand Up @@ -1639,3 +1641,30 @@ export const SendInputStreamResponseBody = z.object({
ok: z.boolean(),
});
export type SendInputStreamResponseBody = z.infer<typeof SendInputStreamResponseBody>;
export const TaskEventLevel = z.enum(["TRACE", "DEBUG", "INFO", "LOG", "WARN", "ERROR"]);
export type TaskEventLevel = z.infer<typeof TaskEventLevel>;

export const RunEvent = z.object({
spanId: z.string(),
parentId: z.string().nullish(),
runId: z.string(),
message: z.string(),
style: TaskEventStyle,
startTime: z.coerce.date(),
duration: z.number(),
isError: z.boolean(),
isPartial: z.boolean(),
isCancelled: z.boolean(),
level: TaskEventLevel,
events: SpanEvents.optional(),
kind: z.string(),
attemptNumber: z.number().optional(),
});

export type RunEvent = z.infer<typeof RunEvent>;

export const ListRunEventsResponse = z.object({
events: z.array(RunEvent),
});

export type ListRunEventsResponse = z.infer<typeof ListRunEventsResponse>;
Loading