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
Copy file name to clipboardExpand all lines: AGENTS.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -36,6 +36,7 @@ The `pnpm test` script intentionally runs `build` first so `tsnapi` snapshots co
36
36
## Conventions
37
37
38
38
- RPC functions must use `defineRpcFunction`; always namespace IDs `devframes:plugin:<slug>:<fn-name>` (matching the plugin's `@devframes/plugin-<slug>` package name).
39
+
-**Stay validator-neutral.**`devframe` and every `@devframes/*` package must not introduce a preferred schema validator dependency — no `valibot`, `zod`, `arktype`, etc. in their runtime `dependencies`. `args`/`returns`/flag schemas are typed against [Standard Schema](https://standardschema.dev/) (`@standard-schema/spec`, types-only); first-party code that needs to author a schema uses the built-in zero-dep `devframe/utils/simple-schema` builder (deliberately minimal — not a general validator). JSON-schema conversion uses each schema's own Standard JSON Schema converter (`~standard.jsonSchema`, implemented by e.g. zod 4) when present and degrades to a permissive object otherwise — no converter library and no vendor dependency is required. Docs, by contrast, should point *users* at a real validator for their own integrations — recommend **valibot** (lightest) or **zod** (worth reusing if they already pull it via the JSON-render or MCP integrations).
39
40
- Shared state via `devframe/utils/shared-state`; keep values serializable.
40
41
- Utility imports use the package-path form `devframe/utils/*`, never relative `../utils/*`.
41
42
- Dependencies go through the pnpm catalogs in `pnpm-workspace.yaml` (`cli`, `inlined`, `testing`, `types`) — add to a catalog and reference as `catalog:<name>`, don't pin versions in `package.json`.
Copy file name to clipboardExpand all lines: docs/errors/DF0019.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ outline: deep
10
10
11
11
## Cause
12
12
13
-
The `agent` field exposes an RPC function as an MCP tool. MCP and the underlying schema-conversion path (`@valibot/to-json-schema`) only consume JSON-shaped data. Functions whose payloads can include `Map`, `Set`, `Date`, `BigInt`, circular references, or class instances cannot be safely advertised to agents.
13
+
The `agent` field exposes an RPC function as an MCP tool. MCP only consumes JSON-shaped data. Functions whose payloads can include `Map`, `Set`, `Date`, `BigInt`, circular references, or class instances cannot be safely advertised to agents.
14
14
15
15
A registered function is rejected when `agent` is present and `jsonSerializable` is not explicitly `true`.
> 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
+11-3Lines changed: 11 additions & 3 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
@@ -22,7 +22,7 @@ sequenceDiagram
22
22
23
23
```ts
24
24
import { defineRpcFunction } from'devframe'
25
-
import*asvfrom'valibot'
25
+
import*asvfrom'valibot'// npm i valibot (or use zod / arktype)
26
26
27
27
exportconst getModules =defineRpcFunction({
28
28
name: 'get-modules', // bare — the scope namespaces it to `my-devframe:get-modules`
@@ -75,7 +75,12 @@ 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, zod, arktype, …) — 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.
79
+
80
+
Devframe forces no validator on you: bring whichever [Standard Schema](https://standardschema.dev/) validator you prefer (valibot, zod, arktype) and install it yourself. The examples here use valibot (`npm i valibot`) — it's the lightest option and a good default.
81
+
82
+
> [!TIP]
83
+
> If your app already pulls in **zod** — the JSON-render integration and the MCP server both use it — prefer zod for your RPC schemas too, and you'll reuse a dependency you're already shipping instead of adding valibot. Any Standard Schema validator works either way; this is purely about dependency reuse.
79
84
80
85
```ts
81
86
defineRpcFunction({
@@ -94,6 +99,9 @@ defineRpcFunction({
94
99
95
100
Prefer a single object argument (`args: [v.object({ ... })]`) over positional args — property names are self-describing and agents/IDEs work best with object shapes.
96
101
102
+
> [!WARNING]
103
+
> Declared `args`/`returns` schemas are enforced at runtime — a call whose arguments, or a handler whose return value, fail the schema is rejected with `DF0043` / `DF0044`. Make sure each schema matches what the function actually accepts and returns; a schema stricter than reality will now reject calls that previously ran.
For flags that are specific to your tool, declare them as valibot schemas so they're validated at parse time and typed at the call site:
171
+
For flags that are specific to your tool, declare them with any [Standard Schema](https://standardschema.dev/) validator (valibot below — `npm i valibot`, the lightest option — or zod / arktype) so they're validated at parse time and typed at the call site. If you already depend on zod through the JSON-render or MCP integrations, prefer zod here to avoid adding a second validator:
|`KnownEditor`| — | type | — | Union of `KNOWN_EDITORS`. |
30
30
31
-
Both functions are `action`-type RPCs returning `void` and use `valibot` schemas for their arguments — `openInEditor`'s `editor` argument is `v.optional(v.picklist(KNOWN_EDITORS))`, so a value outside `KNOWN_EDITORS` fails validation rather than reaching the underlying `launch-editor` process spawn. Both handlers dynamically `import()` their underlying `devframe/utils/*` implementation, so the `launch-editor` and `open` dependencies only load when the recipe actually runs.
31
+
Both functions are `action`-type RPCs returning `void`, and their arguments are schema-validated — `openInEditor`'s `editor` argument is restricted to `KNOWN_EDITORS`, so a value outside that list fails validation rather than reaching the underlying `launch-editor` process spawn. Both handlers dynamically `import()` their underlying `devframe/utils/*` implementation, so the `launch-editor` and `open` dependencies only load when the recipe actually runs.
32
32
33
33
The `devframe/recipes/open-helpers` entry (`openHelpers`) remains as a deprecated alias for this module — new code should import `commonRpcFunctions` from `devframe/recipes/common-rpc-functions`.
0 commit comments