mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
fix(sqlite): speed up read-only snapshot startup (#144448)
* perf(sqlite): bundle read-only snapshot child independently * test(build): retain standalone worker declaration coverage
This commit is contained in:
parent
c89f368142
commit
7b230efeac
5 changed files with 108 additions and 17 deletions
|
|
@ -19,3 +19,16 @@ export function createRuntimeProcessBuildEntries(
|
|||
export const runtimeProcessCoreBuildEntries = createRuntimeProcessBuildEntries(
|
||||
Object.values(runtimeProcessEntrypoints),
|
||||
);
|
||||
|
||||
// Short-lived snapshot children own a separate bundle; parents retain shared runtime identity.
|
||||
export const standaloneRuntimeProcessBuildEntries = createRuntimeProcessBuildEntries([
|
||||
runtimeProcessEntrypoints.sqliteReadOnly,
|
||||
]);
|
||||
|
||||
export function sharedRuntimeProcessBuildEntries(entries: Record<string, string>) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(entries).filter(
|
||||
([name]) => !Object.hasOwn(standaloneRuntimeProcessBuildEntries, name),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ import { createRequire } from "node:module";
|
|||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { createManagedHandoffBuildConfig } from "./managed-handoff-build-config.mts";
|
||||
import {
|
||||
sharedRuntimeProcessBuildEntries,
|
||||
standaloneRuntimeProcessBuildEntries,
|
||||
} from "./runtime-process-core-build-entries.mts";
|
||||
import { createStateSchemaInlinePlugin } from "./state-schema-inline-plugin.mts";
|
||||
import {
|
||||
hashVitestWorkerArtifact,
|
||||
|
|
@ -64,7 +68,7 @@ async function compileVitestWorkerArtifacts(directory: string): Promise<void> {
|
|||
const config: NonNullable<Parameters<typeof build>[0]> = {
|
||||
config: false,
|
||||
cwd: root,
|
||||
entry,
|
||||
entry: sharedRuntimeProcessBuildEntries(entry),
|
||||
outDir,
|
||||
format: "esm",
|
||||
platform: "node",
|
||||
|
|
@ -141,6 +145,11 @@ async function compileVitestWorkerArtifacts(directory: string): Promise<void> {
|
|||
],
|
||||
};
|
||||
await build(config);
|
||||
await build({
|
||||
...config,
|
||||
entry: standaloneRuntimeProcessBuildEntries,
|
||||
outputOptions: { codeSplitting: false },
|
||||
});
|
||||
await build({
|
||||
...createManagedHandoffBuildConfig(),
|
||||
config: false,
|
||||
|
|
|
|||
|
|
@ -608,6 +608,16 @@ describe("tsdown config", () => {
|
|||
const unifiedRuntimeConfig = configs.find(
|
||||
(entry) => entry.name === TSDOWN_UNIFIED_CONFIG_GROUP,
|
||||
);
|
||||
const standaloneRuntimeConfig = configs.find(
|
||||
(entry) =>
|
||||
entry.name === TSDOWN_UNIFIED_CONFIG_GROUP &&
|
||||
entry.dts === false &&
|
||||
hasWorkerEntry(
|
||||
entry,
|
||||
"infra/sqlite-readonly-location.worker",
|
||||
path.resolve("src/infra/sqlite-readonly-location.worker.ts"),
|
||||
),
|
||||
);
|
||||
const unifiedDeclarationConfigs = TSDOWN_UNIFIED_DTS_CONFIG_GROUPS.map((name) =>
|
||||
configs.find((entry) => entry.name === name),
|
||||
);
|
||||
|
|
@ -615,15 +625,28 @@ describe("tsdown config", () => {
|
|||
expect(packageConfigs).not.toHaveLength(0);
|
||||
expect(packageConfigs.map((entry) => entry.dts)).toEqual(packageConfigs.map(() => true));
|
||||
expect(unifiedRuntimeConfig?.dts).toBe(false);
|
||||
expect(standaloneRuntimeConfig?.dts).toBe(false);
|
||||
expect(unifiedDeclarationConfigs.every(Boolean)).toBe(true);
|
||||
const runtimeEntryNames = Object.keys(unifiedRuntimeConfig?.entry ?? {});
|
||||
expect(runtimeEntryNames).toContain("native-hook-relay/entry");
|
||||
const declarationEntryNames = runtimeEntryNames.filter(
|
||||
(name) => name !== "native-hook-relay/entry",
|
||||
);
|
||||
const standaloneEntries = Object.entries(standaloneRuntimeConfig?.entry ?? {});
|
||||
const standaloneNames = new Set(standaloneEntries.map(([name]) => name));
|
||||
const declarationInputs = Object.fromEntries([
|
||||
...Object.entries(unifiedRuntimeConfig?.entry ?? {}).filter(
|
||||
([name]) => name !== "native-hook-relay/entry",
|
||||
),
|
||||
...standaloneEntries,
|
||||
]);
|
||||
for (const declarationConfig of unifiedDeclarationConfigs) {
|
||||
expect(declarationConfig?.dts).toMatchObject({ emitDtsOnly: true });
|
||||
expect(Object.keys(declarationConfig?.entry ?? {})).toEqual(declarationEntryNames);
|
||||
// Splitting executable graphs keeps all declaration aliases and the shared input order.
|
||||
expect(declarationConfig?.entry).toEqual(declarationInputs);
|
||||
expect(
|
||||
Object.keys(declarationConfig?.entry ?? {}).filter((name) => !standaloneNames.has(name)),
|
||||
).toEqual(declarationEntryNames);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Covers bundling rules encoded in the root tsdown config.
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { expectDefined } from "@openclaw/normalization-core/expect";
|
||||
import { bundledPluginRoot } from "openclaw/plugin-sdk/test-fixtures";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../../src/state/openclaw-agent-schema.js";
|
||||
|
|
@ -18,6 +19,10 @@ type TsdownConfigEntry = {
|
|||
entry?: Record<string, string> | string[];
|
||||
inputOptions?: TsdownInputOptions;
|
||||
minify?: unknown;
|
||||
dts?: boolean | { emitDtsOnly?: boolean };
|
||||
define?: Record<string, unknown>;
|
||||
outputOptions?: { codeSplitting?: boolean };
|
||||
outExtensions?: () => { js: string };
|
||||
outDir?: string;
|
||||
plugins?: Array<{ name?: string }>;
|
||||
};
|
||||
|
|
@ -68,6 +73,16 @@ function entrySources(config: TsdownConfigEntry): Record<string, string> {
|
|||
return config.entry;
|
||||
}
|
||||
|
||||
function requireSqliteReadOnlyChildGraph(): TsdownConfigEntry {
|
||||
const graphs = asConfigArray(tsdownConfig).filter(
|
||||
(config) =>
|
||||
!(typeof config.dts === "object" && config.dts.emitDtsOnly) &&
|
||||
entryKeys(config).includes("infra/sqlite-readonly-location.worker"),
|
||||
);
|
||||
expect(graphs).toHaveLength(1);
|
||||
return expectDefined(graphs[0], "read-only snapshot child graph");
|
||||
}
|
||||
|
||||
function bundledEntry(pluginId: string): string {
|
||||
return `${bundledPluginRoot(pluginId)}/index`;
|
||||
}
|
||||
|
|
@ -191,7 +206,10 @@ describe("tsdown config", () => {
|
|||
expect(entrySources(unifiedGraph)["native-hook-relay/entry"]).toBe(
|
||||
"src/cli/native-hook-relay-entry.ts",
|
||||
);
|
||||
expect(inlinePlugins).toHaveLength(3);
|
||||
expect(requireSqliteReadOnlyChildGraph().plugins).toContainEqual(
|
||||
expect.objectContaining({ name: STATE_SCHEMA_INLINE_PLUGIN_NAME }),
|
||||
);
|
||||
expect(inlinePlugins).toHaveLength(4);
|
||||
});
|
||||
|
||||
it("keeps core, plugin runtime, plugin-sdk, bundled root plugins, and bundled hooks in one dist graph", () => {
|
||||
|
|
@ -207,7 +225,7 @@ describe("tsdown config", () => {
|
|||
"agents/compaction-planning.worker",
|
||||
"agents/model-provider-auth.worker",
|
||||
"config/sessions/session-accessor.sqlite-archive.worker",
|
||||
"infra/sqlite-readonly-location.worker",
|
||||
"plugin-sdk/sqlite-runtime",
|
||||
"state/openclaw-database-verify.worker",
|
||||
"plugins/memory-state",
|
||||
"subagent-registry.runtime",
|
||||
|
|
@ -233,6 +251,18 @@ describe("tsdown config", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("emits the read-only snapshot child once without sealing its package loaders", () => {
|
||||
const child = requireSqliteReadOnlyChildGraph();
|
||||
expect(entrySources(child)).toEqual({
|
||||
"infra/sqlite-readonly-location.worker": path.resolve(
|
||||
"src/infra/sqlite-readonly-location.worker.ts",
|
||||
),
|
||||
});
|
||||
expect(child.outputOptions).toEqual({ codeSplitting: false });
|
||||
expect(child.outExtensions?.().js).toBe(".js");
|
||||
expect(child.define?.SEALED_RUNTIME_BUILD).toBeUndefined();
|
||||
});
|
||||
|
||||
it("builds the Docker healthcheck as a stable dist entry", () => {
|
||||
const distGraph = requireUnifiedDistGraph();
|
||||
|
||||
|
|
@ -340,20 +370,22 @@ describe("tsdown config", () => {
|
|||
});
|
||||
|
||||
it("bundles SDK-owned helpers while retaining fs-safe package ownership", () => {
|
||||
const unifiedGraph = requireUnifiedDistGraph();
|
||||
const alwaysBundle = unifiedGraph.deps?.alwaysBundle;
|
||||
for (const graph of [requireUnifiedDistGraph(), requireSqliteReadOnlyChildGraph()]) {
|
||||
const alwaysBundle = graph.deps?.alwaysBundle;
|
||||
const external = graph.inputOptions?.({})?.external;
|
||||
if (typeof alwaysBundle !== "function" || typeof external !== "function") {
|
||||
throw new Error("expected runtime graph dependency predicates");
|
||||
}
|
||||
|
||||
if (typeof alwaysBundle !== "function") {
|
||||
throw new Error("expected unified graph alwaysBundle predicate");
|
||||
expect(alwaysBundle("@openclaw/fs-safe")).toBe(false);
|
||||
expect(alwaysBundle("@openclaw/fs-safe/path")).toBe(false);
|
||||
expect(external("@openclaw/fs-safe/path", undefined, false)).toBe(true);
|
||||
expect(alwaysBundle("openclaw/plugin-sdk/ssrf-runtime-internal")).toBe(true);
|
||||
expect(alwaysBundle("openclaw/plugin-sdk/ssrf-runtime")).toBe(false);
|
||||
expect(alwaysBundle("zod")).toBe(true);
|
||||
expect(alwaysBundle("zod/v4/core")).toBe(true);
|
||||
expect(alwaysBundle("not-a-runtime-dependency")).toBe(false);
|
||||
}
|
||||
|
||||
expect(alwaysBundle("@openclaw/fs-safe")).toBe(false);
|
||||
expect(alwaysBundle("@openclaw/fs-safe/path")).toBe(false);
|
||||
expect(alwaysBundle("openclaw/plugin-sdk/ssrf-runtime-internal")).toBe(true);
|
||||
expect(alwaysBundle("openclaw/plugin-sdk/ssrf-runtime")).toBe(false);
|
||||
expect(alwaysBundle("zod")).toBe(true);
|
||||
expect(alwaysBundle("zod/v4/core")).toBe(true);
|
||||
expect(alwaysBundle("not-a-runtime-dependency")).toBe(false);
|
||||
});
|
||||
|
||||
it("suppresses unresolved imports from extension source", () => {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ import {
|
|||
} from "./scripts/lib/plugin-sdk-entries.mts";
|
||||
import { createRuntimeDependencyOwnershipBuildPlugin } from "./scripts/lib/runtime-dependency-ownership-build-plugin.mts";
|
||||
import { runtimeProcessBuildEntries } from "./scripts/lib/runtime-process-build-entries.mts";
|
||||
import {
|
||||
sharedRuntimeProcessBuildEntries,
|
||||
standaloneRuntimeProcessBuildEntries,
|
||||
} from "./scripts/lib/runtime-process-core-build-entries.mts";
|
||||
import {
|
||||
createStateSchemaInlinePlugin,
|
||||
STATE_SCHEMA_INLINE_PLUGIN_NAME,
|
||||
|
|
@ -813,7 +817,7 @@ const configs: UserConfig[] = [
|
|||
// Build core entrypoints, plugin-sdk subpaths, bundled plugin entrypoints,
|
||||
// and bundled hooks in one graph so runtime singletons are emitted once.
|
||||
entry: {
|
||||
...unifiedDistEntries,
|
||||
...sharedRuntimeProcessBuildEntries(unifiedDistEntries),
|
||||
"native-hook-relay/entry": "src/cli/native-hook-relay-entry.ts",
|
||||
},
|
||||
deps: unifiedDeps,
|
||||
|
|
@ -828,6 +832,16 @@ const configs: UserConfig[] = [
|
|||
},
|
||||
false,
|
||||
),
|
||||
nodeBuildConfig(
|
||||
{
|
||||
name: TSDOWN_UNIFIED_CONFIG_GROUP,
|
||||
entry: standaloneRuntimeProcessBuildEntries,
|
||||
deps: unifiedDeps,
|
||||
outputOptions: { codeSplitting: false },
|
||||
plugins: [createStateSchemaInlinePlugin()],
|
||||
},
|
||||
false,
|
||||
),
|
||||
workerDeployBuildConfig(),
|
||||
{ ...createManagedHandoffBuildConfig(), name: TSDOWN_UNIFIED_CONFIG_GROUP, env },
|
||||
nodeBuildConfig(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue