Skip to content

Ship @hono/perry-server adapter package (#487 follow-through) #1654

Description

@proggeramlug

Scope

Publish a thin @hono/perry-server (or @perryts/hono-server, name TBD) npm package that exposes Hono's standard adapter contract — serve({ fetch: app.fetch, port, hostname? }) — on Perry-native, wrapping Perry's bundled Fastify (perry-ext-fastify). The package is ~100 lines of TypeScript and absorbs the Perry-specific quirks in one well-tested place rather than every downstream app vendoring its own shim (which is the status quo across perry/playground/server/router.ts, perry/compile/server/router.ts, perry/shop/src/platform/serve.ts).

Why

#487 closed "as a side effect" of #577 — the idea being that node:http would work end-to-end and @hono/node-server would suffice. In practice today: #1652 (node:http link gap) and #1651 (node:http2 missing exports) both still block @hono/node-server. A dedicated Perry adapter:

  1. Decouples Hono-on-Perry from the upstream @hono/node-server package (which we don't control and which keeps pulling more Node-stdlib symbols).
  2. Gives a single, versioned, Perry-org-owned place to absorb perry-ext-fastify quirks (reply.code vs reply.status flakiness, method-binding restrictions, etc. — all currently spread across multiple downstream repos).
  3. Matches the per-runtime adapter pattern Hono uses elsewhere (@hono/node-server, @hono/bun, @hono/deno, Workers built-in) — Hono users find this familiar.

Hard blockers (must land first)

Soft blockers (nice-to-have)

Proposed surface

```typescript
// @hono/perry-server/src/index.ts
import fastify from 'fastify'
import type { Hono } from 'hono'

export interface ServeOptions {
fetch: Hono['fetch']
port: number
hostname?: string
}

export interface ServeInfo {
port: number
address: string
family: 'IPv4' | 'IPv6'
}

export function serve(opts: ServeOptions, listener?: (info: ServeInfo) => void): void {
const app = fastify({ logger: false })

app.all('/*', async (req, reply) => {
const url = `http://${req.headers.host}${req.url}`
const headers = new Headers()
for (const [k, v] of Object.entries(req.headers)) {
if (Array.isArray(v)) v.forEach((vv) => headers.append(k, String(vv)))
else if (v !== undefined) headers.set(k, String(v))
}
const method = req.method ?? 'GET'
const hasBody = method !== 'GET' && method !== 'HEAD'

const fetchReq = new Request(url, {
  method,
  headers,
  body: hasBody ? (typeof req.body === 'string' ? req.body : JSON.stringify(req.body)) : undefined,
})

const res = await opts.fetch(fetchReq)              // ← needs #1649
res.headers.forEach((v, k) => { if (k !== 'content-length') reply.header(k, v) })
reply.code(res.status)
// Once #1650 lands, prefer streaming via res.body.getReader() instead.
return reply.send(await res.text())

})

app.listen({ port: opts.port, host: opts.hostname ?? '0.0.0.0' }, (err, address) => {
if (err) throw err
listener?.({ port: opts.port, address, family: address.includes(':') ? 'IPv6' : 'IPv4' })
})
}
```

Acceptance

  • New repo: PerryTS/hono-perry-server (or in-tree under packages/), published as @hono/perry-server (preferred — namespace consistency with @hono/node-server, @hono/bun) or @perryts/hono-server (fallback if @hono namespace requires Hono-org coordination).
  • Source ships with perry: ./src/index.ts export + src/ in files (canonical Perry-package shape, like @perryts/mysql).
  • Acceptance test: a 5-line Hono app — app.get('/', c => c.json({ ok: true })) + serve({ fetch: app.fetch, port: 3000 }) — compiles via perry compile, listens on 3000, returns {"ok":true} to curl localhost:3000/, identical bytes to the same app under node --import tsx with @hono/node-server.
  • A second acceptance: c.html(<Component/>) (needs hono/jsx: <Component/> evaluates to undefined under Perry native compile #1653) round-trips through the adapter and emits real HTML.
  • Documented in docs/src/stdlib/http.md (alongside the existing "Hono" section).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions