Skip to content

Commit b32c6e4

Browse files
committed
feat: quality pass — logger, RPC retries, asyncapi resilience, docs
Core: - Pluggable MqttLogger replaces internal console.* (drain timeout, validation, metric/error handler crashes); consoleLogger default, noopLogger for silence, app.getLogger() for plugins. - app.request() gains retries / retryDelay; only retries on RpcTimeoutError so non-idempotent commands aren't multiplied. - parseSharedSubscription() rejects NUL bytes, empty group, all-slash filter, and misplaced + / # wildcards. - Export MqttPacket type alias with usage notes. AsyncAPI: - listen() errors wrap host:port; long-lived error listener attached after startup; startup log routed through app.getLogger(). - New test suites: builder, handlers, plugin, yaml. zod: - peerDependencies now declares @mqttkit/core (closes silent cross-version installs). tsconfig: - noImplicitOverride enabled. Docs: - New Logger guide (en + zh) under Production sidebar section. - RPC page rewritten — documents retries/retryDelay, RpcTimeoutError, idempotency caveat, and the deliberate choice not to bundle p-retry. - New custom-logger example + docs page. - RPC example gains a flaky-downstream retry demo. - Inlined CHANGELOG body into docs/changelog.md (drops root CHANGELOG). Release tooling: - scripts/publish.mjs now diffs local vs npm version per package, auto-skips in-sync packages, refuses to publish when local < npm, and adds --check / --force flags. New publish:status npm script (avoids bun's prepublish:check lifecycle clash). Versions: @mqttkit/core 0.2.1 → 0.2.2, @mqttkit/asyncapi 0.2.0 → 0.2.1, @mqttkit/zod 0.1.0 → 0.1.1. aedes and typebox unchanged.
1 parent 82e9ce2 commit b32c6e4

42 files changed

Lines changed: 1885 additions & 213 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 0 additions & 143 deletions
This file was deleted.

README.md

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,66 @@ const app = new MqttApp<{ principal?: { uid: string } }>()
6060
await app.listen()
6161
```
6262

63+
## App API at a glance
64+
65+
The `MqttApp` builder methods covered in the quick start are listed below.
66+
Each returns `this`, so they chain.
67+
68+
| Method | Purpose |
69+
| --- | --- |
70+
| `use(plugin \| middleware)` | Register a plugin (`router()`, broker adapter, …) or a global middleware. |
71+
| `decorate(key, value)` | Inject a service into `ctx.services` with type inference on the App generic. |
72+
| `on(eventName, handler)` | Observe broker lifecycle events (`client.connect`, `client.subscribe`, …). |
73+
| `onStart(hook)` / `onStop(hook)` | Run code after `listen()` succeeds or before `stop()` finishes. |
74+
| `onError(handler)` | App-level error sink — receives `{ error, topic, phase, route, ctx }`. |
75+
| `onMetric(handler)` | Structured per-dispatch / per-publish events for Prometheus / OTel. |
76+
| `onBeforePublish(hook)` | Mutate outbound `{ topic, payload, options }` (e.g. inject `traceparent`). |
77+
| `logger(logger)` | Forward mqttkit's internal warnings into pino / OpenTelemetry / Sentry. |
78+
| `addSchemaProvider(provider)` | Plug in non-Standard-Schema validators (e.g. raw TypeBox). |
79+
| `publish(topic, payload, opts?)` | Server-side outbound publish (also funnels through `onBeforePublish`). |
80+
| `request(topic, payload, opts?)` | MQTT 5 RPC with optional `retries` / `retryDelay`. |
81+
| `stop({ drain?, timeout? })` | Graceful shutdown — defaults to draining in-flight handlers. |
82+
83+
### Error handling sketch
84+
85+
```ts
86+
import { MqttApp, router } from '@mqttkit/core'
87+
88+
const app = new MqttApp()
89+
.use(adapter)
90+
.onError(({ error, phase, topic }) => {
91+
// phases: middleware | handler | validation | policy | publish | timeout | overload
92+
sentry.captureException(error, { tags: { topic, phase } })
93+
})
94+
.use(
95+
router().topic('devices/:uid/events', {
96+
timeout: 1_000,
97+
concurrency: 100,
98+
onError: ({ error }) => metrics.routeFailures.inc(), // route-scoped, runs first
99+
async onMessage(ctx) {
100+
await doWork(ctx)
101+
},
102+
}),
103+
)
104+
```
105+
106+
### Custom logger
107+
108+
```ts
109+
import { MqttApp, type MqttLogger } from '@mqttkit/core'
110+
import { pino } from 'pino'
111+
112+
const log = pino({ name: 'mqttkit' })
113+
const logger: MqttLogger = {
114+
debug: (msg, meta) => log.debug(meta, msg),
115+
info: (msg, meta) => log.info(meta, msg),
116+
warn: (msg, meta) => log.warn(meta, msg),
117+
error: (msg, meta) => log.error(meta, msg),
118+
}
119+
120+
new MqttApp().logger(logger)
121+
```
122+
63123
## Schema Validation
64124

65125
`topic({ schema })` accepts any [Standard Schema](https://standardschema.dev/) validator (zod, valibot, arktype, …). The validated payload is exposed on `ctx.body` with full type inference.
@@ -89,7 +149,7 @@ See the [Getting Started guide](https://mqttkit.keyp.dev/getting-started) for ro
89149

90150
## Examples
91151

92-
Runnable examples under [`examples/`](./examples) cover the basic TCP / WebSocket broker, lifecycle events, service push, Kafka bridge, schema validation, MQTT 5 RPC, AsyncAPI docs (standalone HTTP or shared Elysia port), and Prometheus metrics.
152+
Runnable examples under [`examples/`](./examples) cover the basic TCP / WebSocket broker, lifecycle events, service push, Kafka bridge, schema validation, MQTT 5 RPC (including `app.request({ retries, retryDelay })`), AsyncAPI docs (standalone HTTP or shared Elysia port), Prometheus metrics, and a custom JSON logger (`examples/custom-logger`).
93153

94154
```bash
95155
bun install
@@ -103,3 +163,19 @@ bun run test
103163
bun run typecheck
104164
bun run build
105165
```
166+
167+
## Releasing
168+
169+
`scripts/publish.mjs` compares each package's local version against the npm registry and only publishes packages whose local version is ahead (or not on npm yet). Packages already in sync are silently skipped.
170+
171+
```bash
172+
bun run publish:status # report local vs npm without publishing
173+
bun run publish:dry-run # dry-run the actual publish flow
174+
bun run publish:packages # publish whatever needs publishing
175+
176+
# Single package by name (still subject to the version-ahead check)
177+
bun run publish:core
178+
179+
# Bypass the check (rarely needed)
180+
node scripts/publish.mjs --force
181+
```

README.zh-CN.md

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,65 @@ const app = new MqttApp<{ principal?: { uid: string } }>()
6060
await app.listen()
6161
```
6262

63+
## App API 一览
64+
65+
下列方法都返回 `this`,可以链式调用:
66+
67+
| 方法 | 用途 |
68+
| --- | --- |
69+
| `use(plugin \| middleware)` | 注册 plugin(`router()`、broker 适配器…)或全局 middleware。 |
70+
| `decorate(key, value)` | 把服务注入 `ctx.services`,类型推断会沿用到 App 泛型。 |
71+
| `on(eventName, handler)` | 监听 broker lifecycle event(`client.connect``client.subscribe`…)。 |
72+
| `onStart(hook)` / `onStop(hook)` | `listen()` 成功后或 `stop()` 之前运行的钩子。 |
73+
| `onError(handler)` | 应用级错误漏斗——接收 `{ error, topic, phase, route, ctx }`|
74+
| `onMetric(handler)` | 每次 dispatch / publish 完成时发结构化指标,对接 Prometheus / OTel。 |
75+
| `onBeforePublish(hook)` | 出站发布前修改 `{ topic, payload, options }`(例如注入 `traceparent`)。 |
76+
| `logger(logger)` | 把 mqttkit 内部告警导入 pino / OpenTelemetry / Sentry 管线。 |
77+
| `addSchemaProvider(provider)` | 注册非 Standard-Schema 的校验器(如裸 TypeBox)。 |
78+
| `publish(topic, payload, opts?)` | 服务端主动发布(同样走 `onBeforePublish`)。 |
79+
| `request(topic, payload, opts?)` | MQTT 5 RPC,支持 `retries` / `retryDelay`|
80+
| `stop({ drain?, timeout? })` | 优雅停机——默认会等 in-flight handler 排空。 |
81+
82+
### 错误处理示例
83+
84+
```ts
85+
import { MqttApp, router } from '@mqttkit/core'
86+
87+
const app = new MqttApp()
88+
.use(adapter)
89+
.onError(({ error, phase, topic }) => {
90+
// phase 取值: middleware | handler | validation | policy | publish | timeout | overload
91+
sentry.captureException(error, { tags: { topic, phase } })
92+
})
93+
.use(
94+
router().topic('devices/:uid/events', {
95+
timeout: 1_000,
96+
concurrency: 100,
97+
onError: ({ error }) => metrics.routeFailures.inc(), // 路由级,先于应用级运行
98+
async onMessage(ctx) {
99+
await doWork(ctx)
100+
},
101+
}),
102+
)
103+
```
104+
105+
### 自定义 logger
106+
107+
```ts
108+
import { MqttApp, type MqttLogger } from '@mqttkit/core'
109+
import { pino } from 'pino'
110+
111+
const log = pino({ name: 'mqttkit' })
112+
const logger: MqttLogger = {
113+
debug: (msg, meta) => log.debug(meta, msg),
114+
info: (msg, meta) => log.info(meta, msg),
115+
warn: (msg, meta) => log.warn(meta, msg),
116+
error: (msg, meta) => log.error(meta, msg),
117+
}
118+
119+
new MqttApp().logger(logger)
120+
```
121+
63122
## Schema 校验
64123

65124
`topic({ schema })` 接受任意 [Standard Schema](https://standardschema.dev/) 校验器(zod、valibot、arktype 等)。校验后的 payload 暴露在 `ctx.body` 上,并自动推断类型。
@@ -89,7 +148,7 @@ router、middleware、events、RPC、Kafka 桥接等详见 [快速开始](https:
89148

90149
## 示例
91150

92-
[`examples/`](./examples) 下有可运行示例:TCP / WebSocket broker、lifecycle events、service push、Kafka bridge、schema 校验、MQTT 5 RPC、AsyncAPI 文档(独立 HTTP 或与 Elysia 复用端口)、Prometheus 指标。
151+
[`examples/`](./examples) 下有可运行示例:TCP / WebSocket broker、lifecycle events、service push、Kafka bridge、schema 校验、MQTT 5 RPC(含 `app.request({ retries, retryDelay })`、AsyncAPI 文档(独立 HTTP 或与 Elysia 复用端口)、Prometheus 指标,以及自定义 JSON logger(`examples/custom-logger`
93152

94153
```bash
95154
bun install
@@ -103,3 +162,19 @@ bun run test
103162
bun run typecheck
104163
bun run build
105164
```
165+
166+
## 发布
167+
168+
`scripts/publish.mjs` 会把每个包的本地版本和 npm registry 比较,**只发布**本地版本领先(或 npm 上还不存在)的包;已经同步的包会被静默跳过。
169+
170+
```bash
171+
bun run publish:status # 仅打印 local vs npm 状态,不发布
172+
bun run publish:dry-run # 跑一次 dry-run 流水线
173+
bun run publish:packages # 发布所有需要发布的包
174+
175+
# 指定单个包(同样会先做版本检查)
176+
bun run publish:core
177+
178+
# 跳过检查强制发布(一般用不到)
179+
node scripts/publish.mjs --force
180+
```

bun.lock

Lines changed: 12 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)