tempest-react-sdk 0.52.0 → 0.54.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 (507) hide show
  1. package/README.md +58 -7
  2. package/dist/access/Can.cjs +1 -1
  3. package/dist/access/Can.cjs.map +1 -1
  4. package/dist/access/Can.js +3 -3
  5. package/dist/access/Can.js.map +1 -1
  6. package/dist/audio/audio-bus.cjs +2 -0
  7. package/dist/audio/audio-bus.cjs.map +1 -0
  8. package/dist/audio/audio-bus.js +116 -0
  9. package/dist/audio/audio-bus.js.map +1 -0
  10. package/dist/audio/level-meter.cjs +1 -1
  11. package/dist/audio/level-meter.cjs.map +1 -1
  12. package/dist/audio/level-meter.js +15 -15
  13. package/dist/audio/level-meter.js.map +1 -1
  14. package/dist/audio/noise-floor.cjs +2 -0
  15. package/dist/audio/noise-floor.cjs.map +1 -0
  16. package/dist/audio/noise-floor.js +27 -0
  17. package/dist/audio/noise-floor.js.map +1 -0
  18. package/dist/audio/use-audio-bus.cjs +2 -0
  19. package/dist/audio/use-audio-bus.cjs.map +1 -0
  20. package/dist/audio/use-audio-bus.js +11 -0
  21. package/dist/audio/use-audio-bus.js.map +1 -0
  22. package/dist/audio/voice-activity.cjs +2 -0
  23. package/dist/audio/voice-activity.cjs.map +1 -0
  24. package/dist/audio/voice-activity.js +26 -0
  25. package/dist/audio/voice-activity.js.map +1 -0
  26. package/dist/audio/voice-chain-stages.cjs +2 -0
  27. package/dist/audio/voice-chain-stages.cjs.map +1 -0
  28. package/dist/audio/voice-chain-stages.js +82 -0
  29. package/dist/audio/voice-chain-stages.js.map +1 -0
  30. package/dist/audio/voice-chain.cjs +2 -0
  31. package/dist/audio/voice-chain.cjs.map +1 -0
  32. package/dist/audio/voice-chain.js +74 -0
  33. package/dist/audio/voice-chain.js.map +1 -0
  34. package/dist/auth/lazy-with-retry.cjs.map +1 -1
  35. package/dist/auth/lazy-with-retry.js.map +1 -1
  36. package/dist/auth/passkey.cjs.map +1 -1
  37. package/dist/auth/passkey.js.map +1 -1
  38. package/dist/br/BrazilStateCitySelect.cjs +1 -1
  39. package/dist/br/BrazilStateCitySelect.cjs.map +1 -1
  40. package/dist/br/BrazilStateCitySelect.js +36 -28
  41. package/dist/br/BrazilStateCitySelect.js.map +1 -1
  42. package/dist/br/data/br-centroids.cjs +1 -1
  43. package/dist/br/data/br-centroids.cjs.map +1 -1
  44. package/dist/br/data/br-centroids.js +11105 -11045
  45. package/dist/br/data/br-centroids.js.map +1 -1
  46. package/dist/br/data/br-locations.cjs +1 -1
  47. package/dist/br/data/br-locations.cjs.map +1 -1
  48. package/dist/br/data/br-locations.js +5803 -137
  49. package/dist/br/data/br-locations.js.map +1 -1
  50. package/dist/br/data/br-uf-geo.cjs +1 -1
  51. package/dist/br/data/br-uf-geo.cjs.map +1 -1
  52. package/dist/br/data/br-uf-geo.js +5721 -6777
  53. package/dist/br/data/br-uf-geo.js.map +1 -1
  54. package/dist/br/data/mun/AC.cjs +1 -1
  55. package/dist/br/data/mun/AC.cjs.map +1 -1
  56. package/dist/br/data/mun/AC.js +681 -689
  57. package/dist/br/data/mun/AC.js.map +1 -1
  58. package/dist/br/data/mun/AL.cjs +1 -1
  59. package/dist/br/data/mun/AL.cjs.map +1 -1
  60. package/dist/br/data/mun/AL.js +949 -844
  61. package/dist/br/data/mun/AL.js.map +1 -1
  62. package/dist/br/data/mun/AM.cjs +1 -1
  63. package/dist/br/data/mun/AM.cjs.map +1 -1
  64. package/dist/br/data/mun/AM.js +4337 -4218
  65. package/dist/br/data/mun/AM.js.map +1 -1
  66. package/dist/br/data/mun/AP.cjs +1 -1
  67. package/dist/br/data/mun/AP.cjs.map +1 -1
  68. package/dist/br/data/mun/AP.js +786 -780
  69. package/dist/br/data/mun/AP.js.map +1 -1
  70. package/dist/br/data/mun/BA.cjs +1 -1
  71. package/dist/br/data/mun/BA.cjs.map +1 -1
  72. package/dist/br/data/mun/BA.js +6329 -5633
  73. package/dist/br/data/mun/BA.js.map +1 -1
  74. package/dist/br/data/mun/CE.cjs +1 -1
  75. package/dist/br/data/mun/CE.cjs.map +1 -1
  76. package/dist/br/data/mun/CE.js +2544 -2487
  77. package/dist/br/data/mun/CE.js.map +1 -1
  78. package/dist/br/data/mun/DF.cjs +1 -1
  79. package/dist/br/data/mun/DF.cjs.map +1 -1
  80. package/dist/br/data/mun/DF.js +17 -16
  81. package/dist/br/data/mun/DF.js.map +1 -1
  82. package/dist/br/data/mun/ES.cjs +1 -1
  83. package/dist/br/data/mun/ES.cjs.map +1 -1
  84. package/dist/br/data/mun/ES.js +1101 -1070
  85. package/dist/br/data/mun/ES.js.map +1 -1
  86. package/dist/br/data/mun/GO.cjs +1 -1
  87. package/dist/br/data/mun/GO.cjs.map +1 -1
  88. package/dist/br/data/mun/GO.js +4632 -4636
  89. package/dist/br/data/mun/GO.js.map +1 -1
  90. package/dist/br/data/mun/MA.cjs +1 -1
  91. package/dist/br/data/mun/MA.cjs.map +1 -1
  92. package/dist/br/data/mun/MA.js +3603 -3457
  93. package/dist/br/data/mun/MA.js.map +1 -1
  94. package/dist/br/data/mun/MG.cjs +1 -1
  95. package/dist/br/data/mun/MG.cjs.map +1 -1
  96. package/dist/br/data/mun/MG.js +12716 -11739
  97. package/dist/br/data/mun/MG.js.map +1 -1
  98. package/dist/br/data/mun/MS.cjs +1 -1
  99. package/dist/br/data/mun/MS.cjs.map +1 -1
  100. package/dist/br/data/mun/MS.js +2182 -2335
  101. package/dist/br/data/mun/MS.js.map +1 -1
  102. package/dist/br/data/mun/MT.cjs +1 -1
  103. package/dist/br/data/mun/MT.cjs.map +1 -1
  104. package/dist/br/data/mun/MT.js +4808 -4896
  105. package/dist/br/data/mun/MT.js.map +1 -1
  106. package/dist/br/data/mun/PA.cjs +1 -1
  107. package/dist/br/data/mun/PA.cjs.map +1 -1
  108. package/dist/br/data/mun/PA.js +4981 -4901
  109. package/dist/br/data/mun/PA.js.map +1 -1
  110. package/dist/br/data/mun/PB.cjs +1 -1
  111. package/dist/br/data/mun/PB.cjs.map +1 -1
  112. package/dist/br/data/mun/PB.js +2418 -1956
  113. package/dist/br/data/mun/PB.js.map +1 -1
  114. package/dist/br/data/mun/PE.cjs +1 -1
  115. package/dist/br/data/mun/PE.cjs.map +1 -1
  116. package/dist/br/data/mun/PE.js +2164 -1987
  117. package/dist/br/data/mun/PE.js.map +1 -1
  118. package/dist/br/data/mun/PI.cjs +1 -1
  119. package/dist/br/data/mun/PI.cjs.map +1 -1
  120. package/dist/br/data/mun/PI.js +2982 -2844
  121. package/dist/br/data/mun/PI.js.map +1 -1
  122. package/dist/br/data/mun/PR.cjs +1 -1
  123. package/dist/br/data/mun/PR.cjs.map +1 -1
  124. package/dist/br/data/mun/PR.js +5425 -5240
  125. package/dist/br/data/mun/PR.js.map +1 -1
  126. package/dist/br/data/mun/RJ.cjs +1 -1
  127. package/dist/br/data/mun/RJ.cjs.map +1 -1
  128. package/dist/br/data/mun/RJ.js +1395 -1178
  129. package/dist/br/data/mun/RJ.js.map +1 -1
  130. package/dist/br/data/mun/RN.cjs +1 -1
  131. package/dist/br/data/mun/RN.cjs.map +1 -1
  132. package/dist/br/data/mun/RN.js +1608 -1382
  133. package/dist/br/data/mun/RN.js.map +1 -1
  134. package/dist/br/data/mun/RO.cjs +1 -1
  135. package/dist/br/data/mun/RO.cjs.map +1 -1
  136. package/dist/br/data/mun/RO.js +1392 -1470
  137. package/dist/br/data/mun/RO.js.map +1 -1
  138. package/dist/br/data/mun/RR.cjs +1 -1
  139. package/dist/br/data/mun/RR.cjs.map +1 -1
  140. package/dist/br/data/mun/RR.js +795 -778
  141. package/dist/br/data/mun/RR.js.map +1 -1
  142. package/dist/br/data/mun/RS.cjs +1 -1
  143. package/dist/br/data/mun/RS.cjs.map +1 -1
  144. package/dist/br/data/mun/RS.js +7579 -6424
  145. package/dist/br/data/mun/RS.js.map +1 -1
  146. package/dist/br/data/mun/SC.cjs +1 -1
  147. package/dist/br/data/mun/SC.cjs.map +1 -1
  148. package/dist/br/data/mun/SC.js +3979 -3357
  149. package/dist/br/data/mun/SC.js.map +1 -1
  150. package/dist/br/data/mun/SE.cjs +1 -1
  151. package/dist/br/data/mun/SE.cjs.map +1 -1
  152. package/dist/br/data/mun/SE.js +812 -625
  153. package/dist/br/data/mun/SE.js.map +1 -1
  154. package/dist/br/data/mun/SP.cjs +1 -1
  155. package/dist/br/data/mun/SP.cjs.map +1 -1
  156. package/dist/br/data/mun/SP.js +7866 -6803
  157. package/dist/br/data/mun/SP.js.map +1 -1
  158. package/dist/br/data/mun/TO.cjs +1 -1
  159. package/dist/br/data/mun/TO.cjs.map +1 -1
  160. package/dist/br/data/mun/TO.js +2763 -2817
  161. package/dist/br/data/mun/TO.js.map +1 -1
  162. package/dist/br/locations.cjs +1 -1
  163. package/dist/br/locations.cjs.map +1 -1
  164. package/dist/br/locations.js +97 -24
  165. package/dist/br/locations.js.map +1 -1
  166. package/dist/br/scales.cjs +1 -1
  167. package/dist/br/scales.cjs.map +1 -1
  168. package/dist/br/scales.js +8 -4
  169. package/dist/br/scales.js.map +1 -1
  170. package/dist/br/whatsapp.cjs +2 -0
  171. package/dist/br/whatsapp.cjs.map +1 -0
  172. package/dist/br/whatsapp.js +21 -0
  173. package/dist/br/whatsapp.js.map +1 -0
  174. package/dist/br.cjs +1 -1
  175. package/dist/br.cjs.map +1 -1
  176. package/dist/br.d.ts +188 -10
  177. package/dist/br.js +18 -17
  178. package/dist/br.js.map +1 -1
  179. package/dist/components/BottomSheet/BottomSheet.cjs +1 -1
  180. package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
  181. package/dist/components/BottomSheet/BottomSheet.js +30 -27
  182. package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
  183. package/dist/components/Command/Command.cjs +1 -1
  184. package/dist/components/Command/Command.cjs.map +1 -1
  185. package/dist/components/Command/Command.js +78 -75
  186. package/dist/components/Command/Command.js.map +1 -1
  187. package/dist/components/Drawer/Drawer.cjs +1 -1
  188. package/dist/components/Drawer/Drawer.cjs.map +1 -1
  189. package/dist/components/Drawer/Drawer.js +42 -39
  190. package/dist/components/Drawer/Drawer.js.map +1 -1
  191. package/dist/components/DropdownMenu/DropdownMenu.cjs +1 -1
  192. package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
  193. package/dist/components/DropdownMenu/DropdownMenu.js +111 -67
  194. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  195. package/dist/components/Modal/Modal.cjs +1 -1
  196. package/dist/components/Modal/Modal.cjs.map +1 -1
  197. package/dist/components/Modal/Modal.js +45 -42
  198. package/dist/components/Modal/Modal.js.map +1 -1
  199. package/dist/components/Money/Money.cjs +1 -1
  200. package/dist/components/Money/Money.cjs.map +1 -1
  201. package/dist/components/Money/Money.js +10 -9
  202. package/dist/components/Money/Money.js.map +1 -1
  203. package/dist/components/Pagination/Pagination.cjs +1 -1
  204. package/dist/components/Pagination/Pagination.cjs.map +1 -1
  205. package/dist/components/Pagination/Pagination.js +38 -31
  206. package/dist/components/Pagination/Pagination.js.map +1 -1
  207. package/dist/components/Pagination/Pagination.module.cjs +1 -1
  208. package/dist/components/Pagination/Pagination.module.cjs.map +1 -1
  209. package/dist/components/Pagination/Pagination.module.js +4 -4
  210. package/dist/components/Pagination/Pagination.module.js.map +1 -1
  211. package/dist/components/Portal/Portal.cjs +1 -1
  212. package/dist/components/Portal/Portal.cjs.map +1 -1
  213. package/dist/components/Portal/Portal.js +9 -6
  214. package/dist/components/Portal/Portal.js.map +1 -1
  215. package/dist/components/Portal/portal-host.cjs +2 -0
  216. package/dist/components/Portal/portal-host.cjs.map +1 -0
  217. package/dist/components/Portal/portal-host.js +11 -0
  218. package/dist/components/Portal/portal-host.js.map +1 -0
  219. package/dist/components/RangeSlider/RangeSlider.cjs +1 -1
  220. package/dist/components/RangeSlider/RangeSlider.cjs.map +1 -1
  221. package/dist/components/RangeSlider/RangeSlider.js +23 -23
  222. package/dist/components/RangeSlider/RangeSlider.js.map +1 -1
  223. package/dist/components/Scheduler/Scheduler.cjs +1 -1
  224. package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
  225. package/dist/components/Scheduler/Scheduler.js +83 -82
  226. package/dist/components/Scheduler/Scheduler.js.map +1 -1
  227. package/dist/components/Slider/Slider.cjs +1 -1
  228. package/dist/components/Slider/Slider.cjs.map +1 -1
  229. package/dist/components/Slider/Slider.js +9 -9
  230. package/dist/components/Slider/Slider.js.map +1 -1
  231. package/dist/components/Toast/ToastProvider.cjs +1 -1
  232. package/dist/components/Toast/ToastProvider.cjs.map +1 -1
  233. package/dist/components/Toast/ToastProvider.js +55 -53
  234. package/dist/components/Toast/ToastProvider.js.map +1 -1
  235. package/dist/components/VirtualList/VirtualList.cjs +1 -1
  236. package/dist/components/VirtualList/VirtualList.cjs.map +1 -1
  237. package/dist/components/VirtualList/VirtualList.js +41 -31
  238. package/dist/components/VirtualList/VirtualList.js.map +1 -1
  239. package/dist/components/VirtualTable/VirtualTable.cjs +1 -1
  240. package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -1
  241. package/dist/components/VirtualTable/VirtualTable.js +78 -68
  242. package/dist/components/VirtualTable/VirtualTable.js.map +1 -1
  243. package/dist/forms/FormField.cjs +1 -1
  244. package/dist/forms/FormField.cjs.map +1 -1
  245. package/dist/forms/FormField.js +23 -21
  246. package/dist/forms/FormField.js.map +1 -1
  247. package/dist/forms/masked-inputs.cjs +1 -1
  248. package/dist/forms/masked-inputs.cjs.map +1 -1
  249. package/dist/forms/masked-inputs.js +20 -19
  250. package/dist/forms/masked-inputs.js.map +1 -1
  251. package/dist/hooks/use-fullscreen-element.cjs +2 -0
  252. package/dist/hooks/use-fullscreen-element.cjs.map +1 -0
  253. package/dist/hooks/use-fullscreen-element.js +20 -0
  254. package/dist/hooks/use-fullscreen-element.js.map +1 -0
  255. package/dist/hooks/use-fullscreen.cjs +2 -0
  256. package/dist/hooks/use-fullscreen.cjs.map +1 -0
  257. package/dist/hooks/use-fullscreen.js +66 -0
  258. package/dist/hooks/use-fullscreen.js.map +1 -0
  259. package/dist/hooks/use-push-to-talk.cjs +2 -0
  260. package/dist/hooks/use-push-to-talk.cjs.map +1 -0
  261. package/dist/hooks/use-push-to-talk.js +61 -0
  262. package/dist/hooks/use-push-to-talk.js.map +1 -0
  263. package/dist/http/describe-api-error.cjs.map +1 -1
  264. package/dist/http/describe-api-error.js.map +1 -1
  265. package/dist/http/errors.cjs +1 -1
  266. package/dist/http/errors.cjs.map +1 -1
  267. package/dist/http/errors.js +34 -16
  268. package/dist/http/errors.js.map +1 -1
  269. package/dist/http/parse-response.cjs +2 -2
  270. package/dist/http/parse-response.cjs.map +1 -1
  271. package/dist/http/parse-response.js +9 -8
  272. package/dist/http/parse-response.js.map +1 -1
  273. package/dist/i18n/create-i18n.cjs +1 -1
  274. package/dist/i18n/create-i18n.cjs.map +1 -1
  275. package/dist/i18n/create-i18n.js +28 -27
  276. package/dist/i18n/create-i18n.js.map +1 -1
  277. package/dist/query/QueryProvider.cjs +1 -1
  278. package/dist/query/QueryProvider.cjs.map +1 -1
  279. package/dist/query/QueryProvider.js +13 -11
  280. package/dist/query/QueryProvider.js.map +1 -1
  281. package/dist/query/foreign-client-warning.cjs +2 -0
  282. package/dist/query/foreign-client-warning.cjs.map +1 -0
  283. package/dist/query/foreign-client-warning.js +12 -0
  284. package/dist/query/foreign-client-warning.js.map +1 -0
  285. package/dist/router/AppRouter.cjs +1 -1
  286. package/dist/router/AppRouter.cjs.map +1 -1
  287. package/dist/router/AppRouter.js +28 -21
  288. package/dist/router/AppRouter.js.map +1 -1
  289. package/dist/router/RouteGuard.cjs +1 -1
  290. package/dist/router/RouteGuard.cjs.map +1 -1
  291. package/dist/router/RouteGuard.js +4 -4
  292. package/dist/router/RouteGuard.js.map +1 -1
  293. package/dist/sse/create-event-stream.cjs +1 -1
  294. package/dist/sse/create-event-stream.cjs.map +1 -1
  295. package/dist/sse/create-event-stream.js +34 -38
  296. package/dist/sse/create-event-stream.js.map +1 -1
  297. package/dist/styles/AIChat.css +79 -0
  298. package/dist/styles/Accordion.css +12 -0
  299. package/dist/styles/Alert.css +32 -0
  300. package/dist/styles/AppBar.css +17 -0
  301. package/dist/styles/AppShell.css +8 -0
  302. package/dist/styles/AspectRatio.css +3 -0
  303. package/dist/styles/AudioPlayer.css +13 -0
  304. package/dist/styles/AudioRecorder.css +20 -0
  305. package/dist/styles/Avatar.css +13 -0
  306. package/dist/styles/AvatarGroup.css +18 -0
  307. package/dist/styles/Badge.css +25 -0
  308. package/dist/styles/Banner.css +13 -0
  309. package/dist/styles/BarList.css +10 -0
  310. package/dist/styles/BarcodeScanner.css +21 -0
  311. package/dist/styles/BottomNavigation.css +10 -0
  312. package/dist/styles/BottomSheet.css +11 -0
  313. package/dist/styles/BrazilMap.css +20 -0
  314. package/dist/styles/Breadcrumbs.css +8 -0
  315. package/dist/styles/Button.css +42 -0
  316. package/dist/styles/Calendar.css +16 -0
  317. package/dist/styles/Card.css +15 -0
  318. package/dist/styles/Carousel.css +17 -0
  319. package/dist/styles/Center.css +5 -0
  320. package/dist/styles/Chat.css +35 -0
  321. package/dist/styles/Checkbox.css +11 -0
  322. package/dist/styles/ChipInput.css +13 -0
  323. package/dist/styles/CodeBlock.css +25 -0
  324. package/dist/styles/Collapsible.css +6 -0
  325. package/dist/styles/Combobox.css +18 -0
  326. package/dist/styles/Command.css +20 -0
  327. package/dist/styles/ContextMenu.css +11 -0
  328. package/dist/styles/CopyButton.css +4 -0
  329. package/dist/styles/DataList.css +3 -0
  330. package/dist/styles/DataTable.css +19 -0
  331. package/dist/styles/DateRangePicker.css +22 -0
  332. package/dist/styles/DescriptionList.css +4 -0
  333. package/dist/styles/Divider.css +11 -0
  334. package/dist/styles/Drawer.css +22 -0
  335. package/dist/styles/DropdownMenu.css +16 -0
  336. package/dist/styles/Dropzone.css +7 -0
  337. package/dist/styles/EmptyState.css +6 -0
  338. package/dist/styles/ErrorState.css +6 -0
  339. package/dist/styles/ErrorText.css +2 -0
  340. package/dist/styles/FileUpload.css +17 -0
  341. package/dist/styles/FilterBar.css +19 -0
  342. package/dist/styles/FloatingActionButton.css +23 -0
  343. package/dist/styles/Form.css +21 -0
  344. package/dist/styles/HoverCard.css +11 -0
  345. package/dist/styles/IconPicker.css +6 -0
  346. package/dist/styles/Image.css +2 -0
  347. package/dist/styles/ImageCropper.css +16 -0
  348. package/dist/styles/Input.css +24 -0
  349. package/dist/styles/InstallBanner.css +8 -0
  350. package/dist/styles/Kanban.css +15 -0
  351. package/dist/styles/Kbd.css +5 -0
  352. package/dist/styles/Label.css +3 -0
  353. package/dist/styles/Layout.css +22 -0
  354. package/dist/styles/Lightbox.css +21 -0
  355. package/dist/styles/ListTile.css +14 -0
  356. package/dist/styles/MapLegend.css +9 -0
  357. package/dist/styles/Markdown.css +24 -0
  358. package/dist/styles/Masonry.css +4 -0
  359. package/dist/styles/Menubar.css +17 -0
  360. package/dist/styles/Modal.css +21 -0
  361. package/dist/styles/ModalsManager.css +2 -0
  362. package/dist/styles/MultiSelect.css +21 -0
  363. package/dist/styles/MunicipalitySearch.css +6 -0
  364. package/dist/styles/NProgress.css +2 -0
  365. package/dist/styles/Navbar.css +10 -0
  366. package/dist/styles/NavigationMenu.css +12 -0
  367. package/dist/styles/NavigationRail.css +14 -0
  368. package/dist/styles/NotificationCenter.css +21 -0
  369. package/dist/styles/OfflineIndicator.css +7 -0
  370. package/dist/styles/Page.css +13 -0
  371. package/dist/styles/Pagination.css +12 -0
  372. package/dist/styles/PasswordInput.css +21 -0
  373. package/dist/styles/PinInput.css +13 -0
  374. package/dist/styles/PixQRCode.css +9 -0
  375. package/dist/styles/Popover.css +10 -0
  376. package/dist/styles/Progress.css +11 -0
  377. package/dist/styles/QRCode.css +3 -0
  378. package/dist/styles/Radio.css +14 -0
  379. package/dist/styles/RangeSlider.css +15 -0
  380. package/dist/styles/RatingStars.css +11 -0
  381. package/dist/styles/RefreshIndicator.css +5 -0
  382. package/dist/styles/Resizable.css +13 -0
  383. package/dist/styles/RichTextEditor.css +19 -0
  384. package/dist/styles/SafeArea.css +7 -0
  385. package/dist/styles/Scheduler.css +24 -0
  386. package/dist/styles/ScrollArea.css +7 -0
  387. package/dist/styles/SearchBar.css +11 -0
  388. package/dist/styles/SegmentedControl.css +12 -0
  389. package/dist/styles/Select.css +14 -0
  390. package/dist/styles/Sidebar.css +19 -0
  391. package/dist/styles/SignaturePad.css +6 -0
  392. package/dist/styles/Skeleton.css +7 -0
  393. package/dist/styles/Slider.css +15 -0
  394. package/dist/styles/Spacer.css +4 -0
  395. package/dist/styles/Sparkline.css +4 -0
  396. package/dist/styles/Spinner.css +12 -0
  397. package/dist/styles/Stat.css +12 -0
  398. package/dist/styles/Stepper.css +17 -0
  399. package/dist/styles/StepperInput.css +15 -0
  400. package/dist/styles/Switch.css +11 -0
  401. package/dist/styles/SyncStatusBadge.css +13 -0
  402. package/dist/styles/Table.css +16 -0
  403. package/dist/styles/Tabs.css +14 -0
  404. package/dist/styles/Tag.css +14 -0
  405. package/dist/styles/Textarea.css +13 -0
  406. package/dist/styles/TimePicker.css +14 -0
  407. package/dist/styles/Timeline.css +17 -0
  408. package/dist/styles/Toast.css +21 -0
  409. package/dist/styles/Toggle.css +12 -0
  410. package/dist/styles/ToggleGroup.css +9 -0
  411. package/dist/styles/Tooltip.css +10 -0
  412. package/dist/styles/Tour.css +20 -0
  413. package/dist/styles/TrajectoryMap.css +16 -0
  414. package/dist/styles/Transfer.css +21 -0
  415. package/dist/styles/TreeView.css +17 -0
  416. package/dist/styles/TruncateText.css +2 -0
  417. package/dist/styles/UpdatePrompt.css +10 -0
  418. package/dist/styles/VirtualList.css +5 -0
  419. package/dist/styles/VirtualTable.css +17 -0
  420. package/dist/styles/VisuallyHidden.css +2 -0
  421. package/dist/styles/Wizard.css +4 -0
  422. package/dist/styles/actions.css +104 -0
  423. package/dist/styles/advanced.css +66 -0
  424. package/dist/styles/br.css +41 -0
  425. package/dist/styles/chat.css +113 -0
  426. package/dist/styles/core.css +40 -0
  427. package/dist/styles/data.css +222 -0
  428. package/dist/styles/editor.css +19 -0
  429. package/dist/styles/feedback.css +201 -0
  430. package/dist/styles/forms.css +321 -0
  431. package/dist/styles/geo.css +16 -0
  432. package/dist/styles/icons.css +6 -0
  433. package/dist/styles/identity.css +48 -0
  434. package/dist/styles/layout.css +122 -0
  435. package/dist/styles/media.css +52 -0
  436. package/dist/styles/navigation.css +167 -0
  437. package/dist/styles/overlay.css +73 -0
  438. package/dist/styles/utility.css +39 -0
  439. package/dist/styles.css +1 -1
  440. package/dist/sw/register-service-worker.cjs +1 -1
  441. package/dist/sw/register-service-worker.cjs.map +1 -1
  442. package/dist/sw/register-service-worker.js +21 -17
  443. package/dist/sw/register-service-worker.js.map +1 -1
  444. package/dist/sw.d.ts +17 -1
  445. package/dist/tempest-react-sdk.cjs +1 -1
  446. package/dist/tempest-react-sdk.d.ts +1382 -76
  447. package/dist/tempest-react-sdk.js +293 -281
  448. package/dist/utils/dev-mode.cjs.map +1 -1
  449. package/dist/utils/dev-mode.js.map +1 -1
  450. package/dist/utils/duplicate-instance.cjs +2 -0
  451. package/dist/utils/duplicate-instance.cjs.map +1 -0
  452. package/dist/utils/duplicate-instance.js +6 -0
  453. package/dist/utils/duplicate-instance.js.map +1 -0
  454. package/dist/utils/format.cjs +1 -1
  455. package/dist/utils/format.cjs.map +1 -1
  456. package/dist/utils/format.js +22 -14
  457. package/dist/utils/format.js.map +1 -1
  458. package/dist/utils/intl-cache.cjs +2 -0
  459. package/dist/utils/intl-cache.cjs.map +1 -0
  460. package/dist/utils/intl-cache.js +23 -0
  461. package/dist/utils/intl-cache.js.map +1 -0
  462. package/dist/utils/json-frame.cjs +2 -0
  463. package/dist/utils/json-frame.cjs.map +1 -0
  464. package/dist/utils/json-frame.js +30 -0
  465. package/dist/utils/json-frame.js.map +1 -0
  466. package/dist/utils/numbers.cjs +1 -1
  467. package/dist/utils/numbers.cjs.map +1 -1
  468. package/dist/utils/numbers.js +8 -7
  469. package/dist/utils/numbers.js.map +1 -1
  470. package/dist/vision/results.cjs +1 -1
  471. package/dist/vision/results.cjs.map +1 -1
  472. package/dist/vision/results.js +26 -17
  473. package/dist/vision/results.js.map +1 -1
  474. package/dist/vision.d.ts +29 -0
  475. package/dist/vite/tempest-icons.cjs +1 -1
  476. package/dist/vite/tempest-icons.cjs.map +1 -1
  477. package/dist/vite/tempest-icons.js +1 -1
  478. package/dist/vite/tempest-icons.js.map +1 -1
  479. package/dist/webrtc/link-stats.cjs +2 -0
  480. package/dist/webrtc/link-stats.cjs.map +1 -0
  481. package/dist/webrtc/link-stats.js +74 -0
  482. package/dist/webrtc/link-stats.js.map +1 -0
  483. package/dist/webrtc/opus-sdp.cjs +3 -0
  484. package/dist/webrtc/opus-sdp.cjs.map +1 -0
  485. package/dist/webrtc/opus-sdp.js +94 -0
  486. package/dist/webrtc/opus-sdp.js.map +1 -0
  487. package/dist/webrtc/sender-bitrate.cjs +2 -0
  488. package/dist/webrtc/sender-bitrate.cjs.map +1 -0
  489. package/dist/webrtc/sender-bitrate.js +15 -0
  490. package/dist/webrtc/sender-bitrate.js.map +1 -0
  491. package/dist/webrtc/use-link-stats.cjs +2 -0
  492. package/dist/webrtc/use-link-stats.cjs.map +1 -0
  493. package/dist/webrtc/use-link-stats.js +22 -0
  494. package/dist/webrtc/use-link-stats.js.map +1 -0
  495. package/dist/ws/create-web-socket.cjs +1 -1
  496. package/dist/ws/create-web-socket.cjs.map +1 -1
  497. package/dist/ws/create-web-socket.js +130 -58
  498. package/dist/ws/create-web-socket.js.map +1 -1
  499. package/dist/ws/resilience.cjs +2 -0
  500. package/dist/ws/resilience.cjs.map +1 -0
  501. package/dist/ws/resilience.js +21 -0
  502. package/dist/ws/resilience.js.map +1 -0
  503. package/dist/ws/use-web-socket.cjs +1 -1
  504. package/dist/ws/use-web-socket.cjs.map +1 -1
  505. package/dist/ws/use-web-socket.js +47 -39
  506. package/dist/ws/use-web-socket.js.map +1 -1
  507. package/package.json +5 -2
@@ -55,32 +55,41 @@ var e = {
55
55
  return [this.length];
56
56
  }
57
57
  get top1() {
58
- if (this.data.length === 0) return 0;
59
- let e = 0, t = this.data[0];
60
- for (let n = 1; n < this.data.length; n++) {
61
- let r = this.data[n];
62
- r > t && (e = n, t = r);
63
- }
64
- return e;
58
+ return this._top(1).indices[0] ?? 0;
65
59
  }
66
60
  get top1conf() {
67
- return this.data.length === 0 ? 0 : this.data[this.top1];
61
+ return this._top(1).values[0] ?? 0;
68
62
  }
69
63
  get top5() {
70
- return this._topK(5).indices;
64
+ return this._top(5).indices;
71
65
  }
72
66
  get top5conf() {
73
- return this._topK(5).values;
67
+ return this._top(5).values;
68
+ }
69
+ _cache = /* @__PURE__ */ new Map();
70
+ _top(e) {
71
+ let t = this._cache.get(e);
72
+ if (t) return t;
73
+ let n = this._topK(e);
74
+ return this._cache.set(e, n), n;
74
75
  }
75
76
  _topK(e) {
76
- let t = Math.min(e, this.data.length), n = [];
77
- for (let e = 0; e < this.data.length; e++) n.push(e);
78
- n.sort((e, t) => this.data[t] - this.data[e]);
79
- let r = new Int32Array(t), i = new Float32Array(t);
80
- for (let e = 0; e < t; e++) r[e] = n[e], i[e] = this.data[n[e]];
77
+ let t = Math.min(e, this.data.length), n = new Int32Array(t), r = new Float32Array(t);
78
+ if (t === 0) return {
79
+ indices: n,
80
+ values: r
81
+ };
82
+ r.fill(-Infinity);
83
+ for (let e = 0; e < this.data.length; e++) {
84
+ let i = this.data[e];
85
+ if (i <= r[t - 1]) continue;
86
+ let a = t - 1;
87
+ for (; a > 0 && r[a - 1] < i;) r[a] = r[a - 1], n[a] = n[a - 1], --a;
88
+ r[a] = i, n[a] = e;
89
+ }
81
90
  return {
82
- indices: r,
83
- values: i
91
+ indices: n,
92
+ values: r
84
93
  };
85
94
  }
86
95
  }, r = class {
@@ -1 +1 @@
1
- {"version":3,"file":"results.js","names":[],"sources":["../../src/vision/results.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Per-image result envelopes — Ultralytics-style `Results` for the SDK.\n *\n * Each `predict()` call returns a 1-element array of these envelopes,\n * mirroring `YOLO(\"img.jpg\")`. Each envelope holds:\n *\n * - A bulk-array view of the predictions (`Boxes`, `Probs`, `Masks`) with\n * the exact attribute names Ultralytics uses (`xyxy`, `xywh`, `xyxyn`,\n * `xywhn`, `cls`, `conf`, `data`, `top1`, `top5`).\n * - Per-instance dataclasses (`DetectionResult`, `SegmentationResult`,\n * `ClassProbability`) for callers who prefer the OO interface.\n * - `names`: `Record<number, string>` matching Ultralytics' `model.names`.\n * - `origImg` / `origShape` / `path`: provenance for the original input.\n * - `speed`: per-stage timings of the `predict()` call that produced it.\n */\n\nimport { type Speed } from \"./core/timing\";\nimport {\n type ClassificationResult,\n type DetectionResult,\n type RGBImage,\n type SegmentationResult,\n} from \"./types\";\n\n/**\n * Zero timings for an envelope built outside a `predict()` call.\n *\n * Hand-constructed envelopes (tests, adapters) have nothing to measure, and\n * zeros keep `speed.inference` a plain `number` at every call site instead of\n * forcing an optional check that only ever fires for synthetic data.\n */\nconst NO_SPEED: Speed = { load: 0, preprocess: 0, inference: 0, postprocess: 0 };\n\n/**\n * Bulk numpy-style view of detected boxes for a single image.\n *\n * Mirrors Ultralytics' `Boxes` interface. Coordinates in {@link xyxy} and\n * {@link xywh} are absolute pixels in the original image; the `*n` variants\n * are normalized to `[0, 1]` using `origShape`.\n */\nexport class Boxes {\n /**\n * @param xyxy Flat array of length `4 * N` in `[x1, y1, x2, y2, ...]` order.\n * @param cls One class index per box, length `N`.\n * @param conf One confidence per box, length `N`.\n * @param origShape `[height, width]` of the original image.\n */\n constructor(\n public readonly xyxy: Float32Array,\n public readonly cls: Int32Array,\n public readonly conf: Float32Array,\n public readonly origShape: readonly [number, number],\n ) {}\n\n /** Number of detected boxes. */\n get length(): number {\n return this.cls.length;\n }\n\n /** `[N, 4]` shape of the `xyxy` view. */\n get shape(): readonly [number, number] {\n return [this.length, 4];\n }\n\n /** Boxes as `[N, 4]` `[cx, cy, w, h]` flat array in absolute pixels. */\n get xywh(): Float32Array {\n const out = new Float32Array(this.xyxy.length);\n for (let i = 0; i < this.length; i++) {\n const x1 = this.xyxy[i * 4] as number;\n const y1 = this.xyxy[i * 4 + 1] as number;\n const x2 = this.xyxy[i * 4 + 2] as number;\n const y2 = this.xyxy[i * 4 + 3] as number;\n out[i * 4] = (x1 + x2) / 2;\n out[i * 4 + 1] = (y1 + y2) / 2;\n out[i * 4 + 2] = x2 - x1;\n out[i * 4 + 3] = y2 - y1;\n }\n return out;\n }\n\n /** Boxes as `[N, 4]` `[x1, y1, x2, y2]` normalized to `[0, 1]`. */\n get xyxyn(): Float32Array {\n const [h, w] = this.origShape;\n const out = new Float32Array(this.xyxy.length);\n if (this.length === 0 || w <= 0 || h <= 0) return out;\n for (let i = 0; i < this.length; i++) {\n out[i * 4] = (this.xyxy[i * 4] as number) / w;\n out[i * 4 + 1] = (this.xyxy[i * 4 + 1] as number) / h;\n out[i * 4 + 2] = (this.xyxy[i * 4 + 2] as number) / w;\n out[i * 4 + 3] = (this.xyxy[i * 4 + 3] as number) / h;\n }\n return out;\n }\n\n /** Boxes as `[N, 4]` `[cx, cy, w, h]` normalized to `[0, 1]`. */\n get xywhn(): Float32Array {\n const xywh = this.xywh;\n const [h, w] = this.origShape;\n if (this.length === 0 || w <= 0 || h <= 0) return xywh;\n for (let i = 0; i < this.length; i++) {\n xywh[i * 4] = (xywh[i * 4] as number) / w;\n xywh[i * 4 + 1] = (xywh[i * 4 + 1] as number) / h;\n xywh[i * 4 + 2] = (xywh[i * 4 + 2] as number) / w;\n xywh[i * 4 + 3] = (xywh[i * 4 + 3] as number) / h;\n }\n return xywh;\n }\n\n /**\n * Concatenated `[N, 6]` array of `[x1, y1, x2, y2, conf, cls]`.\n *\n * Matches Ultralytics' `boxes.data`.\n */\n get data(): Float32Array {\n const out = new Float32Array(this.length * 6);\n for (let i = 0; i < this.length; i++) {\n out[i * 6] = this.xyxy[i * 4] as number;\n out[i * 6 + 1] = this.xyxy[i * 4 + 1] as number;\n out[i * 6 + 2] = this.xyxy[i * 4 + 2] as number;\n out[i * 6 + 3] = this.xyxy[i * 4 + 3] as number;\n out[i * 6 + 4] = this.conf[i] as number;\n out[i * 6 + 5] = this.cls[i] as number;\n }\n return out;\n }\n}\n\n/**\n * Top-k classification probabilities for a single image.\n *\n * Mirrors Ultralytics' `Probs` interface.\n */\nexport class Probs {\n /** @param data `[numClasses]` per-class probabilities, indexed by class id. */\n constructor(public readonly data: Float32Array) {}\n\n /** Number of classes. */\n get length(): number {\n return this.data.length;\n }\n\n /** `[numClasses]` shape of the underlying vector. */\n get shape(): readonly [number] {\n return [this.length];\n }\n\n /** Index of the most probable class. */\n get top1(): number {\n if (this.data.length === 0) return 0;\n let best = 0;\n let bestVal = this.data[0] as number;\n for (let i = 1; i < this.data.length; i++) {\n const v = this.data[i] as number;\n if (v > bestVal) {\n best = i;\n bestVal = v;\n }\n }\n return best;\n }\n\n /** Probability of the top-1 class. */\n get top1conf(): number {\n if (this.data.length === 0) return 0;\n return this.data[this.top1] as number;\n }\n\n /** Indices of the top-5 most probable classes, descending. */\n get top5(): Int32Array {\n return this._topK(5).indices;\n }\n\n /** Probabilities of the top-5 classes, descending. */\n get top5conf(): Float32Array {\n return this._topK(5).values;\n }\n\n private _topK(k: number): { indices: Int32Array; values: Float32Array } {\n const n = Math.min(k, this.data.length);\n const order: number[] = [];\n for (let i = 0; i < this.data.length; i++) order.push(i);\n order.sort((a, b) => (this.data[b] as number) - (this.data[a] as number));\n const indices = new Int32Array(n);\n const values = new Float32Array(n);\n for (let i = 0; i < n; i++) {\n indices[i] = order[i] as number;\n values[i] = this.data[order[i] as number] as number;\n }\n return { indices, values };\n }\n}\n\n/**\n * Per-instance binary masks for a single image.\n *\n * Each mask is cropped to its instance's bounding box. To paint masks onto\n * a full-image canvas, use `xyxy[i]` as the top-left target.\n */\nexport class Masks {\n /**\n * @param data Per-instance binary masks (`Mask` objects from `types.ts`).\n * @param xyxy Flat `[N, 4]` of bounding-box coordinates in original pixels.\n * @param origShape `[height, width]` of the original image.\n */\n constructor(\n public readonly data: ReadonlyArray<{\n readonly data: Uint8Array;\n readonly width: number;\n readonly height: number;\n }>,\n public readonly xyxy: Float32Array,\n public readonly origShape: readonly [number, number],\n ) {}\n\n /** Number of instance masks. */\n get length(): number {\n return this.data.length;\n }\n\n /** `[N]` shape of the masks collection. */\n get shape(): readonly [number] {\n return [this.length];\n }\n\n [Symbol.iterator](): Iterator<{\n readonly data: Uint8Array;\n readonly width: number;\n readonly height: number;\n }> {\n return this.data[Symbol.iterator]();\n }\n}\n\n/**\n * Per-image detection envelope (Ultralytics-style `Results`).\n *\n * Iterating yields per-instance {@link DetectionResult} entries, so legacy\n * code that did `for (const d of detector.predict(img))` only needs an\n * extra `[0]` to bridge:\n *\n * ```typescript\n * for (const d of (await detector.predict(img))[0]) {\n * console.log(d.cls, d.conf, d.box.xyxy);\n * }\n * ```\n *\n * For numpy-style bulk access, use the `boxes` collection.\n */\nexport class DetectionResults implements Iterable<DetectionResult> {\n constructor(\n public readonly boxes: Boxes,\n public readonly detections: readonly DetectionResult[],\n public readonly names: Readonly<Record<number, string>>,\n public readonly origImg: RGBImage,\n public readonly origShape: readonly [number, number],\n public readonly path: string | null = null,\n public readonly speed: Readonly<Speed> = NO_SPEED,\n ) {}\n\n /** Number of surviving detections. */\n get length(): number {\n return this.detections.length;\n }\n\n /** Index into the per-instance detections. */\n get(index: number): DetectionResult | undefined {\n return this.detections[index];\n }\n\n [Symbol.iterator](): Iterator<DetectionResult> {\n return this.detections[Symbol.iterator]();\n }\n}\n\n/**\n * Per-image envelope for a fused detect→classify pipeline.\n *\n * Structurally a {@link DetectionResults} with a second class map: every\n * detection it yields carries a populated `classification`, and the two stages\n * have their own, unrelated label spaces — a detector that finds `sheep`\n * feeding a classifier that answers `famacha_3` shares no class ids with it.\n * Merging them into one `names` record would make `cls` and\n * `classification.cls` look comparable when they are not.\n *\n * ```typescript\n * const result = (await pipeline.predict(\"flock.jpg\"))[0];\n * for (const detection of result) {\n * console.log(detection.name, detection.conf, detection.classification?.name);\n * }\n * ```\n */\nexport class DetectClassifyResults implements Iterable<DetectionResult> {\n constructor(\n public readonly boxes: Boxes,\n public readonly detections: readonly DetectionResult[],\n public readonly names: Readonly<Record<number, string>>,\n public readonly classifierNames: Readonly<Record<number, string>>,\n public readonly origImg: RGBImage,\n public readonly origShape: readonly [number, number],\n public readonly path: string | null = null,\n public readonly speed: Readonly<Speed> = NO_SPEED,\n ) {}\n\n /** Number of surviving detections. */\n get length(): number {\n return this.detections.length;\n }\n\n /** Index into the per-instance detections. */\n get(index: number): DetectionResult | undefined {\n return this.detections[index];\n }\n\n [Symbol.iterator](): Iterator<DetectionResult> {\n return this.detections[Symbol.iterator]();\n }\n}\n\n/**\n * Per-image classification envelope (Ultralytics-style `Results`).\n */\nexport class ClassificationResults {\n constructor(\n public readonly probs: Probs,\n public readonly result: ClassificationResult,\n public readonly names: Readonly<Record<number, string>>,\n public readonly origImg: RGBImage,\n public readonly origShape: readonly [number, number],\n public readonly path: string | null = null,\n public readonly speed: Readonly<Speed> = NO_SPEED,\n ) {}\n\n /** Top-1 class index (Ultralytics-style alias). */\n get cls(): number {\n return this.probs.top1;\n }\n\n /** Top-1 confidence (Ultralytics-style alias). */\n get conf(): number {\n return this.probs.top1conf;\n }\n\n /** Top-1 class name. */\n get name(): string {\n return this.names[this.cls] ?? `class_${this.cls}`;\n }\n\n /** Per-class probability list, sorted descending (legacy field). */\n get probabilities(): readonly ClassificationResult[\"probabilities\"][number][] {\n return this.result.probabilities;\n }\n}\n\n/**\n * Per-image instance-segmentation envelope (Ultralytics-style `Results`).\n *\n * Iterating yields per-instance {@link SegmentationResult} entries. `boxes`\n * and `masks` mirror Ultralytics' bulk-array views.\n */\nexport class SegmentationResults implements Iterable<SegmentationResult> {\n constructor(\n public readonly boxes: Boxes,\n public readonly masks: Masks,\n public readonly detections: readonly SegmentationResult[],\n public readonly names: Readonly<Record<number, string>>,\n public readonly origImg: RGBImage,\n public readonly origShape: readonly [number, number],\n public readonly path: string | null = null,\n public readonly speed: Readonly<Speed> = NO_SPEED,\n ) {}\n\n /** Number of surviving instances. */\n get length(): number {\n return this.detections.length;\n }\n\n /** Index into the per-instance results. */\n get(index: number): SegmentationResult | undefined {\n return this.detections[index];\n }\n\n [Symbol.iterator](): Iterator<SegmentationResult> {\n return this.detections[Symbol.iterator]();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"results.js","names":[],"sources":["../../src/vision/results.ts"],"sourcesContent":["/** @generated Vendored from @mauriciobenjamin700/ort-vision-sdk-web. Do not hand-edit — regenerate with `npm run vendor:vision`. */\n/**\n * Per-image result envelopes — Ultralytics-style `Results` for the SDK.\n *\n * Each `predict()` call returns a 1-element array of these envelopes,\n * mirroring `YOLO(\"img.jpg\")`. Each envelope holds:\n *\n * - A bulk-array view of the predictions (`Boxes`, `Probs`, `Masks`) with\n * the exact attribute names Ultralytics uses (`xyxy`, `xywh`, `xyxyn`,\n * `xywhn`, `cls`, `conf`, `data`, `top1`, `top5`).\n * - Per-instance dataclasses (`DetectionResult`, `SegmentationResult`,\n * `ClassProbability`) for callers who prefer the OO interface.\n * - `names`: `Record<number, string>` matching Ultralytics' `model.names`.\n * - `origImg` / `origShape` / `path`: provenance for the original input.\n * - `speed`: per-stage timings of the `predict()` call that produced it.\n */\n\nimport { type Speed } from \"./core/timing\";\nimport {\n type ClassificationResult,\n type DetectionResult,\n type RGBImage,\n type SegmentationResult,\n} from \"./types\";\n\n/**\n * Zero timings for an envelope built outside a `predict()` call.\n *\n * Hand-constructed envelopes (tests, adapters) have nothing to measure, and\n * zeros keep `speed.inference` a plain `number` at every call site instead of\n * forcing an optional check that only ever fires for synthetic data.\n */\nconst NO_SPEED: Speed = { load: 0, preprocess: 0, inference: 0, postprocess: 0 };\n\n/**\n * Bulk numpy-style view of detected boxes for a single image.\n *\n * Mirrors Ultralytics' `Boxes` interface. Coordinates in {@link xyxy} and\n * {@link xywh} are absolute pixels in the original image; the `*n` variants\n * are normalized to `[0, 1]` using `origShape`.\n */\nexport class Boxes {\n /**\n * @param xyxy Flat array of length `4 * N` in `[x1, y1, x2, y2, ...]` order.\n * @param cls One class index per box, length `N`.\n * @param conf One confidence per box, length `N`.\n * @param origShape `[height, width]` of the original image.\n */\n constructor(\n public readonly xyxy: Float32Array,\n public readonly cls: Int32Array,\n public readonly conf: Float32Array,\n public readonly origShape: readonly [number, number],\n ) {}\n\n /** Number of detected boxes. */\n get length(): number {\n return this.cls.length;\n }\n\n /** `[N, 4]` shape of the `xyxy` view. */\n get shape(): readonly [number, number] {\n return [this.length, 4];\n }\n\n /** Boxes as `[N, 4]` `[cx, cy, w, h]` flat array in absolute pixels. */\n get xywh(): Float32Array {\n const out = new Float32Array(this.xyxy.length);\n for (let i = 0; i < this.length; i++) {\n const x1 = this.xyxy[i * 4] as number;\n const y1 = this.xyxy[i * 4 + 1] as number;\n const x2 = this.xyxy[i * 4 + 2] as number;\n const y2 = this.xyxy[i * 4 + 3] as number;\n out[i * 4] = (x1 + x2) / 2;\n out[i * 4 + 1] = (y1 + y2) / 2;\n out[i * 4 + 2] = x2 - x1;\n out[i * 4 + 3] = y2 - y1;\n }\n return out;\n }\n\n /** Boxes as `[N, 4]` `[x1, y1, x2, y2]` normalized to `[0, 1]`. */\n get xyxyn(): Float32Array {\n const [h, w] = this.origShape;\n const out = new Float32Array(this.xyxy.length);\n if (this.length === 0 || w <= 0 || h <= 0) return out;\n for (let i = 0; i < this.length; i++) {\n out[i * 4] = (this.xyxy[i * 4] as number) / w;\n out[i * 4 + 1] = (this.xyxy[i * 4 + 1] as number) / h;\n out[i * 4 + 2] = (this.xyxy[i * 4 + 2] as number) / w;\n out[i * 4 + 3] = (this.xyxy[i * 4 + 3] as number) / h;\n }\n return out;\n }\n\n /** Boxes as `[N, 4]` `[cx, cy, w, h]` normalized to `[0, 1]`. */\n get xywhn(): Float32Array {\n const xywh = this.xywh;\n const [h, w] = this.origShape;\n if (this.length === 0 || w <= 0 || h <= 0) return xywh;\n for (let i = 0; i < this.length; i++) {\n xywh[i * 4] = (xywh[i * 4] as number) / w;\n xywh[i * 4 + 1] = (xywh[i * 4 + 1] as number) / h;\n xywh[i * 4 + 2] = (xywh[i * 4 + 2] as number) / w;\n xywh[i * 4 + 3] = (xywh[i * 4 + 3] as number) / h;\n }\n return xywh;\n }\n\n /**\n * Concatenated `[N, 6]` array of `[x1, y1, x2, y2, conf, cls]`.\n *\n * Matches Ultralytics' `boxes.data`.\n */\n get data(): Float32Array {\n const out = new Float32Array(this.length * 6);\n for (let i = 0; i < this.length; i++) {\n out[i * 6] = this.xyxy[i * 4] as number;\n out[i * 6 + 1] = this.xyxy[i * 4 + 1] as number;\n out[i * 6 + 2] = this.xyxy[i * 4 + 2] as number;\n out[i * 6 + 3] = this.xyxy[i * 4 + 3] as number;\n out[i * 6 + 4] = this.conf[i] as number;\n out[i * 6 + 5] = this.cls[i] as number;\n }\n return out;\n }\n}\n\n/** One selection of the highest-probability classes, descending. */\ninterface TopK {\n indices: Int32Array;\n values: Float32Array;\n}\n\n/**\n * Top-k classification probabilities for a single image.\n *\n * Mirrors Ultralytics' `Probs` interface.\n */\nexport class Probs {\n /** @param data `[numClasses]` per-class probabilities, indexed by class id. */\n constructor(public readonly data: Float32Array) {}\n\n /** Number of classes. */\n get length(): number {\n return this.data.length;\n }\n\n /** `[numClasses]` shape of the underlying vector. */\n get shape(): readonly [number] {\n return [this.length];\n }\n\n /** Index of the most probable class. */\n get top1(): number {\n return this._top(1).indices[0] ?? 0;\n }\n\n /** Probability of the top-1 class. */\n get top1conf(): number {\n return this._top(1).values[0] ?? 0;\n }\n\n /** Indices of the top-5 most probable classes, descending. */\n get top5(): Int32Array {\n return this._top(5).indices;\n }\n\n /** Probabilities of the top-5 classes, descending. */\n get top5conf(): Float32Array {\n return this._top(5).values;\n }\n\n private _cache = new Map<number, TopK>();\n\n /**\n * Memoised {@link Probs._topK}.\n *\n * `top5` and `top5conf` are separate getters over the same selection, so a\n * caller reading both would otherwise pay for it twice — once per frame, in\n * a camera loop. The probabilities a `Probs` was built from do not change,\n * so the result is computed once per `k` and kept.\n *\n * @param k - How many classes to select.\n * @returns The cached selection for that `k`.\n */\n private _top(k: number): TopK {\n const hit = this._cache.get(k);\n if (hit) return hit;\n const computed = this._topK(k);\n this._cache.set(k, computed);\n return computed;\n }\n\n /**\n * Select the `k` highest probabilities without ordering the rest.\n *\n * A full sort to read five entries out of a thousand-class vector costs\n * O(n log n) plus an index array the size of the vector; keeping `k` slots\n * ordered by insertion and scanning once costs O(n·k) with no allocation\n * beyond the result. Measured on 1000 classes, that is 123.7 µs against\n * 1.9 µs for the same output.\n *\n * Ties keep the lower class index first, matching the stable sort this\n * replaced: a candidate only displaces an entry it is strictly greater\n * than.\n *\n * @param k - How many classes to select.\n * @returns Indices and probabilities, descending by probability.\n */\n private _topK(k: number): TopK {\n const size = Math.min(k, this.data.length);\n const indices = new Int32Array(size);\n const values = new Float32Array(size);\n if (size === 0) return { indices, values };\n\n values.fill(Number.NEGATIVE_INFINITY);\n for (let i = 0; i < this.data.length; i++) {\n const value = this.data[i] as number;\n if (value <= (values[size - 1] as number)) continue;\n let slot = size - 1;\n while (slot > 0 && (values[slot - 1] as number) < value) {\n values[slot] = values[slot - 1] as number;\n indices[slot] = indices[slot - 1] as number;\n slot -= 1;\n }\n values[slot] = value;\n indices[slot] = i;\n }\n return { indices, values };\n }\n}\n\n/**\n * Per-instance binary masks for a single image.\n *\n * Each mask is cropped to its instance's bounding box. To paint masks onto\n * a full-image canvas, use `xyxy[i]` as the top-left target.\n */\nexport class Masks {\n /**\n * @param data Per-instance binary masks (`Mask` objects from `types.ts`).\n * @param xyxy Flat `[N, 4]` of bounding-box coordinates in original pixels.\n * @param origShape `[height, width]` of the original image.\n */\n constructor(\n public readonly data: ReadonlyArray<{\n readonly data: Uint8Array;\n readonly width: number;\n readonly height: number;\n }>,\n public readonly xyxy: Float32Array,\n public readonly origShape: readonly [number, number],\n ) {}\n\n /** Number of instance masks. */\n get length(): number {\n return this.data.length;\n }\n\n /** `[N]` shape of the masks collection. */\n get shape(): readonly [number] {\n return [this.length];\n }\n\n [Symbol.iterator](): Iterator<{\n readonly data: Uint8Array;\n readonly width: number;\n readonly height: number;\n }> {\n return this.data[Symbol.iterator]();\n }\n}\n\n/**\n * Per-image detection envelope (Ultralytics-style `Results`).\n *\n * Iterating yields per-instance {@link DetectionResult} entries, so legacy\n * code that did `for (const d of detector.predict(img))` only needs an\n * extra `[0]` to bridge:\n *\n * ```typescript\n * for (const d of (await detector.predict(img))[0]) {\n * console.log(d.cls, d.conf, d.box.xyxy);\n * }\n * ```\n *\n * For numpy-style bulk access, use the `boxes` collection.\n */\nexport class DetectionResults implements Iterable<DetectionResult> {\n constructor(\n public readonly boxes: Boxes,\n public readonly detections: readonly DetectionResult[],\n public readonly names: Readonly<Record<number, string>>,\n public readonly origImg: RGBImage,\n public readonly origShape: readonly [number, number],\n public readonly path: string | null = null,\n public readonly speed: Readonly<Speed> = NO_SPEED,\n ) {}\n\n /** Number of surviving detections. */\n get length(): number {\n return this.detections.length;\n }\n\n /** Index into the per-instance detections. */\n get(index: number): DetectionResult | undefined {\n return this.detections[index];\n }\n\n [Symbol.iterator](): Iterator<DetectionResult> {\n return this.detections[Symbol.iterator]();\n }\n}\n\n/**\n * Per-image envelope for a fused detect→classify pipeline.\n *\n * Structurally a {@link DetectionResults} with a second class map: every\n * detection it yields carries a populated `classification`, and the two stages\n * have their own, unrelated label spaces — a detector that finds `sheep`\n * feeding a classifier that answers `famacha_3` shares no class ids with it.\n * Merging them into one `names` record would make `cls` and\n * `classification.cls` look comparable when they are not.\n *\n * ```typescript\n * const result = (await pipeline.predict(\"flock.jpg\"))[0];\n * for (const detection of result) {\n * console.log(detection.name, detection.conf, detection.classification?.name);\n * }\n * ```\n */\nexport class DetectClassifyResults implements Iterable<DetectionResult> {\n constructor(\n public readonly boxes: Boxes,\n public readonly detections: readonly DetectionResult[],\n public readonly names: Readonly<Record<number, string>>,\n public readonly classifierNames: Readonly<Record<number, string>>,\n public readonly origImg: RGBImage,\n public readonly origShape: readonly [number, number],\n public readonly path: string | null = null,\n public readonly speed: Readonly<Speed> = NO_SPEED,\n ) {}\n\n /** Number of surviving detections. */\n get length(): number {\n return this.detections.length;\n }\n\n /** Index into the per-instance detections. */\n get(index: number): DetectionResult | undefined {\n return this.detections[index];\n }\n\n [Symbol.iterator](): Iterator<DetectionResult> {\n return this.detections[Symbol.iterator]();\n }\n}\n\n/**\n * Per-image classification envelope (Ultralytics-style `Results`).\n */\nexport class ClassificationResults {\n constructor(\n public readonly probs: Probs,\n public readonly result: ClassificationResult,\n public readonly names: Readonly<Record<number, string>>,\n public readonly origImg: RGBImage,\n public readonly origShape: readonly [number, number],\n public readonly path: string | null = null,\n public readonly speed: Readonly<Speed> = NO_SPEED,\n ) {}\n\n /** Top-1 class index (Ultralytics-style alias). */\n get cls(): number {\n return this.probs.top1;\n }\n\n /** Top-1 confidence (Ultralytics-style alias). */\n get conf(): number {\n return this.probs.top1conf;\n }\n\n /** Top-1 class name. */\n get name(): string {\n return this.names[this.cls] ?? `class_${this.cls}`;\n }\n\n /** Per-class probability list, sorted descending (legacy field). */\n get probabilities(): readonly ClassificationResult[\"probabilities\"][number][] {\n return this.result.probabilities;\n }\n}\n\n/**\n * Per-image instance-segmentation envelope (Ultralytics-style `Results`).\n *\n * Iterating yields per-instance {@link SegmentationResult} entries. `boxes`\n * and `masks` mirror Ultralytics' bulk-array views.\n */\nexport class SegmentationResults implements Iterable<SegmentationResult> {\n constructor(\n public readonly boxes: Boxes,\n public readonly masks: Masks,\n public readonly detections: readonly SegmentationResult[],\n public readonly names: Readonly<Record<number, string>>,\n public readonly origImg: RGBImage,\n public readonly origShape: readonly [number, number],\n public readonly path: string | null = null,\n public readonly speed: Readonly<Speed> = NO_SPEED,\n ) {}\n\n /** Number of surviving instances. */\n get length(): number {\n return this.detections.length;\n }\n\n /** Index into the per-instance results. */\n get(index: number): SegmentationResult | undefined {\n return this.detections[index];\n }\n\n [Symbol.iterator](): Iterator<SegmentationResult> {\n return this.detections[Symbol.iterator]();\n }\n}\n"],"mappings":"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"}
package/dist/vision.d.ts CHANGED
@@ -1533,6 +1533,35 @@ export declare const DEFAULT_PROVIDERS: readonly string[];
1533
1533
  get top5(): Int32Array;
1534
1534
  /** Probabilities of the top-5 classes, descending. */
1535
1535
  get top5conf(): Float32Array;
1536
+ private _cache;
1537
+ /**
1538
+ * Memoised {@link Probs._topK}.
1539
+ *
1540
+ * `top5` and `top5conf` are separate getters over the same selection, so a
1541
+ * caller reading both would otherwise pay for it twice — once per frame, in
1542
+ * a camera loop. The probabilities a `Probs` was built from do not change,
1543
+ * so the result is computed once per `k` and kept.
1544
+ *
1545
+ * @param k - How many classes to select.
1546
+ * @returns The cached selection for that `k`.
1547
+ */
1548
+ private _top;
1549
+ /**
1550
+ * Select the `k` highest probabilities without ordering the rest.
1551
+ *
1552
+ * A full sort to read five entries out of a thousand-class vector costs
1553
+ * O(n log n) plus an index array the size of the vector; keeping `k` slots
1554
+ * ordered by insertion and scanning once costs O(n·k) with no allocation
1555
+ * beyond the result. Measured on 1000 classes, that is 123.7 µs against
1556
+ * 1.9 µs for the same output.
1557
+ *
1558
+ * Ties keep the lower class index first, matching the stable sort this
1559
+ * replaced: a candidate only displaces an entry it is strictly greater
1560
+ * than.
1561
+ *
1562
+ * @param k - How many classes to select.
1563
+ * @returns Indices and probabilities, descending by probability.
1564
+ */
1536
1565
  private _topK;
1537
1566
  }
1538
1567
 
@@ -5,5 +5,5 @@ export const staticIcons = {
5
5
  ${n.map(e=>` "${e}": ${m(r.get(e))},`).join(`
6
6
  `)}
7
7
  };
8
- `}function f(e){return e.split(`-`).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(``)}var p=new Set([`Infinity`,`NaN`,`undefined`,`eval`,`arguments`]);function m(e){return p.has(e)?`${e}Icon`:e}async function h(e,t){let i=[],a;try{a=await(0,r.readdir)(e,{withFileTypes:!0})}catch{return i}for(let r of a)if(r.isDirectory()){if(t.has(r.name)||r.name.startsWith(`.`))continue;i.push(...await h((0,n.join)(e,r.name),t))}else r.isFile()&&c.test(r.name)&&i.push((0,n.join)(e,r.name));return i}function g(i={}){let{dir:a=`src`,include:s=[],skipDirs:f}=i,p=new Set(t.iconNames),m=new Set(f??l),g=new Set(s.filter(e=>p.has(e))),_=process.cwd(),v=async()=>{let e=await h((0,n.resolve)(_,a),m);for(let t of e)try{let e=await(0,r.readFile)(t,`utf8`);for(let t of u(e,p))g.add(t)}catch{}};return{name:`tempest-icons`,enforce:`pre`,configResolved(e){_=e.root??_},async buildStart(){await v()},resolveId(e){return e===`tempest-react-sdk/icons/virtual`||e===`virtual:tempest-icons`?o:null},load(t){return t===o?d([...g],e.iconAliases):null},async handleHotUpdate(e){if(!c.test(e.file))return;let t=g.size;for(let t of u(await e.read(),p))g.add(t);if(g.size===t)return;let n=e.server.moduleGraph.getModuleById(o);if(n)return[n]}}}exports.TEMPEST_ICONS_ID=i,exports.TEMPEST_ICONS_VIRTUAL_ID=a,exports.buildIconsModule=d,exports.scanIconSlugs=u,exports.tempestIcons=g;
8
+ `}function f(e){return e.split(`-`).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(``)}var p=new Set([`Infinity`,`NaN`,`undefined`,`eval`,`arguments`]);function m(e){return p.has(e)?`${e}Icon`:e}async function h(e,t){let i=[],a;try{a=await(0,r.readdir)(e,{withFileTypes:!0})}catch{return i}for(let r of a)if(r.isDirectory()){if(t.has(r.name)||r.name.startsWith(`.`))continue;i.push(...await h((0,n.join)(e,r.name),t))}else r.isFile()&&c.test(r.name)&&i.push((0,n.join)(e,r.name));return i}function g(i={}){let{dir:a=`src`,include:s=[],skipDirs:f}=i,p=new Set(t.iconNames),m=new Set(f??l),g=new Set(s.filter(e=>p.has(e))),_=process.cwd(),v=async()=>{let e=await h((0,n.resolve)(_,a),m);for(let t of e)try{let e=await(0,r.readFile)(t,`utf8`);for(let t of u(e,p))g.add(t)}catch{}};return{name:`tempest-icons`,enforce:`pre`,configResolved(e){_=e.root??_},async buildStart(){await v()},resolveId(e){return e===`tempest-react-sdk/icons/virtual`||e===`virtual:tempest-icons`?o:null},load(t){return t===o?d([...g],e.iconAliases):null},async handleHotUpdate(e){if(!c.test(e.file))return;let t=g.size;for(let t of u(await e.read(),p))g.add(t);if(g.size===t)return;let n=e.server.moduleGraph.getModuleById(o);return n?[n]:void 0}}}exports.TEMPEST_ICONS_ID=i,exports.TEMPEST_ICONS_VIRTUAL_ID=a,exports.buildIconsModule=d,exports.scanIconSlugs=u,exports.tempestIcons=g;
9
9
  //# sourceMappingURL=tempest-icons.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"tempest-icons.cjs","names":[],"sources":["../../src/vite/tempest-icons.ts"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\n\nimport { iconAliases } from \"@/icons/generated/aliases\";\nimport { iconNames } from \"@/icons/generated/icon-names\";\n\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** The module id an app imports to get its static registry. */\nexport const TEMPEST_ICONS_ID = \"tempest-react-sdk/icons/virtual\";\n\n/**\n * The original virtual id, still honoured.\n *\n * Published as the documented import before the subpath existed, so apps have it\n * written in their entrypoints. It resolves to the same generated module — but it\n * only ever resolves *with the plugin*, which is the reason the subpath above\n * replaced it: that one also resolves without it, to an empty stub.\n */\nexport const TEMPEST_ICONS_VIRTUAL_ID = \"virtual:tempest-icons\";\n\nconst RESOLVED_ID = `\\0${TEMPEST_ICONS_VIRTUAL_ID}`;\n\n/**\n * Slugs written as a literal `name` prop or a literal `name:` field.\n *\n * Matching the *attribute* rather than any kebab-case string is what keeps the\n * scan from sweeping in unrelated text — a CSS class, a translation key and a\n * route segment all look like slugs otherwise. Everything matched is still\n * validated against lucide's real slug list before it reaches the module.\n */\nconst NAME_PATTERNS = [\n /\\bname\\s*=\\s*[\"']([a-z0-9-]+)[\"']/g,\n /\\bname\\s*=\\s*\\{\\s*[\"']([a-z0-9-]+)[\"']\\s*\\}/g,\n /\\bname\\s*:\\s*[\"']([a-z0-9-]+)[\"']/g,\n];\n\nconst SOURCE_FILE = /\\.[cm]?[jt]sx?$/;\nconst SKIP_DIRS = new Set([\"node_modules\", \"dist\", \"build\", \"coverage\"]);\n\nexport interface TempestIconsOptions {\n /**\n * Directory to scan, relative to the Vite root. Default: `\"src\"`.\n */\n dir?: string;\n /**\n * Extra slugs to include even when the scan cannot see them — a name built by\n * concatenation, or one that only ever arrives from the API but is worth\n * paying for statically because it is on the first screen.\n */\n include?: readonly string[];\n /** Directory names to skip. Default: `node_modules`, `dist`, `build`, `coverage`. */\n skipDirs?: readonly string[];\n}\n\n/**\n * Collect the icon slugs a source file references literally.\n *\n * @param code - File contents.\n * @param known - The set of real lucide slugs, aliases included.\n * @returns The slugs found, validated against `known`.\n */\nexport function scanIconSlugs(code: string, known: ReadonlySet<string>): string[] {\n const found = new Set<string>();\n for (const pattern of NAME_PATTERNS) {\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(code)) !== null) {\n if (known.has(match[1])) found.add(match[1]);\n }\n }\n return [...found];\n}\n\n/**\n * Build the virtual module source for a set of slugs.\n *\n * Aliases resolve to their canonical slug before importing, because only the\n * canonical name is a real lucide export — but the alias key still points at the\n * component, so `<Icon name=\"alert-circle\" />` stays a zero-request lookup.\n *\n * @param slugs - Validated slugs to include.\n * @param aliases - Alias slug → canonical slug.\n * @returns ES module source.\n */\nexport function buildIconsModule(\n slugs: readonly string[],\n aliases: Record<string, string>,\n): string {\n const sorted = [...new Set(slugs)].sort();\n if (!sorted.length) return \"export const staticIcons = {};\\n\";\n\n const componentOf = new Map<string, string>();\n for (const slug of sorted) componentOf.set(slug, pascalCase(aliases[slug] ?? slug));\n\n const named = [...new Set(componentOf.values())]\n .sort()\n .map((name) => (localName(name) === name ? name : `${name} as ${localName(name)}`))\n .join(\", \");\n const entries = sorted\n .map((slug) => ` \"${slug}\": ${localName(componentOf.get(slug) as string)},`)\n .join(\"\\n\");\n return `import { ${named} } from \"lucide-react\";\n\nexport const staticIcons = {\n${entries}\n};\n`;\n}\n\n/** `circle-alert` → `CircleAlert`; `a-arrow-down` → `AArrowDown`. */\nfunction pascalCase(slug: string): string {\n return slug\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\n/**\n * Export names that would shadow a restricted global if bound directly.\n *\n * Lucide ships an icon literally called `infinity`, whose export is `Infinity`.\n * Binding that at module scope shadows the global, so the import is aliased.\n */\nconst RESERVED_NAMES = new Set([\"Infinity\", \"NaN\", \"undefined\", \"eval\", \"arguments\"]);\n\n/** The local binding to use for a lucide export name. */\nfunction localName(name: string): string {\n return RESERVED_NAMES.has(name) ? `${name}Icon` : name;\n}\n\n/**\n * Walk a directory and collect every source file path.\n *\n * @param dir - Absolute directory to walk.\n * @param skip - Directory names to skip.\n * @returns Absolute file paths.\n */\nasync function collectSourceFiles(dir: string, skip: ReadonlySet<string>): Promise<string[]> {\n const out: string[] = [];\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return out;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (skip.has(entry.name) || entry.name.startsWith(\".\")) continue;\n out.push(...(await collectSourceFiles(join(dir, entry.name), skip)));\n } else if (entry.isFile() && SOURCE_FILE.test(entry.name)) {\n out.push(join(dir, entry.name));\n }\n }\n return out;\n}\n\n/**\n * Generate a static icon registry from the slugs your source mentions.\n *\n * Removes the reason to reach for `lucide-react`'s `DynamicIcon`: the slugs an app\n * writes as literals become ordinary static imports, so the bundler keeps exactly\n * those icons and the browser makes **no** extra request for them. A slug that only\n * exists at runtime still works — `<Icon>` falls back to fetching one shard per\n * initial letter.\n *\n * The scan reads the source tree directly at `buildStart` rather than harvesting\n * modules as they pass through `transform`: relying on transform order would let\n * the virtual module load before every consumer had been seen, and the registry\n * would come out short on a cold dev start — silently, since the missing slugs\n * would still render via the lazy path.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestIcons } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({ plugins: [tempestIcons()] });\n *\n * @example\n * // src/main.tsx\n * import { IconProvider } from \"tempest-react-sdk/icons\";\n * import { staticIcons } from \"tempest-react-sdk/icons/virtual\";\n *\n * <IconProvider registry={staticIcons}>\n * <App />\n * </IconProvider>\n */\nexport function tempestIcons(options: TempestIconsOptions = {}): TempestVitePlugin {\n const { dir = \"src\", include = [], skipDirs } = options;\n const known = new Set<string>(iconNames);\n const skip = new Set<string>(skipDirs ?? SKIP_DIRS);\n const slugs = new Set<string>(include.filter((slug) => known.has(slug)));\n let root = process.cwd();\n\n /**\n * Rescan the source tree, keeping the explicitly included slugs.\n *\n * @tempest-limits empty-catch — the scan races the editor and the file system:\n * a file listed a moment ago can be renamed, deleted, or held by another process\n * by the time it is read. An unreadable file contributes no slugs and the build\n * continues; failing the whole scan would break `vite dev` over a temp file that\n * no longer exists.\n */\n const rescan = async (): Promise<void> => {\n const files = await collectSourceFiles(resolve(root, dir), skip);\n for (const file of files) {\n try {\n const code = await readFile(file, \"utf8\");\n for (const slug of scanIconSlugs(code, known)) slugs.add(slug);\n } catch {\n /* empty */\n }\n }\n };\n\n return {\n name: \"tempest-icons\",\n enforce: \"pre\",\n\n configResolved(config: { root?: string }) {\n root = config.root ?? root;\n },\n\n async buildStart() {\n await rescan();\n },\n\n /**\n * Claim both spellings of the registry module.\n *\n * The subpath is a real file in the installed package, so returning the\n * virtual id here is what overrides the empty stub with the generated\n * table. Letting Vite resolve it normally would silently ship the stub.\n */\n resolveId(id: string) {\n return id === TEMPEST_ICONS_ID || id === TEMPEST_ICONS_VIRTUAL_ID ? RESOLVED_ID : null;\n },\n\n load(id: string) {\n return id === RESOLVED_ID ? buildIconsModule([...slugs], iconAliases) : null;\n },\n\n /**\n * Pick up a slug added while the dev server is running.\n *\n * A newly written `<Icon name=\"save\" />` would otherwise only render via\n * the lazy path until the next restart. Rescanning the changed file and\n * invalidating the virtual module keeps dev and build agreeing on what the\n * static registry contains.\n */\n async handleHotUpdate(ctx: {\n file: string;\n read: () => Promise<string>;\n server: { moduleGraph: { getModuleById: (id: string) => unknown } };\n }) {\n if (!SOURCE_FILE.test(ctx.file)) return;\n const before = slugs.size;\n for (const slug of scanIconSlugs(await ctx.read(), known)) slugs.add(slug);\n if (slugs.size === before) return;\n const mod = ctx.server.moduleGraph.getModuleById(RESOLVED_ID);\n if (mod) return [mod];\n },\n } as TempestVitePlugin;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tempest-icons.cjs","names":[],"sources":["../../src/vite/tempest-icons.ts"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\n\nimport { iconAliases } from \"@/icons/generated/aliases\";\nimport { iconNames } from \"@/icons/generated/icon-names\";\n\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** The module id an app imports to get its static registry. */\nexport const TEMPEST_ICONS_ID = \"tempest-react-sdk/icons/virtual\";\n\n/**\n * The original virtual id, still honoured.\n *\n * Published as the documented import before the subpath existed, so apps have it\n * written in their entrypoints. It resolves to the same generated module — but it\n * only ever resolves *with the plugin*, which is the reason the subpath above\n * replaced it: that one also resolves without it, to an empty stub.\n */\nexport const TEMPEST_ICONS_VIRTUAL_ID = \"virtual:tempest-icons\";\n\nconst RESOLVED_ID = `\\0${TEMPEST_ICONS_VIRTUAL_ID}`;\n\n/**\n * Slugs written as a literal `name` prop or a literal `name:` field.\n *\n * Matching the *attribute* rather than any kebab-case string is what keeps the\n * scan from sweeping in unrelated text — a CSS class, a translation key and a\n * route segment all look like slugs otherwise. Everything matched is still\n * validated against lucide's real slug list before it reaches the module.\n */\nconst NAME_PATTERNS = [\n /\\bname\\s*=\\s*[\"']([a-z0-9-]+)[\"']/g,\n /\\bname\\s*=\\s*\\{\\s*[\"']([a-z0-9-]+)[\"']\\s*\\}/g,\n /\\bname\\s*:\\s*[\"']([a-z0-9-]+)[\"']/g,\n];\n\nconst SOURCE_FILE = /\\.[cm]?[jt]sx?$/;\nconst SKIP_DIRS = new Set([\"node_modules\", \"dist\", \"build\", \"coverage\"]);\n\nexport interface TempestIconsOptions {\n /**\n * Directory to scan, relative to the Vite root. Default: `\"src\"`.\n */\n dir?: string;\n /**\n * Extra slugs to include even when the scan cannot see them — a name built by\n * concatenation, or one that only ever arrives from the API but is worth\n * paying for statically because it is on the first screen.\n */\n include?: readonly string[];\n /** Directory names to skip. Default: `node_modules`, `dist`, `build`, `coverage`. */\n skipDirs?: readonly string[];\n}\n\n/**\n * Collect the icon slugs a source file references literally.\n *\n * @param code - File contents.\n * @param known - The set of real lucide slugs, aliases included.\n * @returns The slugs found, validated against `known`.\n */\nexport function scanIconSlugs(code: string, known: ReadonlySet<string>): string[] {\n const found = new Set<string>();\n for (const pattern of NAME_PATTERNS) {\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(code)) !== null) {\n if (known.has(match[1])) found.add(match[1]);\n }\n }\n return [...found];\n}\n\n/**\n * Build the virtual module source for a set of slugs.\n *\n * Aliases resolve to their canonical slug before importing, because only the\n * canonical name is a real lucide export — but the alias key still points at the\n * component, so `<Icon name=\"alert-circle\" />` stays a zero-request lookup.\n *\n * @param slugs - Validated slugs to include.\n * @param aliases - Alias slug → canonical slug.\n * @returns ES module source.\n */\nexport function buildIconsModule(\n slugs: readonly string[],\n aliases: Record<string, string>,\n): string {\n const sorted = [...new Set(slugs)].sort();\n if (!sorted.length) return \"export const staticIcons = {};\\n\";\n\n const componentOf = new Map<string, string>();\n for (const slug of sorted) componentOf.set(slug, pascalCase(aliases[slug] ?? slug));\n\n const named = [...new Set(componentOf.values())]\n .sort()\n .map((name) => (localName(name) === name ? name : `${name} as ${localName(name)}`))\n .join(\", \");\n const entries = sorted\n .map((slug) => ` \"${slug}\": ${localName(componentOf.get(slug) as string)},`)\n .join(\"\\n\");\n return `import { ${named} } from \"lucide-react\";\n\nexport const staticIcons = {\n${entries}\n};\n`;\n}\n\n/** `circle-alert` → `CircleAlert`; `a-arrow-down` → `AArrowDown`. */\nfunction pascalCase(slug: string): string {\n return slug\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\n/**\n * Export names that would shadow a restricted global if bound directly.\n *\n * Lucide ships an icon literally called `infinity`, whose export is `Infinity`.\n * Binding that at module scope shadows the global, so the import is aliased.\n */\nconst RESERVED_NAMES = new Set([\"Infinity\", \"NaN\", \"undefined\", \"eval\", \"arguments\"]);\n\n/** The local binding to use for a lucide export name. */\nfunction localName(name: string): string {\n return RESERVED_NAMES.has(name) ? `${name}Icon` : name;\n}\n\n/**\n * Walk a directory and collect every source file path.\n *\n * @param dir - Absolute directory to walk.\n * @param skip - Directory names to skip.\n * @returns Absolute file paths.\n */\nasync function collectSourceFiles(dir: string, skip: ReadonlySet<string>): Promise<string[]> {\n const out: string[] = [];\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return out;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (skip.has(entry.name) || entry.name.startsWith(\".\")) continue;\n out.push(...(await collectSourceFiles(join(dir, entry.name), skip)));\n } else if (entry.isFile() && SOURCE_FILE.test(entry.name)) {\n out.push(join(dir, entry.name));\n }\n }\n return out;\n}\n\n/**\n * Generate a static icon registry from the slugs your source mentions.\n *\n * Removes the reason to reach for `lucide-react`'s `DynamicIcon`: the slugs an app\n * writes as literals become ordinary static imports, so the bundler keeps exactly\n * those icons and the browser makes **no** extra request for them. A slug that only\n * exists at runtime still works — `<Icon>` falls back to fetching one shard per\n * initial letter.\n *\n * The scan reads the source tree directly at `buildStart` rather than harvesting\n * modules as they pass through `transform`: relying on transform order would let\n * the virtual module load before every consumer had been seen, and the registry\n * would come out short on a cold dev start — silently, since the missing slugs\n * would still render via the lazy path.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestIcons } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({ plugins: [tempestIcons()] });\n *\n * @example\n * // src/main.tsx\n * import { IconProvider } from \"tempest-react-sdk/icons\";\n * import { staticIcons } from \"tempest-react-sdk/icons/virtual\";\n *\n * <IconProvider registry={staticIcons}>\n * <App />\n * </IconProvider>\n */\nexport function tempestIcons(options: TempestIconsOptions = {}): TempestVitePlugin {\n const { dir = \"src\", include = [], skipDirs } = options;\n const known = new Set<string>(iconNames);\n const skip = new Set<string>(skipDirs ?? SKIP_DIRS);\n const slugs = new Set<string>(include.filter((slug) => known.has(slug)));\n let root = process.cwd();\n\n /**\n * Rescan the source tree, keeping the explicitly included slugs.\n *\n * @tempest-limits empty-catch — the scan races the editor and the file system:\n * a file listed a moment ago can be renamed, deleted, or held by another process\n * by the time it is read. An unreadable file contributes no slugs and the build\n * continues; failing the whole scan would break `vite dev` over a temp file that\n * no longer exists.\n */\n const rescan = async (): Promise<void> => {\n const files = await collectSourceFiles(resolve(root, dir), skip);\n for (const file of files) {\n try {\n const code = await readFile(file, \"utf8\");\n for (const slug of scanIconSlugs(code, known)) slugs.add(slug);\n } catch {\n /* empty */\n }\n }\n };\n\n return {\n name: \"tempest-icons\",\n enforce: \"pre\",\n\n configResolved(config: { root?: string }) {\n root = config.root ?? root;\n },\n\n async buildStart() {\n await rescan();\n },\n\n /**\n * Claim both spellings of the registry module.\n *\n * The subpath is a real file in the installed package, so returning the\n * virtual id here is what overrides the empty stub with the generated\n * table. Letting Vite resolve it normally would silently ship the stub.\n */\n resolveId(id: string) {\n return id === TEMPEST_ICONS_ID || id === TEMPEST_ICONS_VIRTUAL_ID ? RESOLVED_ID : null;\n },\n\n load(id: string) {\n return id === RESOLVED_ID ? buildIconsModule([...slugs], iconAliases) : null;\n },\n\n /**\n * Pick up a slug added while the dev server is running.\n *\n * A newly written `<Icon name=\"save\" />` would otherwise only render via\n * the lazy path until the next restart. Rescanning the changed file and\n * invalidating the virtual module keeps dev and build agreeing on what the\n * static registry contains.\n */\n async handleHotUpdate(ctx: {\n file: string;\n read: () => Promise<string>;\n server: { moduleGraph: { getModuleById: (id: string) => unknown } };\n }): Promise<unknown[] | undefined> {\n if (!SOURCE_FILE.test(ctx.file)) return undefined;\n const before = slugs.size;\n for (const slug of scanIconSlugs(await ctx.read(), known)) slugs.add(slug);\n if (slugs.size === before) return undefined;\n const mod = ctx.server.moduleGraph.getModuleById(RESOLVED_ID);\n return mod ? [mod] : undefined;\n },\n } as TempestVitePlugin;\n}\n"],"mappings":"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"}
@@ -89,7 +89,7 @@ function v(n = {}) {
89
89
  for (let t of f(await e.read(), l)) h.add(t);
90
90
  if (h.size === t) return;
91
91
  let n = e.server.moduleGraph.getModuleById(c);
92
- if (n) return [n];
92
+ return n ? [n] : void 0;
93
93
  }
94
94
  };
95
95
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tempest-icons.js","names":[],"sources":["../../src/vite/tempest-icons.ts"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\n\nimport { iconAliases } from \"@/icons/generated/aliases\";\nimport { iconNames } from \"@/icons/generated/icon-names\";\n\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** The module id an app imports to get its static registry. */\nexport const TEMPEST_ICONS_ID = \"tempest-react-sdk/icons/virtual\";\n\n/**\n * The original virtual id, still honoured.\n *\n * Published as the documented import before the subpath existed, so apps have it\n * written in their entrypoints. It resolves to the same generated module — but it\n * only ever resolves *with the plugin*, which is the reason the subpath above\n * replaced it: that one also resolves without it, to an empty stub.\n */\nexport const TEMPEST_ICONS_VIRTUAL_ID = \"virtual:tempest-icons\";\n\nconst RESOLVED_ID = `\\0${TEMPEST_ICONS_VIRTUAL_ID}`;\n\n/**\n * Slugs written as a literal `name` prop or a literal `name:` field.\n *\n * Matching the *attribute* rather than any kebab-case string is what keeps the\n * scan from sweeping in unrelated text — a CSS class, a translation key and a\n * route segment all look like slugs otherwise. Everything matched is still\n * validated against lucide's real slug list before it reaches the module.\n */\nconst NAME_PATTERNS = [\n /\\bname\\s*=\\s*[\"']([a-z0-9-]+)[\"']/g,\n /\\bname\\s*=\\s*\\{\\s*[\"']([a-z0-9-]+)[\"']\\s*\\}/g,\n /\\bname\\s*:\\s*[\"']([a-z0-9-]+)[\"']/g,\n];\n\nconst SOURCE_FILE = /\\.[cm]?[jt]sx?$/;\nconst SKIP_DIRS = new Set([\"node_modules\", \"dist\", \"build\", \"coverage\"]);\n\nexport interface TempestIconsOptions {\n /**\n * Directory to scan, relative to the Vite root. Default: `\"src\"`.\n */\n dir?: string;\n /**\n * Extra slugs to include even when the scan cannot see them — a name built by\n * concatenation, or one that only ever arrives from the API but is worth\n * paying for statically because it is on the first screen.\n */\n include?: readonly string[];\n /** Directory names to skip. Default: `node_modules`, `dist`, `build`, `coverage`. */\n skipDirs?: readonly string[];\n}\n\n/**\n * Collect the icon slugs a source file references literally.\n *\n * @param code - File contents.\n * @param known - The set of real lucide slugs, aliases included.\n * @returns The slugs found, validated against `known`.\n */\nexport function scanIconSlugs(code: string, known: ReadonlySet<string>): string[] {\n const found = new Set<string>();\n for (const pattern of NAME_PATTERNS) {\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(code)) !== null) {\n if (known.has(match[1])) found.add(match[1]);\n }\n }\n return [...found];\n}\n\n/**\n * Build the virtual module source for a set of slugs.\n *\n * Aliases resolve to their canonical slug before importing, because only the\n * canonical name is a real lucide export — but the alias key still points at the\n * component, so `<Icon name=\"alert-circle\" />` stays a zero-request lookup.\n *\n * @param slugs - Validated slugs to include.\n * @param aliases - Alias slug → canonical slug.\n * @returns ES module source.\n */\nexport function buildIconsModule(\n slugs: readonly string[],\n aliases: Record<string, string>,\n): string {\n const sorted = [...new Set(slugs)].sort();\n if (!sorted.length) return \"export const staticIcons = {};\\n\";\n\n const componentOf = new Map<string, string>();\n for (const slug of sorted) componentOf.set(slug, pascalCase(aliases[slug] ?? slug));\n\n const named = [...new Set(componentOf.values())]\n .sort()\n .map((name) => (localName(name) === name ? name : `${name} as ${localName(name)}`))\n .join(\", \");\n const entries = sorted\n .map((slug) => ` \"${slug}\": ${localName(componentOf.get(slug) as string)},`)\n .join(\"\\n\");\n return `import { ${named} } from \"lucide-react\";\n\nexport const staticIcons = {\n${entries}\n};\n`;\n}\n\n/** `circle-alert` → `CircleAlert`; `a-arrow-down` → `AArrowDown`. */\nfunction pascalCase(slug: string): string {\n return slug\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\n/**\n * Export names that would shadow a restricted global if bound directly.\n *\n * Lucide ships an icon literally called `infinity`, whose export is `Infinity`.\n * Binding that at module scope shadows the global, so the import is aliased.\n */\nconst RESERVED_NAMES = new Set([\"Infinity\", \"NaN\", \"undefined\", \"eval\", \"arguments\"]);\n\n/** The local binding to use for a lucide export name. */\nfunction localName(name: string): string {\n return RESERVED_NAMES.has(name) ? `${name}Icon` : name;\n}\n\n/**\n * Walk a directory and collect every source file path.\n *\n * @param dir - Absolute directory to walk.\n * @param skip - Directory names to skip.\n * @returns Absolute file paths.\n */\nasync function collectSourceFiles(dir: string, skip: ReadonlySet<string>): Promise<string[]> {\n const out: string[] = [];\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return out;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (skip.has(entry.name) || entry.name.startsWith(\".\")) continue;\n out.push(...(await collectSourceFiles(join(dir, entry.name), skip)));\n } else if (entry.isFile() && SOURCE_FILE.test(entry.name)) {\n out.push(join(dir, entry.name));\n }\n }\n return out;\n}\n\n/**\n * Generate a static icon registry from the slugs your source mentions.\n *\n * Removes the reason to reach for `lucide-react`'s `DynamicIcon`: the slugs an app\n * writes as literals become ordinary static imports, so the bundler keeps exactly\n * those icons and the browser makes **no** extra request for them. A slug that only\n * exists at runtime still works — `<Icon>` falls back to fetching one shard per\n * initial letter.\n *\n * The scan reads the source tree directly at `buildStart` rather than harvesting\n * modules as they pass through `transform`: relying on transform order would let\n * the virtual module load before every consumer had been seen, and the registry\n * would come out short on a cold dev start — silently, since the missing slugs\n * would still render via the lazy path.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestIcons } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({ plugins: [tempestIcons()] });\n *\n * @example\n * // src/main.tsx\n * import { IconProvider } from \"tempest-react-sdk/icons\";\n * import { staticIcons } from \"tempest-react-sdk/icons/virtual\";\n *\n * <IconProvider registry={staticIcons}>\n * <App />\n * </IconProvider>\n */\nexport function tempestIcons(options: TempestIconsOptions = {}): TempestVitePlugin {\n const { dir = \"src\", include = [], skipDirs } = options;\n const known = new Set<string>(iconNames);\n const skip = new Set<string>(skipDirs ?? SKIP_DIRS);\n const slugs = new Set<string>(include.filter((slug) => known.has(slug)));\n let root = process.cwd();\n\n /**\n * Rescan the source tree, keeping the explicitly included slugs.\n *\n * @tempest-limits empty-catch — the scan races the editor and the file system:\n * a file listed a moment ago can be renamed, deleted, or held by another process\n * by the time it is read. An unreadable file contributes no slugs and the build\n * continues; failing the whole scan would break `vite dev` over a temp file that\n * no longer exists.\n */\n const rescan = async (): Promise<void> => {\n const files = await collectSourceFiles(resolve(root, dir), skip);\n for (const file of files) {\n try {\n const code = await readFile(file, \"utf8\");\n for (const slug of scanIconSlugs(code, known)) slugs.add(slug);\n } catch {\n /* empty */\n }\n }\n };\n\n return {\n name: \"tempest-icons\",\n enforce: \"pre\",\n\n configResolved(config: { root?: string }) {\n root = config.root ?? root;\n },\n\n async buildStart() {\n await rescan();\n },\n\n /**\n * Claim both spellings of the registry module.\n *\n * The subpath is a real file in the installed package, so returning the\n * virtual id here is what overrides the empty stub with the generated\n * table. Letting Vite resolve it normally would silently ship the stub.\n */\n resolveId(id: string) {\n return id === TEMPEST_ICONS_ID || id === TEMPEST_ICONS_VIRTUAL_ID ? RESOLVED_ID : null;\n },\n\n load(id: string) {\n return id === RESOLVED_ID ? buildIconsModule([...slugs], iconAliases) : null;\n },\n\n /**\n * Pick up a slug added while the dev server is running.\n *\n * A newly written `<Icon name=\"save\" />` would otherwise only render via\n * the lazy path until the next restart. Rescanning the changed file and\n * invalidating the virtual module keeps dev and build agreeing on what the\n * static registry contains.\n */\n async handleHotUpdate(ctx: {\n file: string;\n read: () => Promise<string>;\n server: { moduleGraph: { getModuleById: (id: string) => unknown } };\n }) {\n if (!SOURCE_FILE.test(ctx.file)) return;\n const before = slugs.size;\n for (const slug of scanIconSlugs(await ctx.read(), known)) slugs.add(slug);\n if (slugs.size === before) return;\n const mod = ctx.server.moduleGraph.getModuleById(RESOLVED_ID);\n if (mod) return [mod];\n },\n } as TempestVitePlugin;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tempest-icons.js","names":[],"sources":["../../src/vite/tempest-icons.ts"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\n\nimport { iconAliases } from \"@/icons/generated/aliases\";\nimport { iconNames } from \"@/icons/generated/icon-names\";\n\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** The module id an app imports to get its static registry. */\nexport const TEMPEST_ICONS_ID = \"tempest-react-sdk/icons/virtual\";\n\n/**\n * The original virtual id, still honoured.\n *\n * Published as the documented import before the subpath existed, so apps have it\n * written in their entrypoints. It resolves to the same generated module — but it\n * only ever resolves *with the plugin*, which is the reason the subpath above\n * replaced it: that one also resolves without it, to an empty stub.\n */\nexport const TEMPEST_ICONS_VIRTUAL_ID = \"virtual:tempest-icons\";\n\nconst RESOLVED_ID = `\\0${TEMPEST_ICONS_VIRTUAL_ID}`;\n\n/**\n * Slugs written as a literal `name` prop or a literal `name:` field.\n *\n * Matching the *attribute* rather than any kebab-case string is what keeps the\n * scan from sweeping in unrelated text — a CSS class, a translation key and a\n * route segment all look like slugs otherwise. Everything matched is still\n * validated against lucide's real slug list before it reaches the module.\n */\nconst NAME_PATTERNS = [\n /\\bname\\s*=\\s*[\"']([a-z0-9-]+)[\"']/g,\n /\\bname\\s*=\\s*\\{\\s*[\"']([a-z0-9-]+)[\"']\\s*\\}/g,\n /\\bname\\s*:\\s*[\"']([a-z0-9-]+)[\"']/g,\n];\n\nconst SOURCE_FILE = /\\.[cm]?[jt]sx?$/;\nconst SKIP_DIRS = new Set([\"node_modules\", \"dist\", \"build\", \"coverage\"]);\n\nexport interface TempestIconsOptions {\n /**\n * Directory to scan, relative to the Vite root. Default: `\"src\"`.\n */\n dir?: string;\n /**\n * Extra slugs to include even when the scan cannot see them — a name built by\n * concatenation, or one that only ever arrives from the API but is worth\n * paying for statically because it is on the first screen.\n */\n include?: readonly string[];\n /** Directory names to skip. Default: `node_modules`, `dist`, `build`, `coverage`. */\n skipDirs?: readonly string[];\n}\n\n/**\n * Collect the icon slugs a source file references literally.\n *\n * @param code - File contents.\n * @param known - The set of real lucide slugs, aliases included.\n * @returns The slugs found, validated against `known`.\n */\nexport function scanIconSlugs(code: string, known: ReadonlySet<string>): string[] {\n const found = new Set<string>();\n for (const pattern of NAME_PATTERNS) {\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(code)) !== null) {\n if (known.has(match[1])) found.add(match[1]);\n }\n }\n return [...found];\n}\n\n/**\n * Build the virtual module source for a set of slugs.\n *\n * Aliases resolve to their canonical slug before importing, because only the\n * canonical name is a real lucide export — but the alias key still points at the\n * component, so `<Icon name=\"alert-circle\" />` stays a zero-request lookup.\n *\n * @param slugs - Validated slugs to include.\n * @param aliases - Alias slug → canonical slug.\n * @returns ES module source.\n */\nexport function buildIconsModule(\n slugs: readonly string[],\n aliases: Record<string, string>,\n): string {\n const sorted = [...new Set(slugs)].sort();\n if (!sorted.length) return \"export const staticIcons = {};\\n\";\n\n const componentOf = new Map<string, string>();\n for (const slug of sorted) componentOf.set(slug, pascalCase(aliases[slug] ?? slug));\n\n const named = [...new Set(componentOf.values())]\n .sort()\n .map((name) => (localName(name) === name ? name : `${name} as ${localName(name)}`))\n .join(\", \");\n const entries = sorted\n .map((slug) => ` \"${slug}\": ${localName(componentOf.get(slug) as string)},`)\n .join(\"\\n\");\n return `import { ${named} } from \"lucide-react\";\n\nexport const staticIcons = {\n${entries}\n};\n`;\n}\n\n/** `circle-alert` → `CircleAlert`; `a-arrow-down` → `AArrowDown`. */\nfunction pascalCase(slug: string): string {\n return slug\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\n/**\n * Export names that would shadow a restricted global if bound directly.\n *\n * Lucide ships an icon literally called `infinity`, whose export is `Infinity`.\n * Binding that at module scope shadows the global, so the import is aliased.\n */\nconst RESERVED_NAMES = new Set([\"Infinity\", \"NaN\", \"undefined\", \"eval\", \"arguments\"]);\n\n/** The local binding to use for a lucide export name. */\nfunction localName(name: string): string {\n return RESERVED_NAMES.has(name) ? `${name}Icon` : name;\n}\n\n/**\n * Walk a directory and collect every source file path.\n *\n * @param dir - Absolute directory to walk.\n * @param skip - Directory names to skip.\n * @returns Absolute file paths.\n */\nasync function collectSourceFiles(dir: string, skip: ReadonlySet<string>): Promise<string[]> {\n const out: string[] = [];\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return out;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (skip.has(entry.name) || entry.name.startsWith(\".\")) continue;\n out.push(...(await collectSourceFiles(join(dir, entry.name), skip)));\n } else if (entry.isFile() && SOURCE_FILE.test(entry.name)) {\n out.push(join(dir, entry.name));\n }\n }\n return out;\n}\n\n/**\n * Generate a static icon registry from the slugs your source mentions.\n *\n * Removes the reason to reach for `lucide-react`'s `DynamicIcon`: the slugs an app\n * writes as literals become ordinary static imports, so the bundler keeps exactly\n * those icons and the browser makes **no** extra request for them. A slug that only\n * exists at runtime still works — `<Icon>` falls back to fetching one shard per\n * initial letter.\n *\n * The scan reads the source tree directly at `buildStart` rather than harvesting\n * modules as they pass through `transform`: relying on transform order would let\n * the virtual module load before every consumer had been seen, and the registry\n * would come out short on a cold dev start — silently, since the missing slugs\n * would still render via the lazy path.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestIcons } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({ plugins: [tempestIcons()] });\n *\n * @example\n * // src/main.tsx\n * import { IconProvider } from \"tempest-react-sdk/icons\";\n * import { staticIcons } from \"tempest-react-sdk/icons/virtual\";\n *\n * <IconProvider registry={staticIcons}>\n * <App />\n * </IconProvider>\n */\nexport function tempestIcons(options: TempestIconsOptions = {}): TempestVitePlugin {\n const { dir = \"src\", include = [], skipDirs } = options;\n const known = new Set<string>(iconNames);\n const skip = new Set<string>(skipDirs ?? SKIP_DIRS);\n const slugs = new Set<string>(include.filter((slug) => known.has(slug)));\n let root = process.cwd();\n\n /**\n * Rescan the source tree, keeping the explicitly included slugs.\n *\n * @tempest-limits empty-catch — the scan races the editor and the file system:\n * a file listed a moment ago can be renamed, deleted, or held by another process\n * by the time it is read. An unreadable file contributes no slugs and the build\n * continues; failing the whole scan would break `vite dev` over a temp file that\n * no longer exists.\n */\n const rescan = async (): Promise<void> => {\n const files = await collectSourceFiles(resolve(root, dir), skip);\n for (const file of files) {\n try {\n const code = await readFile(file, \"utf8\");\n for (const slug of scanIconSlugs(code, known)) slugs.add(slug);\n } catch {\n /* empty */\n }\n }\n };\n\n return {\n name: \"tempest-icons\",\n enforce: \"pre\",\n\n configResolved(config: { root?: string }) {\n root = config.root ?? root;\n },\n\n async buildStart() {\n await rescan();\n },\n\n /**\n * Claim both spellings of the registry module.\n *\n * The subpath is a real file in the installed package, so returning the\n * virtual id here is what overrides the empty stub with the generated\n * table. Letting Vite resolve it normally would silently ship the stub.\n */\n resolveId(id: string) {\n return id === TEMPEST_ICONS_ID || id === TEMPEST_ICONS_VIRTUAL_ID ? RESOLVED_ID : null;\n },\n\n load(id: string) {\n return id === RESOLVED_ID ? buildIconsModule([...slugs], iconAliases) : null;\n },\n\n /**\n * Pick up a slug added while the dev server is running.\n *\n * A newly written `<Icon name=\"save\" />` would otherwise only render via\n * the lazy path until the next restart. Rescanning the changed file and\n * invalidating the virtual module keeps dev and build agreeing on what the\n * static registry contains.\n */\n async handleHotUpdate(ctx: {\n file: string;\n read: () => Promise<string>;\n server: { moduleGraph: { getModuleById: (id: string) => unknown } };\n }): Promise<unknown[] | undefined> {\n if (!SOURCE_FILE.test(ctx.file)) return undefined;\n const before = slugs.size;\n for (const slug of scanIconSlugs(await ctx.read(), known)) slugs.add(slug);\n if (slugs.size === before) return undefined;\n const mod = ctx.server.moduleGraph.getModuleById(RESOLVED_ID);\n return mod ? [mod] : undefined;\n },\n } as TempestVitePlugin;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ function e(e,t){if(typeof e!=`object`||!e)return null;let n=e[t];return typeof n==`number`&&Number.isFinite(n)?n:null}function t(e,t){if(typeof e!=`object`||!e)return null;let n=e[t];return typeof n==`string`?n:null}function n(e){return t(e,`kind`)??t(e,`mediaType`)}function r(e,t){return t===`all`||n(e)===t}function i(n){let r=null;n.forEach(e=>{t(e,`type`)===`transport`&&(r=t(e,`selectedCandidatePairId`)??r)});let i=null,a=null;n.forEach(n=>{if(t(n,`type`)!==`candidate-pair`)return;let o=e(n,`currentRoundTripTime`);o!==null&&(r!==null&&t(n,`id`)===r?i=o*1e3:a===null&&t(n,`state`)===`succeeded`&&(a=o*1e3))});let o=i??a;return o===null?null:Math.round(o)}function a(n={}){let a=n.kind??`video`,o=null,s=0,c={kbps:0,width:0,height:0,fps:0,rttMs:null};function l(n){let l=performance.now(),u=i(n),d=0,f=!1,p=0,m=0,h=0,g=0;if(n.forEach(n=>{if(t(n,`type`)!==`outbound-rtp`||!r(n,a))return;f=!0,d+=e(n,`bytesSent`)??0;let i=e(n,`frameWidth`)??0,o=e(n,`frameHeight`)??0,s=i*o;s<p||(p=s,m=i,h=o,g=Math.round(e(n,`framesPerSecond`)??0))}),!f)return c={...c,rttMs:u},c;let _=o===null?0:(l-s)/1e3,v=o===null?0:d-o,y=_>0&&v>0?Math.round(v*8/1e3/_):0;return o=d,s=l,c={kbps:y,width:m>0?m:c.width,height:h>0?h:c.height,fps:g>0?g:c.fps,rttMs:u},c}return{read:l,sample:async e=>l(await e.getStats()),reset:()=>{o=null,s=0,c={...c,kbps:0}}}}exports.createLinkStatsSampler=a,exports.readRoundTripMs=i;
2
+ //# sourceMappingURL=link-stats.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-stats.cjs","names":[],"sources":["../../src/webrtc/link-stats.ts"],"sourcesContent":["/**\n * One link's outbound picture, in the shape a call UI actually renders.\n *\n * Everything here is derived, not reported: WebRTC hands out cumulative\n * counters and a graph of candidate pairs, and turning that into\n * `\"1,2 Mbps · 42 ms · 1080p60\"` is the work {@link createLinkStatsSampler}\n * does.\n */\nexport interface LinkStats {\n /** Throughput since the previous sample. `0` on the first one — there is no delta yet. */\n kbps: number;\n /** Width of the largest stream being sent, or `0` before one is reported. */\n width: number;\n /** Height of the largest stream being sent, or `0` before one is reported. */\n height: number;\n /** Frame rate of the largest stream being sent, or `0` when the browser omits it. */\n fps: number;\n /** Round trip to the peer in milliseconds, or `null` before the first reading. */\n rttMs: number | null;\n}\n\n/** Which media a sampler counts. */\nexport type LinkStatsKind = \"video\" | \"audio\" | \"all\";\n\n/** Options for {@link createLinkStatsSampler}. */\nexport interface LinkStatsSamplerOptions {\n /**\n * Which media the throughput counts. Default `\"video\"`.\n *\n * Video is the default because it is what saturates an uplink — audio is an\n * order of magnitude cheaper, and mixing it in moves the number by less than\n * the noise between two samples. Use `\"all\"` when the figure is meant to be\n * the connection's real cost rather than the picture's.\n */\n kind?: LinkStatsKind;\n}\n\n/**\n * A sampler bound to one connection.\n *\n * Holds the previous byte counter and timestamp, which is the whole reason this\n * is an object rather than a function: the rate is a delta, so somebody has to\n * remember the last reading. One sampler per link — sharing one across peers\n * subtracts one connection's counter from another's and reports nonsense.\n */\nexport interface LinkStatsSampler {\n /**\n * Reduce a report you already have.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The stats for this sample.\n */\n read: (report: RTCStatsReport) => LinkStats;\n /**\n * Fetch a report and reduce it.\n *\n * @param connection - The connection to sample.\n * @returns The stats for this sample.\n */\n sample: (connection: RTCPeerConnection) => Promise<LinkStats>;\n /**\n * Drop the baseline the rate is derived from.\n *\n * Call it after an ICE restart, a reconnect, or a pause — otherwise the next\n * sample divides the bytes of the whole gap by the whole gap and reports the\n * average of a period nobody is asking about. The next reading comes back at\n * `0` kbps and starts a fresh baseline; the resolution and round trip already\n * on screen are kept, so the badge does not blank out.\n */\n reset: () => void;\n}\n\nfunction numberField(entry: unknown, key: string): number | null {\n if (typeof entry !== \"object\" || entry === null) return null;\n const value: unknown = (entry as Record<string, unknown>)[key];\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\nfunction stringField(entry: unknown, key: string): string | null {\n if (typeof entry !== \"object\" || entry === null) return null;\n const value: unknown = (entry as Record<string, unknown>)[key];\n return typeof value === \"string\" ? value : null;\n}\n\n/**\n * Resolve the media an RTP entry carries.\n *\n * `kind` is the standard field and `mediaType` is what older Chrome reported;\n * both are still in the wild, and a sampler that reads only one of them\n * silently counts nothing on the browser that uses the other.\n */\nfunction entryKind(entry: unknown): string | null {\n return stringField(entry, \"kind\") ?? stringField(entry, \"mediaType\");\n}\n\nfunction matchesKind(entry: unknown, kind: LinkStatsKind): boolean {\n if (kind === \"all\") return true;\n return entryKind(entry) === kind;\n}\n\n/**\n * Read the round trip of the candidate pair actually carrying the link.\n *\n * A connection routinely keeps several viable pairs alive at once — host,\n * server-reflexive, relayed — and only one of them carries traffic. Reading the\n * first `succeeded` pair makes the number jump between paths that are not being\n * travelled: 8 ms on an idle host pair alternating with 180 ms on the TURN pair\n * doing the work. The pair the transport names in `selectedCandidatePairId` is\n * the one being used.\n *\n * A succeeded pair is kept as a fallback because not every browser fills that\n * field in — losing the reading entirely is worse than an occasionally\n * optimistic one.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns Round trip in milliseconds, rounded, or `null` when nothing reported\n * one — which is the normal state before the connection settles.\n *\n * @example\n * const rttMs = readRoundTripMs(await pc.getStats());\n */\nexport function readRoundTripMs(report: RTCStatsReport): number | null {\n let selectedId: string | null = null;\n report.forEach((entry: unknown) => {\n if (stringField(entry, \"type\") !== \"transport\") return;\n selectedId = stringField(entry, \"selectedCandidatePairId\") ?? selectedId;\n });\n\n let selected: number | null = null;\n let fallback: number | null = null;\n report.forEach((entry: unknown) => {\n if (stringField(entry, \"type\") !== \"candidate-pair\") return;\n const seconds = numberField(entry, \"currentRoundTripTime\");\n if (seconds === null) return;\n if (selectedId !== null && stringField(entry, \"id\") === selectedId)\n selected = seconds * 1000;\n else if (fallback === null && stringField(entry, \"state\") === \"succeeded\")\n fallback = seconds * 1000;\n });\n\n const value: number | null = selected ?? fallback;\n return value === null ? null : Math.round(value);\n}\n\n/**\n * Track one link's throughput, resolution and round trip across samples.\n *\n * Every rate here is a **delta**. `bytesSent` is cumulative since the connection\n * opened, so dividing it by the session length gives the historical average —\n * a number that only ever falls and never shows what is happening now. The\n * previous reading is kept on the sampler and subtracted, which is the part\n * every hand-rolled copy of this ends up rewriting.\n *\n * Bytes are summed across every matching sender, because a peer publishing a\n * camera and a screen at once occupies one uplink with both — and the uplink is\n * what runs out. Resolution and frame rate come from the **largest** stream by\n * area, which is the one that dominates that bandwidth and the one somebody\n * watching the call is looking at.\n *\n * @param options - See {@link LinkStatsSamplerOptions}.\n * @returns A sampler. Use one per `RTCPeerConnection`.\n *\n * @example\n * const sampler = createLinkStatsSampler();\n *\n * setInterval(async () => {\n * const stats = await sampler.sample(pc);\n * badge.textContent = `${stats.kbps} kbps · ${stats.rttMs ?? \"—\"} ms`;\n * }, 2000);\n */\nexport function createLinkStatsSampler(options: LinkStatsSamplerOptions = {}): LinkStatsSampler {\n const kind: LinkStatsKind = options.kind ?? \"video\";\n let lastBytes: number | null = null;\n let lastSampleAt = 0;\n let last: LinkStats = { kbps: 0, width: 0, height: 0, fps: 0, rttMs: null };\n\n function read(report: RTCStatsReport): LinkStats {\n const now = performance.now();\n const rttMs = readRoundTripMs(report);\n\n let bytes = 0;\n let sawSender = false;\n let bestArea = 0;\n let width = 0;\n let height = 0;\n let fps = 0;\n\n report.forEach((entry: unknown) => {\n if (stringField(entry, \"type\") !== \"outbound-rtp\") return;\n if (!matchesKind(entry, kind)) return;\n sawSender = true;\n bytes += numberField(entry, \"bytesSent\") ?? 0;\n\n const entryWidth = numberField(entry, \"frameWidth\") ?? 0;\n const entryHeight = numberField(entry, \"frameHeight\") ?? 0;\n const area = entryWidth * entryHeight;\n if (area < bestArea) return;\n bestArea = area;\n width = entryWidth;\n height = entryHeight;\n fps = Math.round(numberField(entry, \"framesPerSecond\") ?? 0);\n });\n\n if (!sawSender) {\n last = { ...last, rttMs };\n return last;\n }\n\n const elapsed = lastBytes === null ? 0 : (now - lastSampleAt) / 1000;\n const delta = lastBytes === null ? 0 : bytes - lastBytes;\n const kbps = elapsed > 0 && delta > 0 ? Math.round((delta * 8) / 1000 / elapsed) : 0;\n\n lastBytes = bytes;\n lastSampleAt = now;\n last = {\n kbps,\n width: width > 0 ? width : last.width,\n height: height > 0 ? height : last.height,\n fps: fps > 0 ? fps : last.fps,\n rttMs,\n };\n return last;\n }\n\n return {\n read,\n sample: async (connection: RTCPeerConnection): Promise<LinkStats> =>\n read(await connection.getStats()),\n reset: (): void => {\n lastBytes = null;\n lastSampleAt = 0;\n last = { ...last, kbps: 0 };\n },\n };\n}\n"],"mappings":"AAwEA,SAAS,EAAY,EAAgB,EAA4B,CAC7D,GAAI,OAAO,GAAU,WAAY,EAAgB,OAAO,KACxD,IAAM,EAAkB,EAAkC,GAC1D,OAAO,OAAO,GAAU,UAAY,OAAO,SAAS,CAAK,EAAI,EAAQ,IACzE,CAEA,SAAS,EAAY,EAAgB,EAA4B,CAC7D,GAAI,OAAO,GAAU,WAAY,EAAgB,OAAO,KACxD,IAAM,EAAkB,EAAkC,GAC1D,OAAO,OAAO,GAAU,SAAW,EAAQ,IAC/C,CASA,SAAS,EAAU,EAA+B,CAC9C,OAAO,EAAY,EAAO,MAAM,GAAK,EAAY,EAAO,WAAW,CACvE,CAEA,SAAS,EAAY,EAAgB,EAA8B,CAE/D,OADI,IAAS,OACN,EAAU,CAAK,IAAM,CAChC,CAuBA,SAAgB,EAAgB,EAAuC,CACnE,IAAI,EAA4B,KAChC,EAAO,QAAS,GAAmB,CAC3B,EAAY,EAAO,MAAM,IAAM,cACnC,EAAa,EAAY,EAAO,yBAAyB,GAAK,EAClE,CAAC,EAED,IAAI,EAA0B,KAC1B,EAA0B,KAC9B,EAAO,QAAS,GAAmB,CAC/B,GAAI,EAAY,EAAO,MAAM,IAAM,iBAAkB,OACrD,IAAM,EAAU,EAAY,EAAO,sBAAsB,EACrD,IAAY,OACZ,IAAe,MAAQ,EAAY,EAAO,IAAI,IAAM,EACpD,EAAW,EAAU,IAChB,IAAa,MAAQ,EAAY,EAAO,OAAO,IAAM,cAC1D,EAAW,EAAU,KAC7B,CAAC,EAED,IAAM,EAAuB,GAAY,EACzC,OAAO,IAAU,KAAO,KAAO,KAAK,MAAM,CAAK,CACnD,CA4BA,SAAgB,EAAuB,EAAmC,CAAC,EAAqB,CAC5F,IAAM,EAAsB,EAAQ,MAAQ,QACxC,EAA2B,KAC3B,EAAe,EACf,EAAkB,CAAE,KAAM,EAAG,MAAO,EAAG,OAAQ,EAAG,IAAK,EAAG,MAAO,IAAK,EAE1E,SAAS,EAAK,EAAmC,CAC7C,IAAM,EAAM,YAAY,IAAI,EACtB,EAAQ,EAAgB,CAAM,EAEhC,EAAQ,EACR,EAAY,GACZ,EAAW,EACX,EAAQ,EACR,EAAS,EACT,EAAM,EAkBV,GAhBA,EAAO,QAAS,GAAmB,CAE/B,GADI,EAAY,EAAO,MAAM,IAAM,gBAC/B,CAAC,EAAY,EAAO,CAAI,EAAG,OAC/B,EAAY,GACZ,GAAS,EAAY,EAAO,WAAW,GAAK,EAE5C,IAAM,EAAa,EAAY,EAAO,YAAY,GAAK,EACjD,EAAc,EAAY,EAAO,aAAa,GAAK,EACnD,EAAO,EAAa,EACtB,EAAO,IACX,EAAW,EACX,EAAQ,EACR,EAAS,EACT,EAAM,KAAK,MAAM,EAAY,EAAO,iBAAiB,GAAK,CAAC,EAC/D,CAAC,EAEG,CAAC,EAED,MADA,GAAO,CAAE,GAAG,EAAM,OAAM,EACjB,EAGX,IAAM,EAAU,IAAc,KAAO,GAAK,EAAM,GAAgB,IAC1D,EAAQ,IAAc,KAAO,EAAI,EAAQ,EACzC,EAAO,EAAU,GAAK,EAAQ,EAAI,KAAK,MAAO,EAAQ,EAAK,IAAO,CAAO,EAAI,EAWnF,MATA,GAAY,EACZ,EAAe,EACf,EAAO,CACH,OACA,MAAO,EAAQ,EAAI,EAAQ,EAAK,MAChC,OAAQ,EAAS,EAAI,EAAS,EAAK,OACnC,IAAK,EAAM,EAAI,EAAM,EAAK,IAC1B,OACJ,EACO,CACX,CAEA,MAAO,CACH,OACA,OAAQ,KAAO,IACX,EAAK,MAAM,EAAW,SAAS,CAAC,EACpC,UAAmB,CACf,EAAY,KACZ,EAAe,EACf,EAAO,CAAE,GAAG,EAAM,KAAM,CAAE,CAC9B,CACJ,CACJ"}
@@ -0,0 +1,74 @@
1
+ //#region src/webrtc/link-stats.ts
2
+ function e(e, t) {
3
+ if (typeof e != "object" || !e) return null;
4
+ let n = e[t];
5
+ return typeof n == "number" && Number.isFinite(n) ? n : null;
6
+ }
7
+ function t(e, t) {
8
+ if (typeof e != "object" || !e) return null;
9
+ let n = e[t];
10
+ return typeof n == "string" ? n : null;
11
+ }
12
+ function n(e) {
13
+ return t(e, "kind") ?? t(e, "mediaType");
14
+ }
15
+ function r(e, t) {
16
+ return t === "all" || n(e) === t;
17
+ }
18
+ function i(n) {
19
+ let r = null;
20
+ n.forEach((e) => {
21
+ t(e, "type") === "transport" && (r = t(e, "selectedCandidatePairId") ?? r);
22
+ });
23
+ let i = null, a = null;
24
+ n.forEach((n) => {
25
+ if (t(n, "type") !== "candidate-pair") return;
26
+ let o = e(n, "currentRoundTripTime");
27
+ o !== null && (r !== null && t(n, "id") === r ? i = o * 1e3 : a === null && t(n, "state") === "succeeded" && (a = o * 1e3));
28
+ });
29
+ let o = i ?? a;
30
+ return o === null ? null : Math.round(o);
31
+ }
32
+ function a(n = {}) {
33
+ let a = n.kind ?? "video", o = null, s = 0, c = {
34
+ kbps: 0,
35
+ width: 0,
36
+ height: 0,
37
+ fps: 0,
38
+ rttMs: null
39
+ };
40
+ function l(n) {
41
+ let l = performance.now(), u = i(n), d = 0, f = !1, p = 0, m = 0, h = 0, g = 0;
42
+ if (n.forEach((n) => {
43
+ if (t(n, "type") !== "outbound-rtp" || !r(n, a)) return;
44
+ f = !0, d += e(n, "bytesSent") ?? 0;
45
+ let i = e(n, "frameWidth") ?? 0, o = e(n, "frameHeight") ?? 0, s = i * o;
46
+ s < p || (p = s, m = i, h = o, g = Math.round(e(n, "framesPerSecond") ?? 0));
47
+ }), !f) return c = {
48
+ ...c,
49
+ rttMs: u
50
+ }, c;
51
+ let _ = o === null ? 0 : (l - s) / 1e3, v = o === null ? 0 : d - o, y = _ > 0 && v > 0 ? Math.round(v * 8 / 1e3 / _) : 0;
52
+ return o = d, s = l, c = {
53
+ kbps: y,
54
+ width: m > 0 ? m : c.width,
55
+ height: h > 0 ? h : c.height,
56
+ fps: g > 0 ? g : c.fps,
57
+ rttMs: u
58
+ }, c;
59
+ }
60
+ return {
61
+ read: l,
62
+ sample: async (e) => l(await e.getStats()),
63
+ reset: () => {
64
+ o = null, s = 0, c = {
65
+ ...c,
66
+ kbps: 0
67
+ };
68
+ }
69
+ };
70
+ }
71
+ //#endregion
72
+ export { a as createLinkStatsSampler, i as readRoundTripMs };
73
+
74
+ //# sourceMappingURL=link-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-stats.js","names":[],"sources":["../../src/webrtc/link-stats.ts"],"sourcesContent":["/**\n * One link's outbound picture, in the shape a call UI actually renders.\n *\n * Everything here is derived, not reported: WebRTC hands out cumulative\n * counters and a graph of candidate pairs, and turning that into\n * `\"1,2 Mbps · 42 ms · 1080p60\"` is the work {@link createLinkStatsSampler}\n * does.\n */\nexport interface LinkStats {\n /** Throughput since the previous sample. `0` on the first one — there is no delta yet. */\n kbps: number;\n /** Width of the largest stream being sent, or `0` before one is reported. */\n width: number;\n /** Height of the largest stream being sent, or `0` before one is reported. */\n height: number;\n /** Frame rate of the largest stream being sent, or `0` when the browser omits it. */\n fps: number;\n /** Round trip to the peer in milliseconds, or `null` before the first reading. */\n rttMs: number | null;\n}\n\n/** Which media a sampler counts. */\nexport type LinkStatsKind = \"video\" | \"audio\" | \"all\";\n\n/** Options for {@link createLinkStatsSampler}. */\nexport interface LinkStatsSamplerOptions {\n /**\n * Which media the throughput counts. Default `\"video\"`.\n *\n * Video is the default because it is what saturates an uplink — audio is an\n * order of magnitude cheaper, and mixing it in moves the number by less than\n * the noise between two samples. Use `\"all\"` when the figure is meant to be\n * the connection's real cost rather than the picture's.\n */\n kind?: LinkStatsKind;\n}\n\n/**\n * A sampler bound to one connection.\n *\n * Holds the previous byte counter and timestamp, which is the whole reason this\n * is an object rather than a function: the rate is a delta, so somebody has to\n * remember the last reading. One sampler per link — sharing one across peers\n * subtracts one connection's counter from another's and reports nonsense.\n */\nexport interface LinkStatsSampler {\n /**\n * Reduce a report you already have.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The stats for this sample.\n */\n read: (report: RTCStatsReport) => LinkStats;\n /**\n * Fetch a report and reduce it.\n *\n * @param connection - The connection to sample.\n * @returns The stats for this sample.\n */\n sample: (connection: RTCPeerConnection) => Promise<LinkStats>;\n /**\n * Drop the baseline the rate is derived from.\n *\n * Call it after an ICE restart, a reconnect, or a pause — otherwise the next\n * sample divides the bytes of the whole gap by the whole gap and reports the\n * average of a period nobody is asking about. The next reading comes back at\n * `0` kbps and starts a fresh baseline; the resolution and round trip already\n * on screen are kept, so the badge does not blank out.\n */\n reset: () => void;\n}\n\nfunction numberField(entry: unknown, key: string): number | null {\n if (typeof entry !== \"object\" || entry === null) return null;\n const value: unknown = (entry as Record<string, unknown>)[key];\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\nfunction stringField(entry: unknown, key: string): string | null {\n if (typeof entry !== \"object\" || entry === null) return null;\n const value: unknown = (entry as Record<string, unknown>)[key];\n return typeof value === \"string\" ? value : null;\n}\n\n/**\n * Resolve the media an RTP entry carries.\n *\n * `kind` is the standard field and `mediaType` is what older Chrome reported;\n * both are still in the wild, and a sampler that reads only one of them\n * silently counts nothing on the browser that uses the other.\n */\nfunction entryKind(entry: unknown): string | null {\n return stringField(entry, \"kind\") ?? stringField(entry, \"mediaType\");\n}\n\nfunction matchesKind(entry: unknown, kind: LinkStatsKind): boolean {\n if (kind === \"all\") return true;\n return entryKind(entry) === kind;\n}\n\n/**\n * Read the round trip of the candidate pair actually carrying the link.\n *\n * A connection routinely keeps several viable pairs alive at once — host,\n * server-reflexive, relayed — and only one of them carries traffic. Reading the\n * first `succeeded` pair makes the number jump between paths that are not being\n * travelled: 8 ms on an idle host pair alternating with 180 ms on the TURN pair\n * doing the work. The pair the transport names in `selectedCandidatePairId` is\n * the one being used.\n *\n * A succeeded pair is kept as a fallback because not every browser fills that\n * field in — losing the reading entirely is worse than an occasionally\n * optimistic one.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns Round trip in milliseconds, rounded, or `null` when nothing reported\n * one — which is the normal state before the connection settles.\n *\n * @example\n * const rttMs = readRoundTripMs(await pc.getStats());\n */\nexport function readRoundTripMs(report: RTCStatsReport): number | null {\n let selectedId: string | null = null;\n report.forEach((entry: unknown) => {\n if (stringField(entry, \"type\") !== \"transport\") return;\n selectedId = stringField(entry, \"selectedCandidatePairId\") ?? selectedId;\n });\n\n let selected: number | null = null;\n let fallback: number | null = null;\n report.forEach((entry: unknown) => {\n if (stringField(entry, \"type\") !== \"candidate-pair\") return;\n const seconds = numberField(entry, \"currentRoundTripTime\");\n if (seconds === null) return;\n if (selectedId !== null && stringField(entry, \"id\") === selectedId)\n selected = seconds * 1000;\n else if (fallback === null && stringField(entry, \"state\") === \"succeeded\")\n fallback = seconds * 1000;\n });\n\n const value: number | null = selected ?? fallback;\n return value === null ? null : Math.round(value);\n}\n\n/**\n * Track one link's throughput, resolution and round trip across samples.\n *\n * Every rate here is a **delta**. `bytesSent` is cumulative since the connection\n * opened, so dividing it by the session length gives the historical average —\n * a number that only ever falls and never shows what is happening now. The\n * previous reading is kept on the sampler and subtracted, which is the part\n * every hand-rolled copy of this ends up rewriting.\n *\n * Bytes are summed across every matching sender, because a peer publishing a\n * camera and a screen at once occupies one uplink with both — and the uplink is\n * what runs out. Resolution and frame rate come from the **largest** stream by\n * area, which is the one that dominates that bandwidth and the one somebody\n * watching the call is looking at.\n *\n * @param options - See {@link LinkStatsSamplerOptions}.\n * @returns A sampler. Use one per `RTCPeerConnection`.\n *\n * @example\n * const sampler = createLinkStatsSampler();\n *\n * setInterval(async () => {\n * const stats = await sampler.sample(pc);\n * badge.textContent = `${stats.kbps} kbps · ${stats.rttMs ?? \"—\"} ms`;\n * }, 2000);\n */\nexport function createLinkStatsSampler(options: LinkStatsSamplerOptions = {}): LinkStatsSampler {\n const kind: LinkStatsKind = options.kind ?? \"video\";\n let lastBytes: number | null = null;\n let lastSampleAt = 0;\n let last: LinkStats = { kbps: 0, width: 0, height: 0, fps: 0, rttMs: null };\n\n function read(report: RTCStatsReport): LinkStats {\n const now = performance.now();\n const rttMs = readRoundTripMs(report);\n\n let bytes = 0;\n let sawSender = false;\n let bestArea = 0;\n let width = 0;\n let height = 0;\n let fps = 0;\n\n report.forEach((entry: unknown) => {\n if (stringField(entry, \"type\") !== \"outbound-rtp\") return;\n if (!matchesKind(entry, kind)) return;\n sawSender = true;\n bytes += numberField(entry, \"bytesSent\") ?? 0;\n\n const entryWidth = numberField(entry, \"frameWidth\") ?? 0;\n const entryHeight = numberField(entry, \"frameHeight\") ?? 0;\n const area = entryWidth * entryHeight;\n if (area < bestArea) return;\n bestArea = area;\n width = entryWidth;\n height = entryHeight;\n fps = Math.round(numberField(entry, \"framesPerSecond\") ?? 0);\n });\n\n if (!sawSender) {\n last = { ...last, rttMs };\n return last;\n }\n\n const elapsed = lastBytes === null ? 0 : (now - lastSampleAt) / 1000;\n const delta = lastBytes === null ? 0 : bytes - lastBytes;\n const kbps = elapsed > 0 && delta > 0 ? Math.round((delta * 8) / 1000 / elapsed) : 0;\n\n lastBytes = bytes;\n lastSampleAt = now;\n last = {\n kbps,\n width: width > 0 ? width : last.width,\n height: height > 0 ? height : last.height,\n fps: fps > 0 ? fps : last.fps,\n rttMs,\n };\n return last;\n }\n\n return {\n read,\n sample: async (connection: RTCPeerConnection): Promise<LinkStats> =>\n read(await connection.getStats()),\n reset: (): void => {\n lastBytes = null;\n lastSampleAt = 0;\n last = { ...last, kbps: 0 };\n },\n };\n}\n"],"mappings":";AAwEA,SAAS,EAAY,GAAgB,GAA4B;CAC7D,IAAI,OAAO,KAAU,aAAY,GAAgB,OAAO;CACxD,IAAM,IAAkB,EAAkC;CAC1D,OAAO,OAAO,KAAU,YAAY,OAAO,SAAS,CAAK,IAAI,IAAQ;AACzE;AAEA,SAAS,EAAY,GAAgB,GAA4B;CAC7D,IAAI,OAAO,KAAU,aAAY,GAAgB,OAAO;CACxD,IAAM,IAAkB,EAAkC;CAC1D,OAAO,OAAO,KAAU,WAAW,IAAQ;AAC/C;AASA,SAAS,EAAU,GAA+B;CAC9C,OAAO,EAAY,GAAO,MAAM,KAAK,EAAY,GAAO,WAAW;AACvE;AAEA,SAAS,EAAY,GAAgB,GAA8B;CAE/D,OADI,MAAS,SACN,EAAU,CAAK,MAAM;AAChC;AAuBA,SAAgB,EAAgB,GAAuC;CACnE,IAAI,IAA4B;CAChC,EAAO,SAAS,MAAmB;EAC3B,EAAY,GAAO,MAAM,MAAM,gBACnC,IAAa,EAAY,GAAO,yBAAyB,KAAK;CAClE,CAAC;CAED,IAAI,IAA0B,MAC1B,IAA0B;CAC9B,EAAO,SAAS,MAAmB;EAC/B,IAAI,EAAY,GAAO,MAAM,MAAM,kBAAkB;EACrD,IAAM,IAAU,EAAY,GAAO,sBAAsB;EACrD,MAAY,SACZ,MAAe,QAAQ,EAAY,GAAO,IAAI,MAAM,IACpD,IAAW,IAAU,MAChB,MAAa,QAAQ,EAAY,GAAO,OAAO,MAAM,gBAC1D,IAAW,IAAU;CAC7B,CAAC;CAED,IAAM,IAAuB,KAAY;CACzC,OAAO,MAAU,OAAO,OAAO,KAAK,MAAM,CAAK;AACnD;AA4BA,SAAgB,EAAuB,IAAmC,CAAC,GAAqB;CAC5F,IAAM,IAAsB,EAAQ,QAAQ,SACxC,IAA2B,MAC3B,IAAe,GACf,IAAkB;EAAE,MAAM;EAAG,OAAO;EAAG,QAAQ;EAAG,KAAK;EAAG,OAAO;CAAK;CAE1E,SAAS,EAAK,GAAmC;EAC7C,IAAM,IAAM,YAAY,IAAI,GACtB,IAAQ,EAAgB,CAAM,GAEhC,IAAQ,GACR,IAAY,IACZ,IAAW,GACX,IAAQ,GACR,IAAS,GACT,IAAM;EAkBV,IAhBA,EAAO,SAAS,MAAmB;GAE/B,IADI,EAAY,GAAO,MAAM,MAAM,kBAC/B,CAAC,EAAY,GAAO,CAAI,GAAG;GAE/B,AADA,IAAY,IACZ,KAAS,EAAY,GAAO,WAAW,KAAK;GAE5C,IAAM,IAAa,EAAY,GAAO,YAAY,KAAK,GACjD,IAAc,EAAY,GAAO,aAAa,KAAK,GACnD,IAAO,IAAa;GACtB,IAAO,MACX,IAAW,GACX,IAAQ,GACR,IAAS,GACT,IAAM,KAAK,MAAM,EAAY,GAAO,iBAAiB,KAAK,CAAC;EAC/D,CAAC,GAEG,CAAC,GAED,OADA,IAAO;GAAE,GAAG;GAAM;EAAM,GACjB;EAGX,IAAM,IAAU,MAAc,OAAO,KAAK,IAAM,KAAgB,KAC1D,IAAQ,MAAc,OAAO,IAAI,IAAQ,GACzC,IAAO,IAAU,KAAK,IAAQ,IAAI,KAAK,MAAO,IAAQ,IAAK,MAAO,CAAO,IAAI;EAWnF,OATA,IAAY,GACZ,IAAe,GACf,IAAO;GACH;GACA,OAAO,IAAQ,IAAI,IAAQ,EAAK;GAChC,QAAQ,IAAS,IAAI,IAAS,EAAK;GACnC,KAAK,IAAM,IAAI,IAAM,EAAK;GAC1B;EACJ,GACO;CACX;CAEA,OAAO;EACH;EACA,QAAQ,OAAO,MACX,EAAK,MAAM,EAAW,SAAS,CAAC;EACpC,aAAmB;GAGf,AAFA,IAAY,MACZ,IAAe,GACf,IAAO;IAAE,GAAG;IAAM,MAAM;GAAE;EAC9B;CACJ;AACJ"}
@@ -0,0 +1,3 @@
1
+ var e=new Set([`maxAverageBitrate`,`maxPlaybackRate`,`stereo`,`fec`,`dtx`,`cbr`,`extra`]);function t(t){let n=Object.keys(t);return n.length===0||n.some(t=>e.has(t))}function n(e){let t={};return e.maxAverageBitrate!==void 0&&(t.maxaveragebitrate=String(e.maxAverageBitrate)),e.maxPlaybackRate!==void 0&&(t.maxplaybackrate=String(e.maxPlaybackRate)),e.stereo!==void 0&&(t.stereo=e.stereo?`1`:`0`,t[`sprop-stereo`]=e.stereo?`1`:`0`),e.fec!==void 0&&(t.useinbandfec=e.fec?`1`:`0`),e.dtx!==void 0&&(t.usedtx=e.dtx?`1`:`0`),e.cbr!==void 0&&(t.cbr=e.cbr?`1`:`0`),{...t,...e.extra}}function r(e,t){let n=new Map;for(let t of e.split(`;`)){let e=t.trim();if(!e)continue;let r=e.indexOf(`=`);r<0?n.set(e,``):n.set(e.slice(0,r),e.slice(r+1))}for(let[e,r]of Object.entries(t))n.set(e,r);return[...n.entries()].map(([e,t])=>t===``?e:`${e}=${t}`).join(`;`)}function i(e){let t=[];for(let n of e){let e=/^a=rtpmap:(\d+)\s+opus\/48000/i.exec(n);e&&t.push(e[1])}return t}function a(e){for(let t of e){let e=/^a=mid:(.+)$/.exec(t);if(e)return e[1].trim()}return null}function o(e,t){let a=n(t),o=i(e);if(o.length===0||Object.keys(a).length===0)return[...e];let s=[];for(let t of e){let n=/^a=fmtp:(\d+)\s+(.*)$/.exec(t);if(n&&o.includes(n[1])){s.push(`a=fmtp:${n[1]} ${r(n[2],a)}`);continue}s.push(t);let i=/^a=rtpmap:(\d+)\s+opus\/48000/i.exec(t);i&&!e.some(e=>e.startsWith(`a=fmtp:${i[1]} `))&&s.push(`a=fmtp:${i[1]} ${r(``,a)}`)}return s}function s(e){let t=[],n=[];for(let r of e.split(/\r\n|\n/))r.startsWith(`m=`)&&n.length>0&&(t.push(n),n=[]),n.push(r);return n.length>0&&t.push(n),t}function c(e,n){let r=t(n)?n:null,i=r?null:n,c=0;return s(e).map(e=>{if(!e[0]?.startsWith(`m=audio`))return e;let t=c;if(c+=1,r)return o(e,r);let n=a(e),s=i?.[t]??(n===null?void 0:i?.[n]);return s?o(e,s):e}).flat().join(`\r
2
+ `)}async function l(e,t,n){if(t.sdp){let r=c(t.sdp,n);if(r!==t.sdp)try{return await e.setLocalDescription({...t,sdp:r}),`tuned`}catch{}}return await e.setLocalDescription(t),`original`}exports.setTunedLocalDescription=l,exports.tuneOpus=c;
3
+ //# sourceMappingURL=opus-sdp.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opus-sdp.cjs","names":[],"sources":["../../src/webrtc/opus-sdp.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines — parsing a session description, merging one\n * `fmtp` line and inserting a missing one are the same pass over the same\n * grammar; split apart, each half is a parser of half an SDP.\n */\n\n/** What an Opus `fmtp` line can be asked to carry. Every field is optional. */\nexport interface OpusProfile {\n /**\n * Ceiling the encoder is asked to respect, in bits per second.\n *\n * This describes what we want **to receive**. To cap what we *send*, use\n * `setSenderBitrate` — in a mesh that is the one that matters, because the\n * uplink carries one copy per participant.\n */\n maxAverageBitrate?: number;\n /** Highest sample rate worth decoding, in Hz. `48000` for full band. */\n maxPlaybackRate?: number;\n /**\n * Two channels instead of one.\n *\n * Sets `stereo` **and** `sprop-stereo`, which point in opposite directions:\n * `stereo=1` asks the *remote* to send two channels, `sprop-stereo=1`\n * announces that *we* will. Setting only one leaves the link asymmetric,\n * which is the recurring reason \"I asked for stereo and got mono\".\n */\n stereo?: boolean;\n /**\n * In-band forward error correction (`useinbandfec`).\n *\n * Rebuilds a lost packet from the next one, which keeps speech intelligible\n * on a lossy link — and smears music, since it spends bitrate on redundancy\n * instead of detail.\n */\n fec?: boolean;\n /**\n * Discontinuous transmission (`usedtx`): stop sending during silence.\n *\n * Saves uplink in a mesh, at the cost of clipping the first instant after a\n * pause. Wrong for music and for a shared screen, where the quiet passages\n * are content.\n */\n dtx?: boolean;\n /** Constant bitrate (`cbr`). Off by default, as Opus intends. */\n cbr?: boolean;\n /**\n * Any other `fmtp` key, merged verbatim.\n *\n * The escape hatch for a parameter this type does not model. Values are\n * written as given; a key with an empty string is emitted as a bare flag.\n */\n extra?: Record<string, string>;\n}\n\n/**\n * Profiles to apply, keyed by audio m-line index (`0`, `1`, …) or by `mid`.\n *\n * Position and `mid` can be mixed in one object. A key that matches nothing is\n * ignored rather than an error — an SDP is negotiated, and a slot that was not\n * offered this time is normal.\n */\nexport type OpusProfileMap = Record<string | number, OpusProfile>;\n\n/** Keys that identify a bare {@link OpusProfile} rather than a map of them. */\nconst PROFILE_KEYS: ReadonlySet<string> = new Set([\n \"maxAverageBitrate\",\n \"maxPlaybackRate\",\n \"stereo\",\n \"fec\",\n \"dtx\",\n \"cbr\",\n \"extra\",\n]);\n\n/**\n * Whether the caller passed one profile for every audio m-line, or a map.\n *\n * An empty object is read as a profile, which makes `tuneOpus(sdp, {})` a no-op\n * instead of a silent surprise.\n */\nfunction isSingleProfile(value: OpusProfile | OpusProfileMap): value is OpusProfile {\n const keys = Object.keys(value);\n return keys.length === 0 || keys.some((key) => PROFILE_KEYS.has(key));\n}\n\n/** The `fmtp` parameters a profile asks for, in the order Opus documents them. */\nfunction fmtpParams(profile: OpusProfile): Record<string, string> {\n const params: Record<string, string> = {};\n if (profile.maxAverageBitrate !== undefined) {\n params.maxaveragebitrate = String(profile.maxAverageBitrate);\n }\n if (profile.maxPlaybackRate !== undefined) {\n params.maxplaybackrate = String(profile.maxPlaybackRate);\n }\n if (profile.stereo !== undefined) {\n params.stereo = profile.stereo ? \"1\" : \"0\";\n params[\"sprop-stereo\"] = profile.stereo ? \"1\" : \"0\";\n }\n if (profile.fec !== undefined) params.useinbandfec = profile.fec ? \"1\" : \"0\";\n if (profile.dtx !== undefined) params.usedtx = profile.dtx ? \"1\" : \"0\";\n if (profile.cbr !== undefined) params.cbr = profile.cbr ? \"1\" : \"0\";\n return { ...params, ...profile.extra };\n}\n\n/**\n * Merge parameters into an existing `fmtp` payload, key by key.\n *\n * Replacing the whole line is the obvious move and it is wrong: the browser\n * emits `minptime=10;useinbandfec=1` on its own, and overwriting drops the\n * `minptime` — a packetization decision nobody meant to change.\n */\nfunction mergeFmtp(existing: string, params: Record<string, string>): string {\n const merged = new Map<string, string>();\n for (const entry of existing.split(\";\")) {\n const trimmed = entry.trim();\n if (!trimmed) continue;\n const eq = trimmed.indexOf(\"=\");\n if (eq < 0) merged.set(trimmed, \"\");\n else merged.set(trimmed.slice(0, eq), trimmed.slice(eq + 1));\n }\n for (const [key, value] of Object.entries(params)) merged.set(key, value);\n return [...merged.entries()]\n .map(([key, value]) => (value === \"\" ? key : `${key}=${value}`))\n .join(\";\");\n}\n\n/** Payload types this media block maps to Opus. There can be more than one. */\nfunction opusPayloads(block: readonly string[]): string[] {\n const payloads: string[] = [];\n for (const line of block) {\n const match = /^a=rtpmap:(\\d+)\\s+opus\\/48000/i.exec(line);\n if (match) payloads.push(match[1]);\n }\n return payloads;\n}\n\n/** The block's `mid`, when it declares one. */\nfunction blockMid(block: readonly string[]): string | null {\n for (const line of block) {\n const match = /^a=mid:(.+)$/.exec(line);\n if (match) return match[1].trim();\n }\n return null;\n}\n\n/**\n * Rewrite one media block's Opus `fmtp` lines to carry a profile.\n *\n * A payload type with no `fmtp` of its own gets one inserted right after its\n * `rtpmap`, because \"the parameter is missing\" and \"the parameter is empty\" are\n * the same request from the caller's side.\n */\nfunction tuneBlock(block: readonly string[], profile: OpusProfile): string[] {\n const params = fmtpParams(profile);\n const payloads = opusPayloads(block);\n if (payloads.length === 0 || Object.keys(params).length === 0) return [...block];\n\n const out: string[] = [];\n for (const line of block) {\n const fmtp = /^a=fmtp:(\\d+)\\s+(.*)$/.exec(line);\n if (fmtp && payloads.includes(fmtp[1])) {\n out.push(`a=fmtp:${fmtp[1]} ${mergeFmtp(fmtp[2], params)}`);\n continue;\n }\n out.push(line);\n\n const rtpmap = /^a=rtpmap:(\\d+)\\s+opus\\/48000/i.exec(line);\n if (rtpmap && !block.some((other) => other.startsWith(`a=fmtp:${rtpmap[1]} `))) {\n out.push(`a=fmtp:${rtpmap[1]} ${mergeFmtp(\"\", params)}`);\n }\n }\n return out;\n}\n\n/** Split a session description into its session part plus one block per m-line. */\nfunction mediaBlocks(sdp: string): string[][] {\n const blocks: string[][] = [];\n let current: string[] = [];\n for (const line of sdp.split(/\\r\\n|\\n/)) {\n if (line.startsWith(\"m=\") && current.length > 0) {\n blocks.push(current);\n current = [];\n }\n current.push(line);\n }\n if (current.length > 0) blocks.push(current);\n return blocks;\n}\n\n/**\n * Apply Opus profiles to the audio m-lines of a session description.\n *\n * Audio over WebRTC is mono and narrow by default, and the only place that is\n * corrected is the SDP — which is why shared-screen audio famously sounds like a\n * telephone: music and video inherit the speech profile (mono, ~32 kbps, FEC on,\n * DTX gating the quiet passages) and no high-level API lets you change it.\n *\n * Deliberately **without** built-in presets: which values to use is the\n * consumer's call — voice in a mesh does not want what system audio wants — and\n * a preset table is the kind of thing that has no business inside a dependency.\n * What lives here is the parsing, merging and insertion, which is where the long\n * tail is. Getting any of it wrong degrades silently: nobody sees an exception,\n * the audio is just worse.\n *\n * @param sdp - The description from `createOffer` or `createAnswer`.\n * @param profiles - One {@link OpusProfile} for every audio m-line, or an\n * {@link OpusProfileMap} keyed by audio m-line index or by `mid`.\n * @returns The rewritten SDP, with CRLF line endings as RFC 4566 requires.\n * Untouched when there is no Opus, when no profile matches, or when a profile\n * asks for nothing.\n *\n * @example\n * const tuned = tuneOpus(offer.sdp, {\n * 0: { maxAverageBitrate: 48_000, stereo: false, fec: true, dtx: true },\n * 1: { maxAverageBitrate: 192_000, stereo: true, fec: false, dtx: false },\n * });\n *\n * @example\n * const tuned = tuneOpus(offer.sdp, { stereo: true, dtx: false });\n */\nexport function tuneOpus(sdp: string, profiles: OpusProfile | OpusProfileMap): string {\n const single = isSingleProfile(profiles) ? profiles : null;\n const map = single ? null : (profiles as OpusProfileMap);\n\n let audioIndex = 0;\n const tuned = mediaBlocks(sdp).map((block) => {\n if (!block[0]?.startsWith(\"m=audio\")) return block;\n\n const index = audioIndex;\n audioIndex += 1;\n if (single) return tuneBlock(block, single);\n\n const mid = blockMid(block);\n const profile = map?.[index] ?? (mid !== null ? map?.[mid] : undefined);\n return profile ? tuneBlock(block, profile) : block;\n });\n\n return tuned.flat().join(\"\\r\\n\");\n}\n\n/** Which description a peer connection actually accepted. */\nexport type TunedDescriptionResult = \"tuned\" | \"original\";\n\n/**\n * Set a local description, falling back to the untouched one if it is refused.\n *\n * Chrome has been tightening what `setLocalDescription` accepts from edited SDP,\n * and there is no way to know in advance. Without a fallback the call dies\n * instead of merely losing the profile — which is the wrong trade by a wide\n * margin: worse audio beats no audio.\n *\n * @param connection - The peer connection.\n * @param description - The description from `createOffer` / `createAnswer`.\n * @param profiles - Passed straight to {@link tuneOpus}.\n * @returns `\"tuned\"` when the rewritten SDP was accepted, `\"original\"` when the\n * fallback was used — worth reporting, because it means the profile silently\n * did not apply.\n * @throws Whatever `setLocalDescription` throws for the original description: at\n * that point the failure is not about the rewrite and the caller has to know.\n *\n * @example\n * const applied = await setTunedLocalDescription(pc, await pc.createOffer(), profiles);\n * if (applied === \"original\") logger.warn(\"opus profile refused by the browser\");\n */\nexport async function setTunedLocalDescription(\n connection: RTCPeerConnection,\n description: RTCSessionDescriptionInit,\n profiles: OpusProfile | OpusProfileMap,\n): Promise<TunedDescriptionResult> {\n if (description.sdp) {\n const sdp = tuneOpus(description.sdp, profiles);\n if (sdp !== description.sdp) {\n try {\n await connection.setLocalDescription({ ...description, sdp });\n return \"tuned\";\n } catch {\n /* the browser refused the edit; the untouched offer still works */\n }\n }\n }\n await connection.setLocalDescription(description);\n return \"original\";\n}\n"],"mappings":"AAgEA,IAAM,EAAoC,IAAI,IAAI,CAC9C,oBACA,kBACA,SACA,MACA,MACA,MACA,OACJ,CAAC,EAQD,SAAS,EAAgB,EAA2D,CAChF,IAAM,EAAO,OAAO,KAAK,CAAK,EAC9B,OAAO,EAAK,SAAW,GAAK,EAAK,KAAM,GAAQ,EAAa,IAAI,CAAG,CAAC,CACxE,CAGA,SAAS,EAAW,EAA8C,CAC9D,IAAM,EAAiC,CAAC,EAcxC,OAbI,EAAQ,oBAAsB,IAAA,KAC9B,EAAO,kBAAoB,OAAO,EAAQ,iBAAiB,GAE3D,EAAQ,kBAAoB,IAAA,KAC5B,EAAO,gBAAkB,OAAO,EAAQ,eAAe,GAEvD,EAAQ,SAAW,IAAA,KACnB,EAAO,OAAS,EAAQ,OAAS,IAAM,IACvC,EAAO,gBAAkB,EAAQ,OAAS,IAAM,KAEhD,EAAQ,MAAQ,IAAA,KAAW,EAAO,aAAe,EAAQ,IAAM,IAAM,KACrE,EAAQ,MAAQ,IAAA,KAAW,EAAO,OAAS,EAAQ,IAAM,IAAM,KAC/D,EAAQ,MAAQ,IAAA,KAAW,EAAO,IAAM,EAAQ,IAAM,IAAM,KACzD,CAAE,GAAG,EAAQ,GAAG,EAAQ,KAAM,CACzC,CASA,SAAS,EAAU,EAAkB,EAAwC,CACzE,IAAM,EAAS,IAAI,IACnB,IAAK,IAAM,KAAS,EAAS,MAAM,GAAG,EAAG,CACrC,IAAM,EAAU,EAAM,KAAK,EAC3B,GAAI,CAAC,EAAS,SACd,IAAM,EAAK,EAAQ,QAAQ,GAAG,EAC1B,EAAK,EAAG,EAAO,IAAI,EAAS,EAAE,EAC7B,EAAO,IAAI,EAAQ,MAAM,EAAG,CAAE,EAAG,EAAQ,MAAM,EAAK,CAAC,CAAC,CAC/D,CACA,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAM,EAAG,EAAO,IAAI,EAAK,CAAK,EACxE,MAAO,CAAC,GAAG,EAAO,QAAQ,CAAC,CAAC,CACvB,KAAK,CAAC,EAAK,KAAY,IAAU,GAAK,EAAM,GAAG,EAAI,GAAG,GAAQ,CAAC,CAC/D,KAAK,GAAG,CACjB,CAGA,SAAS,EAAa,EAAoC,CACtD,IAAM,EAAqB,CAAC,EAC5B,IAAK,IAAM,KAAQ,EAAO,CACtB,IAAM,EAAQ,iCAAiC,KAAK,CAAI,EACpD,GAAO,EAAS,KAAK,EAAM,EAAE,CACrC,CACA,OAAO,CACX,CAGA,SAAS,EAAS,EAAyC,CACvD,IAAK,IAAM,KAAQ,EAAO,CACtB,IAAM,EAAQ,eAAe,KAAK,CAAI,EACtC,GAAI,EAAO,OAAO,EAAM,EAAE,CAAC,KAAK,CACpC,CACA,OAAO,IACX,CASA,SAAS,EAAU,EAA0B,EAAgC,CACzE,IAAM,EAAS,EAAW,CAAO,EAC3B,EAAW,EAAa,CAAK,EACnC,GAAI,EAAS,SAAW,GAAK,OAAO,KAAK,CAAM,CAAC,CAAC,SAAW,EAAG,MAAO,CAAC,GAAG,CAAK,EAE/E,IAAM,EAAgB,CAAC,EACvB,IAAK,IAAM,KAAQ,EAAO,CACtB,IAAM,EAAO,wBAAwB,KAAK,CAAI,EAC9C,GAAI,GAAQ,EAAS,SAAS,EAAK,EAAE,EAAG,CACpC,EAAI,KAAK,UAAU,EAAK,GAAG,GAAG,EAAU,EAAK,GAAI,CAAM,GAAG,EAC1D,QACJ,CACA,EAAI,KAAK,CAAI,EAEb,IAAM,EAAS,iCAAiC,KAAK,CAAI,EACrD,GAAU,CAAC,EAAM,KAAM,GAAU,EAAM,WAAW,UAAU,EAAO,GAAG,EAAE,CAAC,GACzE,EAAI,KAAK,UAAU,EAAO,GAAG,GAAG,EAAU,GAAI,CAAM,GAAG,CAE/D,CACA,OAAO,CACX,CAGA,SAAS,EAAY,EAAyB,CAC1C,IAAM,EAAqB,CAAC,EACxB,EAAoB,CAAC,EACzB,IAAK,IAAM,KAAQ,EAAI,MAAM,SAAS,EAC9B,EAAK,WAAW,IAAI,GAAK,EAAQ,OAAS,IAC1C,EAAO,KAAK,CAAO,EACnB,EAAU,CAAC,GAEf,EAAQ,KAAK,CAAI,EAGrB,OADI,EAAQ,OAAS,GAAG,EAAO,KAAK,CAAO,EACpC,CACX,CAiCA,SAAgB,EAAS,EAAa,EAAgD,CAClF,IAAM,EAAS,EAAgB,CAAQ,EAAI,EAAW,KAChD,EAAM,EAAS,KAAQ,EAEzB,EAAa,EAajB,OAZc,EAAY,CAAG,CAAC,CAAC,IAAK,GAAU,CAC1C,GAAI,CAAC,EAAM,EAAE,EAAE,WAAW,SAAS,EAAG,OAAO,EAE7C,IAAM,EAAQ,EAEd,GADA,GAAc,EACV,EAAQ,OAAO,EAAU,EAAO,CAAM,EAE1C,IAAM,EAAM,EAAS,CAAK,EACpB,EAAU,IAAM,KAAW,IAAQ,KAAoB,IAAA,GAAb,IAAM,IACtD,OAAO,EAAU,EAAU,EAAO,CAAO,EAAI,CACjD,CAEO,CAAA,CAAM,KAAK,CAAC,CAAC,KAAK;CAAM,CACnC,CA0BA,eAAsB,EAClB,EACA,EACA,EAC+B,CAC/B,GAAI,EAAY,IAAK,CACjB,IAAM,EAAM,EAAS,EAAY,IAAK,CAAQ,EAC9C,GAAI,IAAQ,EAAY,IACpB,GAAI,CAEA,OADA,MAAM,EAAW,oBAAoB,CAAE,GAAG,EAAa,KAAI,CAAC,EACrD,OACX,MAAQ,CAER,CAER,CAEA,OADA,MAAM,EAAW,oBAAoB,CAAW,EACzC,UACX"}