Protocol brokers
How the transport boundary works, and how to add a new one by extending BrokerAdapter.
Gland’s core is protocol-agnostic. Everything transport-specific lives in a broker:
an object that owns its own EventBroker, subscribes to the framework’s route
definitions, and hands back a transport application.
This guide covers the contract, then a complete worked example.
The contract
Section titled “The contract”connectTo() does exactly three things:
const adapter = new AdapterClass(options) // 1. build the broker + applicationadapter.broker.connectTo(this.broker) // 2. link the brokersreturn adapter.initialize() // 3. bind routes, return the appFor your adapter to work, that means:
- It extends
BrokerAdapter<TEvents, TApp, TOptions>. - Its constructor takes
options?: TOptionsand assignsthis.brokerandthis.instance. initialize()subscribes togland:define:route, registers each route onthis.instance, and returns it.
The route payload
Section titled “The route payload”When core binds a controller method it broadcasts one gland:define:route event per
route.
interface GlandRoute<TContext = any> { readonly path: string // the handler's own path, e.g. ':id' readonly fullPath: string // fully qualified, e.g. '/products/:id' readonly method: string // UPPER-CASE, e.g. 'GET' readonly action: (ctx: TContext) => unknown}A minimal adapter
Section titled “A minimal adapter”Here is a complete broker for a hypothetical JSON-RPC server. It is deliberately small — the point is the shape.
import { BrokerAdapter, GLAND_ROUTE_EVENT, type BrokerAdapterClass,} from '@glandjs/core'import { EventBroker, type EventRecord } from '@glandjs/events'import { RpcServer, type RpcOptions } from './server'import { RpcContext } from './context'
export class RpcBroker<TEvents extends EventRecord> extends BrokerAdapter< TEvents, RpcServer, RpcOptions> { constructor(options?: RpcOptions) { super(options)
this.broker = new EventBroker({ name: 'rpc' }) as any this.instance = new RpcServer(this.options) }
initialize(): RpcServer { this.broker.on(GLAND_ROUTE_EVENT, (route) => { this.instance.register(route.method, route.fullPath, async (req, res) => { const ctx = new RpcContext(this.instance.broker, req, res) // Attach the channel registry so ctx.call() can resolve names. ctx.attachRegistry?.(this.broker.id as string, this.instance.registry) return route.action(ctx) }) })
return this.instance }}import { GlandFactory } from '@glandjs/core'import { RpcBroker } from './rpc/broker'import { AppModule } from './app.module'
const app = await GlandFactory.create(AppModule)const server = app.connectTo(RpcBroker, { port: 4000 })server.listen()The controllers are unchanged. That is the point.
A context for your transport
Section titled “A context for your transport”Extend Context from @glandjs/core directly if you are not HTTP, or HttpContext
if you are.
import { Context, type ContextState } from '@glandjs/core'import type { EventRecord } from '@glandjs/events'
export class RpcContext<TEvents extends EventRecord> extends Context<TEvents> { constructor( broker: Broker<TEvents>, readonly method: string, readonly params: unknown, ) { super(broker) }
reply(result: unknown, error?: unknown): this { // Write the result back however your transport requires. this.result = { result, error } return this }}Context gives you state, setState(), emit(), call(), on(), once() and
off() for free. The only thing you must supply is how a reply is written.
Reusing @glandjs/http
Section titled “Reusing @glandjs/http”If your transport is HTTP-shaped, extend HttpCore and HttpBroker instead of
starting from scratch. HttpContext already implements the entire request and
response surface against four abstract members.
import { HttpBroker, HttpCore, HttpServerAdapter } from '@glandjs/http'
class MyCore<TEvents> extends HttpCore<Server, App, Request, Response> { constructor(options?: MyOptions) { super(new MyAdapter<TEvents>(), options) }}
class MyBroker<TEvents> extends HttpBroker<TEvents, MyCore<TEvents>, MyOptions> { constructor(options?: MyOptions) { super(new MyCore<TEvents>(options), options) }}This is exactly the shape @glandjs/express uses. See
@glandjs/http for the abstract members you need
to implement.
Checklist
Section titled “Checklist”- Extend
BrokerAdapter. Three generic parameters:TEvents,TApp,TOptions. - Create your broker in the constructor.
new EventBroker({ name: 'my-protocol' }). - Subscribe to
GLAND_ROUTE_EVENTininitialize(). Register each route on your application object. - Build a context. Extend
Context, orHttpContextfor HTTP-shaped transports. - Wire the channel registry.
ctx.attachRegistry(brokerId, registry)is what letsctx.call('product:list', …)resolve a public event name. If you skip it,callthrows. - Return the application from
initialize(). Its type is whatconnectTo()returns, so make it useful.