@smarthome.exchange/integrations 1.0.0 → 2.0.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 (294) hide show
  1. package/changelog.md +49 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/core/classes.configstore.d.ts +2 -0
  4. package/dist_ts/core/classes.configstore.js +84 -27
  5. package/dist_ts/core/classes.nativehomeadmission.d.ts +19 -0
  6. package/dist_ts/core/classes.nativehomeadmission.js +84 -0
  7. package/dist_ts/core/classes.nativeintegrationowner.d.ts +79 -0
  8. package/dist_ts/core/classes.nativeintegrationowner.js +269 -0
  9. package/dist_ts/core/index.d.ts +2 -0
  10. package/dist_ts/core/index.js +3 -1
  11. package/dist_ts/core/types.d.ts +14 -0
  12. package/dist_ts/integrations/braviatv/braviatv.classes.client.d.ts +23 -72
  13. package/dist_ts/integrations/braviatv/braviatv.classes.client.js +265 -625
  14. package/dist_ts/integrations/braviatv/braviatv.classes.configflow.d.ts +7 -7
  15. package/dist_ts/integrations/braviatv/braviatv.classes.configflow.js +51 -68
  16. package/dist_ts/integrations/braviatv/braviatv.classes.integration.d.ts +8 -11
  17. package/dist_ts/integrations/braviatv/braviatv.classes.integration.js +186 -135
  18. package/dist_ts/integrations/braviatv/braviatv.discovery.js +12 -11
  19. package/dist_ts/integrations/braviatv/braviatv.mapper.d.ts +6 -14
  20. package/dist_ts/integrations/braviatv/braviatv.mapper.js +37 -131
  21. package/dist_ts/integrations/braviatv/braviatv.types.d.ts +55 -133
  22. package/dist_ts/integrations/braviatv/braviatv.validation.d.ts +7 -0
  23. package/dist_ts/integrations/braviatv/braviatv.validation.js +72 -0
  24. package/dist_ts/integrations/broadlink/broadlink.actions.d.ts +2 -0
  25. package/dist_ts/integrations/broadlink/broadlink.actions.js +36 -0
  26. package/dist_ts/integrations/broadlink/broadlink.classes.client.d.ts +21 -19
  27. package/dist_ts/integrations/broadlink/broadlink.classes.client.js +185 -124
  28. package/dist_ts/integrations/broadlink/broadlink.classes.configflow.d.ts +5 -4
  29. package/dist_ts/integrations/broadlink/broadlink.classes.configflow.js +64 -83
  30. package/dist_ts/integrations/broadlink/broadlink.classes.integration.d.ts +16 -19
  31. package/dist_ts/integrations/broadlink/broadlink.classes.integration.js +114 -59
  32. package/dist_ts/integrations/broadlink/broadlink.mapper.d.ts +3 -2
  33. package/dist_ts/integrations/broadlink/broadlink.mapper.js +2 -1
  34. package/dist_ts/integrations/broadlink/broadlink.selected.mapper.d.ts +10 -0
  35. package/dist_ts/integrations/broadlink/broadlink.selected.mapper.js +41 -0
  36. package/dist_ts/integrations/broadlink/broadlink.types.d.ts +43 -7
  37. package/dist_ts/integrations/broadlink/broadlink.validation.d.ts +7 -0
  38. package/dist_ts/integrations/broadlink/broadlink.validation.js +63 -0
  39. package/dist_ts/integrations/daikin/daikin.classes.client.d.ts +10 -52
  40. package/dist_ts/integrations/daikin/daikin.classes.client.js +55 -614
  41. package/dist_ts/integrations/daikin/daikin.classes.configflow.d.ts +8 -12
  42. package/dist_ts/integrations/daikin/daikin.classes.configflow.js +77 -138
  43. package/dist_ts/integrations/daikin/daikin.classes.integration.d.ts +7 -24
  44. package/dist_ts/integrations/daikin/daikin.classes.integration.js +175 -67
  45. package/dist_ts/integrations/daikin/daikin.discovery.d.ts +11 -24
  46. package/dist_ts/integrations/daikin/daikin.discovery.js +98 -305
  47. package/dist_ts/integrations/daikin/daikin.mapper.d.ts +6 -7
  48. package/dist_ts/integrations/daikin/daikin.mapper.js +95 -155
  49. package/dist_ts/integrations/daikin/daikin.types.d.ts +24 -44
  50. package/dist_ts/integrations/daikin/daikin.types.js +1 -1
  51. package/dist_ts/integrations/daikin/daikin.validation.d.ts +5 -0
  52. package/dist_ts/integrations/daikin/daikin.validation.js +53 -0
  53. package/dist_ts/integrations/denonavr/denonavr.classes.client.d.ts +31 -40
  54. package/dist_ts/integrations/denonavr/denonavr.classes.client.js +149 -404
  55. package/dist_ts/integrations/denonavr/denonavr.classes.configflow.d.ts +7 -2
  56. package/dist_ts/integrations/denonavr/denonavr.classes.configflow.js +44 -36
  57. package/dist_ts/integrations/denonavr/denonavr.classes.integration.d.ts +6 -11
  58. package/dist_ts/integrations/denonavr/denonavr.classes.integration.js +192 -113
  59. package/dist_ts/integrations/denonavr/denonavr.discovery.d.ts +10 -18
  60. package/dist_ts/integrations/denonavr/denonavr.discovery.js +71 -193
  61. package/dist_ts/integrations/denonavr/denonavr.mapper.d.ts +5 -13
  62. package/dist_ts/integrations/denonavr/denonavr.mapper.js +35 -159
  63. package/dist_ts/integrations/denonavr/denonavr.types.d.ts +55 -90
  64. package/dist_ts/integrations/denonavr/denonavr.validation.d.ts +10 -0
  65. package/dist_ts/integrations/denonavr/denonavr.validation.js +86 -0
  66. package/dist_ts/integrations/kodi/kodi.classes.client.d.ts +11 -45
  67. package/dist_ts/integrations/kodi/kodi.classes.client.js +84 -310
  68. package/dist_ts/integrations/kodi/kodi.classes.configflow.d.ts +9 -8
  69. package/dist_ts/integrations/kodi/kodi.classes.configflow.js +53 -48
  70. package/dist_ts/integrations/kodi/kodi.classes.integration.d.ts +11 -11
  71. package/dist_ts/integrations/kodi/kodi.classes.integration.js +114 -171
  72. package/dist_ts/integrations/kodi/kodi.discovery.js +53 -29
  73. package/dist_ts/integrations/kodi/kodi.mapper.d.ts +7 -2
  74. package/dist_ts/integrations/kodi/kodi.mapper.js +48 -10
  75. package/dist_ts/integrations/kodi/kodi.types.d.ts +32 -28
  76. package/dist_ts/integrations/kodi/kodi.validation.d.ts +9 -0
  77. package/dist_ts/integrations/kodi/kodi.validation.js +62 -0
  78. package/dist_ts/integrations/modbus/modbus.classes.client.d.ts +15 -61
  79. package/dist_ts/integrations/modbus/modbus.classes.client.js +117 -776
  80. package/dist_ts/integrations/modbus/modbus.classes.configflow.d.ts +9 -5
  81. package/dist_ts/integrations/modbus/modbus.classes.configflow.js +70 -38
  82. package/dist_ts/integrations/modbus/modbus.classes.integration.d.ts +6 -17
  83. package/dist_ts/integrations/modbus/modbus.classes.integration.js +133 -57
  84. package/dist_ts/integrations/modbus/modbus.discovery.d.ts +5 -8
  85. package/dist_ts/integrations/modbus/modbus.discovery.js +38 -93
  86. package/dist_ts/integrations/modbus/modbus.mapper.d.ts +8 -32
  87. package/dist_ts/integrations/modbus/modbus.mapper.js +71 -431
  88. package/dist_ts/integrations/modbus/modbus.types.d.ts +82 -332
  89. package/dist_ts/integrations/modbus/modbus.validation.d.ts +10 -0
  90. package/dist_ts/integrations/modbus/modbus.validation.js +128 -0
  91. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.d.ts +12 -34
  92. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.js +143 -231
  93. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.d.ts +6 -1
  94. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.js +105 -18
  95. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.d.ts +8 -8
  96. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.js +141 -155
  97. package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.d.ts +9 -0
  98. package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.js +35 -0
  99. package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.d.ts +6 -18
  100. package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.js +45 -220
  101. package/dist_ts/integrations/nanoleaf/nanoleaf.types.d.ts +33 -147
  102. package/dist_ts/integrations/nanoleaf/nanoleaf.validation.d.ts +6 -0
  103. package/dist_ts/integrations/nanoleaf/nanoleaf.validation.js +82 -0
  104. package/dist_ts/integrations/onkyo/onkyo.classes.client.d.ts +15 -20
  105. package/dist_ts/integrations/onkyo/onkyo.classes.client.js +161 -404
  106. package/dist_ts/integrations/onkyo/onkyo.classes.configflow.d.ts +9 -4
  107. package/dist_ts/integrations/onkyo/onkyo.classes.configflow.js +51 -6
  108. package/dist_ts/integrations/onkyo/onkyo.classes.integration.d.ts +22 -6
  109. package/dist_ts/integrations/onkyo/onkyo.classes.integration.js +144 -11
  110. package/dist_ts/integrations/onkyo/onkyo.discovery.d.ts +6 -1
  111. package/dist_ts/integrations/onkyo/onkyo.discovery.js +40 -4
  112. package/dist_ts/integrations/onkyo/onkyo.mapper.d.ts +7 -8
  113. package/dist_ts/integrations/onkyo/onkyo.mapper.js +45 -12
  114. package/dist_ts/integrations/onkyo/onkyo.types.d.ts +58 -8
  115. package/dist_ts/integrations/onkyo/onkyo.types.js +13 -102
  116. package/dist_ts/integrations/onkyo/onkyo.validation.d.ts +16 -0
  117. package/dist_ts/integrations/onkyo/onkyo.validation.js +77 -0
  118. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.d.ts +25 -25
  119. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.js +116 -360
  120. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.d.ts +11 -3
  121. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.js +76 -6
  122. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.d.ts +26 -4
  123. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.js +142 -9
  124. package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.d.ts +2 -1
  125. package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.js +31 -4
  126. package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.d.ts +9 -9
  127. package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.js +31 -149
  128. package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.d.ts +51 -11
  129. package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.js +22 -87
  130. package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.d.ts +6 -0
  131. package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.js +56 -0
  132. package/dist_ts/integrations/pioneer/pioneer.classes.client.d.ts +31 -16
  133. package/dist_ts/integrations/pioneer/pioneer.classes.client.js +167 -409
  134. package/dist_ts/integrations/pioneer/pioneer.classes.configflow.d.ts +10 -3
  135. package/dist_ts/integrations/pioneer/pioneer.classes.configflow.js +66 -6
  136. package/dist_ts/integrations/pioneer/pioneer.classes.integration.d.ts +13 -5
  137. package/dist_ts/integrations/pioneer/pioneer.classes.integration.js +239 -11
  138. package/dist_ts/integrations/pioneer/pioneer.discovery.d.ts +14 -1
  139. package/dist_ts/integrations/pioneer/pioneer.discovery.js +51 -4
  140. package/dist_ts/integrations/pioneer/pioneer.mapper.d.ts +14 -8
  141. package/dist_ts/integrations/pioneer/pioneer.mapper.js +54 -12
  142. package/dist_ts/integrations/pioneer/pioneer.types.d.ts +79 -12
  143. package/dist_ts/integrations/pioneer/pioneer.types.js +12 -76
  144. package/dist_ts/integrations/pioneer/pioneer.validation.d.ts +10 -0
  145. package/dist_ts/integrations/pioneer/pioneer.validation.js +85 -0
  146. package/dist_ts/integrations/soundtouch/soundtouch.classes.client.d.ts +13 -36
  147. package/dist_ts/integrations/soundtouch/soundtouch.classes.client.js +179 -502
  148. package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.d.ts +6 -1
  149. package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.js +50 -40
  150. package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.d.ts +14 -15
  151. package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.js +108 -128
  152. package/dist_ts/integrations/soundtouch/soundtouch.discovery.d.ts +2 -20
  153. package/dist_ts/integrations/soundtouch/soundtouch.discovery.js +23 -113
  154. package/dist_ts/integrations/soundtouch/soundtouch.mapper.d.ts +3 -12
  155. package/dist_ts/integrations/soundtouch/soundtouch.mapper.js +35 -189
  156. package/dist_ts/integrations/soundtouch/soundtouch.types.d.ts +57 -142
  157. package/dist_ts/integrations/soundtouch/soundtouch.types.js +18 -3
  158. package/dist_ts/integrations/soundtouch/soundtouch.validation.d.ts +22 -0
  159. package/dist_ts/integrations/soundtouch/soundtouch.validation.js +65 -0
  160. package/dist_ts/integrations/wiz/index.d.ts +1 -0
  161. package/dist_ts/integrations/wiz/index.js +2 -1
  162. package/dist_ts/integrations/wiz/wiz.classes.client.d.ts +14 -28
  163. package/dist_ts/integrations/wiz/wiz.classes.client.js +93 -229
  164. package/dist_ts/integrations/wiz/wiz.classes.configflow.d.ts +6 -4
  165. package/dist_ts/integrations/wiz/wiz.classes.configflow.js +38 -67
  166. package/dist_ts/integrations/wiz/wiz.classes.integration.d.ts +10 -21
  167. package/dist_ts/integrations/wiz/wiz.classes.integration.js +204 -44
  168. package/dist_ts/integrations/wiz/wiz.discovery.d.ts +10 -31
  169. package/dist_ts/integrations/wiz/wiz.discovery.js +48 -249
  170. package/dist_ts/integrations/wiz/wiz.mapper.d.ts +7 -51
  171. package/dist_ts/integrations/wiz/wiz.mapper.js +82 -668
  172. package/dist_ts/integrations/wiz/wiz.types.d.ts +34 -208
  173. package/dist_ts/integrations/wiz/wiz.validation.d.ts +8 -0
  174. package/dist_ts/integrations/wiz/wiz.validation.js +72 -0
  175. package/dist_ts/integrations/yamaha/index.d.ts +1 -0
  176. package/dist_ts/integrations/yamaha/index.js +2 -1
  177. package/dist_ts/integrations/yamaha/yamaha.classes.client.d.ts +30 -4
  178. package/dist_ts/integrations/yamaha/yamaha.classes.client.js +162 -6
  179. package/dist_ts/integrations/yamaha/yamaha.classes.configflow.d.ts +10 -3
  180. package/dist_ts/integrations/yamaha/yamaha.classes.configflow.js +106 -6
  181. package/dist_ts/integrations/yamaha/yamaha.classes.integration.d.ts +22 -4
  182. package/dist_ts/integrations/yamaha/yamaha.classes.integration.js +243 -8
  183. package/dist_ts/integrations/yamaha/yamaha.discovery.d.ts +13 -1
  184. package/dist_ts/integrations/yamaha/yamaha.discovery.js +57 -4
  185. package/dist_ts/integrations/yamaha/yamaha.mapper.d.ts +16 -8
  186. package/dist_ts/integrations/yamaha/yamaha.mapper.js +58 -12
  187. package/dist_ts/integrations/yamaha/yamaha.types.d.ts +73 -6
  188. package/dist_ts/integrations/yamaha/yamaha.types.js +8 -94
  189. package/dist_ts/integrations/yamaha/yamaha.validation.d.ts +12 -0
  190. package/dist_ts/integrations/yamaha/yamaha.validation.js +89 -0
  191. package/package.json +3 -3
  192. package/readme.md +514 -2
  193. package/ts/00_commitinfo_data.ts +1 -1
  194. package/ts/core/classes.configstore.ts +84 -27
  195. package/ts/core/classes.nativehomeadmission.ts +96 -0
  196. package/ts/core/classes.nativeintegrationowner.ts +297 -0
  197. package/ts/core/index.ts +2 -0
  198. package/ts/core/types.ts +16 -0
  199. package/ts/integrations/braviatv/braviatv.classes.client.ts +245 -709
  200. package/ts/integrations/braviatv/braviatv.classes.configflow.ts +47 -76
  201. package/ts/integrations/braviatv/braviatv.classes.integration.ts +167 -147
  202. package/ts/integrations/braviatv/braviatv.discovery.ts +11 -10
  203. package/ts/integrations/braviatv/braviatv.mapper.ts +39 -142
  204. package/ts/integrations/braviatv/braviatv.types.ts +53 -173
  205. package/ts/integrations/braviatv/braviatv.validation.ts +68 -0
  206. package/ts/integrations/broadlink/broadlink.actions.ts +31 -0
  207. package/ts/integrations/broadlink/broadlink.classes.client.ts +170 -144
  208. package/ts/integrations/broadlink/broadlink.classes.configflow.ts +59 -89
  209. package/ts/integrations/broadlink/broadlink.classes.integration.ts +110 -64
  210. package/ts/integrations/broadlink/broadlink.mapper.ts +5 -4
  211. package/ts/integrations/broadlink/broadlink.selected.mapper.ts +46 -0
  212. package/ts/integrations/broadlink/broadlink.types.ts +48 -8
  213. package/ts/integrations/broadlink/broadlink.validation.ts +66 -0
  214. package/ts/integrations/daikin/daikin.classes.client.ts +52 -661
  215. package/ts/integrations/daikin/daikin.classes.configflow.ts +72 -152
  216. package/ts/integrations/daikin/daikin.classes.integration.ts +166 -71
  217. package/ts/integrations/daikin/daikin.discovery.ts +94 -346
  218. package/ts/integrations/daikin/daikin.mapper.ts +83 -166
  219. package/ts/integrations/daikin/daikin.types.ts +27 -48
  220. package/ts/integrations/daikin/daikin.validation.ts +51 -0
  221. package/ts/integrations/daikin/readme.md +83 -0
  222. package/ts/integrations/denonavr/denonavr.classes.client.ts +132 -421
  223. package/ts/integrations/denonavr/denonavr.classes.configflow.ts +44 -37
  224. package/ts/integrations/denonavr/denonavr.classes.integration.ts +171 -117
  225. package/ts/integrations/denonavr/denonavr.discovery.ts +67 -220
  226. package/ts/integrations/denonavr/denonavr.mapper.ts +36 -182
  227. package/ts/integrations/denonavr/denonavr.types.ts +48 -111
  228. package/ts/integrations/denonavr/denonavr.validation.ts +87 -0
  229. package/ts/integrations/kodi/kodi.classes.client.ts +72 -352
  230. package/ts/integrations/kodi/kodi.classes.configflow.ts +52 -55
  231. package/ts/integrations/kodi/kodi.classes.integration.ts +97 -178
  232. package/ts/integrations/kodi/kodi.discovery.ts +49 -28
  233. package/ts/integrations/kodi/kodi.mapper.ts +52 -10
  234. package/ts/integrations/kodi/kodi.types.ts +33 -58
  235. package/ts/integrations/kodi/kodi.validation.ts +60 -0
  236. package/ts/integrations/modbus/modbus.classes.client.ts +100 -842
  237. package/ts/integrations/modbus/modbus.classes.configflow.ts +70 -41
  238. package/ts/integrations/modbus/modbus.classes.integration.ts +115 -60
  239. package/ts/integrations/modbus/modbus.discovery.ts +38 -106
  240. package/ts/integrations/modbus/modbus.mapper.ts +64 -467
  241. package/ts/integrations/modbus/modbus.types.ts +64 -381
  242. package/ts/integrations/modbus/modbus.validation.ts +119 -0
  243. package/ts/integrations/nanoleaf/nanoleaf.classes.client.ts +115 -249
  244. package/ts/integrations/nanoleaf/nanoleaf.classes.configflow.ts +90 -19
  245. package/ts/integrations/nanoleaf/nanoleaf.classes.integration.ts +130 -181
  246. package/ts/integrations/nanoleaf/nanoleaf.credentials.ts +39 -0
  247. package/ts/integrations/nanoleaf/nanoleaf.mapper.ts +47 -260
  248. package/ts/integrations/nanoleaf/nanoleaf.types.ts +29 -147
  249. package/ts/integrations/nanoleaf/nanoleaf.validation.ts +77 -0
  250. package/ts/integrations/onkyo/onkyo.classes.client.ts +148 -457
  251. package/ts/integrations/onkyo/onkyo.classes.configflow.ts +52 -6
  252. package/ts/integrations/onkyo/onkyo.classes.integration.ts +121 -12
  253. package/ts/integrations/onkyo/onkyo.discovery.ts +41 -3
  254. package/ts/integrations/onkyo/onkyo.mapper.ts +46 -19
  255. package/ts/integrations/onkyo/onkyo.types.ts +55 -110
  256. package/ts/integrations/onkyo/onkyo.validation.ts +85 -0
  257. package/ts/integrations/onkyo/readme.md +126 -0
  258. package/ts/integrations/panasonic_viera/panasonic_viera.classes.client.ts +101 -398
  259. package/ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.ts +69 -5
  260. package/ts/integrations/panasonic_viera/panasonic_viera.classes.integration.ts +141 -9
  261. package/ts/integrations/panasonic_viera/panasonic_viera.discovery.ts +32 -3
  262. package/ts/integrations/panasonic_viera/panasonic_viera.mapper.ts +34 -161
  263. package/ts/integrations/panasonic_viera/panasonic_viera.types.ts +52 -96
  264. package/ts/integrations/panasonic_viera/panasonic_viera.validation.ts +56 -0
  265. package/ts/integrations/pioneer/pioneer.classes.client.ts +132 -428
  266. package/ts/integrations/pioneer/pioneer.classes.configflow.ts +67 -5
  267. package/ts/integrations/pioneer/pioneer.classes.integration.ts +225 -11
  268. package/ts/integrations/pioneer/pioneer.discovery.ts +45 -3
  269. package/ts/integrations/pioneer/pioneer.mapper.ts +57 -13
  270. package/ts/integrations/pioneer/pioneer.types.ts +87 -85
  271. package/ts/integrations/pioneer/pioneer.validation.ts +84 -0
  272. package/ts/integrations/soundtouch/soundtouch.classes.client.ts +152 -571
  273. package/ts/integrations/soundtouch/soundtouch.classes.configflow.ts +47 -42
  274. package/ts/integrations/soundtouch/soundtouch.classes.integration.ts +95 -150
  275. package/ts/integrations/soundtouch/soundtouch.discovery.ts +21 -133
  276. package/ts/integrations/soundtouch/soundtouch.mapper.ts +36 -207
  277. package/ts/integrations/soundtouch/soundtouch.types.ts +50 -195
  278. package/ts/integrations/soundtouch/soundtouch.validation.ts +60 -0
  279. package/ts/integrations/wiz/index.ts +1 -0
  280. package/ts/integrations/wiz/wiz.classes.client.ts +84 -254
  281. package/ts/integrations/wiz/wiz.classes.configflow.ts +39 -70
  282. package/ts/integrations/wiz/wiz.classes.integration.ts +189 -45
  283. package/ts/integrations/wiz/wiz.discovery.ts +48 -291
  284. package/ts/integrations/wiz/wiz.mapper.ts +81 -754
  285. package/ts/integrations/wiz/wiz.types.ts +24 -224
  286. package/ts/integrations/wiz/wiz.validation.ts +73 -0
  287. package/ts/integrations/yamaha/index.ts +1 -0
  288. package/ts/integrations/yamaha/yamaha.classes.client.ts +150 -6
  289. package/ts/integrations/yamaha/yamaha.classes.configflow.ts +100 -5
  290. package/ts/integrations/yamaha/yamaha.classes.integration.ts +236 -8
  291. package/ts/integrations/yamaha/yamaha.discovery.ts +52 -3
  292. package/ts/integrations/yamaha/yamaha.mapper.ts +65 -13
  293. package/ts/integrations/yamaha/yamaha.types.ts +77 -97
  294. package/ts/integrations/yamaha/yamaha.validation.ts +86 -0
@@ -28,6 +28,12 @@ export interface IConfigStoreInitializationOptions {
28
28
  signal?: AbortSignal;
29
29
  }
30
30
 
31
+ interface IConfigStoreOperationState {
32
+ keyLookups: Set<Promise<void>>;
33
+ settled: boolean;
34
+ providerFailed: boolean;
35
+ }
36
+
31
37
  const validateName = (valueArg: string, nameArg: string, maxLengthArg: number): string => {
32
38
  if (typeof valueArg !== 'string' || valueArg.length === 0 || valueArg.length > maxLengthArg || /[\p{Cc}\p{Cs}]/u.test(valueArg)) {
33
39
  throw new TypeError(`${nameArg} must be a non-empty string without control characters, at most ${maxLengthArg} characters.`);
@@ -102,9 +108,13 @@ export class NoSqlConfigStore implements IConfigStore {
102
108
  readonly #maxValueBytes: number;
103
109
  readonly #lifetime = new AbortController();
104
110
  readonly #preparations = new Set<Promise<void>>();
111
+ readonly #keyLookups = new Set<Promise<void>>();
112
+ readonly #retainedOperations = new Set<IConfigStoreOperationState>();
113
+ readonly #keyProviderFailures = new Set<Error>();
105
114
  #operationTail: Promise<void> = Promise.resolve();
106
115
  #pendingOperations = 0;
107
116
  #closePromise?: Promise<void>;
117
+ #retryPromise?: Promise<void>;
108
118
 
109
119
  constructor(optionsArg: INoSqlConfigStoreOptions) {
110
120
  this.#homeId = validateName(optionsArg.homeId, 'homeId', 256);
@@ -156,19 +166,34 @@ export class NoSqlConfigStore implements IConfigStore {
156
166
  await Promise.allSettled([
157
167
  ...this.#preparations,
158
168
  this.#operationTail,
169
+ ...this.#keyLookups,
159
170
  ]);
171
+ if (this.#keyProviderFailures.size) {
172
+ throw new AggregateError([...this.#keyProviderFailures], 'NoSqlConfigStore key-provider cleanup failed.');
173
+ }
160
174
  });
161
175
  this.#lifetime.abort(new Error('NoSqlConfigStore is closed.'));
162
176
  }
163
177
  return this.#closePromise;
164
178
  }
165
179
 
180
+ /** Acknowledges settled provider cleanup failures without repeating callbacks or reopening the store. */
181
+ public retryCleanup(): Promise<void> {
182
+ if (!this.#closePromise) return Promise.reject(new Error('NoSqlConfigStore cleanup requires terminal close.'));
183
+ this.#retryPromise ??= Promise.resolve().then(async () => {
184
+ await this.#closePromise!.catch(() => undefined);
185
+ this.#keyProviderFailures.clear();
186
+ this.#retainedOperations.clear();
187
+ });
188
+ return this.#retryPromise;
189
+ }
190
+
166
191
  public get<TValue>(keyArg: string, optionsArg: IConfigStoreOperationOptions = {}): Promise<TValue | undefined> {
167
192
  const key = validateName(keyArg, 'config key', 1024);
168
193
  const databaseOptions = this.#databaseOptions(optionsArg);
169
- return this.#runOperation(async () => {
194
+ return this.#runOperation(async (operationArg) => {
170
195
  const record = await this.#records.getInstance(this.#selector(key), databaseOptions);
171
- return record ? this.#decrypt<TValue>(key, record.encryptedValue) : undefined;
196
+ return record ? this.#decrypt<TValue>(key, record.encryptedValue, operationArg) : undefined;
172
197
  });
173
198
  }
174
199
 
@@ -176,8 +201,9 @@ export class NoSqlConfigStore implements IConfigStore {
176
201
  const key = validateName(keyArg, 'config key', 1024);
177
202
  const serialized = this.#serialize(valueArg);
178
203
  const databaseOptions = this.#databaseOptions(optionsArg);
179
- return this.#runOperation(async () => {
180
- const encryptedValue = await this.#encrypt(key, serialized);
204
+ return this.#runOperation(async (operationArg) => {
205
+ const encryptedValue = await this.#encrypt(key, serialized, operationArg);
206
+ this.#assertOpen();
181
207
  await this.#records.atomicUpdate({ id: this.#selector(key).id }, {
182
208
  $set: { homeId: this.#homeId, integrationDomain: this.#integrationDomain, key, encryptedValue },
183
209
  }, { upsert: true, ...databaseOptions });
@@ -189,10 +215,11 @@ export class NoSqlConfigStore implements IConfigStore {
189
215
  const key = validateName(keyArg, 'config key', 1024);
190
216
  const serialized = this.#serialize(valueArg);
191
217
  const databaseOptions = this.#databaseOptions(optionsArg);
192
- return this.#runOperation(async () => {
218
+ return this.#runOperation(async (operationArg) => {
193
219
  const record = Object.assign(new this.#records(), this.#selector(key), {
194
- encryptedValue: await this.#encrypt(key, serialized),
220
+ encryptedValue: await this.#encrypt(key, serialized, operationArg),
195
221
  });
222
+ this.#assertOpen();
196
223
  await this.#records.insert(record, databaseOptions);
197
224
  });
198
225
  }
@@ -253,8 +280,9 @@ export class NoSqlConfigStore implements IConfigStore {
253
280
  return serialized;
254
281
  }
255
282
 
256
- async #encrypt(keyArg: string, serializedArg: string): Promise<IEncryptedConfigValue> {
257
- const encryptionKey = await this.#lookupKey((signalArg) => this.#keyProvider.currentKey(signalArg));
283
+ async #encrypt(keyArg: string, serializedArg: string, operationArg: IConfigStoreOperationState): Promise<IEncryptedConfigValue> {
284
+ const encryptionKey = await this.#lookupKey((signalArg) => this.#keyProvider.currentKey(signalArg), operationArg);
285
+ this.#assertOpen();
258
286
  const keyId = validateName(encryptionKey.id, 'encryption key ID', 128);
259
287
  const key = copyKey(encryptionKey.key);
260
288
  try {
@@ -268,13 +296,15 @@ export class NoSqlConfigStore implements IConfigStore {
268
296
  }
269
297
  }
270
298
 
271
- async #decrypt<TValue>(keyArg: string, valueArg: IEncryptedConfigValue): Promise<TValue> {
299
+ async #decrypt<TValue>(keyArg: string, valueArg: IEncryptedConfigValue, operationArg: IConfigStoreOperationState): Promise<TValue> {
272
300
  if (!valueArg || valueArg.version !== 1) throw new Error('Stored config encryption envelope is invalid.');
273
301
  const keyId = validateName(valueArg.keyId, 'stored encryption key ID', 128);
274
302
  const nonce = decodeBase64(valueArg.nonce, 12, 12);
275
303
  const tag = decodeBase64(valueArg.tag, 16, 16);
276
304
  const ciphertext = decodeBase64(valueArg.ciphertext, this.#maxValueBytes);
277
- const key = copyKey(await this.#lookupKey((signalArg) => this.#keyProvider.keyForId(keyId, signalArg)));
305
+ const decryptionKey = await this.#lookupKey((signalArg) => this.#keyProvider.keyForId(keyId, signalArg), operationArg);
306
+ this.#assertOpen();
307
+ const key = copyKey(decryptionKey);
278
308
  try {
279
309
  const decipher = plugins.crypto.createDecipheriv('aes-256-gcm', key, nonce);
280
310
  decipher.setAAD(this.#aad(keyArg, keyId));
@@ -294,24 +324,46 @@ export class NoSqlConfigStore implements IConfigStore {
294
324
  }
295
325
  }
296
326
 
297
- async #lookupKey<TResult>(lookupArg: (signalArg: AbortSignal) => Promise<TResult>): Promise<TResult> {
327
+ async #lookupKey<TResult>(lookupArg: (signalArg: AbortSignal) => Promise<TResult>, operationArg: IConfigStoreOperationState): Promise<TResult> {
298
328
  this.#assertOpen();
299
329
  const controller = new AbortController();
300
- let timer: ReturnType<typeof setTimeout> | undefined;
301
- const deadline = new Promise<never>((resolveArg, rejectArg) => {
330
+ const signal = AbortSignal.any([controller.signal, this.#lifetime.signal]);
331
+ let resolveProvider!: (valueArg: TResult | PromiseLike<TResult>) => void;
332
+ let rejectProvider!: (errorArg: unknown) => void;
333
+ const provider = new Promise<TResult>((resolveArg, rejectArg) => {
334
+ resolveProvider = resolveArg;
335
+ rejectProvider = rejectArg;
336
+ });
337
+ let abortLookup!: () => void;
338
+ const settlement = provider.then(() => undefined, (errorArg: unknown) => {
339
+ if (signal.aborted && errorArg !== signal.reason) {
340
+ operationArg.providerFailed = true;
341
+ this.#keyProviderFailures.add(new Error('Config encryption key provider rejected after cancellation.'));
342
+ }
343
+ }).finally(() => {
344
+ this.#keyLookups.delete(settlement);
345
+ operationArg.keyLookups.delete(settlement);
346
+ if (operationArg.settled && !operationArg.keyLookups.size && !operationArg.providerFailed) {
347
+ this.#retainedOperations.delete(operationArg);
348
+ }
349
+ signal.removeEventListener('abort', abortLookup);
350
+ });
351
+ // Own the actual callback promise before invoking foreign code, including synchronous close reentry.
352
+ this.#keyLookups.add(settlement);
353
+ operationArg.keyLookups.add(settlement);
354
+ const cancellation = new Promise<never>((resolveArg, rejectArg) => {
302
355
  void resolveArg;
303
- timer = setTimeout(() => {
304
- const error = new Error('Config encryption key lookup timed out.');
305
- controller.abort(error);
306
- rejectArg(error);
307
- }, this.#timeoutMs);
308
- timer.unref();
356
+ abortLookup = () => {
357
+ rejectArg(signal.reason);
358
+ };
359
+ signal.addEventListener('abort', abortLookup, { once: true });
309
360
  });
361
+ const timer = setTimeout(() => controller.abort(new Error('Config encryption key lookup timed out.')), this.#timeoutMs);
362
+ timer.unref();
310
363
  try {
311
- const result = await Promise.race([
312
- lookupArg(AbortSignal.any([controller.signal, this.#lifetime.signal])),
313
- deadline,
314
- ]);
364
+ try { resolveProvider(lookupArg(signal)); } catch (errorArg) { rejectProvider(errorArg); }
365
+ const result = await Promise.race([provider, cancellation]);
366
+ signal.throwIfAborted();
315
367
  this.#assertOpen();
316
368
  return result;
317
369
  } finally {
@@ -319,18 +371,23 @@ export class NoSqlConfigStore implements IConfigStore {
319
371
  }
320
372
  }
321
373
 
322
- #runOperation<TResult>(operationArg: () => Promise<TResult>): Promise<TResult> {
374
+ #runOperation<TResult>(operationArg: (stateArg: IConfigStoreOperationState) => Promise<TResult>): Promise<TResult> {
323
375
  if (this.#lifetime.signal.aborted) {
324
376
  return Promise.reject(this.#lifetime.signal.reason);
325
377
  }
326
- if (this.#pendingOperations >= this.#maxPendingOperations) {
378
+ if (this.#pendingOperations + this.#retainedOperations.size >= this.#maxPendingOperations) {
327
379
  return Promise.reject(new Error(`NoSqlConfigStore operation capacity reached: ${this.#maxPendingOperations}.`));
328
380
  }
329
381
  this.#pendingOperations += 1;
382
+ const state: IConfigStoreOperationState = { keyLookups: new Set(), settled: false, providerFailed: false };
330
383
  const operation = this.#operationTail.then(() => {
331
384
  this.#assertOpen();
332
- return operationArg();
333
- }).finally(() => { this.#pendingOperations -= 1; });
385
+ return operationArg(state);
386
+ }).finally(() => {
387
+ state.settled = true;
388
+ this.#pendingOperations -= 1;
389
+ if (state.keyLookups.size || state.providerFailed) this.#retainedOperations.add(state);
390
+ });
334
391
  this.#operationTail = operation.then(() => undefined, () => undefined);
335
392
  return operation;
336
393
  }
@@ -0,0 +1,96 @@
1
+ import { NoSqlConfigStore } from './classes.configstore.js';
2
+ import type { IConfigFlowContext, IIntegrationSetupContext, ILogger } from './types.js';
3
+
4
+ export interface INativeHomeAdmissionOptions {
5
+ readonly homeId?: string;
6
+ }
7
+
8
+ /** A captured host selection, not an authentication credential. Persistence resources are borrowed. */
9
+ export interface INativeAdmissionContext {
10
+ readonly homeId: string;
11
+ readonly configStore?: NoSqlConfigStore;
12
+ readonly logger?: ILogger;
13
+ readonly abortSignal?: AbortSignal;
14
+ }
15
+
16
+ const identifier = (valueArg: unknown, nameArg: string, maximumArg: number): string => {
17
+ if (typeof valueArg !== 'string' || !valueArg || valueArg.length > maximumArg
18
+ || valueArg.trim() !== valueArg || /[\p{Cc}\p{Cs}]/u.test(valueArg)) {
19
+ throw new TypeError(`${nameArg} must be a non-empty identifier without surrounding whitespace or control characters, at most ${maximumArg} characters.`);
20
+ }
21
+ return valueArg;
22
+ };
23
+
24
+ const dataFields = (valueArg: unknown, allowedArg: readonly string[], nameArg: string): Record<string, unknown> => {
25
+ if (!valueArg || typeof valueArg !== 'object'
26
+ || (Object.getPrototypeOf(valueArg) !== Object.prototype && Object.getPrototypeOf(valueArg) !== null)) {
27
+ throw new TypeError(`${nameArg} must be a plain data object.`);
28
+ }
29
+ const captured: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
30
+ for (const key of Reflect.ownKeys(valueArg)) {
31
+ const descriptor = Object.getOwnPropertyDescriptor(valueArg, key);
32
+ if (typeof key !== 'string' || !allowedArg.includes(key) || !descriptor
33
+ || !descriptor.enumerable || !('value' in descriptor)) {
34
+ throw new TypeError(`${nameArg} contains an unsupported field or accessor.`);
35
+ }
36
+ captured[key] = descriptor.value as unknown;
37
+ }
38
+ return captured;
39
+ };
40
+
41
+ const logger = (valueArg: unknown): ILogger | undefined => {
42
+ if (valueArg === undefined) return undefined;
43
+ if (!valueArg || typeof valueArg !== 'object') throw new TypeError('Native logger must expose a log method.');
44
+ let target: object | null = valueArg;
45
+ for (let depth = 0; target && depth < 16; depth += 1) {
46
+ const descriptor = Object.getOwnPropertyDescriptor(target, 'log');
47
+ if (descriptor) {
48
+ if ('value' in descriptor && typeof descriptor.value === 'function') return valueArg as ILogger;
49
+ break;
50
+ }
51
+ target = Object.getPrototypeOf(target) as object | null;
52
+ }
53
+ throw new TypeError('Native logger must expose a data-method log function.');
54
+ };
55
+
56
+ /** Binds to independently selected host context only after all synchronous checks pass. */
57
+ export class NativeHomeAdmission {
58
+ readonly #domain: string;
59
+ #homeId?: string;
60
+
61
+ constructor(domainArg: string, optionsArg: INativeHomeAdmissionOptions = {}) {
62
+ this.#domain = identifier(domainArg, 'integrationDomain', 128);
63
+ const options = dataFields(optionsArg, ['homeId'], 'Native home options');
64
+ if (options.homeId !== undefined) this.#homeId = identifier(options.homeId, 'homeId', 256);
65
+ }
66
+
67
+ public get homeId(): string | undefined { return this.#homeId; }
68
+
69
+ public admit(contextArg: IIntegrationSetupContext | IConfigFlowContext, configHomeIdArg?: string): INativeAdmissionContext {
70
+ const context = dataFields(contextArg, ['homeId', 'configStore', 'logger', 'abortSignal'], 'Native context');
71
+ const homeId = identifier(context.homeId, 'Host-selected homeId', 256);
72
+ if (this.#homeId !== undefined && this.#homeId !== homeId) throw new Error('Native owning home does not match the selected host home.');
73
+ if (configHomeIdArg !== undefined && identifier(configHomeIdArg, 'Configuration homeId', 256) !== homeId) {
74
+ throw new Error('Native configuration home does not match the selected host home.');
75
+ }
76
+ const configStore = context.configStore;
77
+ if (configStore !== undefined) {
78
+ if (!(configStore instanceof NoSqlConfigStore)) throw new TypeError('Native configStore must be an actual NoSqlConfigStore.');
79
+ // Call the owning implementation: a structural or overridden getScope cannot grant admission.
80
+ const scope = NoSqlConfigStore.prototype.getScope.call(configStore);
81
+ if (scope.homeId !== homeId || scope.integrationDomain !== this.#domain) {
82
+ throw new Error('Native configStore home/domain does not match the admitted owner.');
83
+ }
84
+ }
85
+ const capturedLogger = logger(context.logger);
86
+ const abortSignal = context.abortSignal;
87
+ if (abortSignal !== undefined) {
88
+ if (!(abortSignal instanceof AbortSignal)) throw new TypeError('Native abortSignal must be an AbortSignal.');
89
+ AbortSignal.prototype.throwIfAborted.call(abortSignal);
90
+ }
91
+ const captured = Object.freeze({ homeId, configStore: configStore as NoSqlConfigStore | undefined,
92
+ logger: capturedLogger, abortSignal: abortSignal as AbortSignal | undefined });
93
+ this.#homeId = homeId;
94
+ return captured;
95
+ }
96
+ }
@@ -0,0 +1,297 @@
1
+ import { NativeHomeAdmission, type INativeAdmissionContext, type INativeHomeAdmissionOptions } from './classes.nativehomeadmission.js';
2
+ import type { IConfigFlowContext, IIntegrationSetupContext } from './types.js';
3
+
4
+ export interface INativeIntegrationOwnerOptions extends INativeHomeAdmissionOptions {
5
+ /** Shared limit for pending operations, runtimes, and retained cleanup resources. Defaults to 16. */
6
+ readonly capacity?: number;
7
+ }
8
+
9
+ export interface INativeOperationLease {
10
+ readonly context: INativeAdmissionContext;
11
+ readonly signal: AbortSignal;
12
+ assertOpen(): void;
13
+ /** Register immediately after allocation, before invoking initialization or other foreign callbacks. */
14
+ own(cleanupArg: () => Promise<void>): void;
15
+ close(): Promise<void>;
16
+ }
17
+
18
+ export interface INativeSetupLease extends INativeOperationLease {
19
+ /** Transfers this operation's cleanup ownership to the returned runtime. */
20
+ promoteToRuntime(): void;
21
+ }
22
+
23
+ interface INativeResource {
24
+ readonly cleanup: () => Promise<void>;
25
+ status: 'idle' | 'pending' | 'closed' | 'failed';
26
+ promise?: Promise<void>;
27
+ }
28
+
29
+ interface INativeScopeState {
30
+ readonly lifetime: AbortController;
31
+ readonly leases: Set<NativeIntegrationLease>;
32
+ readonly operations: Set<Promise<unknown>>;
33
+ closed: boolean;
34
+ closePromise?: Promise<void>;
35
+ retryPromise?: Promise<void>;
36
+ }
37
+
38
+ const scopeState = (): INativeScopeState => ({ lifetime: new AbortController(), leases: new Set(), operations: new Set(), closed: false });
39
+
40
+ class NativeIntegrationLease implements INativeSetupLease {
41
+ readonly #state: NativeOwnerState;
42
+ readonly #scope: INativeScopeState;
43
+ readonly #runtimeAllowed: boolean;
44
+ readonly #lifetime = new AbortController();
45
+ readonly #resources = new Set<INativeResource>();
46
+ readonly context: INativeAdmissionContext;
47
+ readonly signal: AbortSignal;
48
+ #pending = true;
49
+ #promoted = false;
50
+ #closed = false;
51
+ #closePromise?: Promise<void>;
52
+
53
+ constructor(stateArg: NativeOwnerState, scopeArg: INativeScopeState, contextArg: INativeAdmissionContext, runtimeAllowedArg: boolean) {
54
+ this.#state = stateArg;
55
+ this.#scope = scopeArg;
56
+ this.#runtimeAllowed = runtimeAllowedArg;
57
+ this.context = contextArg;
58
+ this.signal = AbortSignal.any([stateArg.lifetime.signal, scopeArg.lifetime.signal, this.#lifetime.signal,
59
+ ...(contextArg.abortSignal ? [contextArg.abortSignal] : [])]);
60
+ // Abort owns cleanup as well as cancellation; rejected cleanup remains on this retained lease.
61
+ this.signal.addEventListener('abort', () => { void this.close().catch(() => undefined); }, { once: true });
62
+ }
63
+
64
+ public get promoted(): boolean { return this.#promoted; }
65
+ public get weight(): number { return Math.max(1, [...this.#resources].filter((resource) => resource.status !== 'closed').length); }
66
+ public get failed(): boolean { return [...this.#resources].some((resource) => resource.status === 'failed'); }
67
+
68
+ public assertOpen(): void {
69
+ if (this.#closed || this.#state.closed || this.#scope.closed) throw new Error('Native admission lease is closed.');
70
+ this.signal.throwIfAborted();
71
+ }
72
+
73
+ public own(cleanupArg: () => Promise<void>): void {
74
+ this.assertOpen();
75
+ if (typeof cleanupArg !== 'function') throw new TypeError('Native cleanup must be a function.');
76
+ if ([...this.#resources].filter((resource) => resource.status !== 'closed').length > 0) this.#state.assertCapacity();
77
+ this.#resources.add({ cleanup: cleanupArg, status: 'idle' });
78
+ }
79
+
80
+ public promoteToRuntime(): void {
81
+ this.assertOpen();
82
+ if (!this.#runtimeAllowed || !this.#pending) throw new Error('Only an admitted pending setup can become a runtime.');
83
+ if (!this.#resources.size) throw new Error('A native runtime must retain an owned cleanup handle.');
84
+ this.#promoted = true;
85
+ }
86
+
87
+ public finish(): void { this.#pending = false; this.#releaseIfClosed(); }
88
+
89
+ public close(): Promise<void> {
90
+ if (!this.#closePromise) {
91
+ this.#closed = true;
92
+ this.#closePromise = Promise.resolve().then(() => this.#drain(false));
93
+ this.#lifetime.abort(new Error('Native admission lease is closed.'));
94
+ }
95
+ return this.#closePromise;
96
+ }
97
+
98
+ public retryCleanup(): Promise<void> { return this.#drain(true); }
99
+
100
+ async #drain(retryArg: boolean): Promise<void> {
101
+ const results = await Promise.allSettled([...this.#resources].map((resource) => {
102
+ if (retryArg && resource.status === 'failed') resource.promise = undefined;
103
+ if (!resource.promise) {
104
+ resource.status = 'pending';
105
+ resource.promise = Promise.resolve().then(() => resource.cleanup()).then(() => {
106
+ resource.status = 'closed';
107
+ }, (errorArg: unknown) => {
108
+ resource.status = 'failed';
109
+ throw errorArg;
110
+ });
111
+ }
112
+ return resource.promise;
113
+ }));
114
+ this.#releaseIfClosed();
115
+ const failures = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected').map((result) => result.reason as unknown);
116
+ if (failures.length) throw new AggregateError(failures, 'Native resource cleanup failed; handles remain owned for explicit retry.');
117
+ }
118
+
119
+ #releaseIfClosed(): void {
120
+ if (!this.#pending && this.#closed && [...this.#resources].every((resource) => resource.status === 'closed')) {
121
+ this.#state.release(this, this.#scope);
122
+ }
123
+ }
124
+ }
125
+
126
+ class NativeOwnerState {
127
+ readonly admission: NativeHomeAdmission;
128
+ readonly lifetime = new AbortController();
129
+ readonly root = scopeState();
130
+ readonly #capacity: number;
131
+ readonly #leases = new Set<NativeIntegrationLease>();
132
+ readonly #scopes = new Set<INativeScopeState>([this.root]);
133
+ closed = false;
134
+ #closePromise?: Promise<void>;
135
+ #retryPromise?: Promise<void>;
136
+
137
+ constructor(domainArg: string, optionsArg: INativeIntegrationOwnerOptions) {
138
+ if (!optionsArg || (Object.getPrototypeOf(optionsArg) !== Object.prototype && Object.getPrototypeOf(optionsArg) !== null)) {
139
+ throw new TypeError('Native owner options must be a plain data object.');
140
+ }
141
+ for (const key of Reflect.ownKeys(optionsArg)) {
142
+ const descriptor = Object.getOwnPropertyDescriptor(optionsArg, key);
143
+ if ((key !== 'homeId' && key !== 'capacity') || !descriptor || !descriptor.enumerable || !('value' in descriptor)) {
144
+ throw new TypeError('Native owner options contain an unsupported field or accessor.');
145
+ }
146
+ }
147
+ this.#capacity = optionsArg.capacity ?? 16;
148
+ if (!Number.isSafeInteger(this.#capacity) || this.#capacity < 1) throw new RangeError('Native owner capacity must be a positive safe integer.');
149
+ this.admission = new NativeHomeAdmission(domainArg, { homeId: optionsArg.homeId });
150
+ }
151
+
152
+ public assertOpen(scopeArg: INativeScopeState): void {
153
+ if (this.closed || scopeArg.closed) throw new Error('Native integration owner/scope is closed.');
154
+ }
155
+
156
+ public assertCapacity(): void {
157
+ if ([...this.#leases].reduce((count, lease) => count + lease.weight, 0) >= this.#capacity) {
158
+ throw new Error(`Native integration ownership capacity reached: ${this.#capacity}.`);
159
+ }
160
+ }
161
+
162
+ public createScope(): INativeScopeState {
163
+ this.assertOpen(this.root);
164
+ if (this.#scopes.size > this.#capacity) throw new Error('Native config-flow scope capacity reached.');
165
+ const scope = scopeState();
166
+ this.#scopes.add(scope);
167
+ return scope;
168
+ }
169
+
170
+ public admit(scopeArg: INativeScopeState, contextArg: IIntegrationSetupContext | IConfigFlowContext, configHomeIdArg?: string): INativeAdmissionContext {
171
+ this.assertOpen(scopeArg);
172
+ return this.admission.admit(contextArg, configHomeIdArg);
173
+ }
174
+
175
+ public run<TResult>(scopeArg: INativeScopeState, contextArg: IIntegrationSetupContext | IConfigFlowContext,
176
+ configHomeIdArg: string | undefined, workArg: (leaseArg: INativeSetupLease) => Promise<TResult>, runtimeArg: boolean): Promise<TResult> {
177
+ this.assertOpen(scopeArg);
178
+ if (typeof workArg !== 'function') throw new TypeError('Native operation must be a function.');
179
+ if (runtimeArg && typeof configHomeIdArg !== 'string') throw new TypeError('Native setup requires configuration homeId.');
180
+ this.assertCapacity();
181
+ const context = this.admission.admit(contextArg, configHomeIdArg);
182
+ const lease = new NativeIntegrationLease(this, scopeArg, context, runtimeArg);
183
+ this.#leases.add(lease);
184
+ scopeArg.leases.add(lease);
185
+ // Publish ownership and work before invoking any caller callback.
186
+ const operation = Promise.resolve().then(async () => {
187
+ try {
188
+ lease.assertOpen();
189
+ const result = await workArg(lease);
190
+ if (runtimeArg) {
191
+ // Returning a live runtime is a new admission; a completed flow effect is already accepted.
192
+ lease.assertOpen();
193
+ if (!lease.promoted) throw new Error('Native setup must transfer its cleanup lease to the returned runtime.');
194
+ }
195
+ if (!runtimeArg) await lease.close();
196
+ return result;
197
+ } catch (errorArg) {
198
+ try { await lease.close(); }
199
+ catch (cleanupErrorArg) { throw new AggregateError([errorArg, cleanupErrorArg], 'Native operation and cleanup failed.'); }
200
+ throw errorArg;
201
+ } finally { lease.finish(); }
202
+ });
203
+ scopeArg.operations.add(operation);
204
+ void operation.then(() => scopeArg.operations.delete(operation), () => scopeArg.operations.delete(operation));
205
+ return operation;
206
+ }
207
+
208
+ public release(leaseArg: NativeIntegrationLease, scopeArg: INativeScopeState): void {
209
+ this.#leases.delete(leaseArg);
210
+ scopeArg.leases.delete(leaseArg);
211
+ }
212
+
213
+ public closeScope(scopeArg: INativeScopeState): Promise<void> {
214
+ if (!scopeArg.closePromise) {
215
+ scopeArg.closed = true;
216
+ scopeArg.closePromise = Promise.resolve().then(async () => {
217
+ await this.#drain([scopeArg]);
218
+ this.#scopes.delete(scopeArg);
219
+ });
220
+ for (const lease of scopeArg.leases) void lease.close().catch(() => undefined);
221
+ scopeArg.lifetime.abort(new Error('Native config-flow scope is closed.'));
222
+ }
223
+ return scopeArg.closePromise;
224
+ }
225
+
226
+ public close(): Promise<void> {
227
+ if (!this.#closePromise) {
228
+ this.closed = true;
229
+ for (const scope of this.#scopes) scope.closed = true;
230
+ this.#closePromise = Promise.resolve().then(() => this.#drain([...this.#scopes]));
231
+ for (const lease of this.#leases) void lease.close().catch(() => undefined);
232
+ this.lifetime.abort(new Error('Native integration owner is closed.'));
233
+ for (const scope of this.#scopes) scope.lifetime.abort(this.lifetime.signal.reason);
234
+ }
235
+ return this.#closePromise;
236
+ }
237
+
238
+ public retryCleanup(scopeArg?: INativeScopeState): Promise<void> {
239
+ const existing = scopeArg ? scopeArg.retryPromise : this.#retryPromise;
240
+ if (existing) return existing;
241
+ const retry = Promise.resolve().then(async () => {
242
+ const leases = [...(scopeArg ? scopeArg.leases : this.#leases)].filter((lease) => lease.failed);
243
+ const results = await Promise.allSettled(leases.map((lease) => lease.retryCleanup()));
244
+ const errors = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected').map((result) => result.reason as unknown);
245
+ if (errors.length) throw new AggregateError(errors, 'Explicit native cleanup retry failed.');
246
+ for (const scope of this.#scopes) {
247
+ if (scope !== this.root && scope.closed && !scope.leases.size && !scope.operations.size) this.#scopes.delete(scope);
248
+ }
249
+ });
250
+ if (scopeArg) scopeArg.retryPromise = retry;
251
+ else this.#retryPromise = retry;
252
+ const settled = (): void => { if (scopeArg) scopeArg.retryPromise = undefined; else this.#retryPromise = undefined; };
253
+ void retry.then(settled, settled);
254
+ return retry;
255
+ }
256
+
257
+ async #drain(scopesArg: INativeScopeState[]): Promise<void> {
258
+ const cleanup = scopesArg.flatMap((scope) => [...scope.leases].map((lease) => lease.close()));
259
+ const operations = scopesArg.flatMap((scope) => [...scope.operations]);
260
+ const [results] = await Promise.all([Promise.allSettled(cleanup), Promise.allSettled(operations)]);
261
+ // Operation failures are delivered to their callers; owner close owns joining and cleanup failures.
262
+ const finalResults = await Promise.allSettled(scopesArg.flatMap((scope) => [...scope.leases].map((lease) => lease.close())));
263
+ const errors = [...results, ...finalResults].filter((result): result is PromiseRejectedResult => result.status === 'rejected').map((result) => result.reason as unknown);
264
+ if (errors.length) throw new AggregateError([...new Set(errors)], 'Native owner cleanup failed; resources remain retained.');
265
+ }
266
+ }
267
+
268
+ /** A terminal child scope shares owner home admission and resource capacity. */
269
+ export class NativeIntegrationScope {
270
+ readonly #state: NativeOwnerState;
271
+ readonly #scope: INativeScopeState;
272
+ constructor(stateArg: NativeOwnerState, scopeArg: INativeScopeState) { this.#state = stateArg; this.#scope = scopeArg; }
273
+ public admit(contextArg: IConfigFlowContext, configHomeIdArg?: string): INativeAdmissionContext {
274
+ return this.#state.admit(this.#scope, contextArg, configHomeIdArg);
275
+ }
276
+ public run<TResult>(contextArg: IConfigFlowContext, configHomeIdArg: string | undefined,
277
+ workArg: (leaseArg: INativeOperationLease) => Promise<TResult>): Promise<TResult> {
278
+ return this.#state.run(this.#scope, contextArg, configHomeIdArg, workArg, false);
279
+ }
280
+ public close(): Promise<void> { return this.#state.closeScope(this.#scope); }
281
+ public retryCleanup(): Promise<void> { return this.#state.retryCleanup(this.#scope); }
282
+ }
283
+
284
+ /** Owns native admission, in-flight work, runtimes and failed cleanup without replaying transport work. */
285
+ export class NativeIntegrationOwner {
286
+ readonly #state: NativeOwnerState;
287
+ constructor(domainArg: string, optionsArg: INativeIntegrationOwnerOptions = {}) { this.#state = new NativeOwnerState(domainArg, optionsArg); }
288
+ public get admission(): NativeHomeAdmission { return this.#state.admission; }
289
+ public get signal(): AbortSignal { return this.#state.lifetime.signal; }
290
+ public runSetup<TResult>(contextArg: IIntegrationSetupContext, configHomeIdArg: string,
291
+ workArg: (leaseArg: INativeSetupLease) => Promise<TResult>): Promise<TResult> {
292
+ return this.#state.run(this.#state.root, contextArg, configHomeIdArg, workArg, true);
293
+ }
294
+ public createFlowScope(): NativeIntegrationScope { return new NativeIntegrationScope(this.#state, this.#state.createScope()); }
295
+ public close(): Promise<void> { return this.#state.close(); }
296
+ public retryCleanup(): Promise<void> { return this.#state.retryCleanup(); }
297
+ }
package/ts/core/index.ts CHANGED
@@ -9,3 +9,5 @@ export * from './classes.runtimemanager.js';
9
9
  export * from './classes.simplelocalintegration.js';
10
10
  export * from './errors.js';
11
11
  export * from './types.js';
12
+ export * from './classes.nativehomeadmission.js';
13
+ export * from './classes.nativeintegrationowner.js';
package/ts/core/types.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as plugins from '../plugins.js';
2
+ import type { NoSqlConfigStore } from './classes.configstore.js';
2
3
 
3
4
  export type TDiscoverySource =
4
5
  | 'mdns'
@@ -112,11 +113,18 @@ export interface IDiscoveryDescriptorOptions {
112
113
  }
113
114
 
114
115
  export interface IIntegrationSetupContext {
116
+ /** Explicit owning home selected by the embedding host, never by device configuration. */
117
+ readonly homeId?: string;
115
118
  logger?: ILogger;
116
119
  configStore?: IConfigStore;
117
120
  abortSignal?: AbortSignal;
118
121
  }
119
122
 
123
+ export interface INativeIntegrationSetupContext extends IIntegrationSetupContext {
124
+ readonly homeId: string;
125
+ readonly configStore?: NoSqlConfigStore;
126
+ }
127
+
120
128
  export type TEntityPlatform =
121
129
  | 'light'
122
130
  | 'switch'
@@ -211,8 +219,16 @@ export interface IConfigFlowField {
211
219
  }
212
220
 
213
221
  export interface IConfigFlowContext {
222
+ /** Explicit owning home selected by the embedding host. This is not an authentication token. */
223
+ readonly homeId?: string;
214
224
  logger?: ILogger;
215
225
  configStore?: IConfigStore;
226
+ abortSignal?: AbortSignal;
227
+ }
228
+
229
+ export interface INativeConfigFlowContext extends IConfigFlowContext {
230
+ readonly homeId: string;
231
+ readonly configStore?: NoSqlConfigStore;
216
232
  }
217
233
 
218
234
  export interface IConfigFlowStep<TConfig = unknown> {