mqtt-plus 1.4.0 → 1.4.2
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/AGENTS.md +55 -44
- package/CHANGELOG.md +14 -0
- package/README.md +4 -3
- package/doc/mqtt-plus-api.md +693 -680
- package/doc/mqtt-plus-architecture.d2 +139 -0
- package/doc/mqtt-plus-architecture.md +10 -0
- package/doc/mqtt-plus-architecture.svg +95 -0
- package/doc/mqtt-plus-broker-setup.md +9 -3
- package/doc/mqtt-plus-comm.md +73 -0
- package/doc/mqtt-plus-internals.md +3 -3
- package/dst-stage1/mqtt-plus-base.d.ts +3 -2
- package/dst-stage1/mqtt-plus-base.js +53 -22
- package/dst-stage1/mqtt-plus-event.d.ts +0 -2
- package/dst-stage1/mqtt-plus-event.js +6 -26
- package/dst-stage1/mqtt-plus-meta.d.ts +2 -2
- package/dst-stage1/mqtt-plus-meta.js +2 -2
- package/dst-stage1/mqtt-plus-msg.d.ts +2 -0
- package/dst-stage1/mqtt-plus-msg.js +17 -0
- package/dst-stage1/mqtt-plus-service.d.ts +0 -5
- package/dst-stage1/mqtt-plus-service.js +12 -48
- package/dst-stage1/mqtt-plus-sink.d.ts +0 -10
- package/dst-stage1/mqtt-plus-sink.js +25 -92
- package/dst-stage1/mqtt-plus-source.d.ts +0 -10
- package/dst-stage1/mqtt-plus-source.js +23 -88
- package/dst-stage1/mqtt-plus-subscription.d.ts +20 -0
- package/dst-stage1/mqtt-plus-subscription.js +126 -0
- package/dst-stage1/mqtt-plus-timer.d.ts +8 -0
- package/dst-stage1/mqtt-plus-timer.js +57 -0
- package/dst-stage1/mqtt-plus-topic.d.ts +20 -0
- package/dst-stage1/mqtt-plus-topic.js +112 -0
- package/dst-stage1/mqtt-plus-trace.js +2 -0
- package/dst-stage1/mqtt-plus-util.d.ts +0 -13
- package/dst-stage1/mqtt-plus-util.js +1 -77
- package/dst-stage1/mqtt-plus-version.d.ts +0 -1
- package/dst-stage1/mqtt-plus-version.js +0 -6
- package/dst-stage1/tsc.tsbuildinfo +1 -1
- package/dst-stage2/mqtt-plus.cjs.js +242 -292
- package/dst-stage2/mqtt-plus.esm.js +240 -290
- package/dst-stage2/mqtt-plus.umd.js +12 -12
- package/etc/knip.jsonc +1 -1
- package/etc/stx.conf +6 -4
- package/package.json +1 -1
- package/src/mqtt-plus-base.ts +56 -26
- package/src/mqtt-plus-event.ts +8 -24
- package/src/mqtt-plus-meta.ts +3 -3
- package/src/mqtt-plus-msg.ts +28 -0
- package/src/mqtt-plus-service.ts +12 -50
- package/src/mqtt-plus-sink.ts +32 -105
- package/src/mqtt-plus-source.ts +29 -99
- package/src/mqtt-plus-subscription.ts +141 -0
- package/src/mqtt-plus-timer.ts +61 -0
- package/src/mqtt-plus-trace.ts +4 -0
- package/src/mqtt-plus-util.ts +1 -81
- package/src/mqtt-plus-version.ts +0 -7
- package/tst/mqtt-plus-0-fixture.ts +2 -2
- package/tst/mqtt-plus-0-mosquitto.ts +5 -0
- package/tst/mqtt-plus-1-api.spec.ts +1 -1
- package/tst/mqtt-plus-2-event.spec.ts +0 -6
- package/tst/mqtt-plus-3-service.spec.ts +3 -7
- package/tst/mqtt-plus-4-sink.spec.ts +14 -9
- package/tst/mqtt-plus-5-source.spec.ts +11 -5
- package/tst/mqtt-plus-6-misc.spec.ts +23 -23
- package/tst/tsc.json +1 -1
- package/doc/mqtt-plus-communication.md +0 -68
- /package/doc/{mqtt-plus-1-event-emission.d2 → mqtt-plus-comm-event-emission.d2} +0 -0
- /package/doc/{mqtt-plus-1-event-emission.svg → mqtt-plus-comm-event-emission.svg} +0 -0
- /package/doc/{mqtt-plus-2-service-call.d2 → mqtt-plus-comm-service-call.d2} +0 -0
- /package/doc/{mqtt-plus-2-service-call.svg → mqtt-plus-comm-service-call.svg} +0 -0
- /package/doc/{mqtt-plus-3-sink-push.d2 → mqtt-plus-comm-sink-push.d2} +0 -0
- /package/doc/{mqtt-plus-3-sink-push.svg → mqtt-plus-comm-sink-push.svg} +0 -0
- /package/doc/{mqtt-plus-4-source-fetch.d2 → mqtt-plus-comm-source-fetch.d2} +0 -0
- /package/doc/{mqtt-plus-4-source-fetch.svg → mqtt-plus-comm-source-fetch.svg} +0 -0
- /package/{doc/theme.d2 → etc/d2.theme.d2} +0 -0
package/etc/knip.jsonc
CHANGED
package/etc/stx.conf
CHANGED
|
@@ -35,10 +35,11 @@ build : lint
|
|
|
35
35
|
|
|
36
36
|
# documentation compilation
|
|
37
37
|
build-doc
|
|
38
|
-
node etc/d2.mts
|
|
39
|
-
node etc/d2.mts
|
|
40
|
-
node etc/d2.mts
|
|
41
|
-
node etc/d2.mts
|
|
38
|
+
node etc/d2.mts etc/d2.theme.d2 doc/mqtt-plus-comm-event-emission.d2 doc/mqtt-plus-comm-event-emission.svg
|
|
39
|
+
node etc/d2.mts etc/d2.theme.d2 doc/mqtt-plus-comm-service-call.d2 doc/mqtt-plus-comm-service-call.svg
|
|
40
|
+
node etc/d2.mts etc/d2.theme.d2 doc/mqtt-plus-comm-sink-push.d2 doc/mqtt-plus-comm-sink-push.svg
|
|
41
|
+
node etc/d2.mts etc/d2.theme.d2 doc/mqtt-plus-comm-source-fetch.d2 doc/mqtt-plus-comm-source-fetch.svg
|
|
42
|
+
node etc/d2.mts etc/d2.theme.d2 doc/mqtt-plus-architecture.d2 doc/mqtt-plus-architecture.svg
|
|
42
43
|
|
|
43
44
|
# development loop
|
|
44
45
|
dev
|
|
@@ -50,6 +51,7 @@ dev
|
|
|
50
51
|
|
|
51
52
|
# run test suite
|
|
52
53
|
test
|
|
54
|
+
tsc --project tst/tsc.json --noEmit && \
|
|
53
55
|
eslint --config etc/eslint.mts tst/mqtt-plus-0-fixture.ts tst/mqtt-plus-0-mosquitto.ts tst/*.spec.ts && \
|
|
54
56
|
NODE_OPTIONS="--import=tsx --trace-warnings" mocha --require tst/mqtt-plus-0-fixture.ts tst/*.spec.ts
|
|
55
57
|
|
package/package.json
CHANGED
package/src/mqtt-plus-base.ts
CHANGED
|
@@ -34,12 +34,17 @@ import { MqttClient,
|
|
|
34
34
|
import type { APISchema } from "./mqtt-plus-api"
|
|
35
35
|
import type { APIOptions } from "./mqtt-plus-options"
|
|
36
36
|
import { TraceTrait } from "./mqtt-plus-trace"
|
|
37
|
+
import { ensureError } from "./mqtt-plus-error"
|
|
37
38
|
|
|
38
39
|
/* MQTTp Base class with shared infrastructure */
|
|
39
40
|
export class BaseTrait<T extends APISchema = APISchema> extends TraceTrait<T> {
|
|
40
41
|
protected mqtt: MqttClient
|
|
41
42
|
private _messageHandler: OnMessageCallback
|
|
42
43
|
|
|
44
|
+
/* central message callback registries */
|
|
45
|
+
protected onRequest = new Map<string, (message: any, topicName: string) => void>()
|
|
46
|
+
protected onResponse = new Map<string, (message: any, topicName: string) => void>()
|
|
47
|
+
|
|
43
48
|
/* construct API class */
|
|
44
49
|
constructor (
|
|
45
50
|
mqtt: MqttClient | null,
|
|
@@ -92,7 +97,7 @@ export class BaseTrait<T extends APISchema = APISchema> extends TraceTrait<T> {
|
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
/* destroy API class */
|
|
95
|
-
destroy () {
|
|
100
|
+
async destroy () {
|
|
96
101
|
this.log("info", "un-hooking from MQTT client")
|
|
97
102
|
this.mqtt.off("message", this._messageHandler)
|
|
98
103
|
}
|
|
@@ -171,36 +176,61 @@ export class BaseTrait<T extends APISchema = APISchema> extends TraceTrait<T> {
|
|
|
171
176
|
}
|
|
172
177
|
|
|
173
178
|
/* handle incoming MQTT message */
|
|
174
|
-
private _onMessage (topic: string,
|
|
175
|
-
/*
|
|
176
|
-
|
|
177
|
-
|
|
179
|
+
private _onMessage (topic: string, data: string | Uint8Array, packet: IPublishPacket): void {
|
|
180
|
+
/* parse MQTT topic */
|
|
181
|
+
const topicMatch = this.options.topicMatch(topic)
|
|
182
|
+
if (topicMatch === null)
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
/* parse MQTT data into payload object */
|
|
186
|
+
if (typeof data === "string")
|
|
187
|
+
this.log("info", `received from MQTT topic "${topic}" (type: string, length: ${data.length} chars)`)
|
|
178
188
|
else
|
|
179
|
-
this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${
|
|
180
|
-
let
|
|
189
|
+
this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${data.byteLength} bytes)`)
|
|
190
|
+
let payload: any
|
|
181
191
|
try {
|
|
182
|
-
|
|
183
|
-
parsed = this.msg.parse(payload)
|
|
192
|
+
payload = this.codec.decode(data)
|
|
184
193
|
}
|
|
185
|
-
catch (
|
|
186
|
-
|
|
187
|
-
? new Error(`failed to parse message: ${_err.message}`, { cause: _err })
|
|
188
|
-
: new Error("failed to parse message")
|
|
189
|
-
this.error(err)
|
|
194
|
+
catch (err: unknown) {
|
|
195
|
+
this.error(ensureError(err, "failed to parse message into object"))
|
|
190
196
|
return
|
|
191
197
|
}
|
|
192
|
-
this.log("debug", `received from MQTT topic "${topic}"`, { message: parsed })
|
|
193
198
|
|
|
194
|
-
/*
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
199
|
+
/* parse payload object into typed MQTT+ message */
|
|
200
|
+
let message: any
|
|
201
|
+
try {
|
|
202
|
+
message = this.msg.parse(payload)
|
|
203
|
+
}
|
|
204
|
+
catch (err: unknown) {
|
|
205
|
+
this.error(ensureError(err, "failed to parse object into typed message object"))
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
this.log("debug", `received from MQTT topic "${topic}"`, { message })
|
|
209
|
+
|
|
210
|
+
/* dispatch MQTT+ message */
|
|
211
|
+
if (this.msg.isRequest(message)) {
|
|
212
|
+
/* dispatch request message */
|
|
213
|
+
const handler = this.onRequest.get(`${topicMatch.operation}:${message.name}`)
|
|
214
|
+
if (handler !== undefined) {
|
|
215
|
+
try {
|
|
216
|
+
handler(message, topicMatch.name)
|
|
217
|
+
}
|
|
218
|
+
catch (err: unknown) {
|
|
219
|
+
this.error(ensureError(err, `dispatching request message from MQTT topic "${topic}" failed`))
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else if (this.msg.isResponse(message)) {
|
|
224
|
+
/* dispatch response message */
|
|
225
|
+
const handler = this.onResponse.get(`${topicMatch.operation}:${message.id}`)
|
|
226
|
+
if (handler !== undefined) {
|
|
227
|
+
try {
|
|
228
|
+
handler(message, topicMatch.name)
|
|
229
|
+
}
|
|
230
|
+
catch (err: unknown) {
|
|
231
|
+
this.error(ensureError(err, `dispatching response message from MQTT topic "${topic}" failed`))
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
198
235
|
}
|
|
199
|
-
|
|
200
|
-
/* dispatch parsed message to appropriate handler
|
|
201
|
-
(base implementation, to be overridden in sub-traits) */
|
|
202
|
-
protected async _dispatchMessage (
|
|
203
|
-
_topic: string,
|
|
204
|
-
_parsed: any
|
|
205
|
-
): Promise<void> {}
|
|
206
236
|
}
|
package/src/mqtt-plus-event.ts
CHANGED
|
@@ -37,9 +37,6 @@ import { run, Spool, ensureError } from "./mqtt-plus-error"
|
|
|
37
37
|
|
|
38
38
|
/* Event Emission Trait */
|
|
39
39
|
export class EventTrait<T extends APISchema = APISchema> extends AuthTrait<T> {
|
|
40
|
-
/* internal state */
|
|
41
|
-
private events = new Map<string, (request: EventEmission, topicName: string) => void>()
|
|
42
|
-
|
|
43
40
|
/* register an event handler */
|
|
44
41
|
async event<K extends EventKeys<T> & string> (
|
|
45
42
|
name: K,
|
|
@@ -88,7 +85,7 @@ export class EventTrait<T extends APISchema = APISchema> extends AuthTrait<T> {
|
|
|
88
85
|
const spool = new Spool()
|
|
89
86
|
|
|
90
87
|
/* sanity check situation */
|
|
91
|
-
if (this.
|
|
88
|
+
if (this.onRequest.has(`event-emission:${name}`))
|
|
92
89
|
throw new Error(`event: event "${name}" already registered`)
|
|
93
90
|
|
|
94
91
|
/* generate the corresponding MQTT topics for broadcast and direct use */
|
|
@@ -97,7 +94,7 @@ export class EventTrait<T extends APISchema = APISchema> extends AuthTrait<T> {
|
|
|
97
94
|
const topicD = this.options.topicMake(name, "event-emission", this.options.id)
|
|
98
95
|
|
|
99
96
|
/* remember the registration */
|
|
100
|
-
this.
|
|
97
|
+
this.onRequest.set(`event-emission:${name}`, (request: EventEmission, topicName: string) => {
|
|
101
98
|
/* determine event information */
|
|
102
99
|
const senderId = request.sender
|
|
103
100
|
const params = request.params ?? []
|
|
@@ -123,7 +120,7 @@ export class EventTrait<T extends APISchema = APISchema> extends AuthTrait<T> {
|
|
|
123
120
|
this.error(error, `handler for event "${name}" failed`)
|
|
124
121
|
})
|
|
125
122
|
})
|
|
126
|
-
spool.roll(() => { this.
|
|
123
|
+
spool.roll(() => { this.onRequest.delete(`event-emission:${name}`) })
|
|
127
124
|
|
|
128
125
|
/* subscribe to MQTT topics */
|
|
129
126
|
await run(`subscribe to MQTT topic "${topicB}"`, spool, () =>
|
|
@@ -136,7 +133,7 @@ export class EventTrait<T extends APISchema = APISchema> extends AuthTrait<T> {
|
|
|
136
133
|
/* provide a registration for subsequent destruction */
|
|
137
134
|
return {
|
|
138
135
|
destroy: async (): Promise<void> => {
|
|
139
|
-
if (!this.
|
|
136
|
+
if (!this.onRequest.has(`event-emission:${name}`))
|
|
140
137
|
throw new Error(`destroy: event "${name}" not registered`)
|
|
141
138
|
await spool.unroll(false)?.catch((err: Error) => {
|
|
142
139
|
this.error(err, `destroy: failed to cleanup: ${err.message}`)
|
|
@@ -185,7 +182,7 @@ export class EventTrait<T extends APISchema = APISchema> extends AuthTrait<T> {
|
|
|
185
182
|
let params: Parameters<T[K]>
|
|
186
183
|
let receiver: string | undefined
|
|
187
184
|
let options: IClientPublishOptions = {}
|
|
188
|
-
let meta: Record<string, any>
|
|
185
|
+
let meta: Record<string, any> | undefined
|
|
189
186
|
let dry: boolean | undefined
|
|
190
187
|
if (typeof eventOrConfig === "object" && eventOrConfig !== null) {
|
|
191
188
|
/* object-based API */
|
|
@@ -193,7 +190,7 @@ export class EventTrait<T extends APISchema = APISchema> extends AuthTrait<T> {
|
|
|
193
190
|
params = eventOrConfig.params
|
|
194
191
|
receiver = eventOrConfig.receiver
|
|
195
192
|
options = eventOrConfig.options ?? {}
|
|
196
|
-
meta = eventOrConfig.meta
|
|
193
|
+
meta = eventOrConfig.meta
|
|
197
194
|
dry = eventOrConfig.dry
|
|
198
195
|
}
|
|
199
196
|
else {
|
|
@@ -208,7 +205,8 @@ export class EventTrait<T extends APISchema = APISchema> extends AuthTrait<T> {
|
|
|
208
205
|
/* generate encoded message */
|
|
209
206
|
const auth = this.authenticate()
|
|
210
207
|
const metaStore = this.metaStore(meta)
|
|
211
|
-
const request = this.msg.makeEventEmission(requestId, event, params,
|
|
208
|
+
const request = this.msg.makeEventEmission(requestId, event, params,
|
|
209
|
+
this.options.id, receiver, auth, metaStore)
|
|
212
210
|
const message = this.codec.encode(request)
|
|
213
211
|
|
|
214
212
|
/* generate corresponding MQTT topic */
|
|
@@ -225,18 +223,4 @@ export class EventTrait<T extends APISchema = APISchema> extends AuthTrait<T> {
|
|
|
225
223
|
})
|
|
226
224
|
}
|
|
227
225
|
|
|
228
|
-
/* dispatch message (Event pattern handling) */
|
|
229
|
-
protected override async _dispatchMessage (topic: string, message: any) {
|
|
230
|
-
await super._dispatchMessage(topic, message)
|
|
231
|
-
const topicMatch = this.options.topicMatch(topic)
|
|
232
|
-
|
|
233
|
-
/* on server-side handle event emission request */
|
|
234
|
-
if (topicMatch !== null
|
|
235
|
-
&& topicMatch.operation === "event-emission"
|
|
236
|
-
&& message instanceof EventEmission) {
|
|
237
|
-
const handler = this.events.get(message.name)
|
|
238
|
-
if (handler !== undefined)
|
|
239
|
-
handler(message, topicMatch.name)
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
226
|
}
|
package/src/mqtt-plus-meta.ts
CHANGED
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
/* internal requirements */
|
|
26
|
-
import type { APISchema }
|
|
27
|
-
import {
|
|
26
|
+
import type { APISchema } from "./mqtt-plus-api"
|
|
27
|
+
import { TimerTrait } from "./mqtt-plus-timer"
|
|
28
28
|
|
|
29
29
|
/* Meta trait with meta information management */
|
|
30
|
-
export class MetaTrait<T extends APISchema = APISchema> extends
|
|
30
|
+
export class MetaTrait<T extends APISchema = APISchema> extends TimerTrait<T> {
|
|
31
31
|
/* internal state */
|
|
32
32
|
private _meta: Map<string, any> = new Map()
|
|
33
33
|
|
package/src/mqtt-plus-msg.ts
CHANGED
|
@@ -509,6 +509,34 @@ class Msg {
|
|
|
509
509
|
else
|
|
510
510
|
throw new Error("invalid object: not of any known type")
|
|
511
511
|
}
|
|
512
|
+
|
|
513
|
+
/* guard for request messages */
|
|
514
|
+
isRequest (msg: any): msg is (
|
|
515
|
+
EventEmission | ServiceCallRequest | SourceFetchRequest | SinkPushRequest
|
|
516
|
+
) {
|
|
517
|
+
return (
|
|
518
|
+
msg instanceof EventEmission
|
|
519
|
+
|| msg instanceof ServiceCallRequest
|
|
520
|
+
|| msg instanceof SourceFetchRequest
|
|
521
|
+
|| msg instanceof SinkPushRequest
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/* guard for response messages */
|
|
526
|
+
isResponse (msg: any): msg is (
|
|
527
|
+
ServiceCallResponse | SinkPushResponse | SinkPushChunk | SinkPushCredit |
|
|
528
|
+
SourceFetchResponse | SourceFetchChunk | SourceFetchCredit
|
|
529
|
+
) {
|
|
530
|
+
return (
|
|
531
|
+
msg instanceof ServiceCallResponse
|
|
532
|
+
|| msg instanceof SinkPushResponse
|
|
533
|
+
|| msg instanceof SinkPushChunk
|
|
534
|
+
|| msg instanceof SinkPushCredit
|
|
535
|
+
|| msg instanceof SourceFetchResponse
|
|
536
|
+
|| msg instanceof SourceFetchChunk
|
|
537
|
+
|| msg instanceof SourceFetchCredit
|
|
538
|
+
)
|
|
539
|
+
}
|
|
512
540
|
}
|
|
513
541
|
|
|
514
542
|
/* message trait */
|
package/src/mqtt-plus-service.ts
CHANGED
|
@@ -28,7 +28,6 @@ import type { IClientPublishOptions,
|
|
|
28
28
|
import { nanoid } from "nanoid"
|
|
29
29
|
|
|
30
30
|
/* internal requirements */
|
|
31
|
-
import { RefCountedSubscription } from "./mqtt-plus-util"
|
|
32
31
|
import { run, Spool, ensureError } from "./mqtt-plus-error"
|
|
33
32
|
import { ServiceCallRequest,
|
|
34
33
|
ServiceCallResponse } from "./mqtt-plus-msg"
|
|
@@ -40,19 +39,6 @@ import type { AuthOption } from "./mqtt-plus-auth"
|
|
|
40
39
|
|
|
41
40
|
/* Service Call Trait */
|
|
42
41
|
export class ServiceTrait<T extends APISchema = APISchema> extends EventTrait<T> {
|
|
43
|
-
/* internal state */
|
|
44
|
-
private services = new Map<string, (request: ServiceCallRequest, topicName: string) => void>()
|
|
45
|
-
private callCallbacks = new Map<string, (response: ServiceCallResponse) => void>()
|
|
46
|
-
private callSubscriptions = new RefCountedSubscription(
|
|
47
|
-
(topic, options) => this._subscribeTopic(topic, options),
|
|
48
|
-
(topic) => this._unsubscribeTopic(topic)
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
/* destroy service trait */
|
|
52
|
-
override destroy () {
|
|
53
|
-
super.destroy()
|
|
54
|
-
this.callSubscriptions.flush()
|
|
55
|
-
}
|
|
56
42
|
|
|
57
43
|
/* register a service call handler */
|
|
58
44
|
async service<K extends ServiceKeys<T> & string> (
|
|
@@ -102,8 +88,8 @@ export class ServiceTrait<T extends APISchema = APISchema> extends EventTrait<T>
|
|
|
102
88
|
const spool = new Spool()
|
|
103
89
|
|
|
104
90
|
/* sanity check situation */
|
|
105
|
-
if (this.
|
|
106
|
-
throw new Error(`
|
|
91
|
+
if (this.onRequest.has(`service-call-request:${name}`))
|
|
92
|
+
throw new Error(`service: service "${name}" already registered`)
|
|
107
93
|
|
|
108
94
|
/* generate the corresponding MQTT topics for broadcast and direct use */
|
|
109
95
|
const topicS = `$share/${share}/${name}`
|
|
@@ -111,7 +97,7 @@ export class ServiceTrait<T extends APISchema = APISchema> extends EventTrait<T>
|
|
|
111
97
|
const topicD = this.options.topicMake(name, "service-call-request", this.options.id)
|
|
112
98
|
|
|
113
99
|
/* remember the registration */
|
|
114
|
-
this.
|
|
100
|
+
this.onRequest.set(`service-call-request:${name}`, (request: ServiceCallRequest, topicName: string) => {
|
|
115
101
|
/* determine request information */
|
|
116
102
|
const requestId = request.id
|
|
117
103
|
const senderId = request.sender
|
|
@@ -154,7 +140,7 @@ export class ServiceTrait<T extends APISchema = APISchema> extends EventTrait<T>
|
|
|
154
140
|
this.error(err, `handler for service "${name}" failed`)
|
|
155
141
|
})
|
|
156
142
|
})
|
|
157
|
-
spool.roll(() => { this.
|
|
143
|
+
spool.roll(() => { this.onRequest.delete(`service-call-request:${name}`) })
|
|
158
144
|
|
|
159
145
|
/* subscribe to MQTT topics */
|
|
160
146
|
await run(`subscribe to MQTT topic "${topicB}"`, spool, () =>
|
|
@@ -167,8 +153,8 @@ export class ServiceTrait<T extends APISchema = APISchema> extends EventTrait<T>
|
|
|
167
153
|
/* provide a registration for subsequent destruction */
|
|
168
154
|
return {
|
|
169
155
|
destroy: async (): Promise<void> => {
|
|
170
|
-
if (!this.
|
|
171
|
-
throw new Error(`destroy: service "${name}"
|
|
156
|
+
if (!this.onRequest.has(`service-call-request:${name}`))
|
|
157
|
+
throw new Error(`destroy: service "${name}" not registered`)
|
|
172
158
|
await spool.unroll(false)?.catch((err: Error) => {
|
|
173
159
|
this.error(err, `destroy: failed to cleanup: ${err.message}`)
|
|
174
160
|
})
|
|
@@ -205,14 +191,14 @@ export class ServiceTrait<T extends APISchema = APISchema> extends EventTrait<T>
|
|
|
205
191
|
let params: Parameters<T[K]>
|
|
206
192
|
let receiver: string | undefined
|
|
207
193
|
let options: IClientPublishOptions = {}
|
|
208
|
-
let meta: Record<string, any>
|
|
194
|
+
let meta: Record<string, any> | undefined
|
|
209
195
|
if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
|
|
210
196
|
/* object-based API */
|
|
211
197
|
name = nameOrConfig.name
|
|
212
198
|
params = nameOrConfig.params
|
|
213
199
|
receiver = nameOrConfig.receiver
|
|
214
200
|
options = nameOrConfig.options ?? {}
|
|
215
|
-
meta = nameOrConfig.meta
|
|
201
|
+
meta = nameOrConfig.meta
|
|
216
202
|
}
|
|
217
203
|
else {
|
|
218
204
|
/* positional API */
|
|
@@ -229,8 +215,8 @@ export class ServiceTrait<T extends APISchema = APISchema> extends EventTrait<T>
|
|
|
229
215
|
/* subscribe to MQTT response topic */
|
|
230
216
|
const responseTopic = this.options.topicMake(name, "service-call-response", this.options.id)
|
|
231
217
|
await run(`subscribe to MQTT topic "${responseTopic}"`, spool, () =>
|
|
232
|
-
this.
|
|
233
|
-
spool.roll(() => this.
|
|
218
|
+
this.subscriptions.subscribe(responseTopic, { qos: options.qos ?? 2 }))
|
|
219
|
+
spool.roll(() => this.subscriptions.unsubscribe(responseTopic))
|
|
234
220
|
|
|
235
221
|
/* create promise for MQTT response handling */
|
|
236
222
|
const promise: Promise<ReturnType<T[K]>> = new Promise((resolve, reject) => {
|
|
@@ -245,14 +231,14 @@ export class ServiceTrait<T extends APISchema = APISchema> extends EventTrait<T>
|
|
|
245
231
|
timer = null
|
|
246
232
|
}
|
|
247
233
|
})
|
|
248
|
-
this.
|
|
234
|
+
this.onResponse.set(`service-call-response:${requestId}`, async (response: ServiceCallResponse) => {
|
|
249
235
|
await spool.unroll()
|
|
250
236
|
if (response.error !== undefined)
|
|
251
237
|
reject(new Error(response.error))
|
|
252
238
|
else
|
|
253
239
|
resolve(response.result)
|
|
254
240
|
})
|
|
255
|
-
spool.roll(() => { this.
|
|
241
|
+
spool.roll(() => { this.onResponse.delete(`service-call-response:${requestId}`) })
|
|
256
242
|
})
|
|
257
243
|
|
|
258
244
|
/* generate encoded message */
|
|
@@ -272,28 +258,4 @@ export class ServiceTrait<T extends APISchema = APISchema> extends EventTrait<T>
|
|
|
272
258
|
return promise
|
|
273
259
|
}
|
|
274
260
|
|
|
275
|
-
/* dispatch message (Service pattern handling) */
|
|
276
|
-
protected override async _dispatchMessage (topic: string, message: any) {
|
|
277
|
-
await super._dispatchMessage(topic, message)
|
|
278
|
-
const topicMatch = this.options.topicMatch(topic)
|
|
279
|
-
|
|
280
|
-
/* on server-side handle service call request */
|
|
281
|
-
if (topicMatch !== null
|
|
282
|
-
&& topicMatch.operation === "service-call-request"
|
|
283
|
-
&& message instanceof ServiceCallRequest) {
|
|
284
|
-
const handler = this.services.get(message.name)
|
|
285
|
-
if (handler !== undefined)
|
|
286
|
-
handler(message, topicMatch.name)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/* on client-side handle service call response */
|
|
290
|
-
else if (topicMatch !== null
|
|
291
|
-
&& topicMatch.operation === "service-call-response"
|
|
292
|
-
&& topicMatch.peerId === this.options.id
|
|
293
|
-
&& message instanceof ServiceCallResponse) {
|
|
294
|
-
const handler = this.callCallbacks.get(message.id)
|
|
295
|
-
if (handler !== undefined)
|
|
296
|
-
handler(message)
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
261
|
}
|