Migrating from v0.11 to v0.12

v0.12 keeps Web Request and Response middleware, but moves public adapters, host metadata, and naming from Hattip/platform concepts to srvx/runtime concepts.

v0.12 replaces the Hattip-based public contract with a srvx-oriented Web request contract. Middleware still runs on Web Request and Response objects, but Hattip types, adapter assumptions, and platform compatibility aliases were removed.

Install srvx

Remove Hattip packages used only to serve alien-middleware handlers, then install srvx.

pnpm remove @hattip/core @hattip/adapter-node
pnpm add srvx alien-middleware

Use the adapter subpath when mounting a chain in srvx.

import { serve } from 'srvx'
import { chain } from 'alien-middleware'
import { toFetchHandler } from 'alien-middleware/srvx'

const app = chain().use(() => new Response('ok'))

serve({
  port: 3000,
  fetch: toFetchHandler(app),
})

Replace Hattip handler and context types

v0.11 exposed Hattip-derived handler and context types. v0.12 owns these types.

// v0.11
import type { AdapterRequestContext } from '@hattip/core'
import type { HattipContext } from 'alien-middleware'

// v0.12
import type { RequestContext, RequestHandler } from 'alien-middleware'

If tests or custom adapters constructed Hattip contexts directly, create an alien-middleware context instead.

import { createContext } from 'alien-middleware'

const context = createContext({
  request: new Request('http://localhost/'),
  host: {
    ip: '127.0.0.1',
    runtime: { name: 'test' },
    env: name => process.env[name],
    waitUntil: promise => {
      void promise
    },
  },
})

Use context.host

Host-provided values now live under context.host, matching srvx's ServerRequest shape.

// v0.11
const ip = context.ip
const runtimeName = context.platform.name

// v0.12
const ip = context.host.ip
const runtimeName = context.host.runtime?.name

The context.ip and context.platform aliases were removed. Runtime-specific request data should stay behind context.host.runtime.

Rename platform types to runtime types

Public type metadata now uses runtime instead of platform.

// v0.11
type Host = MyChain['$MiddlewareChain']['platform']

// v0.12
type Host = MyChain['$MiddlewareChain']['runtime']

The third generic argument on chain, RequestContext, and Middleware still represents host-specific data, but it now names the srvx concept.

type Runtime = { name: 'node' }

const app = chain<{}, {}, Runtime>().use(context => {
  context.host.runtime?.name
})

Replace filterPlatform with filterRuntime

filterPlatform was removed. Use filterRuntime.

// v0.11
import { filterPlatform } from 'alien-middleware'

const app = chain().use(filterPlatform('node'))

// v0.12
import { filterRuntime } from 'alien-middleware'

const app = chain().use(filterRuntime('node'))

filterRuntime checks context.host.runtime?.name. It also narrows downstream context.host.runtime types through a reserved runtime middleware marker.

const app = chain()
  .use(filterRuntime<{ name: 'node' }>('node'))
  .use(context => {
    context.host.runtime?.name
  })

Update custom request plugins

The request plugin keys env, runtime, and onResponse are reserved.

Use runtime only as a type-level marker for context.host.runtime; it is not added to context as a property.

const app = chain()
  .use(() => ({ runtime: { name: 'node' } }))
  .use(context => {
    context.host.runtime?.name
    // context.runtime does not exist.
  })

If an old plugin used a regular runtime context property, rename that property before upgrading.

Update passThrough assumptions

Hattip adapter-level pass-through no longer exists. In v0.12, context.passThrough() is chain-local control flow:

  • in an isolated chain, it skips the rest of that isolated chain and lets the parent chain continue
  • in a final fetch handler, unresolved requests become the default 404 Not Found response

srvx metadata mapping

alien-middleware/srvx maps srvx ServerRequest metadata into context.host.

import { createContextFromServerRequest } from 'alien-middleware/srvx'

const context = createContextFromServerRequest(request)

context.host.ip
context.host.runtime
context.waitUntil(Promise.resolve())

For a plain Web Request, use the root adapter and pass host data explicitly when needed.

import { toFetchHandler } from 'alien-middleware'

const fetch = toFetchHandler(app, {
  host: request => ({
    env: name => process.env[name],
    runtime: { name: 'custom' },
  }),
})

The migration is mostly runtime naming, but the mothership did insist on repainting the control panel.