Skip to content

Subscriptions ​

Subscriptions are Hedystia's real-time channel mechanism. They sit alongside regular HTTP routes and share the same schema system, lifecycle hooks, and type safety.

By default, subscriptions use WebSockets. You can switch to Server-Sent Events (SSE) with a constructor option.

Defining a Subscription ​

ts
import 
Hedystia
, {
h
} from 'hedystia'
const
app
= new
Hedystia
()
.
subscription
(
'/updates', async ({
sendData
}) => {
sendData
({
event
: 'connected',
timestamp
:
Date
.
now
() })
}, {
data
:
h
.
object
({
event
:
h
.
string
(),
timestamp
:
h
.
number
(),
}),
error
:
h
.
object
({
message
:
h
.
string
(),
}), } ) .
listen
(3000)

Subscription Context ​

Inside the subscription handler, the context includes:

PropertyDescription
paramsPath parameters (typed and validated)
queryQuery string (typed and validated)
headersRequest headers
sendData(data)Push data to this client
sendError(error)Push an error event to this client
isActive()Returns Promise<boolean> — whether the client is still connected
subscriptionIdUnique ID for this subscription
onMessage(handler)Register a handler for incoming messages from this client
publishPublish to a topic (all subscribers)

Dynamic Path Parameters ​

ts
const 
app
= new
Hedystia
()
.
subscription
(
'/rooms/:roomId', async ({
params
,
sendData
}) => {
sendData
({
joined
:
params
.
roomId
})
}, {
params
:
h
.
object
({
roomId
:
h
.
string
() }),
data
:
h
.
object
({
joined
:
h
.
string
() }),
} )

The client matches the path structure:

ts
import { 
createClient
} from '@hedystia/client'
const
client
=
createClient
<any>('http://localhost:3000')
client
.
rooms
.roomId('lobby').subscribe(({
data
}) => {
console
.
log
(
data
?.joined) // 'lobby'
})

Publishing to Subscribers ​

Use app.publish(topic, options) to push data to all clients subscribed to a topic:

ts
const 
app
= new
Hedystia
()
.
subscription
('/chat', async () => {}, {
data
:
h
.
object
({
message
:
h
.
string
(),
author
:
h
.
string
() }),
}) .
post
(
'/chat/send', ({
body
}) => {
app
.
publish
('/chat', {
data
: {
message
:
body
.
message
,
author
:
body
.
author
} })
return {
sent
: true }
}, {
body
:
h
.
object
({
message
:
h
.
string
(),
author
:
h
.
string
() }),
} ) .
listen
(3000)

Tree-Based Publish API ​

Hedystia also provides a typed tree API via app.pub:

ts
import 
Hedystia
, {
h
} from 'hedystia'
const
app
= new
Hedystia
()
// Instead of: app.publish('/chat', { data: { ... } }) // Use:
app
.
pub
.chat({
data
: {
message
: 'hello',
author
: 'Alice' } })

Receiving Messages from Clients ​

Use ctx.onMessage to register a handler for client→server messages:

ts
app
.
subscription
(
'/commands', async ({
sendData
,
onMessage
}) => {
onMessage
((
msg
) => {
console
.
log
('Received command:',
msg
.
action
)
sendData
({
received
:
msg
.
action
})
}) }, {
message
:
h
.
object
({
action
:
h
.
string
() }),
data
:
h
.
object
({
received
:
h
.
string
() }),
} )

Subscription Lifecycle Events ​

ts
import 
Hedystia
from 'hedystia'
const
app
= new
Hedystia
()
app
.
onSubscriptionOpen
(({
path
,
subscriptionId
}) => {
console
.
log
(`Opened: ${
path
} [${
subscriptionId
}]`)
}) .
onSubscriptionClose
(({
path
,
subscriptionId
,
reason
}) => {
console
.
log
(`Closed: ${
path
} [${
subscriptionId
}] reason=${
reason
}`)
}) .
onSubscriptionMessage
(({
path
,
message
,
sendData
}) => {
console
.
log
(`Message on ${
path
}:`,
message
)
})

SSE Mode ​

Switch from WebSocket to Server-Sent Events by setting sse: true:

ts
import 
Hedystia
, {
h
} from 'hedystia'
const
app
= new
Hedystia
({
sse
: true })
.
subscription
('/feed', async ({
sendData
}) => {
sendData
({
item
: 'first update' })
}, {
data
:
h
.
object
({
item
:
h
.
string
() }),
}) .
listen
(3000)

SSE is useful when clients are browsers that don't support or prefer full WebSocket connections. The client connects via a GET request and receives an event stream.

Heartbeat and Reconnection ​

Hedystia automatically sends heartbeat checks every 30 seconds. If no pong is received within 60 seconds, the connection is closed. Reconnecting clients within a 5-second grace period are seamlessly re-attached to their subscription without triggering close handlers.

These values are internal constants — contact the maintainers if you need to adjust them.

Complete Example ​

ts
import 
Hedystia
, {
h
} from 'hedystia'
export const
app
= new
Hedystia
()
.
subscription
(
'/guild/:id', async ({
params
}) => {
return {
id
:
params
.
id
,
lang
: 'en' } // initial data sent on connect
}, {
params
:
h
.
object
({
id
:
h
.
string
() }),
data
:
h
.
object
({
id
:
h
.
string
(),
lang
:
h
.
options
(
h
.
literal
('en'),
h
.
literal
('es')),
}),
error
:
h
.
object
({
id
:
h
.
string
() }),
} ) .
post
(
'/guild/:id/update', ({
body
,
params
}) => {
app
.
publish
(`/guild/${
params
.
id
}`, {
data
: {
id
:
params
.
id
,
lang
:
body
.
lang
},
}) return {
ok
: true }
}, {
params
:
h
.
object
({
id
:
h
.
string
() }),
body
:
h
.
object
({
lang
:
h
.
options
(
h
.
literal
('en'),
h
.
literal
('es')),
}), } ) .
listen
(3000)