Skip to content

Commit 023bd25

Browse files
committed
feat(asyncapi): add AsyncAPI documentation generator and HTTP plugin
- Introduced `@mqttkit/asyncapi` package for generating AsyncAPI 3.0 documentation. - Implemented core functionality to build AsyncAPI documents from MQTT routes. - Added support for serving JSON, YAML, and HTML documentation over HTTP. - Created example integration with Elysia and Aedes for MQTT-over-WS. - Updated tests to cover new publish policies and middleware behavior. - Included license and README files for the new package.
1 parent 292c72c commit 023bd25

21 files changed

Lines changed: 1283 additions & 10 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mqttkit does not reimplement the MQTT protocol. A broker such as Aedes owns CONN
1515
- `app.on()` observes broker lifecycle events such as client, publish, subscribe, ack, and errors.
1616
- `app.publish()` lets services, workers, and consumers push messages to MQTT clients through the broker.
1717
- `@mqttkit/aedes` provides TCP MQTT and MQTT-over-WebSocket support through Aedes.
18+
- `@mqttkit/asyncapi` generates AsyncAPI 3.0 documentation from routes and serves a browsable docs page.
1819
- Built for Bun and TypeScript.
1920

2021
## Installation
@@ -279,6 +280,8 @@ Use Aedes persistence adapters for offline queues, retain, QoS sessions, and dur
279280
- `examples/events`: broker lifecycle events.
280281
- `examples/service-push`: external service calls `app.publish()`, then Aedes delivers to subscribed MQTT clients.
281282
- `examples/kafka-bridge`: MQTT messages go to Kafka, and Kafka consumer messages are forwarded to MQTT clients.
283+
- `examples/asyncapi-docs`: AsyncAPI documentation served from `@mqttkit/asyncapi` (standalone HTTP).
284+
- `examples/asyncapi-elysia`: share a single Elysia port for both AsyncAPI docs and MQTT-over-WebSocket (aedes ws attached to the same `http.Server`).
282285

283286
Run an example:
284287

@@ -307,3 +310,4 @@ bun run build
307310

308311
- `@mqttkit/core`: core application, router, middleware, context, event types, and broker adapter interface.
309312
- `@mqttkit/aedes`: Aedes adapter for TCP MQTT and MQTT-over-WebSocket.
313+
- `@mqttkit/asyncapi`: AsyncAPI 3.0 generator and HTTP plugin for browsable docs.

README.zh-CN.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mqttkit 不重新实现 MQTT 协议。CONNECT、SUBSCRIBE、PUBLISH、QoS、reta
1515
- `app.on()` 监听 client、publish、subscribe、ack、错误等 broker lifecycle events。
1616
- `app.publish()` 让外部服务、worker、consumer 通过 broker 主动推送消息给 MQTT client。
1717
- `@mqttkit/aedes` 基于 Aedes 提供 TCP MQTT 与 MQTT-over-WebSocket。
18+
- `@mqttkit/asyncapi` 从路由生成 AsyncAPI 3.0 文档,并通过 HTTP 提供可浏览的文档页面。
1819
- 面向 Bun 与 TypeScript。
1920

2021
## 安装
@@ -279,6 +280,8 @@ new MqttApp()
279280
- `examples/events`:监听 broker lifecycle events。
280281
- `examples/service-push`:外部服务调用 `app.publish()`,消息经 Aedes 投递到订阅中的 MQTT client。
281282
- `examples/kafka-bridge`:MQTT 消息写入 Kafka,并把 Kafka consumer 消息透传给 MQTT client。
283+
- `examples/asyncapi-docs`:通过 `@mqttkit/asyncapi` 提供 AsyncAPI 文档与浏览页面(独立 HTTP 端口)。
284+
- `examples/asyncapi-elysia`:Elysia 端口同时承载 AsyncAPI 文档与 MQTT-over-WebSocket(aedes ws 复用同一个 `http.Server`)。
282285

283286
运行示例:
284287

@@ -307,3 +310,4 @@ bun run build
307310

308311
- `@mqttkit/core`:core application、router、middleware、context、event types 与 broker adapter 接口。
309312
- `@mqttkit/aedes`:Aedes adapter,提供 TCP MQTT 与 MQTT-over-WebSocket 接入。
313+
- `@mqttkit/asyncapi`:基于路由生成 AsyncAPI 3.0 文档与 HTTP 浏览页面。

bun.lock

Lines changed: 65 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "@mqttkit/example-asyncapi-docs",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "bun run src/index.ts",
7+
"typecheck": "tsc --noEmit"
8+
},
9+
"dependencies": {
10+
"@mqttkit/aedes": "workspace:*",
11+
"@mqttkit/asyncapi": "workspace:*",
12+
"@mqttkit/core": "workspace:*"
13+
}
14+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { aedes } from '@mqttkit/aedes'
2+
import { asyncapi } from '@mqttkit/asyncapi'
3+
import { MqttApp, router } from '@mqttkit/core'
4+
5+
type Principal = { uid: string }
6+
type State = { principal?: Principal }
7+
8+
const deviceEventSchema = {
9+
type: 'object',
10+
required: ['temperature'],
11+
properties: {
12+
temperature: { type: 'number', description: 'Celsius reading' },
13+
humidity: { type: 'number' },
14+
ts: { type: 'integer', description: 'Unix ms' },
15+
},
16+
}
17+
18+
const notificationSchema = {
19+
type: 'object',
20+
required: ['kind', 'body'],
21+
properties: {
22+
kind: { type: 'string', enum: ['invoice', 'system', 'chat'] },
23+
body: { type: 'string' },
24+
},
25+
}
26+
27+
const app = new MqttApp<State>()
28+
.use(
29+
aedes({
30+
tcp: { port: 1883 },
31+
authenticate: ({ username }) => {
32+
if (!username) return false
33+
return { uid: username }
34+
},
35+
}),
36+
)
37+
.use(
38+
router<State>()
39+
.topic('devices/:uid/events', {
40+
publish: ({ params, principal }) => params.uid === principal?.uid,
41+
qos: 1,
42+
schema: deviceEventSchema,
43+
async onMessage(ctx) {
44+
await ctx.publish(`server/${ctx.params.uid}/echo`, ctx.payload, { qos: 0 })
45+
},
46+
meta: {
47+
summary: 'Device telemetry uplink',
48+
description: 'Device pushes sensor readings. Only the owning principal may publish.',
49+
tags: ['device', 'telemetry'],
50+
examples: [{ temperature: 22.5, humidity: 60, ts: Date.now() }],
51+
},
52+
})
53+
.topic('server/:uid/echo', {
54+
subscribe: ({ params, principal }) => params.uid === principal?.uid,
55+
qos: 0,
56+
meta: {
57+
summary: 'Server echo channel',
58+
description: 'Server echoes device events back to the owning client.',
59+
tags: ['device'],
60+
},
61+
})
62+
.topic('users/:uid/notifications', {
63+
subscribe: ({ params, principal }) => params.uid === principal?.uid,
64+
publish: false,
65+
qos: 1,
66+
retain: true,
67+
schema: notificationSchema,
68+
meta: {
69+
summary: 'User notifications',
70+
description: 'Server-pushed notifications. Clients subscribe only.',
71+
tags: ['notifications'],
72+
},
73+
}),
74+
)
75+
.use(
76+
asyncapi({
77+
info: {
78+
title: 'mqttkit demo',
79+
version: '0.0.1',
80+
description: 'AsyncAPI generated from mqttkit router metadata.',
81+
},
82+
servers: {
83+
tcp: { host: 'localhost:1883', protocol: 'mqtt', description: 'Aedes TCP broker' },
84+
},
85+
port: 9000,
86+
}),
87+
)
88+
89+
await app.listen()
90+
console.log('mqtt://localhost:1883 | docs: http://localhost:9000/docs')
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@mqttkit/example-asyncapi-elysia",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "bun run src/index.ts",
7+
"typecheck": "tsc --noEmit"
8+
},
9+
"dependencies": {
10+
"@mqttkit/aedes": "workspace:*",
11+
"@mqttkit/asyncapi": "workspace:*",
12+
"@mqttkit/core": "workspace:*",
13+
"aedes": "^0.51.3",
14+
"elysia": "^1.4.29",
15+
"mqtt": "^5.15.1"
16+
}
17+
}

0 commit comments

Comments
 (0)