Skip to content

Defining Events And Channels ​

Defining Events ​

Define events as factories that receive runtime values. Do not hardcode dynamic IDs in event files.

ts
import { defineBroadcast, privateChannel } from '@holo-js/broadcast'

export function orderShipmentUpdated(orderId: string, status: 'shipped' | 'delayed') {
  return defineBroadcast({
    name: 'orders.shipment-updated',
    channels: [
      privateChannel('orders.{orderId}', { orderId }),
    ],
    payload: {
      orderId,
      status,
    },
  })
}

Broadcasting Events ​

ts
import { broadcast } from '@holo-js/broadcast'
import { orderShipmentUpdated } from '@/server/broadcast/orders/shipment-updated'

await broadcast(orderShipmentUpdated(order.id, order.status))
  .using('holo')
  .onQueue('broadcast')
  .delay(300)
  .afterCommit()

Broadcast Dispatch Options ​

Available options on broadcast(...):

  • .using('connection-name')
  • .onConnection('queue-connection-name')
  • .onQueue('queue-name')
  • .delay(ms | Date)
  • .afterCommit()

Raw Broadcasting ​

Use broadcastRaw when you need direct event/channels/payload dispatch without an event factory:

ts
import { broadcastRaw } from '@holo-js/broadcast'

await broadcastRaw({
  connection: 'holo',
  event: 'orders.shipment-updated',
  channels: [`orders.${orderId}`],
  payload: { orderId, status: 'shipped' },
})

Authorizing Private Channels ​

Private and presence channels require /broadcasting/auth. Flux clients discover browser-safe connection settings through /broadcasting/config. Both URLs are generated by holo install broadcast; user code does not create route files or import generated route modules.

Channel authorization files live in server/channels:

ts
import { defineChannel } from '@holo-js/broadcast'

export default defineChannel('orders.{orderId}', {
  type: 'private',
  authorize(user, params) {
    return Boolean(user && typeof user === 'object' && String((user as { id: unknown }).id) === params.orderId)
  },
})

/broadcasting/auth is the canonical auth endpoint for private and presence subscriptions. /broadcasting/config is the canonical client configuration endpoint used by Flux. Holo wires both endpoints for the active framework.

When guard is omitted, channel authorization uses the default auth guard from config/auth.ts.

ts
import { defineChannel } from '@holo-js/broadcast'

export default defineChannel('users.{userId}', {
  type: 'private',
  authorize(user, params) {
    return Boolean(user && typeof user === 'object' && String((user as { id: unknown }).id) === params.userId)
  },
})

Set guard when a channel must authenticate against a non-default guard:

ts
import { defineChannel } from '@holo-js/broadcast'

export default defineChannel('admin.blog', {
  type: 'private',
  guard: 'admin',
  authorize(user) {
    return Boolean(user)
  },
})

guard can also be resolved from channel parameters:

ts
import { defineChannel } from '@holo-js/broadcast'

export default defineChannel('teams.{teamId}.{area}', {
  type: 'private',
  guard({ params }) {
    return params.area === 'admin' ? 'admin' : 'web'
  },
  authorize(user) {
    return Boolean(user)
  },
})

Authorizing Presence Channels ​

Presence channel authorization returns false to deny or member payload to allow:

ts
import { defineChannel } from '@holo-js/broadcast'

export default defineChannel('chat.{roomId}', {
  type: 'presence',
  authorize(user, params) {
    if (!user || typeof user !== 'object') {
      return false
    }

    return {
      id: String((user as { id: unknown }).id),
      roomId: params.roomId,
    }
  },
})

Holo owns backend runtime concerns. The host framework owns SSR and routing.