mqtt-plus 1.4.16 → 1.4.18
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/.claude/CLAUDE.md +1 -0
- package/.gemini/settings.json +5 -0
- package/AGENTS.md +86 -97
- package/CHANGELOG.md +41 -0
- package/README.md +1 -1
- package/doc/mqtt-plus-api.md +24 -15
- package/doc/mqtt-plus-architecture.md +1 -1
- package/doc/mqtt-plus-broker-setup.md +2 -2
- package/doc/mqtt-plus-comm.md +3 -2
- package/doc/mqtt-plus-internals.md +11 -1
- package/dst-stage1/mqtt-plus-auth.d.ts +1 -1
- package/dst-stage1/mqtt-plus-auth.js +18 -17
- package/dst-stage1/mqtt-plus-auth.js.map +1 -1
- package/dst-stage1/mqtt-plus-base.d.ts +2 -1
- package/dst-stage1/mqtt-plus-base.js +32 -7
- package/dst-stage1/mqtt-plus-base.js.map +1 -1
- package/dst-stage1/mqtt-plus-error.d.ts +1 -0
- package/dst-stage1/mqtt-plus-error.js +69 -39
- package/dst-stage1/mqtt-plus-error.js.map +1 -1
- package/dst-stage1/mqtt-plus-event.d.ts +4 -2
- package/dst-stage1/mqtt-plus-event.js +61 -13
- package/dst-stage1/mqtt-plus-event.js.map +1 -1
- package/dst-stage1/mqtt-plus-info.d.ts +2 -0
- package/dst-stage1/mqtt-plus-msg.d.ts +12 -8
- package/dst-stage1/mqtt-plus-msg.js +31 -23
- package/dst-stage1/mqtt-plus-msg.js.map +1 -1
- package/dst-stage1/mqtt-plus-options.d.ts +1 -1
- package/dst-stage1/mqtt-plus-service.d.ts +3 -0
- package/dst-stage1/mqtt-plus-service.js +146 -24
- package/dst-stage1/mqtt-plus-service.js.map +1 -1
- package/dst-stage1/mqtt-plus-sink.js +210 -92
- package/dst-stage1/mqtt-plus-sink.js.map +1 -1
- package/dst-stage1/mqtt-plus-source.d.ts +1 -0
- package/dst-stage1/mqtt-plus-source.js +186 -123
- package/dst-stage1/mqtt-plus-source.js.map +1 -1
- package/dst-stage1/mqtt-plus-subscription.d.ts +1 -0
- package/dst-stage1/mqtt-plus-subscription.js +23 -18
- package/dst-stage1/mqtt-plus-subscription.js.map +1 -1
- package/dst-stage1/mqtt-plus-timer.d.ts +2 -0
- package/dst-stage1/mqtt-plus-timer.js +52 -0
- package/dst-stage1/mqtt-plus-timer.js.map +1 -1
- package/dst-stage1/mqtt-plus-trace.js +10 -3
- package/dst-stage1/mqtt-plus-trace.js.map +1 -1
- package/dst-stage1/mqtt-plus-util.js +18 -10
- package/dst-stage1/mqtt-plus-util.js.map +1 -1
- package/dst-stage1/mqtt-plus-version.d.ts +2 -0
- package/dst-stage1/mqtt-plus-version.js +3 -0
- package/dst-stage1/mqtt-plus-version.js.map +1 -1
- package/dst-stage2/mqtt-plus.cjs.cjs +692 -323
- package/dst-stage2/mqtt-plus.esm.js +687 -318
- package/dst-stage2/mqtt-plus.umd.js +11 -11
- package/etc/d2.mts +47 -13
- package/etc/eslint.mts +6 -2
- package/etc/stx.conf +8 -6
- package/etc/vite.mts +14 -5
- package/package.d/{@typescript-eslint+typescript-estree+8.57.2.patch → @typescript-eslint+typescript-estree+8.58.1.patch} +1 -1
- package/package.d/{vite+8.0.3.patch → vite+8.0.8.patch} +2 -2
- package/package.json +16 -16
- package/src/mqtt-plus-auth.ts +18 -17
- package/src/mqtt-plus-base.ts +38 -7
- package/src/mqtt-plus-error.ts +73 -41
- package/src/mqtt-plus-event.ts +71 -17
- package/src/mqtt-plus-info.ts +6 -2
- package/src/mqtt-plus-msg.ts +35 -21
- package/src/mqtt-plus-options.ts +1 -1
- package/src/mqtt-plus-service.ts +157 -26
- package/src/mqtt-plus-sink.ts +227 -94
- package/src/mqtt-plus-source.ts +205 -126
- package/src/mqtt-plus-subscription.ts +26 -19
- package/src/mqtt-plus-timer.ts +56 -0
- package/src/mqtt-plus-trace.ts +10 -3
- package/src/mqtt-plus-util.ts +19 -10
- package/src/mqtt-plus-version.ts +5 -1
- package/tst/mqtt-plus-0-broker-mosquitto.ts +2 -2
- package/tst/mqtt-plus-3-service.spec.ts +3 -3
- package/tst/mqtt-plus-4-sink.spec.ts +3 -3
- package/tst/mqtt-plus-7-spool.spec.ts +65 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@../AGENTS.md
|
package/AGENTS.md
CHANGED
|
@@ -1,49 +1,41 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
|
-
======
|
|
2
|
+
# Overview for AI Agents
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
## Project Abstract
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
MQTT+ (`mqtt-plus`) is a TypeScript library implementing four MQTT
|
|
7
|
+
communication patterns with full type safety: Event Emission, Service
|
|
8
|
+
Call (RPC), Source Fetch, and Sink Push. It uses `mqtt` as a peer
|
|
9
|
+
dependency and builds to ESM, CJS, and UMD formats.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
with full type safety: Event Emission, Service Call (RPC), Source Fetch, and Sink Push.
|
|
12
|
-
It uses `mqtt` as a peer dependency and builds to ESM, CJS, and UMD formats.
|
|
11
|
+
## Technology Stack
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
Build-Tools: npm, @rse/stx, vite, eslint, typescript
|
|
14
|
+
Language: TypeScript
|
|
15
|
+
Libraries: nanoid, cbor2, p-lazy, valibot, jose, @stablelib/pbkdf2, @stablelib/sha256
|
|
16
|
+
Runtime: Browser, Node.js
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
## Build Commands
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
npm install # install dependencies
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Build and development commands use STX (`@rse/stx`) as the task runner:
|
|
20
|
+
Build and development commands use NPM and STX (`@rse/stx`) as the task runner:
|
|
24
21
|
|
|
25
22
|
```bash
|
|
26
|
-
npm
|
|
27
|
-
npm start build # standard: build everything
|
|
28
|
-
npm start test # standard: run unit test suite
|
|
29
|
-
|
|
30
|
-
npm start build-doc # generate SVG diagrams from D2 sources only
|
|
31
|
-
npm start dev # development watch mode (rebuild on change)
|
|
32
|
-
npm start sample # run sample/sample.ts via `tsx`
|
|
23
|
+
npm install # install dependencies
|
|
33
24
|
|
|
34
|
-
npm start
|
|
35
|
-
npm start
|
|
36
|
-
npm start
|
|
37
|
-
```
|
|
25
|
+
npm start lint # standard: perform static code analysis
|
|
26
|
+
npm start build # standard: build everything
|
|
27
|
+
npm start test # standard: run unit test suite
|
|
38
28
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
29
|
+
npm start build-doc # generate SVG diagrams from D2 sources only
|
|
30
|
+
npm start dev # development watch mode (rebuild on change)
|
|
31
|
+
npm start sample # run sample/sample.ts via `tsx`
|
|
42
32
|
|
|
43
|
-
|
|
33
|
+
npm start clean # remove dst-stage1/ and dst-stage2/
|
|
34
|
+
npm start distclean # remove node_modules/ and package-lock.json
|
|
35
|
+
npm start publish # publish to npm (restricted to maintainer host)
|
|
36
|
+
```
|
|
44
37
|
|
|
45
|
-
Build Pipeline
|
|
46
|
-
--------------
|
|
38
|
+
## Build Pipeline
|
|
47
39
|
|
|
48
40
|
Two-stage build:
|
|
49
41
|
|
|
@@ -54,16 +46,22 @@ Two-stage build:
|
|
|
54
46
|
`mqtt-plus.esm.js`, `mqtt-plus.cjs.js`, `mqtt-plus.umd.js`.
|
|
55
47
|
UMD build includes Node polyfills (events, stream, buffer).
|
|
56
48
|
|
|
57
|
-
Configuration lives in `etc/`: `tsc.json`, `vite.mts`, `eslint.mts`,
|
|
49
|
+
Configuration lives in `etc/`: `tsc.json`, `vite.mts`, `eslint.mts`,
|
|
50
|
+
`knip.jsonc`, `stx.conf`, `d2.mts`, `d2.theme.d2`, `logo.ai`,
|
|
51
|
+
`logo.svg`.
|
|
52
|
+
|
|
53
|
+
Tests require an MQTT broker under run-time; the test suite starts/stops
|
|
54
|
+
one automatically. If Docker is available, a Mosquitto broker is used;
|
|
55
|
+
otherwise, the Aedes in-process broker serves as the fallback.
|
|
56
|
+
For regression testing always use the all-in-one command `npm start build test`.
|
|
58
57
|
|
|
59
|
-
Architecture
|
|
60
|
-
------------
|
|
58
|
+
## Architecture
|
|
61
59
|
|
|
62
60
|
### Trait-Based Mixin Tower
|
|
63
61
|
|
|
64
62
|
The library is composed as a vertical chain of trait classes (mixins),
|
|
65
|
-
each extending the previous. The final exported class `MQTTp` sits
|
|
66
|
-
the bottom of this chain:
|
|
63
|
+
each extending the previous one. The final exported class `MQTTp` sits
|
|
64
|
+
at the bottom of this chain:
|
|
67
65
|
|
|
68
66
|
```
|
|
69
67
|
OptionsTrait — configuration (id, codec, timeout, share, chunkSize, chunkCredit, topicMake/topicMatch)
|
|
@@ -87,63 +85,55 @@ Each trait lives in its own file: `src/mqtt-plus-<trait>.ts`.
|
|
|
87
85
|
|
|
88
86
|
### Key Source Files
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
- `
|
|
119
|
-
- `
|
|
120
|
-
- `
|
|
121
|
-
- `
|
|
122
|
-
- `
|
|
123
|
-
- `
|
|
124
|
-
- `
|
|
125
|
-
- `
|
|
126
|
-
- `
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
| `tst/mqtt-plus-0-broker-mosquitto.ts` | Helper for starting/stopping the Mosquitto MQTT broker |
|
|
140
|
-
| `tst/mqtt-plus-1-api.spec.ts` | API type and endpoint definition tests |
|
|
141
|
-
| `tst/mqtt-plus-2-event.spec.ts` | Event Emission pattern tests |
|
|
142
|
-
| `tst/mqtt-plus-3-service.spec.ts` | Service Call / RPC pattern tests |
|
|
143
|
-
| `tst/mqtt-plus-4-sink.spec.ts` | Sink Push pattern tests |
|
|
144
|
-
| `tst/mqtt-plus-5-source.spec.ts` | Source Fetch pattern tests |
|
|
145
|
-
| `tst/mqtt-plus-6-misc.spec.ts` | Miscellaneous / edge-case tests |
|
|
146
|
-
| `tst/tsc.json` | TypeScript configuration for the test directory |
|
|
88
|
+
- `src/mqtt-plus.ts`: Main entry point, re-exports public API types and the final MQTTp class
|
|
89
|
+
- `src/mqtt-plus-api.ts`: Branded endpoint type definitions (Event, Service, Source, Sink) and APISchema generic
|
|
90
|
+
- `src/mqtt-plus-info.ts`: Info/context object types passed to pattern callbacks (sender metadata, etc.)
|
|
91
|
+
- `src/mqtt-plus-error.ts`: Spool (resource cleanup) and run (error handling) utilities
|
|
92
|
+
- `src/mqtt-plus-util.ts`: PLazy, CreditGate flow control, and stream/buffer collection utilities
|
|
93
|
+
- `src/mqtt-plus-version.ts`: Version utility for converting version strings to numeric format
|
|
94
|
+
- `src/mqtt-plus-options.ts`: OptionsTrait — configuration (id, codec, timeout, share, chunkSize, chunkCredit, topicMake/topicMatch)
|
|
95
|
+
- `src/mqtt-plus-codec.ts`: CodecTrait — CBOR and JSON codec encoding/decoding
|
|
96
|
+
- `src/mqtt-plus-encode.ts`: EncodeTrait — string/buffer conversion utilities (str2buf, buf2str)
|
|
97
|
+
- `src/mqtt-plus-msg.ts`: MsgTrait — message class definitions, valibot schemas, and parsing logic
|
|
98
|
+
- `src/mqtt-plus-trace.ts`: TraceTrait — EventEmitter and structured logging
|
|
99
|
+
- `src/mqtt-plus-base.ts`: BaseTrait — MQTT client connection, subscription management, message routing
|
|
100
|
+
- `src/mqtt-plus-subscription.ts`: SubscriptionTrait — RefCountedSubscription class and ref-counted MQTT topic subscription management
|
|
101
|
+
- `src/mqtt-plus-timer.ts`: TimerTrait — named timer management (refresh/clear)
|
|
102
|
+
- `src/mqtt-plus-meta.ts`: MetaTrait — instance and per-request metadata management
|
|
103
|
+
- `src/mqtt-plus-auth.ts`: AuthTrait — JWT authentication (jose) and role-based access control
|
|
104
|
+
- `src/mqtt-plus-event.ts`: EventTrait — Event Emission communication pattern (event/emit)
|
|
105
|
+
- `src/mqtt-plus-service.ts`: ServiceTrait — Service Call / RPC communication pattern (service/call)
|
|
106
|
+
- `src/mqtt-plus-source.ts`: SourceTrait — Source Fetch communication pattern (source/fetch)
|
|
107
|
+
- `src/mqtt-plus-sink.ts`: SinkTrait — Sink Push communication pattern (sink/push)
|
|
108
|
+
|
|
109
|
+
### Key Test Files
|
|
110
|
+
|
|
111
|
+
- `tst/mqtt-plus-0-fixture.ts`: Shared test fixture setup (broker, MQTTp instances, etc.)
|
|
112
|
+
- `tst/mqtt-plus-0-broker.ts`: Broker dispatch: creates Aedes or Mosquitto broker based on env
|
|
113
|
+
- `tst/mqtt-plus-0-broker-aedes.ts`: Helper for starting/stopping the Aedes MQTT broker
|
|
114
|
+
- `tst/mqtt-plus-0-broker-mosquitto.ts`: Helper for starting/stopping the Mosquitto MQTT broker
|
|
115
|
+
- `tst/mqtt-plus-1-api.spec.ts`: API type and endpoint definition tests
|
|
116
|
+
- `tst/mqtt-plus-2-event.spec.ts`: Event Emission pattern tests
|
|
117
|
+
- `tst/mqtt-plus-3-service.spec.ts`: Service Call / RPC pattern tests
|
|
118
|
+
- `tst/mqtt-plus-4-sink.spec.ts`: Sink Push pattern tests
|
|
119
|
+
- `tst/mqtt-plus-5-source.spec.ts`: Source Fetch pattern tests
|
|
120
|
+
- `tst/mqtt-plus-6-misc.spec.ts`: Miscellaneous / edge-case tests
|
|
121
|
+
- `tst/mqtt-plus-7-spool.spec.ts`: Spool (resource cleanup) utility tests
|
|
122
|
+
- `tst/mqtt-plus-8-run.spec.ts`: Run (error handling) utility tests
|
|
123
|
+
- `tst/tsc.std.json`: TypeScript configuration for the test directory (standard)
|
|
124
|
+
- `tst/tsc.cov.json`: TypeScript configuration for the test directory (coverage)
|
|
125
|
+
|
|
126
|
+
### Key Documentation Files
|
|
127
|
+
|
|
128
|
+
- `doc/mqtt-plus-api.md`: public API reference
|
|
129
|
+
- `doc/mqtt-plus-architecture.{d2,svg,md}`: architecture overview (diagram + docs)
|
|
130
|
+
- `doc/mqtt-plus-broker-setup.md`: MQTT broker setup guide
|
|
131
|
+
- `doc/mqtt-plus-comm.md`: communication patterns overview
|
|
132
|
+
- `doc/mqtt-plus-comm-event-emission.{d2,svg}`: Event Emission pattern diagram
|
|
133
|
+
- `doc/mqtt-plus-comm-service-call.{d2,svg}`: Service Call pattern diagram
|
|
134
|
+
- `doc/mqtt-plus-comm-sink-push.{d2,svg}`: Sink Push pattern diagram
|
|
135
|
+
- `doc/mqtt-plus-comm-source-fetch.{d2,svg}`: Source Fetch pattern diagram
|
|
136
|
+
- `doc/mqtt-plus-internals.md`: internal implementation details
|
|
147
137
|
|
|
148
138
|
### Type System
|
|
149
139
|
|
|
@@ -152,8 +142,7 @@ The API uses branded types (`Event<...>`, `Service<...>`, `Source<...>`,
|
|
|
152
142
|
type parameter threads through the trait tower, enabling full type
|
|
153
143
|
inference for pattern names and parameter types.
|
|
154
144
|
|
|
155
|
-
Coding Style
|
|
156
|
-
------------
|
|
145
|
+
## Coding Style
|
|
157
146
|
|
|
158
147
|
- 4-space indentation, double quotes, no semicolons
|
|
159
148
|
- Stroustrup brace style (`else`/`catch`/`finally` on new line after closing brace)
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,47 @@
|
|
|
2
2
|
ChangeLog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
1.4.18 (2026-04-17)
|
|
6
|
+
-------------------
|
|
7
|
+
|
|
8
|
+
- IMPROVEMENT: detect concurrent deliveries and duplicate request ids across service and event traits
|
|
9
|
+
- IMPROVEMENT: improve error handling, honor destroyed flag in base trait, and reject pending calls on destroy
|
|
10
|
+
- IMPROVEMENT: add sanity checks for message validation
|
|
11
|
+
- IMPROVEMENT: allow caller to control MQTT QoS
|
|
12
|
+
- IMPROVEMENT: allow emit() to be awaitable
|
|
13
|
+
- UPDATE: upgrade NPM dependencies
|
|
14
|
+
- CLEANUP: add license headers
|
|
15
|
+
- CLEANUP: various code cleanups (naming, simplification, resource handling, messages)
|
|
16
|
+
- CLEANUP: various cleanups to the source trait
|
|
17
|
+
- CLEANUP: pass-through specific error on cancelAndUnroll in service trait
|
|
18
|
+
- CLEANUP: simplify error handling in sink trait
|
|
19
|
+
|
|
20
|
+
1.4.17 (2026-04-11)
|
|
21
|
+
-------------------
|
|
22
|
+
|
|
23
|
+
- IMPROVEMENT: add "signal" field to info of service and event callbacks for signalling abortion
|
|
24
|
+
- IMPROVEMENT: add more utility functions related to timers
|
|
25
|
+
- IMPROVEMENT: support cancelling push operations with a credit of zero
|
|
26
|
+
- IMPROVEMENT: let Spool.unroll() always execute the cleanup callback
|
|
27
|
+
- IMPROVEMENT: perform a minimum version check in the protocol
|
|
28
|
+
- IMPROVEMENT: log invalid requests with missing senders
|
|
29
|
+
- IMPROVEMENT: add upper bound for the nanoid iteration
|
|
30
|
+
- IMPROVEMENT: perform topic receiver matching and validate service response names
|
|
31
|
+
- IMPROVEMENT: move the destroyed flag to the base class and protect other methods
|
|
32
|
+
- IMPROVEMENT: send errors to peer and provide AggregateError to not lose errors
|
|
33
|
+
- IMPROVEMENT: bump minimum Node version to 20 for ES2022
|
|
34
|
+
- BUGFIX: Spool.unroll() silently skipped remaining cleanups on first async failure
|
|
35
|
+
- BUGFIX: improve semantics of info.authenticated field for event/service/sink/source in case of optional authentication
|
|
36
|
+
- BUGFIX: in the ReadableTee class, do not run read() twice: once ourself and once via the base class
|
|
37
|
+
- BUGFIX: correctly propagate description in run() also to finally callback
|
|
38
|
+
- BUGFIX: fix resource handling in source trait
|
|
39
|
+
- BUGFIX: avoid race conditions and unhandled promise rejections in async processing
|
|
40
|
+
- BUGFIX: fix cleanup and error handling across sink/source traits
|
|
41
|
+
- BUGFIX: fix Mosqitto ACL
|
|
42
|
+
- UPDATE: upgrade NPM dependencies
|
|
43
|
+
- CLEANUP: various code cleanups (callback handling, settle code, destroy handling, termination, subscriptions)
|
|
44
|
+
- CLEANUP: align with ensureError code and fix typos
|
|
45
|
+
|
|
5
46
|
1.4.16 (2026-03-27)
|
|
6
47
|
-------------------
|
|
7
48
|
|
package/README.md
CHANGED
|
@@ -165,7 +165,7 @@ Main documentation:
|
|
|
165
165
|
- [**Communication Patterns**](doc/mqtt-plus-comm.md)
|
|
166
166
|
- [**Application Programming Interface (API)**](doc/mqtt-plus-api.md)
|
|
167
167
|
|
|
168
|
-
Additional
|
|
168
|
+
Additional auxiliary documentation:
|
|
169
169
|
|
|
170
170
|
- [Extra: Architecture Overview](doc/mqtt-plus-architecture.md)
|
|
171
171
|
- [Extra: Internal Protocol](doc/mqtt-plus-internals.md)
|
package/doc/mqtt-plus-api.md
CHANGED
|
@@ -250,9 +250,10 @@ Register for an event.
|
|
|
250
250
|
- The optional `auth` enables authentication validation on incoming events.
|
|
251
251
|
When set to a role name string (e.g., `"admin"`), authentication is required
|
|
252
252
|
and the token must include that role. When set to an object `{ mode, roles }`,
|
|
253
|
-
the mode can be `"require"` (reject unauthenticated
|
|
254
|
-
|
|
255
|
-
the
|
|
253
|
+
the mode can be `"require"` (reject unauthenticated, so `info.authenticated`
|
|
254
|
+
is always `true` in the callback) or `"optional"` (accept all, but set
|
|
255
|
+
`info.authenticated` to `true` or `false` to reflect the validation result),
|
|
256
|
+
and roles specifies the required role names.
|
|
256
257
|
|
|
257
258
|
- Internally, on the MQTT broker, the topics generated by
|
|
258
259
|
`topicMake(name, "event-emission")` (default: `${name}/event-emission/any` and
|
|
@@ -267,14 +268,14 @@ Event Emission
|
|
|
267
268
|
emit(
|
|
268
269
|
name: string,
|
|
269
270
|
...params: any[]
|
|
270
|
-
): void
|
|
271
|
+
): Promise<void>
|
|
271
272
|
emit({
|
|
272
273
|
name: string,
|
|
273
274
|
params: any[],
|
|
274
275
|
receiver?: string,
|
|
275
276
|
options?: MQTT::IClientPublishOptions,
|
|
276
277
|
meta?: Record<string, any>
|
|
277
|
-
}): void
|
|
278
|
+
}): Promise<void>
|
|
278
279
|
emit({
|
|
279
280
|
name: string,
|
|
280
281
|
params: any[],
|
|
@@ -284,7 +285,7 @@ Event Emission
|
|
|
284
285
|
dry: true
|
|
285
286
|
}): { topic: string, payload: string | Uint8Array, options: IClientPublishOptions }
|
|
286
287
|
|
|
287
|
-
Emit an event to all subscribers or a specific subscriber ("fire and forget").
|
|
288
|
+
Emit an event to all subscribers or a specific subscriber (awaitable; ignore the promise for "fire and forget").
|
|
288
289
|
|
|
289
290
|
- The optional `receiver` directs the event to a specific subscriber only.
|
|
290
291
|
|
|
@@ -380,9 +381,10 @@ Register a service.
|
|
|
380
381
|
- The optional `auth` enables authentication validation on incoming service calls.
|
|
381
382
|
When set to a role name string (e.g., `"admin"`), authentication is required
|
|
382
383
|
and the token must include that role. When set to an object `{ mode, roles }`,
|
|
383
|
-
the mode can be `"require"` (reject unauthenticated with error response
|
|
384
|
-
`
|
|
385
|
-
|
|
384
|
+
the mode can be `"require"` (reject unauthenticated with error response, so
|
|
385
|
+
`info.authenticated` is always `true` in the callback) or `"optional"` (accept
|
|
386
|
+
all, but set `info.authenticated` to `true` or `false` to reflect the validation
|
|
387
|
+
result), and roles specifies the required role names.
|
|
386
388
|
|
|
387
389
|
- Internally, on the MQTT broker, the topics generated by
|
|
388
390
|
`topicMake(name, "service-call-request")` (default: `${name}/service-call-request/any` and
|
|
@@ -471,6 +473,9 @@ Register a sink for receiving data.
|
|
|
471
473
|
The `info.buffer` provides a lazy `Promise<Uint8Array>` that resolves to the complete data once the stream ends.
|
|
472
474
|
The `info.signal` is aborted when the push request is cancelled, times out, or is otherwise torn down,
|
|
473
475
|
allowing the sink handler to stop any related side work cooperatively.
|
|
476
|
+
When the sink handler's stream closes abnormally (before normal completion),
|
|
477
|
+
a cancel signal (`credit=0`) is automatically sent to the pusher to abort
|
|
478
|
+
the data transfer on the sender side.
|
|
474
479
|
The `info.meta` contains optional metadata sent by the pusher via `push()`.
|
|
475
480
|
|
|
476
481
|
- The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
|
|
@@ -486,9 +491,10 @@ Register a sink for receiving data.
|
|
|
486
491
|
When set to a role name string (e.g., `"admin"`), authentication
|
|
487
492
|
is required and the token must include that role. When set to an
|
|
488
493
|
object `{ mode, roles }`, the mode can be `"require"` (reject
|
|
489
|
-
unauthenticated
|
|
490
|
-
|
|
491
|
-
|
|
494
|
+
unauthenticated, so `info.authenticated` is always `true` in the
|
|
495
|
+
callback) or `"optional"` (accept all, but set `info.authenticated`
|
|
496
|
+
to `true` or `false` to reflect the validation result), and roles
|
|
497
|
+
specifies the required role names.
|
|
492
498
|
|
|
493
499
|
- Internally, on the MQTT broker, the topics generated by
|
|
494
500
|
`topicMake(name, "sink-push-request")`
|
|
@@ -532,6 +538,8 @@ Pushes data to all established sinks or a specific sink handler.
|
|
|
532
538
|
configurable via `chunkSize` option) and sent over MQTT until the
|
|
533
539
|
stream is closed or the buffer is fully transferred.
|
|
534
540
|
The returned `Promise` resolves when the entire data has been pushed.
|
|
541
|
+
If the receiver cancels the push (via a cancel signal with `credit=0`),
|
|
542
|
+
the returned `Promise` rejects with a cancellation error.
|
|
535
543
|
|
|
536
544
|
- The remote `sink()` `callback` is called with `params` and an `info` object
|
|
537
545
|
containing `signal` (`AbortSignal`) for cooperative cancellation,
|
|
@@ -614,9 +622,10 @@ Register a source for sending data.
|
|
|
614
622
|
When set to a role name string (e.g., `"admin"`), authentication
|
|
615
623
|
is required and the token must include that role. When set to an
|
|
616
624
|
object `{ mode, roles }`, the mode can be `"require"` (reject
|
|
617
|
-
unauthenticated
|
|
618
|
-
|
|
619
|
-
|
|
625
|
+
unauthenticated, so `info.authenticated` is always `true` in the
|
|
626
|
+
callback) or `"optional"` (accept all, but set `info.authenticated`
|
|
627
|
+
to `true` or `false` to reflect the validation result), and roles
|
|
628
|
+
specifies the required role names.
|
|
620
629
|
|
|
621
630
|
- Internally, on the MQTT broker, the topics generated by
|
|
622
631
|
`topicMake(name, "source-fetch-request")`
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
MQTT+ Architecture
|
|
3
3
|
==================
|
|
4
4
|
|
|
5
|
-
**MQTT+** is composed as a vertical chain of API and
|
|
5
|
+
**MQTT+** is composed as a vertical chain of API and infrastructure trait
|
|
6
6
|
classes (mixins), each extending the previous. Additionally, some base
|
|
7
7
|
modules complement the functionality.
|
|
8
8
|
|
|
@@ -69,7 +69,7 @@ pattern read example/server/+/service-call-response/%c
|
|
|
69
69
|
|
|
70
70
|
topic read example/client/+/service-call-request/any
|
|
71
71
|
pattern read example/client/+/service-call-request/%c
|
|
72
|
-
pattern write example/client/+/service-call-response
|
|
72
|
+
pattern write example/client/+/service-call-response/+
|
|
73
73
|
|
|
74
74
|
# ---- source fetch ----
|
|
75
75
|
|
|
@@ -87,7 +87,7 @@ pattern read example/server/+/sink-push-response/%c
|
|
|
87
87
|
|
|
88
88
|
topic read example/client/+/sink-push-request/any
|
|
89
89
|
pattern read example/client/+/sink-push-request/%c
|
|
90
|
-
pattern write example/client/+/sink-push-response
|
|
90
|
+
pattern write example/client/+/sink-push-response/+
|
|
91
91
|
|
|
92
92
|
# ==== server/authenticated ACL ====
|
|
93
93
|
|
package/doc/mqtt-plus-comm.md
CHANGED
|
@@ -50,8 +50,9 @@ chunks as a stream with arguments.
|
|
|
50
50
|
> In contrast to the regular MQTT message publish/subscribe, this
|
|
51
51
|
> pattern allows to transfer arbitrary amounts of arbitrary data by
|
|
52
52
|
> chunking the data via a stream. Additionally, it supports authentication
|
|
53
|
-
> and meta-data,
|
|
54
|
-
> cooperative cancellation,
|
|
53
|
+
> and meta-data, provides an `AbortSignal` to the sink handler for
|
|
54
|
+
> cooperative cancellation, and allows the receiver to cancel an
|
|
55
|
+
> in-progress push via a cancel signal (`credit=0`), etc.
|
|
55
56
|
|
|
56
57
|

|
|
57
58
|
|
|
@@ -138,7 +138,7 @@ Exactly one of `result` or `error` is present.
|
|
|
138
138
|
| Field | Type | Required | Description |
|
|
139
139
|
|----------|-----------|----------|-------------------------------------|
|
|
140
140
|
| `name` | `string` | yes | Sink endpoint name |
|
|
141
|
-
| `credit` | `integer` | yes | Number of additional credits (min
|
|
141
|
+
| `credit` | `integer` | yes | Number of additional credits (min 0). A value of `0` is a **cancel signal**, indicating that the receiver wants to abort the push stream. |
|
|
142
142
|
|
|
143
143
|
### `source-fetch-request`
|
|
144
144
|
|
|
@@ -272,6 +272,16 @@ Setting `chunkCredit` to `0` disables flow control entirely.
|
|
|
272
272
|
| Sink Push | Sink | Pusher | `sink-push-credit` |
|
|
273
273
|
| Source Fetch | Fetcher | Source | `source-fetch-credit` |
|
|
274
274
|
|
|
275
|
+
### Receiver-Initiated Cancellation (Sink Push)
|
|
276
|
+
|
|
277
|
+
The sink (receiver) can cancel an in-progress push by sending a
|
|
278
|
+
`sink-push-credit` message with `credit` set to `0`. This acts as
|
|
279
|
+
a **cancel signal**: the pusher aborts the data transfer immediately
|
|
280
|
+
and does not send an error chunk back to the receiver (since the
|
|
281
|
+
cancellation originated from the receiver itself). On the receiver
|
|
282
|
+
side, the `AbortSignal` provided to the sink callback is triggered
|
|
283
|
+
when the push stream closes abnormally.
|
|
284
|
+
|
|
275
285
|
Authentication
|
|
276
286
|
--------------
|
|
277
287
|
|
|
@@ -20,6 +20,6 @@ export declare class AuthTrait<T extends APISchema = APISchema> extends MetaTrai
|
|
|
20
20
|
authenticate(token: string): void;
|
|
21
21
|
authenticate(token: string, remove: boolean): void;
|
|
22
22
|
private validateToken;
|
|
23
|
-
protected authenticated(clientId: string | undefined, tokens: string[] | undefined, option: AuthOption): Promise<boolean>;
|
|
23
|
+
protected authenticated(clientId: string | undefined, tokens: string[] | undefined, option: AuthOption, label: string): Promise<boolean>;
|
|
24
24
|
}
|
|
25
25
|
export {};
|
|
@@ -62,7 +62,7 @@ export class AuthTrait extends MetaTrait {
|
|
|
62
62
|
}
|
|
63
63
|
authenticate(token, remove) {
|
|
64
64
|
if (token === undefined) {
|
|
65
|
-
const tokens = Array.from(this._tokens)
|
|
65
|
+
const tokens = Array.from(this._tokens);
|
|
66
66
|
return tokens.length > 0 ? tokens : undefined;
|
|
67
67
|
}
|
|
68
68
|
else if (remove === true)
|
|
@@ -79,23 +79,23 @@ export class AuthTrait extends MetaTrait {
|
|
|
79
79
|
async validateToken(token) {
|
|
80
80
|
if (this._credential === null)
|
|
81
81
|
throw new Error("credential has to be provided before validating tokens");
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
try {
|
|
83
|
+
const result = await jwtVerify(token, this._credential);
|
|
84
|
+
return result.payload;
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
const code = err?.code ?? err?.name ?? "unknown";
|
|
88
|
+
const reason = err?.message ?? "";
|
|
89
|
+
this.log("warning", "token validation failed", { code, reason });
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
84
92
|
}
|
|
85
93
|
/* check whether request is authenticated */
|
|
86
|
-
async authenticated(clientId, tokens, option) {
|
|
94
|
+
async authenticated(clientId, tokens, option, label) {
|
|
87
95
|
let authenticated = false;
|
|
88
96
|
/* determine authentication configuration */
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (typeof option === "string") {
|
|
92
|
-
mode = "require";
|
|
93
|
-
roles = [option];
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
mode = option.mode;
|
|
97
|
-
roles = option.roles;
|
|
98
|
-
}
|
|
97
|
+
const roles = typeof option === "string"
|
|
98
|
+
? [option] : option.roles;
|
|
99
99
|
/* iterate over all roles and try to authenticate token (first-match, max 8) */
|
|
100
100
|
if (tokens !== undefined) {
|
|
101
101
|
for (const token of tokens.slice(0, 8)) {
|
|
@@ -120,9 +120,10 @@ export class AuthTrait extends MetaTrait {
|
|
|
120
120
|
break;
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
-
/*
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
/* enforce "require" mode */
|
|
124
|
+
const required = (typeof option === "string" || option.mode === "require");
|
|
125
|
+
if (!authenticated && required)
|
|
126
|
+
throw new Error(`${label} failed authentication`);
|
|
126
127
|
return authenticated;
|
|
127
128
|
}
|
|
128
129
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mqtt-plus-auth.js","sourceRoot":"","sources":["../src/mqtt-plus-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;EAsBE;AAEF,6BAA6B;AAC7B,OAAO,EAAE,OAAO,EAAE,MAAc,eAAe,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAY,iBAAiB,CAAA;AACjD,OAAO,KAAK,MAAM,MAAc,mBAAmB,CAAA;AACnD,OAAO,KAAK,MAAM,MAAc,mBAAmB,CAAA;AAInD,OAAO,EAAE,SAAS,EAAE,MAAY,kBAAkB,CAAA;AAQlD,iCAAiC;AACjC,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAA;AAErC,4BAA4B;AAC5B,MAAM,OAAO,SAA2C,SAAQ,SAAY;IAA5E;;QACI,sBAAsB;QACd,gBAAW,GAAsB,IAAI,CAAA;QACrC,YAAO,GAAG,IAAI,GAAG,EAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"mqtt-plus-auth.js","sourceRoot":"","sources":["../src/mqtt-plus-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,11 +6,12 @@ import { Spool } from "./mqtt-plus-error";
|
|
|
6
6
|
export declare class BaseTrait<T extends APISchema = APISchema> extends TraceTrait<T> {
|
|
7
7
|
private mqtt;
|
|
8
8
|
private messageHandler;
|
|
9
|
+
protected destroyed: boolean;
|
|
9
10
|
protected onRequest: Map<string, (message: any, topicName: string) => void | Promise<void>>;
|
|
10
11
|
protected onResponse: Map<string, (message: any, topicName: string) => void | Promise<void>>;
|
|
11
12
|
constructor(mqtt: MqttClient | null, options?: Partial<APIOptions>);
|
|
12
13
|
destroy(): Promise<void>;
|
|
13
|
-
protected makeRegistration(spool: Spool, kind: string, name: string
|
|
14
|
+
protected makeRegistration(spool: Spool, kind: string, name: string): Registration;
|
|
14
15
|
protected subscribeTopic(topic: string, options?: Partial<IClientSubscribeOptions>): Promise<void>;
|
|
15
16
|
protected unsubscribeTopic(topic: string): Promise<void>;
|
|
16
17
|
protected publishToTopic(topic: string, message: string | Uint8Array, options?: IClientPublishOptions): Promise<void>;
|