Skip to content

perf: use Map for invocationsQueue (O(1) lookup/delete) - #541

Merged
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_
Dec 7, 2025
Merged

perf: use Map for invocationsQueue (O(1) lookup/delete)#541
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.

Changes:

  • private.ts: Change invocationsQueue type from QueueItem[] to Map
  • workflow.ts: Initialize with new Map() instead of []
  • global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
  • step.ts: Use Map.set/has/delete instead of push/findIndex/splice
  • hook.ts: Use Map.set/delete instead of push/findIndex/splice
  • sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice

This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

@changeset-bot

changeset-botBot commented Dec 5, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7a903a8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
NameType
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/aiPatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentPreviewCommentsUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorDec 5, 2025 11:43pm
example-nextjs-workflow-webpackErrorErrorDec 5, 2025 11:43pm
example-workflowErrorErrorDec 5, 2025 11:43pm
workbench-astro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-express-workflowErrorErrorDec 5, 2025 11:43pm
workbench-fastify-workflowErrorErrorDec 5, 2025 11:43pm
workbench-hono-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nitro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nuxt-workflowErrorErrorDec 5, 2025 11:43pm
workbench-sveltekit-workflowErrorErrorDec 5, 2025 11:43pm
workbench-vite-workflowErrorErrorDec 5, 2025 11:43pm
workflow-docsErrorErrorDec 5, 2025 11:43pm

@github-actions

github-actionsBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

No test result files found.


⚠️Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: failure
  • Local Prod: failure
  • Local Postgres: failure
  • Windows: failure
  • Community Worlds: success

Check the workflow run for details.

@pranaygpGraphite App

pranaygp commented Dec 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@pranaygp
pranaygpforce-pushed the pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_ branch from 0b839d9 to c9cd63eCompareDecember 5, 2025 04:18
@pranaygp
pranaygpforce-pushed the pranaygp/12-04-add_stress_test_benchmarks_for_large_concurrent_step_counts branch from 4c9a674 to 369a873CompareDecember 5, 2025 04:18
Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.
Changes:
- private.ts: Change invocationsQueue type from QueueItem[] to Map
- workflow.ts: Initialize with new Map() instead of []
- global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
- step.ts: Use Map.set/has/delete instead of push/findIndex/splice
- hook.ts: Use Map.set/delete instead of push/findIndex/splice
- sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice
This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the invocationsQueue data structure by replacing an array-based implementation with a Map<string, QueueItem> for O(1) lookup and delete operations. This eliminates O(n²) complexity from patterns like findIndex + splice that were previously used in workflow orchestration, improving performance in high-concurrency scenarios.

Key changes:

  • Changed invocationsQueue from QueueItem[] to Map<string, QueueItem> keyed by correlationId
  • Replaced array operations (push, findIndex, splice, find) with Map operations (set, has, delete, get)
  • Updated WorkflowSuspension constructor to accept Map and convert to array for backwards compatibility

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/core/src/private.tsUpdated WorkflowOrchestratorContext type definition to use Map<string, QueueItem> instead of QueueItem[]
packages/core/src/workflow.tsChanged initialization from empty array [] to new Map()
packages/core/src/global.tsModified WorkflowSuspension constructor to accept Map, convert to array, and implement single-pass counting
packages/core/src/step.tsReplaced push with set, findIndex+splice with has+delete
packages/core/src/workflow/hook.tsReplaced push with set, findIndex+splice with delete
packages/core/src/workflow/sleep.tsReplaced push with set, find with get, findIndex+splice with delete
packages/core/src/step.test.tsUpdated test assertions to work with Map (using .values(), .size)
packages/core/src/global.test.tsAdded toQueueMap helper and updated all test instantiations
.changeset/slick-cooks-clean.mdAdded changeset documenting the performance improvement

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/core/src/step.ts Outdated

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one minor suggestion. Otherwise :lgtm:

Co-authored-by: Nathan Rajlich <n@n8.io>

@vercelvercelBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Build Fix:

The if statement block has a misplaced closing brace with incorrect indentation that breaks the if-else structure, causing TypeScript to fail parsing the else clause and subsequent code.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..bcab2e1 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -75,18 +75,17 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
ctx.invocationsQueue.delete(correlationId);
- } else {- setTimeout(() => {- reject(- new WorkflowRuntimeError(- `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`- )- );- }, 0);- return EventConsumerResult.Finished;- }- hasSeenStepStarted = true;+ } else {+ setTimeout(() => {+ reject(+ new WorkflowRuntimeError(+ `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`+ )+ );+ }, 0);+ return EventConsumerResult.Finished;
}
+ hasSeenStepStarted = true;
// If this is a subsequent "step_started" event (after a retry), we just consume it
// without trying to remove from the queue again or logging a warning
return EventConsumerResult.Consumed;

Analysis

Indentation error in if-else statement causes TypeScript compilation failure

What fails: TypeScript compiler fails on packages/core/src/step.ts due to incorrect indentation in an if-else statement structure

How to reproduce:

cd packages/core && pnpm build

Result:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Details: The if (!hasSeenStepStarted) block at line 75-89 had an extra closing brace on line 81 with incorrect indentation, causing the else clause on line 82 to be syntactically malformed. This caused the parser to interpret the structure incorrectly, resulting in cascading syntax errors on subsequent lines. The fix removes the misplaced brace and corrects the indentation to match the proper if-else structure.

Fix on Vercel

);
if (invocationsQueueIndex !== -1) {
ctx.invocationsQueue.splice(invocationsQueueIndex, 1);
// O(1) lookup and delete using Map

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a syntax error in the step handler: an orphaned else clause on line 78 appears after a statement rather than an if block. Additionally, the logic is broken - the error handling code (lines 79-86) is now unreachable because the delete() return value is not checked.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..f76f438 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -74,8 +74,8 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
// Step has started - so remove from the invocations queue (only on the first "step_started" event)
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
- ctx.invocationsQueue.delete(correlationId);- } else {+ const wasDeleted = ctx.invocationsQueue.delete(correlationId);+ if (!wasDeleted) {
setTimeout(() => {
reject(
new WorkflowRuntimeError(

Analysis

Syntax error in step handler due to incomplete refactoring of Map.delete()

What fails: The code in packages/core/src/step.ts (lines 75-89) contains a syntax error where an orphaned else clause appears after a statement rather than an if block, preventing the entire module from compiling.

How to reproduce:

cd packages/core
pnpm typecheck

Result: TypeScript compiler reports:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Root cause: A refactoring commit (fe0a6b0) changed from checking if (ctx.invocationsQueue.has(correlationId)) before deleting to using Map.delete() directly, but the refactoring was incomplete - the inner if check was removed while leaving the corresponding else clause orphaned:

// BROKEN - orphaned else with no matching ifif(!hasSeenStepStarted){ctx.invocationsQueue.delete(correlationId);// Statement, not an if}else{// Syntax error: else without if// error handling}}

Fix: Check the return value of Map.delete() (which returns boolean) to determine if the deletion succeeded:

constwasDeleted=ctx.invocationsQueue.delete(correlationId);if(!wasDeleted){// error handling}

This preserves the original logic while maintaining O(1) complexity with Map operations.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pranaygp@TooTallNate
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
perf: use Map for invocationsQueue (O(1) lookup/delete) by pranaygp · Pull Request #541 · vercel/workflow · GitHub
Skip to content

perf: use Map for invocationsQueue (O(1) lookup/delete) - #541

Merged
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_
Dec 7, 2025
Merged

perf: use Map for invocationsQueue (O(1) lookup/delete)#541
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.

Changes:

  • private.ts: Change invocationsQueue type from QueueItem[] to Map
  • workflow.ts: Initialize with new Map() instead of []
  • global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
  • step.ts: Use Map.set/has/delete instead of push/findIndex/splice
  • hook.ts: Use Map.set/delete instead of push/findIndex/splice
  • sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice

This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

@changeset-bot

changeset-botBot commented Dec 5, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7a903a8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
NameType
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/aiPatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentPreviewCommentsUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorDec 5, 2025 11:43pm
example-nextjs-workflow-webpackErrorErrorDec 5, 2025 11:43pm
example-workflowErrorErrorDec 5, 2025 11:43pm
workbench-astro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-express-workflowErrorErrorDec 5, 2025 11:43pm
workbench-fastify-workflowErrorErrorDec 5, 2025 11:43pm
workbench-hono-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nitro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nuxt-workflowErrorErrorDec 5, 2025 11:43pm
workbench-sveltekit-workflowErrorErrorDec 5, 2025 11:43pm
workbench-vite-workflowErrorErrorDec 5, 2025 11:43pm
workflow-docsErrorErrorDec 5, 2025 11:43pm

@github-actions

github-actionsBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

No test result files found.


⚠️Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: failure
  • Local Prod: failure
  • Local Postgres: failure
  • Windows: failure
  • Community Worlds: success

Check the workflow run for details.

@pranaygpGraphite App

pranaygp commented Dec 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@pranaygp
pranaygpforce-pushed the pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_ branch from 0b839d9 to c9cd63eCompareDecember 5, 2025 04:18
@pranaygp
pranaygpforce-pushed the pranaygp/12-04-add_stress_test_benchmarks_for_large_concurrent_step_counts branch from 4c9a674 to 369a873CompareDecember 5, 2025 04:18
Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.
Changes:
- private.ts: Change invocationsQueue type from QueueItem[] to Map
- workflow.ts: Initialize with new Map() instead of []
- global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
- step.ts: Use Map.set/has/delete instead of push/findIndex/splice
- hook.ts: Use Map.set/delete instead of push/findIndex/splice
- sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice
This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the invocationsQueue data structure by replacing an array-based implementation with a Map<string, QueueItem> for O(1) lookup and delete operations. This eliminates O(n²) complexity from patterns like findIndex + splice that were previously used in workflow orchestration, improving performance in high-concurrency scenarios.

Key changes:

  • Changed invocationsQueue from QueueItem[] to Map<string, QueueItem> keyed by correlationId
  • Replaced array operations (push, findIndex, splice, find) with Map operations (set, has, delete, get)
  • Updated WorkflowSuspension constructor to accept Map and convert to array for backwards compatibility

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/core/src/private.tsUpdated WorkflowOrchestratorContext type definition to use Map<string, QueueItem> instead of QueueItem[]
packages/core/src/workflow.tsChanged initialization from empty array [] to new Map()
packages/core/src/global.tsModified WorkflowSuspension constructor to accept Map, convert to array, and implement single-pass counting
packages/core/src/step.tsReplaced push with set, findIndex+splice with has+delete
packages/core/src/workflow/hook.tsReplaced push with set, findIndex+splice with delete
packages/core/src/workflow/sleep.tsReplaced push with set, find with get, findIndex+splice with delete
packages/core/src/step.test.tsUpdated test assertions to work with Map (using .values(), .size)
packages/core/src/global.test.tsAdded toQueueMap helper and updated all test instantiations
.changeset/slick-cooks-clean.mdAdded changeset documenting the performance improvement

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/core/src/step.ts Outdated

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one minor suggestion. Otherwise :lgtm:

Co-authored-by: Nathan Rajlich <n@n8.io>

@vercelvercelBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Build Fix:

The if statement block has a misplaced closing brace with incorrect indentation that breaks the if-else structure, causing TypeScript to fail parsing the else clause and subsequent code.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..bcab2e1 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -75,18 +75,17 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
ctx.invocationsQueue.delete(correlationId);
- } else {- setTimeout(() => {- reject(- new WorkflowRuntimeError(- `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`- )- );- }, 0);- return EventConsumerResult.Finished;- }- hasSeenStepStarted = true;+ } else {+ setTimeout(() => {+ reject(+ new WorkflowRuntimeError(+ `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`+ )+ );+ }, 0);+ return EventConsumerResult.Finished;
}
+ hasSeenStepStarted = true;
// If this is a subsequent "step_started" event (after a retry), we just consume it
// without trying to remove from the queue again or logging a warning
return EventConsumerResult.Consumed;

Analysis

Indentation error in if-else statement causes TypeScript compilation failure

What fails: TypeScript compiler fails on packages/core/src/step.ts due to incorrect indentation in an if-else statement structure

How to reproduce:

cd packages/core && pnpm build

Result:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Details: The if (!hasSeenStepStarted) block at line 75-89 had an extra closing brace on line 81 with incorrect indentation, causing the else clause on line 82 to be syntactically malformed. This caused the parser to interpret the structure incorrectly, resulting in cascading syntax errors on subsequent lines. The fix removes the misplaced brace and corrects the indentation to match the proper if-else structure.

Fix on Vercel

);
if (invocationsQueueIndex !== -1) {
ctx.invocationsQueue.splice(invocationsQueueIndex, 1);
// O(1) lookup and delete using Map

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a syntax error in the step handler: an orphaned else clause on line 78 appears after a statement rather than an if block. Additionally, the logic is broken - the error handling code (lines 79-86) is now unreachable because the delete() return value is not checked.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..f76f438 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -74,8 +74,8 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
// Step has started - so remove from the invocations queue (only on the first "step_started" event)
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
- ctx.invocationsQueue.delete(correlationId);- } else {+ const wasDeleted = ctx.invocationsQueue.delete(correlationId);+ if (!wasDeleted) {
setTimeout(() => {
reject(
new WorkflowRuntimeError(

Analysis

Syntax error in step handler due to incomplete refactoring of Map.delete()

What fails: The code in packages/core/src/step.ts (lines 75-89) contains a syntax error where an orphaned else clause appears after a statement rather than an if block, preventing the entire module from compiling.

How to reproduce:

cd packages/core
pnpm typecheck

Result: TypeScript compiler reports:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Root cause: A refactoring commit (fe0a6b0) changed from checking if (ctx.invocationsQueue.has(correlationId)) before deleting to using Map.delete() directly, but the refactoring was incomplete - the inner if check was removed while leaving the corresponding else clause orphaned:

// BROKEN - orphaned else with no matching ifif(!hasSeenStepStarted){ctx.invocationsQueue.delete(correlationId);// Statement, not an if}else{// Syntax error: else without if// error handling}}

Fix: Check the return value of Map.delete() (which returns boolean) to determine if the deletion succeeded:

constwasDeleted=ctx.invocationsQueue.delete(correlationId);if(!wasDeleted){// error handling}

This preserves the original logic while maintaining O(1) complexity with Map operations.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pranaygp@TooTallNate
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' perf: use Map for invocationsQueue (O(1) lookup/delete) by pranaygp · Pull Request #541 · vercel/workflow · GitHub
Skip to content

perf: use Map for invocationsQueue (O(1) lookup/delete) - #541

Merged
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_
Dec 7, 2025
Merged

perf: use Map for invocationsQueue (O(1) lookup/delete)#541
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.

Changes:

  • private.ts: Change invocationsQueue type from QueueItem[] to Map
  • workflow.ts: Initialize with new Map() instead of []
  • global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
  • step.ts: Use Map.set/has/delete instead of push/findIndex/splice
  • hook.ts: Use Map.set/delete instead of push/findIndex/splice
  • sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice

This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

@changeset-bot

changeset-botBot commented Dec 5, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7a903a8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
NameType
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/aiPatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentPreviewCommentsUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorDec 5, 2025 11:43pm
example-nextjs-workflow-webpackErrorErrorDec 5, 2025 11:43pm
example-workflowErrorErrorDec 5, 2025 11:43pm
workbench-astro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-express-workflowErrorErrorDec 5, 2025 11:43pm
workbench-fastify-workflowErrorErrorDec 5, 2025 11:43pm
workbench-hono-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nitro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nuxt-workflowErrorErrorDec 5, 2025 11:43pm
workbench-sveltekit-workflowErrorErrorDec 5, 2025 11:43pm
workbench-vite-workflowErrorErrorDec 5, 2025 11:43pm
workflow-docsErrorErrorDec 5, 2025 11:43pm

@github-actions

github-actionsBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

No test result files found.


⚠️Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: failure
  • Local Prod: failure
  • Local Postgres: failure
  • Windows: failure
  • Community Worlds: success

Check the workflow run for details.

@pranaygpGraphite App

pranaygp commented Dec 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@pranaygp
pranaygpforce-pushed the pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_ branch from 0b839d9 to c9cd63eCompareDecember 5, 2025 04:18
@pranaygp
pranaygpforce-pushed the pranaygp/12-04-add_stress_test_benchmarks_for_large_concurrent_step_counts branch from 4c9a674 to 369a873CompareDecember 5, 2025 04:18
Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.
Changes:
- private.ts: Change invocationsQueue type from QueueItem[] to Map
- workflow.ts: Initialize with new Map() instead of []
- global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
- step.ts: Use Map.set/has/delete instead of push/findIndex/splice
- hook.ts: Use Map.set/delete instead of push/findIndex/splice
- sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice
This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the invocationsQueue data structure by replacing an array-based implementation with a Map<string, QueueItem> for O(1) lookup and delete operations. This eliminates O(n²) complexity from patterns like findIndex + splice that were previously used in workflow orchestration, improving performance in high-concurrency scenarios.

Key changes:

  • Changed invocationsQueue from QueueItem[] to Map<string, QueueItem> keyed by correlationId
  • Replaced array operations (push, findIndex, splice, find) with Map operations (set, has, delete, get)
  • Updated WorkflowSuspension constructor to accept Map and convert to array for backwards compatibility

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/core/src/private.tsUpdated WorkflowOrchestratorContext type definition to use Map<string, QueueItem> instead of QueueItem[]
packages/core/src/workflow.tsChanged initialization from empty array [] to new Map()
packages/core/src/global.tsModified WorkflowSuspension constructor to accept Map, convert to array, and implement single-pass counting
packages/core/src/step.tsReplaced push with set, findIndex+splice with has+delete
packages/core/src/workflow/hook.tsReplaced push with set, findIndex+splice with delete
packages/core/src/workflow/sleep.tsReplaced push with set, find with get, findIndex+splice with delete
packages/core/src/step.test.tsUpdated test assertions to work with Map (using .values(), .size)
packages/core/src/global.test.tsAdded toQueueMap helper and updated all test instantiations
.changeset/slick-cooks-clean.mdAdded changeset documenting the performance improvement

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/core/src/step.ts Outdated

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one minor suggestion. Otherwise :lgtm:

Co-authored-by: Nathan Rajlich <n@n8.io>

@vercelvercelBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Build Fix:

The if statement block has a misplaced closing brace with incorrect indentation that breaks the if-else structure, causing TypeScript to fail parsing the else clause and subsequent code.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..bcab2e1 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -75,18 +75,17 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
ctx.invocationsQueue.delete(correlationId);
- } else {- setTimeout(() => {- reject(- new WorkflowRuntimeError(- `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`- )- );- }, 0);- return EventConsumerResult.Finished;- }- hasSeenStepStarted = true;+ } else {+ setTimeout(() => {+ reject(+ new WorkflowRuntimeError(+ `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`+ )+ );+ }, 0);+ return EventConsumerResult.Finished;
}
+ hasSeenStepStarted = true;
// If this is a subsequent "step_started" event (after a retry), we just consume it
// without trying to remove from the queue again or logging a warning
return EventConsumerResult.Consumed;

Analysis

Indentation error in if-else statement causes TypeScript compilation failure

What fails: TypeScript compiler fails on packages/core/src/step.ts due to incorrect indentation in an if-else statement structure

How to reproduce:

cd packages/core && pnpm build

Result:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Details: The if (!hasSeenStepStarted) block at line 75-89 had an extra closing brace on line 81 with incorrect indentation, causing the else clause on line 82 to be syntactically malformed. This caused the parser to interpret the structure incorrectly, resulting in cascading syntax errors on subsequent lines. The fix removes the misplaced brace and corrects the indentation to match the proper if-else structure.

Fix on Vercel

);
if (invocationsQueueIndex !== -1) {
ctx.invocationsQueue.splice(invocationsQueueIndex, 1);
// O(1) lookup and delete using Map

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a syntax error in the step handler: an orphaned else clause on line 78 appears after a statement rather than an if block. Additionally, the logic is broken - the error handling code (lines 79-86) is now unreachable because the delete() return value is not checked.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..f76f438 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -74,8 +74,8 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
// Step has started - so remove from the invocations queue (only on the first "step_started" event)
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
- ctx.invocationsQueue.delete(correlationId);- } else {+ const wasDeleted = ctx.invocationsQueue.delete(correlationId);+ if (!wasDeleted) {
setTimeout(() => {
reject(
new WorkflowRuntimeError(

Analysis

Syntax error in step handler due to incomplete refactoring of Map.delete()

What fails: The code in packages/core/src/step.ts (lines 75-89) contains a syntax error where an orphaned else clause appears after a statement rather than an if block, preventing the entire module from compiling.

How to reproduce:

cd packages/core
pnpm typecheck

Result: TypeScript compiler reports:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Root cause: A refactoring commit (fe0a6b0) changed from checking if (ctx.invocationsQueue.has(correlationId)) before deleting to using Map.delete() directly, but the refactoring was incomplete - the inner if check was removed while leaving the corresponding else clause orphaned:

// BROKEN - orphaned else with no matching ifif(!hasSeenStepStarted){ctx.invocationsQueue.delete(correlationId);// Statement, not an if}else{// Syntax error: else without if// error handling}}

Fix: Check the return value of Map.delete() (which returns boolean) to determine if the deletion succeeded:

constwasDeleted=ctx.invocationsQueue.delete(correlationId);if(!wasDeleted){// error handling}

This preserves the original logic while maintaining O(1) complexity with Map operations.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pranaygp@TooTallNate
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' perf: use Map for invocationsQueue (O(1) lookup/delete) by pranaygp · Pull Request #541 · vercel/workflow · GitHub
Skip to content

perf: use Map for invocationsQueue (O(1) lookup/delete) - #541

Merged
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_
Dec 7, 2025
Merged

perf: use Map for invocationsQueue (O(1) lookup/delete)#541
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.

Changes:

  • private.ts: Change invocationsQueue type from QueueItem[] to Map
  • workflow.ts: Initialize with new Map() instead of []
  • global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
  • step.ts: Use Map.set/has/delete instead of push/findIndex/splice
  • hook.ts: Use Map.set/delete instead of push/findIndex/splice
  • sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice

This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

@changeset-bot

changeset-botBot commented Dec 5, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7a903a8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
NameType
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/aiPatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentPreviewCommentsUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorDec 5, 2025 11:43pm
example-nextjs-workflow-webpackErrorErrorDec 5, 2025 11:43pm
example-workflowErrorErrorDec 5, 2025 11:43pm
workbench-astro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-express-workflowErrorErrorDec 5, 2025 11:43pm
workbench-fastify-workflowErrorErrorDec 5, 2025 11:43pm
workbench-hono-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nitro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nuxt-workflowErrorErrorDec 5, 2025 11:43pm
workbench-sveltekit-workflowErrorErrorDec 5, 2025 11:43pm
workbench-vite-workflowErrorErrorDec 5, 2025 11:43pm
workflow-docsErrorErrorDec 5, 2025 11:43pm

@github-actions

github-actionsBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

No test result files found.


⚠️Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: failure
  • Local Prod: failure
  • Local Postgres: failure
  • Windows: failure
  • Community Worlds: success

Check the workflow run for details.

@pranaygpGraphite App

pranaygp commented Dec 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@pranaygp
pranaygpforce-pushed the pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_ branch from 0b839d9 to c9cd63eCompareDecember 5, 2025 04:18
@pranaygp
pranaygpforce-pushed the pranaygp/12-04-add_stress_test_benchmarks_for_large_concurrent_step_counts branch from 4c9a674 to 369a873CompareDecember 5, 2025 04:18
Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.
Changes:
- private.ts: Change invocationsQueue type from QueueItem[] to Map
- workflow.ts: Initialize with new Map() instead of []
- global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
- step.ts: Use Map.set/has/delete instead of push/findIndex/splice
- hook.ts: Use Map.set/delete instead of push/findIndex/splice
- sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice
This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the invocationsQueue data structure by replacing an array-based implementation with a Map<string, QueueItem> for O(1) lookup and delete operations. This eliminates O(n²) complexity from patterns like findIndex + splice that were previously used in workflow orchestration, improving performance in high-concurrency scenarios.

Key changes:

  • Changed invocationsQueue from QueueItem[] to Map<string, QueueItem> keyed by correlationId
  • Replaced array operations (push, findIndex, splice, find) with Map operations (set, has, delete, get)
  • Updated WorkflowSuspension constructor to accept Map and convert to array for backwards compatibility

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/core/src/private.tsUpdated WorkflowOrchestratorContext type definition to use Map<string, QueueItem> instead of QueueItem[]
packages/core/src/workflow.tsChanged initialization from empty array [] to new Map()
packages/core/src/global.tsModified WorkflowSuspension constructor to accept Map, convert to array, and implement single-pass counting
packages/core/src/step.tsReplaced push with set, findIndex+splice with has+delete
packages/core/src/workflow/hook.tsReplaced push with set, findIndex+splice with delete
packages/core/src/workflow/sleep.tsReplaced push with set, find with get, findIndex+splice with delete
packages/core/src/step.test.tsUpdated test assertions to work with Map (using .values(), .size)
packages/core/src/global.test.tsAdded toQueueMap helper and updated all test instantiations
.changeset/slick-cooks-clean.mdAdded changeset documenting the performance improvement

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/core/src/step.ts Outdated

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one minor suggestion. Otherwise :lgtm:

Co-authored-by: Nathan Rajlich <n@n8.io>

@vercelvercelBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Build Fix:

The if statement block has a misplaced closing brace with incorrect indentation that breaks the if-else structure, causing TypeScript to fail parsing the else clause and subsequent code.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..bcab2e1 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -75,18 +75,17 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
ctx.invocationsQueue.delete(correlationId);
- } else {- setTimeout(() => {- reject(- new WorkflowRuntimeError(- `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`- )- );- }, 0);- return EventConsumerResult.Finished;- }- hasSeenStepStarted = true;+ } else {+ setTimeout(() => {+ reject(+ new WorkflowRuntimeError(+ `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`+ )+ );+ }, 0);+ return EventConsumerResult.Finished;
}
+ hasSeenStepStarted = true;
// If this is a subsequent "step_started" event (after a retry), we just consume it
// without trying to remove from the queue again or logging a warning
return EventConsumerResult.Consumed;

Analysis

Indentation error in if-else statement causes TypeScript compilation failure

What fails: TypeScript compiler fails on packages/core/src/step.ts due to incorrect indentation in an if-else statement structure

How to reproduce:

cd packages/core && pnpm build

Result:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Details: The if (!hasSeenStepStarted) block at line 75-89 had an extra closing brace on line 81 with incorrect indentation, causing the else clause on line 82 to be syntactically malformed. This caused the parser to interpret the structure incorrectly, resulting in cascading syntax errors on subsequent lines. The fix removes the misplaced brace and corrects the indentation to match the proper if-else structure.

Fix on Vercel

);
if (invocationsQueueIndex !== -1) {
ctx.invocationsQueue.splice(invocationsQueueIndex, 1);
// O(1) lookup and delete using Map

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a syntax error in the step handler: an orphaned else clause on line 78 appears after a statement rather than an if block. Additionally, the logic is broken - the error handling code (lines 79-86) is now unreachable because the delete() return value is not checked.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..f76f438 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -74,8 +74,8 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
// Step has started - so remove from the invocations queue (only on the first "step_started" event)
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
- ctx.invocationsQueue.delete(correlationId);- } else {+ const wasDeleted = ctx.invocationsQueue.delete(correlationId);+ if (!wasDeleted) {
setTimeout(() => {
reject(
new WorkflowRuntimeError(

Analysis

Syntax error in step handler due to incomplete refactoring of Map.delete()

What fails: The code in packages/core/src/step.ts (lines 75-89) contains a syntax error where an orphaned else clause appears after a statement rather than an if block, preventing the entire module from compiling.

How to reproduce:

cd packages/core
pnpm typecheck

Result: TypeScript compiler reports:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Root cause: A refactoring commit (fe0a6b0) changed from checking if (ctx.invocationsQueue.has(correlationId)) before deleting to using Map.delete() directly, but the refactoring was incomplete - the inner if check was removed while leaving the corresponding else clause orphaned:

// BROKEN - orphaned else with no matching ifif(!hasSeenStepStarted){ctx.invocationsQueue.delete(correlationId);// Statement, not an if}else{// Syntax error: else without if// error handling}}

Fix: Check the return value of Map.delete() (which returns boolean) to determine if the deletion succeeded:

constwasDeleted=ctx.invocationsQueue.delete(correlationId);if(!wasDeleted){// error handling}

This preserves the original logic while maintaining O(1) complexity with Map operations.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pranaygp@TooTallNate
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' perf: use Map for invocationsQueue (O(1) lookup/delete) by pranaygp · Pull Request #541 · vercel/workflow · GitHub
Skip to content

perf: use Map for invocationsQueue (O(1) lookup/delete) - #541

Merged
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_
Dec 7, 2025
Merged

perf: use Map for invocationsQueue (O(1) lookup/delete)#541
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.

Changes:

  • private.ts: Change invocationsQueue type from QueueItem[] to Map
  • workflow.ts: Initialize with new Map() instead of []
  • global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
  • step.ts: Use Map.set/has/delete instead of push/findIndex/splice
  • hook.ts: Use Map.set/delete instead of push/findIndex/splice
  • sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice

This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

@changeset-bot

changeset-botBot commented Dec 5, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7a903a8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
NameType
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/aiPatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentPreviewCommentsUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorDec 5, 2025 11:43pm
example-nextjs-workflow-webpackErrorErrorDec 5, 2025 11:43pm
example-workflowErrorErrorDec 5, 2025 11:43pm
workbench-astro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-express-workflowErrorErrorDec 5, 2025 11:43pm
workbench-fastify-workflowErrorErrorDec 5, 2025 11:43pm
workbench-hono-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nitro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nuxt-workflowErrorErrorDec 5, 2025 11:43pm
workbench-sveltekit-workflowErrorErrorDec 5, 2025 11:43pm
workbench-vite-workflowErrorErrorDec 5, 2025 11:43pm
workflow-docsErrorErrorDec 5, 2025 11:43pm

@github-actions

github-actionsBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

No test result files found.


⚠️Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: failure
  • Local Prod: failure
  • Local Postgres: failure
  • Windows: failure
  • Community Worlds: success

Check the workflow run for details.

@pranaygpGraphite App

pranaygp commented Dec 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@pranaygp
pranaygpforce-pushed the pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_ branch from 0b839d9 to c9cd63eCompareDecember 5, 2025 04:18
@pranaygp
pranaygpforce-pushed the pranaygp/12-04-add_stress_test_benchmarks_for_large_concurrent_step_counts branch from 4c9a674 to 369a873CompareDecember 5, 2025 04:18
Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.
Changes:
- private.ts: Change invocationsQueue type from QueueItem[] to Map
- workflow.ts: Initialize with new Map() instead of []
- global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
- step.ts: Use Map.set/has/delete instead of push/findIndex/splice
- hook.ts: Use Map.set/delete instead of push/findIndex/splice
- sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice
This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the invocationsQueue data structure by replacing an array-based implementation with a Map<string, QueueItem> for O(1) lookup and delete operations. This eliminates O(n²) complexity from patterns like findIndex + splice that were previously used in workflow orchestration, improving performance in high-concurrency scenarios.

Key changes:

  • Changed invocationsQueue from QueueItem[] to Map<string, QueueItem> keyed by correlationId
  • Replaced array operations (push, findIndex, splice, find) with Map operations (set, has, delete, get)
  • Updated WorkflowSuspension constructor to accept Map and convert to array for backwards compatibility

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/core/src/private.tsUpdated WorkflowOrchestratorContext type definition to use Map<string, QueueItem> instead of QueueItem[]
packages/core/src/workflow.tsChanged initialization from empty array [] to new Map()
packages/core/src/global.tsModified WorkflowSuspension constructor to accept Map, convert to array, and implement single-pass counting
packages/core/src/step.tsReplaced push with set, findIndex+splice with has+delete
packages/core/src/workflow/hook.tsReplaced push with set, findIndex+splice with delete
packages/core/src/workflow/sleep.tsReplaced push with set, find with get, findIndex+splice with delete
packages/core/src/step.test.tsUpdated test assertions to work with Map (using .values(), .size)
packages/core/src/global.test.tsAdded toQueueMap helper and updated all test instantiations
.changeset/slick-cooks-clean.mdAdded changeset documenting the performance improvement

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/core/src/step.ts Outdated

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one minor suggestion. Otherwise :lgtm:

Co-authored-by: Nathan Rajlich <n@n8.io>

@vercelvercelBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Build Fix:

The if statement block has a misplaced closing brace with incorrect indentation that breaks the if-else structure, causing TypeScript to fail parsing the else clause and subsequent code.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..bcab2e1 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -75,18 +75,17 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
ctx.invocationsQueue.delete(correlationId);
- } else {- setTimeout(() => {- reject(- new WorkflowRuntimeError(- `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`- )- );- }, 0);- return EventConsumerResult.Finished;- }- hasSeenStepStarted = true;+ } else {+ setTimeout(() => {+ reject(+ new WorkflowRuntimeError(+ `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`+ )+ );+ }, 0);+ return EventConsumerResult.Finished;
}
+ hasSeenStepStarted = true;
// If this is a subsequent "step_started" event (after a retry), we just consume it
// without trying to remove from the queue again or logging a warning
return EventConsumerResult.Consumed;

Analysis

Indentation error in if-else statement causes TypeScript compilation failure

What fails: TypeScript compiler fails on packages/core/src/step.ts due to incorrect indentation in an if-else statement structure

How to reproduce:

cd packages/core && pnpm build

Result:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Details: The if (!hasSeenStepStarted) block at line 75-89 had an extra closing brace on line 81 with incorrect indentation, causing the else clause on line 82 to be syntactically malformed. This caused the parser to interpret the structure incorrectly, resulting in cascading syntax errors on subsequent lines. The fix removes the misplaced brace and corrects the indentation to match the proper if-else structure.

Fix on Vercel

);
if (invocationsQueueIndex !== -1) {
ctx.invocationsQueue.splice(invocationsQueueIndex, 1);
// O(1) lookup and delete using Map

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a syntax error in the step handler: an orphaned else clause on line 78 appears after a statement rather than an if block. Additionally, the logic is broken - the error handling code (lines 79-86) is now unreachable because the delete() return value is not checked.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..f76f438 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -74,8 +74,8 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
// Step has started - so remove from the invocations queue (only on the first "step_started" event)
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
- ctx.invocationsQueue.delete(correlationId);- } else {+ const wasDeleted = ctx.invocationsQueue.delete(correlationId);+ if (!wasDeleted) {
setTimeout(() => {
reject(
new WorkflowRuntimeError(

Analysis

Syntax error in step handler due to incomplete refactoring of Map.delete()

What fails: The code in packages/core/src/step.ts (lines 75-89) contains a syntax error where an orphaned else clause appears after a statement rather than an if block, preventing the entire module from compiling.

How to reproduce:

cd packages/core
pnpm typecheck

Result: TypeScript compiler reports:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Root cause: A refactoring commit (fe0a6b0) changed from checking if (ctx.invocationsQueue.has(correlationId)) before deleting to using Map.delete() directly, but the refactoring was incomplete - the inner if check was removed while leaving the corresponding else clause orphaned:

// BROKEN - orphaned else with no matching ifif(!hasSeenStepStarted){ctx.invocationsQueue.delete(correlationId);// Statement, not an if}else{// Syntax error: else without if// error handling}}

Fix: Check the return value of Map.delete() (which returns boolean) to determine if the deletion succeeded:

constwasDeleted=ctx.invocationsQueue.delete(correlationId);if(!wasDeleted){// error handling}

This preserves the original logic while maintaining O(1) complexity with Map operations.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pranaygp@TooTallNate
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' perf: use Map for invocationsQueue (O(1) lookup/delete) by pranaygp · Pull Request #541 · vercel/workflow · GitHub
Skip to content

perf: use Map for invocationsQueue (O(1) lookup/delete) - #541

Merged
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_
Dec 7, 2025
Merged

perf: use Map for invocationsQueue (O(1) lookup/delete)#541
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.

Changes:

  • private.ts: Change invocationsQueue type from QueueItem[] to Map
  • workflow.ts: Initialize with new Map() instead of []
  • global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
  • step.ts: Use Map.set/has/delete instead of push/findIndex/splice
  • hook.ts: Use Map.set/delete instead of push/findIndex/splice
  • sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice

This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

@changeset-bot

changeset-botBot commented Dec 5, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7a903a8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
NameType
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/aiPatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentPreviewCommentsUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorDec 5, 2025 11:43pm
example-nextjs-workflow-webpackErrorErrorDec 5, 2025 11:43pm
example-workflowErrorErrorDec 5, 2025 11:43pm
workbench-astro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-express-workflowErrorErrorDec 5, 2025 11:43pm
workbench-fastify-workflowErrorErrorDec 5, 2025 11:43pm
workbench-hono-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nitro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nuxt-workflowErrorErrorDec 5, 2025 11:43pm
workbench-sveltekit-workflowErrorErrorDec 5, 2025 11:43pm
workbench-vite-workflowErrorErrorDec 5, 2025 11:43pm
workflow-docsErrorErrorDec 5, 2025 11:43pm

@github-actions

github-actionsBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

No test result files found.


⚠️Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: failure
  • Local Prod: failure
  • Local Postgres: failure
  • Windows: failure
  • Community Worlds: success

Check the workflow run for details.

@pranaygpGraphite App

pranaygp commented Dec 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@pranaygp
pranaygpforce-pushed the pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_ branch from 0b839d9 to c9cd63eCompareDecember 5, 2025 04:18
@pranaygp
pranaygpforce-pushed the pranaygp/12-04-add_stress_test_benchmarks_for_large_concurrent_step_counts branch from 4c9a674 to 369a873CompareDecember 5, 2025 04:18
Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.
Changes:
- private.ts: Change invocationsQueue type from QueueItem[] to Map
- workflow.ts: Initialize with new Map() instead of []
- global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
- step.ts: Use Map.set/has/delete instead of push/findIndex/splice
- hook.ts: Use Map.set/delete instead of push/findIndex/splice
- sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice
This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the invocationsQueue data structure by replacing an array-based implementation with a Map<string, QueueItem> for O(1) lookup and delete operations. This eliminates O(n²) complexity from patterns like findIndex + splice that were previously used in workflow orchestration, improving performance in high-concurrency scenarios.

Key changes:

  • Changed invocationsQueue from QueueItem[] to Map<string, QueueItem> keyed by correlationId
  • Replaced array operations (push, findIndex, splice, find) with Map operations (set, has, delete, get)
  • Updated WorkflowSuspension constructor to accept Map and convert to array for backwards compatibility

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/core/src/private.tsUpdated WorkflowOrchestratorContext type definition to use Map<string, QueueItem> instead of QueueItem[]
packages/core/src/workflow.tsChanged initialization from empty array [] to new Map()
packages/core/src/global.tsModified WorkflowSuspension constructor to accept Map, convert to array, and implement single-pass counting
packages/core/src/step.tsReplaced push with set, findIndex+splice with has+delete
packages/core/src/workflow/hook.tsReplaced push with set, findIndex+splice with delete
packages/core/src/workflow/sleep.tsReplaced push with set, find with get, findIndex+splice with delete
packages/core/src/step.test.tsUpdated test assertions to work with Map (using .values(), .size)
packages/core/src/global.test.tsAdded toQueueMap helper and updated all test instantiations
.changeset/slick-cooks-clean.mdAdded changeset documenting the performance improvement

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/core/src/step.ts Outdated

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one minor suggestion. Otherwise :lgtm:

Co-authored-by: Nathan Rajlich <n@n8.io>

@vercelvercelBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Build Fix:

The if statement block has a misplaced closing brace with incorrect indentation that breaks the if-else structure, causing TypeScript to fail parsing the else clause and subsequent code.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..bcab2e1 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -75,18 +75,17 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
ctx.invocationsQueue.delete(correlationId);
- } else {- setTimeout(() => {- reject(- new WorkflowRuntimeError(- `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`- )- );- }, 0);- return EventConsumerResult.Finished;- }- hasSeenStepStarted = true;+ } else {+ setTimeout(() => {+ reject(+ new WorkflowRuntimeError(+ `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`+ )+ );+ }, 0);+ return EventConsumerResult.Finished;
}
+ hasSeenStepStarted = true;
// If this is a subsequent "step_started" event (after a retry), we just consume it
// without trying to remove from the queue again or logging a warning
return EventConsumerResult.Consumed;

Analysis

Indentation error in if-else statement causes TypeScript compilation failure

What fails: TypeScript compiler fails on packages/core/src/step.ts due to incorrect indentation in an if-else statement structure

How to reproduce:

cd packages/core && pnpm build

Result:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Details: The if (!hasSeenStepStarted) block at line 75-89 had an extra closing brace on line 81 with incorrect indentation, causing the else clause on line 82 to be syntactically malformed. This caused the parser to interpret the structure incorrectly, resulting in cascading syntax errors on subsequent lines. The fix removes the misplaced brace and corrects the indentation to match the proper if-else structure.

Fix on Vercel

);
if (invocationsQueueIndex !== -1) {
ctx.invocationsQueue.splice(invocationsQueueIndex, 1);
// O(1) lookup and delete using Map

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a syntax error in the step handler: an orphaned else clause on line 78 appears after a statement rather than an if block. Additionally, the logic is broken - the error handling code (lines 79-86) is now unreachable because the delete() return value is not checked.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..f76f438 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -74,8 +74,8 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
// Step has started - so remove from the invocations queue (only on the first "step_started" event)
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
- ctx.invocationsQueue.delete(correlationId);- } else {+ const wasDeleted = ctx.invocationsQueue.delete(correlationId);+ if (!wasDeleted) {
setTimeout(() => {
reject(
new WorkflowRuntimeError(

Analysis

Syntax error in step handler due to incomplete refactoring of Map.delete()

What fails: The code in packages/core/src/step.ts (lines 75-89) contains a syntax error where an orphaned else clause appears after a statement rather than an if block, preventing the entire module from compiling.

How to reproduce:

cd packages/core
pnpm typecheck

Result: TypeScript compiler reports:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Root cause: A refactoring commit (fe0a6b0) changed from checking if (ctx.invocationsQueue.has(correlationId)) before deleting to using Map.delete() directly, but the refactoring was incomplete - the inner if check was removed while leaving the corresponding else clause orphaned:

// BROKEN - orphaned else with no matching ifif(!hasSeenStepStarted){ctx.invocationsQueue.delete(correlationId);// Statement, not an if}else{// Syntax error: else without if// error handling}}

Fix: Check the return value of Map.delete() (which returns boolean) to determine if the deletion succeeded:

constwasDeleted=ctx.invocationsQueue.delete(correlationId);if(!wasDeleted){// error handling}

This preserves the original logic while maintaining O(1) complexity with Map operations.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pranaygp@TooTallNate
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' perf: use Map for invocationsQueue (O(1) lookup/delete) by pranaygp · Pull Request #541 · vercel/workflow · GitHub
Skip to content

perf: use Map for invocationsQueue (O(1) lookup/delete) - #541

Merged
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_
Dec 7, 2025
Merged

perf: use Map for invocationsQueue (O(1) lookup/delete)#541
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.

Changes:

  • private.ts: Change invocationsQueue type from QueueItem[] to Map
  • workflow.ts: Initialize with new Map() instead of []
  • global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
  • step.ts: Use Map.set/has/delete instead of push/findIndex/splice
  • hook.ts: Use Map.set/delete instead of push/findIndex/splice
  • sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice

This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

@changeset-bot

changeset-botBot commented Dec 5, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7a903a8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
NameType
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/aiPatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentPreviewCommentsUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorDec 5, 2025 11:43pm
example-nextjs-workflow-webpackErrorErrorDec 5, 2025 11:43pm
example-workflowErrorErrorDec 5, 2025 11:43pm
workbench-astro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-express-workflowErrorErrorDec 5, 2025 11:43pm
workbench-fastify-workflowErrorErrorDec 5, 2025 11:43pm
workbench-hono-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nitro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nuxt-workflowErrorErrorDec 5, 2025 11:43pm
workbench-sveltekit-workflowErrorErrorDec 5, 2025 11:43pm
workbench-vite-workflowErrorErrorDec 5, 2025 11:43pm
workflow-docsErrorErrorDec 5, 2025 11:43pm

@github-actions

github-actionsBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

No test result files found.


⚠️Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: failure
  • Local Prod: failure
  • Local Postgres: failure
  • Windows: failure
  • Community Worlds: success

Check the workflow run for details.

@pranaygpGraphite App

pranaygp commented Dec 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@pranaygp
pranaygpforce-pushed the pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_ branch from 0b839d9 to c9cd63eCompareDecember 5, 2025 04:18
@pranaygp
pranaygpforce-pushed the pranaygp/12-04-add_stress_test_benchmarks_for_large_concurrent_step_counts branch from 4c9a674 to 369a873CompareDecember 5, 2025 04:18
Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.
Changes:
- private.ts: Change invocationsQueue type from QueueItem[] to Map
- workflow.ts: Initialize with new Map() instead of []
- global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
- step.ts: Use Map.set/has/delete instead of push/findIndex/splice
- hook.ts: Use Map.set/delete instead of push/findIndex/splice
- sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice
This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the invocationsQueue data structure by replacing an array-based implementation with a Map<string, QueueItem> for O(1) lookup and delete operations. This eliminates O(n²) complexity from patterns like findIndex + splice that were previously used in workflow orchestration, improving performance in high-concurrency scenarios.

Key changes:

  • Changed invocationsQueue from QueueItem[] to Map<string, QueueItem> keyed by correlationId
  • Replaced array operations (push, findIndex, splice, find) with Map operations (set, has, delete, get)
  • Updated WorkflowSuspension constructor to accept Map and convert to array for backwards compatibility

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/core/src/private.tsUpdated WorkflowOrchestratorContext type definition to use Map<string, QueueItem> instead of QueueItem[]
packages/core/src/workflow.tsChanged initialization from empty array [] to new Map()
packages/core/src/global.tsModified WorkflowSuspension constructor to accept Map, convert to array, and implement single-pass counting
packages/core/src/step.tsReplaced push with set, findIndex+splice with has+delete
packages/core/src/workflow/hook.tsReplaced push with set, findIndex+splice with delete
packages/core/src/workflow/sleep.tsReplaced push with set, find with get, findIndex+splice with delete
packages/core/src/step.test.tsUpdated test assertions to work with Map (using .values(), .size)
packages/core/src/global.test.tsAdded toQueueMap helper and updated all test instantiations
.changeset/slick-cooks-clean.mdAdded changeset documenting the performance improvement

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/core/src/step.ts Outdated

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one minor suggestion. Otherwise :lgtm:

Co-authored-by: Nathan Rajlich <n@n8.io>

@vercelvercelBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Build Fix:

The if statement block has a misplaced closing brace with incorrect indentation that breaks the if-else structure, causing TypeScript to fail parsing the else clause and subsequent code.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..bcab2e1 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -75,18 +75,17 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
ctx.invocationsQueue.delete(correlationId);
- } else {- setTimeout(() => {- reject(- new WorkflowRuntimeError(- `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`- )- );- }, 0);- return EventConsumerResult.Finished;- }- hasSeenStepStarted = true;+ } else {+ setTimeout(() => {+ reject(+ new WorkflowRuntimeError(+ `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`+ )+ );+ }, 0);+ return EventConsumerResult.Finished;
}
+ hasSeenStepStarted = true;
// If this is a subsequent "step_started" event (after a retry), we just consume it
// without trying to remove from the queue again or logging a warning
return EventConsumerResult.Consumed;

Analysis

Indentation error in if-else statement causes TypeScript compilation failure

What fails: TypeScript compiler fails on packages/core/src/step.ts due to incorrect indentation in an if-else statement structure

How to reproduce:

cd packages/core && pnpm build

Result:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Details: The if (!hasSeenStepStarted) block at line 75-89 had an extra closing brace on line 81 with incorrect indentation, causing the else clause on line 82 to be syntactically malformed. This caused the parser to interpret the structure incorrectly, resulting in cascading syntax errors on subsequent lines. The fix removes the misplaced brace and corrects the indentation to match the proper if-else structure.

Fix on Vercel

);
if (invocationsQueueIndex !== -1) {
ctx.invocationsQueue.splice(invocationsQueueIndex, 1);
// O(1) lookup and delete using Map

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a syntax error in the step handler: an orphaned else clause on line 78 appears after a statement rather than an if block. Additionally, the logic is broken - the error handling code (lines 79-86) is now unreachable because the delete() return value is not checked.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..f76f438 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -74,8 +74,8 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
// Step has started - so remove from the invocations queue (only on the first "step_started" event)
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
- ctx.invocationsQueue.delete(correlationId);- } else {+ const wasDeleted = ctx.invocationsQueue.delete(correlationId);+ if (!wasDeleted) {
setTimeout(() => {
reject(
new WorkflowRuntimeError(

Analysis

Syntax error in step handler due to incomplete refactoring of Map.delete()

What fails: The code in packages/core/src/step.ts (lines 75-89) contains a syntax error where an orphaned else clause appears after a statement rather than an if block, preventing the entire module from compiling.

How to reproduce:

cd packages/core
pnpm typecheck

Result: TypeScript compiler reports:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Root cause: A refactoring commit (fe0a6b0) changed from checking if (ctx.invocationsQueue.has(correlationId)) before deleting to using Map.delete() directly, but the refactoring was incomplete - the inner if check was removed while leaving the corresponding else clause orphaned:

// BROKEN - orphaned else with no matching ifif(!hasSeenStepStarted){ctx.invocationsQueue.delete(correlationId);// Statement, not an if}else{// Syntax error: else without if// error handling}}

Fix: Check the return value of Map.delete() (which returns boolean) to determine if the deletion succeeded:

constwasDeleted=ctx.invocationsQueue.delete(correlationId);if(!wasDeleted){// error handling}

This preserves the original logic while maintaining O(1) complexity with Map operations.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pranaygp@TooTallNate
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); perf: use Map for invocationsQueue (O(1) lookup/delete) by pranaygp · Pull Request #541 · vercel/workflow · GitHub
Skip to content

perf: use Map for invocationsQueue (O(1) lookup/delete) - #541

Merged
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_
Dec 7, 2025
Merged

perf: use Map for invocationsQueue (O(1) lookup/delete)#541
pranaygp merged 2 commits into
mainfrom
pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.

Changes:

  • private.ts: Change invocationsQueue type from QueueItem[] to Map
  • workflow.ts: Initialize with new Map() instead of []
  • global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
  • step.ts: Use Map.set/has/delete instead of push/findIndex/splice
  • hook.ts: Use Map.set/delete instead of push/findIndex/splice
  • sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice

This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

@changeset-bot

changeset-botBot commented Dec 5, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7a903a8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
NameType
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/world-testingPatch
@workflow/nuxtPatch
@workflow/aiPatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentPreviewCommentsUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorDec 5, 2025 11:43pm
example-nextjs-workflow-webpackErrorErrorDec 5, 2025 11:43pm
example-workflowErrorErrorDec 5, 2025 11:43pm
workbench-astro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-express-workflowErrorErrorDec 5, 2025 11:43pm
workbench-fastify-workflowErrorErrorDec 5, 2025 11:43pm
workbench-hono-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nitro-workflowErrorErrorDec 5, 2025 11:43pm
workbench-nuxt-workflowErrorErrorDec 5, 2025 11:43pm
workbench-sveltekit-workflowErrorErrorDec 5, 2025 11:43pm
workbench-vite-workflowErrorErrorDec 5, 2025 11:43pm
workflow-docsErrorErrorDec 5, 2025 11:43pm

@github-actions

github-actionsBot commented Dec 5, 2025

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

No test result files found.


⚠️Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: failure
  • Local Prod: failure
  • Local Postgres: failure
  • Windows: failure
  • Community Worlds: success

Check the workflow run for details.

@pranaygpGraphite App

pranaygp commented Dec 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@pranaygp
pranaygpforce-pushed the pranaygp/12-04-perf_use_map_for_invocationsqueue_o_1_lookup_delete_ branch from 0b839d9 to c9cd63eCompareDecember 5, 2025 04:18
@pranaygp
pranaygpforce-pushed the pranaygp/12-04-add_stress_test_benchmarks_for_large_concurrent_step_counts branch from 4c9a674 to 369a873CompareDecember 5, 2025 04:18
Replace array-based invocationsQueue with Map<string, QueueItem> for
O(1) lookup and delete operations. This eliminates the O(n²) complexity
from findIndex + splice patterns in step.ts, hook.ts, and sleep.ts.
Changes:
- private.ts: Change invocationsQueue type from QueueItem[] to Map
- workflow.ts: Initialize with new Map() instead of []
- global.ts: Accept Map or array in WorkflowSuspension, add single-pass counting
- step.ts: Use Map.set/has/delete instead of push/findIndex/splice
- hook.ts: Use Map.set/delete instead of push/findIndex/splice
- sleep.ts: Use Map.set/get/delete instead of push/find/findIndex/splice
This is Phase 1 of the high-concurrency workflow optimization plan.
See beads issue wrk-fyx for details.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the invocationsQueue data structure by replacing an array-based implementation with a Map<string, QueueItem> for O(1) lookup and delete operations. This eliminates O(n²) complexity from patterns like findIndex + splice that were previously used in workflow orchestration, improving performance in high-concurrency scenarios.

Key changes:

  • Changed invocationsQueue from QueueItem[] to Map<string, QueueItem> keyed by correlationId
  • Replaced array operations (push, findIndex, splice, find) with Map operations (set, has, delete, get)
  • Updated WorkflowSuspension constructor to accept Map and convert to array for backwards compatibility

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
packages/core/src/private.tsUpdated WorkflowOrchestratorContext type definition to use Map<string, QueueItem> instead of QueueItem[]
packages/core/src/workflow.tsChanged initialization from empty array [] to new Map()
packages/core/src/global.tsModified WorkflowSuspension constructor to accept Map, convert to array, and implement single-pass counting
packages/core/src/step.tsReplaced push with set, findIndex+splice with has+delete
packages/core/src/workflow/hook.tsReplaced push with set, findIndex+splice with delete
packages/core/src/workflow/sleep.tsReplaced push with set, find with get, findIndex+splice with delete
packages/core/src/step.test.tsUpdated test assertions to work with Map (using .values(), .size)
packages/core/src/global.test.tsAdded toQueueMap helper and updated all test instantiations
.changeset/slick-cooks-clean.mdAdded changeset documenting the performance improvement

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/core/src/step.ts Outdated

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one minor suggestion. Otherwise :lgtm:

Co-authored-by: Nathan Rajlich <n@n8.io>

@vercelvercelBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Build Fix:

The if statement block has a misplaced closing brace with incorrect indentation that breaks the if-else structure, causing TypeScript to fail parsing the else clause and subsequent code.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..bcab2e1 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -75,18 +75,17 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
ctx.invocationsQueue.delete(correlationId);
- } else {- setTimeout(() => {- reject(- new WorkflowRuntimeError(- `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`- )- );- }, 0);- return EventConsumerResult.Finished;- }- hasSeenStepStarted = true;+ } else {+ setTimeout(() => {+ reject(+ new WorkflowRuntimeError(+ `Corrupted event log: step ${correlationId} (${stepName}) started but not found in invocation queue`+ )+ );+ }, 0);+ return EventConsumerResult.Finished;
}
+ hasSeenStepStarted = true;
// If this is a subsequent "step_started" event (after a retry), we just consume it
// without trying to remove from the queue again or logging a warning
return EventConsumerResult.Consumed;

Analysis

Indentation error in if-else statement causes TypeScript compilation failure

What fails: TypeScript compiler fails on packages/core/src/step.ts due to incorrect indentation in an if-else statement structure

How to reproduce:

cd packages/core && pnpm build

Result:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Details: The if (!hasSeenStepStarted) block at line 75-89 had an extra closing brace on line 81 with incorrect indentation, causing the else clause on line 82 to be syntactically malformed. This caused the parser to interpret the structure incorrectly, resulting in cascading syntax errors on subsequent lines. The fix removes the misplaced brace and corrects the indentation to match the proper if-else structure.

Fix on Vercel

);
if (invocationsQueueIndex !== -1) {
ctx.invocationsQueue.splice(invocationsQueueIndex, 1);
// O(1) lookup and delete using Map

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a syntax error in the step handler: an orphaned else clause on line 78 appears after a statement rather than an if block. Additionally, the logic is broken - the error handling code (lines 79-86) is now unreachable because the delete() return value is not checked.

View Details
📝 Patch Details
diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts
index 10238dc..f76f438 100644
--- a/packages/core/src/step.ts+++ b/packages/core/src/step.ts@@ -74,8 +74,8 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) {
// Step has started - so remove from the invocations queue (only on the first "step_started" event)
if (!hasSeenStepStarted) {
// O(1) lookup and delete using Map
- ctx.invocationsQueue.delete(correlationId);- } else {+ const wasDeleted = ctx.invocationsQueue.delete(correlationId);+ if (!wasDeleted) {
setTimeout(() => {
reject(
new WorkflowRuntimeError(

Analysis

Syntax error in step handler due to incomplete refactoring of Map.delete()

What fails: The code in packages/core/src/step.ts (lines 75-89) contains a syntax error where an orphaned else clause appears after a statement rather than an if block, preventing the entire module from compiling.

How to reproduce:

cd packages/core
pnpm typecheck

Result: TypeScript compiler reports:

src/step.ts(95,9): error TS1005: ',' expected.
src/step.ts(128,8): error TS1005: ',' expected.
src/step.ts(160,1): error TS1128: Declaration or statement expected.

Root cause: A refactoring commit (fe0a6b0) changed from checking if (ctx.invocationsQueue.has(correlationId)) before deleting to using Map.delete() directly, but the refactoring was incomplete - the inner if check was removed while leaving the corresponding else clause orphaned:

// BROKEN - orphaned else with no matching ifif(!hasSeenStepStarted){ctx.invocationsQueue.delete(correlationId);// Statement, not an if}else{// Syntax error: else without if// error handling}}

Fix: Check the return value of Map.delete() (which returns boolean) to determine if the deletion succeeded:

constwasDeleted=ctx.invocationsQueue.delete(correlationId);if(!wasDeleted){// error handling}

This preserves the original logic while maintaining O(1) complexity with Map operations.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@pranaygp@TooTallNate