@crvouga/mockingbird-service-emqx 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog — @crvouga/mockingbird-service-emqx
2
+
3
+ ## 0.2.0 (2026-10-12)
4
+
5
+ ### Features
6
+
7
+ - unify the emulator control plane ([2068547](https://github.com/crvouga/mockingbird/commit/2068547bedde4e2e2331c1657c1a78120e84428b))
8
+
9
+ ### Dependencies
10
+
11
+ - `@crvouga/mockingbird-service-sqlite`
12
+
13
+ ## 0.1.0 (2026-10-10)
14
+
15
+ Initial release.
package/DISCOVERY.md ADDED
@@ -0,0 +1,78 @@
1
+ # @crvouga/mockingbird-service-emqx discovery
2
+
3
+ This is the installed-package index for coding agents and tooling. All relative links resolve
4
+ inside `node_modules/@crvouga/mockingbird-service-emqx/`; no repository checkout is needed to discover the emulator's
5
+ supported surface or documented behavior.
6
+
7
+ ## Capability and behavior sources
8
+
9
+ | Question | Authoritative file | What it contains |
10
+ | --- | --- | --- |
11
+ | Behaviour and integration | [`README.md`](README.md) | Routes, state transitions, auth, webhooks, controls, presets and deliberate omissions. |
12
+ | Exact capabilities | [`SUPPORT.md`](SUPPORT.md) | Supported, unsupported and parity-covered operations or commands, including reasons for gaps. |
13
+ | Wire contract | [`openapi.yaml`](openapi.yaml) | Machine-readable paths, methods, schemas, responses and parity annotations. |
14
+ | Public API | [`dist/index.d.ts`](dist/index.d.ts) | The installed package's exact TypeScript exports and signatures. |
15
+ | Package metadata | [`package.json`](package.json) | Runtime/entry-point claims, vendor links, parity scope/tier and `mockingbird.discovery`. |
16
+
17
+ Read these together: the contract/capability matrix says *what* is available, while the README
18
+ defines stateful behavior, lifecycle rules, test controls, and intentional oracle differences.
19
+ If prose and an executable surface disagree, report a parity mismatch instead of adding a
20
+ consumer-side workaround.
21
+
22
+ ## Parity and oracle
23
+
24
+ - Declared parity surface: **MQTT 5 sessions, JWT ACLs, REST publish and client kick**.
25
+ - Parity tier: **cold** (the repository controls when live checks run).
26
+ - Oracle: **Live vendor API or sandbox**.
27
+ - Repository command: `bun run parity:service -- emqx`.
28
+ - Evidence model: Run from a Mockingbird checkout; credentials come only from .env.local or GitHub Actions secrets. Missing credentials exit 2.
29
+
30
+ The npm package contains evidence summaries and the exact contract, not credentials or the
31
+ repository-only parity harness. Self-parity/property and acceptance tests run in the Mockingbird
32
+ repository; live parity is an additional oracle check, not a substitute for the packaged matrix.
33
+
34
+ ## Runtime introspection
35
+
36
+ - `GET /__admin/health`
37
+ - `GET /__admin`
38
+ - `GET /__admin/controls`
39
+ - `GET /__admin/state`
40
+ - `GET /__admin/requests`
41
+ - `GET /__admin/metrics`
42
+ - `GET /__admin/faults/presets`
43
+ - `GET /__admin/ui`
44
+
45
+ For HTTP services, use `x-mockingbird-namespace` (or the documented credential/path carrier) so
46
+ parallel tests do not share state. Admin state, journal, metrics and fault-preset endpoints are
47
+ designed for assertions and diagnosis by consuming test suites.
48
+
49
+ ## Admin controls
50
+
51
+ Every HTTP emulator takes the same controls, and each means the same thing at boot
52
+ (`createRuntime({ adminControls })`, `serve --admin-controls`), over `/__admin`, and in a request header:
53
+
54
+ | Control | Admin API | Request header | Interface |
55
+ | --- | --- | --- | --- |
56
+ | Isolate state by key | `GET /__admin/namespaces` | `x-mockingbird-namespace: <key>` | [Namespaces](https://mockingbird.chrisvouga.dev/docs/admin-controls/namespaces) |
57
+ | Reset to boot | `POST /__admin/reset` | `x-mockingbird-reset: true` | [Reset](https://mockingbird.chrisvouga.dev/docs/admin-controls/reset) |
58
+ | Snapshot and restore | `POST /__admin/snapshots`, `POST /__admin/snapshots/<id>/restore` | `x-mockingbird-snapshot: <name>`, `x-mockingbird-restore: <name>` | [Snapshot and restore](https://mockingbird.chrisvouga.dev/docs/admin-controls/snapshot-and-restore) |
59
+ | Move the clock | `POST /__admin/clock` | `x-mockingbird-time: <instant>`, `x-mockingbird-advance: <duration>` | [Clock](https://mockingbird.chrisvouga.dev/docs/admin-controls/clock) |
60
+ | Read or branch from old state | `GET /__admin/timeline` | `x-mockingbird-at: <checkpoint>`, `x-mockingbird-branch: <name>` | [Checkpoints and branches](https://mockingbird.chrisvouga.dev/docs/admin-controls/checkpoints-and-branches) |
61
+ | Inject a fault | `POST /__admin/faults` | `x-mockingbird-fault: <fault>` | [Faults](https://mockingbird.chrisvouga.dev/docs/admin-controls/faults) |
62
+ | Latency and timeouts | `PUT /__admin/latency` | `x-mockingbird-delay: <duration>`, `x-mockingbird-timeout: <duration>` | [Latency and timeouts](https://mockingbird.chrisvouga.dev/docs/admin-controls/latency-and-timeouts) |
63
+ | Replay from a seed | `PUT /__admin/controls` | `x-mockingbird-seed: <seed>` | [Seed and determinism](https://mockingbird.chrisvouga.dev/docs/admin-controls/seed-and-determinism) |
64
+ | See what happened | `GET /__admin/requests`, `GET /__admin/state` | | [Journal, metrics and state](https://mockingbird.chrisvouga.dev/docs/admin-controls/journal-and-state) |
65
+
66
+ `GET /__admin` lists every route and header with a one-line summary, and `GET /__admin/controls`
67
+ is the namespace's current control document. The same seed and the same requests give the same
68
+ bytes. Setting controls as an emulator starts is [Boot](https://mockingbird.chrisvouga.dev/docs/admin-controls/boot); every route and header is in
69
+ the [Reference](https://mockingbird.chrisvouga.dev/docs/admin-controls/reference). The full guide: <https://mockingbird.chrisvouga.dev/docs/admin-controls>.
70
+
71
+ ## Report a mismatch or missing capability
72
+
73
+ Follow the [agent reporting contract](https://github.com/crvouga/mockingbird/blob/main/docs/REPORTING_ISSUES.md). Include package version,
74
+ operation/command, a minimal redacted request, actual emulator result, expected oracle result or vendor
75
+ documentation, and whether the mismatch appears in the matrix. Never include keys, tokens,
76
+ customer data, prompts, PHI, card data, or unredacted recordings.
77
+
78
+ Service key: `emqx`.
package/README.md CHANGED
@@ -1,3 +1,211 @@
1
- # Temporary Holding Version
1
+ # @crvouga/mockingbird-service-emqx
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > Local emulators. Real API contracts. Part of [Mockingbird](https://github.com/crvouga/mockingbird).
4
+
5
+ Stateful emulator of an **EMQX 5** MQTT broker for test suites and local development: an MQTT 5
6
+ broker over TCP and WebSocket (`/mqtt`), EMQX's password and JWT authentication with JWT ACLs,
7
+ persistent sessions with offline QoS 1 queues, and the two REST v5 endpoints a publish-only
8
+ client needs (`POST /api/v5/publish`, `DELETE /api/v5/clients/{clientid}`). It runs in the test
9
+ process on OS-assigned ports, with no installed broker, Docker, or native binary, and session
10
+ expiry follows a clock the suite moves instead of waiting.
11
+
12
+ - Operation coverage (HTTP): [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/emqx/SUPPORT.md)
13
+ - No EMQX broker was available as an oracle, so behaviour follows the
14
+ [MQTT 5.0 specification](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html), the
15
+ [EMQX documentation](https://docs.emqx.com/en/emqx/latest/) and the EMQX 5.8.6 source. What
16
+ those do not settle is listed under [Unverified](#unverified).
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ npm install -D @crvouga/mockingbird-service-emqx
22
+ ```
23
+
24
+ ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with
25
+ `npx mockingbird-emqx serve` or `createServer` from `./server` (Node and Bun). `createRuntime`
26
+ (the default entry) is portable: it serves the REST API over `fetch` and MQTT through
27
+ `runtime.attach`, with no listener.
28
+
29
+ ## Usage
30
+
31
+ Point the app's MQTT URL at `mqttUrl` (or `wsUrl`), its EMQX API origin at `url`, and give it the
32
+ API key, MQTT user and JWT secret you configured here.
33
+
34
+ ```ts
35
+ import { createServer } from "@crvouga/mockingbird-service-emqx/server"
36
+ import mqtt from "mqtt"
37
+
38
+ const emqx = await createServer({
39
+ settings: {
40
+ apiKeys: [{ key: "test-key", secret: "test-secret" }],
41
+ users: [{ username: "service", password: "service-password", superuser: true }],
42
+ jwt: { secret: "test-jwt-secret" }, // HS256 token in the CONNECT password
43
+ authorization: { noMatch: "deny" },
44
+ },
45
+ })
46
+ // emqx.url http://127.0.0.1:<port> REST API and /__admin
47
+ // emqx.mqttUrl mqtt://127.0.0.1:<port> MQTT 5 over TCP
48
+ // emqx.wsUrl ws://127.0.0.1:<port>/mqtt MQTT 5 over WebSocket
49
+
50
+ const client = await mqtt.connectAsync(emqx.mqttUrl, {
51
+ protocolVersion: 5,
52
+ clientId: "device-1",
53
+ clean: false,
54
+ properties: { sessionExpiryInterval: 600 },
55
+ username: "service",
56
+ password: "service-password",
57
+ })
58
+ await client.subscribeAsync("space:example", { qos: 1 })
59
+
60
+ // EMQX publishes no JavaScript SDK for its REST API, so the publisher calls it over HTTP.
61
+ await fetch(`${emqx.url}/api/v5/publish`, {
62
+ method: "POST",
63
+ headers: {
64
+ authorization: `Basic ${btoa("test-key:test-secret")}`,
65
+ "content-type": "application/json",
66
+ },
67
+ body: JSON.stringify({ topic: "space:example", payload: "hello", qos: 1, retain: false }),
68
+ })
69
+
70
+ // Test control: move time instead of waiting for a session to expire.
71
+ await fetch(`${emqx.url}/__admin/clock`, { method: "POST", body: '{"advance": "600s"}' })
72
+
73
+ await client.endAsync()
74
+ await emqx.close() // closes every listener and drops every client
75
+ ```
76
+
77
+ ```bash
78
+ npx mockingbird-emqx serve --port 8850 --mqtt-port 1883 \
79
+ --api-key test-key:test-secret --mqtt-user service:service-password \
80
+ --jwt-secret test-jwt-secret --no-match deny
81
+ ```
82
+
83
+ Each `createServer()` is its own broker with its own ports; two of them share nothing. Under
84
+ Bun, `mqtt`'s Node WebSocket transport does not start (Bun's `ws` has no
85
+ `createWebSocketStream`), so connect to `wsUrl` with the library's browser transport
86
+ (`new MqttClient((c) => browserStreamBuilder(c, options), options)` from `mqtt/lib/connect/ws`),
87
+ or use `connectStream(runtime)` from `./server` for a socket-free connection. On Node the plain
88
+ `mqtt.connect("ws://…/mqtt")` works.
89
+
90
+ ### MQTT
91
+
92
+ MQTT 5.0 over TCP and over WebSocket at `/mqtt` (subprotocol `mqtt`, also `mqtt-v3`,
93
+ `mqtt-v3.1.1`, `mqtt-v5`; an upgrade without one is answered `400`, as EMQX does with
94
+ `fail_if_no_subprotocol`). Both transports reach the same sessions.
95
+
96
+ | Packet | Behaviour |
97
+ | --- | --- |
98
+ | CONNECT / CONNACK | Clean Start and Session Expiry Interval as in the specification: `clean: false` resumes a stored session (`sessionPresent: true`), a clean start discards it. EMQX takes the client's interval as sent. The CONNACK carries Maximum Packet Size 1048576, Topic Alias Maximum 65535, Receive Maximum 32, Wildcard Subscription Available 1, Subscription Identifier Available 1, and what this emulator does not do: Maximum QoS 1, Retain Available 0, Shared Subscription Available 0. An empty client id is assigned 16 characters. |
99
+ | Authentication | EMQX's chain: the built-in database (`users`), then the JWT authenticator (`jwt`). With neither configured anyone connects, as on a fresh EMQX. A wrong password is CONNACK `0x86`; a token that verifies but is expired (`exp`), not yet valid (`nbf`), fails `verify_claims` or has a malformed `acl` is `0x86`; a token that does not verify, or credentials no authenticator recognises, is `0x87`. |
100
+ | Second CONNECT with a connected client id | The old connection gets DISCONNECT `0x8E` Session taken over; the new one resumes the session unless it asked for a clean start. |
101
+ | SUBSCRIBE / SUBACK | Exact filters and `+` / `#` wildcards, No Local, Subscription Identifiers. Granted QoS is at most 1. A denied filter is SUBACK `0x87`; `$share/…` is `0x9E`. |
102
+ | PUBLISH / PUBACK | QoS 0 and 1. PUBACK is `0x00`, `0x10` when nobody subscribes (EMQX's behaviour), or `0x87` when denied (the message is dropped; a denied QoS 0 publish is dropped silently). Properties (content type, response topic, correlation data, user properties, payload format indicator, message expiry interval) are forwarded. Inbound topic aliases work. |
103
+ | Offline | A session whose connection is gone keeps its subscriptions and queues messages (QoS 0 too, as `mqtt.mqueue_store_qos0`; at most 1000, oldest dropped, as `mqtt.max_mqueue_len`). Messages sent and not acknowledged are resent with DUP on resume. At most 32 QoS 1 messages are in flight (`mqtt.max_inflight`), fewer if the client's Receive Maximum is lower. |
104
+ | Session expiry | `Session Expiry Interval` seconds after the connection ends, on the emulator clock. `0` (or absent) ends the session with the connection. |
105
+ | JWT expiry | With `disconnectAfterExpire` (EMQX's default) a connected client is sent DISCONNECT `0x87` when its token's `exp` passes; with it off the connection stays and its ACL denies everything. |
106
+
107
+ **JWT ACL.** The token's `acl` claim (name configurable) is asked before anything else:
108
+
109
+ - Object form, `{"pub": [...], "sub": [...], "all": [...]}`: listed topics are allowed and
110
+ everything else is denied.
111
+ - List form, `[{"permission": "allow" | "deny", "action": "pub" | "sub" | "all", "topic": "…",
112
+ "qos"?: [0, 1], "retain"?: false}]`: the first matching rule decides; a topic no rule matches
113
+ falls to `authorization.noMatch` (`allow` by default, as in EMQX 5; EMQX 6 changed its default
114
+ to `deny`).
115
+
116
+ ACL topics are MQTT filters with `${clientid}` and `${username}` expanded; `eq <topic>` compares
117
+ literally. A requested filter is matched word by word as written, so the rule `a/+` covers a
118
+ subscription to `a/#` (EMQX's `emqx_topic:match/2`). A superuser from `users` skips
119
+ authorization. With `authorization.denyAction: "disconnect"` a denied operation ends the
120
+ connection with DISCONNECT `0x87` instead.
121
+
122
+ The emulator models what the broker does with a topic and nothing else: `space:example` is one
123
+ topic level. Mapping an application's logical names onto topics is the client's job.
124
+
125
+ ### Routes
126
+
127
+ Every route needs `Authorization: Basic <api key>:<secret>`. With `apiKeys` empty any pair is
128
+ accepted; a missing header is always refused.
129
+
130
+ | Route | Behaviour |
131
+ | --- | --- |
132
+ | `POST /api/v5/publish` | JSON `{topic, payload, qos?: 0\|1\|2, retain?, payload_encoding?: "plain"\|"base64", properties?}`. `200 {"id": "<32 hex>"}` when at least one session matched (a session that is offline counts: the message is queued); `202 {"reason_code": 16, "message": "no_matching_subscribers"}` when none did. `400 {"code": "BAD_REQUEST", "message"}` for a body that fails the schema (missing `topic`, non-string `payload`, `qos` out of range, unknown field, invalid JSON); `400 {"reason_code": 144, "message": "topic_name_invalid"}` for an empty topic or one with wildcards; `400 {"reason_code": 151, "message": "packet_too_large"}` above 1 MB; `415` when the body is not `application/json`. QoS 2 is accepted and delivered at the subscription's granted QoS. |
133
+ | `DELETE /api/v5/clients/{clientid}` | Kicks the client out: a connected client gets DISCONNECT `0x98` Administrative action, and the session, its subscriptions and its queue are discarded (an offline session too). `204`, or `404 {"code": "CLIENTID_NOT_FOUND", "message": "Client ID not found"}`. |
134
+ | any, without credentials | `401 {"code": "AUTHORIZATION_HEADER_ERROR", "message": "Support authorization: basic/bearer "}`; a wrong key or secret is `401 {"code": "BAD_API_KEY_OR_SECRET", "message": "Check api_key/api_secret"}`; a bearer token is `401 BAD_TOKEN`. All carry `WWW-Authenticate: Basic Realm="emqx-dashboard"`. |
135
+
136
+ ### Admin (beyond the standard contract)
137
+
138
+ | Route | Effect |
139
+ | --- | --- |
140
+ | `GET /__admin/clients` | Every session: `clientId`, `username`, `connected`, `transport`, `sessionExpiryInterval`, `connectedAt`, `disconnectedAt`, `expiresAt`, and counts of `subscriptions`, `queued` and `inflight` messages. |
141
+ | `GET /__admin/clients/:clientId` | One session, with its subscriptions under `topics`. |
142
+ | `GET /__admin/subscriptions?clientId=` | Subscriptions: `topicFilter`, granted `qos`, options. |
143
+ | `GET /__admin/publishes` | `{total, publishes}`: the last 1000 publishes as metadata (`topic`, `qos`, `bytes`, `source: "mqtt" \| "http" \| "admin"`, `clientId`, `matched`, `at`). Payloads are never kept. |
144
+ | `POST /__admin/inject` | `{topic, payload \| payloadBase64, qos?: 0 \| 1}`: deliver raw bytes to the topic's subscribers, bypassing the REST schema and every ACL. Use it to hand a consumer a payload its parser must refuse. Answers `{matched}`. |
145
+ | `POST /__admin/transport/cut` | `{clientId?}`: a network outage for one client, or for all. Live connections die with no DISCONNECT and new CONNECTs are dropped unanswered. Sessions are untouched and expire on their own schedule. |
146
+ | `POST /__admin/transport/restore` | `{clientId?}`: end the outage. `GET /__admin/transport` lists what is cut. |
147
+ | `POST /__admin/clients/:clientId/disconnect` | `{reasonCode?}`: send DISCONNECT (default `0x98`) and close. Unlike the vendor's kick, the session stays. |
148
+ | `GET` / `PUT /__admin/settings` | `{apiKeys?, users?, jwt?: {secret, from?, aclClaimName?, verifyClaims?, secretBase64Encoded?, disconnectAfterExpire?} \| null, authorization?: {noMatch?, denyAction?}}` for the calling namespace. Reset returns to the values the runtime was created with. |
149
+ | `POST /__admin/clock` | Standard. Moving the clock settles expiries at once: sessions past their interval are discarded and clients with an expired token are disconnected. |
150
+ | `POST /__admin/reset`, checkpoints | Standard. Reset drops every connection and session. Restoring a checkpoint restores sessions, subscriptions and queues, and drops live connections (a checkpoint cannot hold a socket). |
151
+
152
+ Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`; `GET /__admin/faults/presets`):
153
+ `connect_bad_credentials` (CONNACK `0x86`), `connect_not_authorized` (`0x87`),
154
+ `connect_server_unavailable` (`0x88`), `publish_unavailable` (REST publish answers `503
155
+ {"reason_code": 131, "message": "failed_to_dispatch"}`), `api_unauthorized` (every REST call
156
+ answers `401`). A CONNECT is the fault operation `MqttConnect`, so any reason code can be
157
+ forced: `{"operationId": "MqttConnect", "effect": "connack", "params": {"reasonCode": 151}}`.
158
+
159
+ ### Namespaces
160
+
161
+ Parallel workers isolate themselves by namespace. HTTP: `x-mockingbird-namespace`, a
162
+ `/__admin/ns/<name>` prefix on the API origin, or by API key
163
+ (`PUT /__admin/credentials {"credentials": {"<api key>": "<namespace>"}}`). MQTT: the WebSocket
164
+ URL `ws://…/__admin/ns/<name>/mqtt`, or the same credential map keyed by the CONNECT's username,
165
+ then its client id. Unmapped clients use the default namespace.
166
+
167
+ ### Unverified
168
+
169
+ Taken from the EMQX 5.8.6 source or left open, not observed on a broker:
170
+
171
+ - The `message` text of a schema `400` (EMQX prints its config library's error; the emulator
172
+ prints `{"kind": "validation_error", "path": "root.<field>", "reason": …}`), of a bad base64
173
+ payload, and of an unknown path's `404`.
174
+ - EMQX coerces an integer `payload` to its decimal string; the emulator refuses it.
175
+ - The CONNACK Receive Maximum is EMQX's `min(client's, 32)`; the emulator always sends 32.
176
+ - The `is_superuser` token claim is not read.
177
+
178
+ ### Deliberately not modelled
179
+
180
+ - MQTT 3.1 and 3.1.1: such a CONNECT is refused with that protocol's return code 1.
181
+ - QoS 2: the CONNACK says Maximum QoS 1, a QoS 2 subscription is granted QoS 1, and a QoS 2
182
+ publish is answered DISCONNECT `0x9B`. EMQX itself supports QoS 2.
183
+ - Retained messages: the CONNACK says Retain Available 0, a retained publish is answered
184
+ DISCONNECT `0x9A`, and `retain: true` on the REST API is `400`.
185
+ - Will messages (accepted in CONNECT, never published), shared subscriptions, enhanced
186
+ authentication (AUTH), keep-alive timeouts (PINGREQ is answered; use a transport cut for a dead
187
+ connection), message expiry, and outbound topic aliases.
188
+ - Public-key and JWKS tokens, password hashing, and authenticators or authorizers other than the
189
+ built-in database and JWT.
190
+ - Clustering, the dashboard and its login tokens, the rule engine, bridges, billing, TLS and
191
+ listener configuration.
192
+
193
+ ## API
194
+
195
+ | Export | Kind | Description |
196
+ | --- | --- | --- |
197
+ | `createRuntime` | function | The emulator with the full service contract. Options: `settings`, `clock`, `seed`, `adminPrefix`, `adminKey`, `onLog`, `sqlite`. Adds `attach(transport, info?, namespace?)` for an MQTT connection, `webSocketPath(pathname)`, `endpoints`, and `stop()`. |
198
+ | `EmqxAPI` | class | One namespace's broker: `fetch(request)` for the REST API, `broker` for MQTT, `state`, `reset()`. Options: `sqlite`, `now`, `namespace`, `settings`, `connectFault`. |
199
+ | `EMQX_PRESETS` | object | Every named fault preset. |
200
+ | `EMQX_NAMESPACE` | string | The service name, `"emqx"`. |
201
+ | `MQTT_CONNECT_OPERATION` | string | `"MqttConnect"`, the operation id a fault rule uses for a CONNECT. |
202
+ | `MQTT_WEBSOCKET_PATH` | string | `"/mqtt"`. |
203
+ | `MAX_PACKET_SIZE` | number | `1048576`, EMQX's `mqtt.max_packet_size`. |
204
+ | `DEFAULT_SETTINGS`, `DEFAULT_JWT_AUTHENTICATOR` | objects | A fresh EMQX: no authenticator, `no_match = allow`; and the JWT authenticator's defaults. |
205
+ | `apiKeyCredential` | function | The API key of a request (how credentials map to namespaces). |
206
+ | `parseAcl`, `checkAcl` | functions | Parse an `acl` claim, and ask it about one publish or subscribe (`allow`, `deny` or `nomatch`). |
207
+ | `ReasonCode` | object | MQTT 5 reason codes by name. |
208
+ | `document`, `operationIds`, `supportedOperationIds` | values | The REST contract and its operation ids. |
209
+ | `createServer`, `connectStream`, `serveTarget`, `DEFAULT_PORT`, `DEFAULT_MQTT_PORT` (`./server`) | Node | Serve HTTP, WebSocket and TCP (`{url, mqttUrl, mqttPort, wsUrl, runtime, close()}`; options add `port`, `mqttPort`, `host`, `sweepMs`); an in-process MQTT stream; the `serve` CLI target; ports 8850 and 1883. |
210
+
211
+ Part of [mockingbird](https://github.com/crvouga/mockingbird).
package/SUPPORT.md ADDED
@@ -0,0 +1,12 @@
1
+ # EMQX REST API v5 (Mockingbird subset) — operation support
2
+
3
+ Generated from `openapi.yaml`; do not edit by hand.
4
+
5
+ - operations in spec: **2**
6
+ - supported by the emulator: **2**
7
+ - parity enabled: **2**
8
+
9
+ | operationId | route | emulator | parity | notes |
10
+ | --- | --- | --- | --- | --- |
11
+ | `PublishMessage` | `POST /api/v5/publish` | ✅ supported | ⚠️ unsafe (opt-in) | |
12
+ | `KickClient` | `DELETE /api/v5/clients/{clientid}` | ✅ supported | ⚠️ unsafe (opt-in) | |