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.

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.x0.9
import { createCli } from 'devframe/adapters/cli'import { createCac } from 'devframe/adapters/cac'
CreateCliOptionsCreateCacOptions
CliHandleCacHandle
// 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)
JsonRenderSpecDevframeJsonRenderSpec
JsonRenderElementelement shape of DevframeJsonRenderSpec
JsonRendererJsonRenderView
DevframeViewJsonRenderDevframeJsonRenderDockEntry (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.x0.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:

RemovedReplacement
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/nodeNotes
DevframeDiagnosticsHost, DevframeServicesHostImpl, DevframeViewHost (classes)Internal host implementations. The same-named types remain on devframe/types.
createRpcSharedStateServerHost, createRpcStreamingServerHostWired internally by createContextRpcServer.
createScopedNodeContext, createNodeSettingsInternal to context assembly.
toDialableHost, formatHostForUrl, isObjectInternal 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:

MovedFromTo
createH3DevframeHost (+ CreateH3DevframeHostOptions)devframe/nodedevframe/internal
StartedServer (the createDevServer return handle)devframe/nodedevframe/internal
DevframeAgentHost (class)devframe/nodedevframe/internal
coerceAgentPositionalArgs (+ AgentArgsFallback)devframe/nodedevframe/internal
registerDevframeInstance / listLiveDevframeInstances (+ DevframeInstanceRecord, DevframeInstanceRegistration)devframe/nodedevframe/internal
normalizeHttpServerUrldevframe/nodedevframe/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.x0.9
startHttpAndWs({ context, port, ... }) for a standalone toolcreateDevServer(def, { port, ... })
startHttpAndWs(...) inside a framework hostinitDevframe(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.x0.9
import { mountDevframe } from '@devframes/hub/node'removed - call ctx.install
await mountDevframe(ctx, def, opts)await ctx.install(def, opts)
MountDevframeOptionsInstallDevframeOptions (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.x0.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.x0.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.