Skip to content

Commit eaeb1f6

Browse files
committed
docs(plans): add 032 \u2014 0.9 public API surface reduction spec
1 parent ffdb690 commit eaeb1f6

2 files changed

Lines changed: 372 additions & 0 deletions

File tree

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
# Plan 032: Reduce the `devframe` + `@devframes/hub` public API surface for 0.9 ⚠️
2+
3+
> **Executor instructions**: Follow this plan step by step. Run every
4+
> verification command and confirm the expected result before moving on. If a
5+
> STOP condition occurs, stop and report. When done, update this plan's row in
6+
> `plans/README.md`.
7+
>
8+
> **Drift check (run first)**: `git diff --stat ffdb690..HEAD -- packages/devframe/package.json packages/devframe/tsdown.config.ts packages/devframe/src/index.ts packages/devframe/src/node/index.ts packages/hub/src/index.ts`
9+
> On any change since this plan was written, compare against the "Current state"
10+
> excerpts below before proceeding; on a mismatch, STOP.
11+
12+
## Status
13+
14+
- **Priority**: P2
15+
- **Effort**: L (touches both packages' export maps + ~80 consumer import sites + docs)
16+
- **Risk**: MEDIUM (breaking, but 0.9 is already the breaking-cleanup release; every removed export is either unused or has a documented replacement)
17+
- **Depends on**: none (extends the 0.9 cleanup already started in #180 / `migration-0.9.md`)
18+
- **Category**: direction / tech-debt
19+
- **Planned at**: commit `ffdb690`, 2026-08-10
20+
21+
## Why this matters
22+
23+
`devframe` publishes **39** entry points and `@devframes/hub` **6**. An audit of
24+
every consumer — the in-repo `plugins/*`, `examples/*`,
25+
`packages/next|nuxt|hub-ui|json-render*`, and the canonical external consumer
26+
`vitejs/devtools` (`packages/core` = node/server host, `packages/kit` =
27+
re-exports the whole hub surface under `DevTools*` aliases) — found:
28+
29+
- Entry points **nobody imports anywhere** (code, docs, skills, templates).
30+
- The **primary authoring function `defineDevframe` is missing from the main
31+
barrel** and only reachable via `devframe/types`, making `devframe/types` the
32+
single most-imported entry point (78 files) for the wrong reason.
33+
- `devframe/node` exports **16 values, 12 of them internal impl with zero
34+
consumer usage** (host classes, rpc/scope/settings factories, host helpers).
35+
- `@devframes/hub` exports `DEFAULT_CATEGORIES_ORDER` from **four** entry points
36+
when consumers only ever import it from one.
37+
38+
0.9 is already the breaking-cleanup release. This plan shrinks the surface to
39+
what consumers actually use, moves the primary author API to the front door, and
40+
records every change in `docs/guide/migration-0.9.md`.
41+
42+
Consumer usage was measured across in-repo `plugins/*`, `examples/*`,
43+
`packages/*` and the external `vitejs/devtools` source; "unused" below means no
44+
consumer in any of those imports it.
45+
46+
## Scope
47+
48+
**In scope**: `packages/devframe` and `packages/hub` `exports` maps + `tsdown`
49+
entries + barrel source; the `defineDevframe` relocation; the `devframe/node`
50+
trim; the hub `DEFAULT_CATEGORIES_ORDER` de-dup; rewriting in-repo consumer
51+
imports; updating `docs/**`, `skills/devframe/**`, and templates; extending
52+
`docs/guide/migration-0.9.md`.
53+
54+
**Out of scope**: any rename of a widely-used, well-placed export
55+
(`defineRpcFunction`, `createHostContext`, `startHttpAndWs`, the hub
56+
client/type surface); version bumps / releasing (needs separate human
57+
approval); changing `vitejs/devtools` (external — it follows the migration
58+
guide).
59+
60+
## Decisions locked in (from the design consult)
61+
62+
| Area | Decision |
63+
|------|----------|
64+
| Ambition | Remove dead subpaths **and** redesign the core shape. |
65+
| `defineDevframe` | Move onto the main `devframe` barrel; make `devframe/types` strictly type-only; steer all consumers to `devframe`. |
66+
| `devframe/node` | Drop all 12 internal exports; keep only the used server-assembly API. |
67+
| `devframe/node/auth` | **Keep public** (low-level auth layer). |
68+
| `devframe/rpc/client` + `rpc/transports/ws-client` | **Keep both**, document as the low-level / test client. |
69+
| `devframe/rpc/server` + `rpc/transports/ws-server` | **Keep** (hand-rolled-server hosts use them). |
70+
| `devframe/adapters/embedded` | **Remove** (one-liner `d.setup(ctx)`). |
71+
| `devframe/rpc/transports/ws-bun` | **Remove** (zero refs). |
72+
| `devframe/utils/hash`, `utils/promise`, `utils/scope` | **Remove** (zero refs). |
73+
| `devframe/utils/structured-clone` | **Keep**. |
74+
| `devframe/utils/agent-tool-name`, `utils/crypto-token`, `utils/streaming-channel` | **Keep all three** (internal primitives w/ occasional external use). |
75+
| `@devframes/hub` `DEFAULT_CATEGORIES_ORDER` | Keep only on `/constants`; drop from `.`, `/node`, `/client`. |
76+
| `@devframes/hub/initiate` (`initHub`) | **Keep as-is** (deliberate parallel to `devframe/initiate`). |
77+
78+
### ⚠️ One item to confirm during review
79+
80+
`packages/devframe/src/node/index.ts` documents `createContextRpcServer`
81+
(`./rpc-core`) as intentionally public "so hosts that bind their own transports
82+
reuse the exact session/auth wiring." No consumer uses it — `vitejs/devtools`
83+
rebuilds that wiring itself from the lower `devframe/rpc/server` +
84+
`devframe/rpc/transports/ws-server` primitives (`packages/core/src/node/ws.ts`),
85+
which this plan **keeps public**. The locked decision ("drop all 12") therefore
86+
removes `rpc-core` too. If the reviewer wants to preserve the documented
87+
custom-transport contract, split it out: keep `createContextRpcServer` and drop
88+
only the other 11. **Default: drop it with the rest.**
89+
90+
---
91+
92+
## Current state
93+
94+
### `packages/devframe/src/index.ts`
95+
```ts
96+
// Public API. The full defineDevframe + adapter surface lands in later commits.
97+
export * from './define'
98+
export type * from './types'
99+
```
100+
`./define` exports only `defineRpcFunction`. `export type *` strips the
101+
`defineDevframe` **value** that lives in `./types/devframe.ts`, so it never
102+
reaches the main barrel.
103+
104+
### `packages/devframe/src/define.ts`
105+
```ts
106+
import type { DevframeNodeContext } from 'devframe/types'
107+
import { createDefineWrapperWithContext } from 'devframe/rpc'
108+
109+
export const defineRpcFunction = createDefineWrapperWithContext<DevframeNodeContext>()
110+
```
111+
112+
### `packages/devframe/src/types/devframe.ts` (tail)
113+
```ts
114+
export function defineDevframe(d: DevframeDefinition): DevframeDefinition {
115+
return d
116+
}
117+
```
118+
`src/types/index.ts` does `export * from './devframe'`, so this function is the
119+
**only value** on the `devframe/types` entry.
120+
121+
### `packages/devframe/src/node/index.ts`
122+
Barrels `./context`, `./host-agent`, `./host-diagnostics`, `./host-services`,
123+
`./host-views`, `./host-h3`, `./rpc-core`, `./rpc-shared-state`,
124+
`./rpc-streaming`, `./scope`, `./server`, `./settings`, `./storage`, `./utils`,
125+
`./agent-args`, a selective `RpcFunctionsHost` type, and the instance-registry
126+
pair. See the file for the exact lines.
127+
128+
### `packages/hub/src/index.ts`
129+
```ts
130+
export { DEFAULT_CATEGORIES_ORDER } from './constants'
131+
export * from './define'
132+
export type * from './types'
133+
```
134+
`DEFAULT_CATEGORIES_ORDER` is **also** re-exported by `src/node/index.ts:3` and
135+
`src/client/index.ts:3`; its canonical home is `src/constants.ts`.
136+
137+
---
138+
139+
## Changes
140+
141+
### A. Remove dead `devframe` subpaths
142+
143+
Delete these keys from `packages/devframe/package.json` `exports` **and** their
144+
entries from `packages/devframe/tsdown.config.ts` (`serverEntries` /
145+
`clientEntries` / the client-dist check list, as applicable):
146+
147+
| Entry | package.json key | tsdown entry key |
148+
|-------|------------------|------------------|
149+
| embedded adapter | `./adapters/embedded` | `adapters/embedded` (serverEntries) |
150+
| Bun WS transport | `./rpc/transports/ws-bun` | `rpc/transports/ws-bun` (serverEntries) |
151+
| hash util | `./utils/hash` | `utils/hash` (clientEntries **and** the `check-client-dist` list) |
152+
| promise util | `./utils/promise` | `utils/promise` (clientEntries **and** the `check-client-dist` list) |
153+
| scope util | `./utils/scope` | `utils/scope` (clientEntries **and** the `check-client-dist` list) |
154+
155+
Then handle the now-orphaned source:
156+
- `src/adapters/embedded.ts` — delete (and its `__tests__` if any).
157+
- `src/rpc/transports/ws-bun.ts` — delete (and `ws-bun*.test.ts` if any).
158+
- `src/utils/hash.ts`, `src/utils/promise.ts`, `src/utils/scope.ts` — if
159+
nothing imports them internally (verify with the STOP check below), delete;
160+
otherwise switch the internal importer(s) to a relative import and leave the
161+
file un-exported. Verify:
162+
```sh
163+
rg -n "utils/hash|utils/promise|utils/scope" packages/devframe/src --glob '!**/*.test.ts'
164+
```
165+
Expected: no non-test hits outside the files themselves. If a hit exists,
166+
STOP and report (a keeper was misclassified).
167+
168+
`DevframeRuntime` in `types/devframe.ts` includes the string literal
169+
`'embedded'`. Leave the literal (it names a runtime mode, not the adapter). Only
170+
the `createEmbedded` adapter goes away.
171+
172+
### B. Move `defineDevframe` to the main barrel; make `devframe/types` type-only
173+
174+
1. In `src/types/devframe.ts`, **delete** the `defineDevframe` function (keep the
175+
`DevframeDefinition` interface and every option type).
176+
2. In `src/define.ts`, **add** it next to `defineRpcFunction`:
177+
```ts
178+
import type { DevframeDefinition, DevframeNodeContext } from 'devframe/types'
179+
import { createDefineWrapperWithContext } from 'devframe/rpc'
180+
181+
export const defineRpcFunction = createDefineWrapperWithContext<DevframeNodeContext>()
182+
183+
/** Identity helper that types a devframe definition. */
184+
export function defineDevframe(d: DevframeDefinition): DevframeDefinition {
185+
return d
186+
}
187+
```
188+
(`src/index.ts` already does `export * from './define'`, so `defineDevframe`
189+
now reaches the main barrel as a value. Drop the stale "lands in later
190+
commits" comment.)
191+
3. `src/types/index.ts` is unchanged; with the function gone it now re-exports
192+
**types only** — `devframe/types` becomes strictly type-only, matching its
193+
name. Keep the `devframe/types` entry point (kept per decision).
194+
195+
**Consumer migration.** All types already reach the main barrel via
196+
`export type *`, so the canonical import is now `devframe`. Rewrite in-repo
197+
consumers:
198+
- Find value importers:
199+
```sh
200+
rg -n "defineDevframe" plugins examples packages --glob '!**/dist/**'
201+
```
202+
Change `import { defineDevframe } from 'devframe/types'` →
203+
`import { defineDevframe } from 'devframe'`.
204+
- Steer type importers off `devframe/types` to `devframe`:
205+
```sh
206+
rg -n "from 'devframe/types'" plugins examples packages --glob '!**/dist/**'
207+
```
208+
Rewrite each to `from 'devframe'` (preserving `import type` / inline `type`).
209+
~78 files reference `devframe/types`; the change is mechanical. Do **not**
210+
touch `packages/hub/src/**` re-exports of `devframe/types` — those are the
211+
hub's own type barrel and stay.
212+
213+
STOP condition: if any consumer imports a **value** (not a type) from
214+
`devframe/types` other than `defineDevframe`, STOP — the audit says there is
215+
none, so a hit means the surface changed.
216+
217+
### C. Trim `devframe/node` to the used server-assembly API
218+
219+
Rewrite `src/node/index.ts` to:
220+
```ts
221+
// Node-side public API for hosts that wire up their own runtime.
222+
export * from './context' // createHostContext (+ CreateHostContextOptions)
223+
export type { RpcFunctionsHost } from './host-functions'
224+
export * from './host-h3' // createH3DevframeHost (+ CreateH3DevframeHostOptions)
225+
export { listLiveDevframeInstances, registerDevframeInstance } from './instance-registry'
226+
export type { DevframeInstanceRecord, DevframeInstanceRegistration } from './instance-registry'
227+
export * from './server' // startHttpAndWs (+ StartHttpAndWsOptions, StartedServer)
228+
export * from './storage' // createStorage (+ CreateStorageOptions)
229+
export { isObject, normalizeHttpServerUrl } from './utils'
230+
```
231+
Removed from the public barrel (source files **stay** for internal use):
232+
- Host classes: `./host-agent` (`DevframeAgentHost`), `./host-diagnostics`
233+
(`DevframeDiagnosticsHost`), `./host-services` (`DevframeServicesHostImpl`),
234+
`./host-views` (`DevframeViewHost`).
235+
- Factories: `./rpc-core` (`createContextRpcServer` + its option/result types —
236+
see the ⚠️ confirm note), `./rpc-shared-state`
237+
(`createRpcSharedStateServerHost`), `./rpc-streaming`
238+
(`createRpcStreamingServerHost`), `./scope` (`createScopedNodeContext`),
239+
`./settings` (`createNodeSettings`).
240+
- Helpers: `./agent-args` (`coerceAgentPositionalArgs`, `AgentArgsFallback`),
241+
and `toDialableHost` / `formatHostForUrl` from `./utils` (keep only `isObject`
242+
+ `normalizeHttpServerUrl`, both used by `vitejs/devtools`).
243+
244+
These are single-package internals reached via relative imports inside
245+
`packages/devframe/src` (e.g. `node/context.ts` news up the host classes);
246+
removing them from the barrel does not break internal wiring. Verify no consumer
247+
depended on them:
248+
```sh
249+
rg -n "DevframeAgentHost|DevframeServicesHostImpl|createContextRpcServer|createRpcSharedStateServerHost|createRpcStreamingServerHost|createScopedNodeContext|createNodeSettings|coerceAgentPositionalArgs|toDialableHost|formatHostForUrl" plugins examples packages/next packages/nuxt packages/hub-ui packages/json-render packages/json-render-ui --glob '!**/dist/**'
250+
```
251+
Expected: no hits. (The `DevframeViewHost` / `DevframeDiagnosticsHost` **types**
252+
remain exported from `devframe/types` and re-exported by the hub — that is
253+
separate from these node **class** exports.)
254+
255+
The `devframe/node` **entry point itself stays** (only its member set shrinks);
256+
no `package.json`/`tsdown` change here.
257+
258+
### D. De-dup `@devframes/hub` `DEFAULT_CATEGORIES_ORDER`
259+
260+
- `packages/hub/src/index.ts`: delete line
261+
`export { DEFAULT_CATEGORIES_ORDER } from './constants'`.
262+
- `packages/hub/src/node/index.ts`: delete line 3
263+
(`export { DEFAULT_CATEGORIES_ORDER } from '../constants'`) and its 2-line
264+
comment.
265+
- `packages/hub/src/client/index.ts`: delete line 3 (same).
266+
- Keep the definition + export in `packages/hub/src/constants.ts`.
267+
268+
Zero consumer churn: hub-ui (`src/client/constants.ts`) and the
269+
`vitejs/devtools` kit (`packages/kit/src/constants.ts`) already import it from
270+
`@devframes/hub/constants`. Verify:
271+
```sh
272+
rg -n "DEFAULT_CATEGORIES_ORDER" plugins examples packages --glob '!**/dist/**' | rg -v "packages/hub/src"
273+
```
274+
Expected: only `/constants` imports.
275+
276+
### E. Docs, skills, templates
277+
278+
- `docs/adapters/embedded.md` — delete the page; drop it from
279+
`docs/adapters/index.md` and any sidebar/nav config. Where dynamic
280+
registration is described, show `await devframe.setup(ctx)` (standalone) or
281+
`mountDevframe` (hub) instead.
282+
- `skills/devframe/SKILL.md` — remove `createEmbedded` / `adapters/embedded`
283+
references; update any `defineDevframe` import example to `from 'devframe'`;
284+
ensure no example imports from `devframe/types`, `utils/hash|promise|scope`,
285+
or `rpc/transports/ws-bun`.
286+
- `skills/devframe/templates/*` and `docs/**` code samples — same import sweep:
287+
```sh
288+
rg -n "from 'devframe/types'|adapters/embedded|createEmbedded|utils/hash|utils/promise|utils/scope|ws-bun" docs skills
289+
```
290+
- Node reference (`docs/**` node page) — remove the dropped node exports from
291+
any documented list.
292+
293+
### F. Migration guide
294+
295+
Append sections to `docs/guide/migration-0.9.md` (style: short prose + a
296+
before/after import table, matching the existing sections):
297+
298+
1. **`defineDevframe` moves to the main entry** — `from 'devframe/types'` →
299+
`from 'devframe'`; note `devframe/types` is now type-only and types are
300+
importable from `devframe` directly.
301+
2. **`devframe/adapters/embedded` is removed** — replace
302+
`createEmbedded(d, { ctx })` with `await d.setup(ctx)` (or `mountDevframe` in
303+
a hub).
304+
3. **`devframe/rpc/transports/ws-bun` is removed** — build a Bun WS route with
305+
`createRpcServer` + `attachWsRpcTransport` (`devframe/rpc/server` +
306+
`devframe/rpc/transports/ws-server`), which remain public.
307+
4. **`devframe/utils/{hash,promise,scope}` are removed** — use
308+
`Promise.withResolvers()` for `promise`; inline the trivial `hash` / `scope`
309+
helpers if needed.
310+
5. **`devframe/node` slimmed** — list the removed host classes / factories /
311+
helpers; note hand-rolled-server hosts should compose from
312+
`devframe/rpc/server` + `devframe/rpc/transports/ws-server` +
313+
`devframe/node/hub-internals` (the path `vitejs/devtools` already uses).
314+
6. **`@devframes/hub` `DEFAULT_CATEGORIES_ORDER`** — import from
315+
`@devframes/hub/constants` (removed from `.`, `/node`, `/client`).
316+
317+
---
318+
319+
## Verification
320+
321+
Run from the repo root, in order:
322+
323+
```sh
324+
pnpm lint --fix
325+
pnpm knip # must report no newly-unused files/exports; a deleted export that
326+
# left a dangling source file will surface here
327+
pnpm test # runs build first (tsnapi api-snapshot compares fresh dist)
328+
pnpm typecheck
329+
pnpm build
330+
```
331+
332+
Expected:
333+
- `knip` clean (if a kept-internal source file is now unused, either it was
334+
truly dead → delete it, or a relative importer is missing → fix it).
335+
- `pnpm test` regenerates the `tsnapi` API snapshots under
336+
`test/__snapshots__/`; **review the snapshot diff** — it is the authoritative
337+
record of the surface change and must show exactly the removed/moved exports
338+
and nothing else. Commit the updated snapshots.
339+
- `typecheck` clean across all workspaces (the consumer import rewrites in B are
340+
the main risk; a missed `devframe/types` value import fails here).
341+
342+
STOP conditions:
343+
- `knip` flags an unexpected unused export → a consumer rewrite was missed.
344+
- The API snapshot shows a surface change **not** in this plan → investigate
345+
before committing.
346+
- Any `vitejs/devtools`-relied entry point (`devframe/rpc/server`,
347+
`devframe/rpc/transports/ws-server`, `devframe/node/hub-internals`,
348+
`devframe/node` {`isObject`,`normalizeHttpServerUrl`,`RpcFunctionsHost`},
349+
`@devframes/hub/{client,node,types,constants}`) changed → revert that part;
350+
it must stay.
351+
352+
## Suggested commit sequence (one PR)
353+
354+
1. `feat(devframe)!: remove dead subpaths (embedded, ws-bun, utils hash/promise/scope)`
355+
2. `feat(devframe)!: slim the node barrel to the server-assembly API`
356+
3. `feat(devframe)!: move defineDevframe to the main entry; make /types type-only` (+ consumer rewrites)
357+
4. `feat(hub)!: export DEFAULT_CATEGORIES_ORDER only from /constants`
358+
5. `docs: 0.9 migration notes + skill/template/adapters-doc sweep`
359+
360+
Each commit must pass `pnpm build && pnpm typecheck` on its own so the API
361+
snapshot updates land with the change that causes them.
362+
363+
## Maintenance notes
364+
365+
**Breaking (⚠️), intentional, all covered by `migration-0.9.md`.** Every removed
366+
export is either unused across all audited consumers or has a documented
367+
in-tree replacement. The kept low-level layer (`node/auth`, `rpc/client` +
368+
`ws-client`, `rpc/server` + `ws-server`, `utils/{agent-tool-name,crypto-token,
369+
streaming-channel,structured-clone}`) is retained deliberately as the primitives
370+
the ergonomic APIs are built on and that test harnesses / hand-rolled hosts
371+
consume. Releasing 0.9.0 itself requires separate human approval.

‎plans/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ changes are allowed as long as they're marked).
4343
| 029 | Bring `@devframes/plugin-git` to the host baseline | direction/dx | P3 | S-M | — | TODO |
4444
| 030 | Spike: server-side auth enforcement ⚠️ | security | P2 | L | 003, 007, 015 | DONE |
4545
| 031 | Agent-native MCP wave (bridges, core surface, connector) | direction | P2 | L | — | IN PROGRESS (phases 1–3 implemented on PR #145; DONE at merge) |
46+
| 032 | Reduce the devframe + @devframes/hub public API surface for 0.9 ⚠️ | direction/tech-debt | P2 | L | — | TODO |
4647

4748
Status values: TODO | IN PROGRESS | DONE | BLOCKED (one-line reason) | REJECTED (one-line rationale).
4849

0 commit comments

Comments
 (0)