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]) => {