Skip to content

Commit f93eae3

Browse files
authored
v3: superjson dynamic import and deploy fixes (#982)
* v3: Dynamically import superjson and fix some bundling issues * Added changeset * Better error handling in the registry proxy and catch uncaught exceptions and unhandled promise rejections instead of crashing the server * Await the prettyPrintPackage
1 parent a2365e4 commit f93eae3

File tree

14 files changed

+125
-150
lines changed

14 files changed

+125
-150
lines changed

.changeset/tall-bees-wave.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
"trigger.dev": patch
4+
"@trigger.dev/core": patch
5+
---
6+
7+
Dynamically import superjson and fix some bundling issues

apps/webapp/app/entry.server.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import {
1414
OperatingSystemContextProvider,
1515
OperatingSystemPlatform,
1616
} from "./components/primitives/OperatingSystemProvider";
17-
import { env } from "./env.server";
1817
import { getSharedSqsEventConsumer } from "./services/events/sqsEventConsumer";
1918
import { singleton } from "./utils/singleton";
19+
import { logger } from "./services/logger.server";
2020

2121
const ABORT_DELAY = 30000;
2222

@@ -178,7 +178,15 @@ function logError(error: unknown, request?: Request) {
178178

179179
const sqsEventConsumer = singleton("sqsEventConsumer", getSharedSqsEventConsumer);
180180

181-
export { wss } from "./v3/handleWebsockets.server";
181+
export { apiRateLimiter } from "./services/apiRateLimit.server";
182182
export { socketIo } from "./v3/handleSocketIo.server";
183+
export { wss } from "./v3/handleWebsockets.server";
183184
export { registryProxy } from "./v3/registryProxy.server";
184-
export { apiRateLimiter } from "./services/apiRateLimit.server";
185+
186+
process.on("uncaughtException", (error, origin) => {
187+
logger.error("Uncaught Exception", { error, origin });
188+
});
189+
190+
process.on("unhandledRejection", (reason, promise) => {
191+
logger.error("Unhandled Rejection", { reason });
192+
});

apps/webapp/app/presenters/v3/SpanPresenter.server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ export class SpanPresenter {
4343
span.outputType === "application/store"
4444
? `/resources/packets/${span.environmentId}/${span.output}`
4545
: typeof span.output !== "undefined" && span.output !== null
46-
? prettyPrintPacket(span.output, span.outputType ?? undefined)
46+
? await prettyPrintPacket(span.output, span.outputType ?? undefined)
4747
: undefined;
4848

4949
const payload =
5050
span.payloadType === "application/store"
5151
? `/resources/packets/${span.environmentId}/${span.payload}`
5252
: typeof span.payload !== "undefined" && span.payload !== null
53-
? prettyPrintPacket(span.payload, span.payloadType ?? undefined)
53+
? await prettyPrintPacket(span.payload, span.payloadType ?? undefined)
5454
: undefined;
5555

5656
return {

apps/webapp/app/v3/eventRepository.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ export class EventRepository {
188188
const event = events[0];
189189

190190
const output = options?.attributes.output
191-
? createPackageAttributesAsJson(
191+
? await createPackageAttributesAsJson(
192192
options?.attributes.output,
193193
options?.attributes.outputType ?? "application/json"
194194
)

apps/webapp/app/v3/registryProxy.server.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,18 @@ export class RegistryProxy {
259259
proxyRes.pipe(response, { end: true });
260260
});
261261

262+
request.on("close", () => {
263+
logger.debug("Client closed the connection");
264+
proxyReq.destroy();
265+
cleanupTempFile();
266+
});
267+
268+
request.on("abort", () => {
269+
logger.debug("Client aborted the connection");
270+
proxyReq.destroy(); // Abort the proxied request
271+
cleanupTempFile(); // Clean up the temporary file if necessary
272+
});
273+
262274
if (tempFilePath) {
263275
const readStream = createReadStream(tempFilePath);
264276

@@ -427,14 +439,22 @@ function initializeProxy() {
427439
});
428440
}
429441

430-
async function streamRequestBodyToTempFile(request: IncomingMessage): Promise<string> {
431-
const tempDir = await mkdtemp(`${tmpdir()}/`);
432-
const tempFilePath = `${tempDir}/requestBody.tmp`;
433-
const writeStream = createWriteStream(tempFilePath);
442+
async function streamRequestBodyToTempFile(request: IncomingMessage): Promise<string | undefined> {
443+
try {
444+
const tempDir = await mkdtemp(`${tmpdir()}/`);
445+
const tempFilePath = `${tempDir}/requestBody.tmp`;
446+
const writeStream = createWriteStream(tempFilePath);
434447

435-
await pipeline(request, writeStream);
448+
await pipeline(request, writeStream);
436449

437-
return tempFilePath;
450+
return tempFilePath;
451+
} catch (error) {
452+
logger.error("Failed to stream request body to temp file", {
453+
error: error instanceof Error ? error.message : error,
454+
});
455+
456+
return;
457+
}
438458
}
439459

440460
type DockerImageParts = {

packages/cli-v3/src/commands/deploy.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,14 @@ async function compileProject(
889889
TRIGGER_API_URL: `"${config.triggerUrl}"`,
890890
__PROJECT_CONFIG__: JSON.stringify(config),
891891
},
892-
plugins: [bundleDependenciesPlugin(config), workerSetupImportConfigPlugin(configPath)],
892+
plugins: [
893+
bundleDependenciesPlugin(
894+
"workerFacade",
895+
config.dependenciesToBundle,
896+
config.tsconfigPath
897+
),
898+
workerSetupImportConfigPlugin(configPath),
899+
],
893900
});
894901

895902
if (result.errors.length > 0) {
@@ -927,15 +934,22 @@ async function compileProject(
927934
write: false,
928935
minify: false,
929936
sourcemap: false,
930-
packages: "external", // https://esbuild.github.io/api/#packages
931937
logLevel: "error",
932938
platform: "node",
939+
packages: "external",
933940
format: "cjs", // This is needed to support opentelemetry instrumentation that uses module patching
934941
target: ["node18", "es2020"],
935942
outdir: "out",
936943
define: {
937944
__PROJECT_CONFIG__: JSON.stringify(config),
938945
},
946+
plugins: [
947+
bundleDependenciesPlugin(
948+
"entryPoint.ts",
949+
config.dependenciesToBundle,
950+
config.tsconfigPath
951+
),
952+
],
939953
});
940954

941955
if (entryPointResult.errors.length > 0) {
@@ -1003,6 +1017,11 @@ async function compileProject(
10031017
// Save the entryPoint outputFile to /tmp/dir/index.js
10041018
await writeFile(join(tempDir, "index.js"), entryPointOutputFile.text);
10051019

1020+
logger.debug("Getting the imports for the worker and entryPoint builds", {
1021+
workerImports: metaOutput.imports,
1022+
entryPointImports: entryPointMetaOutput.imports,
1023+
});
1024+
10061025
// Get all the required dependencies from the metaOutputs and save them to /tmp/dir/package.json
10071026
const allImports = [...metaOutput.imports, ...entryPointMetaOutput.imports];
10081027

@@ -1246,7 +1265,7 @@ async function gatherRequiredDependencies(
12461265
const dependencies: Record<string, string> = {};
12471266

12481267
for (const file of imports) {
1249-
if (file.kind !== "require-call" || !file.external) {
1268+
if ((file.kind !== "require-call" && file.kind !== "dynamic-import") || !file.external) {
12501269
continue;
12511270
}
12521271

packages/cli-v3/src/commands/dev.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,11 @@ function useDev({
362362
__PROJECT_CONFIG__: JSON.stringify(config),
363363
},
364364
plugins: [
365-
bundleDependenciesPlugin(config),
365+
bundleDependenciesPlugin(
366+
"workerFacade",
367+
(config.dependenciesToBundle ?? []).concat([/^@trigger.dev/]),
368+
config.tsconfigPath
369+
),
366370
workerSetupImportConfigPlugin(configPath),
367371
{
368372
name: "trigger.dev v3",
@@ -631,8 +635,12 @@ async function gatherRequiredDependencies(
631635
) {
632636
const dependencies: Record<string, string> = {};
633637

638+
logger.debug("Gathering required dependencies from imports", {
639+
imports: outputMeta.imports,
640+
});
641+
634642
for (const file of outputMeta.imports) {
635-
if (file.kind !== "require-call" || !file.external) {
643+
if ((file.kind !== "require-call" && file.kind !== "dynamic-import") || !file.external) {
636644
continue;
637645
}
638646

packages/cli-v3/src/utilities/build.ts

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { ResolvedConfig } from "@trigger.dev/core/v3";
21
import type * as esbuild from "esbuild";
32
import type { Plugin } from "esbuild";
3+
import { readFileSync } from "node:fs";
44
import { extname, isAbsolute } from "node:path";
55
import tsConfigPaths from "tsconfig-paths";
66
import { logger } from "./logger";
7-
import { readFileSync } from "node:fs";
87

98
export function workerSetupImportConfigPlugin(configPath?: string): Plugin {
109
return {
@@ -37,8 +36,12 @@ export function workerSetupImportConfigPlugin(configPath?: string): Plugin {
3736
};
3837
}
3938

40-
export function bundleDependenciesPlugin(config: ResolvedConfig): Plugin {
41-
const matchPath = config.tsconfigPath ? createMatchPath(config.tsconfigPath) : undefined;
39+
export function bundleDependenciesPlugin(
40+
buildIdentifier: string,
41+
dependenciesToBundle?: Array<string | RegExp>,
42+
tsconfigPath?: string
43+
): Plugin {
44+
const matchPath = tsconfigPath ? createMatchPath(tsconfigPath) : undefined;
4245

4346
function resolvePath(id: string) {
4447
if (!matchPath) {
@@ -53,33 +56,8 @@ export function bundleDependenciesPlugin(config: ResolvedConfig): Plugin {
5356
build.onResolve({ filter: /.*/ }, (args) => {
5457
const resolvedPath = resolvePath(args.path);
5558

56-
logger.ignore(`Checking if ${args.path} should be bundled or external`, {
57-
...args,
58-
resolvedPath,
59-
});
60-
6159
if (!isBareModuleId(resolvedPath)) {
62-
logger.ignore(`Bundling ${args.path} because its not a bareModuleId`, {
63-
...args,
64-
});
65-
66-
return undefined; // let esbuild bundle it
67-
}
68-
69-
if (args.path.startsWith("@trigger.dev/")) {
70-
logger.ignore(`Bundling ${args.path} because its a trigger.dev package`, {
71-
...args,
72-
});
73-
74-
return undefined; // let esbuild bundle it
75-
}
76-
77-
if (args.path === "superjson" || args.path === "copy-anything" || args.path === "is-what") {
78-
logger.debug(`Bundling ${args.path} because its superjson/copy-anything/is-what`, {
79-
...args,
80-
});
81-
82-
return undefined; // let esbuild bundle it
60+
return undefined; // let esbuild handle it
8361
}
8462

8563
// Skip assets that are treated as files (.css, .svg, .png, etc.).
@@ -97,13 +75,13 @@ export function bundleDependenciesPlugin(config: ResolvedConfig): Plugin {
9775
return undefined;
9876
}
9977

100-
for (let pattern of config.dependenciesToBundle ?? []) {
78+
for (let pattern of dependenciesToBundle ?? []) {
10179
if (typeof pattern === "string" ? args.path === pattern : pattern.test(args.path)) {
10280
return undefined; // let esbuild bundle it
10381
}
10482
}
10583

106-
logger.ignore(`Externalizing ${args.path}`, {
84+
logger.ignore(`[${buildIdentifier}] Externalizing ${args.path}`, {
10785
...args,
10886
});
10987

packages/core/src/v3/utils/ioSerialization.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Attributes, Span } from "@opentelemetry/api";
2-
import { deserialize, parse, stringify } from "superjson";
32
import { apiClientManager } from "../apiClient";
43
import { OFFLOAD_IO_PACKET_LENGTH_LIMIT, imposeAttributeLimits } from "../limits";
54
import { SemanticInternalAttributes } from "../semanticInternalAttributes";
@@ -11,7 +10,7 @@ export type IOPacket = {
1110
dataType: string;
1211
};
1312

14-
export function parsePacket(value: IOPacket): any {
13+
export async function parsePacket(value: IOPacket): Promise<any> {
1514
if (!value.data) {
1615
return undefined;
1716
}
@@ -20,6 +19,8 @@ export function parsePacket(value: IOPacket): any {
2019
case "application/json":
2120
return JSON.parse(value.data);
2221
case "application/super+json":
22+
const { parse } = await loadSuperJSON();
23+
2324
return parse(value.data);
2425
case "text/plain":
2526
return value.data;
@@ -32,7 +33,7 @@ export function parsePacket(value: IOPacket): any {
3233
}
3334
}
3435

35-
export function stringifyIO(value: any): IOPacket {
36+
export async function stringifyIO(value: any): Promise<IOPacket> {
3637
if (value === undefined) {
3738
return { dataType: "application/json" };
3839
}
@@ -41,6 +42,8 @@ export function stringifyIO(value: any): IOPacket {
4142
return { data: value, dataType: "text/plain" };
4243
}
4344

45+
const { stringify } = await loadSuperJSON();
46+
4447
return { data: stringify(value), dataType: "application/super+json" };
4548
}
4649

@@ -186,11 +189,11 @@ async function importPacket(packet: IOPacket, span?: Span): Promise<IOPacket> {
186189
return packet;
187190
}
188191

189-
export function createPacketAttributes(
192+
export async function createPacketAttributes(
190193
packet: IOPacket,
191194
dataKey: string,
192195
dataTypeKey: string
193-
): Attributes {
196+
): Promise<Attributes> {
194197
if (!packet.data) {
195198
return {};
196199
}
@@ -202,6 +205,8 @@ export function createPacketAttributes(
202205
[dataTypeKey]: packet.dataType,
203206
};
204207
case "application/super+json":
208+
const { parse } = await loadSuperJSON();
209+
205210
const parsed = parse(packet.data) as any;
206211
const jsonified = JSON.parse(JSON.stringify(parsed, safeReplacer));
207212

@@ -224,7 +229,10 @@ export function createPacketAttributes(
224229
}
225230
}
226231

227-
export function createPackageAttributesAsJson(data: any, dataType: string): Attributes {
232+
export async function createPackageAttributesAsJson(
233+
data: any,
234+
dataType: string
235+
): Promise<Attributes> {
228236
if (
229237
typeof data === "string" ||
230238
typeof data === "number" ||
@@ -239,6 +247,8 @@ export function createPackageAttributesAsJson(data: any, dataType: string): Attr
239247
case "application/json":
240248
return imposeAttributeLimits(flattenAttributes(data, undefined));
241249
case "application/super+json":
250+
const { deserialize } = await loadSuperJSON();
251+
242252
const deserialized = deserialize(data) as any;
243253
const jsonify = JSON.parse(JSON.stringify(deserialized, safeReplacer));
244254

@@ -250,13 +260,15 @@ export function createPackageAttributesAsJson(data: any, dataType: string): Attr
250260
}
251261
}
252262

253-
export function prettyPrintPacket(rawData: any, dataType?: string): string {
263+
export async function prettyPrintPacket(rawData: any, dataType?: string): Promise<string> {
254264
if (rawData === undefined) {
255265
return "";
256266
}
257267

258268
if (dataType === "application/super+json") {
259-
return prettyPrintPacket(deserialize(rawData), "application/json");
269+
const { deserialize } = await loadSuperJSON();
270+
271+
return await prettyPrintPacket(deserialize(rawData), "application/json");
260272
}
261273

262274
if (dataType === "application/json") {
@@ -310,3 +322,7 @@ function getPacketExtension(outputType: string): string {
310322
return "txt";
311323
}
312324
}
325+
326+
async function loadSuperJSON(): Promise<typeof import("superjson")> {
327+
return await import("superjson");
328+
}

0 commit comments

Comments
 (0)