tempest-react-sdk 0.38.2 → 0.38.3

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 (291) hide show
  1. package/bin/lib/design/collect.mjs +19 -0
  2. package/bin/lib/design/functions.mjs +5 -0
  3. package/bin/lib/design/index.mjs +8 -3
  4. package/bin/lib/design/index.test.mjs +31 -0
  5. package/bin/lib/design/scan.mjs +34 -2
  6. package/bin/lib/design/scan.test.mjs +78 -0
  7. package/bin/lib/doctor/doctor.e2e.test.mjs +85 -0
  8. package/bin/tempest.mjs +90 -6
  9. package/dist/audio/level-meter.cjs.map +1 -1
  10. package/dist/audio/level-meter.js.map +1 -1
  11. package/dist/audio/use-audio-recorder.cjs.map +1 -1
  12. package/dist/audio/use-audio-recorder.js.map +1 -1
  13. package/dist/auth/passkey.cjs.map +1 -1
  14. package/dist/auth/passkey.js.map +1 -1
  15. package/dist/auth/use-passkey.cjs.map +1 -1
  16. package/dist/auth/use-passkey.js.map +1 -1
  17. package/dist/br/BrazilMap.cjs.map +1 -1
  18. package/dist/br/BrazilMap.js.map +1 -1
  19. package/dist/br/BrazilStateCitySelect.cjs.map +1 -1
  20. package/dist/br/BrazilStateCitySelect.js.map +1 -1
  21. package/dist/br/BrazilStateMap.cjs.map +1 -1
  22. package/dist/br/BrazilStateMap.js.map +1 -1
  23. package/dist/br/PixQRCode.cjs.map +1 -1
  24. package/dist/br/PixQRCode.js.map +1 -1
  25. package/dist/br/boleto.cjs.map +1 -1
  26. package/dist/br/boleto.js.map +1 -1
  27. package/dist/br/pix.cjs.map +1 -1
  28. package/dist/br/pix.js.map +1 -1
  29. package/dist/br.d.ts +8 -0
  30. package/dist/capture/media-recorder.cjs.map +1 -1
  31. package/dist/capture/media-recorder.js.map +1 -1
  32. package/dist/capture/use-barcode-scanner.cjs.map +1 -1
  33. package/dist/capture/use-barcode-scanner.js.map +1 -1
  34. package/dist/capture/use-screen-capture.cjs.map +1 -1
  35. package/dist/capture/use-screen-capture.js.map +1 -1
  36. package/dist/capture/use-speech-recognition.cjs.map +1 -1
  37. package/dist/capture/use-speech-recognition.js.map +1 -1
  38. package/dist/capture/use-video-recorder.cjs.map +1 -1
  39. package/dist/capture/use-video-recorder.js.map +1 -1
  40. package/dist/charts/types.cjs.map +1 -1
  41. package/dist/charts/types.js.map +1 -1
  42. package/dist/charts.d.ts +8 -0
  43. package/dist/components/AIChat/AIChat.cjs.map +1 -1
  44. package/dist/components/AIChat/AIChat.js.map +1 -1
  45. package/dist/components/AIChat/AIChatComposer.cjs.map +1 -1
  46. package/dist/components/AIChat/AIChatComposer.js.map +1 -1
  47. package/dist/components/AIChat/AIChatTurn.cjs.map +1 -1
  48. package/dist/components/AIChat/AIChatTurn.js.map +1 -1
  49. package/dist/components/AppBar/AppBar.cjs.map +1 -1
  50. package/dist/components/AppBar/AppBar.js.map +1 -1
  51. package/dist/components/AudioPlayer/AudioPlayer.cjs.map +1 -1
  52. package/dist/components/AudioPlayer/AudioPlayer.js.map +1 -1
  53. package/dist/components/AudioRecorder/AudioRecorder.cjs.map +1 -1
  54. package/dist/components/AudioRecorder/AudioRecorder.js.map +1 -1
  55. package/dist/components/BarcodeScanner/BarcodeScanner.cjs.map +1 -1
  56. package/dist/components/BarcodeScanner/BarcodeScanner.js.map +1 -1
  57. package/dist/components/Button/Button.cjs.map +1 -1
  58. package/dist/components/Button/Button.js.map +1 -1
  59. package/dist/components/Calendar/Calendar.cjs.map +1 -1
  60. package/dist/components/Calendar/Calendar.js.map +1 -1
  61. package/dist/components/Carousel/Carousel.cjs.map +1 -1
  62. package/dist/components/Carousel/Carousel.js.map +1 -1
  63. package/dist/components/Chat/Chat.cjs.map +1 -1
  64. package/dist/components/Chat/Chat.js.map +1 -1
  65. package/dist/components/ChipInput/ChipInput.cjs.map +1 -1
  66. package/dist/components/ChipInput/ChipInput.js.map +1 -1
  67. package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -1
  68. package/dist/components/CodeBlock/CodeBlock.js.map +1 -1
  69. package/dist/components/Combobox/Combobox.cjs.map +1 -1
  70. package/dist/components/Combobox/Combobox.js.map +1 -1
  71. package/dist/components/Command/Command.cjs.map +1 -1
  72. package/dist/components/Command/Command.js.map +1 -1
  73. package/dist/components/ConfirmDialog/ConfirmDialog.cjs.map +1 -1
  74. package/dist/components/ConfirmDialog/ConfirmDialog.js.map +1 -1
  75. package/dist/components/ContextMenu/ContextMenu.cjs.map +1 -1
  76. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  77. package/dist/components/CopyButton/CopyButton.cjs.map +1 -1
  78. package/dist/components/CopyButton/CopyButton.js.map +1 -1
  79. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  80. package/dist/components/DataTable/DataTable.js.map +1 -1
  81. package/dist/components/DataTable/DataTable.module.cjs.map +1 -1
  82. package/dist/components/DataTable/DataTable.module.js.map +1 -1
  83. package/dist/components/DataTable/EditableCell.cjs.map +1 -1
  84. package/dist/components/DataTable/EditableCell.js.map +1 -1
  85. package/dist/components/DatePicker/DatePicker.cjs.map +1 -1
  86. package/dist/components/DatePicker/DatePicker.js.map +1 -1
  87. package/dist/components/DateRangePicker/DateRangePicker.cjs.map +1 -1
  88. package/dist/components/DateRangePicker/DateRangePicker.js.map +1 -1
  89. package/dist/components/Drawer/Drawer.cjs.map +1 -1
  90. package/dist/components/Drawer/Drawer.js.map +1 -1
  91. package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
  92. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  93. package/dist/components/Dropzone/Dropzone.cjs.map +1 -1
  94. package/dist/components/Dropzone/Dropzone.js.map +1 -1
  95. package/dist/components/FileUpload/FileUpload.cjs.map +1 -1
  96. package/dist/components/FileUpload/FileUpload.js.map +1 -1
  97. package/dist/components/FilterBar/FilterBar.cjs.map +1 -1
  98. package/dist/components/FilterBar/FilterBar.js.map +1 -1
  99. package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -1
  100. package/dist/components/ImageCropper/ImageCropper.js.map +1 -1
  101. package/dist/components/InstallBanner/InstallBanner.cjs.map +1 -1
  102. package/dist/components/InstallBanner/InstallBanner.js.map +1 -1
  103. package/dist/components/Kanban/Kanban.cjs.map +1 -1
  104. package/dist/components/Kanban/Kanban.js.map +1 -1
  105. package/dist/components/Lightbox/Lightbox.cjs.map +1 -1
  106. package/dist/components/Lightbox/Lightbox.js.map +1 -1
  107. package/dist/components/ListTile/ListTile.cjs.map +1 -1
  108. package/dist/components/ListTile/ListTile.js.map +1 -1
  109. package/dist/components/Markdown/Markdown.cjs.map +1 -1
  110. package/dist/components/Markdown/Markdown.js.map +1 -1
  111. package/dist/components/Markdown/markdown-parse.cjs.map +1 -1
  112. package/dist/components/Markdown/markdown-parse.js.map +1 -1
  113. package/dist/components/Menubar/Menubar.cjs.map +1 -1
  114. package/dist/components/Menubar/Menubar.js.map +1 -1
  115. package/dist/components/Modal/Modal.cjs.map +1 -1
  116. package/dist/components/Modal/Modal.js.map +1 -1
  117. package/dist/components/ModalsManager/ModalsManager.cjs.map +1 -1
  118. package/dist/components/ModalsManager/ModalsManager.js.map +1 -1
  119. package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
  120. package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
  121. package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
  122. package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
  123. package/dist/components/NotificationCenter/NotificationCenter.cjs.map +1 -1
  124. package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -1
  125. package/dist/components/Page/Page.cjs.map +1 -1
  126. package/dist/components/Page/Page.js.map +1 -1
  127. package/dist/components/Pagination/Pagination.cjs.map +1 -1
  128. package/dist/components/Pagination/Pagination.js.map +1 -1
  129. package/dist/components/PasswordInput/PasswordInput.cjs.map +1 -1
  130. package/dist/components/PasswordInput/PasswordInput.js.map +1 -1
  131. package/dist/components/PinInput/PinInput.cjs.map +1 -1
  132. package/dist/components/PinInput/PinInput.js.map +1 -1
  133. package/dist/components/Popover/Popover.cjs.map +1 -1
  134. package/dist/components/Popover/Popover.js.map +1 -1
  135. package/dist/components/Progress/Progress.cjs.map +1 -1
  136. package/dist/components/Progress/Progress.js.map +1 -1
  137. package/dist/components/QRCode/qr-encode.cjs.map +1 -1
  138. package/dist/components/QRCode/qr-encode.js.map +1 -1
  139. package/dist/components/Radio/Radio.cjs.map +1 -1
  140. package/dist/components/Radio/Radio.js.map +1 -1
  141. package/dist/components/RangeSlider/RangeSlider.cjs.map +1 -1
  142. package/dist/components/RangeSlider/RangeSlider.js.map +1 -1
  143. package/dist/components/RatingStars/RatingStars.cjs.map +1 -1
  144. package/dist/components/RatingStars/RatingStars.js.map +1 -1
  145. package/dist/components/RefreshIndicator/RefreshIndicator.cjs.map +1 -1
  146. package/dist/components/RefreshIndicator/RefreshIndicator.js.map +1 -1
  147. package/dist/components/Resizable/Resizable.cjs.map +1 -1
  148. package/dist/components/Resizable/Resizable.js.map +1 -1
  149. package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
  150. package/dist/components/Scheduler/Scheduler.js.map +1 -1
  151. package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
  152. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  153. package/dist/components/SignaturePad/SignaturePad.cjs.map +1 -1
  154. package/dist/components/SignaturePad/SignaturePad.js.map +1 -1
  155. package/dist/components/Slider/Slider.cjs.map +1 -1
  156. package/dist/components/Slider/Slider.js.map +1 -1
  157. package/dist/components/Sparkline/Sparkline.cjs.map +1 -1
  158. package/dist/components/Sparkline/Sparkline.js.map +1 -1
  159. package/dist/components/StepperInput/StepperInput.cjs.map +1 -1
  160. package/dist/components/StepperInput/StepperInput.js.map +1 -1
  161. package/dist/components/Table/Table.cjs.map +1 -1
  162. package/dist/components/Table/Table.js.map +1 -1
  163. package/dist/components/TimePicker/TimePicker.cjs.map +1 -1
  164. package/dist/components/TimePicker/TimePicker.js.map +1 -1
  165. package/dist/components/Toast/ToastProvider.cjs.map +1 -1
  166. package/dist/components/Toast/ToastProvider.js.map +1 -1
  167. package/dist/components/Tour/Tour.cjs.map +1 -1
  168. package/dist/components/Tour/Tour.js.map +1 -1
  169. package/dist/components/Transfer/Transfer.cjs.map +1 -1
  170. package/dist/components/Transfer/Transfer.js.map +1 -1
  171. package/dist/components/TreeView/TreeView.cjs.map +1 -1
  172. package/dist/components/TreeView/TreeView.js.map +1 -1
  173. package/dist/components/VirtualList/VirtualList.cjs.map +1 -1
  174. package/dist/components/VirtualList/VirtualList.js.map +1 -1
  175. package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -1
  176. package/dist/components/VirtualTable/VirtualTable.js.map +1 -1
  177. package/dist/components/Wizard/Wizard.cjs.map +1 -1
  178. package/dist/components/Wizard/Wizard.js.map +1 -1
  179. package/dist/editor/RichTextEditor.cjs.map +1 -1
  180. package/dist/editor/RichTextEditor.js.map +1 -1
  181. package/dist/forms/FormField.cjs.map +1 -1
  182. package/dist/forms/FormField.js.map +1 -1
  183. package/dist/geo/TrajectoryMap.cjs.map +1 -1
  184. package/dist/geo/TrajectoryMap.js.map +1 -1
  185. package/dist/geo/estimate.cjs.map +1 -1
  186. package/dist/geo/estimate.js.map +1 -1
  187. package/dist/geo/projection.cjs.map +1 -1
  188. package/dist/geo/projection.js.map +1 -1
  189. package/dist/hooks/use-event-listener.cjs.map +1 -1
  190. package/dist/hooks/use-event-listener.js.map +1 -1
  191. package/dist/hooks/use-local-storage.cjs.map +1 -1
  192. package/dist/hooks/use-local-storage.js.map +1 -1
  193. package/dist/hooks/use-sortable.cjs.map +1 -1
  194. package/dist/hooks/use-sortable.js.map +1 -1
  195. package/dist/http/api-client.cjs.map +1 -1
  196. package/dist/http/api-client.js.map +1 -1
  197. package/dist/http/errors.cjs.map +1 -1
  198. package/dist/http/errors.js.map +1 -1
  199. package/dist/http/resumable-upload.cjs.map +1 -1
  200. package/dist/http/resumable-upload.js.map +1 -1
  201. package/dist/http/upload-with-progress.cjs.map +1 -1
  202. package/dist/http/upload-with-progress.js.map +1 -1
  203. package/dist/i18n/I18nProvider.cjs.map +1 -1
  204. package/dist/i18n/I18nProvider.js.map +1 -1
  205. package/dist/imaging/canvas.cjs.map +1 -1
  206. package/dist/imaging/canvas.js.map +1 -1
  207. package/dist/imaging.d.ts +5 -0
  208. package/dist/logger/logger.cjs.map +1 -1
  209. package/dist/logger/logger.js.map +1 -1
  210. package/dist/oauth/GoogleSignIn.cjs.map +1 -1
  211. package/dist/oauth/GoogleSignIn.js.map +1 -1
  212. package/dist/offline/create-offline-sync.cjs.map +1 -1
  213. package/dist/offline/create-offline-sync.js.map +1 -1
  214. package/dist/perf/cache-size.cjs +1 -1
  215. package/dist/perf/cache-size.cjs.map +1 -1
  216. package/dist/perf/cache-size.js +27 -6
  217. package/dist/perf/cache-size.js.map +1 -1
  218. package/dist/sse/create-event-stream.cjs.map +1 -1
  219. package/dist/sse/create-event-stream.js.map +1 -1
  220. package/dist/styles.css +1 -1
  221. package/dist/sw/background-sync.cjs.map +1 -1
  222. package/dist/sw/background-sync.js.map +1 -1
  223. package/dist/sw/cache.cjs.map +1 -1
  224. package/dist/sw/cache.js.map +1 -1
  225. package/dist/sw.d.ts +12 -0
  226. package/dist/tabular/compact.cjs.map +1 -1
  227. package/dist/tabular/compact.js.map +1 -1
  228. package/dist/tabular/predictor.cjs.map +1 -1
  229. package/dist/tabular/predictor.js.map +1 -1
  230. package/dist/tabular.d.ts +7 -2
  231. package/dist/tempest-react-sdk.d.ts +133 -6
  232. package/dist/theme/ThemeProvider.cjs.map +1 -1
  233. package/dist/theme/ThemeProvider.js.map +1 -1
  234. package/dist/theme/color.cjs.map +1 -1
  235. package/dist/theme/color.js.map +1 -1
  236. package/dist/theme/create-theme.cjs.map +1 -1
  237. package/dist/theme/create-theme.js.map +1 -1
  238. package/dist/theme/data-viz-ramps.cjs +1 -1
  239. package/dist/theme/data-viz-ramps.cjs.map +1 -1
  240. package/dist/theme/data-viz-ramps.js +1 -1
  241. package/dist/theme/data-viz-ramps.js.map +1 -1
  242. package/dist/utils/storage.cjs.map +1 -1
  243. package/dist/utils/storage.js.map +1 -1
  244. package/dist/vision/core/canvas.cjs.map +1 -1
  245. package/dist/vision/core/canvas.js.map +1 -1
  246. package/dist/vision/core/exceptions.cjs.map +1 -1
  247. package/dist/vision/core/exceptions.js.map +1 -1
  248. package/dist/vision/core/graph.cjs.map +1 -1
  249. package/dist/vision/core/graph.js.map +1 -1
  250. package/dist/vision/core/metadata.cjs.map +1 -1
  251. package/dist/vision/core/metadata.js.map +1 -1
  252. package/dist/vision/core/providers.cjs.map +1 -1
  253. package/dist/vision/core/providers.js.map +1 -1
  254. package/dist/vision/core/session.cjs.map +1 -1
  255. package/dist/vision/core/session.js.map +1 -1
  256. package/dist/vision/core/timing.cjs.map +1 -1
  257. package/dist/vision/core/timing.js.map +1 -1
  258. package/dist/vision/io/image.cjs.map +1 -1
  259. package/dist/vision/io/image.js.map +1 -1
  260. package/dist/vision/labels.cjs.map +1 -1
  261. package/dist/vision/labels.js.map +1 -1
  262. package/dist/vision/luminance.cjs.map +1 -1
  263. package/dist/vision/luminance.js.map +1 -1
  264. package/dist/vision/postprocess/classification.cjs.map +1 -1
  265. package/dist/vision/postprocess/classification.js.map +1 -1
  266. package/dist/vision/postprocess/detection.cjs.map +1 -1
  267. package/dist/vision/postprocess/detection.js.map +1 -1
  268. package/dist/vision/postprocess/segmentation.cjs.map +1 -1
  269. package/dist/vision/postprocess/segmentation.js.map +1 -1
  270. package/dist/vision/preprocess/image.cjs.map +1 -1
  271. package/dist/vision/preprocess/image.js.map +1 -1
  272. package/dist/vision/results.cjs.map +1 -1
  273. package/dist/vision/results.js.map +1 -1
  274. package/dist/vision/tasks/base.cjs.map +1 -1
  275. package/dist/vision/tasks/base.js.map +1 -1
  276. package/dist/vision/tasks/classifier.cjs.map +1 -1
  277. package/dist/vision/tasks/classifier.js.map +1 -1
  278. package/dist/vision/tasks/detector.cjs.map +1 -1
  279. package/dist/vision/tasks/detector.js.map +1 -1
  280. package/dist/vision/tasks/segmenter.cjs.map +1 -1
  281. package/dist/vision/tasks/segmenter.js.map +1 -1
  282. package/dist/vision/types.cjs.map +1 -1
  283. package/dist/vision/types.js.map +1 -1
  284. package/dist/vision.d.ts +6 -0
  285. package/dist/vite/tempest-icons.cjs.map +1 -1
  286. package/dist/vite/tempest-icons.js.map +1 -1
  287. package/dist/vite/tempest-pwa-icons.cjs.map +1 -1
  288. package/dist/vite/tempest-pwa-icons.js.map +1 -1
  289. package/dist/ws/create-web-socket.cjs.map +1 -1
  290. package/dist/ws/create-web-socket.js.map +1 -1
  291. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"predictor.cjs","names":[],"sources":["../../src/tabular/predictor.ts"],"sourcesContent":["/**\n * Running a scikit-learn model in the browser, offline.\n *\n * The model file is produced by `tempest-fastapi-sdk`'s\n * `export_sklearn_to_onnx`. This is everything between that file and an\n * answer — the same glue the Python `OnnxPredictor` provides on a device,\n * with the browser's own traps handled:\n *\n * - **int64 labels arrive as `bigint`.** ONNX Runtime Web surfaces the\n * label tensor as a `BigInt64Array`, so a caller comparing `label === 1`\n * silently gets `false` and `JSON.stringify` throws. Labels are converted.\n * - **`ai.onnx.ml` needs the right build.** Measured: importing\n * `onnxruntime-web/webgpu` loads a WebAssembly binary without those\n * operators, and session creation fails with `No Op registered for\n * TreeEnsembleClassifier`. That failure is translated into an error that\n * names the import.\n * - **Which output is which.** A classifier returns `label` and\n * `probabilities`; a regressor returns a single `variable`. Indexing by\n * position works until the day you deploy the other kind.\n */\n\nimport type * as ort from \"onnxruntime-web\";\n\nimport { configuredOrtAssetPath } from \"./assets\";\nimport {\n FeatureShapeError,\n InferenceError,\n ModelLoadError,\n UnsupportedGraphError,\n} from \"./exceptions\";\nimport type {\n FeatureRow,\n PredictedLabel,\n TabularModelSource,\n TabularPrediction,\n TabularPredictorInfo,\n TabularPredictorOptions,\n} from \"./types\";\n\n/**\n * Import ONNX Runtime Web, only when a model is actually being loaded.\n *\n * Static import would make every consumer of this subpath install the peer,\n * including apps that only ever touch `CompactPredictor` — whose whole\n * point is not needing a runtime. Found by installing the published\n * package into an empty project, which is the only place the difference\n * shows.\n *\n * @returns The runtime module.\n * @throws {@link ModelLoadError} when the peer is not installed, naming it.\n */\nasync function loadRuntime(): Promise<typeof ort> {\n try {\n return (await import(\"onnxruntime-web\")) as typeof ort;\n } catch (error) {\n throw new ModelLoadError(\n \"The ONNX route needs the optional peer dependency: \" +\n \"npm install onnxruntime-web. For a model with no runtime at \" +\n \"all, export it with edge_pipeline(compact=True) and load it \" +\n \"through CompactPredictor.\",\n { cause: error },\n );\n }\n}\n\n/**\n * Execution providers used when the caller does not choose.\n *\n * WebAssembly only, and deliberately: scikit-learn graphs are `ai.onnx.ml`\n * operators, which the WebGPU backend does not implement. There is no\n * speed left on the table here — a 10-tree forest predicts a row in about\n * 0.05 ms in Chromium.\n */\nexport const DEFAULT_TABULAR_PROVIDERS: readonly string[] = [\"wasm\"];\n\n/** Output names that indicate predicted classes rather than scores. */\nconst LABEL_HINTS = [\"label\", \"class\", \"variable\", \"output\"] as const;\n\n/** Output names that indicate class scores. */\nconst PROBABILITY_HINTS = [\"probabilit\", \"score\"] as const;\n\n/** Largest plausible feature count; anything above is a dynamic-dim sentinel. */\nconst MAX_DECLARED_FEATURES = 1_000_000;\n\n/**\n * Pick the first output whose name contains one of `hints`.\n *\n * @param names Graph output names.\n * @param hints Lowercase substrings to look for.\n * @returns The matching name, or `null`.\n */\nfunction matchOutput(names: readonly string[], hints: readonly string[]): string | null {\n for (const hint of hints) {\n const found = names.find((name) => name.toLowerCase().includes(hint));\n if (found !== undefined) return found;\n }\n return null;\n}\n\n/**\n * Read the declared feature count from the input metadata.\n *\n * A dynamic batch dimension is reported as a symbolic string or as an\n * out-of-range number (`4294967295` — an unsigned `-1`), so only a sane\n * positive integer in the second position is trusted.\n *\n * @param session The loaded session.\n * @returns The feature count, or `null` when the graph does not declare one.\n */\nfunction declaredFeatures(session: ort.InferenceSession): number | null {\n const metadata = session.inputMetadata?.[0];\n if (metadata === undefined || metadata.isTensor !== true) return null;\n const dimension = metadata.shape[1];\n if (typeof dimension !== \"number\") return null;\n if (!Number.isInteger(dimension) || dimension <= 0) return null;\n return dimension > MAX_DECLARED_FEATURES ? null : dimension;\n}\n\n/**\n * Convert one raw label value into a JS-friendly label.\n *\n * @param value A tensor element: `bigint` for int64, `number` for float,\n * `string` for a string-labelled classifier.\n * @returns The label as a number or string.\n */\nfunction toLabel(value: unknown): PredictedLabel {\n if (typeof value === \"bigint\") return Number(value);\n if (typeof value === \"number\") return value;\n return String(value);\n}\n\n/**\n * Translate a session-creation failure into an error naming its cause.\n *\n * @param error Whatever ONNX Runtime threw.\n * @returns The error to surface.\n */\nfunction asLoadError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes(\"No Op registered\")) {\n return new UnsupportedGraphError(\n \"This runtime build has no kernels for the model's operators. \" +\n \"scikit-learn exports use the ai.onnx.ml domain, which is missing \" +\n 'from the WebGPU build: import \"onnxruntime-web\", not ' +\n '\"onnxruntime-web/webgpu\". Original error: ' +\n message,\n { cause: error },\n );\n }\n return new ModelLoadError(`Failed to load the model: ${message}`, { cause: error });\n}\n\n/**\n * Translate a run failure into an error naming its cause.\n *\n * Measured: an export made with skl2onnx's default (ZipMap enabled) has a\n * probability output that is a sequence of maps, and ONNX Runtime Web\n * refuses to read non-tensor values — `Reading data from non-tensor typed\n * value is not supported`. That message describes the runtime's limitation,\n * not the fix, so it is replaced by one that names the export flag.\n *\n * @param error Whatever ONNX Runtime threw.\n * @returns The error to surface.\n */\nfunction asRunError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (\n message.includes(\"non-tensor typed value\") ||\n message.includes(\"Can't access output tensor data\")\n ) {\n return new InferenceError(\n \"The model has a non-tensor output, which ONNX Runtime Web cannot \" +\n \"read. A scikit-learn export made with ZipMap enabled returns a \" +\n \"sequence of maps per row — re-export with export_sklearn_to_onnx, \" +\n `which disables it. Original error: ${message}`,\n { cause: error },\n );\n }\n return new InferenceError(`Inference failed: ${message}`, { cause: error });\n}\n\n/**\n * A loaded tabular model, ready to answer.\n *\n * @example\n * ```ts\n * const predictor = await TabularPredictor.create(\"/models/classifier.onnx\");\n * const { labels, probabilities } = await predictor.predict([[5.1, 3.5, 1.4, 0.2]]);\n * ```\n */\nexport class TabularPredictor {\n private constructor(\n private readonly runtime: typeof ort,\n private readonly session: ort.InferenceSession,\n /** What is loaded and how it is configured. */\n public readonly info: TabularPredictorInfo,\n ) {}\n\n /**\n * Load a model and describe its graph.\n *\n * @param source A URL string, or the model bytes (which is what an\n * offline app passes, having read them from the cache).\n * @param options Providers, warm-up and pass-through session options.\n * @throws {@link UnsupportedGraphError} when the runtime build lacks the\n * `ai.onnx.ml` operators — the WebGPU entry point does.\n * @throws {@link ModelLoadError} for any other load failure.\n */\n static async create(\n source: TabularModelSource,\n options: TabularPredictorOptions = {},\n ): Promise<TabularPredictor> {\n const providers = options.providers ?? DEFAULT_TABULAR_PROVIDERS;\n const runtime = await loadRuntime();\n const assets = configuredOrtAssetPath();\n if (assets !== undefined) runtime.env.wasm.wasmPaths = assets;\n\n let session: ort.InferenceSession;\n try {\n session = await runtime.InferenceSession.create(source as never, {\n ...(options.sessionOptions ?? {}),\n executionProviders:\n providers as ort.InferenceSession.SessionOptions[\"executionProviders\"],\n });\n } catch (error) {\n throw asLoadError(error);\n }\n\n const outputNames = [...session.outputNames];\n const probabilityOutput = matchOutput(outputNames, PROBABILITY_HINTS);\n const labelOutput =\n outputNames.find((name) => name !== probabilityOutput && isLabelName(name)) ??\n outputNames.find((name) => name !== probabilityOutput) ??\n (outputNames[0] as string);\n\n const predictor = new TabularPredictor(runtime, session, {\n inputName: session.inputNames[0] as string,\n numFeatures: declaredFeatures(session),\n outputNames,\n labelOutput,\n probabilityOutput,\n isClassifier: probabilityOutput !== null,\n providers,\n });\n\n if (options.warmup !== false) await predictor.warmUp();\n return predictor;\n }\n\n /**\n * Run one throwaway inference so the first real call is not the slow one.\n *\n * Skipped when the graph does not declare a feature count, since there\n * is no shape to synthesise. Failures are swallowed: a warm-up that\n * cannot run is not a reason to refuse to serve.\n */\n async warmUp(): Promise<void> {\n const features = this.info.numFeatures;\n if (features === null) return;\n try {\n await this.predict([new Array<number>(features).fill(0)]);\n } catch {\n /* a failed warm-up must not prevent serving */\n }\n }\n\n /**\n * Predict for a batch of rows.\n *\n * @param rows One array of feature values per row, in training column\n * order. A single row is still wrapped: `[[...]]`.\n * @returns Labels, class scores when the model produces them, and the\n * call's duration.\n * @throws {@link FeatureShapeError} when the batch is empty, ragged, or\n * the wrong width — checked here so the failure names the mismatch\n * instead of surfacing as an opaque runtime error.\n * @throws {@link InferenceError} when the session runs but its outputs\n * cannot be read.\n */\n async predict(rows: readonly FeatureRow[]): Promise<TabularPrediction> {\n if (!Array.isArray(rows) || rows.length === 0) {\n throw new FeatureShapeError(\n \"predict() needs at least one row, shaped [[f1, f2, ...]].\",\n );\n }\n const width = rows[0]?.length ?? 0;\n if (width === 0) {\n throw new FeatureShapeError(\"The first row has no feature values.\");\n }\n const ragged = rows.findIndex((row) => row.length !== width);\n if (ragged !== -1) {\n throw new FeatureShapeError(\n `All rows must have the same width; row ${ragged} has ` +\n `${rows[ragged]?.length} values, expected ${width}.`,\n );\n }\n const expected = this.info.numFeatures;\n if (expected !== null && width !== expected) {\n throw new FeatureShapeError(\n `The model expects ${expected} features per row, got ${width}.`,\n );\n }\n\n const flat = new Float32Array(rows.length * width);\n for (let index = 0; index < rows.length; index += 1) {\n flat.set(rows[index] as number[], index * width);\n }\n const tensor = new this.runtime.Tensor(\"float32\", flat, [rows.length, width]);\n\n const started = performance.now();\n let outputs: ort.InferenceSession.OnnxValueMapType;\n try {\n outputs = await this.session.run({ [this.info.inputName]: tensor });\n } catch (error) {\n throw asRunError(error);\n }\n const ms = performance.now() - started;\n\n const labelTensor = outputs[this.info.labelOutput];\n if (labelTensor?.data === undefined) {\n throw new InferenceError(\n `The model produced no readable \"${this.info.labelOutput}\" output.`,\n );\n }\n\n const labels: PredictedLabel[] = Array.from(\n labelTensor.data as ArrayLike<unknown>,\n toLabel,\n );\n\n const probabilities: number[][] = [];\n if (this.info.probabilityOutput !== null) {\n const scores = outputs[this.info.probabilityOutput];\n if (scores?.data !== undefined) {\n const values = Array.from(scores.data as ArrayLike<number>, Number);\n const classes = values.length / rows.length;\n for (let index = 0; index < rows.length; index += 1) {\n probabilities.push(values.slice(index * classes, (index + 1) * classes));\n }\n }\n }\n\n return { labels, probabilities, numRows: rows.length, ms };\n }\n\n /**\n * Release the session's memory.\n *\n * Worth calling on a route that swaps models: the WebAssembly heap does\n * not shrink on garbage collection alone.\n */\n async dispose(): Promise<void> {\n await this.session.release?.();\n }\n}\n\n/**\n * Whether an output name looks like a label rather than a score.\n *\n * @param name The graph output name.\n * @returns `true` when the name matches a known label convention.\n */\nfunction isLabelName(name: string): boolean {\n const lowered = name.toLowerCase();\n return LABEL_HINTS.some((hint) => lowered.includes(hint));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"predictor.cjs","names":[],"sources":["../../src/tabular/predictor.ts"],"sourcesContent":["/**\n * Running a scikit-learn model in the browser, offline.\n *\n * The model file is produced by `tempest-fastapi-sdk`'s\n * `export_sklearn_to_onnx`. This is everything between that file and an\n * answer — the same glue the Python `OnnxPredictor` provides on a device,\n * with the browser's own traps handled:\n *\n * - **int64 labels arrive as `bigint`.** ONNX Runtime Web surfaces the\n * label tensor as a `BigInt64Array`, so a caller comparing `label === 1`\n * silently gets `false` and `JSON.stringify` throws. Labels are converted.\n * - **`ai.onnx.ml` needs the right build.** Measured: importing\n * `onnxruntime-web/webgpu` loads a WebAssembly binary without those\n * operators, and session creation fails with `No Op registered for\n * TreeEnsembleClassifier`. That failure is translated into an error that\n * names the import.\n * - **Which output is which.** A classifier returns `label` and\n * `probabilities`; a regressor returns a single `variable`. Indexing by\n * position works until the day you deploy the other kind.\n */\n\nimport type * as ort from \"onnxruntime-web\";\n\nimport { configuredOrtAssetPath } from \"./assets\";\nimport {\n FeatureShapeError,\n InferenceError,\n ModelLoadError,\n UnsupportedGraphError,\n} from \"./exceptions\";\nimport type {\n FeatureRow,\n PredictedLabel,\n TabularModelSource,\n TabularPrediction,\n TabularPredictorInfo,\n TabularPredictorOptions,\n} from \"./types\";\n\n/**\n * Import ONNX Runtime Web, only when a model is actually being loaded.\n *\n * Static import would make every consumer of this subpath install the peer,\n * including apps that only ever touch `CompactPredictor` — whose whole\n * point is not needing a runtime. Found by installing the published\n * package into an empty project, which is the only place the difference\n * shows.\n *\n * @returns The runtime module.\n * @throws {@link ModelLoadError} when the peer is not installed, naming it.\n */\nasync function loadRuntime(): Promise<typeof ort> {\n try {\n return (await import(\"onnxruntime-web\")) as typeof ort;\n } catch (error) {\n throw new ModelLoadError(\n \"The ONNX route needs the optional peer dependency: \" +\n \"npm install onnxruntime-web. For a model with no runtime at \" +\n \"all, export it with edge_pipeline(compact=True) and load it \" +\n \"through CompactPredictor.\",\n { cause: error },\n );\n }\n}\n\n/**\n * Execution providers used when the caller does not choose.\n *\n * WebAssembly only, and deliberately: scikit-learn graphs are `ai.onnx.ml`\n * operators, which the WebGPU backend does not implement. There is no\n * speed left on the table here — a 10-tree forest predicts a row in about\n * 0.05 ms in Chromium.\n */\nexport const DEFAULT_TABULAR_PROVIDERS: readonly string[] = [\"wasm\"];\n\n/** Output names that indicate predicted classes rather than scores. */\nconst LABEL_HINTS = [\"label\", \"class\", \"variable\", \"output\"] as const;\n\n/** Output names that indicate class scores. */\nconst PROBABILITY_HINTS = [\"probabilit\", \"score\"] as const;\n\n/** Largest plausible feature count; anything above is a dynamic-dim sentinel. */\nconst MAX_DECLARED_FEATURES = 1_000_000;\n\n/**\n * Pick the first output whose name contains one of `hints`.\n *\n * @param names Graph output names.\n * @param hints Lowercase substrings to look for.\n * @returns The matching name, or `null`.\n */\nfunction matchOutput(names: readonly string[], hints: readonly string[]): string | null {\n for (const hint of hints) {\n const found = names.find((name) => name.toLowerCase().includes(hint));\n if (found !== undefined) return found;\n }\n return null;\n}\n\n/**\n * Read the declared feature count from the input metadata.\n *\n * A dynamic batch dimension is reported as a symbolic string or as an\n * out-of-range number (`4294967295` — an unsigned `-1`), so only a sane\n * positive integer in the second position is trusted.\n *\n * @param session The loaded session.\n * @returns The feature count, or `null` when the graph does not declare one.\n */\nfunction declaredFeatures(session: ort.InferenceSession): number | null {\n const metadata = session.inputMetadata?.[0];\n if (metadata === undefined || metadata.isTensor !== true) return null;\n const dimension = metadata.shape[1];\n if (typeof dimension !== \"number\") return null;\n if (!Number.isInteger(dimension) || dimension <= 0) return null;\n return dimension > MAX_DECLARED_FEATURES ? null : dimension;\n}\n\n/**\n * Convert one raw label value into a JS-friendly label.\n *\n * @param value A tensor element: `bigint` for int64, `number` for float,\n * `string` for a string-labelled classifier.\n * @returns The label as a number or string.\n */\nfunction toLabel(value: unknown): PredictedLabel {\n if (typeof value === \"bigint\") return Number(value);\n if (typeof value === \"number\") return value;\n return String(value);\n}\n\n/**\n * Translate a session-creation failure into an error naming its cause.\n *\n * @param error Whatever ONNX Runtime threw.\n * @returns The error to surface.\n */\nfunction asLoadError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes(\"No Op registered\")) {\n return new UnsupportedGraphError(\n \"This runtime build has no kernels for the model's operators. \" +\n \"scikit-learn exports use the ai.onnx.ml domain, which is missing \" +\n 'from the WebGPU build: import \"onnxruntime-web\", not ' +\n '\"onnxruntime-web/webgpu\". Original error: ' +\n message,\n { cause: error },\n );\n }\n return new ModelLoadError(`Failed to load the model: ${message}`, { cause: error });\n}\n\n/**\n * Translate a run failure into an error naming its cause.\n *\n * Measured: an export made with skl2onnx's default (ZipMap enabled) has a\n * probability output that is a sequence of maps, and ONNX Runtime Web\n * refuses to read non-tensor values — `Reading data from non-tensor typed\n * value is not supported`. That message describes the runtime's limitation,\n * not the fix, so it is replaced by one that names the export flag.\n *\n * @param error Whatever ONNX Runtime threw.\n * @returns The error to surface.\n */\nfunction asRunError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (\n message.includes(\"non-tensor typed value\") ||\n message.includes(\"Can't access output tensor data\")\n ) {\n return new InferenceError(\n \"The model has a non-tensor output, which ONNX Runtime Web cannot \" +\n \"read. A scikit-learn export made with ZipMap enabled returns a \" +\n \"sequence of maps per row — re-export with export_sklearn_to_onnx, \" +\n `which disables it. Original error: ${message}`,\n { cause: error },\n );\n }\n return new InferenceError(`Inference failed: ${message}`, { cause: error });\n}\n\n/**\n * A loaded tabular model, ready to answer.\n *\n * @example\n * ```ts\n * const predictor = await TabularPredictor.create(\"/models/classifier.onnx\");\n * const { labels, probabilities } = await predictor.predict([[5.1, 3.5, 1.4, 0.2]]);\n * ```\n */\nexport class TabularPredictor {\n private constructor(\n private readonly runtime: typeof ort,\n private readonly session: ort.InferenceSession,\n /** What is loaded and how it is configured. */\n public readonly info: TabularPredictorInfo,\n ) {}\n\n /**\n * Load a model and describe its graph.\n *\n * @param source A URL string, or the model bytes (which is what an\n * offline app passes, having read them from the cache).\n * @param options Providers, warm-up and pass-through session options.\n * @throws {@link UnsupportedGraphError} when the runtime build lacks the\n * `ai.onnx.ml` operators — the WebGPU entry point does.\n * @throws {@link ModelLoadError} for any other load failure.\n */\n static async create(\n source: TabularModelSource,\n options: TabularPredictorOptions = {},\n ): Promise<TabularPredictor> {\n const providers = options.providers ?? DEFAULT_TABULAR_PROVIDERS;\n const runtime = await loadRuntime();\n const assets = configuredOrtAssetPath();\n if (assets !== undefined) runtime.env.wasm.wasmPaths = assets;\n\n let session: ort.InferenceSession;\n try {\n session = await runtime.InferenceSession.create(source as never, {\n ...(options.sessionOptions ?? {}),\n executionProviders:\n providers as ort.InferenceSession.SessionOptions[\"executionProviders\"],\n });\n } catch (error) {\n throw asLoadError(error);\n }\n\n const outputNames = [...session.outputNames];\n const probabilityOutput = matchOutput(outputNames, PROBABILITY_HINTS);\n const labelOutput =\n outputNames.find((name) => name !== probabilityOutput && isLabelName(name)) ??\n outputNames.find((name) => name !== probabilityOutput) ??\n (outputNames[0] as string);\n\n const predictor = new TabularPredictor(runtime, session, {\n inputName: session.inputNames[0] as string,\n numFeatures: declaredFeatures(session),\n outputNames,\n labelOutput,\n probabilityOutput,\n isClassifier: probabilityOutput !== null,\n providers,\n });\n\n if (options.warmup !== false) await predictor.warmUp();\n return predictor;\n }\n\n /**\n * Run one throwaway inference so the first real call is not the slow one.\n *\n * Skipped when the graph does not declare a feature count, since there\n * is no shape to synthesise.\n *\n * @tempest-limits empty-catch — a warm-up that cannot run is not a reason to\n * refuse to serve. The synthetic all-zero row can be rejected by a graph that\n * expects a different dtype or a categorical encoding, and that says nothing\n * about the real rows the caller will send; the only cost of the failure is\n * that the first real inference pays the lazy-init it would have paid anyway.\n */\n async warmUp(): Promise<void> {\n const features = this.info.numFeatures;\n if (features === null) return;\n try {\n await this.predict([new Array<number>(features).fill(0)]);\n } catch {\n /* empty */\n }\n }\n\n /**\n * Predict for a batch of rows.\n *\n * @param rows One array of feature values per row, in training column\n * order. A single row is still wrapped: `[[...]]`.\n * @returns Labels, class scores when the model produces them, and the\n * call's duration.\n * @throws {@link FeatureShapeError} when the batch is empty, ragged, or\n * the wrong width — checked here so the failure names the mismatch\n * instead of surfacing as an opaque runtime error.\n * @throws {@link InferenceError} when the session runs but its outputs\n * cannot be read.\n */\n async predict(rows: readonly FeatureRow[]): Promise<TabularPrediction> {\n if (!Array.isArray(rows) || rows.length === 0) {\n throw new FeatureShapeError(\n \"predict() needs at least one row, shaped [[f1, f2, ...]].\",\n );\n }\n const width = rows[0]?.length ?? 0;\n if (width === 0) {\n throw new FeatureShapeError(\"The first row has no feature values.\");\n }\n const ragged = rows.findIndex((row) => row.length !== width);\n if (ragged !== -1) {\n throw new FeatureShapeError(\n `All rows must have the same width; row ${ragged} has ` +\n `${rows[ragged]?.length} values, expected ${width}.`,\n );\n }\n const expected = this.info.numFeatures;\n if (expected !== null && width !== expected) {\n throw new FeatureShapeError(\n `The model expects ${expected} features per row, got ${width}.`,\n );\n }\n\n const flat = new Float32Array(rows.length * width);\n for (let index = 0; index < rows.length; index += 1) {\n flat.set(rows[index] as number[], index * width);\n }\n const tensor = new this.runtime.Tensor(\"float32\", flat, [rows.length, width]);\n\n const started = performance.now();\n let outputs: ort.InferenceSession.OnnxValueMapType;\n try {\n outputs = await this.session.run({ [this.info.inputName]: tensor });\n } catch (error) {\n throw asRunError(error);\n }\n const ms = performance.now() - started;\n\n const labelTensor = outputs[this.info.labelOutput];\n if (labelTensor?.data === undefined) {\n throw new InferenceError(\n `The model produced no readable \"${this.info.labelOutput}\" output.`,\n );\n }\n\n const labels: PredictedLabel[] = Array.from(\n labelTensor.data as ArrayLike<unknown>,\n toLabel,\n );\n\n const probabilities: number[][] = [];\n if (this.info.probabilityOutput !== null) {\n const scores = outputs[this.info.probabilityOutput];\n if (scores?.data !== undefined) {\n const values = Array.from(scores.data as ArrayLike<number>, Number);\n const classes = values.length / rows.length;\n for (let index = 0; index < rows.length; index += 1) {\n probabilities.push(values.slice(index * classes, (index + 1) * classes));\n }\n }\n }\n\n return { labels, probabilities, numRows: rows.length, ms };\n }\n\n /**\n * Release the session's memory.\n *\n * Worth calling on a route that swaps models: the WebAssembly heap does\n * not shrink on garbage collection alone.\n */\n async dispose(): Promise<void> {\n await this.session.release?.();\n }\n}\n\n/**\n * Whether an output name looks like a label rather than a score.\n *\n * @param name The graph output name.\n * @returns `true` when the name matches a known label convention.\n */\nfunction isLabelName(name: string): boolean {\n const lowered = name.toLowerCase();\n return LABEL_HINTS.some((hint) => lowered.includes(hint));\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"predictor.js","names":[],"sources":["../../src/tabular/predictor.ts"],"sourcesContent":["/**\n * Running a scikit-learn model in the browser, offline.\n *\n * The model file is produced by `tempest-fastapi-sdk`'s\n * `export_sklearn_to_onnx`. This is everything between that file and an\n * answer — the same glue the Python `OnnxPredictor` provides on a device,\n * with the browser's own traps handled:\n *\n * - **int64 labels arrive as `bigint`.** ONNX Runtime Web surfaces the\n * label tensor as a `BigInt64Array`, so a caller comparing `label === 1`\n * silently gets `false` and `JSON.stringify` throws. Labels are converted.\n * - **`ai.onnx.ml` needs the right build.** Measured: importing\n * `onnxruntime-web/webgpu` loads a WebAssembly binary without those\n * operators, and session creation fails with `No Op registered for\n * TreeEnsembleClassifier`. That failure is translated into an error that\n * names the import.\n * - **Which output is which.** A classifier returns `label` and\n * `probabilities`; a regressor returns a single `variable`. Indexing by\n * position works until the day you deploy the other kind.\n */\n\nimport type * as ort from \"onnxruntime-web\";\n\nimport { configuredOrtAssetPath } from \"./assets\";\nimport {\n FeatureShapeError,\n InferenceError,\n ModelLoadError,\n UnsupportedGraphError,\n} from \"./exceptions\";\nimport type {\n FeatureRow,\n PredictedLabel,\n TabularModelSource,\n TabularPrediction,\n TabularPredictorInfo,\n TabularPredictorOptions,\n} from \"./types\";\n\n/**\n * Import ONNX Runtime Web, only when a model is actually being loaded.\n *\n * Static import would make every consumer of this subpath install the peer,\n * including apps that only ever touch `CompactPredictor` — whose whole\n * point is not needing a runtime. Found by installing the published\n * package into an empty project, which is the only place the difference\n * shows.\n *\n * @returns The runtime module.\n * @throws {@link ModelLoadError} when the peer is not installed, naming it.\n */\nasync function loadRuntime(): Promise<typeof ort> {\n try {\n return (await import(\"onnxruntime-web\")) as typeof ort;\n } catch (error) {\n throw new ModelLoadError(\n \"The ONNX route needs the optional peer dependency: \" +\n \"npm install onnxruntime-web. For a model with no runtime at \" +\n \"all, export it with edge_pipeline(compact=True) and load it \" +\n \"through CompactPredictor.\",\n { cause: error },\n );\n }\n}\n\n/**\n * Execution providers used when the caller does not choose.\n *\n * WebAssembly only, and deliberately: scikit-learn graphs are `ai.onnx.ml`\n * operators, which the WebGPU backend does not implement. There is no\n * speed left on the table here — a 10-tree forest predicts a row in about\n * 0.05 ms in Chromium.\n */\nexport const DEFAULT_TABULAR_PROVIDERS: readonly string[] = [\"wasm\"];\n\n/** Output names that indicate predicted classes rather than scores. */\nconst LABEL_HINTS = [\"label\", \"class\", \"variable\", \"output\"] as const;\n\n/** Output names that indicate class scores. */\nconst PROBABILITY_HINTS = [\"probabilit\", \"score\"] as const;\n\n/** Largest plausible feature count; anything above is a dynamic-dim sentinel. */\nconst MAX_DECLARED_FEATURES = 1_000_000;\n\n/**\n * Pick the first output whose name contains one of `hints`.\n *\n * @param names Graph output names.\n * @param hints Lowercase substrings to look for.\n * @returns The matching name, or `null`.\n */\nfunction matchOutput(names: readonly string[], hints: readonly string[]): string | null {\n for (const hint of hints) {\n const found = names.find((name) => name.toLowerCase().includes(hint));\n if (found !== undefined) return found;\n }\n return null;\n}\n\n/**\n * Read the declared feature count from the input metadata.\n *\n * A dynamic batch dimension is reported as a symbolic string or as an\n * out-of-range number (`4294967295` — an unsigned `-1`), so only a sane\n * positive integer in the second position is trusted.\n *\n * @param session The loaded session.\n * @returns The feature count, or `null` when the graph does not declare one.\n */\nfunction declaredFeatures(session: ort.InferenceSession): number | null {\n const metadata = session.inputMetadata?.[0];\n if (metadata === undefined || metadata.isTensor !== true) return null;\n const dimension = metadata.shape[1];\n if (typeof dimension !== \"number\") return null;\n if (!Number.isInteger(dimension) || dimension <= 0) return null;\n return dimension > MAX_DECLARED_FEATURES ? null : dimension;\n}\n\n/**\n * Convert one raw label value into a JS-friendly label.\n *\n * @param value A tensor element: `bigint` for int64, `number` for float,\n * `string` for a string-labelled classifier.\n * @returns The label as a number or string.\n */\nfunction toLabel(value: unknown): PredictedLabel {\n if (typeof value === \"bigint\") return Number(value);\n if (typeof value === \"number\") return value;\n return String(value);\n}\n\n/**\n * Translate a session-creation failure into an error naming its cause.\n *\n * @param error Whatever ONNX Runtime threw.\n * @returns The error to surface.\n */\nfunction asLoadError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes(\"No Op registered\")) {\n return new UnsupportedGraphError(\n \"This runtime build has no kernels for the model's operators. \" +\n \"scikit-learn exports use the ai.onnx.ml domain, which is missing \" +\n 'from the WebGPU build: import \"onnxruntime-web\", not ' +\n '\"onnxruntime-web/webgpu\". Original error: ' +\n message,\n { cause: error },\n );\n }\n return new ModelLoadError(`Failed to load the model: ${message}`, { cause: error });\n}\n\n/**\n * Translate a run failure into an error naming its cause.\n *\n * Measured: an export made with skl2onnx's default (ZipMap enabled) has a\n * probability output that is a sequence of maps, and ONNX Runtime Web\n * refuses to read non-tensor values — `Reading data from non-tensor typed\n * value is not supported`. That message describes the runtime's limitation,\n * not the fix, so it is replaced by one that names the export flag.\n *\n * @param error Whatever ONNX Runtime threw.\n * @returns The error to surface.\n */\nfunction asRunError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (\n message.includes(\"non-tensor typed value\") ||\n message.includes(\"Can't access output tensor data\")\n ) {\n return new InferenceError(\n \"The model has a non-tensor output, which ONNX Runtime Web cannot \" +\n \"read. A scikit-learn export made with ZipMap enabled returns a \" +\n \"sequence of maps per row — re-export with export_sklearn_to_onnx, \" +\n `which disables it. Original error: ${message}`,\n { cause: error },\n );\n }\n return new InferenceError(`Inference failed: ${message}`, { cause: error });\n}\n\n/**\n * A loaded tabular model, ready to answer.\n *\n * @example\n * ```ts\n * const predictor = await TabularPredictor.create(\"/models/classifier.onnx\");\n * const { labels, probabilities } = await predictor.predict([[5.1, 3.5, 1.4, 0.2]]);\n * ```\n */\nexport class TabularPredictor {\n private constructor(\n private readonly runtime: typeof ort,\n private readonly session: ort.InferenceSession,\n /** What is loaded and how it is configured. */\n public readonly info: TabularPredictorInfo,\n ) {}\n\n /**\n * Load a model and describe its graph.\n *\n * @param source A URL string, or the model bytes (which is what an\n * offline app passes, having read them from the cache).\n * @param options Providers, warm-up and pass-through session options.\n * @throws {@link UnsupportedGraphError} when the runtime build lacks the\n * `ai.onnx.ml` operators — the WebGPU entry point does.\n * @throws {@link ModelLoadError} for any other load failure.\n */\n static async create(\n source: TabularModelSource,\n options: TabularPredictorOptions = {},\n ): Promise<TabularPredictor> {\n const providers = options.providers ?? DEFAULT_TABULAR_PROVIDERS;\n const runtime = await loadRuntime();\n const assets = configuredOrtAssetPath();\n if (assets !== undefined) runtime.env.wasm.wasmPaths = assets;\n\n let session: ort.InferenceSession;\n try {\n session = await runtime.InferenceSession.create(source as never, {\n ...(options.sessionOptions ?? {}),\n executionProviders:\n providers as ort.InferenceSession.SessionOptions[\"executionProviders\"],\n });\n } catch (error) {\n throw asLoadError(error);\n }\n\n const outputNames = [...session.outputNames];\n const probabilityOutput = matchOutput(outputNames, PROBABILITY_HINTS);\n const labelOutput =\n outputNames.find((name) => name !== probabilityOutput && isLabelName(name)) ??\n outputNames.find((name) => name !== probabilityOutput) ??\n (outputNames[0] as string);\n\n const predictor = new TabularPredictor(runtime, session, {\n inputName: session.inputNames[0] as string,\n numFeatures: declaredFeatures(session),\n outputNames,\n labelOutput,\n probabilityOutput,\n isClassifier: probabilityOutput !== null,\n providers,\n });\n\n if (options.warmup !== false) await predictor.warmUp();\n return predictor;\n }\n\n /**\n * Run one throwaway inference so the first real call is not the slow one.\n *\n * Skipped when the graph does not declare a feature count, since there\n * is no shape to synthesise. Failures are swallowed: a warm-up that\n * cannot run is not a reason to refuse to serve.\n */\n async warmUp(): Promise<void> {\n const features = this.info.numFeatures;\n if (features === null) return;\n try {\n await this.predict([new Array<number>(features).fill(0)]);\n } catch {\n /* a failed warm-up must not prevent serving */\n }\n }\n\n /**\n * Predict for a batch of rows.\n *\n * @param rows One array of feature values per row, in training column\n * order. A single row is still wrapped: `[[...]]`.\n * @returns Labels, class scores when the model produces them, and the\n * call's duration.\n * @throws {@link FeatureShapeError} when the batch is empty, ragged, or\n * the wrong width — checked here so the failure names the mismatch\n * instead of surfacing as an opaque runtime error.\n * @throws {@link InferenceError} when the session runs but its outputs\n * cannot be read.\n */\n async predict(rows: readonly FeatureRow[]): Promise<TabularPrediction> {\n if (!Array.isArray(rows) || rows.length === 0) {\n throw new FeatureShapeError(\n \"predict() needs at least one row, shaped [[f1, f2, ...]].\",\n );\n }\n const width = rows[0]?.length ?? 0;\n if (width === 0) {\n throw new FeatureShapeError(\"The first row has no feature values.\");\n }\n const ragged = rows.findIndex((row) => row.length !== width);\n if (ragged !== -1) {\n throw new FeatureShapeError(\n `All rows must have the same width; row ${ragged} has ` +\n `${rows[ragged]?.length} values, expected ${width}.`,\n );\n }\n const expected = this.info.numFeatures;\n if (expected !== null && width !== expected) {\n throw new FeatureShapeError(\n `The model expects ${expected} features per row, got ${width}.`,\n );\n }\n\n const flat = new Float32Array(rows.length * width);\n for (let index = 0; index < rows.length; index += 1) {\n flat.set(rows[index] as number[], index * width);\n }\n const tensor = new this.runtime.Tensor(\"float32\", flat, [rows.length, width]);\n\n const started = performance.now();\n let outputs: ort.InferenceSession.OnnxValueMapType;\n try {\n outputs = await this.session.run({ [this.info.inputName]: tensor });\n } catch (error) {\n throw asRunError(error);\n }\n const ms = performance.now() - started;\n\n const labelTensor = outputs[this.info.labelOutput];\n if (labelTensor?.data === undefined) {\n throw new InferenceError(\n `The model produced no readable \"${this.info.labelOutput}\" output.`,\n );\n }\n\n const labels: PredictedLabel[] = Array.from(\n labelTensor.data as ArrayLike<unknown>,\n toLabel,\n );\n\n const probabilities: number[][] = [];\n if (this.info.probabilityOutput !== null) {\n const scores = outputs[this.info.probabilityOutput];\n if (scores?.data !== undefined) {\n const values = Array.from(scores.data as ArrayLike<number>, Number);\n const classes = values.length / rows.length;\n for (let index = 0; index < rows.length; index += 1) {\n probabilities.push(values.slice(index * classes, (index + 1) * classes));\n }\n }\n }\n\n return { labels, probabilities, numRows: rows.length, ms };\n }\n\n /**\n * Release the session's memory.\n *\n * Worth calling on a route that swaps models: the WebAssembly heap does\n * not shrink on garbage collection alone.\n */\n async dispose(): Promise<void> {\n await this.session.release?.();\n }\n}\n\n/**\n * Whether an output name looks like a label rather than a score.\n *\n * @param name The graph output name.\n * @returns `true` when the name matches a known label convention.\n */\nfunction isLabelName(name: string): boolean {\n const lowered = name.toLowerCase();\n return LABEL_HINTS.some((hint) => lowered.includes(hint));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"predictor.js","names":[],"sources":["../../src/tabular/predictor.ts"],"sourcesContent":["/**\n * Running a scikit-learn model in the browser, offline.\n *\n * The model file is produced by `tempest-fastapi-sdk`'s\n * `export_sklearn_to_onnx`. This is everything between that file and an\n * answer — the same glue the Python `OnnxPredictor` provides on a device,\n * with the browser's own traps handled:\n *\n * - **int64 labels arrive as `bigint`.** ONNX Runtime Web surfaces the\n * label tensor as a `BigInt64Array`, so a caller comparing `label === 1`\n * silently gets `false` and `JSON.stringify` throws. Labels are converted.\n * - **`ai.onnx.ml` needs the right build.** Measured: importing\n * `onnxruntime-web/webgpu` loads a WebAssembly binary without those\n * operators, and session creation fails with `No Op registered for\n * TreeEnsembleClassifier`. That failure is translated into an error that\n * names the import.\n * - **Which output is which.** A classifier returns `label` and\n * `probabilities`; a regressor returns a single `variable`. Indexing by\n * position works until the day you deploy the other kind.\n */\n\nimport type * as ort from \"onnxruntime-web\";\n\nimport { configuredOrtAssetPath } from \"./assets\";\nimport {\n FeatureShapeError,\n InferenceError,\n ModelLoadError,\n UnsupportedGraphError,\n} from \"./exceptions\";\nimport type {\n FeatureRow,\n PredictedLabel,\n TabularModelSource,\n TabularPrediction,\n TabularPredictorInfo,\n TabularPredictorOptions,\n} from \"./types\";\n\n/**\n * Import ONNX Runtime Web, only when a model is actually being loaded.\n *\n * Static import would make every consumer of this subpath install the peer,\n * including apps that only ever touch `CompactPredictor` — whose whole\n * point is not needing a runtime. Found by installing the published\n * package into an empty project, which is the only place the difference\n * shows.\n *\n * @returns The runtime module.\n * @throws {@link ModelLoadError} when the peer is not installed, naming it.\n */\nasync function loadRuntime(): Promise<typeof ort> {\n try {\n return (await import(\"onnxruntime-web\")) as typeof ort;\n } catch (error) {\n throw new ModelLoadError(\n \"The ONNX route needs the optional peer dependency: \" +\n \"npm install onnxruntime-web. For a model with no runtime at \" +\n \"all, export it with edge_pipeline(compact=True) and load it \" +\n \"through CompactPredictor.\",\n { cause: error },\n );\n }\n}\n\n/**\n * Execution providers used when the caller does not choose.\n *\n * WebAssembly only, and deliberately: scikit-learn graphs are `ai.onnx.ml`\n * operators, which the WebGPU backend does not implement. There is no\n * speed left on the table here — a 10-tree forest predicts a row in about\n * 0.05 ms in Chromium.\n */\nexport const DEFAULT_TABULAR_PROVIDERS: readonly string[] = [\"wasm\"];\n\n/** Output names that indicate predicted classes rather than scores. */\nconst LABEL_HINTS = [\"label\", \"class\", \"variable\", \"output\"] as const;\n\n/** Output names that indicate class scores. */\nconst PROBABILITY_HINTS = [\"probabilit\", \"score\"] as const;\n\n/** Largest plausible feature count; anything above is a dynamic-dim sentinel. */\nconst MAX_DECLARED_FEATURES = 1_000_000;\n\n/**\n * Pick the first output whose name contains one of `hints`.\n *\n * @param names Graph output names.\n * @param hints Lowercase substrings to look for.\n * @returns The matching name, or `null`.\n */\nfunction matchOutput(names: readonly string[], hints: readonly string[]): string | null {\n for (const hint of hints) {\n const found = names.find((name) => name.toLowerCase().includes(hint));\n if (found !== undefined) return found;\n }\n return null;\n}\n\n/**\n * Read the declared feature count from the input metadata.\n *\n * A dynamic batch dimension is reported as a symbolic string or as an\n * out-of-range number (`4294967295` — an unsigned `-1`), so only a sane\n * positive integer in the second position is trusted.\n *\n * @param session The loaded session.\n * @returns The feature count, or `null` when the graph does not declare one.\n */\nfunction declaredFeatures(session: ort.InferenceSession): number | null {\n const metadata = session.inputMetadata?.[0];\n if (metadata === undefined || metadata.isTensor !== true) return null;\n const dimension = metadata.shape[1];\n if (typeof dimension !== \"number\") return null;\n if (!Number.isInteger(dimension) || dimension <= 0) return null;\n return dimension > MAX_DECLARED_FEATURES ? null : dimension;\n}\n\n/**\n * Convert one raw label value into a JS-friendly label.\n *\n * @param value A tensor element: `bigint` for int64, `number` for float,\n * `string` for a string-labelled classifier.\n * @returns The label as a number or string.\n */\nfunction toLabel(value: unknown): PredictedLabel {\n if (typeof value === \"bigint\") return Number(value);\n if (typeof value === \"number\") return value;\n return String(value);\n}\n\n/**\n * Translate a session-creation failure into an error naming its cause.\n *\n * @param error Whatever ONNX Runtime threw.\n * @returns The error to surface.\n */\nfunction asLoadError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes(\"No Op registered\")) {\n return new UnsupportedGraphError(\n \"This runtime build has no kernels for the model's operators. \" +\n \"scikit-learn exports use the ai.onnx.ml domain, which is missing \" +\n 'from the WebGPU build: import \"onnxruntime-web\", not ' +\n '\"onnxruntime-web/webgpu\". Original error: ' +\n message,\n { cause: error },\n );\n }\n return new ModelLoadError(`Failed to load the model: ${message}`, { cause: error });\n}\n\n/**\n * Translate a run failure into an error naming its cause.\n *\n * Measured: an export made with skl2onnx's default (ZipMap enabled) has a\n * probability output that is a sequence of maps, and ONNX Runtime Web\n * refuses to read non-tensor values — `Reading data from non-tensor typed\n * value is not supported`. That message describes the runtime's limitation,\n * not the fix, so it is replaced by one that names the export flag.\n *\n * @param error Whatever ONNX Runtime threw.\n * @returns The error to surface.\n */\nfunction asRunError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (\n message.includes(\"non-tensor typed value\") ||\n message.includes(\"Can't access output tensor data\")\n ) {\n return new InferenceError(\n \"The model has a non-tensor output, which ONNX Runtime Web cannot \" +\n \"read. A scikit-learn export made with ZipMap enabled returns a \" +\n \"sequence of maps per row — re-export with export_sklearn_to_onnx, \" +\n `which disables it. Original error: ${message}`,\n { cause: error },\n );\n }\n return new InferenceError(`Inference failed: ${message}`, { cause: error });\n}\n\n/**\n * A loaded tabular model, ready to answer.\n *\n * @example\n * ```ts\n * const predictor = await TabularPredictor.create(\"/models/classifier.onnx\");\n * const { labels, probabilities } = await predictor.predict([[5.1, 3.5, 1.4, 0.2]]);\n * ```\n */\nexport class TabularPredictor {\n private constructor(\n private readonly runtime: typeof ort,\n private readonly session: ort.InferenceSession,\n /** What is loaded and how it is configured. */\n public readonly info: TabularPredictorInfo,\n ) {}\n\n /**\n * Load a model and describe its graph.\n *\n * @param source A URL string, or the model bytes (which is what an\n * offline app passes, having read them from the cache).\n * @param options Providers, warm-up and pass-through session options.\n * @throws {@link UnsupportedGraphError} when the runtime build lacks the\n * `ai.onnx.ml` operators — the WebGPU entry point does.\n * @throws {@link ModelLoadError} for any other load failure.\n */\n static async create(\n source: TabularModelSource,\n options: TabularPredictorOptions = {},\n ): Promise<TabularPredictor> {\n const providers = options.providers ?? DEFAULT_TABULAR_PROVIDERS;\n const runtime = await loadRuntime();\n const assets = configuredOrtAssetPath();\n if (assets !== undefined) runtime.env.wasm.wasmPaths = assets;\n\n let session: ort.InferenceSession;\n try {\n session = await runtime.InferenceSession.create(source as never, {\n ...(options.sessionOptions ?? {}),\n executionProviders:\n providers as ort.InferenceSession.SessionOptions[\"executionProviders\"],\n });\n } catch (error) {\n throw asLoadError(error);\n }\n\n const outputNames = [...session.outputNames];\n const probabilityOutput = matchOutput(outputNames, PROBABILITY_HINTS);\n const labelOutput =\n outputNames.find((name) => name !== probabilityOutput && isLabelName(name)) ??\n outputNames.find((name) => name !== probabilityOutput) ??\n (outputNames[0] as string);\n\n const predictor = new TabularPredictor(runtime, session, {\n inputName: session.inputNames[0] as string,\n numFeatures: declaredFeatures(session),\n outputNames,\n labelOutput,\n probabilityOutput,\n isClassifier: probabilityOutput !== null,\n providers,\n });\n\n if (options.warmup !== false) await predictor.warmUp();\n return predictor;\n }\n\n /**\n * Run one throwaway inference so the first real call is not the slow one.\n *\n * Skipped when the graph does not declare a feature count, since there\n * is no shape to synthesise.\n *\n * @tempest-limits empty-catch — a warm-up that cannot run is not a reason to\n * refuse to serve. The synthetic all-zero row can be rejected by a graph that\n * expects a different dtype or a categorical encoding, and that says nothing\n * about the real rows the caller will send; the only cost of the failure is\n * that the first real inference pays the lazy-init it would have paid anyway.\n */\n async warmUp(): Promise<void> {\n const features = this.info.numFeatures;\n if (features === null) return;\n try {\n await this.predict([new Array<number>(features).fill(0)]);\n } catch {\n /* empty */\n }\n }\n\n /**\n * Predict for a batch of rows.\n *\n * @param rows One array of feature values per row, in training column\n * order. A single row is still wrapped: `[[...]]`.\n * @returns Labels, class scores when the model produces them, and the\n * call's duration.\n * @throws {@link FeatureShapeError} when the batch is empty, ragged, or\n * the wrong width — checked here so the failure names the mismatch\n * instead of surfacing as an opaque runtime error.\n * @throws {@link InferenceError} when the session runs but its outputs\n * cannot be read.\n */\n async predict(rows: readonly FeatureRow[]): Promise<TabularPrediction> {\n if (!Array.isArray(rows) || rows.length === 0) {\n throw new FeatureShapeError(\n \"predict() needs at least one row, shaped [[f1, f2, ...]].\",\n );\n }\n const width = rows[0]?.length ?? 0;\n if (width === 0) {\n throw new FeatureShapeError(\"The first row has no feature values.\");\n }\n const ragged = rows.findIndex((row) => row.length !== width);\n if (ragged !== -1) {\n throw new FeatureShapeError(\n `All rows must have the same width; row ${ragged} has ` +\n `${rows[ragged]?.length} values, expected ${width}.`,\n );\n }\n const expected = this.info.numFeatures;\n if (expected !== null && width !== expected) {\n throw new FeatureShapeError(\n `The model expects ${expected} features per row, got ${width}.`,\n );\n }\n\n const flat = new Float32Array(rows.length * width);\n for (let index = 0; index < rows.length; index += 1) {\n flat.set(rows[index] as number[], index * width);\n }\n const tensor = new this.runtime.Tensor(\"float32\", flat, [rows.length, width]);\n\n const started = performance.now();\n let outputs: ort.InferenceSession.OnnxValueMapType;\n try {\n outputs = await this.session.run({ [this.info.inputName]: tensor });\n } catch (error) {\n throw asRunError(error);\n }\n const ms = performance.now() - started;\n\n const labelTensor = outputs[this.info.labelOutput];\n if (labelTensor?.data === undefined) {\n throw new InferenceError(\n `The model produced no readable \"${this.info.labelOutput}\" output.`,\n );\n }\n\n const labels: PredictedLabel[] = Array.from(\n labelTensor.data as ArrayLike<unknown>,\n toLabel,\n );\n\n const probabilities: number[][] = [];\n if (this.info.probabilityOutput !== null) {\n const scores = outputs[this.info.probabilityOutput];\n if (scores?.data !== undefined) {\n const values = Array.from(scores.data as ArrayLike<number>, Number);\n const classes = values.length / rows.length;\n for (let index = 0; index < rows.length; index += 1) {\n probabilities.push(values.slice(index * classes, (index + 1) * classes));\n }\n }\n }\n\n return { labels, probabilities, numRows: rows.length, ms };\n }\n\n /**\n * Release the session's memory.\n *\n * Worth calling on a route that swaps models: the WebAssembly heap does\n * not shrink on garbage collection alone.\n */\n async dispose(): Promise<void> {\n await this.session.release?.();\n }\n}\n\n/**\n * Whether an output name looks like a label rather than a score.\n *\n * @param name The graph output name.\n * @returns `true` when the name matches a known label convention.\n */\nfunction isLabelName(name: string): boolean {\n const lowered = name.toLowerCase();\n return LABEL_HINTS.some((hint) => lowered.includes(hint));\n}\n"],"mappings":"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"}
package/dist/tabular.d.ts CHANGED
@@ -540,8 +540,13 @@ export declare class CompactPredictor {
540
540
  * Run one throwaway inference so the first real call is not the slow one.
541
541
  *
542
542
  * Skipped when the graph does not declare a feature count, since there
543
- * is no shape to synthesise. Failures are swallowed: a warm-up that
544
- * cannot run is not a reason to refuse to serve.
543
+ * is no shape to synthesise.
544
+ *
545
+ * @tempest-limits empty-catch — a warm-up that cannot run is not a reason to
546
+ * refuse to serve. The synthetic all-zero row can be rejected by a graph that
547
+ * expects a different dtype or a categorical encoding, and that says nothing
548
+ * about the real rows the caller will send; the only cost of the failure is
549
+ * that the first real inference pays the lazy-init it would have paid anyway.
545
550
  */
546
551
  warmUp(): Promise<void>;
547
552
  /**
@@ -1559,6 +1559,11 @@ export { BrowserRouter }
1559
1559
  * @param headers - The response headers (for the `X-Request-ID` fallback).
1560
1560
  * @param sentRequestId - The id the client sent on the request, if any.
1561
1561
  * @returns A fully-populated `ApiError`.
1562
+ *
1563
+ * @tempest-limits param-count — the arguments are the response as it arrives
1564
+ * (`status`, `body`, `headers`) plus the id the request was sent with, and they are
1565
+ * passed at exactly one place: the client's response path. Exported from the package
1566
+ * root, so the rewrite would be breaking for callers that build their own errors.
1562
1567
  */
1563
1568
  export declare function buildApiError(status: number, body: unknown, headers?: Headers | {
1564
1569
  get(name: string): string | null;
@@ -1621,18 +1626,25 @@ export declare const CACHE_TIME: {
1621
1626
  /**
1622
1627
  * How large the assets a page precached actually are.
1623
1628
  *
1624
- * Reads the `Content-Length` of a stored response instead of its body:
1625
- * materializing a cached ONNX model or WASM binary to learn its length would
1626
- * pull tens of megabytes into memory on every measurement.
1629
+ * Prefers the stored `Content-Length`, because materializing a cached ONNX model
1630
+ * or WASM binary to learn its length would pull tens of megabytes into memory on
1631
+ * every measurement. When the header is missing the size is counted off the body
1632
+ * stream instead — chunk by chunk, keeping only one chunk alive at a time.
1633
+ *
1634
+ * The fallback exists because "no `Content-Length`" is not the rare case a
1635
+ * header-only reader assumes. A chunked transfer drops it, a proxy that
1636
+ * re-encodes drops it, and a cross-origin response only exposes it under CORS
1637
+ * rules — and in a report a `null` from any of those is indistinguishable from a
1638
+ * model nobody measured. FAMACHApp shipped 12 field runs with both model-size
1639
+ * columns empty for exactly this reason.
1627
1640
  */
1628
1641
  /**
1629
1642
  * Byte size of a response sitting in a Cache Storage bucket.
1630
1643
  *
1631
1644
  * @param cacheName The cache bucket to look in.
1632
1645
  * @param url The request URL the response was stored under.
1633
- * @returns The size in bytes, or `null` when Cache Storage is unavailable,
1634
- * the entry is absent, or the stored response carries no usable
1635
- * `Content-Length` (a chunked transfer, typically).
1646
+ * @returns The size in bytes, or `null` when Cache Storage is unavailable, the
1647
+ * entry is absent, or the body cannot be read either.
1636
1648
  *
1637
1649
  * @example
1638
1650
  * ```typescript
@@ -2512,6 +2524,12 @@ export declare interface Coordinate {
2512
2524
  * label toggles between "Copy" and "Copied". The reset timer is cleared on
2513
2525
  * unmount to avoid setting state on an unmounted component.
2514
2526
  *
2527
+ * @tempest-limits empty-catch — `navigator.clipboard.writeText` rejects when the
2528
+ * page is not a secure context, when the document is not focused, or when the user
2529
+ * denied the permission. None of those are recoverable here and none deserve an
2530
+ * error surface: the button simply never enters the "copied" state, which is the
2531
+ * feedback the user needs.
2532
+ *
2515
2533
  * @example
2516
2534
  * <CopyButton value="npm i tempest-react-sdk" />
2517
2535
  */
@@ -2844,6 +2862,16 @@ export declare interface CreateLaunchDarklyFeatureFlagsAdapterOptions {
2844
2862
  * of live contexts (Chrome allows around six), so a meter left running on unmount
2845
2863
  * eventually breaks every later one on the page.
2846
2864
  *
2865
+ * `level()` applies instant attack and eased release: a value above the current one
2866
+ * is taken as is, a lower one decays by `decay`. A meter that fell as fast as it rose
2867
+ * reads as noise instead of as loudness.
2868
+ *
2869
+ * @tempest-limits empty-catch — `stop()` disconnects nodes whose context may already
2870
+ * be closed (the tab was backgrounded, the stream's track ended, `stop()` raced an
2871
+ * unmount), and a disconnect on a dead graph throws. There is nothing to report and
2872
+ * nothing to retry: the resource this call would have released is already gone, and
2873
+ * the `context.close()` right after it is what actually matters.
2874
+ *
2847
2875
  * @param stream - A live audio stream.
2848
2876
  * @param options - See {@link LevelMeterOptions}.
2849
2877
  * @returns A `level()` reader and a `stop()`.
@@ -2871,6 +2899,12 @@ export declare function createLocalUploadStorage(prefix?: string): ResumableUplo
2871
2899
  /**
2872
2900
  * Create a structured leveled logger. Plug arbitrary sinks (Sentry, Datadog,
2873
2901
  * remote ingestion) by implementing the `LoggerSink` interface.
2902
+ *
2903
+ * @tempest-limits empty-catch — a sink that throws must not take the app with it,
2904
+ * and it must not stop the sinks after it in the list either. Logging is the thing
2905
+ * you reach for when something is already going wrong; a transport failure that
2906
+ * propagates would turn a diagnostic into the outage. Nothing here can report the
2907
+ * failure without recursing into the logger that just failed.
2874
2908
  */
2875
2909
  export declare function createLogger(options?: CreateLoggerOptions): Logger;
2876
2910
 
@@ -4445,6 +4479,12 @@ export declare function estimateStorage(): Promise<StorageEstimate_2>;
4445
4479
  * { latitude: -23.5629, longitude: -46.6544 },
4446
4480
  * "car",
4447
4481
  * ); // { mode: "car", distance_km: …, duration_minutes: …, source: "heuristic" }
4482
+ *
4483
+ * @tempest-limits param-count — `(origin, destination, mode)` is the shape every
4484
+ * routing API uses, and the fourth argument is already the named-options object the
4485
+ * rule asks for. Folding origin and destination into it would hide that the pair is
4486
+ * ordered, and this function is re-exported from the package root, so the change
4487
+ * would break every caller to satisfy a count.
4448
4488
  */
4449
4489
  export declare function estimateTravel(origin: Coordinate, destination: Coordinate, mode?: TravelMode, options?: EstimateTravelOptions): TravelEstimate;
4450
4490
 
@@ -4477,6 +4517,11 @@ export declare interface EventStreamMessage<T> {
4477
4517
  raw: MessageEvent;
4478
4518
  }
4479
4519
 
4520
+ /**
4521
+ * @tempest-limits function-lines — the body is the SSE line protocol — accumulate
4522
+ * `data:` lines until a blank line, track `id:` for Last-Event-ID, honour `retry:` —
4523
+ * plus the reconnect that uses the id it just tracked.
4524
+ */
4480
4525
  export declare type EventStreamStatus = "idle" | "connecting" | "open" | "closed" | "error";
4481
4526
 
4482
4527
  /**
@@ -4658,6 +4703,11 @@ export declare function filtersToSearchParams(filters: readonly Filter[]): URLSe
4658
4703
  * @param height - Viewport height in pixels.
4659
4704
  * @param options - Padding tuning.
4660
4705
  * @returns A {@link FittedProjection} with a `project(coord)` mapper.
4706
+ *
4707
+ * @tempest-limits param-count — `(bounds, width, height)` is the viewport the caller
4708
+ * measured, usually straight out of a `getBoundingClientRect()`, and the fourth
4709
+ * argument is already the options object. Public surface via the package root:
4710
+ * renaming these into one bag breaks callers and buys a count, not clarity.
4661
4711
  */
4662
4712
  export declare function fitProjection(bounds: GeoBounds, width: number, height: number, options?: FitProjectionOptions): FittedProjection;
4663
4713
 
@@ -5344,6 +5394,10 @@ export declare interface I18nContextValue extends I18n {
5344
5394
  /**
5345
5395
  * React provider for the SDK's lightweight i18n. Exposes the translation
5346
5396
  * helpers and a `setLocale` setter to children.
5397
+ *
5398
+ * @tempest-limits empty-catch — remembering the chosen locale is best-effort. The
5399
+ * switch itself is React state and already happened by the time the write runs, so
5400
+ * a storage failure costs the preference on the next load and nothing in this one.
5347
5401
  */
5348
5402
  export declare function I18nProvider({ children, locale: initialLocale, fallbackLocale, messages, storageKey, }: I18nProviderProps): JSX.Element;
5349
5403
 
@@ -5561,6 +5615,12 @@ export declare function inspectCaches(options?: {
5561
5615
  * is serialized to IndexedDB and a sync is registered; the original fetch still
5562
5616
  * rejects (so your app can show an offline state), and the request is replayed
5563
5617
  * later when the network returns.
5618
+ *
5619
+ * @tempest-limits empty-catch — `registration.sync.register` is unavailable on
5620
+ * every non-Chromium browser and fails on a revoked background-sync permission.
5621
+ * The entry is already durable in IndexedDB at that point, and the queue is also
5622
+ * drained opportunistically on the next successful request, so losing the OS-level
5623
+ * wakeup degrades *when* the replay happens, not *whether* it happens.
5564
5624
  */
5565
5625
  export declare function installBackgroundSync(options?: InstallBackgroundSyncOptions): void;
5566
5626
 
@@ -5606,6 +5666,11 @@ export declare interface InstallBackgroundSyncOptions {
5606
5666
  * platforms that never fire `beforeinstallprompt` (e.g. iOS Safari) it stays
5607
5667
  * hidden and you can surface manual instructions elsewhere.
5608
5668
  *
5669
+ * @tempest-limits empty-catch — persisting the dismissal is a courtesy, not the
5670
+ * feature. When `localStorage` refuses the write (quota, private mode) the banner
5671
+ * still hides for this session; the worst case is that it comes back next visit,
5672
+ * which beats an error thrown out of a click handler that only closed a banner.
5673
+ *
5609
5674
  * @example
5610
5675
  * <InstallBanner
5611
5676
  * title="Instale o FAMACHApp"
@@ -6478,6 +6543,13 @@ export declare type MarkdownBlock = {
6478
6543
  type: "rule";
6479
6544
  };
6480
6545
 
6546
+ /**
6547
+ * @tempest-limits file-lines, function-lines — a Markdown block parser is a state
6548
+ * machine over line kinds: paragraph, heading, fence, list, quote, table and
6549
+ * thematic break each need to see the lines around them to know where they end.
6550
+ * parseBlocks is that loop, and cutting it into per-kind functions would hand each
6551
+ * one the same lookahead cursor.
6552
+ */
6481
6553
  /** Inline content. */
6482
6554
  export declare type MarkdownInline = {
6483
6555
  type: "text";
@@ -6639,6 +6711,12 @@ export declare interface MediaRecorderHandle {
6639
6711
  mimeType: string;
6640
6712
  }
6641
6713
 
6714
+ /**
6715
+ * @tempest-limits function-lines — the engine behind both the audio and the video
6716
+ * recorder: MIME negotiation, the state machine MediaRecorder does not give you, and
6717
+ * the clock kept by hand because a fresh WebM reports no duration. The clock has to
6718
+ * pause and resume with the state machine, so they are one closure.
6719
+ */
6642
6720
  /** Lifecycle of a recording. */
6643
6721
  export declare type MediaRecorderStatus = "idle" | "recording" | "paused" | "stopped";
6644
6722
 
@@ -7472,6 +7550,12 @@ export declare interface OffsetParams {
7472
7550
  ascending?: boolean;
7473
7551
  }
7474
7552
 
7553
+ /**
7554
+ * @tempest-limits file-lines — OKLCH ↔ sRGB with the gamut mapping in between: the
7555
+ * transfer function, the LMS matrices, the chroma search that finds the nearest in-
7556
+ * gamut colour and the contrast ratio used to check it. Matrices split across files
7557
+ * are matrices that get edited one half at a time.
7558
+ */
7475
7559
  /**
7476
7560
  * Color math behind {@link createTheme} — OKLab/OKLCH conversions, tint scale
7477
7561
  * generation and WCAG contrast picking.
@@ -7873,6 +7957,14 @@ export declare class PasskeyError extends Error {
7873
7957
  constructor(kind: PasskeyErrorKind, message: string, cause?: unknown);
7874
7958
  }
7875
7959
 
7960
+ /**
7961
+ * @tempest-limits file-lines, function-lines — the WebAuthn client half: the JSON
7962
+ * shapes both ceremonies exchange with a backend, base64url ↔ ArrayBuffer, the
7963
+ * capability probes and the error classifier. The two ceremonies are near-mirrors
7964
+ * that must not drift — register and authenticate encode the same credential fields
7965
+ * in the same order — and the file's docstring is also the specification of the four
7966
+ * backend routes it expects.
7967
+ */
7876
7968
  /**
7877
7969
  * Classified reason a passkey ceremony did not produce a credential.
7878
7970
  *
@@ -9155,6 +9247,12 @@ export declare interface RuntimeRoute {
9155
9247
  rangeRequests?: boolean;
9156
9248
  }
9157
9249
 
9250
+ /**
9251
+ * @tempest-limits file-lines — precache, runtime strategies (cache-first, network-
9252
+ * first, stale-while-revalidate), expiry and the inspect/clear helpers all address
9253
+ * the same Cache Storage namespace, and the naming scheme that keeps them from
9254
+ * evicting each other is the file's whole contract.
9255
+ */
9158
9256
  /**
9159
9257
  * Service-worker caching helpers — a small, dependency-free subset of what
9160
9258
  * Workbox provides: precaching of the build's app shell (so the app launches
@@ -10097,6 +10195,12 @@ export declare function stopAudio(): void;
10097
10195
  /**
10098
10196
  * Typed wrapper around `localStorage` that JSON-encodes values and
10099
10197
  * silently handles environments where storage is unavailable (SSR, private mode).
10198
+ *
10199
+ * @tempest-limits empty-catch — every method here is best-effort by contract:
10200
+ * `localStorage` throws on quota exhaustion, in Safari private mode, and when a
10201
+ * cross-origin frame has storage blocked. A caller persisting a preference has no
10202
+ * recovery to run and no user-facing message to show, so the write is dropped and
10203
+ * the app keeps the value in memory for the session.
10100
10204
  */
10101
10205
  export declare const storage: {
10102
10206
  get<T>(key: string, fallback: T): T;
@@ -10557,6 +10661,11 @@ export declare const themePresets: Record<ThemePresetName, CreateThemeOptions>;
10557
10661
  *
10558
10662
  * Pair with `themeInitScript()` in the HTML head to prevent the flash of
10559
10663
  * incorrect theme on first paint.
10664
+ *
10665
+ * @tempest-limits empty-catch — the theme is applied to the DOM before it is
10666
+ * persisted, so a `localStorage` write refused by quota or private mode leaves the
10667
+ * user with the theme they just picked and only forfeits it on the next load.
10668
+ * Throwing from the setter would break the switch that already worked.
10560
10669
  */
10561
10670
  export declare function ThemeProvider({ children, defaultTheme, storageKey, target, attribute, themeColor, }: ThemeProviderProps): JSX.Element;
10562
10671
 
@@ -11874,6 +11983,11 @@ export declare function useErrorHandler(): (error: unknown) => void;
11874
11983
  * - `target` defaults to `window`. Accepts a raw `EventTarget` (window, document,
11875
11984
  * element) OR a ref pointing at one.
11876
11985
  * - Returns nothing — cleanup is automatic on unmount or when `eventName`/`target` change.
11986
+ *
11987
+ * @tempest-limits param-count — deliberately the same positional shape as
11988
+ * `addEventListener(type, listener, options)`, with the target inserted where React
11989
+ * needs it. A hook that mirrors a DOM API is read by everyone who already knows the
11990
+ * DOM API; a bespoke options bag would make the familiar call unfamiliar.
11877
11991
  */
11878
11992
  export declare function useEventListener<K extends keyof WindowEventMap>(eventName: K, handler: (event: WindowEventMap[K]) => void, target?: AnyEventTarget, options?: AddEventListenerOptions | boolean): void;
11879
11993
 
@@ -12150,6 +12264,13 @@ export declare function useListState<T>(initial?: T[]): [T[], ListStateHandlers<
12150
12264
  * provided default; the stored value is hydrated after mount. Updates to the
12151
12265
  * same key in other tabs are picked up via the `storage` event.
12152
12266
  *
12267
+ * @tempest-limits empty-catch — the three swallows here are the hook's contract.
12268
+ * A write that hits the quota (or Safari private mode) still updates React state,
12269
+ * so the session keeps working and only persistence is lost; a `storage` event
12270
+ * carrying a value this tab cannot deserialize is another tab's bug, and the
12271
+ * useful response is to keep the value already rendered rather than tear the
12272
+ * component down over a foreign write.
12273
+ *
12153
12274
  * @param key - localStorage key.
12154
12275
  * @param defaultValue - value used when nothing is stored or in SSR.
12155
12276
  * @param options - custom `serialize` / `deserialize` (default JSON).
@@ -14463,6 +14584,12 @@ export declare interface WebSocketMessage<T> {
14463
14584
  raw: MessageEvent;
14464
14585
  }
14465
14586
 
14587
+ /**
14588
+ * @tempest-limits file-lines, function-lines — reconnect with backoff, heartbeat,
14589
+ * the send queue that survives a disconnect and the listener set that must be re-
14590
+ * attached to each new socket — one connection's lifetime, one closure. The queue
14591
+ * and the reconnect timer are the same decision seen twice.
14592
+ */
14466
14593
  export declare type WebSocketStatus = "idle" | "connecting" | "open" | "closing" | "closed" | "error";
14467
14594
 
14468
14595
  export declare type WeekStart = 0 | 1;
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.cjs","names":[],"sources":["../../src/theme/ThemeProvider.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { ResolvedTheme, ThemeMode } from \"./types\";\n\nexport interface ThemeContextValue {\n /** Raw user preference (light / dark / system). */\n theme: ThemeMode;\n /** Effective theme actually applied to the DOM (light or dark). */\n resolvedTheme: ResolvedTheme;\n /** Update the preference. Persisted to localStorage when `storageKey` is set. */\n setTheme: (next: ThemeMode) => void;\n /** Convenience: flip light ↔ dark. When in `system` mode, switches to the opposite of the current resolved theme. */\n toggle: () => void;\n}\n\nconst ThemeContext = createContext<ThemeContextValue | null>(null);\n\nexport interface ThemeProviderProps {\n children: ReactNode;\n /** Initial preference when nothing is stored. Default: `\"system\"`. */\n defaultTheme?: ThemeMode;\n /** localStorage key used to persist the preference. Pass `null` to disable persistence. Default: `\"tempest-theme\"`. */\n storageKey?: string | null;\n /**\n * Element that receives the theme attribute(s). Defaults to\n * `document.documentElement`. Override when scoping the theme to a subtree.\n */\n target?: () => HTMLElement | null;\n /**\n * Attribute name(s) written on the target with the resolved theme\n * (`\"light\"` / `\"dark\"`). Default: `\"data-tempest-theme\"`.\n *\n * Pass an array to mirror the theme onto more than one attribute — handy\n * when the SDK components read `data-tempest-theme` but the host app's own\n * CSS keys off a different attribute (e.g. `[\"data-tempest-theme\",\n * \"data-theme\"]`). Avoids a separate sync effect in the consumer.\n */\n attribute?: string | string[];\n /**\n * When set, keeps `<meta name=\"theme-color\">` in sync with the resolved\n * theme — `content` becomes `themeColor.dark` in dark mode and\n * `themeColor.light` in light mode. The meta tag must already exist in the\n * document `<head>`. No-op when omitted.\n */\n themeColor?: { light: string; dark: string };\n}\n\nfunction resolve(mode: ThemeMode): ResolvedTheme {\n if (mode === \"dark\" || mode === \"light\") return mode;\n if (typeof window === \"undefined\") return \"light\";\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\n/**\n * Write the resolved theme onto every configured attribute and, when a\n * `themeColor` map is provided, sync the `<meta name=\"theme-color\">` tag.\n */\nfunction applyResolved(\n element: HTMLElement,\n resolved: ResolvedTheme,\n attribute: string | string[],\n themeColor?: { light: string; dark: string },\n): void {\n const attrs = Array.isArray(attribute) ? attribute : [attribute];\n for (const attr of attrs) element.setAttribute(attr, resolved);\n if (themeColor && typeof document !== \"undefined\") {\n const meta = document.querySelector<HTMLMetaElement>('meta[name=\"theme-color\"]');\n if (meta) meta.content = themeColor[resolved];\n }\n}\n\nfunction readStored(storageKey: string | null): ThemeMode | null {\n if (!storageKey || typeof window === \"undefined\") return null;\n try {\n const value = window.localStorage.getItem(storageKey);\n if (value === \"light\" || value === \"dark\" || value === \"system\") return value;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Wire dark/light theming. Writes a data attribute on a target element (the\n * `<html>` element by default) and exposes the current preference via\n * {@link useTheme}.\n *\n * Pair with `themeInitScript()` in the HTML head to prevent the flash of\n * incorrect theme on first paint.\n */\nexport function ThemeProvider({\n children,\n defaultTheme = \"system\",\n storageKey = \"tempest-theme\",\n target,\n attribute = \"data-tempest-theme\",\n themeColor,\n}: ThemeProviderProps) {\n const [theme, setThemeState] = useState<ThemeMode>(\n () => readStored(storageKey) ?? defaultTheme,\n );\n const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>(() => resolve(theme));\n\n const targetRef = useRef<typeof target>(target);\n useEffect(() => {\n targetRef.current = target;\n }, [target]);\n\n const attributeKey = Array.isArray(attribute) ? attribute.join(\",\") : attribute;\n const themeColorRef = useRef(themeColor);\n useEffect(() => {\n themeColorRef.current = themeColor;\n }, [themeColor]);\n\n useEffect(() => {\n const element = targetRef.current?.() ?? document.documentElement;\n if (!element) return;\n const next = resolve(theme);\n applyResolved(element, next, attribute, themeColorRef.current);\n setResolvedTheme(next);\n // attributeKey is the stable string form of `attribute`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [theme, attributeKey]);\n\n useEffect(() => {\n if (theme !== \"system\" || typeof window === \"undefined\") return;\n const list = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const handler = (): void => {\n const element = targetRef.current?.() ?? document.documentElement;\n if (!element) return;\n const next: ResolvedTheme = list.matches ? \"dark\" : \"light\";\n applyResolved(element, next, attribute, themeColorRef.current);\n setResolvedTheme(next);\n };\n list.addEventListener(\"change\", handler);\n return () => list.removeEventListener(\"change\", handler);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [theme, attributeKey]);\n\n const setTheme = useCallback(\n (next: ThemeMode) => {\n setThemeState(next);\n if (storageKey && typeof window !== \"undefined\") {\n try {\n window.localStorage.setItem(storageKey, next);\n } catch {\n /* ignore quota errors */\n }\n }\n },\n [storageKey],\n );\n\n const toggle = useCallback(() => {\n setTheme(resolvedTheme === \"dark\" ? \"light\" : \"dark\");\n }, [resolvedTheme, setTheme]);\n\n const value = useMemo<ThemeContextValue>(\n () => ({ theme, resolvedTheme, setTheme, toggle }),\n [theme, resolvedTheme, setTheme, toggle],\n );\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n}\n\n/**\n * Read and mutate the current theme. Must be used inside a {@link ThemeProvider}.\n */\nexport function useTheme(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (!ctx) throw new Error(\"useTheme must be used inside a <ThemeProvider>\");\n return ctx;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ThemeProvider.cjs","names":[],"sources":["../../src/theme/ThemeProvider.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { ResolvedTheme, ThemeMode } from \"./types\";\n\nexport interface ThemeContextValue {\n /** Raw user preference (light / dark / system). */\n theme: ThemeMode;\n /** Effective theme actually applied to the DOM (light or dark). */\n resolvedTheme: ResolvedTheme;\n /** Update the preference. Persisted to localStorage when `storageKey` is set. */\n setTheme: (next: ThemeMode) => void;\n /** Convenience: flip light ↔ dark. When in `system` mode, switches to the opposite of the current resolved theme. */\n toggle: () => void;\n}\n\nconst ThemeContext = createContext<ThemeContextValue | null>(null);\n\nexport interface ThemeProviderProps {\n children: ReactNode;\n /** Initial preference when nothing is stored. Default: `\"system\"`. */\n defaultTheme?: ThemeMode;\n /** localStorage key used to persist the preference. Pass `null` to disable persistence. Default: `\"tempest-theme\"`. */\n storageKey?: string | null;\n /**\n * Element that receives the theme attribute(s). Defaults to\n * `document.documentElement`. Override when scoping the theme to a subtree.\n */\n target?: () => HTMLElement | null;\n /**\n * Attribute name(s) written on the target with the resolved theme\n * (`\"light\"` / `\"dark\"`). Default: `\"data-tempest-theme\"`.\n *\n * Pass an array to mirror the theme onto more than one attribute — handy\n * when the SDK components read `data-tempest-theme` but the host app's own\n * CSS keys off a different attribute (e.g. `[\"data-tempest-theme\",\n * \"data-theme\"]`). Avoids a separate sync effect in the consumer.\n */\n attribute?: string | string[];\n /**\n * When set, keeps `<meta name=\"theme-color\">` in sync with the resolved\n * theme — `content` becomes `themeColor.dark` in dark mode and\n * `themeColor.light` in light mode. The meta tag must already exist in the\n * document `<head>`. No-op when omitted.\n */\n themeColor?: { light: string; dark: string };\n}\n\nfunction resolve(mode: ThemeMode): ResolvedTheme {\n if (mode === \"dark\" || mode === \"light\") return mode;\n if (typeof window === \"undefined\") return \"light\";\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\n/**\n * Write the resolved theme onto every configured attribute and, when a\n * `themeColor` map is provided, sync the `<meta name=\"theme-color\">` tag.\n */\nfunction applyResolved(\n element: HTMLElement,\n resolved: ResolvedTheme,\n attribute: string | string[],\n themeColor?: { light: string; dark: string },\n): void {\n const attrs = Array.isArray(attribute) ? attribute : [attribute];\n for (const attr of attrs) element.setAttribute(attr, resolved);\n if (themeColor && typeof document !== \"undefined\") {\n const meta = document.querySelector<HTMLMetaElement>('meta[name=\"theme-color\"]');\n if (meta) meta.content = themeColor[resolved];\n }\n}\n\nfunction readStored(storageKey: string | null): ThemeMode | null {\n if (!storageKey || typeof window === \"undefined\") return null;\n try {\n const value = window.localStorage.getItem(storageKey);\n if (value === \"light\" || value === \"dark\" || value === \"system\") return value;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Wire dark/light theming. Writes a data attribute on a target element (the\n * `<html>` element by default) and exposes the current preference via\n * {@link useTheme}.\n *\n * Pair with `themeInitScript()` in the HTML head to prevent the flash of\n * incorrect theme on first paint.\n *\n * @tempest-limits empty-catch — the theme is applied to the DOM before it is\n * persisted, so a `localStorage` write refused by quota or private mode leaves the\n * user with the theme they just picked and only forfeits it on the next load.\n * Throwing from the setter would break the switch that already worked.\n */\nexport function ThemeProvider({\n children,\n defaultTheme = \"system\",\n storageKey = \"tempest-theme\",\n target,\n attribute = \"data-tempest-theme\",\n themeColor,\n}: ThemeProviderProps) {\n const [theme, setThemeState] = useState<ThemeMode>(\n () => readStored(storageKey) ?? defaultTheme,\n );\n const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>(() => resolve(theme));\n\n const targetRef = useRef<typeof target>(target);\n useEffect(() => {\n targetRef.current = target;\n }, [target]);\n\n const attributeKey = Array.isArray(attribute) ? attribute.join(\",\") : attribute;\n const themeColorRef = useRef(themeColor);\n useEffect(() => {\n themeColorRef.current = themeColor;\n }, [themeColor]);\n\n useEffect(() => {\n const element = targetRef.current?.() ?? document.documentElement;\n if (!element) return;\n const next = resolve(theme);\n applyResolved(element, next, attribute, themeColorRef.current);\n setResolvedTheme(next);\n // attributeKey is the stable string form of `attribute`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [theme, attributeKey]);\n\n useEffect(() => {\n if (theme !== \"system\" || typeof window === \"undefined\") return;\n const list = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const handler = (): void => {\n const element = targetRef.current?.() ?? document.documentElement;\n if (!element) return;\n const next: ResolvedTheme = list.matches ? \"dark\" : \"light\";\n applyResolved(element, next, attribute, themeColorRef.current);\n setResolvedTheme(next);\n };\n list.addEventListener(\"change\", handler);\n return () => list.removeEventListener(\"change\", handler);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [theme, attributeKey]);\n\n const setTheme = useCallback(\n (next: ThemeMode) => {\n setThemeState(next);\n if (storageKey && typeof window !== \"undefined\") {\n try {\n window.localStorage.setItem(storageKey, next);\n } catch {\n /* empty */\n }\n }\n },\n [storageKey],\n );\n\n const toggle = useCallback(() => {\n setTheme(resolvedTheme === \"dark\" ? \"light\" : \"dark\");\n }, [resolvedTheme, setTheme]);\n\n const value = useMemo<ThemeContextValue>(\n () => ({ theme, resolvedTheme, setTheme, toggle }),\n [theme, resolvedTheme, setTheme, toggle],\n );\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n}\n\n/**\n * Read and mutate the current theme. Must be used inside a {@link ThemeProvider}.\n */\nexport function useTheme(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (!ctx) throw new Error(\"useTheme must be used inside a <ThemeProvider>\");\n return ctx;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.js","names":[],"sources":["../../src/theme/ThemeProvider.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { ResolvedTheme, ThemeMode } from \"./types\";\n\nexport interface ThemeContextValue {\n /** Raw user preference (light / dark / system). */\n theme: ThemeMode;\n /** Effective theme actually applied to the DOM (light or dark). */\n resolvedTheme: ResolvedTheme;\n /** Update the preference. Persisted to localStorage when `storageKey` is set. */\n setTheme: (next: ThemeMode) => void;\n /** Convenience: flip light ↔ dark. When in `system` mode, switches to the opposite of the current resolved theme. */\n toggle: () => void;\n}\n\nconst ThemeContext = createContext<ThemeContextValue | null>(null);\n\nexport interface ThemeProviderProps {\n children: ReactNode;\n /** Initial preference when nothing is stored. Default: `\"system\"`. */\n defaultTheme?: ThemeMode;\n /** localStorage key used to persist the preference. Pass `null` to disable persistence. Default: `\"tempest-theme\"`. */\n storageKey?: string | null;\n /**\n * Element that receives the theme attribute(s). Defaults to\n * `document.documentElement`. Override when scoping the theme to a subtree.\n */\n target?: () => HTMLElement | null;\n /**\n * Attribute name(s) written on the target with the resolved theme\n * (`\"light\"` / `\"dark\"`). Default: `\"data-tempest-theme\"`.\n *\n * Pass an array to mirror the theme onto more than one attribute — handy\n * when the SDK components read `data-tempest-theme` but the host app's own\n * CSS keys off a different attribute (e.g. `[\"data-tempest-theme\",\n * \"data-theme\"]`). Avoids a separate sync effect in the consumer.\n */\n attribute?: string | string[];\n /**\n * When set, keeps `<meta name=\"theme-color\">` in sync with the resolved\n * theme — `content` becomes `themeColor.dark` in dark mode and\n * `themeColor.light` in light mode. The meta tag must already exist in the\n * document `<head>`. No-op when omitted.\n */\n themeColor?: { light: string; dark: string };\n}\n\nfunction resolve(mode: ThemeMode): ResolvedTheme {\n if (mode === \"dark\" || mode === \"light\") return mode;\n if (typeof window === \"undefined\") return \"light\";\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\n/**\n * Write the resolved theme onto every configured attribute and, when a\n * `themeColor` map is provided, sync the `<meta name=\"theme-color\">` tag.\n */\nfunction applyResolved(\n element: HTMLElement,\n resolved: ResolvedTheme,\n attribute: string | string[],\n themeColor?: { light: string; dark: string },\n): void {\n const attrs = Array.isArray(attribute) ? attribute : [attribute];\n for (const attr of attrs) element.setAttribute(attr, resolved);\n if (themeColor && typeof document !== \"undefined\") {\n const meta = document.querySelector<HTMLMetaElement>('meta[name=\"theme-color\"]');\n if (meta) meta.content = themeColor[resolved];\n }\n}\n\nfunction readStored(storageKey: string | null): ThemeMode | null {\n if (!storageKey || typeof window === \"undefined\") return null;\n try {\n const value = window.localStorage.getItem(storageKey);\n if (value === \"light\" || value === \"dark\" || value === \"system\") return value;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Wire dark/light theming. Writes a data attribute on a target element (the\n * `<html>` element by default) and exposes the current preference via\n * {@link useTheme}.\n *\n * Pair with `themeInitScript()` in the HTML head to prevent the flash of\n * incorrect theme on first paint.\n */\nexport function ThemeProvider({\n children,\n defaultTheme = \"system\",\n storageKey = \"tempest-theme\",\n target,\n attribute = \"data-tempest-theme\",\n themeColor,\n}: ThemeProviderProps) {\n const [theme, setThemeState] = useState<ThemeMode>(\n () => readStored(storageKey) ?? defaultTheme,\n );\n const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>(() => resolve(theme));\n\n const targetRef = useRef<typeof target>(target);\n useEffect(() => {\n targetRef.current = target;\n }, [target]);\n\n const attributeKey = Array.isArray(attribute) ? attribute.join(\",\") : attribute;\n const themeColorRef = useRef(themeColor);\n useEffect(() => {\n themeColorRef.current = themeColor;\n }, [themeColor]);\n\n useEffect(() => {\n const element = targetRef.current?.() ?? document.documentElement;\n if (!element) return;\n const next = resolve(theme);\n applyResolved(element, next, attribute, themeColorRef.current);\n setResolvedTheme(next);\n // attributeKey is the stable string form of `attribute`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [theme, attributeKey]);\n\n useEffect(() => {\n if (theme !== \"system\" || typeof window === \"undefined\") return;\n const list = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const handler = (): void => {\n const element = targetRef.current?.() ?? document.documentElement;\n if (!element) return;\n const next: ResolvedTheme = list.matches ? \"dark\" : \"light\";\n applyResolved(element, next, attribute, themeColorRef.current);\n setResolvedTheme(next);\n };\n list.addEventListener(\"change\", handler);\n return () => list.removeEventListener(\"change\", handler);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [theme, attributeKey]);\n\n const setTheme = useCallback(\n (next: ThemeMode) => {\n setThemeState(next);\n if (storageKey && typeof window !== \"undefined\") {\n try {\n window.localStorage.setItem(storageKey, next);\n } catch {\n /* ignore quota errors */\n }\n }\n },\n [storageKey],\n );\n\n const toggle = useCallback(() => {\n setTheme(resolvedTheme === \"dark\" ? \"light\" : \"dark\");\n }, [resolvedTheme, setTheme]);\n\n const value = useMemo<ThemeContextValue>(\n () => ({ theme, resolvedTheme, setTheme, toggle }),\n [theme, resolvedTheme, setTheme, toggle],\n );\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n}\n\n/**\n * Read and mutate the current theme. Must be used inside a {@link ThemeProvider}.\n */\nexport function useTheme(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (!ctx) throw new Error(\"useTheme must be used inside a <ThemeProvider>\");\n return ctx;\n}\n"],"mappings":";;;AAuBA,IAAM,IAAe,EAAwC,IAAI;AAgCjE,SAAS,EAAQ,GAAgC;CAG7C,OAFI,MAAS,UAAU,MAAS,UAAgB,IAC5C,OAAO,SAAW,MAAoB,UACnC,OAAO,WAAW,8BAA8B,CAAC,CAAC,UAAU,SAAS;AAChF;AAMA,SAAS,EACL,GACA,GACA,GACA,GACI;CACJ,IAAM,IAAQ,MAAM,QAAQ,CAAS,IAAI,IAAY,CAAC,CAAS;CAC/D,KAAK,IAAM,KAAQ,GAAO,EAAQ,aAAa,GAAM,CAAQ;CAC7D,IAAI,KAAc,OAAO,WAAa,KAAa;EAC/C,IAAM,IAAO,SAAS,cAA+B,4BAA0B;EAC/E,AAAI,MAAM,EAAK,UAAU,EAAW;CACxC;AACJ;AAEA,SAAS,EAAW,GAA6C;CAC7D,IAAI,CAAC,KAAc,OAAO,SAAW,KAAa,OAAO;CACzD,IAAI;EACA,IAAM,IAAQ,OAAO,aAAa,QAAQ,CAAU;EAEpD,OADI,MAAU,WAAW,MAAU,UAAU,MAAU,WAAiB,IACjE;CACX,QAAQ;EACJ,OAAO;CACX;AACJ;AAUA,SAAgB,EAAc,EAC1B,aACA,kBAAe,UACf,gBAAa,iBACb,WACA,eAAY,sBACZ,iBACmB;CACnB,IAAM,CAAC,GAAO,KAAiB,QACrB,EAAW,CAAU,KAAK,CACpC,GACM,CAAC,GAAe,KAAoB,QAA8B,EAAQ,CAAK,CAAC,GAEhF,IAAY,EAAsB,CAAM;CAC9C,QAAgB;EACZ,EAAU,UAAU;CACxB,GAAG,CAAC,CAAM,CAAC;CAEX,IAAM,IAAe,MAAM,QAAQ,CAAS,IAAI,EAAU,KAAK,GAAG,IAAI,GAChE,IAAgB,EAAO,CAAU;CAevC,AAdA,QAAgB;EACZ,EAAc,UAAU;CAC5B,GAAG,CAAC,CAAU,CAAC,GAEf,QAAgB;EACZ,IAAM,IAAU,EAAU,UAAU,KAAK,SAAS;EAClD,IAAI,CAAC,GAAS;EACd,IAAM,IAAO,EAAQ,CAAK;EAE1B,AADA,EAAc,GAAS,GAAM,GAAW,EAAc,OAAO,GAC7D,EAAiB,CAAI;CAGzB,GAAG,CAAC,GAAO,CAAY,CAAC,GAExB,QAAgB;EACZ,IAAI,MAAU,YAAY,OAAO,SAAW,KAAa;EACzD,IAAM,IAAO,OAAO,WAAW,8BAA8B,GACvD,UAAsB;GACxB,IAAM,IAAU,EAAU,UAAU,KAAK,SAAS;GAClD,IAAI,CAAC,GAAS;GACd,IAAM,IAAsB,EAAK,UAAU,SAAS;GAEpD,AADA,EAAc,GAAS,GAAM,GAAW,EAAc,OAAO,GAC7D,EAAiB,CAAI;EACzB;EAEA,OADA,EAAK,iBAAiB,UAAU,CAAO,SAC1B,EAAK,oBAAoB,UAAU,CAAO;CAE3D,GAAG,CAAC,GAAO,CAAY,CAAC;CAExB,IAAM,IAAW,GACZ,MAAoB;EAEjB,IADA,EAAc,CAAI,GACd,KAAc,OAAO,SAAW,KAChC,IAAI;GACA,OAAO,aAAa,QAAQ,GAAY,CAAI;EAChD,QAAQ,CAER;CAER,GACA,CAAC,CAAU,CACf,GAEM,IAAS,QAAkB;EAC7B,EAAS,MAAkB,SAAS,UAAU,MAAM;CACxD,GAAG,CAAC,GAAe,CAAQ,CAAC,GAEtB,IAAQ,SACH;EAAE;EAAO;EAAe;EAAU;CAAO,IAChD;EAAC;EAAO;EAAe;EAAU;CAAM,CAC3C;CAEA,OAAO,kBAAC,EAAa,UAAd;EAA8B;EAAQ;CAAgC,CAAA;AACjF;AAKA,SAAgB,IAA8B;CAC1C,IAAM,IAAM,EAAW,CAAY;CACnC,IAAI,CAAC,GAAK,MAAU,MAAM,gDAAgD;CAC1E,OAAO;AACX"}
1
+ {"version":3,"file":"ThemeProvider.js","names":[],"sources":["../../src/theme/ThemeProvider.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { ResolvedTheme, ThemeMode } from \"./types\";\n\nexport interface ThemeContextValue {\n /** Raw user preference (light / dark / system). */\n theme: ThemeMode;\n /** Effective theme actually applied to the DOM (light or dark). */\n resolvedTheme: ResolvedTheme;\n /** Update the preference. Persisted to localStorage when `storageKey` is set. */\n setTheme: (next: ThemeMode) => void;\n /** Convenience: flip light ↔ dark. When in `system` mode, switches to the opposite of the current resolved theme. */\n toggle: () => void;\n}\n\nconst ThemeContext = createContext<ThemeContextValue | null>(null);\n\nexport interface ThemeProviderProps {\n children: ReactNode;\n /** Initial preference when nothing is stored. Default: `\"system\"`. */\n defaultTheme?: ThemeMode;\n /** localStorage key used to persist the preference. Pass `null` to disable persistence. Default: `\"tempest-theme\"`. */\n storageKey?: string | null;\n /**\n * Element that receives the theme attribute(s). Defaults to\n * `document.documentElement`. Override when scoping the theme to a subtree.\n */\n target?: () => HTMLElement | null;\n /**\n * Attribute name(s) written on the target with the resolved theme\n * (`\"light\"` / `\"dark\"`). Default: `\"data-tempest-theme\"`.\n *\n * Pass an array to mirror the theme onto more than one attribute — handy\n * when the SDK components read `data-tempest-theme` but the host app's own\n * CSS keys off a different attribute (e.g. `[\"data-tempest-theme\",\n * \"data-theme\"]`). Avoids a separate sync effect in the consumer.\n */\n attribute?: string | string[];\n /**\n * When set, keeps `<meta name=\"theme-color\">` in sync with the resolved\n * theme — `content` becomes `themeColor.dark` in dark mode and\n * `themeColor.light` in light mode. The meta tag must already exist in the\n * document `<head>`. No-op when omitted.\n */\n themeColor?: { light: string; dark: string };\n}\n\nfunction resolve(mode: ThemeMode): ResolvedTheme {\n if (mode === \"dark\" || mode === \"light\") return mode;\n if (typeof window === \"undefined\") return \"light\";\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n}\n\n/**\n * Write the resolved theme onto every configured attribute and, when a\n * `themeColor` map is provided, sync the `<meta name=\"theme-color\">` tag.\n */\nfunction applyResolved(\n element: HTMLElement,\n resolved: ResolvedTheme,\n attribute: string | string[],\n themeColor?: { light: string; dark: string },\n): void {\n const attrs = Array.isArray(attribute) ? attribute : [attribute];\n for (const attr of attrs) element.setAttribute(attr, resolved);\n if (themeColor && typeof document !== \"undefined\") {\n const meta = document.querySelector<HTMLMetaElement>('meta[name=\"theme-color\"]');\n if (meta) meta.content = themeColor[resolved];\n }\n}\n\nfunction readStored(storageKey: string | null): ThemeMode | null {\n if (!storageKey || typeof window === \"undefined\") return null;\n try {\n const value = window.localStorage.getItem(storageKey);\n if (value === \"light\" || value === \"dark\" || value === \"system\") return value;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Wire dark/light theming. Writes a data attribute on a target element (the\n * `<html>` element by default) and exposes the current preference via\n * {@link useTheme}.\n *\n * Pair with `themeInitScript()` in the HTML head to prevent the flash of\n * incorrect theme on first paint.\n *\n * @tempest-limits empty-catch — the theme is applied to the DOM before it is\n * persisted, so a `localStorage` write refused by quota or private mode leaves the\n * user with the theme they just picked and only forfeits it on the next load.\n * Throwing from the setter would break the switch that already worked.\n */\nexport function ThemeProvider({\n children,\n defaultTheme = \"system\",\n storageKey = \"tempest-theme\",\n target,\n attribute = \"data-tempest-theme\",\n themeColor,\n}: ThemeProviderProps) {\n const [theme, setThemeState] = useState<ThemeMode>(\n () => readStored(storageKey) ?? defaultTheme,\n );\n const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>(() => resolve(theme));\n\n const targetRef = useRef<typeof target>(target);\n useEffect(() => {\n targetRef.current = target;\n }, [target]);\n\n const attributeKey = Array.isArray(attribute) ? attribute.join(\",\") : attribute;\n const themeColorRef = useRef(themeColor);\n useEffect(() => {\n themeColorRef.current = themeColor;\n }, [themeColor]);\n\n useEffect(() => {\n const element = targetRef.current?.() ?? document.documentElement;\n if (!element) return;\n const next = resolve(theme);\n applyResolved(element, next, attribute, themeColorRef.current);\n setResolvedTheme(next);\n // attributeKey is the stable string form of `attribute`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [theme, attributeKey]);\n\n useEffect(() => {\n if (theme !== \"system\" || typeof window === \"undefined\") return;\n const list = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const handler = (): void => {\n const element = targetRef.current?.() ?? document.documentElement;\n if (!element) return;\n const next: ResolvedTheme = list.matches ? \"dark\" : \"light\";\n applyResolved(element, next, attribute, themeColorRef.current);\n setResolvedTheme(next);\n };\n list.addEventListener(\"change\", handler);\n return () => list.removeEventListener(\"change\", handler);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [theme, attributeKey]);\n\n const setTheme = useCallback(\n (next: ThemeMode) => {\n setThemeState(next);\n if (storageKey && typeof window !== \"undefined\") {\n try {\n window.localStorage.setItem(storageKey, next);\n } catch {\n /* empty */\n }\n }\n },\n [storageKey],\n );\n\n const toggle = useCallback(() => {\n setTheme(resolvedTheme === \"dark\" ? \"light\" : \"dark\");\n }, [resolvedTheme, setTheme]);\n\n const value = useMemo<ThemeContextValue>(\n () => ({ theme, resolvedTheme, setTheme, toggle }),\n [theme, resolvedTheme, setTheme, toggle],\n );\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n}\n\n/**\n * Read and mutate the current theme. Must be used inside a {@link ThemeProvider}.\n */\nexport function useTheme(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (!ctx) throw new Error(\"useTheme must be used inside a <ThemeProvider>\");\n return ctx;\n}\n"],"mappings":";;;AAuBA,IAAM,IAAe,EAAwC,IAAI;AAgCjE,SAAS,EAAQ,GAAgC;CAG7C,OAFI,MAAS,UAAU,MAAS,UAAgB,IAC5C,OAAO,SAAW,MAAoB,UACnC,OAAO,WAAW,8BAA8B,CAAC,CAAC,UAAU,SAAS;AAChF;AAMA,SAAS,EACL,GACA,GACA,GACA,GACI;CACJ,IAAM,IAAQ,MAAM,QAAQ,CAAS,IAAI,IAAY,CAAC,CAAS;CAC/D,KAAK,IAAM,KAAQ,GAAO,EAAQ,aAAa,GAAM,CAAQ;CAC7D,IAAI,KAAc,OAAO,WAAa,KAAa;EAC/C,IAAM,IAAO,SAAS,cAA+B,4BAA0B;EAC/E,AAAI,MAAM,EAAK,UAAU,EAAW;CACxC;AACJ;AAEA,SAAS,EAAW,GAA6C;CAC7D,IAAI,CAAC,KAAc,OAAO,SAAW,KAAa,OAAO;CACzD,IAAI;EACA,IAAM,IAAQ,OAAO,aAAa,QAAQ,CAAU;EAEpD,OADI,MAAU,WAAW,MAAU,UAAU,MAAU,WAAiB,IACjE;CACX,QAAQ;EACJ,OAAO;CACX;AACJ;AAeA,SAAgB,EAAc,EAC1B,aACA,kBAAe,UACf,gBAAa,iBACb,WACA,eAAY,sBACZ,iBACmB;CACnB,IAAM,CAAC,GAAO,KAAiB,QACrB,EAAW,CAAU,KAAK,CACpC,GACM,CAAC,GAAe,KAAoB,QAA8B,EAAQ,CAAK,CAAC,GAEhF,IAAY,EAAsB,CAAM;CAC9C,QAAgB;EACZ,EAAU,UAAU;CACxB,GAAG,CAAC,CAAM,CAAC;CAEX,IAAM,IAAe,MAAM,QAAQ,CAAS,IAAI,EAAU,KAAK,GAAG,IAAI,GAChE,IAAgB,EAAO,CAAU;CAevC,AAdA,QAAgB;EACZ,EAAc,UAAU;CAC5B,GAAG,CAAC,CAAU,CAAC,GAEf,QAAgB;EACZ,IAAM,IAAU,EAAU,UAAU,KAAK,SAAS;EAClD,IAAI,CAAC,GAAS;EACd,IAAM,IAAO,EAAQ,CAAK;EAE1B,AADA,EAAc,GAAS,GAAM,GAAW,EAAc,OAAO,GAC7D,EAAiB,CAAI;CAGzB,GAAG,CAAC,GAAO,CAAY,CAAC,GAExB,QAAgB;EACZ,IAAI,MAAU,YAAY,OAAO,SAAW,KAAa;EACzD,IAAM,IAAO,OAAO,WAAW,8BAA8B,GACvD,UAAsB;GACxB,IAAM,IAAU,EAAU,UAAU,KAAK,SAAS;GAClD,IAAI,CAAC,GAAS;GACd,IAAM,IAAsB,EAAK,UAAU,SAAS;GAEpD,AADA,EAAc,GAAS,GAAM,GAAW,EAAc,OAAO,GAC7D,EAAiB,CAAI;EACzB;EAEA,OADA,EAAK,iBAAiB,UAAU,CAAO,SAC1B,EAAK,oBAAoB,UAAU,CAAO;CAE3D,GAAG,CAAC,GAAO,CAAY,CAAC;CAExB,IAAM,IAAW,GACZ,MAAoB;EAEjB,IADA,EAAc,CAAI,GACd,KAAc,OAAO,SAAW,KAChC,IAAI;GACA,OAAO,aAAa,QAAQ,GAAY,CAAI;EAChD,QAAQ,CAER;CAER,GACA,CAAC,CAAU,CACf,GAEM,IAAS,QAAkB;EAC7B,EAAS,MAAkB,SAAS,UAAU,MAAM;CACxD,GAAG,CAAC,GAAe,CAAQ,CAAC,GAEtB,IAAQ,SACH;EAAE;EAAO;EAAe;EAAU;CAAO,IAChD;EAAC;EAAO;EAAe;EAAU;CAAM,CAC3C;CAEA,OAAO,kBAAC,EAAa,UAAd;EAA8B;EAAQ;CAAgC,CAAA;AACjF;AAKA,SAAgB,IAA8B;CAC1C,IAAM,IAAM,EAAW,CAAY;CACnC,IAAI,CAAC,GAAK,MAAU,MAAM,gDAAgD;CAC1E,OAAO;AACX"}