u8-mqtt 0.4.0 → 0.5.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.
Files changed (82) hide show
  1. package/README.md +3 -3
  2. package/cjs/basic-v4.cjs +107 -146
  3. package/cjs/basic-v4.cjs.map +1 -1
  4. package/cjs/basic-v5.cjs +106 -145
  5. package/cjs/basic-v5.cjs.map +1 -1
  6. package/cjs/index.cjs +163 -206
  7. package/cjs/index.cjs.map +1 -1
  8. package/cjs/v4.cjs +164 -207
  9. package/cjs/v4.cjs.map +1 -1
  10. package/cjs/v5.cjs +163 -206
  11. package/cjs/v5.cjs.map +1 -1
  12. package/code/_dispatch.jsy +16 -0
  13. package/code/base.jsy +72 -133
  14. package/code/core.jsy +15 -9
  15. package/code/router_path.jsy +38 -51
  16. package/code/with_topic_router.jsy +20 -11
  17. package/esm/basic-v4.js +1154 -0
  18. package/esm/basic-v4.js.map +1 -0
  19. package/esm/basic-v5.js +1416 -0
  20. package/esm/basic-v5.js.map +1 -0
  21. package/esm/deno/basic-v4.js +107 -146
  22. package/esm/deno/basic-v4.js.map +1 -1
  23. package/esm/deno/basic-v5.js +106 -145
  24. package/esm/deno/basic-v5.js.map +1 -1
  25. package/esm/deno/index.js +163 -206
  26. package/esm/deno/index.js.map +1 -1
  27. package/esm/deno/v4.js +164 -207
  28. package/esm/deno/v4.js.map +1 -1
  29. package/esm/deno/v5.js +163 -206
  30. package/esm/deno/v5.js.map +1 -1
  31. package/esm/index.js +1599 -0
  32. package/esm/index.js.map +1 -0
  33. package/esm/node/basic-v4.js +107 -146
  34. package/esm/node/basic-v4.js.map +1 -1
  35. package/esm/node/basic-v4.mjs +107 -146
  36. package/esm/node/basic-v4.mjs.map +1 -1
  37. package/esm/node/basic-v5.js +106 -145
  38. package/esm/node/basic-v5.js.map +1 -1
  39. package/esm/node/basic-v5.mjs +106 -145
  40. package/esm/node/basic-v5.mjs.map +1 -1
  41. package/esm/node/index.js +163 -206
  42. package/esm/node/index.js.map +1 -1
  43. package/esm/node/index.mjs +163 -206
  44. package/esm/node/index.mjs.map +1 -1
  45. package/esm/node/v4.js +164 -207
  46. package/esm/node/v4.js.map +1 -1
  47. package/esm/node/v4.mjs +164 -207
  48. package/esm/node/v4.mjs.map +1 -1
  49. package/esm/node/v5.js +163 -206
  50. package/esm/node/v5.js.map +1 -1
  51. package/esm/node/v5.mjs +163 -206
  52. package/esm/node/v5.mjs.map +1 -1
  53. package/esm/v4.js +1336 -0
  54. package/esm/v4.js.map +1 -0
  55. package/esm/v5.js +1599 -0
  56. package/esm/v5.js.map +1 -0
  57. package/esm/web/basic-v4.js +107 -146
  58. package/esm/web/basic-v4.js.map +1 -1
  59. package/esm/web/basic-v4.min.js +1 -1
  60. package/esm/web/basic-v4.min.js.br +0 -0
  61. package/esm/web/basic-v4.min.js.gz +0 -0
  62. package/esm/web/basic-v5.js +106 -145
  63. package/esm/web/basic-v5.js.map +1 -1
  64. package/esm/web/basic-v5.min.js +1 -1
  65. package/esm/web/basic-v5.min.js.br +0 -0
  66. package/esm/web/basic-v5.min.js.gz +0 -0
  67. package/esm/web/index.js +163 -206
  68. package/esm/web/index.js.map +1 -1
  69. package/esm/web/index.min.js +1 -1
  70. package/esm/web/index.min.js.br +0 -0
  71. package/esm/web/index.min.js.gz +0 -0
  72. package/esm/web/v4.js +164 -207
  73. package/esm/web/v4.js.map +1 -1
  74. package/esm/web/v4.min.js +1 -1
  75. package/esm/web/v4.min.js.br +0 -0
  76. package/esm/web/v4.min.js.gz +0 -0
  77. package/esm/web/v5.js +163 -206
  78. package/esm/web/v5.js.map +1 -1
  79. package/esm/web/v5.min.js +1 -1
  80. package/esm/web/v5.min.js.br +0 -0
  81. package/esm/web/v5.min.js.gz +0 -0
  82. package/package.json +7 -8
package/code/base.jsy CHANGED
@@ -9,28 +9,34 @@ export class MQTTError extends Error ::
9
9
 
10
10
  export class MQTTBase ::
11
11
  constructor(opt={}) ::
12
+ this.with(opt)
12
13
  this._conn_ = _mqtt_conn @ this,
13
14
  this._init_dispatch(opt, this)
14
15
 
16
+ with(fns_ns) ::
17
+ for let [k,v] of Object.entries(fns_ns) ::
18
+ if 'function' === typeof v :: this[k] = v
19
+ return this
20
+
15
21
  async conn_emit(evt, arg, err_arg) ::
16
22
  this.log_conn?.(evt, arg, err_arg)
17
23
  try ::
18
- let fn_evt = this[await evt] // microtask break
24
+ let fn_evt = this[await evt] // microtask break using `await evt`
19
25
  if fn_evt ::
20
26
  await fn_evt.call(this, this, arg, err_arg)
21
- else if err_arg ::
22
- await this.on_error(err_arg, evt)
27
+ else if err_arg :: throw err_arg
23
28
  catch err ::
24
29
  this.on_error(err, evt)
25
30
 
26
- on_error(err, err_path) ::
27
- console.warn @ '[[u8-mqtt error: %s]]', err_path, err
31
+ on_error(err, evt) ::
32
+ console.warn @ '[[u8-mqtt error: %s]]', evt, err
28
33
 
29
34
  // Handshaking Packets
30
35
 
31
36
  async connect(pkt={}) ::
32
- let cid = pkt.client_id || ['u8-mqtt--', '']
33
- if Array.isArray(cid) ::
37
+ let cid = pkt.client_id || this.client_id
38
+ if 'string' !== typeof cid ::
39
+ // see init_client_id implementation in core.jsy
34
40
  pkt.client_id = cid = this.init_client_id(cid)
35
41
  this.client_id = cid
36
42
 
@@ -56,12 +62,13 @@ export class MQTTBase ::
56
62
  return this._send('auth', pkt, 'auth')
57
63
 
58
64
  ping() :: return this._send('pingreq', null, 'pingresp')
59
-
65
+ puback({pkt_id}) :: return this._send('puback', {pkt_id})
60
66
 
61
67
  // alias: sub
62
68
  subscribe(pkt, ex, topic_prefix) ::
63
69
  pkt = _as_topics(pkt, ex, topic_prefix)
64
- return this._send('subscribe', pkt, pkt)
70
+ let suback = this._send('subscribe', pkt, pkt)
71
+ return this.on_sub?.(suback, pkt) ?? suback
65
72
 
66
73
  // alias: unsub
67
74
  unsubscribe(pkt, ex, topic_prefix) ::
@@ -69,48 +76,52 @@ export class MQTTBase ::
69
76
  return this._send('unsubscribe', pkt, pkt)
70
77
 
71
78
 
72
- // alias: pub
73
- publish(pkt, pub_opt) :: return _pub(this, pkt, pub_opt)
74
- post(topic, payload, pub_opt) :: return _pub.m(this, topic, payload, pub_opt)
75
- send(topic, payload, pub_opt) :: return _pub.mq(this, topic, payload, pub_opt)
76
- store(topic, payload, pub_opt) :: return _pub.mqr(this, topic, payload, pub_opt)
77
-
78
- json_post(topic, msg, pub_opt) :: return _pub.o(this, topic, msg, pub_opt)
79
- json_send(topic, msg, pub_opt) :: return _pub.oq(this, topic, msg, pub_opt)
80
- json_store(topic, msg, pub_opt) :: return _pub.oqr(this, topic, msg, pub_opt)
81
-
82
- obj_post(topic, msg, pub_opt) :: return _pub.o(this, topic, msg, pub_opt)
83
- obj_send(topic, msg, pub_opt) :: return _pub.oq(this, topic, msg, pub_opt)
84
- obj_store(topic, msg, pub_opt) :: return _pub.oqr(this, topic, msg, pub_opt)
85
-
86
-
87
-
88
- // Utility Methods
89
-
90
- init_client_id(parts) ::
91
- let cid = this.client_id
92
-
93
- if undefined === cid ::
94
- this.client_id = cid = (
95
- #IF PLAT_WEB
96
- this.sess_client_id(parts)
97
- #ELSE
98
- this.new_client_id(parts)
99
- )
100
-
101
- return cid
102
-
103
- new_client_id(parts) ::
104
- return [parts[0], Math.random().toString(36).slice(2), parts[1]].join('')
105
-
106
- #IF PLAT_WEB
107
- sess_client_id(parts) ::
108
- let key = parts.join('\x20')
109
- let cid = sessionStorage.getItem(key)
110
- if null == cid ::
111
- cid = this.new_client_id(parts)
112
- sessionStorage.setItem(key, cid)
113
- return cid
79
+ post(topic, payload, pub_opt) :: // qos:0
80
+ return this.pub({topic, payload, qos:0}, pub_opt)
81
+ send(topic, payload, pub_opt) :: // qos:1
82
+ return this.pub({topic, payload, qos:1}, pub_opt)
83
+ store(topic, payload, pub_opt) :: // qos:1, retain: 1
84
+ return this.pub({topic, payload, qos:1, retain: 1}, pub_opt)
85
+
86
+ // alias: json_post
87
+ obj_post(topic, msg, pub_opt) :: // qos:0
88
+ return this.pub({topic, msg, arg: 'msg', qos:0}, pub_opt)
89
+ // alias: json_send
90
+ obj_send(topic, msg, pub_opt) :: // qos:1
91
+ return this.pub({topic, msg, arg: 'msg', qos:1}, pub_opt)
92
+ // alias: json_store
93
+ obj_store(topic, msg, pub_opt) :: // qos:1, retain: 1
94
+ return this.pub({topic, msg, arg: 'msg', qos:1, retain: 1}, pub_opt)
95
+
96
+ // alias: publish -- because 'pub' is shorter for semantic aliases above
97
+ async pub(pkt, pub_opt) ::
98
+ if undefined === pkt.payload ::
99
+ if 'function' === typeof pub_opt ::
100
+ pub_opt = {fn_encode: pub_opt}
101
+
102
+ let {msg} = pkt
103
+ switch typeof msg ::
104
+ case 'function':
105
+ pub_opt = {...pub_opt, fn_encode: msg}
106
+ // flow into 'undefined' case
107
+ case 'undefined':
108
+ // return a single-value closure to publish packets
109
+ return v => this.pub({...pkt, [pkt.arg || 'payload']: v}, pub_opt)
110
+
111
+ // Encode payload from msg; fn_encode allows alternative to JSON.stringify
112
+ let {fn_encode} = pub_opt || {}
113
+ pkt.payload = fn_encode
114
+ ? await fn_encode(msg)
115
+ : JSON.stringify(msg)
116
+
117
+ if pub_opt ::
118
+ if pub_opt.props ::
119
+ pkt.props = pub_opt.props
120
+ if pub_opt.xform ::
121
+ pkt = pub_opt.xform(pkt) || pkt
122
+
123
+ return this._send @ 'publish', pkt,
124
+ pkt.qos ? pkt : void 0 // key
114
125
 
115
126
 
116
127
  // Internal API
@@ -119,52 +130,24 @@ export class MQTTBase ::
119
130
 
120
131
  _init_dispatch(opt) ::
121
132
  this.constructor?._once_()
122
- let router = this.router =
123
- this._init_router?.(opt, this)
124
-
125
- let tgt = @{}
126
- __proto__: opt.on_mqtt_type || {}
127
- router
128
-
129
- tgt.mqtt_publish ||= router?.invoke
130
- return _mqtt_dispatch(this, tgt)
133
+ let target = @{} __proto__: opt.on_mqtt_type
134
+ target.mqtt_publish ||=
135
+ this._init_router?.(opt, this, target)
136
+ return _mqtt_dispatch(this, target)
131
137
 
132
138
  static _aliases() ::
133
- return ' pub:publish sub:subscribe unsub:unsubscribe '
139
+ return ' publish:pub sub:subscribe unsub:unsubscribe json_post:obj_post json_send:obj_send json_store:obj_store'
134
140
 
135
141
  static _once_(self=this) ::
136
142
  self._once_ = _=>0
137
- self.MQTTError = MQTTError
138
143
  let p = self.prototype
144
+ p.MQTTError = MQTTError
139
145
  for let alias of self._aliases().split(/\s+/) ::
140
146
  alias = alias.split(':')
141
147
  let fn = alias[1] && p[alias[1]]
142
148
  if fn :: p[alias[0]] = fn
143
149
 
144
150
 
145
- /*
146
- on_mqtt_type = {
147
- mqtt_auth(pkt, ctx) ::
148
- mqtt_connect(pkt, ctx) ::
149
- mqtt_connack(pkt, ctx) ::
150
- mqtt_disconnect(pkt, ctx) ::
151
-
152
- mqtt_publish(pkt, ctx)
153
- mqtt_subscribe(pkt, ctx) ::
154
- mqtt_unsubscribe(pkt, ctx) ::
155
-
156
- mqtt_pingreq(pkt, ctx) ::
157
- mqtt_pingresp(pkt, ctx) ::
158
- }
159
- */
160
-
161
-
162
- const _prefix_topics = (topic_prefix, iterable) =>
163
- Array.from @ iterable, value => @
164
- value.trim // string
165
- ? _prefix_topics(topic_prefix, value)
166
- : topic_prefix + value
167
-
168
151
  function _as_topics(pkt, ex, topic_prefix) ::
169
152
  if ex?.trim :: // string
170
153
  topic_prefix = ex
@@ -181,53 +164,9 @@ function _as_topics(pkt, ex, topic_prefix) ::
181
164
  if topic_prefix ::
182
165
  // particularly useful with shared queues, e.g.
183
166
  // topic_prefix = '$share/some-queue-name/'
184
- pkt.topics = _prefix_topics(topic_prefix, pkt.topics)
185
- return pkt
167
+ let _prefix_topics = v =>
168
+ v.trim ? topic_prefix+v : v.map(_prefix_topics)
186
169
 
187
-
188
- async function _pub(self, pkt, pub_opt) ::
189
- if undefined === pkt.payload ::
190
- if 'function' === typeof pub_opt ::
191
- pub_opt = {fn_encode: pub_opt}
192
-
193
- let {msg} = pkt
194
- switch typeof msg ::
195
- case 'function':
196
- pub_opt = {...pub_opt, fn_encode: msg}
197
- // flow into 'undefined' case
198
- case 'undefined':
199
- // return a single-value closure to publish packets
200
- return v => _pub(self, {...pkt, [pkt.arg || 'payload']: v}, pub_opt)
201
-
202
- default:
203
- // Encode payload from msg; fn_encode allows alternative to JSON.stringify
204
- let {fn_encode} = pub_opt || {}
205
- pkt.payload = fn_encode
206
- ? await fn_encode(msg)
207
- : JSON.stringify(msg)
208
-
209
- if pub_opt ::
210
- if pub_opt.props ::
211
- pkt.props = pub_opt.props
212
- if pub_opt.xform ::
213
- pkt = pub_opt.xform(pkt) || pkt
214
-
215
- return self._send @ 'publish', pkt,
216
- pkt.qos ? pkt : void 0 // key
217
-
218
- ::
219
- Object.assign @ _pub, @{}
220
- m: (self, topic, payload, pub_opt) =>
221
- _pub(self, {topic, payload, qos:0}, pub_opt)
222
- mq: (self, topic, payload, pub_opt) =>
223
- _pub(self, {topic, payload, qos:1}, pub_opt)
224
- mqr: (self, topic, payload, pub_opt) =>
225
- _pub(self, {topic, payload, qos:1, retain: 1}, pub_opt)
226
-
227
- o: (self, topic, msg, pub_opt) =>
228
- _pub(self, {topic, msg, arg: 'msg', qos:0}, pub_opt)
229
- oq: (self, topic, msg, pub_opt) =>
230
- _pub(self, {topic, msg, arg: 'msg', qos:1}, pub_opt)
231
- oqr: (self, topic, msg, pub_opt) =>
232
- _pub(self, {topic, msg, arg: 'msg', qos:1, retain: 1}, pub_opt)
170
+ pkt.topics = pkt.topics.map(_prefix_topics)
171
+ return pkt
233
172
 
package/code/core.jsy CHANGED
@@ -12,20 +12,27 @@ const pkt_api = {
12
12
  }
13
13
 
14
14
  export class MQTTCore extends MQTTBase ::
15
- constructor(opt={}) ::
16
- super(opt)
17
- this.with(opt)
18
-
19
15
  static mqtt_ctx(mqtt_level, mqtt_opts, pkt_ctx=pkt_api) ::
20
16
  let self = class extends this {}
21
17
  self.prototype.mqtt_ctx =
22
18
  mqtt_pkt_ctx(mqtt_level, mqtt_opts, pkt_ctx)
23
19
  return self
24
20
 
25
- with(fns_ns) ::
26
- for let [k,v] of Object.entries(fns_ns) ::
27
- if 'function' === typeof v :: this[k] = v
28
- return this
21
+
22
+ // automatic Client Id for connect()
23
+ init_client_id(parts=['u8-mqtt--','']) ::
24
+ let sess_stg=this.sess_stg
25
+ let key, cid = sess_stg?.getItem(key=parts.join(' '))
26
+ if ! cid ::
27
+ cid = parts.join @ Math.random().toString(36).slice(2)
28
+ sess_stg?.setItem(key, cid)
29
+ return cid
30
+
31
+ get sess_stg() :: return globalThis.sessionStorage
32
+
33
+
34
+ //on_error(err, evt) ::
35
+ // console.warn @ '[[u8-mqtt error: %s]]', evt, err
29
36
 
30
37
  //log_conn(evt, arg, err_arg) ::
31
38
  // console.info @ '[[u8-mqtt log: %s]]', evt, arg, err_arg
@@ -102,7 +109,6 @@ export class MQTTCore extends MQTTBase ::
102
109
  #IF PLAT_NODEJS
103
110
  with_tcp(...opt) ::
104
111
  opt = this._conn_opt(opt)
105
- console.log @: opt
106
112
  return this._use_conn @=>
107
113
  this.with_stream @
108
114
  tcp_connect(opt)
@@ -10,17 +10,23 @@ export const with_topic_path_router = /* #__PURE__ */
10
10
 
11
11
  const mqtt_topic = topic_route =>
12
12
  topic_route
13
- .replace(/[*].*$/, '#')
14
- .replace(/:\w+\??/g, '+')
13
+ .replace @ /[*].*$/, '#'
14
+ .replace @ /:\w[^\/]*/g, '+'
15
15
 
16
- export const as_topic_path = topic_route => @
16
+ /* From the [MQTT v5 Spec](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Topic_Names_and)
17
+ 4.7.1.2 Multi-level wildcard -- (‘#’ U+0023)
18
+ ... MUST be specified either on its own or following a topic level separator.
19
+ In either case it MUST be the last character specified in the Topic Filter
20
+
21
+ 4.7.1.3 Single-level wildcard -- (‘+’ U+002B)
22
+ ...it MUST occupy an entire level of the filter.
23
+ */
24
+
25
+ export const as_topic_path = (topic_route, id) => @
26
+ id=1,
17
27
  topic_route
18
- .replace(/#$/, '*') // replace MQTT # wildcard at end
19
- .split(/([^\/]*[+][^\/]*)/) // split on MQTT + match tokens
20
- .reduce @\ sz, v, idx => sz + @
21
- idx & 1 // even entires are body, odd are MQTT + tokens
22
- ? `:$${1 + idx>>1}` // replace with `:$#` sequential ids, using ? for partial entries
23
- : v // pass through body
28
+ .replace @ /#$/, '*' // replace MQTT '#' multi-level wildcard at end
29
+ .replace @ /\+/g, () => `:$${id++}` // replace MQTT '+' single-level wildcards
24
30
 
25
31
  function _ignore(pkt, params, ctx) :: ctx.done = true
26
32
 
@@ -35,9 +41,8 @@ function mqtt_topic_path_router() ::
35
41
  let priority = args.pop()
36
42
 
37
43
  if 'function' !== typeof fn ::
38
- if false === fn ::
39
- fn = _ignore
40
- else throw new TypeError()
44
+ if fn :: throw new TypeError()
45
+ fn = _ignore
41
46
 
42
47
  let rte = _rxp_parse @ as_topic_path @ topic_route
43
48
 
@@ -53,7 +58,7 @@ function mqtt_topic_path_router() ::
53
58
  clear(priority) ::
54
59
  pri_lsts[priority ? 0 : 1] = []
55
60
  if null == priority ::
56
- pri_lsts[1] = []
61
+ pri_lsts[1] = [] // null clears both lists
57
62
 
58
63
  async invoke(pkt, ctx) ::
59
64
  ctx.idx = 0
@@ -69,51 +74,33 @@ function mqtt_topic_path_router() ::
69
74
  break
70
75
  else ctx.idx++
71
76
 
72
- let {pkt_id, qos} = pkt
73
- if 1 === qos ::
74
- await ctx.mqtt._send('puback', {pkt_id})
77
+ if 1 === pkt.qos ::
78
+ await ctx.mqtt.puback(pkt)
75
79
 
76
80
 
77
81
  function * _routes_iter(all_route_lists, topic) ::
82
+ topic = topic.replace(/^[\/]*/, '/') // ensure '/' prefix for regexparam library
78
83
  for let route_list of all_route_lists ::
79
- for let route of route_list ::
80
- let res = _route_match_one(topic, route)
81
- if undefined !== res ::
82
- yield res
83
-
84
-
85
- function _route_match_one(topic, {keys, pattern, tgt}) ::
86
- let match = '/' !== topic[0]
87
- ? pattern.exec('/'+topic)
88
- : pattern.exec(topic)
89
-
90
- if null === match ::
91
- return
92
-
93
- if false === keys ::
94
- let {groups} = match
95
- if ! groups ::
96
- return [tgt]
97
-
98
- let params = {}
99
- for let k in groups ::
100
- params[k] = groups[k]
101
-
102
- return [tgt, params]
103
-
104
- if 0 === keys.length ::
105
- return [tgt]
106
-
107
- let params = {}
108
- for let i=0; i<keys.length; i++ ::
109
- params[ keys[i] ] = match[1+i]
110
- return [tgt, params]
84
+ for let {keys, pattern, tgt} of route_list ::
85
+ let match = pattern.exec(topic)
86
+ if match ::
87
+ let params = keys
88
+ ? keys.reduce @
89
+ (o, k, i) => (o[k] = match[1+i], o)
90
+ {}
91
+ : match.groups ?? match
92
+ yield [tgt, params]
111
93
 
112
94
 
113
95
  function _route_remove(all_route_lists, query) ::
114
- let match = route => route===query || route.tgt===query || route.key===query
96
+ let fn_match = route => @
97
+ route===query
98
+ || route.tgt===query
99
+ || route.key===query
115
100
  for let lst of all_route_lists ::
116
- let i = lst.findIndex(match)
117
- if 0 <= i :: return !! lst.splice(i,1)
101
+ let i = lst.findIndex(fn_match)
102
+ if 0 <= i ::
103
+ lst.splice(i,1)
104
+ return true
118
105
  return false
119
106
 
@@ -14,28 +14,37 @@ export const with_topic_router = mqtt_topic_router =>
14
14
  return super._aliases() +
15
15
  ' sub_topic:subscribe_topic unsub_topic:unsubscribe_topic'
16
16
 
17
- _init_router(opt) ::
18
- return mqtt_topic_router(opt, this)
17
+ _init_router(opt, self, target) ::
18
+ this._subs = []
19
+ let router = this.router = target.router =
20
+ mqtt_topic_router(opt, this, target)
21
+ return router?.invoke
19
22
 
20
- get on_topic() :: return this.router.add
21
-
22
- _sub_chain(topic, ex, topic_prefix) ::
23
- let res = this.subscribe @ [[ topic ]], ex, topic_prefix
24
- let subs = this.subs || @ this.subs = new Map()
25
- subs.set @ (res.topic = topic), (subs.last = res)
26
- return this // fluent api -- return this and track side effects
23
+ on_sub(suback, pkt) ::
24
+ suback.pkt = pkt
25
+ this._subs.push(suback)
26
+ return suback
27
+ subs_settled() ::
28
+ return Promise.allSettled @
29
+ this._subs.splice(0,Infinity)
27
30
 
28
31
  // alias: sub_topic
29
32
  subscribe_topic(topic_route, ...args) ::
30
33
  let router = this.router
31
34
  router.add @ topic_route, true, args.pop() // handler
32
35
  let topic = router.mqtt_topic(topic_route)
33
- return this._sub_chain @ topic, ...args // ex, topic_prefix
36
+ this.subscribe @ topic, ...args // ex, topic_prefix
37
+ return this // fluent api -- return this and track side effects
34
38
 
35
39
  // alias: unsub_topic
36
40
  unsubscribe_topic(topic_route, ...args) ::
37
41
  let router = this.router
38
42
  router.remove @ topic_route, true
39
43
  let topic = router.mqtt_topic(topic_route)
40
- return this.unsubscribe @ [[ topic ]], ...args // topic_prefix
44
+ return this.unsubscribe @ topic, ...args // topic_prefix
45
+
46
+ // add topic handlers without corresponding subscribe packet
47
+ on_topic(...args) ::
48
+ this.router.add(...args)
49
+ return this
41
50