diff --git a/.changeset/many-ligers-pump.md b/.changeset/many-ligers-pump.md new file mode 100644 index 000000000..a80fcbd09 --- /dev/null +++ b/.changeset/many-ligers-pump.md @@ -0,0 +1,6 @@ +--- +"trigger.dev": patch +"@trigger.dev/core": patch +--- + +Handle string and non-stringifiable outputs like functions diff --git a/apps/webapp/app/entry.server.tsx b/apps/webapp/app/entry.server.tsx index 85cbfce81..7893f01ab 100644 --- a/apps/webapp/app/entry.server.tsx +++ b/apps/webapp/app/entry.server.tsx @@ -182,11 +182,3 @@ export { apiRateLimiter } from "./services/apiRateLimit.server"; export { socketIo } from "./v3/handleSocketIo.server"; export { wss } from "./v3/handleWebsockets.server"; export { registryProxy } from "./v3/registryProxy.server"; - -process.on("uncaughtException", (error, origin) => { - logger.error("Uncaught Exception", { error, origin }); -}); - -process.on("unhandledRejection", (reason, promise) => { - logger.error("Unhandled Rejection", { reason }); -}); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route.tsx index a8475ef38..8ae95dd16 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route.tsx @@ -222,19 +222,25 @@ function PacketDisplay({ dataType: string; title: string; }) { - if (dataType === "application/store") { - return ( -
- - {title} - - - Download - -
- ); - } else { - return ; + switch (dataType) { + case "application/store": { + return ( +
+ + {title} + + + Download + +
+ ); + } + case "text/plain": { + return ; + } + default: { + return ; + } } } diff --git a/apps/webapp/app/v3/eventRepository.server.ts b/apps/webapp/app/v3/eventRepository.server.ts index ab50cf3ca..aa8049e10 100644 --- a/apps/webapp/app/v3/eventRepository.server.ts +++ b/apps/webapp/app/v3/eventRepository.server.ts @@ -10,7 +10,7 @@ import { SpanMessagingEvent, TaskEventStyle, correctErrorStackTrace, - createPackageAttributesAsJson, + createPacketAttributesAsJson, flattenAttributes, isExceptionSpanEvent, omit, @@ -188,7 +188,7 @@ export class EventRepository { const event = events[0]; const output = options?.attributes.output - ? await createPackageAttributesAsJson( + ? await createPacketAttributesAsJson( options?.attributes.output, options?.attributes.outputType ?? "application/json" ) @@ -213,8 +213,9 @@ export class EventRepository { style: event.style as Attributes, output: output, outputType: - options?.attributes.outputType === "application/store" - ? "application/store" + options?.attributes.outputType === "application/store" || + options?.attributes.outputType === "text/plain" + ? options?.attributes.outputType : "application/json", payload: event.payload as Attributes, payloadType: event.payloadType, diff --git a/apps/webapp/app/v3/services/completeAttempt.server.ts b/apps/webapp/app/v3/services/completeAttempt.server.ts index 743ce1d2a..dd4d3d5a3 100644 --- a/apps/webapp/app/v3/services/completeAttempt.server.ts +++ b/apps/webapp/app/v3/services/completeAttempt.server.ts @@ -105,7 +105,7 @@ export class CompleteAttemptService extends BaseService { attributes: { isError: false, output: - completion.outputType === "application/store" + completion.outputType === "application/store" || completion.outputType === "text/plain" ? completion.output : completion.output ? (safeJsonParse(completion.output) as Attributes) diff --git a/packages/cli-v3/src/commands/dev.tsx b/packages/cli-v3/src/commands/dev.tsx index a21594470..5cdad32ee 100644 --- a/packages/cli-v3/src/commands/dev.tsx +++ b/packages/cli-v3/src/commands/dev.tsx @@ -26,7 +26,7 @@ import * as packageJson from "../../package.json"; import { CliApiClient } from "../apiClient"; import { CommonCommandOptions, commonOptions, wrapCommandAction } from "../cli/common.js"; import { bundleDependenciesPlugin, workerSetupImportConfigPlugin } from "../utilities/build"; -import { chalkGrey, chalkPurple, chalkWorker } from "../utilities/cliOutput"; +import { chalkError, chalkGrey, chalkPurple, chalkTask, chalkWorker } from "../utilities/cliOutput"; import { readConfig } from "../utilities/configFiles"; import { readJSONFile } from "../utilities/fileSystem"; import { printDevBanner, printStandloneInitialBanner } from "../utilities/initialBanner.js"; @@ -77,9 +77,13 @@ export async function devCommand(dir: string, options: DevCommandOptions) { if (!authorization.ok) { if (authorization.error === "fetch failed") { - logger.error("Fetch failed. Platform down?"); + logger.log( + `${chalkError( + "X Error:" + )} Connecting to the server failed. Please check your internet connection or contact eric@trigger.dev for help.` + ); } else { - logger.error("You must login first. Use `trigger.dev login` to login."); + logger.log(`${chalkError("X Error:")} You must login first. Use the \`login\` CLI command.`); } process.exitCode = 1; return; @@ -701,13 +705,13 @@ function createDuplicateTaskIdOutputErrorMessage( .map((id) => { const tasks = taskResources.filter((task) => task.id === id); - return `id "${chalkPurple(id)}" was found in:\n${tasks - .map((task) => `${task.filePath} -> ${task.exportName}`) - .join("\n")}`; + return `\n\n${chalkTask(id)} was found in:${tasks + .map((task) => `\n${task.filePath} -> ${task.exportName}`) + .join("")}`; }) - .join("\n\n"); + .join(""); - return `Duplicate task ids detected:\n\n${duplicateTable}\n\n`; + return `Duplicate ${chalkTask("task id")} detected:${duplicateTable}`; } function gatherProcessEnv() { diff --git a/packages/cli-v3/src/workers/dev/backgroundWorker.ts b/packages/cli-v3/src/workers/dev/backgroundWorker.ts index d5859679e..16fb006f3 100644 --- a/packages/cli-v3/src/workers/dev/backgroundWorker.ts +++ b/packages/cli-v3/src/workers/dev/backgroundWorker.ts @@ -18,16 +18,11 @@ import { formatDurationMilliseconds, workerToChildMessages, } from "@trigger.dev/core/v3"; -import chalk from "chalk"; import dotenv from "dotenv"; import { Evt } from "evt"; import { ChildProcess, fork } from "node:child_process"; import { dirname, resolve } from "node:path"; import terminalLink from "terminal-link"; -import { safeDeleteFileSync } from "../../utilities/fileSystem.js"; -import { installPackages } from "../../utilities/installPackages.js"; -import { logger } from "../../utilities/logger.js"; -import { UncaughtExceptionError } from "../common/errors.js"; import { chalkError, chalkGrey, @@ -39,6 +34,10 @@ import { chalkWorker, prettyPrintDate, } from "../../utilities/cliOutput.js"; +import { safeDeleteFileSync } from "../../utilities/fileSystem.js"; +import { installPackages } from "../../utilities/installPackages.js"; +import { logger } from "../../utilities/logger.js"; +import { UncaughtExceptionError } from "../common/errors.js"; export type CurrentWorkers = BackgroundWorkerCoordinator["currentWorkers"]; export class BackgroundWorkerCoordinator { diff --git a/packages/core/src/v3/index.ts b/packages/core/src/v3/index.ts index d3696950a..7c1871924 100644 --- a/packages/core/src/v3/index.ts +++ b/packages/core/src/v3/index.ts @@ -64,7 +64,7 @@ export { stringifyIO, prettyPrintPacket, createPacketAttributes, - createPackageAttributesAsJson, + createPacketAttributesAsJson, conditionallyExportPacket, conditionallyImportPacket, packetRequiresOffloading, diff --git a/packages/core/src/v3/utils/ioSerialization.ts b/packages/core/src/v3/utils/ioSerialization.ts index 4aa25d0f1..e6215551f 100644 --- a/packages/core/src/v3/utils/ioSerialization.ts +++ b/packages/core/src/v3/utils/ioSerialization.ts @@ -42,9 +42,14 @@ export async function stringifyIO(value: any): Promise { return { data: value, dataType: "text/plain" }; } - const { stringify } = await loadSuperJSON(); + try { + const { stringify } = await loadSuperJSON(); + const data = stringify(value); - return { data: stringify(value), dataType: "application/super+json" }; + return { data, dataType: "application/super+json" }; + } catch { + return { dataType: "application/json" }; + } } export async function conditionallyExportPacket( @@ -193,9 +198,9 @@ export async function createPacketAttributes( packet: IOPacket, dataKey: string, dataTypeKey: string -): Promise { +): Promise { if (!packet.data) { - return {}; + return; } switch (packet.dataType) { @@ -207,13 +212,22 @@ export async function createPacketAttributes( case "application/super+json": const { parse } = await loadSuperJSON(); - const parsed = parse(packet.data) as any; - const jsonified = JSON.parse(JSON.stringify(parsed, safeReplacer)); + if (typeof packet.data === "undefined" || packet.data === null) { + return; + } + + try { + const parsed = parse(packet.data) as any; + const jsonified = JSON.parse(JSON.stringify(parsed, safeReplacer)); + + return { + ...flattenAttributes(jsonified, dataKey), + [dataTypeKey]: "application/json", + }; + } catch { + return; + } - return { - ...flattenAttributes(jsonified, dataKey), - [dataTypeKey]: "application/json", - }; case "application/store": return { [dataKey]: packet.data, @@ -221,15 +235,15 @@ export async function createPacketAttributes( }; case "text/plain": return { - [SemanticInternalAttributes.OUTPUT]: packet.data, - [SemanticInternalAttributes.OUTPUT_TYPE]: packet.dataType, + [dataKey]: packet.data, + [dataTypeKey]: packet.dataType, }; default: - return {}; + return; } } -export async function createPackageAttributesAsJson( +export async function createPacketAttributesAsJson( data: any, dataType: string ): Promise { @@ -250,7 +264,7 @@ export async function createPackageAttributesAsJson( const { deserialize } = await loadSuperJSON(); const deserialized = deserialize(data) as any; - const jsonify = JSON.parse(JSON.stringify(deserialized, safeReplacer)); + const jsonify = safeJsonParse(JSON.stringify(deserialized, safeReplacer)); return imposeAttributeLimits(flattenAttributes(jsonify, undefined)); case "application/store": @@ -326,3 +340,11 @@ function getPacketExtension(outputType: string): string { async function loadSuperJSON(): Promise { return await import("superjson"); } + +function safeJsonParse(value: string): any { + try { + return JSON.parse(value); + } catch { + return; + } +} diff --git a/packages/core/src/v3/workers/taskExecutor.ts b/packages/core/src/v3/workers/taskExecutor.ts index 2e5d44420..fe709caa9 100644 --- a/packages/core/src/v3/workers/taskExecutor.ts +++ b/packages/core/src/v3/workers/taskExecutor.ts @@ -104,14 +104,16 @@ export class TaskExecutor { this._tracer ); - span.setAttributes( - await createPacketAttributes( - finalOutput, - SemanticInternalAttributes.OUTPUT, - SemanticInternalAttributes.OUTPUT_TYPE - ) + const attributes = await createPacketAttributes( + finalOutput, + SemanticInternalAttributes.OUTPUT, + SemanticInternalAttributes.OUTPUT_TYPE ); + if (attributes) { + span.setAttributes(attributes); + } + return { ok: true, id: execution.attempt.id, diff --git a/references/v3-catalog/src/trigger/superjson.ts b/references/v3-catalog/src/trigger/superjson.ts index 6e75e8a69..2059986d2 100644 --- a/references/v3-catalog/src/trigger/superjson.ts +++ b/references/v3-catalog/src/trigger/superjson.ts @@ -17,9 +17,7 @@ export const superParentTask = task({ logger.log(`typeof result.error = ${typeof result.error}`); logger.log(`typeof result.url = ${typeof result.url}`); - return { - result, - }; + return "## super-parent-task completed"; }, }); @@ -120,6 +118,35 @@ export const superHugeOutputTask = task({ }, }); +export const superStringTask = task({ + id: "super-string-parent-task", + run: async () => { + const result = await superStringChildTask.triggerAndWait({ + payload: { + foo: "bar", + }, + }); + + return result; + }, +}); + +export const superStringChildTask = task({ + id: "super-string-child-task", + run: async () => { + return "## super-string-child-task completed"; + }, +}); + +export const superBadOutputTask = task({ + id: "super-bad-output-task", + run: async () => { + // Returning something that cannot be serialized + + return () => {}; + }, +}); + function createLargeObject(size: number, length: number) { return Array.from({ length }, (_, i) => [i.toString(), i.toString().padStart(size, "0")]).reduce( (acc, [key, value]) => {