README.md
May 25, 2026 · View on GitHub
@mdsiha369/adonis-mercure
AdonisJS v6 package to publish real-time updates via a Mercure Hub (SSE)
Table of Contents
Requirements
- AdonisJS v6
- Node.js >= 20.6.0
- A running Mercure Hub
Installation
npm install @mdsiha369/adonis-mercure
node ace configure @mdsiha369/adonis-mercure
Configuration
After running ace configure, a config/mercure.ts file is created and your .env is updated automatically.
// config/mercure.ts
import env from '#start/env'
import { defineConfig } from '@mdsiha369/adonis-mercure'
export default defineConfig({
endpoint: env.get('MERCURE_ENDPOINT'),
adminToken: env.get('MERCURE_ADMIN_JWT'),
jwt: {
alg: 'HS256',
secret: env.get('MERCURE_JWT_SECRET'),
},
// optional — default: 5000ms, set to 0 to disable
http: {
timeout: 5000,
},
})
Set the following env variables in your .env:
MERCURE_ENDPOINT=http://localhost:3000/.well-known/mercure
MERCURE_ADMIN_JWT=<your-admin-jwt>
MERCURE_JWT_SECRET=<your-jwt-secret>
Note: The
adminTokenmust be a JWT signed with your hub's secret and a"publish": ["*"]claim in themercurefield. See the Mercure auth docs for details.
Usage
Import the service anywhere in your app:
import mercure from '@mdsiha369/adonis-mercure/services/main'
Publish an update
await mercure.send('/orders/42', { status: 'shipped' })
Multiple topics
await mercure.send(['/orders/42', '/notifications/user/1'], { status: 'shipped' })
Private updates
Private updates are only delivered to authenticated subscribers who hold a valid token for that topic.
// shorthand (backward compatible)
await mercure.send('/orders/42', { status: 'shipped' }, true)
// options object
await mercure.send('/orders/42', { status: 'shipped' }, { private: true })
SSE options (id, type, retry)
await mercure.send(
'/orders/42',
{ status: 'shipped' },
{
id: 'msg-001', // event ID — enables reconnection recovery
type: 'order.shipped', // event type
retry: 5000, // client reconnect delay in ms
private: true,
}
)
Generate a subscriber token
Use this to create JWT tokens for your frontend clients so they can subscribe to topics, including private ones.
// typed shorthand
const token = await mercure.generateSubscribeToken(['/orders/42'])
// or low-level
const token = await mercure.generate({ subscribe: ['/orders/42'] })
Pass the token to your frontend:
const url = new URL('http://localhost:3000/.well-known/mercure')
url.searchParams.append('topic', '/orders/42')
const eventSource = new EventSource(url.toString(), {
headers: { Authorization: `Bearer ${token}` },
})
Health check
const isReachable = await mercure.ping() // true | false
Testing
FakeMercure lets you test your application code without a real Mercure Hub.
Swap the container binding in your test setup:
import { FakeMercure } from '@mdsiha369/adonis-mercure'
// before your test
app.container.swap('mercure', () => new FakeMercure())
// after your test
app.container.restore('mercure')
Assert on what was sent:
const fake = (await app.container.make('mercure')) as FakeMercure
// assert a topic received a message
fake.assertSent('/orders/42')
// assert a topic received a specific payload
fake.assertSent('/orders/42', { status: 'shipped' })
// assert a topic was never sent to
fake.assertNotSent('/admin/secret')
// assert nothing was sent at all
fake.assertNothingSent()
// inspect all recorded messages
const messages = fake.getSent()
// reset between tests
fake.clear()
API Reference
send(topics, data?, options?)
Publishes an update to the Mercure Hub.
| Parameter | Type | Default | Description |
|---|---|---|---|
topics | string | string[] | — | Topic(s) to publish to |
data | Record<string, unknown> | {} | Payload — serialized as JSON |
options | boolean | SendOptions | false | true for private (legacy), or a SendOptions object |
SendOptions
| Property | Type | Description |
|---|---|---|
private | boolean | Restrict delivery to authenticated subscribers |
id | string | SSE event ID (enables reconnection recovery) |
type | string | SSE event type |
retry | number | Client reconnect delay in milliseconds |
Throws MercurePublishError if the hub returns a non-2xx response.
Throws MercureTimeoutError if the hub does not respond within the configured timeout.
generateSubscribeToken(topics)
Generates a JWT with { subscribe: topics } for a frontend client.
const token = await mercure.generateSubscribeToken(['/chat/1', '/notifications/me'])
generate(payload)
Low-level JWT generation. Wraps payload under the mercure claim.
const token = await mercure.generate({ subscribe: ['/chat/1'], publish: ['/chat/1'] })
ping()
Returns true if the hub is reachable, false on network error or timeout.
Roadmap
Features planned for upcoming releases:
generatePublishToken(topics)— typed shorthand for publisher JWT tokens (symmetric togenerateSubscribeToken)sendBatch(messages[])— publish multiple updates in a single call- Retry with backoff — automatic retry on transient hub errors with configurable strategy
- Typed event classes — define reusable event objects (
new OrderShippedEvent(id)) instead of inline payloads
Have a use case or suggestion? Open an issue.
License
MIT — Michael DAŞ