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
> Scoped RPC registration for namespace "`{namespace}`" received an already-namespaced function name "`{name}`".
10
+
11
+
## Cause
12
+
13
+
A [scoped context](../guide/scoped-context) auto-namespaces the ids you pass it. `ctx.scope('my-plugin').rpc.register(...)` therefore expects a **bare** function name and stores it as `my-plugin:<name>`. Passing a name that already contains a `:` separator would produce a double-prefixed id, so registration throws instead.
- Pass a bare name (no `:` separator) to the scoped `register`.
30
+
- To register a fully-qualified name on purpose, use the unscoped host: `ctx.base.rpc.register(...)` (server) or `client.scope(...).base.client.register(...)` (browser).
31
+
32
+
## Source
33
+
34
+
-[`packages/devframe/src/node/scope.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/node/scope.ts) — `register()` / `update()` throw this when the supplied definition name is already namespaced.
TypeScript types flow through from the server's `defineRpcFunction` definitions, so argument and return shapes are known at the call site.
111
+
The unscoped `rpc.call('my-devframe:get-modules', ...)` works too. Either way, TypeScript types flow through from the server's `defineRpcFunction` definitions, so argument and return shapes are known at the call site.
110
112
111
113
## Registering client functions
112
114
113
-
The client can register functions that the server calls via `ctx.rpc.broadcast`:
115
+
The client can register functions that the server calls via `rpc.broadcast`:
Client-side mutations round-trip through the server before reappearing locally. See [Shared State](./shared-state) for the full API.
148
150
151
+
## Settings
152
+
153
+
A scoped client also exposes a top-level persisted `settings` store, synced from the server. Read and write per-user (`global`) or per-workspace (`project`) values:
description: 'A one-line summary of what the tool does.',
22
22
icon: 'ph:gauge-duotone',
23
23
setup(ctx) {
24
+
// A scoped context auto-namespaces ids with your devframe `id`.
25
+
const my =ctx.scope('my-devframe')
26
+
24
27
// Register your RPC functions, shared state, etc. here.
25
-
ctx.rpc.register(defineRpcFunction({
26
-
name: 'my-devframe:hello',
28
+
my.rpc.register(defineRpcFunction({
29
+
name: 'hello', // stored as `my-devframe:hello`
27
30
type: 'static',
28
31
jsonSerializable: true,
29
32
handler: () => ({ message: 'hello' }),
@@ -32,7 +35,7 @@ export default defineDevframe({
32
35
})
33
36
```
34
37
35
-
Host adapters (such as the [`vite` adapter](/adapters/vite) for Vite DevTools) derive their mount entry from `id`, `name`, `icon`, and `basePath` automatically.
38
+
`ctx.scope(id)` is the preferred way to consume the context — see [Scoped Context](./scoped-context). Host adapters (such as the [`vite` adapter](/adapters/vite) for Vite DevTools) derive their mount entry from `id`, `name`, `icon`, and `basePath` automatically.
`ctx.scope(id)` returns a namespace-scoped view that auto-prefixes every RPC id, shared-state key, and streaming channel and adds a persisted top-level `settings` store. It's the recommended entry point from a single tool's setup code — see [Scoped Context](./scoped-context).
121
+
115
122
Host adapters can augment `ctx` with additional surfaces. For example, the [`vite` adapter](/adapters/vite) exposes Vite DevTools' dock, command, message, and terminal hosts via an optional `setup` hook on `createPluginFromDevframe` — consult the host's docs for those extras.
The unscoped `ctx.rpc.register(getModules)` works too — it's the underlying primitive the scoped surface wraps.
58
+
56
59
Place each function in its own file under `src/rpc/functions/`, and barrel them in `src/rpc/index.ts` as `const serverFunctions = [...] as const`. The same array feeds the [type-safe client registry](#type-safe-client-registry) and keeps registration order explicit. When per-file functions need to share setup-time state (channels, shared state handles, loaders), expose it through a `WeakMap<DevframeNodeContext, T>` in a sibling `src/context.ts`.
57
60
58
61
### Naming convention
59
62
60
-
Scope with your devframe id and use kebab-case for the action: `my-devframe:get-modules`, `my-devframe:read-file`, `my-devframe:trigger-rebuild`.
63
+
Scope with your devframe id and use kebab-case for the action: `my-devframe:get-modules`, `my-devframe:read-file`, `my-devframe:trigger-rebuild`. A scoped context applies this prefix for you: `ctx.scope('my-devframe').rpc.register({ name: 'get-modules' })` stores `my-devframe:get-modules`. Define each function with a bare name and let the scope namespace it.
61
64
62
65
### Function types
63
66
@@ -76,7 +79,7 @@ Handlers accept any serializable arguments. With `args` valibot schemas, argumen
@@ -159,52 +163,58 @@ See the [Streaming guide](./streaming) for the full API.
159
163
160
164
## Local invocation
161
165
162
-
`ctx.rpc.invokeLocal` calls a registered server function directly, skipping the transport — useful for cross-function composition on the server side:
166
+
A scoped `rpc.call` invokes a registered server function directly, skipping the transport — useful for cross-function composition on the server side. Bare names resolve within the namespace:
This wraps `ctx.rpc.invokeLocal('my-devframe:get-modules', { limit: 10 })`. Pass a fully-qualified name (containing `:`) to call another tool's function.
174
+
168
175
## Client-side calls
169
176
170
-
From the browser, [`connectDevframe`](./client) (or `getDevframeRpcClient`) returns a client for calling registered functions:
177
+
From the browser, [`connectDevframe`](./client) (or `getDevframeRpcClient`) returns a client. Scope it the same way to call registered functions by bare name:
Client-side registration (for server→client calls) goes through `rpc.client.register()` — the mirror API of `ctx.rpc.register()`.
188
+
Client-side registration (for server→client calls) goes through `my.rpc.register()` — the mirror API of the server-side scoped `rpc.register()`.
181
189
182
190
## Type-safe client registry
183
191
184
192
Devframe exposes two augmentable interfaces — `DevframeRpcServerFunctions` (client→server calls) and `DevframeRpcClientFunctions` (server→client calls) — so each registered RPC name shows up on the typed client. Augment them once per devframe via `declare module 'devframe'`.
185
193
186
-
The recommended pattern collects every server-side definition into a const array and feeds it through `RpcDefinitionsToFunctions`:
194
+
The recommended pattern collects every server-side definition into a const array and feeds it through `RpcDefinitionsToFunctionsWithNamespace` — it prefixes each bare definition name with your devframe id, matching the ids the scoped `register` stores at runtime:
If you define functions with full namespaced names instead, use `RpcDefinitionsToFunctions<typeof serverFunctions>` (no namespace argument) and register them through the unscoped `ctx.rpc.register`.
209
+
200
210
Now `connectDevframe()` returns a client where every registered name is autocompletable and argument-typed:
@@ -241,7 +251,7 @@ For `query` functions, provide an explicit `dump` to enumerate which argument se
241
251
242
252
```ts
243
253
defineRpcFunction({
244
-
name: 'my-devframe:get-session',
254
+
name: 'get-session',
245
255
type: 'query',
246
256
setup: ctx=> ({
247
257
handler: async (id:string) =>loadSession(id),
@@ -253,7 +263,7 @@ defineRpcFunction({
253
263
})
254
264
```
255
265
256
-
At runtime, static clients resolve `rpc.call('my-devframe:get-session', 'session-a')` from the baked dump; unmatched arguments resolve to `dump.fallback` (or throw without one).
266
+
At runtime, static clients resolve `my.rpc.call('get-session', 'session-a')` from the baked dump; unmatched arguments resolve to `dump.fallback` (or throw without one).
257
267
258
268
## JSON-serializable declaration
259
269
@@ -272,7 +282,7 @@ The wire stays plain JSON when every participating function is JSON-flagged —
272
282
273
283
```ts
274
284
defineRpcFunction({
275
-
name: 'my-devframe:graph',
285
+
name: 'graph',
276
286
jsonSerializable: true,
277
287
// ⚠ throws DF0020 because Map cannot round-trip through JSON
278
288
handler: () => ({ nodes: newMap([['a', 1]]) }),
@@ -291,7 +301,7 @@ Add an `agent` field to surface the function to coding agents over MCP. Agent ex
0 commit comments