Skip to content

Commit 7d1c492

Browse files
committed
Merge branch 'main' of https://github.com/devframes/devframe into feat/plugin-inspect
2 parents 89d579c + 09f382a commit 7d1c492

131 files changed

Lines changed: 3359 additions & 481 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎AGENTS.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ pnpm install # requires pnpm@11.x
2222
pnpm build # tsdown
2323
pnpm dev # tsdown --watch
2424
pnpm test # pnpm build && vitest (api snapshot guards against stale dist)
25-
pnpm typecheck # tsc --noEmit
25+
pnpm typecheck # turbo run typecheck (per-package tsc --noEmit)
2626
pnpm lint --fix # ESLint via @antfu/eslint-config
2727
pnpm start # tsx src/index.ts
2828
```
2929

3030
The `pnpm test` script intentionally runs `build` first so `tsnapi` snapshots compare against fresh `dist/`. `tsdown-stale-guard` enforces this in `test/api-snapshot.test.ts`.
3131

32+
`pnpm typecheck` fans out through Turbo: every workspace package owns a `"typecheck": "tsc --noEmit"` script and its own `tsconfig.json` (extending `tsconfig.base.json` with an explicit `include`). Cross-package imports resolve to source through the `paths` aliases in `tsconfig.base.json`, so no prior build is needed. Any package added under `packages/*` or `plugins/*` is typechecked automatically once it ships that `typecheck` script — add one to every new package so it can't silently skip type errors.
33+
3234
## Conventions
3335

3436
- RPC functions must use `defineRpcFunction`; always namespace IDs (`my-plugin:fn-name`).

‎alias.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const alias = {
2626
'devframe/utils/nanoid': r('devframe/src/utils/nanoid.ts'),
2727
'devframe/utils/open': r('devframe/src/utils/open.ts'),
2828
'devframe/utils/promise': r('devframe/src/utils/promise.ts'),
29+
'devframe/utils/scope': r('devframe/src/utils/scope.ts'),
2930
'devframe/utils/serve-static': r('devframe/src/utils/serve-static.ts'),
3031
'devframe/utils/shared-state': r('devframe/src/utils/shared-state.ts'),
3132
'devframe/utils/streaming-channel': r('devframe/src/utils/streaming-channel.ts'),

‎docs/.vitepress/config.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ function guideItems(prefix: string): DefaultTheme.NavItemWithLink[] {
2121
{ text: 'Introduction', link: `${prefix}/guide/` },
2222
{ text: 'Built with Devframe', link: `${prefix}/guide/built-with` },
2323
{ text: 'Devframe Definition', link: `${prefix}/guide/devframe-definition` },
24+
{ text: 'Scoped Context', link: `${prefix}/guide/scoped-context` },
2425
{ text: 'RPC', link: `${prefix}/guide/rpc` },
2526
{ text: 'Shared State', link: `${prefix}/guide/shared-state` },
2627
{ text: 'Streaming', link: `${prefix}/guide/streaming` },

‎docs/errors/DF0034.md‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DF0034: Already-Namespaced Scoped Registration
6+
7+
## Message
8+
9+
> 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.
14+
15+
## Example
16+
17+
```ts
18+
const my = ctx.scope('my-plugin')
19+
20+
// ✗ Bad — already namespaced
21+
my.rpc.register(defineRpcFunction({ name: 'my-plugin:get-cwd', type: 'query', handler }))
22+
23+
// ✓ Good — bare name, stored as `my-plugin:get-cwd`
24+
my.rpc.register(defineRpcFunction({ name: 'get-cwd', type: 'query', handler }))
25+
```
26+
27+
## Fix
28+
29+
- 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.

‎docs/errors/DF8101.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ outline: deep
66

77
## Message
88

9-
> Cannot change the id of a dock. Use register() to add new docks.
9+
> Cannot change the id of dock "`{id}`" to "`{attempted}`". Dock ids are immutable once registered
1010
1111
## Cause
1212

‎docs/errors/DF8102.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ outline: deep
66

77
## Message
88

9-
> Dock with id "`{id}`" is not registered. Use register() to add new docks.
9+
> Dock with id "`{id}`" is not registered and cannot be updated
1010
1111
## Cause
1212

‎docs/errors/DF8103.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DF8103: Dock Entry Cannot Group Itself
6+
7+
## Message
8+
9+
> Dock entry "`{id}`" cannot set groupId to its own id
10+
11+
## Cause
12+
13+
A dock entry registered with `groupId` pointing at its own `id`. `groupId` is a pointer to a *different* group entry the entry belongs to, so a self-reference would describe an entry that collapses under itself.
14+
15+
## Fix
16+
17+
- Point `groupId` at the `id` of a `type: 'group'` entry, such as `groupId: 'nuxt'`.
18+
- Omit `groupId` entirely to keep the entry as a normal top-level dock entry.
19+
20+
## Source
21+
22+
- [`packages/hub/src/node/host-docks.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/host-docks.ts) — `DevframeDocksHost.register()` and `update()` throw this when `view.groupId === view.id`.

‎docs/errors/DF8104.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DF8104: Nested Dock Groups Unsupported
6+
7+
## Message
8+
9+
> Dock group "`{id}`" cannot itself belong to a group (nested groups are unsupported)
10+
11+
## Cause
12+
13+
A `type: 'group'` entry was registered with `groupId` set. Dock grouping is one level deep: a group collects member entries, but a group cannot itself be a member of another group.
14+
15+
## Fix
16+
17+
- Remove `groupId` from the group entry so it stays a top-level dock-bar button.
18+
- Keep members one level under their group; place each member's `groupId` on the leaf entry, not on another group.
19+
20+
## Source
21+
22+
- [`packages/hub/src/node/host-docks.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/host-docks.ts) — `DevframeDocksHost.register()` and `update()` throw this when `view.type === 'group'` and `view.groupId` is set.

‎docs/errors/DF8105.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DF8105: Devframe Already Mounted
6+
7+
## Message
8+
9+
> Devframe "`{name}`" (id "`{id}`") is already mounted on this hub
10+
11+
## Cause
12+
13+
`mountDevframe(ctx, def)` was called with a devframe whose `id` already belongs to another devframe mounted on the same hub. Devframes are deduplicated by `id`, and the definition's `duplicationStrategy` is `'warn'` (the default) or `'throw'`.
14+
15+
## Fix
16+
17+
Set `duplicationStrategy` on the definition to choose how duplicates are handled:
18+
19+
- `'warn'` (default) — keep the first registration, drop the later one, and emit this warning.
20+
- `'silent'` — drop the later one without warning.
21+
- `'throw'` — surface duplicates as a thrown error.
22+
- `'duplicate'` — let every instance coexist under a disambiguated dock id (`my-tool`, `my-tool-2`, …).
23+
24+
Otherwise, remove the redundant `mountDevframe` call so each devframe is mounted once.
25+
26+
## Source
27+
28+
- [`packages/hub/src/node/mount-devframe.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/mount-devframe.ts) — `mountDevframe()` emits this when a devframe sharing an already-mounted `id` is mounted and the strategy is not `'duplicate'`.

‎docs/guide/client.md‎

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,30 +93,32 @@ const ok = await rpc.requestTrustWithToken('another-token')
9393

9494
## Calling functions
9595

96+
Derive a [scoped client](./scoped-context) so ids are namespaced for you:
97+
9698
```ts
97-
const rpc = await connectDevframe()
99+
const my = (await connectDevframe()).scope('my-devframe')
98100

99101
// Standard call — awaits a response or throws.
100-
const modules = await rpc.call('my-devframe:get-modules', { limit: 10 })
102+
const modules = await my.rpc.call('get-modules', { limit: 10 })
101103

102104
// Optional — returns undefined when no handler responds (useful while HMR is restarting).
103-
const maybe = await rpc.callOptional('my-devframe:get-modules', { limit: 10 })
105+
const maybe = await my.rpc.callOptional('get-modules', { limit: 10 })
104106

105107
// Event — fire-and-forget, no response expected.
106-
rpc.callEvent('my-devframe:notify', { message: 'hello' })
108+
my.rpc.callEvent('notify', { message: 'hello' })
107109
```
108110

109-
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.
110112

111113
## Registering client functions
112114

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`:
114116

115117
```ts
116118
import { defineRpcFunction } from 'devframe'
117119

118-
rpc.client.register(defineRpcFunction({
119-
name: 'my-devframe:on-file-changed',
120+
my.rpc.register(defineRpcFunction({
121+
name: 'on-file-changed', // -> my-devframe:on-file-changed
120122
type: 'event',
121123
setup: () => ({
122124
handler: async ({ file }: { file: string }) => {
@@ -131,7 +133,7 @@ That's how the server pushes live updates into the UI — file-watcher events, s
131133
## Shared state
132134

133135
```ts
134-
const state = await rpc.sharedState.get('my-devframe:state')
136+
const state = await my.rpc.sharedState('state') // -> my-devframe:state
135137

136138
console.log(state.value())
137139

@@ -146,6 +148,17 @@ state.on('updated', (next) => {
146148

147149
Client-side mutations round-trip through the server before reappearing locally. See [Shared State](./shared-state) for the full API.
148150

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:
154+
155+
```ts
156+
await my.settings.project.set('theme', 'dark')
157+
const theme = await my.settings.project.get('theme')
158+
```
159+
160+
See [Scoped Context](./scoped-context#settings) for the full API.
161+
149162
## Caching
150163

151164
Set `cacheOptions: true` (or an options object) when constructing the client:

0 commit comments

Comments
 (0)