From a4deab3e4553111df4b10832a7298394e998b023 Mon Sep 17 00:00:00 2001 From: zhuojg Date: Tue, 11 Nov 2025 19:04:42 +0800 Subject: [PATCH 1/2] Add cache for world (#288) Signed-off-by: zhuojg --- .../src/api/workflow-server-actions.ts | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/web-shared/src/api/workflow-server-actions.ts b/packages/web-shared/src/api/workflow-server-actions.ts index 0d0ba9cb3..b8b3774d0 100644 --- a/packages/web-shared/src/api/workflow-server-actions.ts +++ b/packages/web-shared/src/api/workflow-server-actions.ts @@ -8,6 +8,7 @@ import type { Step, WorkflowRun, WorkflowRunStatus, + World, } from '@workflow/world'; export type EnvMap = Record; @@ -38,14 +39,37 @@ export type ServerActionResult = | { success: true; data: T } | { success: false; error: ServerActionError }; +/** + * Cache for World instances keyed by envMap + */ +const worldCache = new Map(); + function getWorldFromEnv(envMap: EnvMap) { + // Generate stable cache key from envMap + const sortedKeys = Object.keys(envMap).sort(); + const sortedEntries = sortedKeys.map((key) => [key, envMap[key]]); + const cacheKey = JSON.stringify(Object.fromEntries(sortedEntries)); + + // Check if we have a cached World for this configuration + const cachedWorld = worldCache.get(cacheKey); + if (cachedWorld) { + return cachedWorld; + } + + // No cached World found, create a new one for (const [key, value] of Object.entries(envMap)) { if (value === undefined || value === null || value === '') { continue; } process.env[key] = value; } - return createWorld(); + + const world = createWorld(); + + // Cache the newly created World + worldCache.set(cacheKey, world); + + return world; } /** From 61eb3f9f375ab4d03d1092c308e7b593c6732d80 Mon Sep 17 00:00:00 2001 From: zhuojg Date: Wed, 12 Nov 2025 16:05:36 +0800 Subject: [PATCH 2/2] add notes on world cache assumptions and update README Signed-off-by: zhuojg --- packages/web-shared/README.md | 2 ++ packages/web-shared/src/api/workflow-server-actions.ts | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/packages/web-shared/README.md b/packages/web-shared/README.md index 8a071074d..a764fe26d 100644 --- a/packages/web-shared/README.md +++ b/packages/web-shared/README.md @@ -61,6 +61,8 @@ See `npx workflow inspect --help` for more information. If you're deploying this as part of your Vercel NextJS app, setting `WORKFLOW_TARGET_WORLD` to `vercel` is enough to infer your other project details from the Vercel environment variables. +**Important:** When using the UI to inspect different worlds, all relevant environment variables should be passed via the `EnvMap` parameter to the hooks and components, rather than setting them directly on your Next.js instance via `process.env`. The server-side World caching is based on the `EnvMap` configuration, so setting environment variables directly on `process.env` may cause cached World instances to operate with incorrect environment configuration. + ## Styling In order for tailwind classes to be picked up correctly, you might need to configure your NextJS app diff --git a/packages/web-shared/src/api/workflow-server-actions.ts b/packages/web-shared/src/api/workflow-server-actions.ts index b8b3774d0..fc4ca1d18 100644 --- a/packages/web-shared/src/api/workflow-server-actions.ts +++ b/packages/web-shared/src/api/workflow-server-actions.ts @@ -41,6 +41,12 @@ export type ServerActionResult = /** * Cache for World instances keyed by envMap + * + * IMPORTANT: This cache works under the assumption that if the UI is used to look at + * different worlds, the user should pass all relevant variables via EnvMap, instead of + * setting them directly on their Next.js instance. If environment variables are set + * directly on process.env, the cached World may operate with incorrect environment + * configuration. */ const worldCache = new Map(); @@ -51,6 +57,8 @@ function getWorldFromEnv(envMap: EnvMap) { const cacheKey = JSON.stringify(Object.fromEntries(sortedEntries)); // Check if we have a cached World for this configuration + // Note: This returns the cached World without re-setting process.env. + // See comment above worldCache for important usage assumptions. const cachedWorld = worldCache.get(cacheKey); if (cachedWorld) { return cachedWorld;