0Z4C0
// navigation
// @ozaco/server

Gateway

Platform HTTP + WebSocket gateways that expose services and actions over the web.

import from@ozaco/server/gateway/bun

A gateway is a platform implementation of the single Gateway protocol from @ozaco/server/core. That one protocol gathers server lifecycle, routing (rou3), the REST transformer and the WebSocket transformer under one surface, so each impl owns its own server, router, rest and ws internals — and every plugin (cors, docs, auth) hooks exactly one surface.

You pick an impl for your runtime and install it with GatewayDef.Options (port default 3000, host default '0.0.0.0', plus statusMap, maxBodyBytes, reusePort and a failure-simplify hook). From then on you drive it through the shared Gateway.actions.*: mount(prefix, service | action) to expose routes, start({ port, host, reusePort }) to begin listening (it resolves the actually-bound { host, port }, so port: 0 reports the OS-assigned port), pause / resume / destroy({ drainMs }) for lifecycle, and the realtime family (emit, broadcast, join, leave, toRoom, listen) for WebSockets.

Streaming responses are first-class: the handler delivers the Response as soon as headers are known, then the action's byte Stream drains into the body in the background on the gateway scope, paced by socket backpressure. WebSocket routes can be per-message RPC (mount a ws action) or app-owned raw frames; Gateway.actions.listen(path, handlers) registers an endpoint without defineAction/mount and hands each connection an effect-native Socket (with send, join/leave, toRoom/broadcast and a lifetime-bound spawn).

Both impls expose the same protocol and the wsBearer helper; they differ only in their host server and how WebSocket upgrades are handled.

// additional entry points
  • @ozaco/server/gateway/bunHTTP + native WebSocket gateway built on `Bun.serve`. WebSocket upgrades are negotiated inside `fetch`; streaming bodies drain with Bun read-paced backpressure.
  • @ozaco/server/gateway/nodeHTTP gateway built on `node:http` `createServer`. WebSockets require the optional `ws` peer dependency (lazily imported); `reusePort` enables SO_REUSEPORT balancing.
01

Bun gateway

The `Bun.serve` HTTP/WebSocket implementation.

const

BunGateway

The `Gateway` protocol implemented on `Bun.serve` (HTTP + native WebSockets).

signature
// from '@ozaco/server/gateway/bun'
const BunGateway: GatewayDef.Default
// = Plugin<GatewayDef.Context, [options?: GatewayDef.Options], GatewayDef.Actions>

install(BunGateway, options?: GatewayDef.Options)

// then drive it through the shared Gateway protocol:
Gateway.actions.mount(prefix: string, target: Service | Action): Future<void>
Gateway.actions.start(
  options: Partial<{ port: number; host: string; reusePort: boolean }>,
): Future<{ port: number; host: string }>
Gateway.actions.destroy(options?: { drainMs?: number }): Future<void>

Runs Bun.serve with idleTimeout: -1 (the effect runtime owns lifetimes). Its fetch first attempts a WebSocket upgrade; non-upgraded requests fall through to the REST transformer, and dispatch runs in the background on the gateway scope so a streaming body can keep draining after the Response headers are returned. destroy halts every per-socket pump, terminates live WS connections and aborts in-flight request pumps, then calls server.stop() with a drainMs grace period (default 30_000 ms) before a hard stop. start reads the real hostname/port back off the server, so an ephemeral port: 0 reports the assigned port.

example
import { install } from '@ozaco/std/plugin'
import { Gateway } from '@ozaco/server/core'
import { BunGateway } from '@ozaco/server/gateway/bun'

yield* install(BunGateway, { port: 3000 })
yield* Gateway.actions.mount('/api', UserService)

const { host, port } = yield* Gateway.actions.start({})
console.log(`listening on http://${host}:${port}`)
fn

wsBearer

Extract a bearer token from a WebSocket connection (header or `?token=` query).

signature
function wsBearer(data: {
  url?: string
  headers?: Record<string, string>
}): string | undefined

Reads the authorization header captured at upgrade (stripping a leading Bearer ), falling back to a ?token= or ?access_token= query parameter. Because browsers cannot set custom headers on the WS handshake, the query-param form is the browser-friendly convention. Use it inside a listen handler (typically open) to authenticate a socket; per-message-RPC routes receive the token automatically. Also re-exported, identically, from @ozaco/server/gateway/node.

example
import { Gateway } from '@ozaco/server/core'
import { wsBearer } from '@ozaco/server/gateway/bun'

yield* Gateway.actions.listen('/realtime', {
  *open(socket) {
    const token = wsBearer(socket.data)
    if (!token) {
      return yield* socket.send({ error: 'unauthorized' })
    }
    // ...verify token, join rooms, etc.
  },
})
02

Node gateway

The `node:http` implementation.

const

NodeGateway

The `Gateway` protocol implemented on `node:http` `createServer`.

signature
// from '@ozaco/server/gateway/node'
const NodeGateway: GatewayDef.Default
// = Plugin<GatewayDef.Context, [options?: GatewayDef.Options], GatewayDef.Actions>

install(NodeGateway, options?: GatewayDef.Options)

// same shared Gateway.actions.* surface as the Bun gateway
Gateway.actions.start(
  options: Partial<{ port: number; host: string; reusePort: boolean }>,
): Future<{ port: number; host: string }>

Adapts each Node IncomingMessage into a web-standard Request, dispatches through the same transformer as the Bun gateway, and pipes the Response body back to the socket with backpressure (buffered JSON/text and live ReadableStream bodies alike). WebSocket upgrades are bridged to the optional ws peer dependency: it is imported lazily, so the gateway starts without it, and upgrade requests are dropped if it is absent. Upgraded sockets funnel through the same platform-agnostic onOpen/onMessage/onClose actions as Bun, sharing the registry and rooms. reusePort enables kernel-level SO_REUSEPORT balancing, and start reports the real bound port for port: 0. The wsBearer helper is re-exported here too, identical to the Bun export.

example
import { install } from '@ozaco/std/plugin'
import { Gateway } from '@ozaco/server/core'
import { NodeGateway } from '@ozaco/server/gateway/node'

// WebSockets need the optional 'ws' package installed alongside @ozaco/server
yield* install(NodeGateway, { port: 8080, reusePort: true })
yield* Gateway.actions.mount('/api', UserService)
yield* Gateway.actions.start({})