mqtt-plus 0.9.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/etc/logo.svg ADDED
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 805 283">
3
+ <defs>
4
+ <style>
5
+ .cls-1 {
6
+ fill: #369;
7
+ }
8
+ </style>
9
+ </defs>
10
+ <path class="cls-1" d="M728.126,19.9971v60.9019h58.3037v31.1724h-58.3037v61.4795h-32.6162v-61.4795h-58.3047v-31.1724h58.3047V19.9971h32.6162Z"/>
11
+ <path class="cls-1" d="M429.6187,268.3086c-30.5801-9.0498-54.2949-18.7227-84.251-31.5166-4.9927-2.1846-10.2974-3.1201-15.2905-3.4326-46.4941-3.4326-91.7402-37.7568-91.7402-107.0303,0-61.4727,39.3174-110.4629,105.1582-110.4629,67.0894,0,101.4141,50.5508,101.4141,106.0938,0,45.2461-20.9072,78.6348-45.2466,91.1172v1.248c14.042,4.6807,29.9561,7.8008,45.8706,11.2334l-15.9146,42.75ZM342.2471,188.7373c27.1475,0,43.062-26.2119,43.062-64.5928,0-36.8213-16.2266-63.6562-43.686-63.6562-26.5234,0-43.686,26.835-43.686,63.9688-.312,38.0684,16.5381,64.2803,43.686,64.2803h.624Z"/>
12
+ <path d="M228.4019,126.7417c0-26.8698,7.5125-51.3542,21.4678-70.4055l-1.8526-36.7254h-73.9541l-15.9141,56.1675c-6.2407,22.1553-13.106,51.7993-19.0347,77.3867h-.936c-4.3687-25.5874-10.6094-53.6714-16.5381-77.0747l-13.73-56.4795H32.084l-13.106,210.3164h51.7988l3.4326-75.2021c.936-24.0273,1.8721-55.2314,3.1206-84.8755h.624c4.9927,28.708,11.2334,59.9121,16.8501,82.6909l17.4746,73.0181h43.9976l20.9072-73.3301c6.8647-23.0908,15.6021-54.6069,22.1548-82.3789h.936c0,32.7642.312,60.8481,1.2485,84.8755l2.4961,75.2021h54.6074l-1.2318-24.4186c-17.433-17.6805-28.9928-43.7579-28.9928-78.767Z"/>
13
+ <g>
14
+ <polygon points="568.0386 19.6108 528.2663 19.6108 547.709 67.041 568.0386 67.041 568.9517 67.041 568.9517 229.9272 625.4312 229.9272 625.4312 67.041 680.0386 67.041 680.0386 19.6108 568.0386 19.6108"/>
15
+ <path d="M516.378,19.6108h-113.0977v6.1184c18.5046,9.1639,32.2805,23.7489,41.2421,41.3118h12.4293v52.0095c.0134.7731.0225,1.5469.0225,2.3221,0,.5229-.0169,1.0361-.0225,1.5558v101.9045c.2156.0464.4308.0918.6465.1383l-.6465,1.7366v3.2195h56.4795V67.041h22.3896l-19.4427-47.4302Z"/>
16
+ </g>
17
+ </svg>
package/etc/stx.conf ADDED
@@ -0,0 +1,47 @@
1
+ ##
2
+ ## MQTT+ -- MQTT Communication Patterns
3
+ ## Copyright (c) 2018-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ ##
5
+ ## Permission is hereby granted, free of charge, to any person obtaining
6
+ ## a copy of this software and associated documentation files (the
7
+ ## "Software"), to deal in the Software without restriction, including
8
+ ## without limitation the rights to use, copy, modify, merge, publish,
9
+ ## distribute, sublicense, and/or sell copies of the Software, and to
10
+ ## permit persons to whom the Software is furnished to do so, subject to
11
+ ## the following conditions:
12
+ ##
13
+ ## The above copyright notice and this permission notice shall be included
14
+ ## in all copies or substantial portions of the Software.
15
+ ##
16
+ ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ ## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ ## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ ## IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ ## CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ ## TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ ## SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ ##
24
+
25
+ # static code analysis (linting)
26
+ lint
27
+ tsc --project etc/tsc.json --noEmit && \
28
+ eslint --config etc/eslint.mts src/*.ts sample/*.ts
29
+
30
+ # code compilation/transpiling (building)
31
+ build : lint
32
+ VITE_BUILD_FORMATS=es,cjs vite --config etc/vite.mts build --mode production
33
+ VITE_BUILD_FORMATS=umd vite --config etc/vite.mts build --mode production
34
+
35
+ # execute simple sample for testing
36
+ test
37
+ npx tsx sample/sample.ts
38
+
39
+ # cleanup (built artifacts)
40
+ clean
41
+ shx rm -rf dst
42
+
43
+ # cleanup (bootstrap artifacts)
44
+ distclean : clean
45
+ shx rm -f package-lock.json
46
+ shx rm -rf node_modules
47
+
package/etc/tsc.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "../dst-stage1",
4
+ "target": "es2022",
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "useDefineForClassFields": false,
8
+ "composite": false,
9
+ "strict": true,
10
+ "strictFunctionTypes": true,
11
+ "resolveJsonModule": false,
12
+ "isolatedModules": false,
13
+ "esModuleInterop": false,
14
+ "lib": [ "es2022", "dom" ],
15
+ "skipLibCheck": true,
16
+ "declaration": true,
17
+ "noEmit": false,
18
+ "rootDir": "../src"
19
+ },
20
+ "include": [
21
+ "../src/**/*.ts",
22
+ "../src/**/*.d.ts"
23
+ ],
24
+ "exclude": [
25
+ "../node_modules"
26
+ ]
27
+ }
@@ -0,0 +1 @@
1
+ {"root":["../src/mqtt-plus-codec.ts","../src/mqtt-plus-msg.ts","../src/mqtt-plus.ts"],"version":"5.9.3"}
package/etc/vite.mts ADDED
@@ -0,0 +1,66 @@
1
+ /*
2
+ ** MQTT+ -- MQTT Communication Patterns
3
+ ** Copyright (c) 2018-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ **
5
+ ** Permission is hereby granted, free of charge, to any person obtaining
6
+ ** a copy of this software and associated documentation files (the
7
+ ** "Software"), to deal in the Software without restriction, including
8
+ ** without limitation the rights to use, copy, modify, merge, publish,
9
+ ** distribute, sublicense, and/or sell copies of the Software, and to
10
+ ** permit persons to whom the Software is furnished to do so, subject to
11
+ ** the following conditions:
12
+ **
13
+ ** The above copyright notice and this permission notice shall be included
14
+ ** in all copies or substantial portions of the Software.
15
+ **
16
+ ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ */
24
+
25
+ import * as Vite from "vite"
26
+ import { tscPlugin } from "@wroud/vite-plugin-tsc"
27
+ import { viteSingleFile } from "vite-plugin-singlefile"
28
+ import { nodePolyfills } from "vite-plugin-node-polyfills"
29
+
30
+ const formats = process.env.VITE_BUILD_FORMATS ?? "esm"
31
+
32
+ export default Vite.defineConfig(({ command, mode }) => ({
33
+ logLevel: "info",
34
+ appType: "custom",
35
+ base: "",
36
+ root: "",
37
+ plugins: [
38
+ tscPlugin({
39
+ tscArgs: [ "--project", "etc/tsc.json" ],
40
+ packageManager: "npx",
41
+ prebuild: true
42
+ }),
43
+ ...(formats === "umd" ? [ nodePolyfills() ] : []),
44
+ viteSingleFile()
45
+ ],
46
+ build: {
47
+ rollupOptions: {
48
+ external: formats === "umd" ? [] : [ "stream" ]
49
+ },
50
+ lib: {
51
+ entry: "dst-stage1/mqtt-plus.js",
52
+ formats: formats.split(","),
53
+ name: "MQTTp",
54
+ fileName: (format) => `mqtt-plus.${format === "es" ? "esm" : format}.js`
55
+ },
56
+ target: formats === "umd" ? "es2022" : "node20",
57
+ outDir: "dst-stage2",
58
+ assetsDir: "",
59
+ emptyOutDir: (mode === "production") && formats !== "umd",
60
+ chunkSizeWarningLimit: 5000,
61
+ assetsInlineLimit: 0,
62
+ sourcemap: (mode === "development"),
63
+ minify: (mode === "production") && formats === "umd",
64
+ reportCompressedSize: (mode === "production")
65
+ }
66
+ }))
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "mqtt-plus",
3
+ "version": "0.9.0",
4
+ "description": "MQTT Communication Patterns",
5
+ "keywords": [ "mqtt", "event", "service", "rpc", "request", "response" ],
6
+ "license": "MIT",
7
+ "repository": { "type": "git", "url": "git+https://github.com/rse/mqtt-plus.git" },
8
+ "homepage": "https://github.com/rse/mqtt-plus",
9
+ "bugs": "https://github.com/rse/mqtt-plus/issues",
10
+ "author": {
11
+ "name": "Dr. Ralf S. Engelschall",
12
+ "email": "rse@engelschall.com",
13
+ "url": "http://engelschall.com"
14
+ },
15
+ "type": "module",
16
+ "types": "./dst-stage1/mqtt-plus.d.ts",
17
+ "module": "./dst-stage2/mqtt-plus.esm.js",
18
+ "main": "./dst-stage2/mqtt-plus.cjs.js",
19
+ "browser": "./dst-stage2/mqtt-plus.umd.js",
20
+ "exports": {
21
+ ".": {
22
+ "import": { "types": "./dst-stage1/mqtt-plus.d.ts", "default": "./dst-stage2/mqtt-plus.esm.js" },
23
+ "require": { "types": "./dst-stage1/mqtt-plus.d.ts", "default": "./dst-stage2/mqtt-plus.cjs.js" }
24
+ }
25
+ },
26
+ "devDependencies": {
27
+ "@rse/stx": "1.1.4",
28
+ "shx": "0.4.0",
29
+ "eslint": "9.39.2",
30
+ "eslint-plugin-node": "11.1.0",
31
+ "globals": "17.0.0",
32
+ "@eslint/js": "9.39.2",
33
+ "@eslint/eslintrc": "3.3.3",
34
+ "jiti": "2.6.1",
35
+ "tsx": "4.21.0",
36
+ "typescript": "5.9.3",
37
+ "typescript-eslint": "8.51.0",
38
+ "@typescript-eslint/parser": "8.51.0",
39
+ "mqtt": "5.14.1",
40
+ "vite": "7.3.0",
41
+ "vite-plugin-singlefile": "2.3.0",
42
+ "vite-plugin-node-polyfills": "0.24.0",
43
+ "@wroud/vite-plugin-tsc": "0.12.2"
44
+ },
45
+ "peerDependencies": {
46
+ "mqtt": ">=4.0.0"
47
+ },
48
+ "dependencies": {
49
+ "nanoid": "5.1.6",
50
+ "cbor": "10.0.11"
51
+ },
52
+ "engines": {
53
+ "node": ">=16.0.0"
54
+ },
55
+ "scripts": {
56
+ "prepublishOnly": "npm start build",
57
+ "start": "stx -v4 -c etc/stx.conf",
58
+ "test": "npm start test"
59
+ }
60
+ }
@@ -0,0 +1,59 @@
1
+ /*
2
+ ** MQTT+ -- MQTT Communication Patterns
3
+ ** Copyright (c) 2018-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ **
5
+ ** Permission is hereby granted, free of charge, to any person obtaining
6
+ ** a copy of this software and associated documentation files (the
7
+ ** "Software"), to deal in the Software without restriction, including
8
+ ** without limitation the rights to use, copy, modify, merge, publish,
9
+ ** distribute, sublicense, and/or sell copies of the Software, and to
10
+ ** permit persons to whom the Software is furnished to do so, subject to
11
+ ** the following conditions:
12
+ **
13
+ ** The above copyright notice and this permission notice shall be included
14
+ ** in all copies or substantial portions of the Software.
15
+ **
16
+ ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ */
24
+
25
+ import CBOR from "cbor"
26
+
27
+ /* the encoder/decoder abstraction */
28
+ export default class Codec {
29
+ constructor (private type: "cbor" | "json") {}
30
+ encode (data: unknown): Buffer | string {
31
+ let result: Buffer | string
32
+ if (this.type === "cbor") {
33
+ try { result = CBOR.encode(data) }
34
+ catch (_ex) { throw new Error("failed to encode CBOR format") }
35
+ }
36
+ else if (this.type === "json") {
37
+ try { result = JSON.stringify(data) }
38
+ catch (_ex) { throw new Error("failed to encode JSON format") }
39
+ }
40
+ else
41
+ throw new Error("invalid format")
42
+ return result
43
+ }
44
+ decode (data: Buffer | string): unknown {
45
+ let result: unknown
46
+ if (this.type === "cbor" && typeof data === "object" && data instanceof Buffer) {
47
+ try { result = CBOR.decode(data) }
48
+ catch (_ex) { throw new Error("failed to decode CBOR format") }
49
+ }
50
+ else if (this.type === "json" && typeof data === "string") {
51
+ try { result = JSON.parse(data) }
52
+ catch (_ex) { throw new Error("failed to decode JSON format") }
53
+ }
54
+ else
55
+ throw new Error("invalid format or wrong data type")
56
+ return result
57
+ }
58
+ }
59
+
@@ -0,0 +1,171 @@
1
+ /*
2
+ ** MQTT+ -- MQTT Communication Patterns
3
+ ** Copyright (c) 2018-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ **
5
+ ** Permission is hereby granted, free of charge, to any person obtaining
6
+ ** a copy of this software and associated documentation files (the
7
+ ** "Software"), to deal in the Software without restriction, including
8
+ ** without limitation the rights to use, copy, modify, merge, publish,
9
+ ** distribute, sublicense, and/or sell copies of the Software, and to
10
+ ** permit persons to whom the Software is furnished to do so, subject to
11
+ ** the following conditions:
12
+ **
13
+ ** The above copyright notice and this permission notice shall be included
14
+ ** in all copies or substantial portions of the Software.
15
+ **
16
+ ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ */
24
+
25
+ /* base class */
26
+ export class Base {
27
+ constructor (
28
+ public id: string,
29
+ public sender?: string,
30
+ public receiver?: string
31
+ ) {}
32
+ }
33
+
34
+ /* event emission */
35
+ export class EventEmission extends Base {
36
+ constructor (
37
+ id: string,
38
+ public event: string,
39
+ public params?: any[],
40
+ sender?: string,
41
+ receiver?: string,
42
+ ) { super(id, sender, receiver) }
43
+ }
44
+
45
+ /* service request */
46
+ export class ServiceRequest extends Base {
47
+ constructor (
48
+ id: string,
49
+ public service: string,
50
+ public params?: any[],
51
+ sender?: string,
52
+ receiver?: string,
53
+ ) { super(id, sender, receiver) }
54
+ }
55
+
56
+ /* service response success */
57
+ export class ServiceResponseSuccess extends Base {
58
+ constructor (
59
+ id: string,
60
+ public result: any,
61
+ sender?: string,
62
+ receiver?: string
63
+ ) { super(id, sender, receiver) }
64
+ }
65
+
66
+ /* service response error */
67
+ export class ServiceResponseError extends Base {
68
+ constructor (
69
+ id: string,
70
+ public error: string,
71
+ sender?: string,
72
+ receiver?: string
73
+ ) { super(id, sender, receiver) }
74
+ }
75
+
76
+ /* utility class */
77
+ export default class Msg {
78
+ /* factory for event emission */
79
+ makeEventEmission (
80
+ id: string,
81
+ event: string,
82
+ params?: any[],
83
+ sender?: string,
84
+ receiver?: string
85
+ ): EventEmission {
86
+ return new EventEmission(id, event, params, sender, receiver)
87
+ }
88
+
89
+ /* factory for service request */
90
+ makeServiceRequest (
91
+ id: string,
92
+ service: string,
93
+ params?: any[],
94
+ sender?: string,
95
+ receiver?: string
96
+ ): ServiceRequest {
97
+ return new ServiceRequest(id, service, params, sender, receiver)
98
+ }
99
+
100
+ /* factory for service response success */
101
+ makeServiceResponseSuccess (
102
+ id: string,
103
+ result: any,
104
+ sender?: string,
105
+ receiver?: string
106
+ ): ServiceResponseSuccess {
107
+ return new ServiceResponseSuccess(id, result, sender, receiver)
108
+ }
109
+
110
+ /* factory for service response error */
111
+ makeServiceResponseError (
112
+ id: string,
113
+ error: string,
114
+ sender?: string,
115
+ receiver?: string
116
+ ): ServiceResponseError {
117
+ return new ServiceResponseError(id, error, sender, receiver)
118
+ }
119
+
120
+ /* parse any object into typed object */
121
+ parse (obj: any): EventEmission | ServiceRequest | ServiceResponseSuccess | ServiceResponseError {
122
+ if (typeof obj !== "object" || obj === null)
123
+ throw new Error("invalid argument: not an object")
124
+
125
+ /* validate common fields */
126
+ if (!("id" in obj) || typeof obj.id !== "string")
127
+ throw new Error("invalid object: missing or invalid \"id\" field")
128
+ if ("sender" in obj && typeof obj.sender !== "string")
129
+ throw new Error("invalid object: invalid \"sender\" field")
130
+ if ("receiver" in obj && typeof obj.sender !== "string")
131
+ throw new Error("invalid object: invalid \"receiver\" field")
132
+
133
+ /* dispatch according to type indication by field */
134
+ const anyFieldsExcept = (obj: object, allowed: string[]) =>
135
+ Object.keys(obj).some((key) => !allowed.includes(key))
136
+ if ("event" in obj) {
137
+ /* detect and parse event emission */
138
+ if (typeof obj.event !== "string")
139
+ throw new Error("invalid EventEmission object: \"event\" field must be a string")
140
+ if (anyFieldsExcept(obj, [ "type", "id", "event", "params", "sender", "receiver" ]))
141
+ throw new Error("invalid EventEmission object: contains unknown fields")
142
+ if (obj.params !== undefined && (typeof obj.params !== "object" || !Array.isArray(obj.params)))
143
+ throw new Error("invalid EventEmission object: \"params\" field must be an array")
144
+ return this.makeEventEmission(obj.id, obj.event, obj.params, obj.sender, obj.receiver)
145
+ }
146
+ else if ("service" in obj) {
147
+ /* detect and parse service request */
148
+ if (typeof obj.service !== "string")
149
+ throw new Error("invalid ServiceRequest object: \"service\" field must be a string")
150
+ if (anyFieldsExcept(obj, [ "type", "id", "service", "params", "sender", "receiver" ]))
151
+ throw new Error("invalid ServiceRequest object: contains unknown fields")
152
+ if (obj.params !== undefined && (typeof obj.params !== "object" || !Array.isArray(obj.params)))
153
+ throw new Error("invalid ServiceRequest object: \"params\" field must be an array")
154
+ return this.makeServiceRequest(obj.id, obj.service, obj.params, obj.sender, obj.receiver)
155
+ }
156
+ else if ("result" in obj) {
157
+ /* detect and parse service response success */
158
+ if (anyFieldsExcept(obj, [ "type", "id", "result", "sender", "receiver" ]))
159
+ throw new Error("invalid ServiceResponseSuccess object: contains unknown fields")
160
+ return this.makeServiceResponseSuccess(obj.id, obj.result, obj.sender, obj.receiver)
161
+ }
162
+ else if ("error" in obj) {
163
+ /* detect and parse service response error */
164
+ if (anyFieldsExcept(obj, [ "type", "id", "error", "sender", "receiver" ]))
165
+ throw new Error("invalid ServiceResponseError object: contains unknown fields")
166
+ return this.makeServiceResponseError(obj.id, obj.error, obj.sender, obj.receiver)
167
+ }
168
+ else
169
+ throw new Error("invalid object: not of any known type")
170
+ }
171
+ }