Skip to content

feat(devframe): jsonSerializable declaration for RPC + per-call wire dispatch#301

Merged
antfu merged 8 commits intomainfrom
antfu/rpc-json-serializable
May 1, 2026
Merged

feat(devframe): jsonSerializable declaration for RPC + per-call wire dispatch#301
antfu merged 8 commits intomainfrom
antfu/rpc-json-serializable

Conversation

@antfu
Copy link
Copy Markdown
Member

@antfu antfu commented May 1, 2026

Description

Adds an opt-in jsonSerializable: boolean field on RpcFunctionDefinition that declares the on-wire / on-disk shape contract:

  • false (default) — payloads use structured-clone-es (round-trips Map, Set, Date, BigInt, cycles, class instances). Status quo behavior.
  • true (opt-in) — payloads use a strict single-pass JSON.stringify that throws DF0019 synchronously at the offending call when it sees a value JSON cannot round-trip. Errors surface in dev right next to the bad return value, not silently coerced to {} later at build time.

The WS transport dispatches per-call: outgoing requests choose the encoder from local defs (msg.m); responses use a per-channel pendingRequestMethods map (populated on incoming request) so each direction independently consults its own defs — no birpc change required. The wire is plain JSON when JSON-flagged, structured-clone with an s: prefix otherwise (decode is self-describing via the prefix). Build dumps mirror the same per-function dispatch and tag each manifest entry with serialization: 'json' | 'structured-clone'.

agent: {...} now requires jsonSerializable: true — registration throws DF0018 otherwise. Aligns the MCP contract (JSON-only consumers) with the wire/dump contract.

Linked Issues

Additional context

  • Two new diagnostics: DF0018 (registration: agent without jsonSerializable: true) and DF0019 (runtime: JSON-flagged value contains a non-JSON shape). Both with docs pages + sidebar bump.
  • ConnectionMeta gains jsonSerializableMethods: string[] so the browser-side WS client builds its defs map from the existing .connection.json fetch — no extra request, no birpc patch.
  • New tests cover strictJsonStringify rejection cases, the prefix dispatcher, registration gating, and structured-clone round-trip through the static client. Existing host-agent / static-dump / example tests updated where the new fields are observable.
  • Lint, typecheck, build, and 412/412 tests pass.

🤖 Generated with Claude Code

Adds an opt-in `jsonSerializable: boolean` field to RPC function
definitions. When true, the WS transport encodes the function's
messages as plain JSON via a strict single-pass `JSON.stringify` that
throws DF0019 synchronously on Map/Set/Date/BigInt/class instances/
undefined-in-array. When false (default) the function uses
structured-clone-es with an `s:` wire prefix, preserving fancy types.

The wire dispatcher reads the prefix on decode (no defs lookup
needed) and consults local defs on encode, so request and response
can independently choose their encoder per channel. Build dumps tag
each manifest entry with `serialization: 'json' | 'structured-clone'`
and dispatch the matching encoder/decoder; static client revives
sc-tagged entries via `scDeserialize`.

Agent exposure now requires `jsonSerializable: true` — registration
throws DF0018 if `agent` is set without it. Aligns the MCP contract
with the wire contract: only JSON-serializable functions are
advertised to coding agents.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 1, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@vitejs/devtools@301
npm i https://pkg.pr.new/@vitejs/devtools-kit@301
npm i https://pkg.pr.new/@vitejs/devtools-rolldown@301
npm i https://pkg.pr.new/@vitejs/devtools-self-inspect@301

commit: 3f125ad

antfu and others added 7 commits May 1, 2026 17:06
Adds a `structured-clone dumps` group to `collectStaticRpcDump` tests:

- Verifies Map/Set values survive in `files[].data` for default
  (structured-clone) functions.
- Round-trips a Map through scStringify → JSON.parse → scDeserialize
  to confirm the build-write / client-read pipeline is lossless.
- Covers query-mode records and fallback shards both encode as
  structured-clone when default.
- Confirms `jsonSerializable: true` produces plain JSON shards that
  parse losslessly.
- Asserts DF0019 fires at build time when a JSON-flagged handler
  returns non-JSON data.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renames the structured-clone-es re-exports for clarity:
- scStringify → structuredCloneStringify
- scParse → structuredCloneParse
- scDeserialize → structuredCloneDeserialize

Migrates the leftover DTK0001-DTK0008 codes in `rpc/diagnostics.ts`
to DF0020-DF0027. The DTK prefix was misleading — these emit from
the devframe package, not @vitejs/devtools, and they shadowed the
docs URL space of the genuinely-DTK codes in `packages/core`. Moving
them to DF lets `rpc/diagnostics.ts` use a single `defineDiagnostics`
block with the correct devframe docsBase, dropping the previous
two-block split.

Adds DF0020-DF0027 docs pages with their DTK migration mapping;
updates `errors/index.md` and the sidebar count.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes `makePerCallChannelOptions` from `rpc/serialization.ts` and
inlines the wire dispatch logic directly into `ws-server.ts` and
`ws-client.ts`. The helper had only two callers and the abstraction
wasn't pulling its weight — a single function name shouldn't need a
factory builder.

`STRUCTURED_CLONE_PREFIX`, `strictJsonStringify`, and the
`structuredClone*` re-exports remain — they're the actual reusable
primitives. The unit tests targeting `makePerCallChannelOptions`
are dropped; the dispatch behavior is now exercised end-to-end via
the existing static-rpc and static-dump tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves a numbering conflict: main's #302 introduced DF0018 for the
`ctx.logs` deprecation warning, so my jsonSerializable diagnostics
shift up by one. New numbering on this branch:

- DF0018 (main) — `ctx.logs` deprecated.
- DF0019 — Agent Requires JSON-Serializable RPC.
- DF0020 — Non-JSON Value in JSON-Serializable RPC.
- DF0021..DF0028 — migrated from DTK0001..DTK0008.

Updates code, callsites, docs pages, sidebar count (28), error
index table, and skill / guide references to the new numbers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Turbo had cached a pre-merge kit build, so the previous `-u` ran
against stale dist and lost the new exports introduced upstream
(`DevToolsDiagnosticsHost`, `DevToolsMessage*`, etc.). CI built
fresh and tripped on the mismatch. Forced rebuild + snapshot
update brings the kit snapshots in line with the merged code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- devframe: tinyexec 1.1.1 → 1.1.2
- core: @clack/core + @clack/prompts 1.2.0 → 1.3.0;
  fast-string-truncated-width 1.2.1 → 3.0.3;
  fast-string-width 1.1.0 → 3.0.2;
  fast-wrap-ansi 0.1.6 → 0.2.0

Picked up automatically from the workspace catalog ranges.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-export `serialize` from structured-clone-es alongside the existing
`structuredCloneDeserialize` / `structuredCloneParse` /
`structuredCloneStringify` so callers can build the tagged
intermediate representation without going through JSON text — useful
when piping into another transport or storage that already accepts
plain JSON values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@antfu antfu merged commit df0195b into main May 1, 2026
9 checks passed
@antfu antfu deleted the antfu/rpc-json-serializable branch May 1, 2026 09:49
Sign up for free to 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.

1 participant