mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-23 09:42:53 +00:00
* Split the inbound WebSocket validator per message type zod-aot compiled WSOutboundMessageSchema into one 11 MB function. Every inbound frame, heartbeats included, ran through it, so V8 hosts (daemon clients, plugin hosts, the CLI, Electron) eventually Maglev-compiled the whole function: about 800 MB of compiler memory and multi-second stalls, again after each deoptimization. Generate a tag-only envelope validator plus one validator per session message, and dispatch on the message type. Issue paths, defaults and passthrough behave as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Use forward slashes in generated validator imports path.relative() returns backslash paths on Windows, which break the import specifiers written into the generated files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Declare the session export helper as a function Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
168 lines
6.6 KiB
JavaScript
168 lines
6.6 KiB
JavaScript
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { createRequire } from "node:module";
|
|
import { dirname, relative, resolve, sep } from "node:path";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
const envelopeSource = resolve(packageRoot, "src/validation/ws-outbound-envelope.ts");
|
|
const source = resolve(packageRoot, "codegen/generated/ws-outbound.compile.ts");
|
|
const runtimeSchemaMetadata = resolve(
|
|
packageRoot,
|
|
"src/generated/validation/ws-outbound-schema-metadata.ts",
|
|
);
|
|
const output = resolve(packageRoot, "src/generated/validation/ws-outbound.aot.ts");
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const zodAotEntry = require.resolve("zod-aot");
|
|
const zodAotRoot = resolve(dirname(zodAotEntry), "..");
|
|
const emitterPath = resolve(zodAotRoot, "dist/cli/emitter.js");
|
|
const discriminatedUnionPath = resolve(
|
|
zodAotRoot,
|
|
"dist/core/codegen/schemas/discriminated-union.js",
|
|
);
|
|
|
|
async function ensureZodAotRuntimeImportExtensionPatch() {
|
|
const emitter = await readFile(emitterPath, "utf8");
|
|
if (emitter.includes('sourceRelPath.endsWith(".js")')) {
|
|
return;
|
|
}
|
|
|
|
const before = 'let importPath = sourceRelPath.replace(/\\.[cm]?[jt]sx?$/, "");';
|
|
const after =
|
|
'let importPath = sourceRelPath.endsWith(".js")\n ? sourceRelPath\n : sourceRelPath.replace(/\\.[cm]?[jt]sx?$/, "");';
|
|
if (!emitter.includes(before)) {
|
|
throw new Error("zod-aot emitter shape changed; update the runtime import extension patch");
|
|
}
|
|
await writeFile(emitterPath, emitter.replace(before, after));
|
|
}
|
|
|
|
async function ensureZodAotDiscriminatedUnionOutputPatch() {
|
|
let discriminatedUnionEmitter = await readFile(discriminatedUnionPath, "utf8");
|
|
if (
|
|
discriminatedUnionEmitter.includes(
|
|
"const needsOutputPropagation = ir.options.some(hasMutation);",
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const importBefore = 'import { escapeString } from "../context.js";';
|
|
const importAfter = 'import { escapeString, hasMutation } from "../context.js";';
|
|
const outputFlagBefore = "const discKey = escapeString(ir.discriminator);\n let code = emit `";
|
|
const outputFlagAfter =
|
|
"const discKey = escapeString(ir.discriminator);\n const needsOutputPropagation = ir.options.some(hasMutation);\n let code = emit `";
|
|
const propagationBefore =
|
|
" ${g.visit(option, { input: objVar, output: objVar })}\n break;`;";
|
|
const propagationAfter =
|
|
' ${g.visit(option, { input: objVar, output: objVar })}\n ${needsOutputPropagation ? `${g.output}=${objVar};` : ""}\n break;`;';
|
|
|
|
if (
|
|
!discriminatedUnionEmitter.includes(importBefore) ||
|
|
!discriminatedUnionEmitter.includes(outputFlagBefore) ||
|
|
!discriminatedUnionEmitter.includes(propagationBefore)
|
|
) {
|
|
throw new Error("zod-aot discriminated-union emitter shape changed; update the output patch");
|
|
}
|
|
|
|
discriminatedUnionEmitter = discriminatedUnionEmitter
|
|
.replace(importBefore, importAfter)
|
|
.replace(outputFlagBefore, outputFlagAfter)
|
|
.replace(propagationBefore, propagationAfter);
|
|
await writeFile(discriminatedUnionPath, discriminatedUnionEmitter);
|
|
}
|
|
|
|
await Promise.all([
|
|
ensureZodAotRuntimeImportExtensionPatch(),
|
|
ensureZodAotDiscriminatedUnionOutputPatch(),
|
|
]);
|
|
|
|
const [
|
|
{ loadSourceFile },
|
|
{ discoverSchemas },
|
|
{ compileSchemas },
|
|
{ generateCompiledFileContent },
|
|
] = await Promise.all([
|
|
import(pathToFileURL(resolve(zodAotRoot, "dist/loader.js")).href),
|
|
import(pathToFileURL(resolve(zodAotRoot, "dist/discovery.js")).href),
|
|
import(pathToFileURL(resolve(zodAotRoot, "dist/core/pipeline.js")).href),
|
|
import(pathToFileURL(resolve(zodAotRoot, "dist/cli/emitter.js")).href),
|
|
]);
|
|
|
|
// One compiled validator per session message plus a tag-only envelope. zod-aot emits one function
|
|
// per compiled schema, so this keeps every generated function small enough for V8 to optimize cheaply.
|
|
const { sessionOutboundMessageOptions } = await loadSourceFile(envelopeSource, { cacheBust: true });
|
|
const sessionTags = sessionOutboundMessageOptions.map((option) => option.shape.type.value);
|
|
function sessionExport(index) {
|
|
return `SessionOutboundMessage${index}`;
|
|
}
|
|
const envelopeImport = relative(dirname(source), envelopeSource)
|
|
.replace(/\.ts$/, ".js")
|
|
.split(sep)
|
|
.join("/");
|
|
const metadataImport = relative(dirname(runtimeSchemaMetadata), envelopeSource)
|
|
.replace(/\.ts$/, ".js")
|
|
.split(sep)
|
|
.join("/");
|
|
|
|
await mkdir(dirname(source), { recursive: true });
|
|
await writeFile(
|
|
source,
|
|
[
|
|
"// AUTO-GENERATED by generate-validation-aot.mjs — DO NOT EDIT",
|
|
'import { compile } from "zod-aot";',
|
|
`import { WSOutboundEnvelopeSchema, sessionOutboundMessageOptions } from "${envelopeImport}";`,
|
|
"export const WSOutboundEnvelope = compile(WSOutboundEnvelopeSchema);",
|
|
...sessionTags.map(
|
|
(_, index) =>
|
|
`export const ${sessionExport(index)} = compile(sessionOutboundMessageOptions[${index}]);`,
|
|
),
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
await mkdir(dirname(runtimeSchemaMetadata), { recursive: true });
|
|
await writeFile(
|
|
runtimeSchemaMetadata,
|
|
[
|
|
"// AUTO-GENERATED by generate-validation-aot.mjs — DO NOT EDIT",
|
|
`import { WSOutboundEnvelopeSchema, sessionOutboundMessageOptions } from "${metadataImport}";`,
|
|
"export const WSOutboundEnvelope = { schema: WSOutboundEnvelopeSchema };",
|
|
...sessionTags.map(
|
|
(_, index) =>
|
|
`export const ${sessionExport(index)} = { schema: sessionOutboundMessageOptions[${index}] };`,
|
|
),
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
|
|
const schemas = await discoverSchemas(source, { cacheBust: true });
|
|
if (schemas.length !== sessionTags.length + 1) {
|
|
throw new Error(
|
|
`Expected ${sessionTags.length + 1} zod-aot compile() exports in ${relative(packageRoot, source)}, found ${schemas.length}`,
|
|
);
|
|
}
|
|
|
|
const compiled = compileSchemas(schemas, { mode: "inline" });
|
|
const runtimeImportPath = relative(dirname(output), runtimeSchemaMetadata)
|
|
.replace(/\.[cm]?[jt]sx?$/, ".js")
|
|
.split(sep)
|
|
.join("/");
|
|
const content = generateCompiledFileContent(compiled, runtimeImportPath, {
|
|
zodCompat: false,
|
|
}).replace(
|
|
"// AUTO-GENERATED by zod-aot — DO NOT EDIT",
|
|
"// @ts-nocheck\n// AUTO-GENERATED by zod-aot — DO NOT EDIT",
|
|
);
|
|
const sessionMessageValidators = [
|
|
"export const SessionOutboundMessageValidators = {",
|
|
...sessionTags.map((tag, index) => ` ${JSON.stringify(tag)}: ${sessionExport(index)},`),
|
|
"};",
|
|
"",
|
|
].join("\n");
|
|
|
|
await mkdir(dirname(output), { recursive: true });
|
|
await writeFile(output, `${content}\n${sessionMessageValidators}`);
|
|
|
|
console.info(
|
|
`generated ${relative(packageRoot, output)}: envelope + ${sessionTags.length} session message validators`,
|
|
);
|