v3: fix string and non-standard outputs (#983)

* Fixed string outputs missing and incorrectly formatted

* Handle non-serializable outputs like a function

* Add changeset
This commit is contained in:
Eric Allam
2024-03-28 19:51:29 +00:00
committed by GitHub
parent bf7827e7b8
commit e3cf456c69
11 changed files with 123 additions and 64 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---
Handle string and non-stringifiable outputs like functions
-8
View File
@@ -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 });
});
@@ -222,19 +222,25 @@ function PacketDisplay({
dataType: string;
title: string;
}) {
if (dataType === "application/store") {
return (
<div className="flex flex-col">
<Paragraph variant="base/bright" className="w-full border-b border-grid-dimmed py-2.5">
{title}
</Paragraph>
<LinkButton LeadingIcon={CloudArrowDownIcon} to={data} variant="tertiary/medium" download>
Download
</LinkButton>
</div>
);
} else {
return <CodeBlock rowTitle={title} code={data} maxLines={20} />;
switch (dataType) {
case "application/store": {
return (
<div className="flex flex-col">
<Paragraph variant="base/bright" className="w-full border-b border-grid-dimmed py-2.5">
{title}
</Paragraph>
<LinkButton LeadingIcon={CloudArrowDownIcon} to={data} variant="tertiary/medium" download>
Download
</LinkButton>
</div>
);
}
case "text/plain": {
return <CodeBlock language="markdown" rowTitle={title} code={data} maxLines={20} />;
}
default: {
return <CodeBlock language="json" rowTitle={title} code={data} maxLines={20} />;
}
}
}
+5 -4
View File
@@ -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,
@@ -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)
+12 -8
View File
@@ -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() {
@@ -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 {
+1 -1
View File
@@ -64,7 +64,7 @@ export {
stringifyIO,
prettyPrintPacket,
createPacketAttributes,
createPackageAttributesAsJson,
createPacketAttributesAsJson,
conditionallyExportPacket,
conditionallyImportPacket,
packetRequiresOffloading,
+37 -15
View File
@@ -42,9 +42,14 @@ export async function stringifyIO(value: any): Promise<IOPacket> {
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<Attributes> {
): Promise<Attributes | undefined> {
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<Attributes> {
@@ -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<typeof import("superjson")> {
return await import("superjson");
}
function safeJsonParse(value: string): any {
try {
return JSON.parse(value);
} catch {
return;
}
}
+8 -6
View File
@@ -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,
+30 -3
View File
@@ -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]) => {