Simple message queue backed by Autobee.
HyperMQ is a peer-to-peer message queue that uses Hyperswarm for discovery and Autobee for persistent, replicated storage. It supports pub/sub (fan-out) and work queue (competing consumer) messaging patterns.
npm install hypermq
You will also need corestore for storage:
npm install corestore
const Corestore = require('corestore')
const HyperMQ = require('hypermq')
const b4a = require('b4a')
const store = new Corestore('./my-storage')
const mq = new HyperMQ(store)
await mq.ready()
mq.subscribe('greetings', (msg) => {
console.log(b4a.toString(msg.data)) // 'hello world'
})
await mq.publish('greetings', 'hello world')
await mq.close()Create a new HyperMQ instance.
corestore- a Corestore instancekey- optional Buffer or hex string to join an existing queue (omit to create a new one)opts- optional configuration object
| Option | Type | Default | Description |
|---|---|---|---|
producer |
boolean | false |
Producer-only mode. Will not consume work queue messages. |
concurrent |
number | 0 |
Default concurrent workers for published messages. 0 means pub/sub mode. |
timeout |
number | 5000 |
Timeout (ms) for work queue claim evaluation. |
keyPair |
object | null |
Custom key pair for this instance. |
swarm |
Hyperswarm | null |
Custom Hyperswarm instance. Creates its own if not provided. |
maxOrphanAcks |
number | 10000 |
Maximum orphan acks to keep in memory. |
Ensure the instance is initialized. Called automatically by publish, flush, addWriter, and removeWriter.
Publish a message.
topic- non-empty stringdata- payload (string, Buffer, object, array, number, boolean, or null)opts.concurrent- override the instance-levelconcurrentsetting for this message
Returns a 32-byte Buffer key identifying the message.
Subscribe to messages on a topic.
The callback receives a message object:
{
topic, // string
data, // Buffer
key, // Buffer (32 bytes)
concurrent, // number
setStatus // function (see below)
}Every message object includes a setStatus helper. Calling it appends a status update for the message to the replicated log. The status is persisted in the view and propagated to all peers reactively.
status- Buffer, string, or JSON-serializable value
Only has effect when the instance is writable. Last write wins in the view.
For work queue messages (concurrent > 0), the ack is sent in parallel to the callback execution.
Remove a subscription. If callback is omitted, all subscribers for the topic are removed.
Process work-queue messages (concurrent > 0) that were buffered in _pending during log replay (for example because subscribe() was called after ready()). Call after subscribe() on a consumer. Pub/sub messages left in _pending are scheduled via the normal delivery path.
Wait for an acknowledgment.
key- 32-byte Buffer frompublish(). If omitted, waits for the next ack on any message.
Returns { consumer } where consumer is a Buffer identifying the acknowledging peer, or null if the instance closes before an ack arrives.
Authorize a remote peer to write to the queue.
key- 32-byte Buffer or hex string (the remote peer's writer key)
Revoke write access for a peer.
Subscribe to status updates. The callback is invoked whenever a status update is applied (locally or via replication).
key- optional 32-byte Buffer or hex string. If provided, only status updates for that message trigger the callback. If omitted, all status updates trigger it.callback(key, status)-keyis the 32-byte message Buffer,statusis a Buffer.
Remove a status subscription. If key is omitted, removes from the wildcard set. If callback is also omitted, removes all subscriptions for that key (or all wildcard subscriptions).
Flush all pending operations to the underlying Autobee.
Close the instance and release all resources. Pending waitAck promises resolve to null.
| Property | Type | Description |
|---|---|---|
mq.key |
Buffer | 32-byte queue key (available after ready()) |
mq.discoveryKey |
Buffer | Discovery key used for Hyperswarm (available after ready()) |
mq.writable |
boolean | Whether this instance can write to the queue |
Emitted on non-fatal errors (malformed entries, failed callbacks, etc.).
When concurrent is 0 (the default), messages are delivered to all subscribers on a topic. This is a fan-out broadcast pattern.
mq.subscribe('news', (msg) => {
console.log('Subscriber A:', b4a.toString(msg.data))
})
mq.subscribe('news', (msg) => {
console.log('Subscriber B:', b4a.toString(msg.data))
})
await mq.publish('news', 'breaking story')
// Both subscribers receive the messageWhen concurrent is greater than 0, messages are delivered to a single consumer at a time. Consumers are selected by XOR distance from the message key, with timeout-based fallback. The ack is sent automatically after the subscriber callback completes.
mq.subscribe('jobs', async (msg) => {
await processTask(b4a.toString(msg.data))
// Ack is sent after this callback resolves
})
const key = await mq.publish('jobs', 'resize-image-42', { concurrent: 1 })
const ack = await mq.waitAck(key)
console.log('Processed by:', b4a.toString(ack.consumer, 'hex'))Any subscriber (pub/sub or work queue) can update the status of a message by calling msg.setStatus(status). Status updates are appended to the replicated log, persisted in the view, and propagated to all peers reactively -- no polling required.
Producers (or any peer) can subscribe to status changes with mq.onStatus():
// Consumer reports progress
consumer.subscribe('jobs', async (msg) => {
msg.setStatus({ status: 'running', progress: 0 })
await doWork()
msg.setStatus({ status: 'running', progress: 50 })
await doMoreWork()
msg.setStatus({ status: 'done', progress: 100 })
})
// Producer subscribes to a specific job's status
const key = await producer.publish('jobs', 'resize-image', { concurrent: 1 })
producer.onStatus(key, (k, state) => {
console.log('Status:', JSON.parse(b4a.toString(state)))
})
// Or subscribe to all status updates
producer.onStatus((key, state) => {
console.log('Any status:', JSON.parse(b4a.toString(state)))
})See examples/job-status.js for a full working example.
publish() accepts strings, Buffers, objects, arrays, numbers, booleans, and null. Non-Buffer values are JSON-encoded before storage. The subscriber always receives msg.data as a Buffer:
await mq.publish('topic', { hello: 'world' })
mq.subscribe('topic', (msg) => {
const obj = JSON.parse(b4a.toString(msg.data))
// { hello: 'world' }
})Apache-2.0