From d763cb884f9e7eb276c76801116d49a7e6aa4a3a Mon Sep 17 00:00:00 2001 From: bharathkumar39293 Date: Sat, 14 Mar 2026 08:20:58 +0600 Subject: [PATCH 1/9] fix(core): define RunEvent schema and update ApiClient to use it --- packages/core/src/v3/apiClient/index.ts | 3 +- packages/core/src/v3/schemas/api-type.test.ts | 59 ++++++++++++++++++- packages/core/src/v3/schemas/api.ts | 23 ++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/packages/core/src/v3/apiClient/index.ts b/packages/core/src/v3/apiClient/index.ts index 7de6e275fc4..fbbc209057d 100644 --- a/packages/core/src/v3/apiClient/index.ts +++ b/packages/core/src/v3/apiClient/index.ts @@ -42,6 +42,7 @@ import { RetrieveQueueParam, RetrieveRunResponse, RetrieveRunTraceResponseBody, + RunEvent, ScheduleObject, SendInputStreamResponseBody, StreamBatchItemsResponse, @@ -700,7 +701,7 @@ export class ApiClient { listRunEvents(runId: string, requestOptions?: ZodFetchOptions) { return zodfetch( - z.any(), // TODO: define a proper schema for this + RunEvent.array(), `${this.baseUrl}/api/v1/runs/${runId}/events`, { method: "GET", diff --git a/packages/core/src/v3/schemas/api-type.test.ts b/packages/core/src/v3/schemas/api-type.test.ts index c936b3c769d..47b474aeee3 100644 --- a/packages/core/src/v3/schemas/api-type.test.ts +++ b/packages/core/src/v3/schemas/api-type.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { InitializeDeploymentRequestBody } from "./api.js"; +import { InitializeDeploymentRequestBody, RunEvent } from "./api.js"; import type { InitializeDeploymentRequestBody as InitializeDeploymentRequestBodyType } from "./api.js"; describe("InitializeDeploymentRequestBody", () => { @@ -139,3 +139,60 @@ 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 parentId", () => { + const eventWithoutParent = { ...validEvent }; + delete (eventWithoutParent as any).parentId; + const result = RunEvent.safeParse(eventWithoutParent); + expect(result.success).toBe(true); + }); +}); diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index dc72f155bdc..a38b562d4d7 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -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")]); @@ -1639,3 +1641,24 @@ export const SendInputStreamResponseBody = z.object({ ok: z.boolean(), }); export type SendInputStreamResponseBody = z.infer; +export const TaskEventLevel = z.enum(["TRACE", "DEBUG", "INFO", "LOG", "WARN", "ERROR"]); +export type TaskEventLevel = z.infer; + +export const RunEvent = z.object({ + spanId: z.string(), + parentId: z.string().optional(), + 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; From 0fba878b965db04ceffddda5d46ea04f3f151da0 Mon Sep 17 00:00:00 2001 From: bharathkumar39293 Date: Sat, 14 Mar 2026 08:39:54 +0600 Subject: [PATCH 2/9] chore: add changeset for RunEvent schema --- .changeset/define-runevent-schema.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/define-runevent-schema.md diff --git a/.changeset/define-runevent-schema.md b/.changeset/define-runevent-schema.md new file mode 100644 index 00000000000..fd4248add22 --- /dev/null +++ b/.changeset/define-runevent-schema.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/core": patch +--- + +Define RunEvent schema and update ApiClient to use it From 6bc752cfb177c515f5061a12790025c4344b231d Mon Sep 17 00:00:00 2001 From: bharathkumar39293 Date: Sat, 14 Mar 2026 08:49:10 +0600 Subject: [PATCH 3/9] fix(core): fix RunEvent schema mismatch and nullable parentId --- packages/core/src/v3/apiClient/index.ts | 3 +- packages/core/src/v3/schemas/api-type.test.ts | 43 +++++++++++++++++-- packages/core/src/v3/schemas/api.ts | 8 +++- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/packages/core/src/v3/apiClient/index.ts b/packages/core/src/v3/apiClient/index.ts index fbbc209057d..ace3f8e8210 100644 --- a/packages/core/src/v3/apiClient/index.ts +++ b/packages/core/src/v3/apiClient/index.ts @@ -28,6 +28,7 @@ import { EnvironmentVariableResponseBody, EnvironmentVariableWithSecret, ListQueueOptions, + ListRunEventsResponse, ListRunResponseItem, ListScheduleOptions, QueueItem, @@ -701,7 +702,7 @@ export class ApiClient { listRunEvents(runId: string, requestOptions?: ZodFetchOptions) { return zodfetch( - RunEvent.array(), + ListRunEventsResponse, `${this.baseUrl}/api/v1/runs/${runId}/events`, { method: "GET", diff --git a/packages/core/src/v3/schemas/api-type.test.ts b/packages/core/src/v3/schemas/api-type.test.ts index 47b474aeee3..657d29c0ac2 100644 --- a/packages/core/src/v3/schemas/api-type.test.ts +++ b/packages/core/src/v3/schemas/api-type.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { InitializeDeploymentRequestBody, RunEvent } from "./api.js"; +import { InitializeDeploymentRequestBody, RunEvent, ListRunEventsResponse } from "./api.js"; import type { InitializeDeploymentRequestBody as InitializeDeploymentRequestBodyType } from "./api.js"; describe("InitializeDeploymentRequestBody", () => { @@ -189,10 +189,47 @@ describe("RunEvent Schema", () => { expect(result.startTime.toISOString()).toBe("2024-03-14T00:00:00.000Z"); }); - it("allows optional parentId", () => { + it("allows optional/null parentId", () => { const eventWithoutParent = { ...validEvent }; delete (eventWithoutParent as any).parentId; - const result = RunEvent.safeParse(eventWithoutParent); + 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) { + expect(result.data.events).toHaveLength(1); + 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); }); }); diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index a38b562d4d7..705741de688 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -1646,7 +1646,7 @@ export type TaskEventLevel = z.infer; export const RunEvent = z.object({ spanId: z.string(), - parentId: z.string().optional(), + parentId: z.string().nullish(), runId: z.string(), message: z.string(), style: TaskEventStyle, @@ -1662,3 +1662,9 @@ export const RunEvent = z.object({ }); export type RunEvent = z.infer; + +export const ListRunEventsResponse = z.object({ + events: z.array(RunEvent), +}); + +export type ListRunEventsResponse = z.infer; From 0d361c8ba19f9da53a481466f301046148209570 Mon Sep 17 00:00:00 2001 From: bharathkumar39293 Date: Sat, 14 Mar 2026 22:05:45 +0600 Subject: [PATCH 4/9] fix(core): fix lint error in RunEvent tests --- packages/core/src/v3/schemas/api-type.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/core/src/v3/schemas/api-type.test.ts b/packages/core/src/v3/schemas/api-type.test.ts index 657d29c0ac2..cb3e45a0aca 100644 --- a/packages/core/src/v3/schemas/api-type.test.ts +++ b/packages/core/src/v3/schemas/api-type.test.ts @@ -221,9 +221,8 @@ describe("ListRunEventsResponse Schema", () => { const result = ListRunEventsResponse.safeParse(response); expect(result.success).toBe(true); - if (result.success) { - expect(result.data.events).toHaveLength(1); - expect(result.data.events[0].spanId).toBe("span_1"); + if (result.success && result.data) { + expect(result.data.events[0]!.spanId).toBe("span_1"); } }); From fd2f8231423afd85cdb18a1eba5e9fb889942b5a Mon Sep 17 00:00:00 2001 From: bharathkumar39293 Date: Sat, 14 Mar 2026 22:32:18 +0600 Subject: [PATCH 5/9] fix(core): handle nanosecond timestamps and nullable attemptNumber in RunEvent --- packages/core/src/v3/schemas/api-type.test.ts | 35 +++++++++++++++++++ packages/core/src/v3/schemas/api.ts | 26 ++++++++++++-- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/packages/core/src/v3/schemas/api-type.test.ts b/packages/core/src/v3/schemas/api-type.test.ts index cb3e45a0aca..54267485766 100644 --- a/packages/core/src/v3/schemas/api-type.test.ts +++ b/packages/core/src/v3/schemas/api-type.test.ts @@ -189,6 +189,21 @@ describe("RunEvent Schema", () => { expect(result.startTime.toISOString()).toBe("2024-03-14T00:00:00.000Z"); }); + it("handles 19-digit nanosecond startTime strings", () => { + const event = { ...validEvent, startTime: "1710374400000000000" }; + const result = RunEvent.parse(event); + expect(result.startTime).toBeInstanceOf(Date); + // 1710374400000000000 ns = 1710374400000 ms = 2024-03-14T00:00:00Z + expect(result.startTime.toISOString()).toBe("2024-03-14T00:00:00.000Z"); + }); + + it("handles bigint nanosecond startTime", () => { + const event = { ...validEvent, startTime: 1710374400000000000n }; + const result = RunEvent.parse(event as any); + 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; @@ -197,6 +212,26 @@ describe("RunEvent Schema", () => { const eventWithNullParent = { ...validEvent, parentId: null }; expect(RunEvent.safeParse(eventWithNullParent).success).toBe(true); }); + + it("allows nullish attemptNumber", () => { + const eventWithNullAttempt = { ...validEvent, attemptNumber: null }; + const result = RunEvent.safeParse(eventWithNullAttempt); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.attemptNumber).toBe(null); + } + + const eventWithoutAttempt = { ...validEvent }; + delete (eventWithoutAttempt as any).attemptNumber; + const result2 = RunEvent.safeParse(eventWithoutAttempt); + expect(result2.success).toBe(true); + }); + + it("supports taskSlug", () => { + const eventWithSlug = { ...validEvent, taskSlug: "my-task" }; + const result = RunEvent.parse(eventWithSlug); + expect(result.taskSlug).toBe("my-task"); + }); }); describe("ListRunEventsResponse Schema", () => { diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 705741de688..eb000af1898 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -1643,6 +1643,27 @@ export const SendInputStreamResponseBody = z.object({ export type SendInputStreamResponseBody = z.infer; export const TaskEventLevel = z.enum(["TRACE", "DEBUG", "INFO", "LOG", "WARN", "ERROR"]); export type TaskEventLevel = z.infer; +export const NanosecondTimestampSchema = z + .union([z.string(), z.number(), z.bigint()]) + .transform((val) => { + // If it's already a Date, return it (though union doesn't include it) + if (typeof val === "object" && val instanceof Date) return val; + + const str = val.toString(); + + // 19 digits is nanoseconds (e.g., 1710374400000000000) + if (str.length === 19) { + return new Date(Number(BigInt(str) / 1000000n)); + } + + // 13 digits is milliseconds + if (str.length === 13) { + return new Date(Number(val)); + } + + // Default to standard date coercion + return new Date(val as any); + }); export const RunEvent = z.object({ spanId: z.string(), @@ -1650,7 +1671,7 @@ export const RunEvent = z.object({ runId: z.string(), message: z.string(), style: TaskEventStyle, - startTime: z.coerce.date(), + startTime: NanosecondTimestampSchema, duration: z.number(), isError: z.boolean(), isPartial: z.boolean(), @@ -1658,7 +1679,8 @@ export const RunEvent = z.object({ level: TaskEventLevel, events: SpanEvents.optional(), kind: z.string(), - attemptNumber: z.number().optional(), + attemptNumber: z.number().nullish(), + taskSlug: z.string().optional(), }); export type RunEvent = z.infer; From 45c7318b299bf9946457a8de564351c0a3f65152 Mon Sep 17 00:00:00 2001 From: bharathkumar39293 Date: Sun, 15 Mar 2026 07:20:12 +0600 Subject: [PATCH 6/9] fix(core): harden RunEvent schema and acknowledge contract change risk --- packages/core/src/v3/schemas/api-type.test.ts | 6 +++++ packages/core/src/v3/schemas/api.ts | 24 ++++++++++--------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/core/src/v3/schemas/api-type.test.ts b/packages/core/src/v3/schemas/api-type.test.ts index 54267485766..57044c992be 100644 --- a/packages/core/src/v3/schemas/api-type.test.ts +++ b/packages/core/src/v3/schemas/api-type.test.ts @@ -204,6 +204,12 @@ describe("RunEvent Schema", () => { expect(result.startTime.toISOString()).toBe("2024-03-14T00:00:00.000Z"); }); + it("fails on invalid startTime", () => { + const event = { ...validEvent, startTime: "not-a-date" }; + const result = RunEvent.safeParse(event); + expect(result.success).toBe(false); + }); + it("allows optional/null parentId", () => { const eventWithoutParent = { ...validEvent }; delete (eventWithoutParent as any).parentId; diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index eb000af1898..280f0e907f0 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -1646,23 +1646,25 @@ export type TaskEventLevel = z.infer; export const NanosecondTimestampSchema = z .union([z.string(), z.number(), z.bigint()]) .transform((val) => { - // If it's already a Date, return it (though union doesn't include it) - if (typeof val === "object" && val instanceof Date) return val; - const str = val.toString(); + let date: Date; + // 19 digits is nanoseconds (e.g., 1710374400000000000) if (str.length === 19) { - return new Date(Number(BigInt(str) / 1000000n)); + date = new Date(Number(BigInt(str) / 1000000n)); + } else if (str.length === 13) { + // 13 digits is milliseconds + date = new Date(Number(str)); + } else { + // Default to standard date coercion + date = new Date(str); } - // 13 digits is milliseconds - if (str.length === 13) { - return new Date(Number(val)); - } - - // Default to standard date coercion - return new Date(val as any); + return date; + }) + .refine((d) => !isNaN(d.getTime()), { + message: "Invalid timestamp", }); export const RunEvent = z.object({ From 3993730737103a3c522ca34d56b72d60b3ce3a90 Mon Sep 17 00:00:00 2001 From: bharathkumar39293 Date: Sun, 15 Mar 2026 08:55:17 +0600 Subject: [PATCH 7/9] fix(core): Harden RunEvent schema and add backward-compatibility transform --- .changeset/define-runevent-schema.md | 2 +- packages/cli-v3/src/commands/deploy.ts | 91 ++++++++---------- packages/cli-v3/tsc_output.txt | Bin 0 -> 17112 bytes .../src/v3/realtimeStreams/streamsWriterV2.ts | 20 ++-- packages/core/src/v3/schemas/api-type.test.ts | 27 +++++- packages/core/src/v3/schemas/api.ts | 53 +++++++--- 6 files changed, 114 insertions(+), 79 deletions(-) create mode 100644 packages/cli-v3/tsc_output.txt diff --git a/.changeset/define-runevent-schema.md b/.changeset/define-runevent-schema.md index fd4248add22..531ca78f234 100644 --- a/.changeset/define-runevent-schema.md +++ b/.changeset/define-runevent-schema.md @@ -1,5 +1,5 @@ --- -"@trigger.dev/core": patch +"@trigger.dev/core": minor --- Define RunEvent schema and update ApiClient to use it diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 1ac161d3e4a..27ed56f1c60 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -501,9 +501,8 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { const version = deployment.version; const rawDeploymentLink = `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project}/deployments/${deployment.shortCode}`; - const rawTestLink = `${authorization.dashboardUrl}/projects/v3/${ - resolvedConfig.project - }/test?environment=${options.env === "prod" ? "prod" : "stg"}`; + const rawTestLink = `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project + }/test?environment=${options.env === "prod" ? "prod" : "stg"}`; const deploymentLink = cliLink("View deployment", rawDeploymentLink); const testLink = cliLink("Test tasks", rawTestLink); @@ -720,8 +719,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { } } else { outro( - `Version ${version} deployed with ${taskCount} detected task${taskCount === 1 ? "" : "s"} ${ - isLinksSupported ? `| ${deploymentLink} | ${testLink}` : "" + `Version ${version} deployed with ${taskCount} detected task${taskCount === 1 ? "" : "s"} ${isLinksSupported ? `| ${deploymentLink} | ${testLink}` : "" }` ); @@ -745,18 +743,16 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { TRIGGER_VERSION: version, TRIGGER_DEPLOYMENT_SHORT_CODE: deployment.shortCode, TRIGGER_DEPLOYMENT_URL: `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project}/deployments/${deployment.shortCode}`, - TRIGGER_TEST_URL: `${authorization.dashboardUrl}/projects/v3/${ - resolvedConfig.project - }/test?environment=${options.env === "prod" ? "prod" : "stg"}`, + TRIGGER_TEST_URL: `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project + }/test?environment=${options.env === "prod" ? "prod" : "stg"}`, }, outputs: { deploymentVersion: version, workerVersion: version, deploymentShortCode: deployment.shortCode, deploymentUrl: `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project}/deployments/${deployment.shortCode}`, - testUrl: `${authorization.dashboardUrl}/projects/v3/${ - resolvedConfig.project - }/test?environment=${options.env === "prod" ? "prod" : "stg"}`, + testUrl: `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project + }/test?environment=${options.env === "prod" ? "prod" : "stg"}`, needsPromotion: options.skipPromotion ? "true" : "false", }, }); @@ -799,8 +795,7 @@ async function failDeploy( checkLogsForErrors(logs); outro( - `${chalkError(`${prefix}:`)} ${ - error.message + `${chalkError(`${prefix}:`)} ${error.message }. Full build logs have been saved to ${logPath}` ); @@ -1100,9 +1095,8 @@ async function handleNativeBuildServerDeploy({ const deployment = initializeDeploymentResult.data; const rawDeploymentLink = `${dashboardUrl}/projects/v3/${config.project}/deployments/${deployment.shortCode}`; - const rawTestLink = `${dashboardUrl}/projects/v3/${config.project}/test?environment=${ - options.env === "prod" ? "prod" : "stg" - }`; + const rawTestLink = `${dashboardUrl}/projects/v3/${config.project}/test?environment=${options.env === "prod" ? "prod" : "stg" + }`; const exposedDeploymentLink = isLinksSupported ? cliLink(chalk.bold(rawDeploymentLink), rawDeploymentLink) @@ -1155,8 +1149,9 @@ async function handleNativeBuildServerDeploy({ const [readSessionError, readSession] = await tryCatch( stream.readSession( { - start: { from: { seqNum: 0 }, clamp: true }, - stop: { waitSecs: 60 * 20 }, // 20 minutes + seq_num: 0, + clamp: true, + wait: 60 * 20, // 20 minutes }, { signal: abortController.signal } ) @@ -1167,8 +1162,7 @@ async function handleNativeBuildServerDeploy({ log.warn(`Failed streaming build logs, open the deployment in the dashboard to view the logs`); outro( - `Version ${deployment.version} is being deployed ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + `Version ${deployment.version} is being deployed ${isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" }` ); @@ -1193,7 +1187,7 @@ async function handleNativeBuildServerDeploy({ switch (event.type) { case "log": { - if (record.seqNum === 0) { + if (record.seq_num === 0) { $queuedSpinner.stop("Build started"); console.log("│"); queuedSpinnerStopped = true; @@ -1214,10 +1208,10 @@ async function handleNativeBuildServerDeploy({ level === "error" ? chalk.bold(chalkError(message)) : level === "warn" - ? chalkWarning(message) - : level === "debug" - ? chalkGrey(message) - : message; + ? chalkWarning(message) + : level === "debug" + ? chalkGrey(message) + : message; // We use console.log here instead of clack's logger as the current version does not support changing the line spacing. // And the logs look verbose with the default spacing. @@ -1250,8 +1244,7 @@ async function handleNativeBuildServerDeploy({ log.error("Failed dequeueing build, please try again shortly"); throw new OutroCommandError( - `Version ${deployment.version} ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + `Version ${deployment.version} ${isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" }` ); } @@ -1266,8 +1259,7 @@ async function handleNativeBuildServerDeploy({ } throw new OutroCommandError( - `Version ${deployment.version} ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + `Version ${deployment.version} ${isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" }` ); } @@ -1293,13 +1285,12 @@ async function handleNativeBuildServerDeploy({ } outro( - `Version ${deployment.version} was deployed ${ - isLinksSupported - ? `| ${cliLink("Test tasks", rawTestLink)} | ${cliLink( - "View deployment", - rawDeploymentLink - )}` - : "" + `Version ${deployment.version} was deployed ${isLinksSupported + ? `| ${cliLink("Test tasks", rawTestLink)} | ${cliLink( + "View deployment", + rawDeploymentLink + )}` + : "" }` ); return process.exit(0); @@ -1313,14 +1304,13 @@ async function handleNativeBuildServerDeploy({ chalk.bold( chalkError( "Deployment failed" + - (finalDeploymentEvent.message ? `: ${finalDeploymentEvent.message}` : "") + (finalDeploymentEvent.message ? `: ${finalDeploymentEvent.message}` : "") ) ) ); throw new OutroCommandError( - `Version ${deployment.version} deployment failed ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + `Version ${deployment.version} deployment failed ${isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" }` ); } @@ -1333,14 +1323,13 @@ async function handleNativeBuildServerDeploy({ chalk.bold( chalkError( "Deployment timed out" + - (finalDeploymentEvent.message ? `: ${finalDeploymentEvent.message}` : "") + (finalDeploymentEvent.message ? `: ${finalDeploymentEvent.message}` : "") ) ) ); throw new OutroCommandError( - `Version ${deployment.version} deployment timed out ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + `Version ${deployment.version} deployment timed out ${isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" }` ); } @@ -1353,14 +1342,13 @@ async function handleNativeBuildServerDeploy({ chalk.bold( chalkError( "Deployment was canceled" + - (finalDeploymentEvent.message ? `: ${finalDeploymentEvent.message}` : "") + (finalDeploymentEvent.message ? `: ${finalDeploymentEvent.message}` : "") ) ) ); throw new OutroCommandError( - `Version ${deployment.version} deployment canceled ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + `Version ${deployment.version} deployment canceled ${isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" }` ); } @@ -1379,13 +1367,12 @@ async function handleNativeBuildServerDeploy({ } outro( - `Version ${deployment.version} ${ - isLinksSupported - ? `| ${cliLink("Test tasks", rawTestLink)} | ${cliLink( - "View deployment", - rawDeploymentLink - )}` - : "" + `Version ${deployment.version} ${isLinksSupported + ? `| ${cliLink("Test tasks", rawTestLink)} | ${cliLink( + "View deployment", + rawDeploymentLink + )}` + : "" }` ); return process.exit(0); diff --git a/packages/cli-v3/tsc_output.txt b/packages/cli-v3/tsc_output.txt new file mode 100644 index 0000000000000000000000000000000000000000..c6eacfa21910ed4a9241eb569259fb1fae88ace6 GIT binary patch literal 17112 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 i*RYvgK1Xk7 implements StreamsWriter { accessToken: options.accessToken, ...(options.endpoint ? { - endpoints: { - account: options.endpoint, - basin: options.endpoint, - }, - } + endpoints: { + account: options.endpoint, + basin: options.endpoint, + }, + } : {}), }); this.flushIntervalMs = options.flushIntervalMs ?? 200; @@ -135,7 +135,7 @@ export class StreamsWriterV2 implements StreamsWriter { const stream = basin.stream(this.options.stream); const session = await stream.appendSession({ - maxInflightBytes: this.maxInflightBytes, + maxQueuedBytes: this.maxInflightBytes, }); this.sessionWritable = session.writable; @@ -152,7 +152,7 @@ export class StreamsWriterV2 implements StreamsWriter { return; } // Convert each chunk to JSON string and wrap in AppendRecord - controller.enqueue(AppendRecord.string({ body: JSON.stringify({ data: chunk, id: nanoid(7) }) })); + controller.enqueue(AppendRecord.make(JSON.stringify({ data: chunk, id: nanoid(7) }))); }, }) ) @@ -169,9 +169,9 @@ export class StreamsWriterV2 implements StreamsWriter { const lastAcked = session.lastAckedPosition(); if (lastAcked?.end) { - const recordsWritten = lastAcked.end.seqNum; + const recordsWritten = lastAcked.end.seq_num; this.log( - `[S2MetadataStream] Written ${recordsWritten} records, ending at seqNum=${lastAcked.end.seqNum}` + `[S2MetadataStream] Written ${recordsWritten} records, ending at seq_num=${lastAcked.end.seq_num}` ); } } catch (error) { @@ -223,5 +223,5 @@ async function* streamToAsyncIterator(stream: ReadableStream): AsyncIterab function safeReleaseLock(reader: ReadableStreamDefaultReader) { try { reader.releaseLock(); - } catch (error) {} + } catch (error) { } } diff --git a/packages/core/src/v3/schemas/api-type.test.ts b/packages/core/src/v3/schemas/api-type.test.ts index 57044c992be..87e41b4ece9 100644 --- a/packages/core/src/v3/schemas/api-type.test.ts +++ b/packages/core/src/v3/schemas/api-type.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { InitializeDeploymentRequestBody, RunEvent, ListRunEventsResponse } from "./api.js"; +import { InitializeDeploymentRequestBody, RunEvent, ListRunEventsResponse, ListRunEventsResponseWithStringDates } from "./api.js"; import type { InitializeDeploymentRequestBody as InitializeDeploymentRequestBodyType } from "./api.js"; describe("InitializeDeploymentRequestBody", () => { @@ -197,6 +197,18 @@ describe("RunEvent Schema", () => { expect(result.startTime.toISOString()).toBe("2024-03-14T00:00:00.000Z"); }); + it("should handle Date object", () => { + const now = new Date(); + const result = RunEvent.safeParse({ + ...validEvent, + startTime: now, + }); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.startTime.toISOString()).toBe(now.toISOString()); + } + }); + it("handles bigint nanosecond startTime", () => { const event = { ...validEvent, startTime: 1710374400000000000n }; const result = RunEvent.parse(event as any); @@ -238,6 +250,19 @@ describe("RunEvent Schema", () => { const result = RunEvent.parse(eventWithSlug); expect(result.taskSlug).toBe("my-task"); }); + + it("ListRunEventsResponseWithStringDates correctly transforms Dates to strings", () => { + const rawResponse = { + events: [validEvent], + }; + + const parsed = ListRunEventsResponse.parse(rawResponse); + expect(parsed.events[0]!.startTime).toBeInstanceOf(Date); + + const legacy = ListRunEventsResponseWithStringDates.parse(rawResponse); + expect(typeof legacy.events[0]!.startTime).toBe("string"); + expect(legacy.events[0]!.startTime).toBe(parsed.events[0]!.startTime.toISOString()); + }); }); describe("ListRunEventsResponse Schema", () => { diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 280f0e907f0..43f6eefceb1 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -1644,27 +1644,27 @@ export type SendInputStreamResponseBody = z.infer; export const NanosecondTimestampSchema = z - .union([z.string(), z.number(), z.bigint()]) + .union([z.string(), z.number(), z.bigint(), z.date()]) .transform((val) => { + if (val instanceof Date) return val; + const str = val.toString(); - let date: Date; - - // 19 digits is nanoseconds (e.g., 1710374400000000000) - if (str.length === 19) { - date = new Date(Number(BigInt(str) / 1000000n)); - } else if (str.length === 13) { - // 13 digits is milliseconds - date = new Date(Number(str)); - } else { - // Default to standard date coercion - date = new Date(str); + // 19-digit nanoseconds -> milliseconds + if (str.length === 19 && /^\d+$/.test(str)) { + return new Date(Number(BigInt(str) / 1_000_000n)); + } + + // 13-digit milliseconds + if (str.length === 13 && /^\d+$/.test(str)) { + return new Date(Number(str)); } - return date; + // Fallback: parse ISO or other date string + return new Date(str); }) - .refine((d) => !isNaN(d.getTime()), { - message: "Invalid timestamp", + .refine((date) => !isNaN(date.getTime()), { + message: "Invalid date", }); export const RunEvent = z.object({ @@ -1687,8 +1687,31 @@ export const RunEvent = z.object({ export type RunEvent = z.infer; +/** + * A legacy-focused version of RunEvent where startTime is guaranteed to be an ISO string. + * Useful for consumers who haven't yet migrated to handling Date objects. + */ +export type RunEventWithStringDates = Omit & { + startTime: string; +}; + export const ListRunEventsResponse = z.object({ events: z.array(RunEvent), }); export type ListRunEventsResponse = z.infer; + +/** + * A legacy-focused version of the response where events have startTime as an ISO string. + * This can be used as a transform on the original response for backward compatibility. + */ +export const ListRunEventsResponseWithStringDates = ListRunEventsResponse.transform((resp) => ({ + events: resp.events.map((e) => ({ + ...e, + startTime: e.startTime.toISOString(), + })), +})); + +export type ListRunEventsResponseWithStringDates = z.infer< + typeof ListRunEventsResponseWithStringDates +>; From 64c0271c6d669a5f9c4f1b4087c117f0f9965163 Mon Sep 17 00:00:00 2001 From: bharathkumar39293 Date: Sun, 15 Mar 2026 08:57:55 +0600 Subject: [PATCH 8/9] fix(core): Final hardening of NanosecondTimestampSchema with trim and edge-case tests --- packages/core/src/v3/schemas/api-type.test.ts | 40 +++++++++++++++++++ packages/core/src/v3/schemas/api.ts | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/core/src/v3/schemas/api-type.test.ts b/packages/core/src/v3/schemas/api-type.test.ts index 87e41b4ece9..2c9f3a421d3 100644 --- a/packages/core/src/v3/schemas/api-type.test.ts +++ b/packages/core/src/v3/schemas/api-type.test.ts @@ -222,6 +222,46 @@ describe("RunEvent Schema", () => { expect(result.success).toBe(false); }); + describe("startTime edge cases", () => { + it("should handle whitespace-padded strings", () => { + const result = RunEvent.safeParse({ + ...validEvent, + startTime: " 2024-03-14T00:00:00Z ", + }); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.startTime.toISOString()).toBe("2024-03-14T00:00:00.000Z"); + } + }); + + it("should handle whitespace-padded nanosecond strings", () => { + const result = RunEvent.safeParse({ + ...validEvent, + startTime: " 1710374400000000000 ", + }); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.startTime.toISOString()).toBe("2024-03-14T00:00:00.000Z"); + } + }); + + it("should fail on empty string", () => { + const result = RunEvent.safeParse({ + ...validEvent, + startTime: "", + }); + expect(result.success).toBe(false); + }); + + it("should fail on whitespace-only string", () => { + const result = RunEvent.safeParse({ + ...validEvent, + startTime: " ", + }); + expect(result.success).toBe(false); + }); + }); + it("allows optional/null parentId", () => { const eventWithoutParent = { ...validEvent }; delete (eventWithoutParent as any).parentId; diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 43f6eefceb1..e854f67352e 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -1648,7 +1648,7 @@ export const NanosecondTimestampSchema = z .transform((val) => { if (val instanceof Date) return val; - const str = val.toString(); + const str = typeof val === "string" ? val.trim() : val.toString(); // 19-digit nanoseconds -> milliseconds if (str.length === 19 && /^\d+$/.test(str)) { From 838be18327420c521c72653d3e631cdb9c5fdebe Mon Sep 17 00:00:00 2001 From: bharathkumar39293 Date: Sun, 15 Mar 2026 12:30:22 +0600 Subject: [PATCH 9/9] fix(core,cli): resolve S2 dependency version drift and align SDK schemas --- packages/cli-v3/src/commands/deploy.ts | 8 ++++---- .../core/src/v3/realtimeStreams/streamsWriterV2.ts | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 27ed56f1c60..0b88ba7cd69 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -1149,9 +1149,9 @@ async function handleNativeBuildServerDeploy({ const [readSessionError, readSession] = await tryCatch( stream.readSession( { - seq_num: 0, - clamp: true, - wait: 60 * 20, // 20 minutes + start: { + from: { seqNum: 0 }, + }, }, { signal: abortController.signal } ) @@ -1187,7 +1187,7 @@ async function handleNativeBuildServerDeploy({ switch (event.type) { case "log": { - if (record.seq_num === 0) { + if (record.seqNum === 0) { $queuedSpinner.stop("Build started"); console.log("│"); queuedSpinnerStopped = true; diff --git a/packages/core/src/v3/realtimeStreams/streamsWriterV2.ts b/packages/core/src/v3/realtimeStreams/streamsWriterV2.ts index a038fa7aaee..4dbccb383ff 100644 --- a/packages/core/src/v3/realtimeStreams/streamsWriterV2.ts +++ b/packages/core/src/v3/realtimeStreams/streamsWriterV2.ts @@ -135,7 +135,7 @@ export class StreamsWriterV2 implements StreamsWriter { const stream = basin.stream(this.options.stream); const session = await stream.appendSession({ - maxQueuedBytes: this.maxInflightBytes, + maxInflightBytes: this.maxInflightBytes, }); this.sessionWritable = session.writable; @@ -152,7 +152,9 @@ export class StreamsWriterV2 implements StreamsWriter { return; } // Convert each chunk to JSON string and wrap in AppendRecord - controller.enqueue(AppendRecord.make(JSON.stringify({ data: chunk, id: nanoid(7) }))); + controller.enqueue( + AppendRecord.string({ body: JSON.stringify({ data: chunk, id: nanoid(7) }) }) + ); }, }) ) @@ -169,9 +171,9 @@ export class StreamsWriterV2 implements StreamsWriter { const lastAcked = session.lastAckedPosition(); if (lastAcked?.end) { - const recordsWritten = lastAcked.end.seq_num; + const recordsWritten = lastAcked.end.seqNum; this.log( - `[S2MetadataStream] Written ${recordsWritten} records, ending at seq_num=${lastAcked.end.seq_num}` + `[S2MetadataStream] Written ${recordsWritten} records, ending at seqNum=${lastAcked.end.seqNum}` ); } } catch (error) {