You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(rpc): accept any Standard Schema for RPC args/returns and validate at runtime
Widen `args`/`returns` from valibot's `GenericSchema` to the
`StandardSchemaV1` interface so valibot, zod, arktype, and any other
Standard Schema validator work interchangeably, and infer handler
types from the schema's Standard Schema input types.
Every invocation path (local, over-the-wire, agent/MCP) now validates
declared `args`/`returns` at the boundary via `getRpcHandler`, throwing
coded diagnostics DF0043 / DF0044 on failure. Validation is guard-only:
payloads are never rewritten, so schemas describing a subset of an
object don't strip the sender's extra fields.
The MCP JSON-schema surface keeps valibot's converter for precise
schemas and degrades non-valibot vendors to a permissive object schema.
> RPC function "`{name}`" received an invalid argument at position `{index}`: `{issues}`
10
+
11
+
## Cause
12
+
13
+
When an RPC function declares `args` schemas, each incoming argument is validated against its positional [Standard Schema](https://standardschema.dev/) (valibot, zod, arktype, …) before the handler runs — on every path: local calls, over-the-wire calls, and the agent/MCP bridge. The argument at `{index}` failed that schema. Validation guards the payload without rewriting it, so extra object fields the schema doesn't mention still reach the handler.
14
+
15
+
## Example
16
+
17
+
```ts
18
+
const greet =defineRpcFunction({
19
+
name: 'greet',
20
+
args: [v.string()],
21
+
returns: v.string(),
22
+
handler: name=>`hi ${name}`,
23
+
})
24
+
25
+
// ✓ Good
26
+
awaitctx.rpc.functions.greet('ada')
27
+
28
+
// ✗ Bad — a number where a string is required → DF0043 at position 0
29
+
awaitctx.rpc.functions.greet(42asnever)
30
+
```
31
+
32
+
## Fix
33
+
34
+
Pass a value that satisfies the `args` schema declared for the function, or widen the schema if the value is legitimately allowed.
35
+
36
+
## Source
37
+
38
+
-[`packages/devframe/src/rpc/validate-io.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/rpc/validate-io.ts) — `validateRpcArgs()` throws `DF0043` on the first argument that fails its declared schema.
> RPC function "`{name}`" returned a value that failed its `returns` schema: `{issues}`
10
+
11
+
## Cause
12
+
13
+
When an RPC function declares a `returns` schema, the handler's resolved value is validated against that [Standard Schema](https://standardschema.dev/) (valibot, zod, arktype, …) before it is sent back to the caller. The value the handler produced does not satisfy the schema — a bug in the handler or a schema that is narrower than the real result. Validation guards the payload without rewriting it, so a value that merely carries extra object fields is accepted.
14
+
15
+
## Example
16
+
17
+
```ts
18
+
const count =defineRpcFunction({
19
+
name: 'count',
20
+
args: [],
21
+
returns: v.number(),
22
+
// ✗ Bad — returns a string where a number is declared → DF0044
23
+
handler: () =>'twelve'asnever,
24
+
})
25
+
```
26
+
27
+
## Fix
28
+
29
+
Make the handler return a value that satisfies the `returns` schema, or relax the schema so it describes the value the handler actually produces.
30
+
31
+
## Source
32
+
33
+
-[`packages/devframe/src/rpc/validate-io.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/rpc/validate-io.ts) — `validateRpcReturn()` throws `DF0044` when the handler's resolved value fails its declared schema.
Copy file name to clipboardExpand all lines: docs/guide/rpc.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,7 +4,7 @@ outline: deep
4
4
5
5
# RPC
6
6
7
-
Devframe's RPC layer is type-safe bidirectional communication between your server (Node.js) and client (browser), built on [`birpc`](https://github.com/antfu/birpc) and validated at runtime with [`valibot`](https://valibot.dev/). In dev mode it runs over WebSocket; in build / SPA mode it serves a pre-computed static dump so the client still works offline.
7
+
Devframe's RPC layer is type-safe bidirectional communication between your server (Node.js) and client (browser), built on [`birpc`](https://github.com/antfu/birpc) and validated at runtime against any [Standard Schema](https://standardschema.dev/) validator — valibot, zod, arktype, and others all work. In dev mode it runs over WebSocket; in build / SPA mode it serves a pre-computed static dump so the client still works offline.
8
8
9
9
## Overview
10
10
@@ -75,7 +75,7 @@ Use `static` for data collected once during `setup` and shipped to read-only sta
75
75
76
76
### Handler arguments
77
77
78
-
Handlers accept any serializable arguments. With`args`valibot schemas, arguments are validated at the boundary:
78
+
Handlers accept any serializable arguments. Declare`args` schemas — any [Standard Schema](https://standardschema.dev/) validator, valibot below — and each argument is validated at the boundary before the handler runs; a mismatch is rejected with a coded diagnostic. Validation guards the payload without rewriting it, so extra object fields the schema doesn't mention still reach the handler:
why: (p: {name: string,type: string})=>`Function "${p.name}" with type "${p.type}" cannot use \`snapshot: true\`. Only "query" functions support this sugar; "static" functions have equivalent default behavior already.`,
42
42
fix: 'Remove `snapshot: true`, or change the function type to `query`.',
0 commit comments