Migrating to 0.9
0.9 removes the compatibility shims deprecated across the 0.7 series and trims the public API of devframe and @devframes/hub. Each change has a drop-in replacement.
devframe/adapters/cli is removed
The devframe/adapters/cli entry is gone; import from devframe/adapters/cac:
| 0.8.x | 0.9 |
|---|---|
import { createCli } from 'devframe/adapters/cli' | import { createCac } from 'devframe/adapters/cac' |
CreateCliOptions | CreateCacOptions |
CliHandle | CacHandle |
// 0.8.x
import { createCli } from 'devframe/adapters/cli'
await createCli(devframe).parse()// 0.9
import { createCac } from 'devframe/adapters/cac'
await createCac(devframe).parse()Typed-flag helpers defineCliFlags and parseCliFlags live on devframe/adapters/cac too.
devframe/recipes/open-helpers is removed
Renamed to common-rpc-functions in 0.7.16. Import commonRpcFunctions:
// 0.8.x
import { openHelpers } from 'devframe/recipes/open-helpers'// 0.9
import { commonRpcFunctions } from 'devframe/recipes/common-rpc-functions'openInEditor and openInFinder are unchanged.
RPC dump re-exports move to devframe/rpc/dump
Static-dump re-exports from the devframe/rpc barrel are removed; import from devframe/rpc/dump:
// 0.8.x
import { createClientFromDump, dumpFunctions } from 'devframe/rpc'
// 0.9
import { createClientFromDump, dumpFunctions } from 'devframe/rpc/dump'This covers every dump export: collectStaticRpcDump, createClientFromDump, dumpFunctions, getDefinitionsWithDumps, reviveDumpError, serializeDumpError, and the StaticRpcDump* types.
@devframes/hub json-render shims are removed
json-render moved into the opt-in @devframes/json-render integration in 0.7; the hub-local shims are removed:
0.8.x (@devframes/hub) | 0.9 (@devframes/json-render) |
|---|---|
defineJsonRenderSpec(spec) | Pass the spec directly to createJsonRenderView(ctx, { id, spec }) |
ctx.createJsonRenderer(spec) | createJsonRenderView(ctx, { id, spec }) (from @devframes/json-render/node) |
JsonRenderSpec | DevframeJsonRenderSpec |
JsonRenderElement | element shape of DevframeJsonRenderSpec |
JsonRenderer | JsonRenderView |
DevframeViewJsonRender | DevframeJsonRenderDockEntry (from @devframes/json-render/hub) |
// 0.8.x
import { defineJsonRenderSpec } from '@devframes/hub'
const spec = defineJsonRenderSpec({ root: 'panel', elements: { /* ... */ } })
const renderer = ctx.createJsonRenderer(spec)// 0.9
import { createJsonRenderView } from '@devframes/json-render/node'
const view = createJsonRenderView(ctx, {
id: 'panel',
spec: { root: 'panel', elements: { /* ... */ } },
})createJsonRenderView returns a view with a serializable ref. Project it onto a hub dock via toJsonRenderDockEntry (@devframes/json-render/hub), which contributes the 'json-render' dock type.
defineDevframe moves to the package root
defineDevframe lives on devframe alongside defineRpcFunction; devframe/types is type-only. Import values and types from devframe:
| 0.8.x | 0.9 |
|---|---|
import { defineDevframe } from 'devframe/types' | import { defineDevframe } from 'devframe' |
import type { DevframeNodeContext } from 'devframe/types' | import type { DevframeNodeContext } from 'devframe' |
import type { DevframeNodeContext } from 'devframe'
// 0.9
import { defineDevframe, defineRpcFunction } from 'devframe'devframe/utils/{promise,scope} are removed
Two unused utility subpaths are removed:
| Removed | Replacement |
|---|---|
import { promiseWithResolver } from 'devframe/utils/promise' | Promise.withResolvers() (native) |
import { isQualifiedName, qualifyName } from 'devframe/utils/scope' | Inline the check (name.includes(':')) |
devframe/node is slimmed to the context surface
devframe/node keeps only the context API — createHostContext, createStorage (with their options types), and RpcFunctionsHost. Serve via the adapters or devframe/initiate.
Internal host implementations and factories are no longer exported:
Removed from devframe/node | Notes |
|---|---|
DevframeDiagnosticsHost, DevframeServicesHostImpl, DevframeViewHost (classes) | Internal host implementations. The same-named types remain on devframe/types. |
createRpcSharedStateServerHost, createRpcStreamingServerHost | Wired internally by createContextRpcServer. |
createScopedNodeContext, createNodeSettings | Internal to context assembly. |
toDialableHost, formatHostForUrl, isObject | Internal helpers (isObject is removed entirely - inline typeof x === 'object' && x !== null). |
Cross-package internals move to devframe/internal
Low-level primitives shared between devframe and its integrations move from devframe/node to the new unstable devframe/internal entry point:
| Moved | From | To |
|---|---|---|
createH3DevframeHost (+ CreateH3DevframeHostOptions) | devframe/node | devframe/internal |
StartedServer (the createDevServer return handle) | devframe/node | devframe/internal |
DevframeAgentHost (class) | devframe/node | devframe/internal |
coerceAgentPositionalArgs (+ AgentArgsFallback) | devframe/node | devframe/internal |
registerDevframeInstance / listLiveDevframeInstances (+ DevframeInstanceRecord, DevframeInstanceRegistration) | devframe/node | devframe/internal |
normalizeHttpServerUrl | devframe/node | devframe/internal |
startHttpAndWs is removed
startHttpAndWs is gone; createDevServer, initDevframe, and initHub own the binding internally. StartHttpAndWsOptions is removed; StartedServer stays (re-exported from devframe/internal).
| 0.8.x | 0.9 |
|---|---|
startHttpAndWs({ context, port, ... }) for a standalone tool | createDevServer(def, { port, ... }) |
startHttpAndWs(...) inside a framework host | initDevframe(def, { base, ... }) / initHub({ base, ... }) |
Bind your own transport with createContextRpcServer (devframe/internal) plus a transport from devframe/rpc/transports/*:
// 0.9 - bind the RPC socket onto a server you own
import { createServer } from 'node:http'
import { createContextRpcServer } from 'devframe/internal'
import { attachWsRpcTransport } from 'devframe/rpc/transports/ws-server'
const httpServer = createServer()
const { rpcGroup, onConnected, onDisconnected } = createContextRpcServer({ context, auth: false })
attachWsRpcTransport(rpcGroup, { server: httpServer, onConnected, onDisconnected })
httpServer.listen(port)@devframes/hub's mountDevframe is removed - use ctx.install
| 0.8.x | 0.9 |
|---|---|
import { mountDevframe } from '@devframes/hub/node' | removed - call ctx.install |
await mountDevframe(ctx, def, opts) | await ctx.install(def, opts) |
MountDevframeOptions | InstallDevframeOptions (from @devframes/hub/node) |
// 0.8.x
import { createHubContext, mountDevframe } from '@devframes/hub/node'
const ctx = await createHubContext({ host, cwd, mode: 'dev' })
await mountDevframe(ctx, myDevframe)// 0.9
import { createHubContext } from '@devframes/hub/node'
const ctx = await createHubContext({ host, cwd, mode: 'dev' })
await ctx.install(myDevframe)@devframes/hub category order lives only on /constants
DEFAULT_CATEGORIES_ORDER now lives only on @devframes/hub/constants; re-exports from @devframes/hub, @devframes/hub/node, and @devframes/hub/client are removed:
// 0.9
import { DEFAULT_CATEGORIES_ORDER } from '@devframes/hub/constants'The Vite bridge moves to @devframes/vite
devframe/helpers/vite is its own package @devframes/vite. viteDevBridge becomes three purpose-named plugins on @devframes/vite/single:
| 0.8.x | 0.9 |
|---|---|
import { viteDevBridge } from 'devframe/helpers/vite' | import { devframeVite } from '@devframes/vite/single' |
viteDevBridge(def) (static mount) | devframeVitePlugin(def) |
viteDevBridge(def, { devMiddleware: true }) (RPC bridge) | devframeViteBridge(def) |
viteDevBridge(def, { devMiddleware: { port, host, flags } }) | devframeViteBridge(def, { port, host, flags }) |
The devMiddleware option is gone; bridge options flatten to the top level (port, host, flags, auth, mcp). devframeVite(def, { bridge }) picks between the two plugins.
// 0.8.x
import { viteDevBridge } from 'devframe/helpers/vite'
export default defineConfig({
plugins: [viteDevBridge(devframe, { devMiddleware: true })],
})// 0.9
import { devframeViteBridge } from '@devframes/vite/single'
export default defineConfig({
plugins: [devframeViteBridge(devframe)],
})@devframes/vite (and @devframes/nuxt / @devframes/next) take @devframes/hub and @devframes/hub-ui as optional peers — only the /hub scope needs them.
Built-in plugins' /vite subpath is removed
Each plugin's @devframes/plugin-<name>/vite export is removed. For Vite, mount into Vite DevTools with createPluginFromDevframe (@vitejs/devtools-kit/node):
// 0.8.x
import { a11yVitePlugin } from '@devframes/plugin-a11y/vite'
export default defineConfig({
plugins: [a11yVitePlugin()],
})// 0.9
import createA11yDevframe from '@devframes/plugin-a11y'
import { createPluginFromDevframe } from '@vitejs/devtools-kit/node'
export default defineConfig({
plugins: [createPluginFromDevframe(createA11yDevframe())],
})Without Vite DevTools, call devframeVite against the plugin's default-export instance:
import createA11yDevframe from '@devframes/plugin-a11y'
import { devframeVite } from '@devframes/vite/single'
export default defineConfig({
plugins: [devframeVite(createA11yDevframe())],
})codeServerVite and terminalsVite are unaffected.
Built-in plugins' default export is now the factory, not an instance
The default export is now the create<X>Devframe factory, not a pre-built DevframeDefinition; call it to get an instance:
// 0.8.x
import a11yDevframe from '@devframes/plugin-a11y'
await ctx.install(a11yDevframe)// 0.9
import createA11yDevframe from '@devframes/plugin-a11y'
await ctx.install(createA11yDevframe())@devframes/nuxt and @devframes/next split into /single and /hub
Both serve their single-devframe surface from .../single; the bare root throws.
Nuxt — register the module by its subpath:
// 0.8.x [nuxt.config.ts]
export default defineNuxtConfig({ modules: ['@devframes/nuxt'] })
// 0.9 [nuxt.config.ts]
export default defineNuxtConfig({ modules: ['@devframes/nuxt/single'] })Next — helpers and the React client move down a level:
| 0.8.x | 0.9 |
|---|---|
import { withDevframe } from '@devframes/next' | import { withDevframe } from '@devframes/next/single' |
import { createDevframeNextHandler } from '@devframes/next' | import { createDevframeNextHandler } from '@devframes/next/single' |
import { RpcProvider, useRpc } from '@devframes/next/client' | import { RpcProvider, useRpc } from '@devframes/next/single/client' |
Mounting a hub: the new /hub scope
@devframes/vite/hub, @devframes/nuxt/hub, and @devframes/next/hub mount a whole @devframes/hub, wrapping initHub and defaulting the UI to @devframes/hub-ui's createUi() (override with ui, or ui: false for headless).
// Vite
import { viteDevframeHub } from '@devframes/vite/hub'
export default defineConfig({ plugins: [viteDevframeHub({ devframes: [] })] })// Next — app/__devframes/[[...path]]/route.ts
import { nextDevframeHub } from '@devframes/next/hub'
export const runtime = 'nodejs'
const hub = nextDevframeHub({ devframes: [] })
export const GET = (req: Request) => hub.handler(req)
export const POST = (req: Request) => hub.handler(req)
export const DELETE = (req: Request) => hub.handler(req)@devframes/vite/hub and @devframes/nuxt/hub recommend the native Vite DevTools / Nuxt DevTools once (silence with { quiet: true }); @devframes/next/hub stays quiet.
Migrations
Version-by-version upgrade guides for devframe and @devframes/hub. Each release below has a drop-in path from the previous one.
Migrating to 0.8
0.8 makes RPC schemas validator-neutral and runtime-validated, upgrades the MCP adapter to @modelcontextprotocol/sdk v2, and adds the agent-native MCP surface. See the v0.8.0 release notes.