Simplify build process at the expense of emitting all declaration files

This commit is contained in:
Tat Dat Duong
2025-05-03 00:08:47 +02:00
parent 4f59bdf49a
commit 703fed2f69
8 changed files with 132 additions and 124 deletions
+7 -3
View File
@@ -12,17 +12,21 @@
],
"exports": {
".": {
"types": "./dist/cli/spawn.d.ts",
"types": "./dist/cli/spawn.d.mts",
"default": "./dist/cli/spawn.mjs"
},
"./auth": {
"types": "./dist/auth/index.d.ts",
"types": "./dist/auth/index.d.mts",
"default": "./dist/auth/index.mjs"
},
"./semver": {
"types": "./dist/semver/index.d.ts",
"types": "./dist/semver/index.d.mts",
"default": "./dist/semver/index.mjs"
},
"./schema": {
"types": "./dist/graph/parser/index.d.mts",
"default": "./dist/graph/parser/index.mjs"
},
"./package.json": "./package.json"
},
"repository": {
-6
View File
@@ -4,12 +4,6 @@ import { $ } from "./utils.mjs";
await $`rm -rf dist`;
await $`pnpm tsc --outDir dist`;
await Promise.all([
$`pnpm tsc --module nodenext --outDir dist/src/cli -d src/cli/spawn.mts`,
$`pnpm tsc --module nodenext --outDir dist/src/auth -d src/auth/index.mts`,
$`pnpm tsc --module nodenext --outDir dist/src/semver -d src/semver/index.mts`,
]);
await $`cp src/graph/parser/schema/types.template.mts dist/src/graph/parser/schema`;
await $`rm -rf dist/src/graph/parser/schema/types.template.mjs`;
+8 -5
View File
@@ -7,9 +7,9 @@ import { z } from "zod";
import {
getAssistantId,
getGraph,
getGraphSchema,
getRuntimeGraphSchema,
getCachedStaticGraphSchema,
} from "../graph/load.mjs";
import { getRuntimeGraphSchema } from "../graph/parser/index.mjs";
import { Assistants } from "../storage/ops.mjs";
import * as schemas from "../schemas.mjs";
@@ -146,7 +146,7 @@ api.get(
const runtimeSchema = await getRuntimeGraphSchema(graph);
if (runtimeSchema) return runtimeSchema;
const graphSchema = await getGraphSchema(assistant.graph_id);
const graphSchema = await getCachedStaticGraphSchema(assistant.graph_id);
const rootGraphId = Object.keys(graphSchema).find(
(i) => !i.includes("|"),
);
@@ -191,13 +191,16 @@ api.get(
: // @ts-expect-error older versions of langgraph don't have getSubgraphsAsync
graph.getSubgraphs.bind(graph);
let graphSchemaPromise: ReturnType<typeof getGraphSchema> | undefined;
let graphSchemaPromise:
| ReturnType<typeof getCachedStaticGraphSchema>
| undefined;
for await (const [ns, subgraph] of subgraphsGenerator(namespace, recurse)) {
const schema = await (async () => {
const runtimeSchema = await getRuntimeGraphSchema(subgraph);
if (runtimeSchema) return runtimeSchema;
graphSchemaPromise ??= getGraphSchema(assistant.graph_id);
graphSchemaPromise ??= getCachedStaticGraphSchema(assistant.graph_id);
const graphSchema = await graphSchemaPromise;
const rootGraphId = Object.keys(graphSchema).find(
+21 -39
View File
@@ -2,22 +2,16 @@ import { z } from "zod";
import * as uuid from "uuid";
import { Assistants } from "../storage/ops.mjs";
import type { JSONSchema7 } from "json-schema";
import type {
Pregel,
BaseCheckpointSaver,
BaseStore,
CompiledGraph,
LangGraphRunnableConfig,
} from "@langchain/langgraph";
import { HTTPException } from "hono/http-exception";
import {
type CompiledGraphFactory,
type GraphSchema,
type GraphSpec,
resolveGraph,
runGraphSchemaWorker,
} from "./load.utils.mjs";
import { type CompiledGraphFactory, resolveGraph } from "./load.utils.mjs";
import type { GraphSchema, GraphSpec } from "./parser/index.mjs";
import { getStaticGraphSchema } from "./parser/index.mjs";
import { checkpointer } from "../storage/checkpoint.mjs";
import { store } from "../storage/store.mjs";
import { logger } from "../logging.mjs";
@@ -107,42 +101,30 @@ export async function getGraph(
return compiled;
}
export async function getRuntimeGraphSchema(
graph: Pregel<any, any, any, any, any>,
): Promise<GraphSchema | undefined> {
try {
const {
getInputTypeSchema,
getOutputTypeSchema,
getStateTypeSchema,
getConfigTypeSchema,
} = await import("@langchain/langgraph/zod/schema");
const result = {
input: getInputTypeSchema(graph) as JSONSchema7 | undefined,
output: getOutputTypeSchema(graph) as JSONSchema7 | undefined,
state: getStateTypeSchema(graph) as JSONSchema7 | undefined,
config: getConfigTypeSchema(graph) as JSONSchema7 | undefined,
};
if (Object.values(result).every((i) => i == null)) return undefined;
return result;
} catch {
// ignore
}
return undefined;
}
export async function getGraphSchema(graphId: string) {
export async function getCachedStaticGraphSchema(graphId: string) {
if (!GRAPH_SPEC[graphId])
throw new HTTPException(404, {
message: `Spec for "${graphId}" not found`,
});
if (!GRAPH_SCHEMA[graphId] || true) {
if (!GRAPH_SCHEMA[graphId]) {
let timeoutMs = 30_000;
try {
GRAPH_SCHEMA[graphId] = await runGraphSchemaWorker(GRAPH_SPEC[graphId]);
const envTimeout = Number.parseInt(
process.env.LANGGRAPH_SCHEMA_RESOLVE_TIMEOUT_MS ?? "0",
10,
);
if (!Number.isNaN(envTimeout) && envTimeout > 0) {
timeoutMs = envTimeout;
}
} catch {
// ignore
}
try {
GRAPH_SCHEMA[graphId] = await getStaticGraphSchema(GRAPH_SPEC[graphId], {
timeoutMs,
});
} catch (error) {
throw new Error(`Failed to extract schema for "${graphId}"`, {
cause: error,
+4 -67
View File
@@ -1,28 +1,14 @@
import { Worker } from "node:worker_threads";
import * as fs from "node:fs/promises";
import type { CompiledGraph, Graph } from "@langchain/langgraph";
import * as path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import * as uuid from "uuid";
import type { JSONSchema7 } from "json-schema";
import * as path from "node:path";
import * as fs from "node:fs/promises";
import { pathToFileURL } from "node:url";
export const GRAPHS: Record<string, CompiledGraph<string>> = {};
export const NAMESPACE_GRAPH = uuid.parse(
"6ba7b821-9dad-11d1-80b4-00c04fd430c8",
);
export interface GraphSchema {
state: JSONSchema7 | undefined;
input: JSONSchema7 | undefined;
output: JSONSchema7 | undefined;
config: JSONSchema7 | undefined;
}
export interface GraphSpec {
sourceFile: string;
exportSymbol: string;
}
export type CompiledGraphFactory<T extends string> = (config: {
configurable?: Record<string, unknown>;
}) => Promise<CompiledGraph<T>>;
@@ -51,7 +37,7 @@ export async function resolveGraph(
// validate file exists
await fs.stat(sourceFile);
if (options?.onlyFilePresence) {
return { sourceFile: userFile, exportSymbol, resolved: undefined };
return { sourceFile, exportSymbol, resolved: undefined };
}
type GraphLike = CompiledGraph<string> | Graph<string>;
@@ -95,52 +81,3 @@ export async function resolveGraph(
return { sourceFile, exportSymbol, resolved };
}
export async function runGraphSchemaWorker(
spec: GraphSpec,
options?: { mainThread?: boolean },
) {
let SCHEMA_RESOLVE_TIMEOUT_MS = 30_000;
try {
const envTimeout = Number.parseInt(
process.env.LANGGRAPH_SCHEMA_RESOLVE_TIMEOUT_MS ?? "0",
10,
);
if (!Number.isNaN(envTimeout) && envTimeout > 0) {
SCHEMA_RESOLVE_TIMEOUT_MS = envTimeout;
}
} catch {
// ignore
}
if (options?.mainThread) {
const { SubgraphExtractor } = await import("./parser/parser.mjs");
return SubgraphExtractor.extractSchemas(
spec.sourceFile,
spec.exportSymbol,
{ strict: false },
);
}
return await new Promise<Record<string, GraphSchema>>((resolve, reject) => {
const worker = new Worker(
fileURLToPath(new URL("./parser/parser.worker.mjs", import.meta.url)),
{ argv: process.argv.slice(-1) },
);
// Set a timeout to reject if the worker takes too long
const timeoutId = setTimeout(() => {
worker.terminate();
reject(new Error("Schema extract worker timed out"));
}, SCHEMA_RESOLVE_TIMEOUT_MS);
worker.on("message", (result) => {
worker.terminate();
clearTimeout(timeoutId);
resolve(result);
});
worker.on("error", reject);
worker.postMessage(spec);
});
}
@@ -0,0 +1,79 @@
import { fileURLToPath } from "node:url";
import { Worker } from "node:worker_threads";
import type { JSONSchema7 } from "json-schema";
import type { Pregel } from "@langchain/langgraph";
export interface GraphSchema {
state: JSONSchema7 | undefined;
input: JSONSchema7 | undefined;
output: JSONSchema7 | undefined;
config: JSONSchema7 | undefined;
}
export interface GraphSpec {
sourceFile: string;
exportSymbol: string;
}
export async function getStaticGraphSchema(
spec: GraphSpec,
options?: { mainThread?: boolean; timeoutMs?: number },
): Promise<Record<string, GraphSchema>> {
if (options?.mainThread) {
const { SubgraphExtractor } = await import("./parser.mjs");
return SubgraphExtractor.extractSchemas(
spec.sourceFile,
spec.exportSymbol,
{ strict: false },
);
}
return await new Promise<Record<string, GraphSchema>>((resolve, reject) => {
const worker = new Worker(
fileURLToPath(new URL("./parser/parser.worker.mjs", import.meta.url)),
{ argv: process.argv.slice(-1) },
);
// Set a timeout to reject if the worker takes too long
const timeoutId = setTimeout(() => {
worker.terminate();
reject(new Error("Schema extract worker timed out"));
}, options?.timeoutMs ?? 30000);
worker.on("message", (result) => {
worker.terminate();
clearTimeout(timeoutId);
resolve(result);
});
worker.on("error", reject);
worker.postMessage(spec);
});
}
export async function getRuntimeGraphSchema(
graph: Pregel<any, any, any, any, any>,
): Promise<GraphSchema | undefined> {
try {
const {
getInputTypeSchema,
getOutputTypeSchema,
getUpdateTypeSchema,
getConfigTypeSchema,
} = await import("@langchain/langgraph/zod/schema");
const result = {
state: getUpdateTypeSchema(graph),
input: getInputTypeSchema(graph),
output: getOutputTypeSchema(graph),
config: getConfigTypeSchema(graph),
} as GraphSchema;
if (Object.values(result).every((i) => i == null)) return undefined;
return result;
} catch {
// ignore
}
return undefined;
}
+12 -4
View File
@@ -4,6 +4,14 @@ import * as path from "node:path";
import dedent from "dedent";
import { buildGenerator } from "./schema/types.mjs";
import { fileURLToPath } from "node:url";
import type { JSONSchema7 } from "json-schema";
interface GraphSchema {
state: JSONSchema7 | undefined;
input: JSONSchema7 | undefined;
output: JSONSchema7 | undefined;
config: JSONSchema7 | undefined;
}
const __dirname = fileURLToPath(new URL(".", import.meta.url));
@@ -249,7 +257,7 @@ export class SubgraphExtractor {
),
].join("\n\n");
const inferFilePath = "__langraph__infer.mts";
const inferFilePath = "__langgraph__infer.mts";
const inferContents = [
...typeExports.map(
({ typeName }) =>
@@ -351,7 +359,7 @@ export class SubgraphExtractor {
},
name: string,
options?: { strict?: boolean },
) {
): Record<string, GraphSchema> {
const dirname =
typeof target === "string" ? path.dirname(target) : __dirname;
@@ -441,7 +449,7 @@ export class SubgraphExtractor {
}
const extract = ts.createProgram({
rootNames: [path.resolve(dirname, "./__langraph__infer.mts")],
rootNames: [path.resolve(dirname, "./__langgraph__infer.mts")],
options: compilerOptions,
host,
});
@@ -463,9 +471,9 @@ export class SubgraphExtractor {
exports.map(({ typeName, graphName }) => [
graphName,
{
state: trySymbol(schemaGenerator, `${typeName}__update`),
input: trySymbol(schemaGenerator, `${typeName}__input`),
output: trySymbol(schemaGenerator, `${typeName}__output`),
state: trySymbol(schemaGenerator, `${typeName}__update`),
config: trySymbol(schemaGenerator, `${typeName}__config`),
},
]),
+1
View File
@@ -5,6 +5,7 @@
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"module": "NodeNext",
"allowJs": true,
"jsx": "react-jsx",