Repository navigation
[core] Upgrade devalue to 6.0.3 - #4856
Conversation
Picks up the native-JSON.stringify string escaping (sveltejs/devalue#217): 2-3x faster on multi-MB strings with many quotes, byte-identical output. 6.0.0's breaking changes are the uneval replacer API, which core does not use, and a Node floor that 6.0.3 lowered back to >=18. Co-Authored-By: Samarth Chitgopekar <67826352+http-samc@users.noreply.github.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
🧪 E2E Test Results✅ All tests passed
|
| Passed | Failed | Skipped | Total | |
|---|---|---|---|---|
| ✅ ▲ Vercel Production | 3982 | 0 | 878 | 4860 |
| ✅ 💻 Local Development | 4669 | 0 | 551 | 5220 |
| ✅ 📦 Local Production | 4669 | 0 | 551 | 5220 |
| ✅ 🐘 Local Postgres | 4669 | 0 | 551 | 5220 |
| ✅ 🪟 Windows | 348 | 0 | 12 | 360 |
| ✅ 🌐 Cross-language Conformance | 68 | 0 | 87 | 155 |
| ✅ dynamic-runs | 0 | 0 | 0 | 0 |
| ✅ vercel-http-transport | 897 | 0 | 183 | 1080 |
| ✅ vercel-multi-region | 27 | 0 | 0 | 27 |
| ✅ vercel-ws-transport | 607 | 0 | 113 | 720 |
| Total | 19936 | 0 | 2926 | 22862 |
Details by Category
✅ ▲ Vercel Production
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ astro-node | 145 | 0 | 35 |
| ✅ astro-quickjs | 145 | 0 | 35 |
| ✅ example-node | 145 | 0 | 35 |
| ✅ example-quickjs | 145 | 0 | 35 |
| ✅ express-node | 145 | 0 | 35 |
| ✅ express-quickjs | 145 | 0 | 35 |
| ✅ fastify-node | 145 | 0 | 35 |
| ✅ fastify-quickjs | 145 | 0 | 35 |
| ✅ hono-node | 145 | 0 | 35 |
| ✅ hono-quickjs | 145 | 0 | 35 |
| ✅ nest-node | 145 | 0 | 35 |
| ✅ nest-quickjs | 145 | 0 | 35 |
| ✅ nextjs-turbopack-node | 172 | 0 | 8 |
| ✅ nextjs-turbopack-quickjs | 172 | 0 | 8 |
| ✅ nextjs-webpack-node | 172 | 0 | 8 |
| ✅ nextjs-webpack-quickjs | 172 | 0 | 8 |
| ✅ nitro-node | 145 | 0 | 35 |
| ✅ nitro-quickjs | 145 | 0 | 35 |
| ✅ nuxt-node | 145 | 0 | 35 |
| ✅ nuxt-quickjs | 145 | 0 | 35 |
| ✅ python-node | 66 | 0 | 114 |
| ✅ sveltekit-node | 164 | 0 | 16 |
| ✅ sveltekit-quickjs | 164 | 0 | 16 |
| ✅ tanstack-start-node | 145 | 0 | 35 |
| ✅ tanstack-start-quickjs | 145 | 0 | 35 |
| ✅ vite-node | 145 | 0 | 35 |
| ✅ vite-quickjs | 145 | 0 | 35 |
✅ 💻 Local Development
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ astro-stable-node | 151 | 0 | 29 |
| ✅ astro-stable-quickjs | 151 | 0 | 29 |
| ✅ express-stable-node | 151 | 0 | 29 |
| ✅ express-stable-quickjs | 151 | 0 | 29 |
| ✅ fastify-stable-node | 151 | 0 | 29 |
| ✅ fastify-stable-quickjs | 151 | 0 | 29 |
| ✅ hono-stable-node | 151 | 0 | 29 |
| ✅ hono-stable-quickjs | 151 | 0 | 29 |
| ✅ nest-stable-node | 151 | 0 | 29 |
| ✅ nest-stable-quickjs | 151 | 0 | 29 |
| ✅ nextjs-turbopack-canary-node | 179 | 0 | 1 |
| ✅ nextjs-turbopack-canary-quickjs | 179 | 0 | 1 |
| ✅ nextjs-turbopack-quickjs-snapshot | 179 | 0 | 1 |
| ✅ nextjs-turbopack-stable-node | 179 | 0 | 1 |
| ✅ nextjs-turbopack-stable-quickjs | 179 | 0 | 1 |
| ✅ nextjs-webpack-canary-node | 179 | 0 | 1 |
| ✅ nextjs-webpack-canary-quickjs | 179 | 0 | 1 |
| ✅ nextjs-webpack-stable-node | 179 | 0 | 1 |
| ✅ nextjs-webpack-stable-quickjs | 179 | 0 | 1 |
| ✅ nitro-stable-node | 151 | 0 | 29 |
| ✅ nitro-stable-quickjs | 151 | 0 | 29 |
| ✅ nuxt-stable-node | 151 | 0 | 29 |
| ✅ nuxt-stable-quickjs | 151 | 0 | 29 |
| ✅ sveltekit-stable-node | 170 | 0 | 10 |
| ✅ sveltekit-stable-quickjs | 170 | 0 | 10 |
| ✅ tanstack-start-node | 151 | 0 | 29 |
| ✅ tanstack-start-quickjs | 151 | 0 | 29 |
| ✅ vite-stable-node | 151 | 0 | 29 |
| ✅ vite-stable-quickjs | 151 | 0 | 29 |
✅ 📦 Local Production
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ astro-stable-node | 151 | 0 | 29 |
| ✅ astro-stable-quickjs | 151 | 0 | 29 |
| ✅ express-stable-node | 151 | 0 | 29 |
| ✅ express-stable-quickjs | 151 | 0 | 29 |
| ✅ fastify-stable-node | 151 | 0 | 29 |
| ✅ fastify-stable-quickjs | 151 | 0 | 29 |
| ✅ hono-stable-node | 151 | 0 | 29 |
| ✅ hono-stable-quickjs | 151 | 0 | 29 |
| ✅ nest-stable-node | 151 | 0 | 29 |
| ✅ nest-stable-quickjs | 151 | 0 | 29 |
| ✅ nextjs-turbopack-canary-node | 179 | 0 | 1 |
| ✅ nextjs-turbopack-canary-quickjs | 179 | 0 | 1 |
| ✅ nextjs-turbopack-quickjs-snapshot | 179 | 0 | 1 |
| ✅ nextjs-turbopack-stable-node | 179 | 0 | 1 |
| ✅ nextjs-turbopack-stable-quickjs | 179 | 0 | 1 |
| ✅ nextjs-webpack-canary-node | 179 | 0 | 1 |
| ✅ nextjs-webpack-canary-quickjs | 179 | 0 | 1 |
| ✅ nextjs-webpack-stable-node | 179 | 0 | 1 |
| ✅ nextjs-webpack-stable-quickjs | 179 | 0 | 1 |
| ✅ nitro-stable-node | 151 | 0 | 29 |
| ✅ nitro-stable-quickjs | 151 | 0 | 29 |
| ✅ nuxt-stable-node | 151 | 0 | 29 |
| ✅ nuxt-stable-quickjs | 151 | 0 | 29 |
| ✅ sveltekit-stable-node | 170 | 0 | 10 |
| ✅ sveltekit-stable-quickjs | 170 | 0 | 10 |
| ✅ tanstack-start-node | 151 | 0 | 29 |
| ✅ tanstack-start-quickjs | 151 | 0 | 29 |
| ✅ vite-stable-node | 151 | 0 | 29 |
| ✅ vite-stable-quickjs | 151 | 0 | 29 |
✅ 🐘 Local Postgres
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ astro-stable-node | 151 | 0 | 29 |
| ✅ astro-stable-quickjs | 151 | 0 | 29 |
| ✅ express-stable-node | 151 | 0 | 29 |
| ✅ express-stable-quickjs | 151 | 0 | 29 |
| ✅ fastify-stable-node | 151 | 0 | 29 |
| ✅ fastify-stable-quickjs | 151 | 0 | 29 |
| ✅ hono-stable-node | 151 | 0 | 29 |
| ✅ hono-stable-quickjs | 151 | 0 | 29 |
| ✅ nest-stable-node | 151 | 0 | 29 |
| ✅ nest-stable-quickjs | 151 | 0 | 29 |
| ✅ nextjs-turbopack-canary-node | 179 | 0 | 1 |
| ✅ nextjs-turbopack-canary-quickjs | 179 | 0 | 1 |
| ✅ nextjs-turbopack-quickjs-snapshot | 179 | 0 | 1 |
| ✅ nextjs-turbopack-stable-node | 179 | 0 | 1 |
| ✅ nextjs-turbopack-stable-quickjs | 179 | 0 | 1 |
| ✅ nextjs-webpack-canary-node | 179 | 0 | 1 |
| ✅ nextjs-webpack-canary-quickjs | 179 | 0 | 1 |
| ✅ nextjs-webpack-stable-node | 179 | 0 | 1 |
| ✅ nextjs-webpack-stable-quickjs | 179 | 0 | 1 |
| ✅ nitro-stable-node | 151 | 0 | 29 |
| ✅ nitro-stable-quickjs | 151 | 0 | 29 |
| ✅ nuxt-stable-node | 151 | 0 | 29 |
| ✅ nuxt-stable-quickjs | 151 | 0 | 29 |
| ✅ sveltekit-stable-node | 170 | 0 | 10 |
| ✅ sveltekit-stable-quickjs | 170 | 0 | 10 |
| ✅ tanstack-start-node | 151 | 0 | 29 |
| ✅ tanstack-start-quickjs | 151 | 0 | 29 |
| ✅ vite-stable-node | 151 | 0 | 29 |
| ✅ vite-stable-quickjs | 151 | 0 | 29 |
✅ 🪟 Windows
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ nextjs-turbopack-node | 174 | 0 | 6 |
| ✅ nextjs-turbopack-quickjs | 174 | 0 | 6 |
✅ 🌐 Cross-language Conformance
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ python | 68 | 0 | 87 |
✅ dynamic-runs
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ astro-vercel | 0 | 0 | 0 |
| ✅ example-vercel | 0 | 0 | 0 |
| ✅ express-vercel | 0 | 0 | 0 |
| ✅ fastify-vercel | 0 | 0 | 0 |
| ✅ hono-vercel | 0 | 0 | 0 |
| ✅ nest-vercel | 0 | 0 | 0 |
| ✅ nextjs-turbopack-vercel | 0 | 0 | 0 |
| ✅ nextjs-webpack-vercel | 0 | 0 | 0 |
| ✅ nitro-vercel | 0 | 0 | 0 |
| ✅ nuxt-vercel | 0 | 0 | 0 |
| ✅ sveltekit-vercel | 0 | 0 | 0 |
| ✅ tanstack-start-vercel | 0 | 0 | 0 |
| ✅ vite-vercel | 0 | 0 | 0 |
✅ vercel-http-transport
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ example | 145 | 0 | 35 |
| ✅ express | 145 | 0 | 35 |
| ✅ hono | 145 | 0 | 35 |
| ✅ nextjs-turbopack | 172 | 0 | 8 |
| ✅ nitro | 145 | 0 | 35 |
| ✅ vite | 145 | 0 | 35 |
✅ vercel-multi-region
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ nextjs-turbopack | 27 | 0 | 0 |
✅ vercel-ws-transport
| App | Passed | Failed | Skipped |
|---|---|---|---|
| ✅ example | 145 | 0 | 35 |
| ✅ express | 145 | 0 | 35 |
| ✅ nextjs-turbopack | 172 | 0 | 8 |
| ✅ vite | 145 | 0 | 35 |
📊 Workflow Benchmarkscommit Backend:
📈 STSO distribution vs main (inline / queue-hop histograms)1020 steps (inline) Cumulative STSO time: main 97617ms → this run 124725ms (Δ +27108ms, +28%) 📜 Previous results (1)94f7bb6Tue, 13 Oct 2026 17:32:24 GMT · run logs
ℹ️ Metric definitions & methodologyThe collapsed STSO distribution section above buckets every step gap, split inline (same warm process — pure framework overhead) vs queue-hop (fresh process — dispatch, reinit, replay). Best/P75/P90/P99 deltas compare against the most recent benchmark run on Metrics — TTFS: time to first step body (in-deployment start() → first step body) · Fan-out TTFS: fan-out time to first step (in-deployment start() → first of the parallel step bodies to complete) · Fan-out TTLS: fan-out time to last step (in-deployment start() → last of the parallel step bodies to complete, i.e. when the Promise.all resolves) · STSO: step-to-step overhead (gap between consecutive step bodies) · WO: workflow overhead (whole-run time outside step bodies, in-deployment anchored) Scenarios — step: one trivial no-op step, no stream; no hooks, so the run stays in turbo mode (in-process fast path) · stream: one streaming step; no hooks, so the run stays in turbo mode (in-process fast path) · hook + stream: registers a hook before one step, which exits turbo mode (dispatch path) · 1020 steps: 1020 trivial sequential steps; STSO is measured between consecutive steps in the given step ranges, and WO is the whole-run overhead outside step bodies · Promise.all(100 steps): 100 trivial no-op steps started together in a single Promise.all; Fan-out TTFS is the first of them to complete and Fan-out TTLS the last, both from the in-deployment clientStart, so their gap is the spread the runtime adds across the fan-out 🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 All timestamps are deployment-side; runs are triggered in-deployment, so the CI runner and api.vercel.com sit outside every measured window. TTFS = Cold starts stay in the numbers (real bursty-workload latency, inflates P75+); Best is the warm floor. |
Sim WorldSimulated world deterministic testing for races. Traces 🟢 world-sim scenario book — 0 fail of 42 total
Full trace: |
About these numbersSizes are gzip; parentheses show the change against
|
VaguelySerious
left a comment
There was a problem hiding this comment.
(AI) Preliminary review: AI review: blocking issues found
| '@opentelemetry/api': 1.9.1 | ||
| rfc6902: 5.1.2 | ||
| devalue: 5.9.2 | ||
| devalue: 6.0.3 |
There was a problem hiding this comment.
AI Review: Blocking
This is a workspace-wide overrides entry, not a catalog pin, so it forces devalue 6 onto every in-repo consumer, not only @workflow/core. @sveltejs/kit, svelte, astro, and nuxt all declare devalue: ^5.8.1, and kit, svelte, and astro call uneval(value, replacer) with the 5.x contract (the replacer returns a JS string, or uses its second argument as a nested uneval). On 6.x a string result throws TypeError: Invalid JavaScript fragment, and the second argument is now the js tag, so calling it as a function throws.
I reproduced this in workbench/sveltekit with a throwaway route whose +page.server.ts load returns { now: 'sync', later: new Promise(...) } (streamed data), then ran pnpm build and node build:
- PR branch (devalue 6.0.3): the server logs
[500] GET /zz-devalue-streamandTypeError: Cannot read properties of undefined (reading 'slice')fromadd_node. That's kit's error handler reading.pathoff the non-DevalueError. The page renders the 500 page. - main (devalue 5.9.2): 200, and both the sync value and the streamed value render.
The same code path is hit by transport hooks, Svelte's hydratable, and Astro content collections with URL or asset data. The current workbench pages don't use any of these, so CI probably stays green, but the next test or page that does will fail with a confusing error.
Scoping the bump fixes it. I verified both options keep core on 6.0.3 and kit on 5.9.2, and with the first one the streaming route returns 200 again:
'devalue@^5': 5.9.2or
devalue: 5.9.2
'@workflow/core>devalue': 6.0.3This only affects the repo's own install. Published @workflow/core resolves its own exact devalue@6.0.3 next to a user's framework copy, so apps aren't affected.
There was a problem hiding this comment.
AI Review: Note
Fixed in ad9ae7c: the override is now 'devalue@^5': 5.9.2, so SvelteKit, Svelte, Astro and Nuxt stay on 5.9.2 and @workflow/core resolves its own 6.0.3 pin. I rebuilt workbench/sveltekit with the streamed-load route and it returns 200 again, with both values rendered.
| '@workflow/core': patch | ||
| --- | ||
|
|
||
| Upgrade devalue to 6.0.3, which escapes large strings with native `JSON.stringify` instead of a per-character loop. |
There was a problem hiding this comment.
AI Review: Note
There's one behavior change users can observe, and it might be worth mentioning in the changeset. A lone surrogate in a serialized value (for example 'a\ud800b' as a start() argument) used to come back as a\uFFFDb after the UTF-8 round trip. On 6.0.3 it round-trips exactly. I checked this with dehydrateWorkflowArguments → hydrateWorkflowArguments from dist under each devalue version.
Cross-version compat looks fine. Payloads written with 5.9.2 (plain, Map/Set/Date/BigInt/RegExp/URL/-0/NaN, typed arrays/Buffer, Error, sparse and cyclic arrays, a 4.8 MB escape-heavy string) are byte-identical under 6.0.3 and hydrate on both versions in both directions. Old payloads that contain U+FFFD are already lossy and stay that way. A small unit test that pins the lone-surrogate round trip would guard the new behavior.
There was a problem hiding this comment.
AI Review: Note
Fixed in ad9ae7c: the changeset now mentions the unpaired-surrogate change, and serialization.test.ts has a round-trip regression test. The test fails on 5.9.2 (a\ufffdb) and passes on 6.0.3.
The workspace-wide `devalue: 6.0.3` override forced devalue 6 onto SvelteKit, Svelte, Astro and Nuxt, which depend on `devalue: ^5` and call `uneval` with string-returning replacers that devalue 6 rejects (e.g. SvelteKit streamed `load` data returned a 500). Narrow the override to `devalue@^5` so frameworks stay on 5.9.2 while core keeps its own 6.0.3 pin. Also note the unpaired-surrogate round-trip change in the changeset and add a regression test for it. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Peter Wielander <29887157+VaguelySerious@users.noreply.github.com>
🦋 Changeset detectedLatest commit: ad9ae7c The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 packages
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 |
VaguelySerious
left a comment
There was a problem hiding this comment.
AI review: no blocking issues
|
No backport to This is a major-version dependency bump (devalue 5.9.2 → 6.0.3) whose stated motivation is performance — faster string escaping in To override, re-run the Backport to stable workflow manually via |
Catches the docs up with SDK changes that shipped in the last day without (complete) docs updates. | Change | Source PR | Docs edit | | --- | --- | --- | | QuickJS runs with snapshotting are exempt from the default per-run event limit on the Local and Vercel Worlds, and `MAX_EVENTS_EXCEEDED` / `REPLAY_TIMEOUT` now have their own error pages | #4870 | The error-code table in Errors and retries, the log-growth note in Event sourcing, and the event-limit row in What's new now mention the QuickJS exemption and link to the new error pages. #4870 updated the errors index, Runtime tuning, Worlds, and Vercel World pages but not these | I reviewed the other PRs from the last day and they need no further docs: removing the experimental resilient step dispatch (#4818) updated its own docs and the variable no longer appears outside changelogs, the `setAttributes()` delivery-order fix (#4861) and terminal-inert replay (#4819) are bug fixes, reading step creation from `step_started` (#4866) is reader-side forward compatibility with no user-facing change yet, and the devalue upgrade (#4856), dependency bumps, and Version Packages PRs aren't documented behavior. The Workflows pricing and limits page in `vercel/front` gets the matching event-limit note in vercel/front#91076. ## Docs Preview | Page | Preview | | --- | --- | | Errors and retries | [Error codes](https://workflow-docs-git-workflow-docs-cleanup-wed-14-oct.vercel.sh/docs/foundations/errors-and-retries) | | Event sourcing | [How fast a log grows](https://workflow-docs-git-workflow-docs-cleanup-wed-14-oct.vercel.sh/docs/how-it-works/event-sourcing#how-fast-a-log-grows) | | What's new | [Breaking changes: Application code](https://workflow-docs-git-workflow-docs-cleanup-wed-14-oct.vercel.sh/docs/whats-new#application-code) | 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>
Bumps
devalue5.9.2 → 6.0.3 (catalog pin inpnpm-workspace.yaml+packages/core).Why
6.0.3 ships sveltejs/devalue#217:
stringifyescapes strings via nativeJSON.stringifyplus one regex pass instead of a per-character JS loop. Output is byte-identical; on JSON-shaped strings it is 2–3× faster (7 MB: 84 → 37 ms, 35 MB: 514 → 176 ms, measured locally). This is the dominant cost instart()argument serialization for large string inputs, which the/flowhost pays on the caller's event loop.Also picked up from 6.0.x: the no-escape fast path and quoted-key cache (6.0.1), unpaired-surrogate escaping so output survives UTF-8 transport (6.0.0), and the
.js-extension type-import fix fornodenext(6.0.3).6.0.0 breaking changes, checked
unevalreplacer API changed to a tagged template —@workflow/coredoes not useuneval.>=18in 6.0.3; this repo's engines are^22 || ^24either way.quickjs-serde.tsoperates on handles; there is no in-VM copy), so the QuickJS engine is unaffected.Verification
Full
@workflow/coreunit suite on 6.0.3: 144 files, 3089 passed, 6 expected-fail (pre-existing), 1 skipped.tsc --noEmitclean. Lockfile diff is devalue-only.Changeset:
@workflow/corepatch.