QuickJS engine: host-side, side-effect-free serialization via handles - #3263
QuickJS engine: host-side, side-effect-free serialization via handles#3263TooTallNate wants to merge 1 commit into
Conversation
🦋 Changeset detectedLatest commit: cecd664 The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 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 |
🧪 E2E Test Results
⏳ Tests are running... _Started at: _ ❌ Some tests failed ❌ Failed E2E Tests▲ Vercel Production (26 failed)astro-quickjs (3 failed):
example-quickjs (3 failed):
express-quickjs (2 failed):
fastify-quickjs (2 failed):
hono-quickjs (2 failed):
nextjs-turbopack-quickjs (2 failed):
nextjs-webpack-quickjs (3 failed):
nitro-quickjs (2 failed):
nuxt-quickjs (2 failed):
sveltekit-quickjs (3 failed):
vite-quickjs (2 failed):
💻 Local Development (22 failed)astro-stable-quickjs (2 failed):
express-stable-quickjs (2 failed):
fastify-stable-quickjs (2 failed):
hono-stable-quickjs (2 failed):
nextjs-turbopack-canary-quickjs (2 failed):
nextjs-turbopack-stable-quickjs (2 failed):
nextjs-webpack-stable-quickjs (2 failed):
nitro-stable-quickjs (2 failed):
nuxt-stable-quickjs (2 failed):
sveltekit-stable-quickjs (2 failed):
vite-stable-quickjs (2 failed):
📦 Local Production (26 failed)astro-stable-quickjs (2 failed):
express-stable-quickjs (2 failed):
fastify-stable-quickjs (2 failed):
hono-stable-quickjs (2 failed):
nextjs-turbopack-canary-quickjs (2 failed):
nextjs-turbopack-stable-node (1 failed):
nextjs-turbopack-stable-quickjs (2 failed):
nextjs-webpack-canary-quickjs (2 failed):
nextjs-webpack-stable-node (1 failed):
nextjs-webpack-stable-quickjs (2 failed):
nitro-stable-quickjs (2 failed):
nuxt-stable-quickjs (2 failed):
sveltekit-stable-quickjs (2 failed):
vite-stable-quickjs (2 failed):
🐘 Local Postgres (22 failed)astro-stable-quickjs (2 failed):
express-stable-quickjs (2 failed):
fastify-stable-quickjs (2 failed):
hono-stable-quickjs (2 failed):
nextjs-turbopack-canary-quickjs (2 failed):
nextjs-webpack-canary-quickjs (2 failed):
nextjs-webpack-stable-quickjs (2 failed):
nitro-stable-quickjs (2 failed):
nuxt-stable-quickjs (2 failed):
sveltekit-stable-quickjs (2 failed):
vite-stable-quickjs (2 failed):
🪟 Windows (2 failed)nextjs-turbopack-quickjs (2 failed):
📋 Other (16 failed)e2e-local-dev-nest-stable-quickjs (2 failed):
e2e-local-dev-tanstack-start-quickjs (2 failed):
e2e-local-postgres-nest-stable-quickjs (2 failed):
e2e-local-postgres-tanstack-start-quickjs (2 failed):
e2e-local-prod-nest-stable-quickjs (2 failed):
e2e-local-prod-tanstack-start-quickjs (2 failed):
e2e-vercel-prod-nest-quickjs (2 failed):
e2e-vercel-prod-tanstack-start-quickjs (2 failed):
E2E Test SummarySummary
Details by Category❌ ▲ Vercel Production
❌ 💻 Local Development
❌ 📦 Local Production
❌ 🐘 Local Postgres
❌ 🪟 Windows
❌ 📋 Other
✅ vercel-multi-region
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
b9dd118 to
40773d2
Compare
a682051 to
6c12c68
Compare
(Re-applied onto the review-fixed base; original commits da27230 + 9814ed9 squashed.) Replace the in-VM serde bundle with a host-side codec (runtime/quickjs-serde.ts) built on quickjs-wasi 3.3's introspection primitives and devalue 5.9's pluggable stringify/parse operations — mirroring the node:vm engine's architecture. Review fixes incorporated: - reducer/reviver key sets are pinned against codec-devalue-vm's workflow mode by exhaustiveness tests (exact order for reducers — first match wins), so the handle-space codec can't silently drift from the shared value-space sets. - the devalue entry in minimumReleaseAgeExclude is removed: the exact version is pinned via the workspace catalog + lockfile, so the cooldown waiver was unnecessary (verified with both frozen and regular installs). - eval-string interpolation inherits the JSON.stringify(cid) hardening from the base branch.
Summary
Moves the QuickJS engine's serialization entirely to the host, operating on
JSValueHandles — the serde bundle previously bundled by esbuild and evaluated inside the VM is gone. This mirrors the node:vm engine's architecture (the serializer is host code reaching into the sandbox realm) and is the QuickJS counterpart to #3257's side-effect-free serialization for node:vm.Built on:
classIdbrand checks,identity, descriptor reads,vm.construct, ephemeral functions; see feat: trap-free introspection primitives (isProxy, brand checks, descriptor reads) vercel-labs/quickjs-wasi#24/test: POC for host-side devalue serialization of guest values vercel-labs/quickjs-wasi#26)operationsfor stringify/parse (feat: pluggable operations for stringify sveltejs/devalue#172, feat: pluggable operations for parse sveltejs/devalue#173)Architecture
runtime/quickjs-serde.tsimplements the workflow wire codec over handles:JSValueHandleand falls back todefaultStringifyOperationsfor host values. Parse operations are handle-only — every revived value is built inside the VM through boot-captured constructors.classIdmap,isError,isProxy) — neverinstanceoforSymbol.toStringTag; extraction through boot-captured intrinsics invoked with explicit receivers; property access through descriptors. Patched prototypes and spoofed brands can no longer perturb serialization (aSymbol.toStringTag: 'Date'spoof serializes as the plain object it is — the previous codec crashed on that input). The only guest code executed is what the contract always executed:WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE,__closureVarsFn,WORKFLOW_USE_STEPon revival.Math.random, so the interleaved draw sequence — and every generated ID — is identical to the in-VM factory's. Existing runs replay byte-for-byte.Wire-format parity
Event logs persist across SDK versions, so parity with the previous in-VM codec is load-bearing (old runs must replay; node-engine steps must read VM-serialized inputs). The old codec's value-space implementation is retained as
serialization/workflow-vm.ts(host reference codec) and a new parity suite byte-compares against it in both directions: 42 tests covering primitives/bigints/-0/NaN, containers, typed arrays/views/buffers, the full Error family with cause chains, shared refs + cycles, null-proto objects, boxed primitives, step-function proxies (closure vars + bound this), workflow refs, symbol-stamped stream handles, registry class instances, and patched-prototype/spoof resistance.Removed
scripts/build-vm-serde-bundle.js, the generatedvm-serde-bundle.generated.ts,serialization/vm-bundle-entry.ts, and the bundle eval in VM init.Testing
@workflow/coresuite: 1757 passed | 3 expected failWORKFLOW_VM=quickjs, dev server): hooks 26✓, AbortController 26✓, streams 7✓, retries/errors 7✓, instance methods ✓, recursivestart()(fibonacci) ✓Follow-up candidates