Skip to content

Make start() types unknown when deploymentId is provided - #1367

Merged
TooTallNate merged 4 commits into
mainfrom
v0/pranaygp-6fadd605
Apr 3, 2026
Merged

Make start() types unknown when deploymentId is provided#1367
TooTallNate merged 4 commits into
mainfrom
v0/pranaygp-6fadd605

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

When deploymentId is explicitly provided to start(), the workflow function at that deployment may have different argument and return types than the current codebase. This change splits StartOptions into separate types and adds function overloads so that:

  • Without deploymentId: Full type inference is preserved (TArgs and TResult)
  • With deploymentId: Args become unknown[] and return becomes Run<unknown> to avoid false type expectations

What changed

  • Split StartOptions into StartOptionsBase, StartOptionsWithDeploymentId, and StartOptionsWithoutDeploymentId
  • Added overloads for start() that return Run<unknown> when deploymentId is provided
  • Exported the new option types from runtime.ts
  • Updated docs to explain the type behavior with deploymentId

Slack Thread

Ensure types are 'unknown[]' and 'unknown' for 'deploymentId' and update exports and documentation.
Slack-Thread: https://vercel.slack.com/archives/C09G3EQAL84/p1773368990070059?thread_ts=1773368990.070059&cid=C09G3EQAL84
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>
@pranaygp
pranaygp requested a review from a team as a code ownerMarch 13, 2026 02:34
CopilotAI review requested due to automatic review settings March 13, 2026 02:34
@changeset-bot

changeset-botBot commented Mar 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1f22fd4

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

This PR includes changesets to release 16 packages
NameType
@workflow/corePatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/world-testingPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@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 Mar 13, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.037s (-17.0% 🟢)1.005s (~)0.967s101.00x
💻 LocalNitro0.044s (-7.9% 🟢)1.005s (~)0.961s101.19x
💻 LocalNext.js (Turbopack)0.053s1.005s0.952s101.44x
🌐 RedisNext.js (Turbopack)0.056s1.005s0.949s101.50x
🐘 PostgresExpress0.061s (-9.1% 🟢)1.012s (-0.5%)0.951s101.64x
🐘 PostgresNitro0.064s (+4.1%)1.011s (~)0.947s101.71x
🐘 PostgresNext.js (Turbopack)0.064s1.012s0.948s101.72x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.454s (-12.6% 🟢)3.279s (+42.4% 🔺)2.826s101.00x
▲ VercelNitro0.467s (-6.8% 🟢)3.007s (+14.7% 🔺)2.540s101.03x
▲ VercelNext.js (Turbopack)0.621s (+34.6% 🔺)3.670s (+46.0% 🔺)3.049s101.37x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.094s (-3.1%)2.005s (~)0.910s101.00x
🌐 RedisNext.js (Turbopack)1.117s2.007s0.889s101.02x
💻 LocalNext.js (Turbopack)1.123s2.006s0.883s101.03x
💻 LocalNitro1.125s (~)2.005s (~)0.881s101.03x
🐘 PostgresNext.js (Turbopack)1.127s2.012s0.884s101.03x
🐘 PostgresExpress1.153s (+0.8%)2.012s (~)0.859s101.05x
🐘 PostgresNitro1.153s (+2.9%)2.013s (~)0.860s101.05x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.009s (-28.5% 🟢)3.711s (-20.8% 🟢)1.701s101.00x
▲ VercelNitro2.137s (+5.9% 🔺)3.947s (-17.7% 🟢)1.810s101.06x
▲ VercelNext.js (Turbopack)2.611s (+29.6% 🔺)4.515s (+4.0%)1.904s101.30x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.608s (-2.8%)11.021s (~)0.412s31.00x
🌐 RedisNext.js (Turbopack)10.733s11.022s0.289s31.01x
🐘 PostgresNext.js (Turbopack)10.742s11.020s0.278s31.01x
💻 LocalNext.js (Turbopack)10.776s11.023s0.246s31.02x
💻 LocalNitro10.888s (~)11.022s (~)0.134s31.03x
🐘 PostgresExpress10.921s (~)11.024s (~)0.103s31.03x
🐘 PostgresNitro10.923s (+2.7%)11.024s (~)0.102s31.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro16.233s (-5.9% 🟢)17.955s (-11.4% 🟢)1.722s21.00x
▲ VercelNext.js (Turbopack)17.419s (-1.4%)19.466s (-1.8%)2.047s21.07x
▲ VercelExpress17.820s (+8.4% 🔺)20.194s (+13.0% 🔺)2.374s21.10x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)14.130s15.025s0.896s41.00x
💻 LocalExpress14.158s (-5.2% 🟢)15.027s (~)0.869s41.00x
🌐 RedisNext.js (Turbopack)14.191s15.028s0.837s41.00x
🐘 PostgresExpress14.559s (~)15.026s (~)0.467s41.03x
🐘 PostgresNitro14.584s (+4.3%)15.025s (+5.6% 🔺)0.442s41.03x
💻 LocalNext.js (Turbopack)14.648s15.029s0.381s41.04x
💻 LocalNitro14.930s (~)15.280s (+1.6%)0.350s41.06x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express30.401s (-11.0% 🟢)32.332s (-8.5% 🟢)1.931s21.00x
▲ VercelNext.js (Turbopack)31.693s (-3.3%)33.746s (-2.0%)2.053s21.04x
▲ VercelNitro34.596s (+1.6%)36.352s (+1.1%)1.757s21.14x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)13.180s14.025s0.846s71.00x
🌐 RedisNext.js (Turbopack)13.346s14.025s0.678s71.01x
🐘 PostgresExpress13.985s (-0.9%)14.310s (-2.9%)0.325s71.06x
🐘 PostgresNitro14.021s (+8.1% 🔺)14.594s (+9.7% 🔺)0.573s71.06x
💻 LocalExpress14.713s (-11.0% 🟢)15.027s (-11.8% 🟢)0.313s61.12x
💻 LocalNext.js (Turbopack)16.213s16.699s0.486s61.23x
💻 LocalNitro16.518s (~)17.030s (~)0.512s61.25x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro59.572s (+6.5% 🔺)61.835s (+6.6% 🔺)2.263s21.00x
▲ VercelExpress60.257s (+8.3% 🔺)61.830s (+7.6% 🔺)1.573s21.01x
▲ VercelNext.js (Turbopack)62.203s (+12.0% 🔺)64.216s (+12.2% 🔺)2.013s21.04x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.196s2.010s0.814s151.00x
🐘 PostgresExpress1.253s (-1.0%)2.013s (~)0.760s151.05x
🐘 PostgresNitro1.259s (+4.3%)2.011s (~)0.752s151.05x
🌐 RedisNext.js (Turbopack)1.283s2.007s0.724s151.07x
💻 LocalExpress1.449s (-2.9%)2.005s (~)0.556s151.21x
💻 LocalNext.js (Turbopack)1.512s2.006s0.495s151.26x
💻 LocalNitro1.517s (-0.9%)2.006s (~)0.489s151.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.396s (-0.8%)5.063s (+18.8% 🔺)2.667s71.00x
▲ VercelNext.js (Turbopack)3.084s (+8.4% 🔺)5.371s (+22.0% 🔺)2.286s61.29x
▲ VercelNitro3.454s (+37.5% 🔺)6.304s (+14.5% 🔺)2.850s51.44x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.315s (+1.9%)3.010s (~)0.695s101.00x
🐘 PostgresExpress2.322s (-1.4%)3.012s (~)0.690s101.00x
🐘 PostgresNext.js (Turbopack)2.371s3.011s0.640s101.02x
🌐 RedisNext.js (Turbopack)2.547s3.008s0.460s101.10x
💻 LocalExpress2.592s (-9.6% 🟢)3.007s (-3.2%)0.416s101.12x
💻 LocalNitro2.856s (-6.5% 🟢)3.208s (-14.7% 🟢)0.351s101.23x
💻 LocalNext.js (Turbopack)3.084s3.454s0.371s91.33x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.779s (+8.8% 🔺)5.051s (-1.6%)2.273s61.00x
▲ VercelNext.js (Turbopack)2.859s (-3.2%)5.038s (+6.7% 🔺)2.179s61.03x
▲ VercelExpress3.009s (+21.4% 🔺)4.744s (+32.4% 🔺)1.734s71.08x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.451s (~)4.012s (~)0.562s81.00x
🐘 PostgresNitro3.455s (+2.3%)4.013s (~)0.558s81.00x
🐘 PostgresNext.js (Turbopack)3.589s4.011s0.422s81.04x
🌐 RedisNext.js (Turbopack)4.085s4.868s0.782s71.18x
💻 LocalExpress6.830s (-11.6% 🟢)7.013s (-15.2% 🟢)0.183s51.98x
💻 LocalNext.js (Turbopack)7.877s8.519s0.642s42.28x
💻 LocalNitro8.112s (~)8.774s (-2.7%)0.662s42.35x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.152s (+2.6%)4.575s (+2.8%)1.423s71.00x
▲ VercelNitro3.383s (+2.3%)5.027s (-2.7%)1.644s61.07x
▲ VercelNext.js (Turbopack)4.213s (+12.1% 🔺)6.391s (+25.1% 🔺)2.178s61.34x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.189s2.009s0.821s151.00x
🐘 PostgresNitro1.255s (+4.1%)2.010s (~)0.754s151.06x
🐘 PostgresExpress1.263s (+1.1%)2.011s (~)0.748s151.06x
🌐 RedisNext.js (Turbopack)1.310s2.006s0.696s151.10x
💻 LocalExpress1.434s (-7.3% 🟢)2.005s (~)0.571s151.21x
💻 LocalNitro1.528s (+1.6%)2.005s (~)0.477s151.29x
💻 LocalNext.js (Turbopack)1.546s2.005s0.460s151.30x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.285s (~)4.628s (+22.7% 🔺)2.343s71.00x
▲ VercelNext.js (Turbopack)2.319s (-12.9% 🟢)4.435s (~)2.116s71.01x
▲ VercelNitro2.911s (+31.9% 🔺)5.465s (+37.8% 🔺)2.555s61.27x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.328s (+2.1%)3.011s (~)0.683s101.00x
🐘 PostgresExpress2.337s (-0.9%)3.011s (~)0.674s101.00x
🐘 PostgresNext.js (Turbopack)2.360s3.010s0.649s101.01x
🌐 RedisNext.js (Turbopack)2.527s3.008s0.480s101.09x
💻 LocalExpress2.655s (-13.7% 🟢)3.007s (-22.6% 🟢)0.353s101.14x
💻 LocalNitro2.990s (+1.2%)3.759s (+8.8% 🔺)0.770s81.28x
💻 LocalNext.js (Turbopack)3.035s3.759s0.724s81.30x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.335s (-17.9% 🟢)4.208s (+4.5%)1.873s81.00x
▲ VercelNext.js (Turbopack)2.593s (-7.1% 🟢)4.131s (-8.6% 🟢)1.538s81.11x
▲ VercelNitro2.772s (+4.4%)4.296s (-0.8%)1.524s71.19x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.456s (+2.9%)4.014s (~)0.558s81.00x
🐘 PostgresExpress3.480s (+0.7%)4.012s (~)0.532s81.01x
🐘 PostgresNext.js (Turbopack)3.602s4.013s0.411s81.04x
🌐 RedisNext.js (Turbopack)4.190s5.011s0.821s61.21x
💻 LocalExpress7.568s (-13.3% 🟢)8.017s (-13.6% 🟢)0.449s42.19x
💻 LocalNext.js (Turbopack)8.296s9.020s0.724s42.40x
💻 LocalNitro8.980s (+3.5%)9.271s (+2.8%)0.290s42.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.240s (-29.9% 🟢)5.287s (-30.3% 🟢)2.048s61.00x
▲ VercelExpress3.289s (+11.8% 🔺)4.906s (+22.0% 🔺)1.617s71.02x
▲ VercelNext.js (Turbopack)3.376s (-27.8% 🟢)5.782s (-21.4% 🟢)2.406s61.04x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.637s1.007s0.369s601.00x
💻 LocalExpress0.671s (-30.8% 🟢)1.004s (-6.7% 🟢)0.333s601.05x
🌐 RedisNext.js (Turbopack)0.686s1.021s0.335s591.08x
🐘 PostgresNitro0.818s (+38.4% 🔺)1.007s (~)0.189s601.28x
🐘 PostgresExpress0.831s (+2.9%)1.007s (-1.7%)0.177s601.30x
💻 LocalNext.js (Turbopack)0.861s1.021s0.161s591.35x
💻 LocalNitro0.967s (~)1.095s (+5.4% 🔺)0.129s551.52x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)9.735s (+5.9% 🔺)12.091s (+9.7% 🔺)2.356s51.00x
▲ VercelNitro9.781s (+7.8% 🔺)11.831s (+8.4% 🔺)2.050s61.00x
▲ VercelExpress9.825s (+4.0%)11.786s (+7.7% 🔺)1.962s61.01x

🔍 Observability: Next.js (Turbopack) | Nitro | Express

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.595s2.030s0.436s451.00x
🌐 RedisNext.js (Turbopack)1.599s2.006s0.407s451.00x
🐘 PostgresNitro1.919s (+31.8% 🔺)2.031s (~)0.112s451.20x
🐘 PostgresExpress1.960s (+3.5%)2.229s (+10.9% 🔺)0.269s411.23x
💻 LocalExpress2.190s (-26.1% 🟢)3.007s (-7.7% 🟢)0.817s301.37x
💻 LocalNext.js (Turbopack)2.637s3.008s0.371s301.65x
💻 LocalNitro2.965s (~)3.180s (~)0.215s291.86x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro28.355s (+1.4%)30.287s (-2.1%)1.932s31.00x
▲ VercelExpress28.682s (-11.2% 🟢)30.625s (-9.6% 🟢)1.942s31.01x
▲ VercelNext.js (Turbopack)29.714s (-22.0% 🟢)32.742s (-18.3% 🟢)3.028s31.05x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)3.227s4.011s0.785s301.00x
🌐 RedisNext.js (Turbopack)3.304s4.042s0.738s301.02x
🐘 PostgresExpress3.936s (+0.8%)4.217s (+1.8%)0.281s291.22x
🐘 PostgresNitro3.941s (+36.2% 🔺)4.183s (+36.7% 🔺)0.242s291.22x
💻 LocalExpress7.216s (-20.0% 🟢)7.827s (-18.1% 🟢)0.611s162.24x
💻 LocalNext.js (Turbopack)8.271s9.016s0.745s142.56x
💻 LocalNitro9.030s (~)9.556s (+0.8%)0.526s132.80x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express84.700s (+1.1%)86.517s (+2.2%)1.817s21.00x
▲ VercelNitro86.237s (+13.0% 🔺)88.860s (+12.9% 🔺)2.623s21.02x
▲ VercelNext.js (Turbopack)86.941s (+4.6%)90.003s (+6.1% 🔺)3.062s21.03x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.203s1.008s0.804s601.00x
🐘 PostgresExpress0.261s (-6.3% 🟢)1.008s (~)0.747s601.29x
🐘 PostgresNitro0.274s (+24.6% 🔺)1.009s (~)0.735s601.35x
🌐 RedisNext.js (Turbopack)0.381s1.004s0.623s601.87x
💻 LocalNext.js (Turbopack)0.539s1.004s0.466s602.65x
💻 LocalExpress0.573s (-4.4%)1.004s (~)0.431s602.82x
💻 LocalNitro0.596s (+0.8%)1.004s (~)0.408s602.93x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.747s (-4.7%)3.792s (+1.8%)2.044s161.00x
▲ VercelExpress1.778s (-4.8%)3.836s (+10.2% 🔺)2.058s161.02x
▲ VercelNitro1.814s (+7.3% 🔺)4.216s (+25.2% 🔺)2.402s151.04x

🔍 Observability: Next.js (Turbopack) | Express | Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.430s1.007s0.578s901.00x
🐘 PostgresExpress0.487s (-3.0%)1.008s (~)0.521s901.13x
🐘 PostgresNitro0.512s (+40.4% 🔺)1.008s (~)0.496s901.19x
🌐 RedisNext.js (Turbopack)1.169s2.006s0.837s452.72x
💻 LocalExpress2.340s (-4.9%)3.007s (~)0.667s305.44x
💻 LocalNext.js (Turbopack)2.433s3.008s0.576s305.66x
💻 LocalNitro2.455s (-2.0%)3.008s (~)0.553s305.71x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.689s (-5.5% 🟢)4.620s (+1.0%)1.931s201.00x
▲ VercelNitro2.709s (-19.3% 🟢)4.468s (-15.0% 🟢)1.759s211.01x
▲ VercelNext.js (Turbopack)3.112s (-13.0% 🟢)4.994s (-9.5% 🟢)1.882s191.16x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.645s1.007s0.362s1201.00x
🐘 PostgresExpress0.761s (-1.9%)1.008s (~)0.247s1201.18x
🐘 PostgresNitro0.774s (+36.5% 🔺)1.008s (~)0.235s1201.20x
🌐 RedisNext.js (Turbopack)2.645s3.008s0.362s404.10x
💻 LocalExpress10.128s (-8.0% 🟢)10.935s (-6.2% 🟢)0.807s1115.69x
💻 LocalNext.js (Turbopack)10.582s11.117s0.535s1116.40x
💻 LocalNitro11.083s (~)11.754s (-0.8%)0.671s1117.17x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.213s (-20.1% 🟢)8.025s (-20.1% 🟢)1.811s151.00x
▲ VercelExpress6.676s (-4.7%)9.283s (+7.7% 🔺)2.607s141.07x
▲ VercelNext.js (Turbopack)83.022s (~)85.516s (~)2.494s413.36x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.140s (-33.0% 🟢)1.003s (~)0.009s (-18.6% 🟢)1.015s (~)0.876s101.00x
🐘 PostgresNext.js (Turbopack)0.165s1.000s0.001s1.010s0.846s101.18x
💻 LocalNext.js (Turbopack)0.171s1.002s0.012s1.018s0.846s101.23x
🌐 RedisNext.js (Turbopack)0.172s1.000s0.002s1.007s0.836s101.23x
🐘 PostgresExpress0.201s (-3.1%)0.995s (~)0.001s (-26.7% 🟢)1.010s (~)0.809s101.44x
💻 LocalNitro0.203s (+1.2%)1.003s (~)0.012s (+13.0% 🔺)1.018s (~)0.815s101.45x
🐘 PostgresNitro0.209s (+31.6% 🔺)0.993s (-0.6%)0.001s (~)1.013s (~)0.803s101.50x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.676s (+15.4% 🔺)3.069s (-1.7%)0.608s (+61.4% 🔺)4.340s (+8.4% 🔺)2.664s101.00x
▲ VercelNext.js (Turbopack)1.787s (-7.0% 🟢)2.650s (-19.7% 🟢)0.634s (+41.0% 🔺)4.138s (-5.1% 🟢)2.350s101.07x
▲ VercelNitro2.056s (+18.8% 🔺)2.939s (-0.7%)0.619s (+90.4% 🔺)4.419s (+12.2% 🔺)2.363s101.23x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🌐 Redis🥇 Next.js (Turbopack)0.474s1.001s0.003s1.012s0.538s601.00x
🐘 PostgresNext.js (Turbopack)0.555s1.007s0.004s1.029s0.474s591.17x
💻 LocalExpress0.563s (-22.3% 🟢)1.008s (~)0.009s (-4.0%)1.021s (~)0.458s591.19x
🐘 PostgresExpress0.601s (-0.8%)1.003s (~)0.004s (+1.2%)1.025s (~)0.424s591.27x
🐘 PostgresNitro0.604s (+23.5% 🔺)1.003s (~)0.004s (+19.1% 🔺)1.024s (~)0.421s591.27x
💻 LocalNext.js (Turbopack)0.663s1.008s0.010s1.024s0.361s591.40x
💻 LocalNitro0.715s (-1.3%)1.009s (~)0.010s (+9.8% 🔺)1.023s (~)0.308s591.51x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.348s (-4.1%)5.574s (-11.3% 🟢)0.285s (+44.3% 🔺)6.563s (-8.2% 🟢)2.215s101.00x
▲ VercelNext.js (Turbopack)4.580s (+14.0% 🔺)6.609s (+21.0% 🔺)0.201s (-32.4% 🟢)7.523s (+8.9% 🔺)2.944s81.05x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🌐 Redis🥇 Next.js (Turbopack)0.886s1.017s0.000s1.021s0.135s591.00x
🐘 PostgresNext.js (Turbopack)0.891s1.071s0.000s1.080s0.189s561.01x
🐘 PostgresExpress0.949s (-3.1%)1.082s (-16.6% 🟢)0.000s (+67.3% 🔺)1.100s (-16.2% 🟢)0.151s551.07x
🐘 PostgresNitro0.984s (+10.7% 🔺)1.234s (+15.3% 🔺)0.000s (+Infinity% 🔺)1.248s (+15.6% 🔺)0.265s501.11x
💻 LocalExpress1.141s (-6.7% 🟢)2.017s (~)0.000s (-71.4% 🟢)2.020s (~)0.879s301.29x
💻 LocalNitro1.210s (-2.4%)2.019s (~)0.000s (-30.8% 🟢)2.022s (~)0.812s301.37x
💻 LocalNext.js (Turbopack)1.263s2.020s0.000s2.024s0.761s301.43x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.751s (-3.9%)3.492s (-4.4%)0.000s (+300.0% 🔺)4.410s (+5.2% 🔺)1.659s151.00x
▲ VercelNext.js (Turbopack)3.000s (-1.0%)4.159s (-2.8%)0.000s (-100.0% 🟢)4.978s (+1.0%)1.978s131.09x
▲ VercelNitro3.317s (+15.6% 🔺)4.995s (+29.6% 🔺)0.000s (-100.0% 🟢)5.707s (+25.5% 🔺)2.390s111.21x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🌐 Redis🥇 Next.js (Turbopack)1.539s2.035s0.000s2.040s0.501s301.00x
🐘 PostgresNitro1.761s (+9.9% 🔺)2.101s (+1.6%)0.000s (+Infinity% 🔺)2.114s (+1.2%)0.353s291.14x
🐘 PostgresExpress1.788s (+3.2%)2.139s (+3.7%)0.000s (+314.3% 🔺)2.153s (+3.6%)0.365s281.16x
🐘 PostgresNext.js (Turbopack)1.826s2.180s0.000s2.189s0.363s281.19x
💻 LocalNitro3.447s (-2.9%)3.968s (-3.2%)0.001s (-14.8% 🟢)3.972s (-3.2%)0.525s162.24x
💻 LocalExpress3.459s (-4.6%)3.968s (-4.8%)0.001s (+3.1%)3.973s (-4.7%)0.515s162.25x
💻 LocalNext.js (Turbopack)3.665s4.166s0.001s4.172s0.506s152.38x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.457s (-1.7%)4.839s (-8.6% 🟢)0.000s (+Infinity% 🔺)5.464s (-10.1% 🟢)2.007s111.00x
▲ VercelExpress3.904s (+13.4% 🔺)5.633s (+22.2% 🔺)0.000s (-100.0% 🟢)6.598s (+23.0% 🔺)2.694s101.13x
▲ VercelNext.js (Turbopack)4.524s (+18.1% 🔺)5.811s (+7.7% 🔺)0.000s (NaN%)6.451s (+6.2% 🔺)1.927s101.31x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress19/21
🐘 PostgresNext.js (Turbopack)15/21
▲ VercelExpress11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres11/21
Next.js (Turbopack)🐘 Postgres14/21
Nitro🐘 Postgres15/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

@github-actions

github-actionsBot commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

Some tests failed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production813067880
✅ 💻 Local Development7820178960
✅ 📦 Local Production7820178960
✅ 🐘 Local Postgres7820178960
✅ 🪟 Windows720880
❌ 🌍 Community Worlds1255524204
✅ 📋 Other198042240
Total3554556754284

❌ Failed Tests

🌍 Community Worlds (55 failed)

mongodb (2 failed):

  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KMF3403BKKDT88P7KVV7T4DX
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KMF3BMSN9XEM9ZF2D00KM2RP

redis (2 failed):

  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KMF3403BKKDT88P7KVV7T4DX
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KMF3BMSN9XEM9ZF2D00KM2RP

turso (51 failed):

  • addTenWorkflow | wrun_01KMF32SWN9BVW9DG2S702AB0N
  • addTenWorkflow | wrun_01KMF32SWN9BVW9DG2S702AB0N
  • wellKnownAgentWorkflow (.well-known/agent) | wrun_01KMF357V4MX08YNT418S4RXG7
  • should work with react rendering in step
  • promiseAllWorkflow | wrun_01KMF331E69WEC3STMGBWNNBX3
  • promiseRaceWorkflow | wrun_01KMF335YFPTQ046JE0YAPM0N5
  • promiseAnyWorkflow | wrun_01KMF33836BJ17MB3AA70ZZ0KB
  • importedStepOnlyWorkflow | wrun_01KMF35VCYTV31J3MXMGKES3CX
  • hookWorkflow | wrun_01KMF33MDX9JE657MX5E05EQSZ
  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KMF3403BKKDT88P7KVV7T4DX
  • webhookWorkflow | wrun_01KMF349YH905DNKVARSP30CY3
  • sleepingWorkflow | wrun_01KMF34GFJ7ZJM0B7M5DFR5E0V
  • parallelSleepWorkflow | wrun_01KMF34WS01HBVWFD0YE43RRJ5
  • nullByteWorkflow | wrun_01KMF356336S1MA803YVMEEF65
  • workflowAndStepMetadataWorkflow | wrun_01KMF359EJ1KCENYANQ5YHY3D0
  • fetchWorkflow | wrun_01KMF3811FDMC4GQM2V4DHYAPX
  • promiseRaceStressTestWorkflow | wrun_01KMF384FM909P1ZMXDGM5AHK7
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_01KMF3B0GANW5XPXY14PVY602X
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KMF3BMSN9XEM9ZF2D00KM2RP
  • hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running | wrun_01KMF3CCC495RP6M703XPW1RZ2
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars) | wrun_01KMF3D4RNE2NK4CR7Y4M8R2Q7
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument | wrun_01KMF3DDBYZ4HBMPH9SQ1GDKFZ
  • closureVariableWorkflow - nested step functions with closure variables | wrun_01KMF3DJJ8PPZ99VWBBQYZK2HM
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step | wrun_01KMF3DN1AEK4BZ18F8DZX81PG
  • health check (queue-based) - workflow and step endpoints respond to health check messages
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly | wrun_01KMF3E3GMWXED3E3Z1EPW2BYC
  • Calculator.calculate - static workflow method using static step methods from another class | wrun_01KMF3E9MCWDWXW8JEB7Z43FE7
  • AllInOneService.processNumber - static workflow method using sibling static step methods | wrun_01KMF3EG1XF1PKXN7P7MKXWWD9
  • ChainableService.processWithThis - static step methods using this to reference the class | wrun_01KMF3EPM1KDVF84050899KNSA
  • thisSerializationWorkflow - step function invoked with .call() and .apply() | wrun_01KMF3EX506QC678C8K8K9CM4V
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE | wrun_01KMF3F3H128TK8QKPWD4H2Y31
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_01KMF3F9YX81ST5Y75Y2SV112W
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context | wrun_01KMF3FN4BSWV5AVNYJPQ580BZ
  • stepFunctionAsStartArgWorkflow - step function reference passed as start() argument | wrun_01KMF3G0CN5QSPC9Q0781XF79M
  • cancelRun - cancelling a running workflow | wrun_01KMF3G6TG9VDPF66K7RWD1ZGC
  • cancelRun via CLI - cancelling a running workflow | wrun_01KMF3GFZESQKY437WQMC2MDYZ
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_01KMF3GVWPV3X2AA93YHXDC9KR
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_01KMF3HGRCPNG8AMGH4CZYRVRP
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_01KMF3HW7J7KV22BATBTJBN25J

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro7307
✅ example7307
✅ express7307
✅ fastify7307
✅ hono7307
✅ nextjs-turbopack7802
✅ nextjs-webpack7802
✅ nitro7307
✅ nuxt7307
✅ sveltekit7307
✅ vite7307
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable66014
✅ express-stable66014
✅ fastify-stable66014
✅ hono-stable66014
✅ nextjs-turbopack-canary55025
✅ nextjs-turbopack-stable7208
✅ nextjs-webpack-canary55025
✅ nextjs-webpack-stable7208
✅ nitro-stable66014
✅ nuxt-stable66014
✅ sveltekit-stable66014
✅ vite-stable66014
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable66014
✅ express-stable66014
✅ fastify-stable66014
✅ hono-stable66014
✅ nextjs-turbopack-canary55025
✅ nextjs-turbopack-stable7208
✅ nextjs-webpack-canary55025
✅ nextjs-webpack-stable7208
✅ nitro-stable66014
✅ nuxt-stable66014
✅ sveltekit-stable66014
✅ vite-stable66014
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable66014
✅ express-stable66014
✅ fastify-stable66014
✅ hono-stable66014
✅ nextjs-turbopack-canary55025
✅ nextjs-turbopack-stable7208
✅ nextjs-webpack-canary55025
✅ nextjs-webpack-stable7208
✅ nitro-stable66014
✅ nuxt-stable66014
✅ sveltekit-stable66014
✅ vite-stable66014
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack7208
❌ 🌍 Community Worlds
AppPassedFailedSkipped
✅ mongodb-dev500
❌ mongodb5328
✅ redis-dev500
❌ redis5328
✅ turso-dev500
❌ turso4518
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable66014
✅ e2e-local-postgres-nest-stable66014
✅ e2e-local-prod-nest-stable66014

📋 View full workflow run

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

Adjusts @workflow/core’s start() TypeScript API so that providing an explicit deploymentId no longer implies compile-time argument/return type compatibility with the local workflow code, avoiding misleading type inference when targeting other deployments.

Changes:

  • Split StartOptions into StartOptionsBase, StartOptionsWithDeploymentId, and StartOptionsWithoutDeploymentId.
  • Added start() overloads so deploymentId calls use unknown[] args and return Promise<Run<unknown>>.
  • Exported the new option types from packages/core/src/runtime.ts and documented the unknown typing behavior.

Reviewed changes

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

FileDescription
packages/core/src/runtime/start.tsRefactors start option types and adds overloads to switch to unknown typing when deploymentId is provided.
packages/core/src/runtime.tsRe-exports the newly split start option types.
docs/content/docs/api-reference/workflow-api/start.mdxDocuments that passing deploymentId makes args/return type unknown.

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

You can also share your feedback on Copilot code review. Take the survey.

*/
specVersion?: number;
export interface StartOptionsWithoutDeploymentId extends StartOptionsBase {
deploymentId?: undefined;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Acknowledged — the string | undefined pattern no longer type-checks with the split options types. Since main has since updated the deploymentId docs to remove the @deprecated annotation and frame it as a standard option, this is an acceptable trade-off for the improved type safety.

Comment threadpackages/core/src/runtime/start.ts Outdated
Comment on lines +83 to +90
export function start(
workflow: WorkflowFunction<unknown[], unknown> | WorkflowMetadata,
args: unknown[],
options: StartOptionsWithDeploymentId
): Promise<Run<unknown>>;

export function start(
workflow: WorkflowFunction<unknown[], unknown> | WorkflowMetadata,

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed — changed the deploymentId overloads to use generics (WorkflowFunction<TArgs, TResult>) instead of WorkflowFunction<unknown[], unknown>, which resolves the contravariance issue. Typed workflows like WorkflowFunction<[string], number> are now assignable. The return type remains Run<unknown> and args remain unknown[] as intended. Also added type-level tests to prevent regressions.

Comment threadpackages/core/src/runtime/start.ts Outdated
): Promise<Run<unknown>>;

export function start(
workflow: WorkflowFunction<unknown[], unknown> | WorkflowMetadata,

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good catch — the no-args overload for the deploymentId case now constrains to WorkflowFunction<[], TResult>, so only zero-arg workflows can omit the args array.

@karthikscale3

Copy link
Copy Markdown
Contributor

Claude flagged a blocker and some more comments:

PR #1367 Review: Make start() types unknown when deploymentId is provided

Intent: When deploymentId is explicitly provided to start(), the workflow at that deployment may have different types than the current codebase, so args/return types should be unknown.

Blocker

Overload with WorkflowFunction<unknown[], unknown> is too restrictive under strictFunctionTypes

The deploymentId overloads use WorkflowFunction<unknown[], unknown> for the workflow parameter:

// (from the PR diff - new overloads)exportfunctionstart(workflow: WorkflowFunction<unknown[],unknown>|WorkflowMetadata,args: unknown[],options: StartOptionsWithDeploymentId): Promise<Run<unknown>>;exportfunctionstart(workflow: WorkflowFunction<unknown[],unknown>|WorkflowMetadata,options: StartOptionsWithDeploymentId): Promise<Run<unknown>>;

Due to TypeScript's function parameter contravariance under strictFunctionTypes, a typed workflow like WorkflowFunction<[number], string> (i.e., (n: number) => Promise<string>) is not assignable to WorkflowFunction<unknown[], unknown> (i.e., (...args: unknown[]) => Promise<unknown>). This means:

import{myWorkflow}from'./workflows/my-workflow';// WorkflowFunction<[string], number>start(myWorkflow,['hello'],{deploymentId: 'dpl_xxx'});// TYPE ERROR

This defeats the purpose of the PR since the main use case is calling start() with an existing typed workflow but targeting a different deployment. The fix would be to use WorkflowFunction<any[], any> in those overloads, or use a generic that still returns Run<unknown>:

exportfunctionstart<TArgsextendsunknown[],TResult>(workflow: WorkflowFunction<TArgs,TResult>|WorkflowMetadata,args: unknown[],options: StartOptionsWithDeploymentId): Promise<Run<unknown>>;

(Copilot's review comment #2 flagged the same issue.)

Potential Regressions

  1. Breaking change for string | undefined deploymentId values -- The split into StartOptionsWithDeploymentId (required deploymentId) and StartOptionsWithoutDeploymentId (deploymentId?: undefined) means code like this no longer compiles:

    constmaybeDeploymentId: string|undefined=getConfig();start(wf,args,{deploymentId: maybeDeploymentId});// TYPE ERROR

    Since deploymentId is @deprecated and documented as "should not be set in user code", this is likely acceptable, but worth noting.

  2. Sub-types not re-exported from workflow/api -- The new types StartOptionsBase, StartOptionsWithDeploymentId, StartOptionsWithoutDeploymentId are exported from @workflow/core/runtime but the user-facing packages/workflow/src/api.ts only re-exports StartOptions. If users need these types, they'd have to import from @workflow/core directly.

Minor Issues

  1. Changeset message is misleading -- The changeset says "Deprecate deploymentId in StartOptions with warning that it should not be set by users", but the actual change is about making types unknown when deploymentId is provided. The changeset message should match the actual behavior change.

  2. No type-level tests -- There are no expectTypeOf or @ts-expect-error assertions to verify the overload resolution works correctly in both branches (with and without deploymentId). Given the subtlety of the contravariance issue, type-level tests would be valuable to prevent regressions.

Verdict

The contravariance issue with the deploymentId overloads (issue #1) is a blocker -- users with typed workflows won't be able to call start() with deploymentId at all, which is the entire purpose of the PR. The other items are minor but worth addressing.

pranaygpand others added 3 commits March 23, 2026 21:49
…ssue
Addresses PR review feedback: typed workflows like
WorkflowFunction<[string], number> were not assignable to
WorkflowFunction<unknown[], unknown> under strictFunctionTypes.
Changed to generic parameters while keeping Run<unknown> return type.
Also adds type-level tests for overload resolution.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…naygp-6fadd605
* 'main' of https://github.com/vercel/workflow: (73 commits)
chore: bump next to 16.2.1 and fix deferred build (#1496)
chore: bump nitropack to ^2.13.1 (#1501)
chore: bump nuxt ecosystem dependencies (#1500)
chore: bump sveltekit ecosystem (#1498)
chore: bump express and fastify in workbenches (#1499)
chore: bump @nestjs/* to ^11.1.17 (#1497)
chore: bump hono to ^4.12.8 (#1495)
Revert "Inline class serialization registration to fix 3rd-party package supp…" (#1493)
[world] Add stream pagination and metadata endpoints (#1470)
[cli] [world-local] Ensure update checks don't suggest upgrading from stable release to pre-releases (#1490)
Remove NestJS Vercel integration while in experimental phase (#1485)
feat: export semantic error types and add API reference docs (#1447)
feat: enforce max queue deliveries in handlers with graceful failure (#1344)
[world-postgres] Migrate client from `postgres.js` to `pg` (#1484)
Inline class serialization registration to fix 3rd-party package support (#1480)
[ai] Add experimental_context to DurableAgentOptions (#1489)
[ai] Expose configured tools on DurableAgent instances (#1488)
fix(builders): catch node builtin usage when entry fields diverge (#1455)
[web-shared] Fix timeline duration format and precision (#1482)
[cli] Add bulk cancel, --status filter, fix step JSON hydration (#1467)
...
# Conflicts:
#	packages/core/src/runtime/start.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@pranaygp

Copy link
Copy Markdown
ContributorAuthor

Thanks for the thorough review! Here's how each item was addressed:

Blocker (contravariance) — Fixed. The deploymentId overloads now use generic parameters (WorkflowFunction<TArgs, TResult>) instead of WorkflowFunction<unknown[], unknown>, so typed workflows are assignable. Return type stays Run<unknown> and args stay unknown[]. Added type-level tests to prevent regressions.

Issue 1 (string | undefined) — Acknowledged. The split types mean string | undefined no longer works, but since main has removed the @deprecated annotation on deploymentId, this is an acceptable trade-off.

Issue 2 (sub-types not re-exported) — These are internal implementation types; users interact via the StartOptions union which is already re-exported.

Issue 3 (changeset) — Added a changeset with an accurate description.

Issue 4 (type-level tests) — Added in the test file using expectTypeOf assertions.

Also merged latest main (73 commits) and resolved the conflict in start.ts (main updated the deploymentId JSDoc to remove @deprecated).

@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.

Review Summary

Clean, well-thought-out change. The overload design correctly handles the type-safety tradeoff when deploymentId is provided — making args unknown[] and return Run<unknown> is the right call since there's no guarantee the remote deployment's workflow function has the same signature.

What looks good:

  • Overload ordering is correct: deploymentId overloads first (with required deploymentId), then the type-preserving overloads. TypeScript resolves them in order so the right one always wins.
  • Using generics on the deploymentId overloads (<TArgs extends unknown[], TResult>) avoids contravariance issues — a WorkflowFunction<[string, number], boolean> is assignable without needing a cast.
  • The StartOptionsWithoutDeploymentId variant with deploymentId?: undefined is the correct discriminated union pattern.
  • Type-level tests with expectTypeOf are a great addition.
  • Implementation body is unchanged — this is purely a type-level refactor of the public API surface.
  • Changeset is appropriately scoped as patch.
  • Unrelated formatting changes (classify-error.test.ts, serialization.ts) are trivial Biome reformats.

Minor observations (non-blocking):

  • The new sub-types (StartOptionsBase, StartOptionsWithDeploymentId, StartOptionsWithoutDeploymentId) are exported from @workflow/core but not re-exported from the workflow package (api.ts / api-workflow.ts). Since StartOptions (the union) is already re-exported and is likely what most consumers use, this is fine — but worth noting if users later need to narrow to a specific variant.
  • There is a merge commit (77fd9ad3) for the inline class serialization change. The diff is clean so this does not cause issues, but consider rebasing to keep history linear if that is the repo convention.

LGTM — no blocking issues found.

@TooTallNate
TooTallNate merged commit 047c01b into mainApr 3, 2026
104 of 105 checks passed
@TooTallNate
TooTallNate deleted the v0/pranaygp-6fadd605 branch April 3, 2026 02:52
@ghostghost mentioned this pull request Apr 3, 2026
pranaygp added a commit that referenced this pull request Apr 3, 2026
…1-refresh
* origin/main: (21 commits)
Fix node-module-error plugin matching identifiers in multi-line comments (#1554)
fix(swc-plugin): use binding name for class expression method registrations (#1599)
fix(builders): override `sideEffects: false` for discovered workflow/step/serde entries (#1598)
[world-vercel] align header names to `x-vercel-workflow-*` convention (#1602)
[docs] Add vercel world consumer function security documentation (#1543)
Make `start()` types `unknown` when `deploymentId` is provided (#1367)
fix(next): stop force-setting WORKFLOW_PUBLIC_MANIFEST=1 during next dev (#1597)
Version Packages (beta) (#1593)
[docs] Tidy world API docs and document new stream helpers (#1581)
Rename 'Workflow Development Kit' / 'DevKit' to 'Workflow SDK' (#1595)
[world] Use zod/v4 in queue files to match @workflow/world schemas (#1588)
[ai] Fix fatal stream errors surfacing as [object Object] (#1589)
[web] Fix server crash on unmatched routes (#1590)
docs: rename 'Complex Example' to 'Instance Methods as Steps' (#1592)
Version Packages (beta) (#1563)
[core] Extend flow route duration to "max" and fail runs where replay takes too long (#1567)
fix: check target run capabilities before encrypting hook payloads (#1572)
[core] Combine initial run fetch, event fetch, and run_started event creation (#1569)
[docs] Split World API docs into sub-pages, update skill.md (#1457)
[nitro] Preserve workflow step registration side effects (#1386)
...
# Conflicts:
#	skills/workflow/SKILL.md
@ghostghost mentioned this pull request Apr 7, 2026
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.

4 participants

@pranaygp@karthikscale3@TooTallNate