tempest-react-sdk 0.39.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/_rolldown/runtime.cjs +1 -1
- package/dist/access/access-control-context.cjs.map +1 -1
- package/dist/access/use-can.cjs.map +1 -1
- package/dist/app/AppProviders.cjs.map +1 -1
- package/dist/app/AppProviders.js.map +1 -1
- package/dist/audio/use-audio-recorder.cjs.map +1 -1
- package/dist/audio/use-audio.cjs.map +1 -1
- package/dist/audio/use-microphone.cjs.map +1 -1
- package/dist/auth/create-auth-store.cjs.map +1 -1
- package/dist/auth/lazy-with-retry.cjs.map +1 -1
- package/dist/auth/passkey.cjs.map +1 -1
- package/dist/auth/passkey.js.map +1 -1
- package/dist/auth/use-passkey.cjs.map +1 -1
- package/dist/br/BrazilMap.cjs +1 -1
- package/dist/br/BrazilMap.cjs.map +1 -1
- package/dist/br/BrazilMap.js +2 -2
- package/dist/br/BrazilMap.js.map +1 -1
- package/dist/br/BrazilStateCitySelect.cjs.map +1 -1
- package/dist/br/BrazilStateCitySelect.js.map +1 -1
- package/dist/br/BrazilStateMap.cjs +1 -1
- package/dist/br/BrazilStateMap.cjs.map +1 -1
- package/dist/br/BrazilStateMap.js +2 -2
- package/dist/br/BrazilStateMap.js.map +1 -1
- package/dist/br/MapLegend.cjs.map +1 -1
- package/dist/br/MapLegend.js.map +1 -1
- package/dist/br/MapMarkers.cjs.map +1 -1
- package/dist/br/MapMarkers.js.map +1 -1
- package/dist/br/MunicipalitySearch.cjs.map +1 -1
- package/dist/br/MunicipalitySearch.js.map +1 -1
- package/dist/br/PixQRCode.cjs.map +1 -1
- package/dist/br/PixQRCode.js.map +1 -1
- package/dist/br/use-map-hover.cjs.map +1 -1
- package/dist/br/use-map-zoom.cjs.map +1 -1
- package/dist/capture/media-recorder.cjs +1 -1
- package/dist/capture/media-recorder.js +2 -2
- package/dist/capture/use-barcode-scanner.cjs +1 -1
- package/dist/capture/use-barcode-scanner.cjs.map +1 -1
- package/dist/capture/use-barcode-scanner.js +2 -2
- package/dist/capture/use-barcode-scanner.js.map +1 -1
- package/dist/capture/use-screen-capture.cjs.map +1 -1
- package/dist/capture/use-speech-recognition.cjs.map +1 -1
- package/dist/capture/use-torch.cjs.map +1 -1
- package/dist/capture/use-video-recorder.cjs.map +1 -1
- package/dist/charts/AreaChart.cjs.map +1 -1
- package/dist/charts/AreaChart.js.map +1 -1
- package/dist/charts/BarChart.cjs.map +1 -1
- package/dist/charts/BarChart.js.map +1 -1
- package/dist/charts/LineChart.cjs.map +1 -1
- package/dist/charts/LineChart.js.map +1 -1
- package/dist/charts/PieChart.cjs.map +1 -1
- package/dist/charts/PieChart.js.map +1 -1
- package/dist/charts/RadarChart.cjs.map +1 -1
- package/dist/charts/RadarChart.js.map +1 -1
- package/dist/charts/use-chart-colors.cjs.map +1 -1
- package/dist/components/AIChat/AIChat.cjs.map +1 -1
- package/dist/components/AIChat/AIChat.js.map +1 -1
- package/dist/components/AIChat/AIChatComposer.cjs.map +1 -1
- package/dist/components/AIChat/AIChatComposer.js.map +1 -1
- package/dist/components/AIChat/AIChatTurn.cjs.map +1 -1
- package/dist/components/AIChat/AIChatTurn.js.map +1 -1
- package/dist/components/Accordion/Accordion.cjs.map +1 -1
- package/dist/components/Accordion/Accordion.js.map +1 -1
- package/dist/components/Alert/Alert.cjs.map +1 -1
- package/dist/components/Alert/Alert.js.map +1 -1
- package/dist/components/AppBar/AppBar.cjs.map +1 -1
- package/dist/components/AppBar/AppBar.js.map +1 -1
- package/dist/components/AppShell/AppShell.cjs.map +1 -1
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/AudioPlayer/AudioPlayer.cjs.map +1 -1
- package/dist/components/AudioPlayer/AudioPlayer.js.map +1 -1
- package/dist/components/AudioRecorder/AudioRecorder.cjs.map +1 -1
- package/dist/components/AudioRecorder/AudioRecorder.js.map +1 -1
- package/dist/components/Avatar/Avatar.cjs.map +1 -1
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/AvatarGroup/AvatarGroup.cjs.map +1 -1
- package/dist/components/AvatarGroup/AvatarGroup.js.map +1 -1
- package/dist/components/Badge/Badge.cjs.map +1 -1
- package/dist/components/Badge/Badge.js.map +1 -1
- package/dist/components/Banner/Banner.cjs.map +1 -1
- package/dist/components/Banner/Banner.js.map +1 -1
- package/dist/components/BarcodeScanner/BarcodeScanner.cjs.map +1 -1
- package/dist/components/BarcodeScanner/BarcodeScanner.js.map +1 -1
- package/dist/components/BottomNavigation/BottomNavigation.cjs.map +1 -1
- package/dist/components/BottomNavigation/BottomNavigation.js.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/components/Button/Button.cjs.map +1 -1
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Calendar/Calendar.cjs +1 -1
- package/dist/components/Calendar/Calendar.cjs.map +1 -1
- package/dist/components/Calendar/Calendar.js +38 -41
- package/dist/components/Calendar/Calendar.js.map +1 -1
- package/dist/components/Card/Card.cjs.map +1 -1
- package/dist/components/Card/Card.js.map +1 -1
- package/dist/components/Carousel/Carousel.cjs.map +1 -1
- package/dist/components/Carousel/Carousel.js.map +1 -1
- package/dist/components/Chat/Chat.cjs.map +1 -1
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/Chat/ChatComposer.cjs.map +1 -1
- package/dist/components/Chat/ChatComposer.js.map +1 -1
- package/dist/components/Chat/chat-groups.cjs +1 -1
- package/dist/components/Chat/chat-groups.cjs.map +1 -1
- package/dist/components/Chat/chat-groups.js +2 -2
- package/dist/components/Chat/chat-groups.js.map +1 -1
- package/dist/components/Checkbox/Checkbox.cjs.map +1 -1
- package/dist/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/ChipInput/ChipInput.cjs.map +1 -1
- package/dist/components/ChipInput/ChipInput.js.map +1 -1
- package/dist/components/ClickOutside/ClickOutside.cjs.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.js.map +1 -1
- package/dist/components/Collapsible/Collapsible.cjs.map +1 -1
- package/dist/components/Collapsible/Collapsible.js.map +1 -1
- package/dist/components/Combobox/Combobox.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Command/Command.cjs.map +1 -1
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.cjs.map +1 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.cjs.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/CopyButton/CopyButton.cjs.map +1 -1
- package/dist/components/CopyButton/CopyButton.js.map +1 -1
- package/dist/components/DataList/DataList.cjs.map +1 -1
- package/dist/components/DataList/DataList.js.map +1 -1
- package/dist/components/DataTable/DataTable.cjs +1 -1
- package/dist/components/DataTable/DataTable.cjs.map +1 -1
- package/dist/components/DataTable/DataTable.js +2 -2
- package/dist/components/DataTable/DataTable.js.map +1 -1
- package/dist/components/DataTable/EditableCell.cjs.map +1 -1
- package/dist/components/DataTable/EditableCell.js.map +1 -1
- package/dist/components/DatePicker/DatePicker.cjs.map +1 -1
- package/dist/components/DateRangePicker/DateRangePicker.cjs.map +1 -1
- package/dist/components/DateRangePicker/DateRangePicker.js.map +1 -1
- package/dist/components/DescriptionList/DescriptionList.cjs.map +1 -1
- package/dist/components/DescriptionList/DescriptionList.js.map +1 -1
- package/dist/components/Divider/Divider.cjs.map +1 -1
- package/dist/components/Divider/Divider.js.map +1 -1
- package/dist/components/Drawer/Drawer.cjs.map +1 -1
- package/dist/components/Drawer/Drawer.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/Dropzone/Dropzone.cjs.map +1 -1
- package/dist/components/Dropzone/Dropzone.js.map +1 -1
- package/dist/components/EmptyState/EmptyState.cjs.map +1 -1
- package/dist/components/EmptyState/EmptyState.js.map +1 -1
- package/dist/components/ErrorState/ErrorState.cjs.map +1 -1
- package/dist/components/ErrorState/ErrorState.js.map +1 -1
- package/dist/components/FileUpload/FileUpload.cjs +1 -1
- package/dist/components/FileUpload/FileUpload.cjs.map +1 -1
- package/dist/components/FileUpload/FileUpload.js +1 -1
- package/dist/components/FileUpload/FileUpload.js.map +1 -1
- package/dist/components/FilterBar/FilterBar.cjs.map +1 -1
- package/dist/components/FilterBar/FilterBar.js.map +1 -1
- package/dist/components/FloatingActionButton/FloatingActionButton.cjs.map +1 -1
- package/dist/components/FloatingActionButton/FloatingActionButton.js.map +1 -1
- package/dist/components/Form/Form.cjs.map +1 -1
- package/dist/components/Form/Form.js.map +1 -1
- package/dist/components/HoverCard/HoverCard.cjs.map +1 -1
- package/dist/components/HoverCard/HoverCard.js.map +1 -1
- package/dist/components/Image/Image.cjs.map +1 -1
- package/dist/components/ImageCropper/ImageCropper.cjs +1 -1
- package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -1
- package/dist/components/ImageCropper/ImageCropper.js +1 -1
- package/dist/components/ImageCropper/ImageCropper.js.map +1 -1
- package/dist/components/Input/Input.cjs.map +1 -1
- package/dist/components/Input/Input.js.map +1 -1
- package/dist/components/InstallBanner/InstallBanner.cjs.map +1 -1
- package/dist/components/InstallBanner/InstallBanner.js.map +1 -1
- package/dist/components/InstallButton/InstallButton.cjs.map +1 -1
- package/dist/components/InstallButton/InstallButton.js.map +1 -1
- package/dist/components/Kanban/Kanban.cjs.map +1 -1
- package/dist/components/Kanban/Kanban.js.map +1 -1
- package/dist/components/Label/Label.cjs.map +1 -1
- package/dist/components/Label/Label.js.map +1 -1
- package/dist/components/Lightbox/Lightbox.cjs.map +1 -1
- package/dist/components/Lightbox/Lightbox.js.map +1 -1
- package/dist/components/ListTile/ListTile.cjs.map +1 -1
- package/dist/components/ListTile/ListTile.js.map +1 -1
- package/dist/components/Markdown/Markdown.cjs +1 -1
- package/dist/components/Markdown/Markdown.cjs.map +1 -1
- package/dist/components/Markdown/Markdown.js +2 -2
- package/dist/components/Markdown/Markdown.js.map +1 -1
- package/dist/components/Masonry/Masonry.cjs.map +1 -1
- package/dist/components/Masonry/Masonry.js.map +1 -1
- package/dist/components/Menubar/Menubar.cjs.map +1 -1
- package/dist/components/Menubar/Menubar.js.map +1 -1
- package/dist/components/Modal/Modal.cjs.map +1 -1
- package/dist/components/Modal/Modal.js.map +1 -1
- package/dist/components/ModalsManager/ModalsManager.cjs.map +1 -1
- package/dist/components/ModalsManager/ModalsManager.js.map +1 -1
- package/dist/components/Money/Money.cjs.map +1 -1
- package/dist/components/Money/Money.js.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/components/NProgress/NProgress.cjs.map +1 -1
- package/dist/components/Navbar/Navbar.cjs.map +1 -1
- package/dist/components/Navbar/Navbar.js.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
- package/dist/components/NavigationRail/NavigationRail.cjs.map +1 -1
- package/dist/components/NavigationRail/NavigationRail.js.map +1 -1
- package/dist/components/NotificationCenter/NotificationCenter.cjs.map +1 -1
- package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -1
- package/dist/components/NotificationCenter/use-notification-inbox.cjs.map +1 -1
- package/dist/components/OfflineIndicator/OfflineIndicator.cjs.map +1 -1
- package/dist/components/OfflineIndicator/OfflineIndicator.js.map +1 -1
- package/dist/components/Page/Page.cjs.map +1 -1
- package/dist/components/Page/Page.js.map +1 -1
- package/dist/components/Pagination/Pagination.cjs.map +1 -1
- package/dist/components/Pagination/Pagination.js.map +1 -1
- package/dist/components/PasswordInput/PasswordInput.cjs.map +1 -1
- package/dist/components/PasswordInput/PasswordInput.js.map +1 -1
- package/dist/components/PinInput/PinInput.cjs.map +1 -1
- package/dist/components/PinInput/PinInput.js.map +1 -1
- package/dist/components/Popover/Popover.cjs.map +1 -1
- package/dist/components/Popover/Popover.js.map +1 -1
- package/dist/components/Portal/Portal.cjs.map +1 -1
- package/dist/components/Progress/Progress.cjs.map +1 -1
- package/dist/components/Progress/Progress.js.map +1 -1
- package/dist/components/QRCode/QRCode.cjs.map +1 -1
- package/dist/components/QRCode/QRCode.js.map +1 -1
- package/dist/components/Radio/Radio.cjs.map +1 -1
- package/dist/components/Radio/Radio.js.map +1 -1
- package/dist/components/RangeSlider/RangeSlider.cjs.map +1 -1
- package/dist/components/RangeSlider/RangeSlider.js.map +1 -1
- package/dist/components/RatingStars/RatingStars.cjs.map +1 -1
- package/dist/components/RatingStars/RatingStars.js.map +1 -1
- package/dist/components/RefreshIndicator/RefreshIndicator.cjs.map +1 -1
- package/dist/components/RefreshIndicator/RefreshIndicator.js.map +1 -1
- package/dist/components/RelativeTime/RelativeTime.cjs.map +1 -1
- package/dist/components/RelativeTime/RelativeTime.js.map +1 -1
- package/dist/components/Resizable/Resizable.cjs.map +1 -1
- package/dist/components/Resizable/Resizable.js.map +1 -1
- package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
- package/dist/components/Scheduler/Scheduler.js.map +1 -1
- package/dist/components/ScrollArea/ScrollArea.cjs.map +1 -1
- package/dist/components/SearchBar/SearchBar.cjs.map +1 -1
- package/dist/components/SearchBar/SearchBar.js.map +1 -1
- package/dist/components/SegmentedControl/SegmentedControl.cjs.map +1 -1
- package/dist/components/SegmentedControl/SegmentedControl.js.map +1 -1
- package/dist/components/Select/Select.cjs.map +1 -1
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/SignaturePad/SignaturePad.cjs.map +1 -1
- package/dist/components/SignaturePad/SignaturePad.js.map +1 -1
- package/dist/components/Slider/Slider.cjs.map +1 -1
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/components/Sparkline/Sparkline.cjs.map +1 -1
- package/dist/components/Sparkline/Sparkline.js.map +1 -1
- package/dist/components/Spinner/Spinner.cjs.map +1 -1
- package/dist/components/Spinner/Spinner.js.map +1 -1
- package/dist/components/Stat/Stat.cjs.map +1 -1
- package/dist/components/Stat/Stat.js.map +1 -1
- package/dist/components/Stepper/Stepper.cjs.map +1 -1
- package/dist/components/Stepper/Stepper.js.map +1 -1
- package/dist/components/StepperInput/StepperInput.cjs.map +1 -1
- package/dist/components/StepperInput/StepperInput.js.map +1 -1
- package/dist/components/Switch/Switch.cjs.map +1 -1
- package/dist/components/Switch/Switch.js.map +1 -1
- package/dist/components/SyncStatusBadge/SyncStatusBadge.cjs.map +1 -1
- package/dist/components/SyncStatusBadge/SyncStatusBadge.js.map +1 -1
- package/dist/components/Table/Table.cjs.map +1 -1
- package/dist/components/Table/Table.js.map +1 -1
- package/dist/components/Tabs/Tabs.cjs.map +1 -1
- package/dist/components/Tabs/Tabs.js.map +1 -1
- package/dist/components/Tag/Tag.cjs.map +1 -1
- package/dist/components/Tag/Tag.js.map +1 -1
- package/dist/components/Textarea/Textarea.cjs.map +1 -1
- package/dist/components/Textarea/Textarea.js.map +1 -1
- package/dist/components/TimePicker/TimePicker.cjs.map +1 -1
- package/dist/components/TimePicker/TimePicker.js.map +1 -1
- package/dist/components/Timeline/Timeline.cjs.map +1 -1
- package/dist/components/Timeline/Timeline.js.map +1 -1
- package/dist/components/Toast/ToastProvider.cjs.map +1 -1
- package/dist/components/Toast/ToastProvider.js.map +1 -1
- package/dist/components/Toggle/Toggle.cjs.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.cjs.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/Tour/Tour.cjs.map +1 -1
- package/dist/components/Tour/Tour.js.map +1 -1
- package/dist/components/Transfer/Transfer.cjs.map +1 -1
- package/dist/components/Transfer/Transfer.js.map +1 -1
- package/dist/components/TreeView/TreeView.cjs +1 -1
- package/dist/components/TreeView/TreeView.cjs.map +1 -1
- package/dist/components/TreeView/TreeView.js +1 -4
- package/dist/components/TreeView/TreeView.js.map +1 -1
- package/dist/components/UpdatePrompt/UpdatePrompt.cjs.map +1 -1
- package/dist/components/UpdatePrompt/UpdatePrompt.js.map +1 -1
- package/dist/components/VirtualList/VirtualList.cjs.map +1 -1
- package/dist/components/VirtualList/VirtualList.js.map +1 -1
- package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -1
- package/dist/components/VirtualTable/VirtualTable.js.map +1 -1
- package/dist/components/VisuallyHidden/VisuallyHidden.cjs.map +1 -1
- package/dist/components/Wizard/Wizard.cjs.map +1 -1
- package/dist/components/Wizard/Wizard.js.map +1 -1
- package/dist/data/data-provider-context.cjs.map +1 -1
- package/dist/data/use-resource.cjs.map +1 -1
- package/dist/editor/RichTextEditor.cjs.map +1 -1
- package/dist/editor/RichTextEditor.js.map +1 -1
- package/dist/error-boundary/use-error-handler.cjs.map +1 -1
- package/dist/feature-flags/FeatureFlagsProvider.cjs.map +1 -1
- package/dist/forms/FormField.cjs.map +1 -1
- package/dist/forms/masked-inputs.cjs +1 -1
- package/dist/forms/masked-inputs.cjs.map +1 -1
- package/dist/forms/masked-inputs.js +1 -1
- package/dist/forms/masked-inputs.js.map +1 -1
- package/dist/forms/use-viacep.cjs.map +1 -1
- package/dist/forms/use-zod-form.cjs.map +1 -1
- package/dist/forms/zod-resolver.cjs.map +1 -1
- package/dist/forms/zod-resolver.js.map +1 -1
- package/dist/geo/TrajectoryMap.cjs.map +1 -1
- package/dist/geo/TrajectoryMap.js.map +1 -1
- package/dist/geo/leaflet-map.cjs.map +1 -1
- package/dist/geo/leaflet-map.js.map +1 -1
- package/dist/geo/use-position-tracker.cjs.map +1 -1
- package/dist/hooks/use-announce.cjs.map +1 -1
- package/dist/hooks/use-async.cjs.map +1 -1
- package/dist/hooks/use-before-install-prompt.cjs.map +1 -1
- package/dist/hooks/use-breakpoint.cjs.map +1 -1
- package/dist/hooks/use-click-outside.cjs.map +1 -1
- package/dist/hooks/use-client-filter.cjs.map +1 -1
- package/dist/hooks/use-clipboard.cjs.map +1 -1
- package/dist/hooks/use-counter.cjs.map +1 -1
- package/dist/hooks/use-debounce.cjs.map +1 -1
- package/dist/hooks/use-deep-memo.cjs.map +1 -1
- package/dist/hooks/use-disclosure.cjs.map +1 -1
- package/dist/hooks/use-document-visibility.cjs.map +1 -1
- package/dist/hooks/use-event-listener.cjs.map +1 -1
- package/dist/hooks/use-geolocation.cjs.map +1 -1
- package/dist/hooks/use-hover.cjs.map +1 -1
- package/dist/hooks/use-idle.cjs.map +1 -1
- package/dist/hooks/use-install-prompt.cjs +1 -1
- package/dist/hooks/use-install-prompt.cjs.map +1 -1
- package/dist/hooks/use-install-prompt.js +1 -1
- package/dist/hooks/use-install-prompt.js.map +1 -1
- package/dist/hooks/use-intersection-observer.cjs.map +1 -1
- package/dist/hooks/use-interval.cjs.map +1 -1
- package/dist/hooks/use-is-first-render.cjs.map +1 -1
- package/dist/hooks/use-keyboard-shortcut.cjs +1 -1
- package/dist/hooks/use-keyboard-shortcut.cjs.map +1 -1
- package/dist/hooks/use-keyboard-shortcut.js +1 -1
- package/dist/hooks/use-keyboard-shortcut.js.map +1 -1
- package/dist/hooks/use-list-state.cjs.map +1 -1
- package/dist/hooks/use-local-storage.cjs.map +1 -1
- package/dist/hooks/use-long-press-handlers.cjs.map +1 -1
- package/dist/hooks/use-long-press.cjs.map +1 -1
- package/dist/hooks/use-map.cjs.map +1 -1
- package/dist/hooks/use-media-devices.cjs.map +1 -1
- package/dist/hooks/use-media-permission.cjs.map +1 -1
- package/dist/hooks/use-media-query.cjs.map +1 -1
- package/dist/hooks/use-object-url.cjs.map +1 -1
- package/dist/hooks/use-online.cjs.map +1 -1
- package/dist/hooks/use-pagination.cjs.map +1 -1
- package/dist/hooks/use-previous.cjs.map +1 -1
- package/dist/hooks/use-queue.cjs.map +1 -1
- package/dist/hooks/use-resize-observer.cjs.map +1 -1
- package/dist/hooks/use-scroll-overflow.cjs.map +1 -1
- package/dist/hooks/use-service-worker-update.cjs.map +1 -1
- package/dist/hooks/use-set.cjs.map +1 -1
- package/dist/hooks/use-sortable.cjs +1 -1
- package/dist/hooks/use-sortable.cjs.map +1 -1
- package/dist/hooks/use-sortable.js +0 -2
- package/dist/hooks/use-sortable.js.map +1 -1
- package/dist/hooks/use-stable-callback.cjs.map +1 -1
- package/dist/hooks/use-storage-estimate.cjs.map +1 -1
- package/dist/hooks/use-throttle.cjs.map +1 -1
- package/dist/hooks/use-timeout.cjs.map +1 -1
- package/dist/hooks/use-toggle.cjs.map +1 -1
- package/dist/hooks/use-window-size.cjs.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +7 -5
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +1 -1
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/use-poll.cjs.map +1 -1
- package/dist/i18n/I18nProvider.cjs.map +1 -1
- package/dist/icons/Icon.cjs.map +1 -1
- package/dist/icons/IconProvider.cjs.map +1 -1
- package/dist/icons/icon-context.cjs.map +1 -1
- package/dist/icons/use-icon.cjs.map +1 -1
- package/dist/imaging/use-image-processing.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.js.map +1 -1
- package/dist/oauth/use-oauth-callback.cjs.map +1 -1
- package/dist/offline/use-offline-sync.cjs.map +1 -1
- package/dist/push/use-push-subscription.cjs.map +1 -1
- package/dist/query/QueryProvider.cjs.map +1 -1
- package/dist/query/constants.cjs +1 -1
- package/dist/query/constants.cjs.map +1 -1
- package/dist/query/constants.js +10 -10
- package/dist/query/constants.js.map +1 -1
- package/dist/query/create-query-keys.cjs +1 -1
- package/dist/query/create-query-keys.cjs.map +1 -1
- package/dist/query/create-query-keys.js +1 -1
- package/dist/query/create-query-keys.js.map +1 -1
- package/dist/query/offline-persistence.cjs.map +1 -1
- package/dist/query/use-cursor-query.cjs.map +1 -1
- package/dist/query/use-offline-mutation.cjs.map +1 -1
- package/dist/query/use-paginated-query.cjs.map +1 -1
- package/dist/router/AppRouter.cjs +1 -1
- package/dist/router/AppRouter.cjs.map +1 -1
- package/dist/router/AppRouter.js +7 -2
- package/dist/router/AppRouter.js.map +1 -1
- package/dist/sse/use-event-stream.cjs.map +1 -1
- package/dist/store/create-store.cjs.map +1 -1
- package/dist/tabular/use-tabular-predictor.cjs.map +1 -1
- package/dist/telemetry/TelemetryProvider.cjs.map +1 -1
- package/dist/tempest-react-sdk.d.ts +25 -26
- package/dist/theme/ThemeProvider.cjs.map +1 -1
- package/dist/theme/color.cjs +1 -1
- package/dist/theme/color.cjs.map +1 -1
- package/dist/theme/color.js +1 -1
- package/dist/theme/color.js.map +1 -1
- package/dist/utils/cn.cjs +1 -1
- package/dist/utils/cn.cjs.map +1 -1
- package/dist/utils/cn.js +1 -1
- package/dist/utils/cn.js.map +1 -1
- package/dist/utils/objects.cjs +1 -1
- package/dist/utils/objects.cjs.map +1 -1
- package/dist/utils/objects.js +1 -1
- package/dist/utils/objects.js.map +1 -1
- package/dist/utils/xlsx.cjs +1 -1
- package/dist/utils/xlsx.cjs.map +1 -1
- package/dist/utils/xlsx.js +3 -3
- package/dist/utils/xlsx.js.map +1 -1
- package/dist/vision/core/exceptions.cjs.map +1 -1
- package/dist/vision/core/exceptions.js.map +1 -1
- package/dist/vision/core/metadata.cjs +1 -1
- package/dist/vision/core/metadata.cjs.map +1 -1
- package/dist/vision/core/metadata.js +1 -1
- package/dist/vision/core/metadata.js.map +1 -1
- package/dist/vision/io/image.cjs +1 -1
- package/dist/vision/io/image.cjs.map +1 -1
- package/dist/vision/io/image.js +4 -2
- package/dist/vision/io/image.js.map +1 -1
- package/dist/vision/preprocess/image.cjs +1 -1
- package/dist/vision/preprocess/image.cjs.map +1 -1
- package/dist/vision/preprocess/image.js +4 -2
- package/dist/vision/preprocess/image.js.map +1 -1
- package/dist/vision/tasks/base.cjs +1 -1
- package/dist/vision/tasks/base.cjs.map +1 -1
- package/dist/vision/tasks/base.js +3 -1
- package/dist/vision/tasks/base.js.map +1 -1
- package/dist/vision/tasks/classifier.cjs +1 -1
- package/dist/vision/tasks/classifier.cjs.map +1 -1
- package/dist/vision/tasks/classifier.js +2 -2
- package/dist/vision/tasks/classifier.js.map +1 -1
- package/dist/vision/tasks/detectClassify.cjs +1 -1
- package/dist/vision/tasks/detectClassify.cjs.map +1 -1
- package/dist/vision/tasks/detectClassify.js +16 -15
- package/dist/vision/tasks/detectClassify.js.map +1 -1
- package/dist/vision/use-camera-stream.cjs.map +1 -1
- package/dist/vision/use-live-luminance.cjs.map +1 -1
- package/dist/vite/create-vite-config.cjs +1 -1
- package/dist/vite/create-vite-config.cjs.map +1 -1
- package/dist/vite/create-vite-config.js +3 -3
- package/dist/vite/create-vite-config.js.map +1 -1
- package/dist/vite/tempest-icons.cjs.map +1 -1
- package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
- package/dist/vite/tempest-pwa-icons.cjs.map +1 -1
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +5 -3
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +2 -2
- package/template/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resumable-upload.js","names":[],"sources":["../../src/http/resumable-upload.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a resumable upload is one long-lived\n * state machine: chunk the file, negotiate the offset the server already has, upload\n * with retry and backoff, honour pause, resume and abort, and report progress\n * throughout. Every stage reads the same cursor and the same abort signal, and\n * createResumableUpload is the closure that owns them.\n */\nimport { buildApiError, TempestApiError } from \"./errors\";\nimport { generateIdempotencyKey } from \"./idempotency\";\nimport { retry, type RetryOptions } from \"./retry\";\n\n/** The tus protocol version this client speaks. */\nexport const TUS_VERSION = \"1.0.0\";\n\n/** Default chunk size: 5 MiB, the size most tus servers are tuned for. */\nexport const DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024;\n\n/**\n * Where a resumable upload is.\n *\n * `\"paused\"` and `\"aborted\"` are both \"not running\", but only `\"paused\"` keeps the\n * persisted offset — `abort({ discard: true })` throws it away.\n */\nexport type ResumableUploadState =\n \"idle\" | \"creating\" | \"uploading\" | \"paused\" | \"done\" | \"error\" | \"aborted\";\n\n/** Byte-level progress for a resumable upload. */\nexport interface ResumableUploadProgress {\n /** Bytes the server holds, including anything a resume skipped. */\n loaded: number;\n /** Total size of the file. */\n total: number;\n /** `loaded / total`, between 0 and 1. */\n fraction: number;\n /** Bytes already on the server when this run started. `0` on a fresh upload. */\n resumedFrom: number;\n}\n\n/** What has to survive a page reload for a resume to be possible. */\nexport interface ResumableUploadRecord {\n /** Upload URL the creation POST returned, absolute. */\n url: string;\n /** Last offset the server confirmed. */\n offset: number;\n /** File size, so a different file under the same key is not resumed into. */\n size: number;\n /** Idempotency key of the creation request, reused if creation is retried. */\n idempotencyKey: string;\n /** Epoch ms of the last write, so an app can sweep stale records. */\n updatedAt: number;\n}\n\n/**\n * Persistence for resume state. Sync or async — both are awaited.\n *\n * Implement it over anything: the default is `localStorage`, and\n * `createOfflineStore` from `@/offline` slots in when you already have a Dexie\n * database open.\n */\nexport interface ResumableUploadStorage {\n /** Read the record for `key`, or `null`. */\n get(key: string): Promise<ResumableUploadRecord | null> | ResumableUploadRecord | null;\n /** Write the record for `key`. */\n set(key: string, record: ResumableUploadRecord): Promise<void> | void;\n /** Forget the record for `key`. */\n delete(key: string): Promise<void> | void;\n}\n\n/** Options for {@link createResumableUpload}. */\nexport interface ResumableUploadOptions {\n /** tus creation endpoint, e.g. `\"/api/uploads\"`. */\n endpoint: string;\n /** The bytes to upload. A `File` also supplies the default resume key. */\n file: Blob | File;\n /** Bytes per `PATCH`. Default {@link DEFAULT_CHUNK_SIZE}. */\n chunkSize?: number;\n /** Sent as `Upload-Metadata` (base64-encoded values), e.g. `{ filename }`. */\n metadata?: Record<string, string>;\n /** Extra headers on every request. */\n headers?: Record<string, string>;\n /** Returns the current bearer token, read before each request. */\n getToken?: () => string | null | undefined;\n /** Send cookies. Default `false`. */\n withCredentials?: boolean;\n /**\n * Resume key. Defaults to a fingerprint of endpoint + file name/size/mtime, so\n * picking the same file after a reload resumes instead of restarting.\n */\n key?: string;\n /**\n * Where to persist resume state. Defaults to `localStorage`. Pass `null` to\n * disable persistence — resume then only survives a network blip, not a reload.\n */\n storage?: ResumableUploadStorage | null;\n /** Backoff for a failed chunk. Forwarded to `retry`. Default 5 attempts. */\n retry?: RetryOptions;\n /** Called on every upload-progress tick and after every confirmed chunk. */\n onProgress?: (progress: ResumableUploadProgress) => void;\n /** Called whenever {@link ResumableUpload.state} changes. */\n onStateChange?: (state: ResumableUploadState) => void;\n}\n\n/** What a finished upload resolves with. */\nexport interface ResumableUploadResult {\n /** The tus upload URL — hand this to your API to link the stored file. */\n url: string;\n /** Total bytes uploaded. */\n size: number;\n}\n\n/** A resumable upload in progress. Build one with {@link createResumableUpload}. */\nexport interface ResumableUpload {\n /**\n * Create (or re-attach to) the upload and push chunks until it is complete.\n *\n * Resolves `null` when the run stopped because of `pause()` or `abort()` —\n * neither is a failure. Rejects with a `TempestApiError` when the server\n * refused and the retries ran out.\n */\n start(): Promise<ResumableUploadResult | null>;\n /** Stop after the in-flight chunk is dropped, keeping the resume point. */\n pause(): void;\n /** Continue from the server's offset. Same resolution contract as `start`. */\n resume(): Promise<ResumableUploadResult | null>;\n /**\n * Stop for good.\n *\n * @param options - `discard: true` also sends `DELETE` (tus termination) and\n * forgets the persisted record, so the next `start()` uploads from zero.\n */\n abort(options?: { discard?: boolean }): Promise<void>;\n /** Current state. */\n readonly state: ResumableUploadState;\n /** Bytes the server has confirmed. */\n readonly offset: number;\n /** The upload URL, once creation succeeded. */\n readonly url: string | null;\n /** The resume key in use. */\n readonly key: string;\n}\n\ninterface RawResponse {\n status: number;\n text: string;\n header(name: string): string | null;\n}\n\n/**\n * Encode a string as standard base64 (padded), UTF-8 first.\n *\n * `Upload-Metadata` carries base64 values precisely so a filename with accents\n * survives an HTTP header, so the UTF-8 step is not optional: `btoa` alone throws\n * on any code point above U+00FF.\n *\n * @param value - Text to encode.\n * @returns Padded base64.\n */\nfunction base64Utf8(value: string): string {\n const bytes = new TextEncoder().encode(value);\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n\n/**\n * Build the `Upload-Metadata` header value: comma-separated `key base64(value)`.\n *\n * @param metadata - Plain string map.\n * @returns The header value, or `null` when there is nothing to send.\n */\nfunction encodeMetadata(metadata: Record<string, string> | undefined): string | null {\n if (!metadata) return null;\n const parts = Object.entries(metadata).map(([name, value]) => `${name} ${base64Utf8(value)}`);\n return parts.length > 0 ? parts.join(\",\") : null;\n}\n\n/**\n * A stable-enough identity for a file, used as the default resume key.\n *\n * Name + size + last-modified is what the tus reference clients fingerprint on:\n * it is cheap (hashing the bytes of a 400 MB recording is not) and it changes\n * whenever the file does, which is the property that matters — resuming into the\n * wrong file would corrupt it silently.\n *\n * @param endpoint - Creation endpoint, so the same file to two servers is two uploads.\n * @param file - The blob or file being uploaded.\n * @returns A key safe to use in `localStorage`.\n */\nexport function uploadFingerprint(endpoint: string, file: Blob | File): string {\n const named = file as File;\n const name = typeof named.name === \"string\" ? named.name : \"blob\";\n const modified = typeof named.lastModified === \"number\" ? named.lastModified : 0;\n return `${endpoint}|${name}|${file.size}|${file.type}|${modified}`;\n}\n\n/**\n * `localStorage`-backed resume storage — the default.\n *\n * `localStorage` and not IndexedDB on purpose. The record is four fields and a\n * URL; the requirement is only that it survives a reload, and pulling Dexie in for\n * that would put an IndexedDB dependency in the bundle of every app that uploads a\n * file. Apps that already have `createOfflineStore` open can pass their own\n * {@link ResumableUploadStorage} instead.\n *\n * @param prefix - Key prefix. Default `\"tempest-upload:\"`.\n * @returns A storage that no-ops when `localStorage` is unavailable.\n */\nexport function createLocalUploadStorage(prefix = \"tempest-upload:\"): ResumableUploadStorage {\n function backend(): Storage | null {\n try {\n return typeof localStorage === \"undefined\" ? null : localStorage;\n } catch {\n return null;\n }\n }\n\n return {\n get(key) {\n const raw = backend()?.getItem(prefix + key);\n if (!raw) return null;\n try {\n return JSON.parse(raw) as ResumableUploadRecord;\n } catch {\n return null;\n }\n },\n set(key, record) {\n backend()?.setItem(prefix + key, JSON.stringify(record));\n },\n delete(key) {\n backend()?.removeItem(prefix + key);\n },\n };\n}\n\n/**\n * Send one request over `XMLHttpRequest`.\n *\n * `XMLHttpRequest` rather than `fetch` for the same reason `uploadWithProgress`\n * uses it — `fetch` still cannot report upload progress in any browser — plus one\n * more: tus answers every write with the new `Upload-Offset` in a **response\n * header**, and `uploadWithProgress` only hands back a parsed body, so it could\n * not be reused here.\n *\n * @param init - Method, URL, headers, optional body and progress callback.\n * @returns Status, raw text and a header reader.\n */\nfunction sendRequest(init: {\n method: \"POST\" | \"HEAD\" | \"PATCH\" | \"DELETE\";\n url: string;\n headers: Record<string, string>;\n body?: Blob;\n withCredentials: boolean;\n onProgress?: (loaded: number) => void;\n register: (xhr: XMLHttpRequest) => void;\n}): Promise<RawResponse> {\n return new Promise<RawResponse>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open(init.method, init.url);\n xhr.withCredentials = init.withCredentials;\n for (const [name, value] of Object.entries(init.headers)) {\n xhr.setRequestHeader(name, value);\n }\n if (init.onProgress) {\n const report = init.onProgress;\n xhr.upload.onprogress = (event: ProgressEvent) => report(event.loaded);\n }\n xhr.onload = () =>\n resolve({\n status: xhr.status,\n text: xhr.responseText,\n header: (name) => xhr.getResponseHeader(name),\n });\n xhr.onerror = () =>\n reject(\n new TempestApiError({\n status: 0,\n detail: \"Falha de rede no upload resumível.\",\n }),\n );\n xhr.onabort = () => reject(new DOMException(\"Aborted\", \"AbortError\"));\n init.register(xhr);\n xhr.send(init.body);\n });\n}\n\nfunction parseOffset(response: RawResponse): number | null {\n const raw = response.header(\"Upload-Offset\");\n if (raw === null) return null;\n const value = Number(raw);\n return Number.isFinite(value) && value >= 0 ? value : null;\n}\n\n/**\n * Read an error body without assuming it is JSON.\n *\n * A tus proxy that rejects a chunk often answers with plain text or an HTML error\n * page, and `JSON.parse` throwing there would replace a useful status with a parse\n * error.\n *\n * @param text - Raw response text.\n * @returns The parsed object, the raw text, or `null` when the body was empty.\n */\nfunction parseErrorBody(text: string): unknown {\n if (!text) return null;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\n/**\n * Turn a refused tus response into a `TempestApiError`.\n *\n * The fallback `detail` is used unless the server sent a real error envelope,\n * because `buildApiError`'s own fallback (`\"Erro 409\"`) says nothing about which\n * step of the protocol broke — and that is the whole diagnostic value here.\n *\n * @param response - The raw response that was not acceptable.\n * @param detail - Message to use when the body carries none.\n * @returns The error to throw.\n */\nfunction failed(response: RawResponse, detail: string): TempestApiError {\n const body = parseErrorBody(response.text);\n const envelope = buildApiError(response.status, body, { get: response.header });\n const hasDetail =\n typeof body === \"object\" && body !== null && (\"detail\" in body || \"message\" in body);\n return new TempestApiError({ ...envelope, detail: hasDetail ? envelope.detail : detail });\n}\n\n/**\n * Resolve a `Location` header against the page, so a relative upload URL works.\n *\n * tus servers are free to answer creation with either an absolute URL or a\n * path, and the spec does not prefer one — a client that only handles absolute\n * URLs breaks against half the implementations.\n *\n * @param value - The raw `Location` header.\n * @returns An absolute URL, or the input when there is no base to resolve against.\n */\nfunction resolveUploadUrl(value: string): string {\n const base = typeof window === \"undefined\" ? undefined : window.location.href;\n try {\n return new URL(value, base).href;\n } catch {\n return value;\n }\n}\n\n/**\n * Chunked, resumable upload speaking the **tus 1.0.0** protocol (core plus the\n * *creation* and *termination* extensions).\n *\n * ## Why tus and not a bespoke scheme\n *\n * A resumable client whose wire format is undocumented cannot be integrated, and\n * inventing one means the backend is ours forever. tus is a published spec with\n * off-the-shelf servers (`tusd`, `tuspy`, `tus-node-server`), so a caller can point\n * this at something they did not write.\n *\n * ## What the backend must implement\n *\n * Every request carries `Tus-Resumable: 1.0.0`.\n *\n * | Step | Request | Expected response |\n * | --- | --- | --- |\n * | Create | `POST {endpoint}` + `Upload-Length`, `Upload-Metadata`, `Idempotency-Key` | `201` + `Location` (the upload URL, absolute or endpoint-relative) |\n * | Probe | `HEAD {uploadUrl}` | `200`/`204` + `Upload-Offset` |\n * | Write | `PATCH {uploadUrl}` + `Upload-Offset`, `Content-Type: application/offset+octet-stream`, chunk body | `204` + the new `Upload-Offset`; `409` when the offset does not match |\n * | Discard | `DELETE {uploadUrl}` | `204` |\n *\n * ## The failure that actually happens\n *\n * A chunk that the server stored but whose response never arrived. The client\n * cannot tell that from a chunk that was lost, and re-sending it blindly would\n * duplicate bytes. Two things prevent that:\n *\n * - **Writes are addressed, not appended.** Every `PATCH` states the offset it\n * writes at, so a retry after a lost response is asked to write bytes the server\n * already has and answers `409`. On any retry the client re-reads the truth with\n * `HEAD` first and continues from there.\n * - **Creation carries an `Idempotency-Key`** (from `generateIdempotencyKey`),\n * persisted before the first attempt and reused on retry. tus has no idempotent\n * creation of its own, so without this a lost `201` leaves an orphan upload on\n * the server. A backend that honours the header returns the same `Location`; one\n * that ignores it still works, it just keeps the orphan.\n *\n * @param options - Endpoint, file, and the knobs above.\n * @returns A handle with `start`/`pause`/`resume`/`abort` and live `state`/`offset`.\n *\n * @example\n * const upload = createResumableUpload({\n * endpoint: \"/api/uploads\",\n * file: recording,\n * metadata: { filename: \"nota.webm\", ticket: ticketId },\n * getToken: () => auth.getToken(),\n * onProgress: ({ fraction }) => setPercent(Math.round(fraction * 100)),\n * });\n *\n * const done = await upload.start();\n * if (done) await api.post(\"/api/tickets/1/audio\", { body: { url: done.url } });\n */\nexport function createResumableUpload(options: ResumableUploadOptions): ResumableUpload {\n const {\n endpoint,\n file,\n chunkSize = DEFAULT_CHUNK_SIZE,\n metadata,\n headers = {},\n getToken,\n withCredentials = false,\n key = uploadFingerprint(endpoint, file),\n storage = createLocalUploadStorage(),\n retry: retryOptions,\n onProgress,\n onStateChange,\n } = options;\n\n let state: ResumableUploadState = \"idle\";\n let offset = 0;\n let url: string | null = null;\n let idempotencyKey: string | null = null;\n let stopping: \"pause\" | \"abort\" | null = null;\n let inFlight: XMLHttpRequest | null = null;\n let resumedFrom = 0;\n\n function setState(next: ResumableUploadState): void {\n if (state === next) return;\n state = next;\n onStateChange?.(next);\n }\n\n function report(loaded: number): void {\n onProgress?.({\n loaded,\n total: file.size,\n fraction: file.size === 0 ? 1 : loaded / file.size,\n resumedFrom,\n });\n }\n\n function baseHeaders(): Record<string, string> {\n const result: Record<string, string> = { ...headers, \"Tus-Resumable\": TUS_VERSION };\n const token = getToken?.();\n if (token && !(\"Authorization\" in result)) result.Authorization = `Bearer ${token}`;\n return result;\n }\n\n function register(xhr: XMLHttpRequest): void {\n inFlight = xhr;\n }\n\n async function persist(): Promise<void> {\n if (!storage || !url || !idempotencyKey) return;\n await storage.set(key, {\n url,\n offset,\n size: file.size,\n idempotencyKey,\n updatedAt: Date.now(),\n });\n }\n\n /**\n * Ask the server how much it holds. The only source of truth after any failure.\n */\n async function probe(target: string): Promise<number> {\n const response = await sendRequest({\n method: \"HEAD\",\n url: target,\n headers: baseHeaders(),\n withCredentials,\n register,\n });\n if (response.status === 404 || response.status === 410) {\n throw new TempestApiError({\n status: response.status,\n detail: \"O upload expirou no servidor. Comece de novo.\",\n });\n }\n const confirmed = parseOffset(response);\n if (confirmed === null) throw failed(response, \"HEAD sem Upload-Offset.\");\n return confirmed;\n }\n\n /**\n * Re-attach to a persisted upload, or create a new one.\n *\n * The persisted record is only trusted when the file size still matches, and the\n * offset it holds is re-checked with `HEAD` — the client's copy can be ahead of\n * the server's whenever the last response was lost.\n */\n async function ensureUpload(): Promise<string> {\n const stored = storage ? await storage.get(key) : null;\n if (stored && stored.size === file.size) {\n idempotencyKey = stored.idempotencyKey;\n if (stored.url) {\n try {\n offset = await probe(stored.url);\n url = stored.url;\n return stored.url;\n } catch {\n offset = 0;\n }\n }\n }\n\n setState(\"creating\");\n idempotencyKey ??= generateIdempotencyKey();\n url = null;\n offset = 0;\n if (storage) {\n await storage.set(key, {\n url: \"\",\n offset: 0,\n size: file.size,\n idempotencyKey,\n updatedAt: Date.now(),\n });\n }\n\n const creationHeaders: Record<string, string> = {\n ...baseHeaders(),\n \"Upload-Length\": String(file.size),\n \"Idempotency-Key\": idempotencyKey,\n };\n const encoded = encodeMetadata(metadata);\n if (encoded) creationHeaders[\"Upload-Metadata\"] = encoded;\n\n const response = await sendRequest({\n method: \"POST\",\n url: endpoint,\n headers: creationHeaders,\n withCredentials,\n register,\n });\n if (response.status !== 201) throw failed(response, \"Criação do upload recusada.\");\n const locationHeader = response.header(\"Location\");\n if (!locationHeader) throw failed(response, \"Criação do upload sem cabeçalho Location.\");\n\n url = resolveUploadUrl(locationHeader);\n await persist();\n return url;\n }\n\n /** Push one chunk, resyncing the offset first when a previous attempt failed. */\n async function writeChunk(target: string, resync: { needed: boolean }): Promise<void> {\n if (resync.needed) {\n offset = await probe(target);\n resync.needed = false;\n report(offset);\n await persist();\n if (offset >= file.size) return;\n }\n\n const end = Math.min(offset + chunkSize, file.size);\n const from = offset;\n const response = await sendRequest({\n method: \"PATCH\",\n url: target,\n headers: {\n ...baseHeaders(),\n \"Content-Type\": \"application/offset+octet-stream\",\n \"Upload-Offset\": String(from),\n },\n body: file.slice(from, end),\n withCredentials,\n onProgress: (loaded) => report(Math.min(from + loaded, file.size)),\n register,\n });\n\n if (response.status === 409 || response.status === 412) {\n resync.needed = true;\n throw failed(response, \"Offset divergente — o servidor já tinha esses bytes.\");\n }\n if (response.status !== 204 && response.status !== 200) {\n throw failed(response, \"Chunk recusado pelo servidor.\");\n }\n\n offset = parseOffset(response) ?? end;\n report(offset);\n await persist();\n }\n\n /**\n * Drive the whole upload: attach or create, then chunk until complete.\n *\n * The `shouldRetry` predicate does double duty — besides deciding, it arms\n * `resync` so the next attempt re-reads the server's offset with `HEAD` before\n * writing. That is deliberate: it is the one place that sees *every* chunk\n * failure, whatever the cause, and after any failure the client's idea of the\n * offset is exactly what cannot be trusted.\n *\n * @returns The result, or `null` when `pause`/`abort` stopped the run.\n */\n async function run(): Promise<ResumableUploadResult | null> {\n stopping = null;\n const target = await ensureUpload();\n resumedFrom = offset;\n setState(\"uploading\");\n report(offset);\n\n const resync = { needed: false };\n while (offset < file.size) {\n if (stopping) break;\n await retry(() => writeChunk(target, resync), {\n retries: 5,\n ...retryOptions,\n shouldRetry: (error, attempt) => {\n if (stopping) return false;\n if (error instanceof DOMException && error.name === \"AbortError\") return false;\n resync.needed = true;\n return retryOptions?.shouldRetry?.(error, attempt) ?? true;\n },\n });\n }\n\n if (stopping === \"pause\") {\n setState(\"paused\");\n return null;\n }\n if (stopping === \"abort\") {\n setState(\"aborted\");\n return null;\n }\n\n setState(\"done\");\n if (storage) await storage.delete(key);\n return { url: target, size: file.size };\n }\n\n async function guarded(): Promise<ResumableUploadResult | null> {\n try {\n return await run();\n } catch (error) {\n if (\n stopping !== null ||\n (error instanceof DOMException && error.name === \"AbortError\")\n ) {\n setState(stopping === \"abort\" ? \"aborted\" : \"paused\");\n return null;\n }\n setState(\"error\");\n throw error;\n } finally {\n inFlight = null;\n }\n }\n\n function stop(reason: \"pause\" | \"abort\"): void {\n stopping = reason;\n inFlight?.abort();\n inFlight = null;\n }\n\n return {\n start: guarded,\n resume: guarded,\n pause: () => stop(\"pause\"),\n abort: async ({ discard = false } = {}) => {\n stop(\"abort\");\n setState(\"aborted\");\n if (!discard) return;\n if (url) {\n await sendRequest({\n method: \"DELETE\",\n url,\n headers: baseHeaders(),\n withCredentials,\n register: () => undefined,\n }).catch(() => undefined);\n }\n if (storage) await storage.delete(key);\n },\n get state() {\n return state;\n },\n get offset() {\n return offset;\n },\n get url() {\n return url;\n },\n key,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resumable-upload.js","names":[],"sources":["../../src/http/resumable-upload.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a resumable upload is one long-lived\n * state machine: chunk the file, negotiate the offset the server already has, upload\n * with retry and backoff, honour pause, resume and abort, and report progress\n * throughout. Every stage reads the same cursor and the same abort signal, and\n * createResumableUpload is the closure that owns them.\n */\nimport { buildApiError, TempestApiError } from \"./errors\";\nimport { generateIdempotencyKey } from \"./idempotency\";\nimport { retry, type RetryOptions } from \"./retry\";\n\n/** The tus protocol version this client speaks. */\nexport const TUS_VERSION = \"1.0.0\";\n\n/** Default chunk size: 5 MiB, the size most tus servers are tuned for. */\nexport const DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024;\n\n/**\n * Where a resumable upload is.\n *\n * `\"paused\"` and `\"aborted\"` are both \"not running\", but only `\"paused\"` keeps the\n * persisted offset — `abort({ discard: true })` throws it away.\n */\nexport type ResumableUploadState =\n \"idle\" | \"creating\" | \"uploading\" | \"paused\" | \"done\" | \"error\" | \"aborted\";\n\n/** Byte-level progress for a resumable upload. */\nexport interface ResumableUploadProgress {\n /** Bytes the server holds, including anything a resume skipped. */\n loaded: number;\n /** Total size of the file. */\n total: number;\n /** `loaded / total`, between 0 and 1. */\n fraction: number;\n /** Bytes already on the server when this run started. `0` on a fresh upload. */\n resumedFrom: number;\n}\n\n/** What has to survive a page reload for a resume to be possible. */\nexport interface ResumableUploadRecord {\n /** Upload URL the creation POST returned, absolute. */\n url: string;\n /** Last offset the server confirmed. */\n offset: number;\n /** File size, so a different file under the same key is not resumed into. */\n size: number;\n /** Idempotency key of the creation request, reused if creation is retried. */\n idempotencyKey: string;\n /** Epoch ms of the last write, so an app can sweep stale records. */\n updatedAt: number;\n}\n\n/**\n * Persistence for resume state. Sync or async — both are awaited.\n *\n * Implement it over anything: the default is `localStorage`, and\n * `createOfflineStore` from `@/offline` slots in when you already have a Dexie\n * database open.\n */\nexport interface ResumableUploadStorage {\n /** Read the record for `key`, or `null`. */\n get(key: string): Promise<ResumableUploadRecord | null> | ResumableUploadRecord | null;\n /** Write the record for `key`. */\n set(key: string, record: ResumableUploadRecord): Promise<void> | void;\n /** Forget the record for `key`. */\n delete(key: string): Promise<void> | void;\n}\n\n/** Options for {@link createResumableUpload}. */\nexport interface ResumableUploadOptions {\n /** tus creation endpoint, e.g. `\"/api/uploads\"`. */\n endpoint: string;\n /** The bytes to upload. A `File` also supplies the default resume key. */\n file: Blob | File;\n /** Bytes per `PATCH`. Default {@link DEFAULT_CHUNK_SIZE}. */\n chunkSize?: number;\n /** Sent as `Upload-Metadata` (base64-encoded values), e.g. `{ filename }`. */\n metadata?: Record<string, string>;\n /** Extra headers on every request. */\n headers?: Record<string, string>;\n /** Returns the current bearer token, read before each request. */\n getToken?: () => string | null | undefined;\n /** Send cookies. Default `false`. */\n withCredentials?: boolean;\n /**\n * Resume key. Defaults to a fingerprint of endpoint + file name/size/mtime, so\n * picking the same file after a reload resumes instead of restarting.\n */\n key?: string;\n /**\n * Where to persist resume state. Defaults to `localStorage`. Pass `null` to\n * disable persistence — resume then only survives a network blip, not a reload.\n */\n storage?: ResumableUploadStorage | null;\n /** Backoff for a failed chunk. Forwarded to `retry`. Default 5 attempts. */\n retry?: RetryOptions;\n /** Called on every upload-progress tick and after every confirmed chunk. */\n onProgress?: (progress: ResumableUploadProgress) => void;\n /** Called whenever {@link ResumableUpload.state} changes. */\n onStateChange?: (state: ResumableUploadState) => void;\n}\n\n/** What a finished upload resolves with. */\nexport interface ResumableUploadResult {\n /** The tus upload URL — hand this to your API to link the stored file. */\n url: string;\n /** Total bytes uploaded. */\n size: number;\n}\n\n/** A resumable upload in progress. Build one with {@link createResumableUpload}. */\nexport interface ResumableUpload {\n /**\n * Create (or re-attach to) the upload and push chunks until it is complete.\n *\n * Resolves `null` when the run stopped because of `pause()` or `abort()` —\n * neither is a failure. Rejects with a `TempestApiError` when the server\n * refused and the retries ran out.\n */\n start(): Promise<ResumableUploadResult | null>;\n /** Stop after the in-flight chunk is dropped, keeping the resume point. */\n pause(): void;\n /** Continue from the server's offset. Same resolution contract as `start`. */\n resume(): Promise<ResumableUploadResult | null>;\n /**\n * Stop for good.\n *\n * @param options - `discard: true` also sends `DELETE` (tus termination) and\n * forgets the persisted record, so the next `start()` uploads from zero.\n */\n abort(options?: { discard?: boolean }): Promise<void>;\n /** Current state. */\n readonly state: ResumableUploadState;\n /** Bytes the server has confirmed. */\n readonly offset: number;\n /** The upload URL, once creation succeeded. */\n readonly url: string | null;\n /** The resume key in use. */\n readonly key: string;\n}\n\ninterface RawResponse {\n status: number;\n text: string;\n header(name: string): string | null;\n}\n\n/**\n * Encode a string as standard base64 (padded), UTF-8 first.\n *\n * `Upload-Metadata` carries base64 values precisely so a filename with accents\n * survives an HTTP header, so the UTF-8 step is not optional: `btoa` alone throws\n * on any code point above U+00FF.\n *\n * @param value - Text to encode.\n * @returns Padded base64.\n */\nfunction base64Utf8(value: string): string {\n const bytes = new TextEncoder().encode(value);\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n\n/**\n * Build the `Upload-Metadata` header value: comma-separated `key base64(value)`.\n *\n * @param metadata - Plain string map.\n * @returns The header value, or `null` when there is nothing to send.\n */\nfunction encodeMetadata(metadata: Record<string, string> | undefined): string | null {\n if (!metadata) return null;\n const parts = Object.entries(metadata).map(([name, value]) => `${name} ${base64Utf8(value)}`);\n return parts.length > 0 ? parts.join(\",\") : null;\n}\n\n/**\n * A stable-enough identity for a file, used as the default resume key.\n *\n * Name + size + last-modified is what the tus reference clients fingerprint on:\n * it is cheap (hashing the bytes of a 400 MB recording is not) and it changes\n * whenever the file does, which is the property that matters — resuming into the\n * wrong file would corrupt it silently.\n *\n * @param endpoint - Creation endpoint, so the same file to two servers is two uploads.\n * @param file - The blob or file being uploaded.\n * @returns A key safe to use in `localStorage`.\n */\nexport function uploadFingerprint(endpoint: string, file: Blob | File): string {\n const named = file as File;\n const name = typeof named.name === \"string\" ? named.name : \"blob\";\n const modified = typeof named.lastModified === \"number\" ? named.lastModified : 0;\n return `${endpoint}|${name}|${file.size}|${file.type}|${modified}`;\n}\n\n/**\n * `localStorage`-backed resume storage — the default.\n *\n * `localStorage` and not IndexedDB on purpose. The record is four fields and a\n * URL; the requirement is only that it survives a reload, and pulling Dexie in for\n * that would put an IndexedDB dependency in the bundle of every app that uploads a\n * file. Apps that already have `createOfflineStore` open can pass their own\n * {@link ResumableUploadStorage} instead.\n *\n * @param prefix - Key prefix. Default `\"tempest-upload:\"`.\n * @returns A storage that no-ops when `localStorage` is unavailable.\n */\nexport function createLocalUploadStorage(prefix = \"tempest-upload:\"): ResumableUploadStorage {\n function backend(): Storage | null {\n try {\n return typeof localStorage === \"undefined\" ? null : localStorage;\n } catch {\n return null;\n }\n }\n\n return {\n get(key) {\n const raw = backend()?.getItem(prefix + key);\n if (!raw) return null;\n try {\n return JSON.parse(raw) as ResumableUploadRecord;\n } catch {\n return null;\n }\n },\n set(key, record) {\n backend()?.setItem(prefix + key, JSON.stringify(record));\n },\n delete(key) {\n backend()?.removeItem(prefix + key);\n },\n };\n}\n\n/**\n * Send one request over `XMLHttpRequest`.\n *\n * `XMLHttpRequest` rather than `fetch` for the same reason `uploadWithProgress`\n * uses it — `fetch` still cannot report upload progress in any browser — plus one\n * more: tus answers every write with the new `Upload-Offset` in a **response\n * header**, and `uploadWithProgress` only hands back a parsed body, so it could\n * not be reused here.\n *\n * @param init - Method, URL, headers, optional body and progress callback.\n * @returns Status, raw text and a header reader.\n */\nfunction sendRequest(init: {\n method: \"POST\" | \"HEAD\" | \"PATCH\" | \"DELETE\";\n url: string;\n headers: Record<string, string>;\n body?: Blob;\n withCredentials: boolean;\n onProgress?: (loaded: number) => void;\n register: (xhr: XMLHttpRequest) => void;\n}): Promise<RawResponse> {\n return new Promise<RawResponse>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open(init.method, init.url);\n xhr.withCredentials = init.withCredentials;\n for (const [name, value] of Object.entries(init.headers)) {\n xhr.setRequestHeader(name, value);\n }\n if (init.onProgress) {\n const report = init.onProgress;\n xhr.upload.onprogress = (event: ProgressEvent) => report(event.loaded);\n }\n xhr.onload = () =>\n resolve({\n status: xhr.status,\n text: xhr.responseText,\n header: (name) => xhr.getResponseHeader(name),\n });\n xhr.onerror = () =>\n reject(\n new TempestApiError({\n status: 0,\n detail: \"Falha de rede no upload resumível.\",\n }),\n );\n xhr.onabort = () => reject(new DOMException(\"Aborted\", \"AbortError\"));\n init.register(xhr);\n xhr.send(init.body);\n });\n}\n\nfunction parseOffset(response: RawResponse): number | null {\n const raw = response.header(\"Upload-Offset\");\n if (raw === null) return null;\n const value = Number(raw);\n return Number.isFinite(value) && value >= 0 ? value : null;\n}\n\n/**\n * Read an error body without assuming it is JSON.\n *\n * A tus proxy that rejects a chunk often answers with plain text or an HTML error\n * page, and `JSON.parse` throwing there would replace a useful status with a parse\n * error.\n *\n * @param text - Raw response text.\n * @returns The parsed object, the raw text, or `null` when the body was empty.\n */\nfunction parseErrorBody(text: string): unknown {\n if (!text) return null;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\n/**\n * Turn a refused tus response into a `TempestApiError`.\n *\n * The fallback `detail` is used unless the server sent a real error envelope,\n * because `buildApiError`'s own fallback (`\"Erro 409\"`) says nothing about which\n * step of the protocol broke — and that is the whole diagnostic value here.\n *\n * @param response - The raw response that was not acceptable.\n * @param detail - Message to use when the body carries none.\n * @returns The error to throw.\n */\nfunction failed(response: RawResponse, detail: string): TempestApiError {\n const body = parseErrorBody(response.text);\n const envelope = buildApiError(response.status, body, { get: response.header });\n const hasDetail =\n typeof body === \"object\" && body !== null && (\"detail\" in body || \"message\" in body);\n return new TempestApiError({ ...envelope, detail: hasDetail ? envelope.detail : detail });\n}\n\n/**\n * Resolve a `Location` header against the page, so a relative upload URL works.\n *\n * tus servers are free to answer creation with either an absolute URL or a\n * path, and the spec does not prefer one — a client that only handles absolute\n * URLs breaks against half the implementations.\n *\n * @param value - The raw `Location` header.\n * @returns An absolute URL, or the input when there is no base to resolve against.\n */\nfunction resolveUploadUrl(value: string): string {\n const base = typeof window === \"undefined\" ? undefined : window.location.href;\n try {\n return new URL(value, base).href;\n } catch {\n return value;\n }\n}\n\n/**\n * Chunked, resumable upload speaking the **tus 1.0.0** protocol (core plus the\n * *creation* and *termination* extensions).\n *\n * ## Why tus and not a bespoke scheme\n *\n * A resumable client whose wire format is undocumented cannot be integrated, and\n * inventing one means the backend is ours forever. tus is a published spec with\n * off-the-shelf servers (`tusd`, `tuspy`, `tus-node-server`), so a caller can point\n * this at something they did not write.\n *\n * ## What the backend must implement\n *\n * Every request carries `Tus-Resumable: 1.0.0`.\n *\n * | Step | Request | Expected response |\n * | --- | --- | --- |\n * | Create | `POST {endpoint}` + `Upload-Length`, `Upload-Metadata`, `Idempotency-Key` | `201` + `Location` (the upload URL, absolute or endpoint-relative) |\n * | Probe | `HEAD {uploadUrl}` | `200`/`204` + `Upload-Offset` |\n * | Write | `PATCH {uploadUrl}` + `Upload-Offset`, `Content-Type: application/offset+octet-stream`, chunk body | `204` + the new `Upload-Offset`; `409` when the offset does not match |\n * | Discard | `DELETE {uploadUrl}` | `204` |\n *\n * ## The failure that actually happens\n *\n * A chunk that the server stored but whose response never arrived. The client\n * cannot tell that from a chunk that was lost, and re-sending it blindly would\n * duplicate bytes. Two things prevent that:\n *\n * - **Writes are addressed, not appended.** Every `PATCH` states the offset it\n * writes at, so a retry after a lost response is asked to write bytes the server\n * already has and answers `409`. On any retry the client re-reads the truth with\n * `HEAD` first and continues from there.\n * - **Creation carries an `Idempotency-Key`** (from `generateIdempotencyKey`),\n * persisted before the first attempt and reused on retry. tus has no idempotent\n * creation of its own, so without this a lost `201` leaves an orphan upload on\n * the server. A backend that honours the header returns the same `Location`; one\n * that ignores it still works, it just keeps the orphan.\n *\n * @param options - Endpoint, file, and the knobs above.\n * @returns A handle with `start`/`pause`/`resume`/`abort` and live `state`/`offset`.\n *\n * @example\n * const upload = createResumableUpload({\n * endpoint: \"/api/uploads\",\n * file: recording,\n * metadata: { filename: \"nota.webm\", ticket: ticketId },\n * getToken: () => auth.getToken(),\n * onProgress: ({ fraction }) => setPercent(Math.round(fraction * 100)),\n * });\n *\n * const done = await upload.start();\n * if (done) await api.post(\"/api/tickets/1/audio\", { body: { url: done.url } });\n */\nexport function createResumableUpload(options: ResumableUploadOptions): ResumableUpload {\n const {\n endpoint,\n file,\n chunkSize = DEFAULT_CHUNK_SIZE,\n metadata,\n headers = {},\n getToken,\n withCredentials = false,\n key = uploadFingerprint(endpoint, file),\n storage = createLocalUploadStorage(),\n retry: retryOptions,\n onProgress,\n onStateChange,\n } = options;\n\n let state: ResumableUploadState = \"idle\";\n let offset = 0;\n let url: string | null = null;\n let idempotencyKey: string | null = null;\n let stopping: \"pause\" | \"abort\" | null = null;\n let inFlight: XMLHttpRequest | null = null;\n let resumedFrom = 0;\n\n function setState(next: ResumableUploadState): void {\n if (state === next) return;\n state = next;\n onStateChange?.(next);\n }\n\n function report(loaded: number): void {\n onProgress?.({\n loaded,\n total: file.size,\n fraction: file.size === 0 ? 1 : loaded / file.size,\n resumedFrom,\n });\n }\n\n function baseHeaders(): Record<string, string> {\n const result: Record<string, string> = { ...headers, \"Tus-Resumable\": TUS_VERSION };\n const token = getToken?.();\n if (token && !(\"Authorization\" in result)) result.Authorization = `Bearer ${token}`;\n return result;\n }\n\n function register(xhr: XMLHttpRequest): void {\n inFlight = xhr;\n }\n\n async function persist(): Promise<void> {\n if (!storage || !url || !idempotencyKey) return;\n await storage.set(key, {\n url,\n offset,\n size: file.size,\n idempotencyKey,\n updatedAt: Date.now(),\n });\n }\n\n /**\n * Ask the server how much it holds. The only source of truth after any failure.\n */\n async function probe(target: string): Promise<number> {\n const response = await sendRequest({\n method: \"HEAD\",\n url: target,\n headers: baseHeaders(),\n withCredentials,\n register,\n });\n if (response.status === 404 || response.status === 410) {\n throw new TempestApiError({\n status: response.status,\n detail: \"O upload expirou no servidor. Comece de novo.\",\n });\n }\n const confirmed = parseOffset(response);\n if (confirmed === null) throw failed(response, \"HEAD sem Upload-Offset.\");\n return confirmed;\n }\n\n /**\n * Re-attach to a persisted upload, or create a new one.\n *\n * The persisted record is only trusted when the file size still matches, and the\n * offset it holds is re-checked with `HEAD` — the client's copy can be ahead of\n * the server's whenever the last response was lost.\n */\n async function ensureUpload(): Promise<string> {\n const stored = storage ? await storage.get(key) : null;\n if (stored && stored.size === file.size) {\n idempotencyKey = stored.idempotencyKey;\n if (stored.url) {\n try {\n offset = await probe(stored.url);\n url = stored.url;\n return stored.url;\n } catch {\n offset = 0;\n }\n }\n }\n\n setState(\"creating\");\n idempotencyKey ??= generateIdempotencyKey();\n url = null;\n offset = 0;\n if (storage) {\n await storage.set(key, {\n url: \"\",\n offset: 0,\n size: file.size,\n idempotencyKey,\n updatedAt: Date.now(),\n });\n }\n\n const creationHeaders: Record<string, string> = {\n ...baseHeaders(),\n \"Upload-Length\": String(file.size),\n \"Idempotency-Key\": idempotencyKey,\n };\n const encoded = encodeMetadata(metadata);\n if (encoded) creationHeaders[\"Upload-Metadata\"] = encoded;\n\n const response = await sendRequest({\n method: \"POST\",\n url: endpoint,\n headers: creationHeaders,\n withCredentials,\n register,\n });\n if (response.status !== 201) throw failed(response, \"Criação do upload recusada.\");\n const locationHeader = response.header(\"Location\");\n if (!locationHeader) throw failed(response, \"Criação do upload sem cabeçalho Location.\");\n\n url = resolveUploadUrl(locationHeader);\n await persist();\n return url;\n }\n\n /** Push one chunk, resyncing the offset first when a previous attempt failed. */\n async function writeChunk(target: string, resync: { needed: boolean }): Promise<void> {\n if (resync.needed) {\n offset = await probe(target);\n resync.needed = false;\n report(offset);\n await persist();\n if (offset >= file.size) return;\n }\n\n const end = Math.min(offset + chunkSize, file.size);\n const from = offset;\n const response = await sendRequest({\n method: \"PATCH\",\n url: target,\n headers: {\n ...baseHeaders(),\n \"Content-Type\": \"application/offset+octet-stream\",\n \"Upload-Offset\": String(from),\n },\n body: file.slice(from, end),\n withCredentials,\n onProgress: (loaded) => report(Math.min(from + loaded, file.size)),\n register,\n });\n\n if (response.status === 409 || response.status === 412) {\n resync.needed = true;\n throw failed(response, \"Offset divergente — o servidor já tinha esses bytes.\");\n }\n if (response.status !== 204 && response.status !== 200) {\n throw failed(response, \"Chunk recusado pelo servidor.\");\n }\n\n offset = parseOffset(response) ?? end;\n report(offset);\n await persist();\n }\n\n /**\n * Drive the whole upload: attach or create, then chunk until complete.\n *\n * The `shouldRetry` predicate does double duty — besides deciding, it arms\n * `resync` so the next attempt re-reads the server's offset with `HEAD` before\n * writing. That is deliberate: it is the one place that sees *every* chunk\n * failure, whatever the cause, and after any failure the client's idea of the\n * offset is exactly what cannot be trusted.\n *\n * @returns The result, or `null` when `pause`/`abort` stopped the run.\n */\n async function run(): Promise<ResumableUploadResult | null> {\n stopping = null;\n const target = await ensureUpload();\n resumedFrom = offset;\n setState(\"uploading\");\n report(offset);\n\n const resync = { needed: false };\n while (offset < file.size) {\n if (stopping) break;\n await retry(() => writeChunk(target, resync), {\n retries: 5,\n ...retryOptions,\n shouldRetry: (error, attempt) => {\n if (stopping) return false;\n if (error instanceof DOMException && error.name === \"AbortError\") return false;\n resync.needed = true;\n return retryOptions?.shouldRetry?.(error, attempt) ?? true;\n },\n });\n }\n\n if (stopping === \"pause\") {\n setState(\"paused\");\n return null;\n }\n if (stopping === \"abort\") {\n setState(\"aborted\");\n return null;\n }\n\n setState(\"done\");\n if (storage) await storage.delete(key);\n return { url: target, size: file.size };\n }\n\n async function guarded(): Promise<ResumableUploadResult | null> {\n try {\n return await run();\n } catch (error) {\n if (\n stopping !== null ||\n (error instanceof DOMException && error.name === \"AbortError\")\n ) {\n setState(stopping === \"abort\" ? \"aborted\" : \"paused\");\n return null;\n }\n setState(\"error\");\n throw error;\n } finally {\n inFlight = null;\n }\n }\n\n function stop(reason: \"pause\" | \"abort\"): void {\n stopping = reason;\n inFlight?.abort();\n inFlight = null;\n }\n\n return {\n start: guarded,\n resume: guarded,\n pause: () => stop(\"pause\"),\n abort: async ({ discard = false } = {}) => {\n stop(\"abort\");\n setState(\"aborted\");\n if (!discard) return;\n if (url) {\n await sendRequest({\n method: \"DELETE\",\n url,\n headers: baseHeaders(),\n withCredentials,\n register: () => undefined,\n }).catch(() => undefined);\n }\n if (storage) await storage.delete(key);\n },\n get state() {\n return state;\n },\n get offset() {\n return offset;\n },\n get url() {\n return url;\n },\n key,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-poll.cjs","names":[],"sources":["../../src/http/use-poll.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\n\nexport interface UsePollOptions<T> {\n /** Polling interval in ms. */\n interval: number;\n /** Disable polling without unmounting. Default: false. */\n disabled?: boolean;\n /** Stop polling once the predicate returns true. */\n stopWhen?: (data: T) => boolean;\n /** Called on each error. */\n onError?: (error: unknown) => void;\n}\n\nexport interface UsePollResult<T> {\n data: T | null;\n error: unknown;\n loading: boolean;\n stop: () => void;\n start: () => void;\n}\n\n/**\n * Poll an async factory on a fixed interval. Skips overlapping requests if a\n * prior call has not finished. Pause via `disabled` or `stopWhen`.\n */\nexport function usePoll<T>(\n factory: () => Promise<T>,\n options: UsePollOptions<T>,\n): UsePollResult<T> {\n const { interval, disabled = false, stopWhen, onError } = options;\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<unknown>(null);\n const [loading, setLoading] = useState<boolean>(!disabled);\n const stopped = useRef<boolean>(disabled);\n const inFlight = useRef<boolean>(false);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n async function tick(): Promise<void> {\n if (stopped.current || inFlight.current) return;\n inFlight.current = true;\n try {\n const next = await factory();\n if (stopped.current) return;\n setData(next);\n setError(null);\n if (stopWhen?.(next)) stopped.current = true;\n } catch (err) {\n if (!stopped.current) {\n setError(err);\n onError?.(err);\n }\n } finally {\n inFlight.current = false;\n setLoading(false);\n if (!stopped.current) {\n timer.current = setTimeout(tick, interval);\n }\n }\n }\n\n useEffect(() => {\n stopped.current = disabled;\n if (disabled) {\n if (timer.current) clearTimeout(timer.current);\n return;\n }\n setLoading(true);\n void tick();\n return () => {\n stopped.current = true;\n if (timer.current) clearTimeout(timer.current);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [interval, disabled]);\n\n return {\n data,\n error,\n loading,\n stop: () => {\n stopped.current = true;\n if (timer.current) clearTimeout(timer.current);\n },\n start: () => {\n if (!stopped.current) return;\n stopped.current = false;\n void tick();\n },\n };\n}\n"],"mappings":"uBAyBA,SAAgB,EACZ,EACA,EACgB,CAChB,GAAM,CAAE,WAAU,WAAW,GAAO,WAAU,WAAY,EACpD,CAAC,EAAM,IAAA,
|
|
1
|
+
{"version":3,"file":"use-poll.cjs","names":[],"sources":["../../src/http/use-poll.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\n\nexport interface UsePollOptions<T> {\n /** Polling interval in ms. */\n interval: number;\n /** Disable polling without unmounting. Default: false. */\n disabled?: boolean;\n /** Stop polling once the predicate returns true. */\n stopWhen?: (data: T) => boolean;\n /** Called on each error. */\n onError?: (error: unknown) => void;\n}\n\nexport interface UsePollResult<T> {\n data: T | null;\n error: unknown;\n loading: boolean;\n stop: () => void;\n start: () => void;\n}\n\n/**\n * Poll an async factory on a fixed interval. Skips overlapping requests if a\n * prior call has not finished. Pause via `disabled` or `stopWhen`.\n */\nexport function usePoll<T>(\n factory: () => Promise<T>,\n options: UsePollOptions<T>,\n): UsePollResult<T> {\n const { interval, disabled = false, stopWhen, onError } = options;\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<unknown>(null);\n const [loading, setLoading] = useState<boolean>(!disabled);\n const stopped = useRef<boolean>(disabled);\n const inFlight = useRef<boolean>(false);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n async function tick(): Promise<void> {\n if (stopped.current || inFlight.current) return;\n inFlight.current = true;\n try {\n const next = await factory();\n if (stopped.current) return;\n setData(next);\n setError(null);\n if (stopWhen?.(next)) stopped.current = true;\n } catch (err) {\n if (!stopped.current) {\n setError(err);\n onError?.(err);\n }\n } finally {\n inFlight.current = false;\n setLoading(false);\n if (!stopped.current) {\n timer.current = setTimeout(tick, interval);\n }\n }\n }\n\n useEffect(() => {\n stopped.current = disabled;\n if (disabled) {\n if (timer.current) clearTimeout(timer.current);\n return;\n }\n setLoading(true);\n void tick();\n return () => {\n stopped.current = true;\n if (timer.current) clearTimeout(timer.current);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [interval, disabled]);\n\n return {\n data,\n error,\n loading,\n stop: () => {\n stopped.current = true;\n if (timer.current) clearTimeout(timer.current);\n },\n start: () => {\n if (!stopped.current) return;\n stopped.current = false;\n void tick();\n },\n };\n}\n"],"mappings":"uBAyBA,SAAgB,EACZ,EACA,EACgB,CAChB,GAAM,CAAE,WAAU,WAAW,GAAO,WAAU,WAAY,EACpD,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAmB,IAAI,EACzC,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAkB,IAAI,EAC1C,CAAC,EAAS,IAAA,EAAc,EAAA,SAAA,CAAkB,CAAC,CAAQ,EACnD,GAAA,EAAU,EAAA,OAAA,CAAgB,CAAQ,EAClC,GAAA,EAAW,EAAA,OAAA,CAAgB,EAAK,EAChC,GAAA,EAAQ,EAAA,OAAA,CAA6C,IAAI,EAE/D,eAAe,GAAsB,CAC7B,OAAQ,SAAW,EAAS,SAChC,GAAS,QAAU,GACnB,GAAI,CACA,IAAM,EAAO,MAAM,EAAQ,EAC3B,GAAI,EAAQ,QAAS,OACrB,EAAQ,CAAI,EACZ,EAAS,IAAI,EACT,IAAW,CAAI,IAAG,EAAQ,QAAU,GAC5C,OAAS,EAAK,CACL,EAAQ,UACT,EAAS,CAAG,EACZ,IAAU,CAAG,EAErB,QAAU,CACN,EAAS,QAAU,GACnB,EAAW,EAAK,EACX,EAAQ,UACT,EAAM,QAAU,WAAW,EAAM,CAAQ,EAEjD,CAlBmB,CAmBvB,CAiBA,OAfA,EAAA,EAAA,UAAA,KAAgB,CAEZ,GADA,EAAQ,QAAU,EACd,EAAU,CACN,EAAM,SAAS,aAAa,EAAM,OAAO,EAC7C,MACJ,CAGA,OAFA,EAAW,EAAI,EACf,EAAU,MACG,CACT,EAAQ,QAAU,GACd,EAAM,SAAS,aAAa,EAAM,OAAO,CACjD,CAEJ,EAAG,CAAC,EAAU,CAAQ,CAAC,EAEhB,CACH,OACA,QACA,UACA,SAAY,CACR,EAAQ,QAAU,GACd,EAAM,SAAS,aAAa,EAAM,OAAO,CACjD,EACA,UAAa,CACJ,EAAQ,UACb,EAAQ,QAAU,GAClB,EAAU,EACd,CACJ,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"I18nProvider.cjs","names":[],"sources":["../../src/i18n/I18nProvider.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createI18n, type Catalog, type I18n } from \"./create-i18n\";\n\nexport interface I18nContextValue extends I18n {\n /** Switch the active locale. Persisted when `storageKey` is set. */\n setLocale: (locale: string) => void;\n /** Locales available in the catalog. */\n availableLocales: string[];\n}\n\nconst I18nContext = createContext<I18nContextValue | null>(null);\n\nexport interface I18nProviderProps {\n children: ReactNode;\n /** Initial locale. */\n locale: string;\n /** Fallback locale used when a key is missing. */\n fallbackLocale?: string;\n /** Translation catalog. */\n messages: Catalog;\n /** localStorage key for persisting the user choice. Pass `null` to disable. Default: `\"tempest-locale\"`. */\n storageKey?: string | null;\n}\n\nfunction readStored(storageKey: string | null, available: string[]): string | null {\n if (!storageKey || typeof window === \"undefined\") return null;\n try {\n const value = window.localStorage.getItem(storageKey);\n if (value && available.includes(value)) return value;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * React provider for the SDK's lightweight i18n. Exposes the translation\n * helpers and a `setLocale` setter to children.\n *\n * @tempest-limits empty-catch — remembering the chosen locale is best-effort. The\n * switch itself is React state and already happened by the time the write runs, so\n * a storage failure costs the preference on the next load and nothing in this one.\n */\nexport function I18nProvider({\n children,\n locale: initialLocale,\n fallbackLocale,\n messages,\n storageKey = \"tempest-locale\",\n}: I18nProviderProps) {\n const availableLocales = useMemo(() => Object.keys(messages), [messages]);\n const [locale, setLocaleState] = useState<string>(\n () => readStored(storageKey, availableLocales) ?? initialLocale,\n );\n\n useEffect(() => {\n if (typeof document !== \"undefined\") {\n document.documentElement.setAttribute(\"lang\", locale);\n }\n }, [locale]);\n\n const setLocale = useCallback(\n (next: string) => {\n setLocaleState(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 value = useMemo<I18nContextValue>(() => {\n const i18n = createI18n({ locale, fallbackLocale, messages });\n return { ...i18n, setLocale, availableLocales };\n }, [locale, fallbackLocale, messages, setLocale, availableLocales]);\n\n return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;\n}\n\n/**\n * Access translation helpers. Must be used inside an {@link I18nProvider}.\n */\nexport function useI18n(): I18nContextValue {\n const ctx = useContext(I18nContext);\n if (!ctx) throw new Error(\"useI18n must be used inside an <I18nProvider>\");\n return ctx;\n}\n\n/**\n * Shortcut for components that only need the `t` function — avoids destructuring.\n */\nexport function useTranslate(): I18nContextValue[\"t\"] {\n return useI18n().t;\n}\n"],"mappings":"2FAWA,IAAM,GAAA,
|
|
1
|
+
{"version":3,"file":"I18nProvider.cjs","names":[],"sources":["../../src/i18n/I18nProvider.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createI18n, type Catalog, type I18n } from \"./create-i18n\";\n\nexport interface I18nContextValue extends I18n {\n /** Switch the active locale. Persisted when `storageKey` is set. */\n setLocale: (locale: string) => void;\n /** Locales available in the catalog. */\n availableLocales: string[];\n}\n\nconst I18nContext = createContext<I18nContextValue | null>(null);\n\nexport interface I18nProviderProps {\n children: ReactNode;\n /** Initial locale. */\n locale: string;\n /** Fallback locale used when a key is missing. */\n fallbackLocale?: string;\n /** Translation catalog. */\n messages: Catalog;\n /** localStorage key for persisting the user choice. Pass `null` to disable. Default: `\"tempest-locale\"`. */\n storageKey?: string | null;\n}\n\nfunction readStored(storageKey: string | null, available: string[]): string | null {\n if (!storageKey || typeof window === \"undefined\") return null;\n try {\n const value = window.localStorage.getItem(storageKey);\n if (value && available.includes(value)) return value;\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * React provider for the SDK's lightweight i18n. Exposes the translation\n * helpers and a `setLocale` setter to children.\n *\n * @tempest-limits empty-catch — remembering the chosen locale is best-effort. The\n * switch itself is React state and already happened by the time the write runs, so\n * a storage failure costs the preference on the next load and nothing in this one.\n */\nexport function I18nProvider({\n children,\n locale: initialLocale,\n fallbackLocale,\n messages,\n storageKey = \"tempest-locale\",\n}: I18nProviderProps) {\n const availableLocales = useMemo(() => Object.keys(messages), [messages]);\n const [locale, setLocaleState] = useState<string>(\n () => readStored(storageKey, availableLocales) ?? initialLocale,\n );\n\n useEffect(() => {\n if (typeof document !== \"undefined\") {\n document.documentElement.setAttribute(\"lang\", locale);\n }\n }, [locale]);\n\n const setLocale = useCallback(\n (next: string) => {\n setLocaleState(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 value = useMemo<I18nContextValue>(() => {\n const i18n = createI18n({ locale, fallbackLocale, messages });\n return { ...i18n, setLocale, availableLocales };\n }, [locale, fallbackLocale, messages, setLocale, availableLocales]);\n\n return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;\n}\n\n/**\n * Access translation helpers. Must be used inside an {@link I18nProvider}.\n */\nexport function useI18n(): I18nContextValue {\n const ctx = useContext(I18nContext);\n if (!ctx) throw new Error(\"useI18n must be used inside an <I18nProvider>\");\n return ctx;\n}\n\n/**\n * Shortcut for components that only need the `t` function — avoids destructuring.\n */\nexport function useTranslate(): I18nContextValue[\"t\"] {\n return useI18n().t;\n}\n"],"mappings":"2FAWA,IAAM,GAAA,EAAc,EAAA,cAAA,CAAuC,IAAI,EAc/D,SAAS,EAAW,EAA2B,EAAoC,CAC/E,GAAI,CAAC,GAAc,OAAO,OAAW,IAAa,OAAO,KACzD,GAAI,CACA,IAAM,EAAQ,OAAO,aAAa,QAAQ,CAAU,EAEpD,OADI,GAAS,EAAU,SAAS,CAAK,EAAU,EACxC,IACX,MAAQ,CACJ,OAAO,IACX,CACJ,CAUA,SAAgB,EAAa,CACzB,WACA,OAAQ,EACR,iBACA,WACA,aAAa,kBACK,CAClB,IAAM,GAAA,EAAmB,EAAA,QAAA,KAAc,OAAO,KAAK,CAAQ,EAAG,CAAC,CAAQ,CAAC,EAClE,CAAC,EAAQ,IAAA,EAAkB,EAAA,SAAA,KACvB,EAAW,EAAY,CAAgB,GAAK,CACtD,GAEA,EAAA,EAAA,UAAA,KAAgB,CACR,OAAO,SAAa,KACpB,SAAS,gBAAgB,aAAa,OAAQ,CAAM,CAE5D,EAAG,CAAC,CAAM,CAAC,EAEX,IAAM,GAAA,EAAY,EAAA,YAAA,CACb,GAAiB,CAEd,GADA,EAAe,CAAI,EACf,GAAc,OAAO,OAAW,IAChC,GAAI,CACA,OAAO,aAAa,QAAQ,EAAY,CAAI,CAChD,MAAQ,CAER,CAER,EACA,CAAC,CAAU,CACf,EAEM,GAAA,EAAQ,EAAA,QAAA,MAEH,CAAE,GADI,EAAA,WAAW,CAAE,SAAQ,iBAAgB,UAAS,CAC/C,EAAM,YAAW,kBAAiB,GAC/C,CAAC,EAAQ,EAAgB,EAAU,EAAW,CAAgB,CAAC,EAElE,OAAO,EAAA,EAAA,IAAA,CAAC,EAAY,SAAb,CAA6B,QAAQ,UAA+B,CAAA,CAC/E,CAKA,SAAgB,GAA4B,CACxC,IAAM,GAAA,EAAM,EAAA,WAAA,CAAW,CAAW,EAClC,GAAI,CAAC,EAAK,MAAU,MAAM,+CAA+C,EACzE,OAAO,CACX,CAKA,SAAgB,GAAsC,CAClD,OAAO,EAAQ,CAAC,CAAC,CACrB"}
|
package/dist/icons/Icon.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Icon.cjs","names":[],"sources":["../../src/icons/Icon.tsx"],"sourcesContent":["import { createElement, type ReactNode, type SVGProps } from \"react\";\n\nimport type { IconName } from \"./generated/icon-name\";\nimport { useIconContext } from \"./icon-context\";\nimport { iconStatus } from \"./shard-cache\";\nimport { useIcon } from \"./use-icon\";\n\nexport interface IconProps extends Omit<SVGProps<SVGSVGElement>, \"ref\"> {\n /**\n * The lucide slug, kebab-case: `\"save\"`, `\"trash-2\"`, `\"circle-alert\"`.\n *\n * Typed as the union of every slug lucide ships, so a typo is a compile\n * error — while still accepting a plain `string` for the case this component\n * exists for: a name that arrives from an API, a CMS or a config table.\n */\n name: IconName | (string & {});\n /** Width and height in px. Defaults to the provider's value, then lucide's `24`. */\n size?: number | string;\n /** Stroke width. Defaults to the provider's value, then lucide's `2`. */\n strokeWidth?: number;\n /**\n * Rendered while the icon's shard is in flight, and for a slug that does not\n * exist at all. Defaults to nothing.\n *\n * Pass a same-sized placeholder when the surrounding layout would otherwise\n * shift. Note that a slug already in the registry or the cache never renders\n * this — it resolves during render.\n */\n fallback?: ReactNode;\n}\n\n/**\n * Render a lucide icon by slug.\n *\n * All ~2000 lucide icons are addressable, without the cost that makes\n * `lucide-react`'s own `DynamicIcon` unusable: its icon map holds a dynamic\n * `import()` per icon, so a bundler has to emit ~2000 chunk boundaries and the\n * browser floods with requests. Here a slug the app wrote as a literal is\n * resolved from a static registry, and a slug only known at runtime pulls a\n * single shard covering every icon sharing its initial letter.\n *\n * An unknown slug renders `fallback` (nothing, by default) and warns in dev only\n * — it never throws, because a bad name from an API must not take down the tree.\n *\n * @example\n * <Icon name=\"save\" size={18} />\n * <Icon name={row.iconSlug} fallback={<span className=\"skeleton\" />} />\n */\nexport function Icon({ name, size, strokeWidth, fallback = null, ...rest }: IconProps): ReactNode {\n const context = useIconContext();\n const resolved = useIcon(name);\n\n if (!resolved) {\n if (isDev() && iconStatus(name) === \"missing\") warnUnknownIcon(name);\n return fallback;\n }\n\n return createElement(resolved, {\n size: size ?? context?.size,\n strokeWidth: strokeWidth ?? context?.strokeWidth,\n ...rest,\n });\n}\n\n/**\n * Whether the bundle was built for development.\n *\n * Guarded because the SDK also runs where `import.meta.env` does not exist — a\n * service-worker context, a Node test runner, a build-time script.\n */\nfunction isDev(): boolean {\n return Boolean(import.meta.env?.DEV);\n}\n\nconst warnedSlugs = new Set<string>();\n\n/**\n * Warn once per unknown slug.\n *\n * Once, because `<Icon>` re-renders and a warning per render would bury the\n * console; the set is never cleared, since a slug that does not exist will not\n * start existing within the life of the page.\n *\n * @param name - The slug that resolved to nothing.\n */\nfunction warnUnknownIcon(name: string): void {\n if (warnedSlugs.has(name)) return;\n warnedSlugs.add(name);\n console.warn(\n `[tempest-react-sdk] <Icon name=\"${name}\" /> — no such lucide icon. ` +\n `Slugs are kebab-case (\"circle-alert\", not \"CircleAlert\"); check against \\`iconNames\\`.`,\n );\n}\n"],"mappings":"wHAgDA,SAAgB,EAAK,CAAE,OAAM,OAAM,cAAa,WAAW,KAAM,GAAG,GAA8B,CAC9F,IAAM,EAAU,EAAA,eAAe,EACzB,EAAW,EAAA,QAAQ,CAAI,EAO7B,OALK,GAKL,
|
|
1
|
+
{"version":3,"file":"Icon.cjs","names":[],"sources":["../../src/icons/Icon.tsx"],"sourcesContent":["import { createElement, type ReactNode, type SVGProps } from \"react\";\n\nimport type { IconName } from \"./generated/icon-name\";\nimport { useIconContext } from \"./icon-context\";\nimport { iconStatus } from \"./shard-cache\";\nimport { useIcon } from \"./use-icon\";\n\nexport interface IconProps extends Omit<SVGProps<SVGSVGElement>, \"ref\"> {\n /**\n * The lucide slug, kebab-case: `\"save\"`, `\"trash-2\"`, `\"circle-alert\"`.\n *\n * Typed as the union of every slug lucide ships, so a typo is a compile\n * error — while still accepting a plain `string` for the case this component\n * exists for: a name that arrives from an API, a CMS or a config table.\n */\n name: IconName | (string & {});\n /** Width and height in px. Defaults to the provider's value, then lucide's `24`. */\n size?: number | string;\n /** Stroke width. Defaults to the provider's value, then lucide's `2`. */\n strokeWidth?: number;\n /**\n * Rendered while the icon's shard is in flight, and for a slug that does not\n * exist at all. Defaults to nothing.\n *\n * Pass a same-sized placeholder when the surrounding layout would otherwise\n * shift. Note that a slug already in the registry or the cache never renders\n * this — it resolves during render.\n */\n fallback?: ReactNode;\n}\n\n/**\n * Render a lucide icon by slug.\n *\n * All ~2000 lucide icons are addressable, without the cost that makes\n * `lucide-react`'s own `DynamicIcon` unusable: its icon map holds a dynamic\n * `import()` per icon, so a bundler has to emit ~2000 chunk boundaries and the\n * browser floods with requests. Here a slug the app wrote as a literal is\n * resolved from a static registry, and a slug only known at runtime pulls a\n * single shard covering every icon sharing its initial letter.\n *\n * An unknown slug renders `fallback` (nothing, by default) and warns in dev only\n * — it never throws, because a bad name from an API must not take down the tree.\n *\n * @example\n * <Icon name=\"save\" size={18} />\n * <Icon name={row.iconSlug} fallback={<span className=\"skeleton\" />} />\n */\nexport function Icon({ name, size, strokeWidth, fallback = null, ...rest }: IconProps): ReactNode {\n const context = useIconContext();\n const resolved = useIcon(name);\n\n if (!resolved) {\n if (isDev() && iconStatus(name) === \"missing\") warnUnknownIcon(name);\n return fallback;\n }\n\n return createElement(resolved, {\n size: size ?? context?.size,\n strokeWidth: strokeWidth ?? context?.strokeWidth,\n ...rest,\n });\n}\n\n/**\n * Whether the bundle was built for development.\n *\n * Guarded because the SDK also runs where `import.meta.env` does not exist — a\n * service-worker context, a Node test runner, a build-time script.\n */\nfunction isDev(): boolean {\n return Boolean(import.meta.env?.DEV);\n}\n\nconst warnedSlugs = new Set<string>();\n\n/**\n * Warn once per unknown slug.\n *\n * Once, because `<Icon>` re-renders and a warning per render would bury the\n * console; the set is never cleared, since a slug that does not exist will not\n * start existing within the life of the page.\n *\n * @param name - The slug that resolved to nothing.\n */\nfunction warnUnknownIcon(name: string): void {\n if (warnedSlugs.has(name)) return;\n warnedSlugs.add(name);\n console.warn(\n `[tempest-react-sdk] <Icon name=\"${name}\" /> — no such lucide icon. ` +\n `Slugs are kebab-case (\"circle-alert\", not \"CircleAlert\"); check against \\`iconNames\\`.`,\n );\n}\n"],"mappings":"wHAgDA,SAAgB,EAAK,CAAE,OAAM,OAAM,cAAa,WAAW,KAAM,GAAG,GAA8B,CAC9F,IAAM,EAAU,EAAA,eAAe,EACzB,EAAW,EAAA,QAAQ,CAAI,EAO7B,OALK,GAKL,EAAO,EAAA,cAAA,CAAc,EAAU,CAC3B,KAAM,GAAQ,GAAS,KACvB,YAAa,GAAe,GAAS,YACrC,GAAG,CACP,CAAC,GARO,EAAM,GAAK,EAAA,WAAW,CAAI,IAAM,WAAW,EAAgB,CAAI,EAC5D,EAQf,CAQA,SAAS,GAAiB,CACtB,MAAO,CAAA,CACX,CAEA,IAAM,EAAc,IAAI,IAWxB,SAAS,EAAgB,EAAoB,CACrC,EAAY,IAAI,CAAI,IACxB,EAAY,IAAI,CAAI,EACpB,QAAQ,KACJ,mCAAmC,EAAK,mHAE5C,EACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IconProvider.cjs","names":[],"sources":["../../src/icons/IconProvider.tsx"],"sourcesContent":["import { type ReactNode, useMemo } from \"react\";\n\nimport { IconContext, type IconContextValue, type IconRegistry } from \"./icon-context\";\n\nexport interface IconProviderProps {\n /**\n * Icons resolved with zero extra requests.\n *\n * Pass `staticIcons` from the `virtual:tempest-icons` module the\n * `tempestIcons()` Vite plugin generates, or a table built by\n * `createIconRegistry`. Slugs found here never trigger a shard fetch.\n */\n registry?: IconRegistry;\n /** Default `size` for every `<Icon>` below. Lucide's own default is `24`. */\n size?: number | string;\n /** Default `strokeWidth` for every `<Icon>` below. Lucide's own default is `2`. */\n strokeWidth?: number;\n children: ReactNode;\n}\n\nconst EMPTY_REGISTRY: IconRegistry = {};\n\n/**\n * Provide a static icon registry and shared `<Icon>` defaults to a subtree.\n *\n * Entirely optional: without it `<Icon>` still resolves any of lucide's ~2000\n * slugs by fetching the shard that owns it. What the provider buys is the\n * zero-request path for the slugs an app actually uses, plus one place to set\n * `size`/`strokeWidth` instead of repeating them at every call site.\n *\n * @example\n * import { IconProvider } from \"tempest-react-sdk/icons\";\n * import { staticIcons } from \"virtual:tempest-icons\";\n *\n * <IconProvider registry={staticIcons} size={18}>\n * <App />\n * </IconProvider>\n */\nexport function IconProvider({\n registry = EMPTY_REGISTRY,\n size,\n strokeWidth,\n children,\n}: IconProviderProps): ReactNode {\n const value = useMemo<IconContextValue>(\n () => ({ registry, size, strokeWidth }),\n [registry, size, strokeWidth],\n );\n return <IconContext.Provider value={value}>{children}</IconContext.Provider>;\n}\n"],"mappings":"4FAoBA,IAAM,EAA+B,CAAC,EAkBtC,SAAgB,EAAa,CACzB,WAAW,EACX,OACA,cACA,YAC6B,CAC7B,IAAM,GAAA,
|
|
1
|
+
{"version":3,"file":"IconProvider.cjs","names":[],"sources":["../../src/icons/IconProvider.tsx"],"sourcesContent":["import { type ReactNode, useMemo } from \"react\";\n\nimport { IconContext, type IconContextValue, type IconRegistry } from \"./icon-context\";\n\nexport interface IconProviderProps {\n /**\n * Icons resolved with zero extra requests.\n *\n * Pass `staticIcons` from the `virtual:tempest-icons` module the\n * `tempestIcons()` Vite plugin generates, or a table built by\n * `createIconRegistry`. Slugs found here never trigger a shard fetch.\n */\n registry?: IconRegistry;\n /** Default `size` for every `<Icon>` below. Lucide's own default is `24`. */\n size?: number | string;\n /** Default `strokeWidth` for every `<Icon>` below. Lucide's own default is `2`. */\n strokeWidth?: number;\n children: ReactNode;\n}\n\nconst EMPTY_REGISTRY: IconRegistry = {};\n\n/**\n * Provide a static icon registry and shared `<Icon>` defaults to a subtree.\n *\n * Entirely optional: without it `<Icon>` still resolves any of lucide's ~2000\n * slugs by fetching the shard that owns it. What the provider buys is the\n * zero-request path for the slugs an app actually uses, plus one place to set\n * `size`/`strokeWidth` instead of repeating them at every call site.\n *\n * @example\n * import { IconProvider } from \"tempest-react-sdk/icons\";\n * import { staticIcons } from \"virtual:tempest-icons\";\n *\n * <IconProvider registry={staticIcons} size={18}>\n * <App />\n * </IconProvider>\n */\nexport function IconProvider({\n registry = EMPTY_REGISTRY,\n size,\n strokeWidth,\n children,\n}: IconProviderProps): ReactNode {\n const value = useMemo<IconContextValue>(\n () => ({ registry, size, strokeWidth }),\n [registry, size, strokeWidth],\n );\n return <IconContext.Provider value={value}>{children}</IconContext.Provider>;\n}\n"],"mappings":"4FAoBA,IAAM,EAA+B,CAAC,EAkBtC,SAAgB,EAAa,CACzB,WAAW,EACX,OACA,cACA,YAC6B,CAC7B,IAAM,GAAA,EAAQ,EAAA,QAAA,MACH,CAAE,WAAU,OAAM,aAAY,GACrC,CAAC,EAAU,EAAM,CAAW,CAChC,EACA,OAAO,EAAA,EAAA,IAAA,CAAC,EAAA,YAAY,SAAb,CAA6B,QAAQ,UAA+B,CAAA,CAC/E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"icon-context.cjs","names":[],"sources":["../../src/icons/icon-context.ts"],"sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { LucideIcon } from \"lucide-react\";\n\n/** A slug → icon-component table, as produced by `createIconRegistry`. */\nexport type IconRegistry = Readonly<Record<string, LucideIcon>>;\n\nexport interface IconContextValue {\n registry: IconRegistry;\n size: number | string | undefined;\n strokeWidth: number | undefined;\n}\n\n/**\n * The icon context.\n *\n * Lives here rather than next to `IconProvider` so that the provider module\n * exports only a component — a file mixing components with other values breaks\n * Fast Refresh, which is what `react-refresh/only-export-components` guards.\n */\nexport const IconContext = createContext<IconContextValue | null>(null);\n\n/**\n * Build an icon registry from statically imported lucide components.\n *\n * The point is that the imports are static, so the bundler keeps exactly the\n * icons named here and tree-shakes the rest — the opposite of `DynamicIcon`,\n * which forces a chunk boundary for all ~2000 icons whether you use them or not.\n *\n * @example\n * import { createIconRegistry } from \"tempest-react-sdk/icons\";\n * import { Save, Trash2 } from \"lucide-react\";\n *\n * export const icons = createIconRegistry({ save: Save, \"trash-2\": Trash2 });\n *\n * @param icons - Slug → icon component.\n * @returns The registry, ready for `IconProvider`.\n */\nexport function createIconRegistry(icons: Record<string, LucideIcon>): IconRegistry {\n return icons;\n}\n\n/**\n * Read the nearest icon context, or `null` outside a provider.\n *\n * @returns The context value, or `null`.\n */\nexport function useIconContext(): IconContextValue | null {\n return useContext(IconContext);\n}\n"],"mappings":"uBAmBA,IAAa,GAAA,
|
|
1
|
+
{"version":3,"file":"icon-context.cjs","names":[],"sources":["../../src/icons/icon-context.ts"],"sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { LucideIcon } from \"lucide-react\";\n\n/** A slug → icon-component table, as produced by `createIconRegistry`. */\nexport type IconRegistry = Readonly<Record<string, LucideIcon>>;\n\nexport interface IconContextValue {\n registry: IconRegistry;\n size: number | string | undefined;\n strokeWidth: number | undefined;\n}\n\n/**\n * The icon context.\n *\n * Lives here rather than next to `IconProvider` so that the provider module\n * exports only a component — a file mixing components with other values breaks\n * Fast Refresh, which is what `react-refresh/only-export-components` guards.\n */\nexport const IconContext = createContext<IconContextValue | null>(null);\n\n/**\n * Build an icon registry from statically imported lucide components.\n *\n * The point is that the imports are static, so the bundler keeps exactly the\n * icons named here and tree-shakes the rest — the opposite of `DynamicIcon`,\n * which forces a chunk boundary for all ~2000 icons whether you use them or not.\n *\n * @example\n * import { createIconRegistry } from \"tempest-react-sdk/icons\";\n * import { Save, Trash2 } from \"lucide-react\";\n *\n * export const icons = createIconRegistry({ save: Save, \"trash-2\": Trash2 });\n *\n * @param icons - Slug → icon component.\n * @returns The registry, ready for `IconProvider`.\n */\nexport function createIconRegistry(icons: Record<string, LucideIcon>): IconRegistry {\n return icons;\n}\n\n/**\n * Read the nearest icon context, or `null` outside a provider.\n *\n * @returns The context value, or `null`.\n */\nexport function useIconContext(): IconContextValue | null {\n return useContext(IconContext);\n}\n"],"mappings":"uBAmBA,IAAa,GAAA,EAAc,EAAA,cAAA,CAAuC,IAAI,EAkBtE,SAAgB,EAAmB,EAAiD,CAChF,OAAO,CACX,CAOA,SAAgB,GAA0C,CACtD,OAAA,EAAO,EAAA,WAAA,CAAW,CAAW,CACjC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-icon.cjs","names":[],"sources":["../../src/icons/use-icon.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { LucideIcon } from \"lucide-react\";\n\nimport { useIconContext } from \"./icon-context\";\nimport { loadIcon, peekIcon, subscribeToIcons } from \"./shard-cache\";\n\n/**\n * Resolve an icon slug to its component.\n *\n * Resolution is synchronous whenever it can be: the provider registry first, then\n * the shard cache. Both are plain reads during render, so a slug the app declared\n * statically — or one whose shard a sibling icon already pulled in — is returned\n * on the first frame with no intermediate empty state.\n *\n * Only a genuinely cold slug falls through to the effect, which fetches the shard\n * for that initial letter and re-renders once it lands.\n *\n * @param slug - Any icon slug, canonical or deprecated. `undefined` skips work.\n * @returns The icon component, or `undefined` while it is loading or if the slug\n * does not exist.\n */\nexport function useIcon(slug: string | undefined): LucideIcon | undefined {\n const context = useIconContext();\n const fromRegistry = slug ? context?.registry[slug] : undefined;\n const [, forceRender] = useState(0);\n\n const cached = !fromRegistry && slug ? peekIcon(slug) : undefined;\n const resolved = fromRegistry ?? cached;\n\n useEffect(() => {\n if (!slug || resolved) return;\n let active = true;\n const unsubscribe = subscribeToIcons(() => {\n if (active) forceRender((n) => n + 1);\n });\n void loadIcon(slug);\n return () => {\n active = false;\n unsubscribe();\n };\n }, [slug, resolved]);\n\n return resolved;\n}\n"],"mappings":"4FAqBA,SAAgB,EAAQ,EAAkD,CACtE,IAAM,EAAU,EAAA,eAAe,EACzB,EAAe,EAAO,GAAS,SAAS,GAAQ,IAAA,GAChD,EAAG,IAAA,
|
|
1
|
+
{"version":3,"file":"use-icon.cjs","names":[],"sources":["../../src/icons/use-icon.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { LucideIcon } from \"lucide-react\";\n\nimport { useIconContext } from \"./icon-context\";\nimport { loadIcon, peekIcon, subscribeToIcons } from \"./shard-cache\";\n\n/**\n * Resolve an icon slug to its component.\n *\n * Resolution is synchronous whenever it can be: the provider registry first, then\n * the shard cache. Both are plain reads during render, so a slug the app declared\n * statically — or one whose shard a sibling icon already pulled in — is returned\n * on the first frame with no intermediate empty state.\n *\n * Only a genuinely cold slug falls through to the effect, which fetches the shard\n * for that initial letter and re-renders once it lands.\n *\n * @param slug - Any icon slug, canonical or deprecated. `undefined` skips work.\n * @returns The icon component, or `undefined` while it is loading or if the slug\n * does not exist.\n */\nexport function useIcon(slug: string | undefined): LucideIcon | undefined {\n const context = useIconContext();\n const fromRegistry = slug ? context?.registry[slug] : undefined;\n const [, forceRender] = useState(0);\n\n const cached = !fromRegistry && slug ? peekIcon(slug) : undefined;\n const resolved = fromRegistry ?? cached;\n\n useEffect(() => {\n if (!slug || resolved) return;\n let active = true;\n const unsubscribe = subscribeToIcons(() => {\n if (active) forceRender((n) => n + 1);\n });\n void loadIcon(slug);\n return () => {\n active = false;\n unsubscribe();\n };\n }, [slug, resolved]);\n\n return resolved;\n}\n"],"mappings":"4FAqBA,SAAgB,EAAQ,EAAkD,CACtE,IAAM,EAAU,EAAA,eAAe,EACzB,EAAe,EAAO,GAAS,SAAS,GAAQ,IAAA,GAChD,EAAG,IAAA,EAAe,EAAA,SAAA,CAAS,CAAC,EAE5B,EAAS,CAAC,GAAgB,EAAO,EAAA,SAAS,CAAI,EAAI,IAAA,GAClD,EAAW,GAAgB,EAejC,OAbA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,GAAQ,EAAU,OACvB,IAAI,EAAS,GACP,EAAc,EAAA,qBAAuB,CACnC,GAAQ,EAAa,GAAM,EAAI,CAAC,CACxC,CAAC,EAED,OADA,EAAK,SAAS,CAAI,MACL,CACT,EAAS,GACT,EAAY,CAChB,CACJ,EAAG,CAAC,EAAM,CAAQ,CAAC,EAEZ,CACX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-image-processing.cjs","names":[],"sources":["../../src/imaging/use-image-processing.ts"],"sourcesContent":["/**\n * React bindings for image work.\n *\n * Two things components get wrong every time: they leak object URLs (an\n * `URL.createObjectURL` without its `revokeObjectURL` holds the whole blob\n * in memory for the page's lifetime), and they set state after unmounting\n * when a slow resize finishes late. Both live here instead of in each\n * component.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { compressToTarget, type CompressedImage } from \"./compress\";\nimport type { CompressOptions, ImageSource, ProcessedImage, ResizeOptions } from \"./types\";\nimport { resizeImage } from \"./transform\";\n\n/** What {@link useImagePreview} returns. */\nexport interface UseImagePreviewResult {\n /** Object URL for the current source, or `null`. */\n readonly url: string | null;\n}\n\n/**\n * Hold an object URL for a blob, and revoke it when it is replaced.\n *\n * @example\n * ```tsx\n * function Preview({ file }: { file: File | null }) {\n * const { url } = useImagePreview(file);\n * return url === null ? null : <img src={url} alt=\"\" />;\n * }\n * ```\n *\n * @param source The blob to preview, or `null`.\n * @returns The object URL, valid until the source changes or the component\n * unmounts.\n */\nexport function useImagePreview(source: Blob | null | undefined): UseImagePreviewResult {\n const [url, setUrl] = useState<string | null>(null);\n\n useEffect(() => {\n if (source === null || source === undefined) {\n setUrl(null);\n return;\n }\n const created = URL.createObjectURL(source);\n setUrl(created);\n return () => {\n URL.revokeObjectURL(created);\n };\n }, [source]);\n\n return { url };\n}\n\n/** Lifecycle of a processing call. */\nexport type ImageProcessingStatus = \"idle\" | \"working\" | \"done\" | \"error\";\n\n/** What {@link useImageProcessing} returns. */\nexport interface UseImageProcessingResult {\n /** Resize (and re-encode) an image. */\n readonly resize: (source: ImageSource, options?: ResizeOptions) => Promise<ProcessedImage>;\n /** Compress an image into a byte budget. */\n readonly compress: (source: ImageSource, options: CompressOptions) => Promise<CompressedImage>;\n /** The most recent result. */\n readonly result: ProcessedImage | null;\n /** Where the last call is. */\n readonly status: ImageProcessingStatus;\n /** Why the last call failed. */\n readonly error: Error | null;\n /** Whether a call is in flight. */\n readonly isWorking: boolean;\n}\n\n/**\n * Run image operations with status tracking, safe against unmount.\n *\n * @example\n * ```tsx\n * function Upload() {\n * const { compress, isWorking, result } = useImageProcessing();\n *\n * async function onPick(file: File) {\n * const ready = await compress(file, { maxBytes: 1_000_000, width: 1600 });\n * await fetch(\"/api/photos\", { method: \"POST\", body: ready.blob });\n * }\n *\n * return <input type=\"file\" disabled={isWorking} onChange={(e) => onPick(e.target.files![0]!)} />;\n * }\n * ```\n *\n * The returned promises still reject on failure, so a caller can `try`\n * around them; `status` and `error` exist for rendering, not for swallowing\n * the failure.\n *\n * @returns The operations plus their state.\n */\nexport function useImageProcessing(): UseImageProcessingResult {\n const [result, setResult] = useState<ProcessedImage | null>(null);\n const [status, setStatus] = useState<ImageProcessingStatus>(\"idle\");\n const [error, setError] = useState<Error | null>(null);\n const mounted = useRef(true);\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const run = useCallback(\n async <T extends ProcessedImage>(work: () => Promise<T>): Promise<T> => {\n if (mounted.current) {\n setStatus(\"working\");\n setError(null);\n }\n try {\n const produced = await work();\n if (mounted.current) {\n setResult(produced);\n setStatus(\"done\");\n }\n return produced;\n } catch (caught) {\n const failure = caught instanceof Error ? caught : new Error(String(caught));\n if (mounted.current) {\n setError(failure);\n setStatus(\"error\");\n }\n throw failure;\n }\n },\n [],\n );\n\n const resize = useCallback(\n (source: ImageSource, options: ResizeOptions = {}) =>\n run(() => resizeImage(source, options)),\n [run],\n );\n\n const compress = useCallback(\n (source: ImageSource, options: CompressOptions) =>\n run(() => compressToTarget(source, options)),\n [run],\n );\n\n return {\n resize,\n compress,\n result,\n status,\n error,\n isWorking: status === \"working\",\n };\n}\n"],"mappings":"sFAqCA,SAAgB,EAAgB,EAAwD,CACpF,GAAM,CAAC,EAAK,IAAA,
|
|
1
|
+
{"version":3,"file":"use-image-processing.cjs","names":[],"sources":["../../src/imaging/use-image-processing.ts"],"sourcesContent":["/**\n * React bindings for image work.\n *\n * Two things components get wrong every time: they leak object URLs (an\n * `URL.createObjectURL` without its `revokeObjectURL` holds the whole blob\n * in memory for the page's lifetime), and they set state after unmounting\n * when a slow resize finishes late. Both live here instead of in each\n * component.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { compressToTarget, type CompressedImage } from \"./compress\";\nimport type { CompressOptions, ImageSource, ProcessedImage, ResizeOptions } from \"./types\";\nimport { resizeImage } from \"./transform\";\n\n/** What {@link useImagePreview} returns. */\nexport interface UseImagePreviewResult {\n /** Object URL for the current source, or `null`. */\n readonly url: string | null;\n}\n\n/**\n * Hold an object URL for a blob, and revoke it when it is replaced.\n *\n * @example\n * ```tsx\n * function Preview({ file }: { file: File | null }) {\n * const { url } = useImagePreview(file);\n * return url === null ? null : <img src={url} alt=\"\" />;\n * }\n * ```\n *\n * @param source The blob to preview, or `null`.\n * @returns The object URL, valid until the source changes or the component\n * unmounts.\n */\nexport function useImagePreview(source: Blob | null | undefined): UseImagePreviewResult {\n const [url, setUrl] = useState<string | null>(null);\n\n useEffect(() => {\n if (source === null || source === undefined) {\n setUrl(null);\n return;\n }\n const created = URL.createObjectURL(source);\n setUrl(created);\n return () => {\n URL.revokeObjectURL(created);\n };\n }, [source]);\n\n return { url };\n}\n\n/** Lifecycle of a processing call. */\nexport type ImageProcessingStatus = \"idle\" | \"working\" | \"done\" | \"error\";\n\n/** What {@link useImageProcessing} returns. */\nexport interface UseImageProcessingResult {\n /** Resize (and re-encode) an image. */\n readonly resize: (source: ImageSource, options?: ResizeOptions) => Promise<ProcessedImage>;\n /** Compress an image into a byte budget. */\n readonly compress: (source: ImageSource, options: CompressOptions) => Promise<CompressedImage>;\n /** The most recent result. */\n readonly result: ProcessedImage | null;\n /** Where the last call is. */\n readonly status: ImageProcessingStatus;\n /** Why the last call failed. */\n readonly error: Error | null;\n /** Whether a call is in flight. */\n readonly isWorking: boolean;\n}\n\n/**\n * Run image operations with status tracking, safe against unmount.\n *\n * @example\n * ```tsx\n * function Upload() {\n * const { compress, isWorking, result } = useImageProcessing();\n *\n * async function onPick(file: File) {\n * const ready = await compress(file, { maxBytes: 1_000_000, width: 1600 });\n * await fetch(\"/api/photos\", { method: \"POST\", body: ready.blob });\n * }\n *\n * return <input type=\"file\" disabled={isWorking} onChange={(e) => onPick(e.target.files![0]!)} />;\n * }\n * ```\n *\n * The returned promises still reject on failure, so a caller can `try`\n * around them; `status` and `error` exist for rendering, not for swallowing\n * the failure.\n *\n * @returns The operations plus their state.\n */\nexport function useImageProcessing(): UseImageProcessingResult {\n const [result, setResult] = useState<ProcessedImage | null>(null);\n const [status, setStatus] = useState<ImageProcessingStatus>(\"idle\");\n const [error, setError] = useState<Error | null>(null);\n const mounted = useRef(true);\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const run = useCallback(\n async <T extends ProcessedImage>(work: () => Promise<T>): Promise<T> => {\n if (mounted.current) {\n setStatus(\"working\");\n setError(null);\n }\n try {\n const produced = await work();\n if (mounted.current) {\n setResult(produced);\n setStatus(\"done\");\n }\n return produced;\n } catch (caught) {\n const failure = caught instanceof Error ? caught : new Error(String(caught));\n if (mounted.current) {\n setError(failure);\n setStatus(\"error\");\n }\n throw failure;\n }\n },\n [],\n );\n\n const resize = useCallback(\n (source: ImageSource, options: ResizeOptions = {}) =>\n run(() => resizeImage(source, options)),\n [run],\n );\n\n const compress = useCallback(\n (source: ImageSource, options: CompressOptions) =>\n run(() => compressToTarget(source, options)),\n [run],\n );\n\n return {\n resize,\n compress,\n result,\n status,\n error,\n isWorking: status === \"working\",\n };\n}\n"],"mappings":"sFAqCA,SAAgB,EAAgB,EAAwD,CACpF,GAAM,CAAC,EAAK,IAAA,EAAU,EAAA,SAAA,CAAwB,IAAI,EAclD,OAZA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,GAAW,KAA8B,CACzC,EAAO,IAAI,EACX,MACJ,CACA,IAAM,EAAU,IAAI,gBAAgB,CAAM,EAE1C,OADA,EAAO,CAAO,MACD,CACT,IAAI,gBAAgB,CAAO,CAC/B,CACJ,EAAG,CAAC,CAAM,CAAC,EAEJ,CAAE,KAAI,CACjB,CA4CA,SAAgB,GAA+C,CAC3D,GAAM,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAAgC,IAAI,EAC1D,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAAgC,MAAM,EAC5D,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAuB,IAAI,EAC/C,GAAA,EAAU,EAAA,OAAA,CAAO,EAAI,GAE3B,EAAA,EAAA,UAAA,MACI,EAAQ,QAAU,OACL,CACT,EAAQ,QAAU,EACtB,GACD,CAAC,CAAC,EAEL,IAAM,GAAA,EAAM,EAAA,YAAA,CACR,KAAiC,IAAuC,CAChE,EAAQ,UACR,EAAU,SAAS,EACnB,EAAS,IAAI,GAEjB,GAAI,CACA,IAAM,EAAW,MAAM,EAAK,EAK5B,OAJI,EAAQ,UACR,EAAU,CAAQ,EAClB,EAAU,MAAM,GAEb,CACX,OAAS,EAAQ,CACb,IAAM,EAAU,aAAkB,MAAQ,EAAa,MAAM,OAAO,CAAM,CAAC,EAK3E,MAJI,EAAQ,UACR,EAAS,CAAO,EAChB,EAAU,OAAO,GAEf,CACV,CACJ,EACA,CAAC,CACL,EAcA,MAAO,CACH,QAAA,EAbW,EAAA,YAAA,EACV,EAAqB,EAAyB,CAAC,IAC5C,MAAU,EAAA,YAAY,EAAQ,CAAO,CAAC,EAC1C,CAAC,CAAG,CAUJ,EACA,UAAA,EARa,EAAA,YAAA,EACZ,EAAqB,IAClB,MAAU,EAAA,iBAAiB,EAAQ,CAAO,CAAC,EAC/C,CAAC,CAAG,CAKJ,EACA,SACA,SACA,QACA,UAAW,IAAW,SAC1B,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleSignIn.cjs","names":[],"sources":["../../src/oauth/GoogleSignIn.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — component, theme, text, shape, size and width are\n * Google's own button parameters, passed through with their names intact so the\n * reader can match them to Google's documentation;\n * onSuccess/onError/locale/disableOneTap are the wrapper's.\n */\nimport type { CSSProperties, ReactNode } from \"react\";\nimport type { OAuthCredential, OAuthError } from \"./types\";\n\nexport type GoogleSignInTheme = \"filled_blue\" | \"filled_black\" | \"outline\";\nexport type GoogleSignInText = \"signin_with\" | \"signup_with\" | \"continue_with\" | \"signin\";\nexport type GoogleSignInShape = \"rectangular\" | \"pill\" | \"circle\" | \"square\";\nexport type GoogleSignInSize = \"large\" | \"medium\" | \"small\";\n\nexport interface GoogleSignInProps {\n /**\n * `GoogleLogin` component from `@react-oauth/google`. The caller imports\n * it and passes it through so the SDK doesn't take `@react-oauth/google`\n * as a peer dep.\n */\n component: (props: Record<string, unknown>) => ReactNode;\n /** Fired with the validated credential on success. */\n onSuccess: (credential: OAuthCredential) => void | Promise<void>;\n /** Fired with a normalised error on failure. */\n onError?: (error: OAuthError) => void;\n /** Optional locale override (e.g. `\"pt-BR\"`). Falls back to browser locale. */\n locale?: string;\n /** Visual theme — passed through to `<GoogleLogin>`. */\n theme?: GoogleSignInTheme;\n /** Button text variant. */\n text?: GoogleSignInText;\n /** Button shape. */\n shape?: GoogleSignInShape;\n /** Button size. */\n size?: GoogleSignInSize;\n /** Disable Google's \"One Tap\" auto-prompt. Default `false`. */\n disableOneTap?: boolean;\n /** Optional `width` override (px) — Google's button is fixed-width. */\n width?: number;\n /** Optional className applied to the wrapper. */\n className?: string;\n /** Optional inline style applied to the wrapper. */\n style?: CSSProperties;\n}\n\n/**\n * Thin wrapper over `@react-oauth/google`'s `<GoogleLogin>` that:\n *\n * 1. Normalises the success payload into [[OAuthCredential]] (`idToken`,\n * `provider: \"google\"`, `raw`).\n * 2. Normalises errors into [[OAuthError]].\n * 3. Lets you pass `GoogleLogin` via the `component` prop, so the SDK does\n * not declare `@react-oauth/google` as a peer dep — apps that don't use\n * Google never pay for it.\n *\n * @example\n * import { GoogleLogin, GoogleOAuthProvider } from \"@react-oauth/google\";\n * import { GoogleSignIn } from \"tempest-react-sdk\";\n *\n * <GoogleOAuthProvider clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID}>\n * <GoogleSignIn\n * component={GoogleLogin}\n * onSuccess={async ({ idToken }) => {\n * await api.post(\"/auth/google\", { body: { id_token: idToken } });\n * }}\n * onError={(err) => toast.error(err.message)}\n * />\n * </GoogleOAuthProvider>\n */\nexport function GoogleSignIn({\n component: Component,\n onSuccess,\n onError,\n locale,\n theme,\n text,\n shape,\n size,\n disableOneTap,\n width,\n className,\n style,\n}: GoogleSignInProps) {\n return (\n <div className={className} style={style}>\n {Component({\n onSuccess: (response: { credential?: string }) => {\n if (!response.credential) {\n onError?.({\n provider: \"google\",\n message: \"Google returned no credential\",\n raw: response,\n });\n return;\n }\n return onSuccess({\n idToken: response.credential,\n provider: \"google\",\n raw: response,\n });\n },\n onError: () => {\n onError?.({\n provider: \"google\",\n message: \"Google login failed\",\n });\n },\n locale,\n theme,\n text,\n shape,\n size,\n useOneTap: !disableOneTap,\n width,\n })}\n </div>\n );\n}\n"],"mappings":"mCAqEA,SAAgB,EAAa,CACzB,UAAW,EACX,YACA,UACA,SACA,QACA,OACA,QACA,OACA,gBACA,QACA,YACA,SACkB,CAClB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAgB,YAAkB,
|
|
1
|
+
{"version":3,"file":"GoogleSignIn.cjs","names":[],"sources":["../../src/oauth/GoogleSignIn.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — component, theme, text, shape, size and width are\n * Google's own button parameters, passed through with their names intact so the\n * reader can match them to Google's documentation;\n * onSuccess/onError/locale/disableOneTap are the wrapper's.\n */\nimport type { CSSProperties, ReactNode } from \"react\";\nimport type { OAuthCredential, OAuthError } from \"./types\";\n\nexport type GoogleSignInTheme = \"filled_blue\" | \"filled_black\" | \"outline\";\nexport type GoogleSignInText = \"signin_with\" | \"signup_with\" | \"continue_with\" | \"signin\";\nexport type GoogleSignInShape = \"rectangular\" | \"pill\" | \"circle\" | \"square\";\nexport type GoogleSignInSize = \"large\" | \"medium\" | \"small\";\n\nexport interface GoogleSignInProps {\n /**\n * `GoogleLogin` component from `@react-oauth/google`. The caller imports\n * it and passes it through so the SDK doesn't take `@react-oauth/google`\n * as a peer dep.\n */\n component: (props: Record<string, unknown>) => ReactNode;\n /** Fired with the validated credential on success. */\n onSuccess: (credential: OAuthCredential) => void | Promise<void>;\n /** Fired with a normalised error on failure. */\n onError?: (error: OAuthError) => void;\n /** Optional locale override (e.g. `\"pt-BR\"`). Falls back to browser locale. */\n locale?: string;\n /** Visual theme — passed through to `<GoogleLogin>`. */\n theme?: GoogleSignInTheme;\n /** Button text variant. */\n text?: GoogleSignInText;\n /** Button shape. */\n shape?: GoogleSignInShape;\n /** Button size. */\n size?: GoogleSignInSize;\n /** Disable Google's \"One Tap\" auto-prompt. Default `false`. */\n disableOneTap?: boolean;\n /** Optional `width` override (px) — Google's button is fixed-width. */\n width?: number;\n /** Optional className applied to the wrapper. */\n className?: string;\n /** Optional inline style applied to the wrapper. */\n style?: CSSProperties;\n}\n\n/**\n * Thin wrapper over `@react-oauth/google`'s `<GoogleLogin>` that:\n *\n * 1. Normalises the success payload into [[OAuthCredential]] (`idToken`,\n * `provider: \"google\"`, `raw`).\n * 2. Normalises errors into [[OAuthError]].\n * 3. Lets you pass `GoogleLogin` via the `component` prop, so the SDK does\n * not declare `@react-oauth/google` as a peer dep — apps that don't use\n * Google never pay for it.\n *\n * @example\n * import { GoogleLogin, GoogleOAuthProvider } from \"@react-oauth/google\";\n * import { GoogleSignIn } from \"tempest-react-sdk\";\n *\n * <GoogleOAuthProvider clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID}>\n * <GoogleSignIn\n * component={GoogleLogin}\n * onSuccess={async ({ idToken }) => {\n * await api.post(\"/auth/google\", { body: { id_token: idToken } });\n * }}\n * onError={(err) => toast.error(err.message)}\n * />\n * </GoogleOAuthProvider>\n */\nexport function GoogleSignIn({\n component: Component,\n onSuccess,\n onError,\n locale,\n theme,\n text,\n shape,\n size,\n disableOneTap,\n width,\n className,\n style,\n}: GoogleSignInProps) {\n return (\n <div className={className} style={style}>\n {Component({\n onSuccess: (response: { credential?: string }) => {\n if (!response.credential) {\n onError?.({\n provider: \"google\",\n message: \"Google returned no credential\",\n raw: response,\n });\n return;\n }\n return onSuccess({\n idToken: response.credential,\n provider: \"google\",\n raw: response,\n });\n },\n onError: () => {\n onError?.({\n provider: \"google\",\n message: \"Google login failed\",\n });\n },\n locale,\n theme,\n text,\n shape,\n size,\n useOneTap: !disableOneTap,\n width,\n })}\n </div>\n );\n}\n"],"mappings":"mCAqEA,SAAgB,EAAa,CACzB,UAAW,EACX,YACA,UACA,SACA,QACA,OACA,QACA,OACA,gBACA,QACA,YACA,SACkB,CAClB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAgB,YAAkB,QAC7B,SAAA,EAAU,CACP,UAAY,GAAsC,CAC9C,GAAI,CAAC,EAAS,WAAY,CACtB,IAAU,CACN,SAAU,SACV,QAAS,gCACT,IAAK,CACT,CAAC,EACD,MACJ,CACA,OAAO,EAAU,CACb,QAAS,EAAS,WAClB,SAAU,SACV,IAAK,CACT,CAAC,CACL,EACA,YAAe,CACX,IAAU,CACN,SAAU,SACV,QAAS,qBACb,CAAC,CACL,EACA,SACA,QACA,OACA,QACA,OACA,UAAW,CAAC,EACZ,OACJ,CAAC,CACA,CAAA,CAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleSignIn.js","names":[],"sources":["../../src/oauth/GoogleSignIn.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — component, theme, text, shape, size and width are\n * Google's own button parameters, passed through with their names intact so the\n * reader can match them to Google's documentation;\n * onSuccess/onError/locale/disableOneTap are the wrapper's.\n */\nimport type { CSSProperties, ReactNode } from \"react\";\nimport type { OAuthCredential, OAuthError } from \"./types\";\n\nexport type GoogleSignInTheme = \"filled_blue\" | \"filled_black\" | \"outline\";\nexport type GoogleSignInText = \"signin_with\" | \"signup_with\" | \"continue_with\" | \"signin\";\nexport type GoogleSignInShape = \"rectangular\" | \"pill\" | \"circle\" | \"square\";\nexport type GoogleSignInSize = \"large\" | \"medium\" | \"small\";\n\nexport interface GoogleSignInProps {\n /**\n * `GoogleLogin` component from `@react-oauth/google`. The caller imports\n * it and passes it through so the SDK doesn't take `@react-oauth/google`\n * as a peer dep.\n */\n component: (props: Record<string, unknown>) => ReactNode;\n /** Fired with the validated credential on success. */\n onSuccess: (credential: OAuthCredential) => void | Promise<void>;\n /** Fired with a normalised error on failure. */\n onError?: (error: OAuthError) => void;\n /** Optional locale override (e.g. `\"pt-BR\"`). Falls back to browser locale. */\n locale?: string;\n /** Visual theme — passed through to `<GoogleLogin>`. */\n theme?: GoogleSignInTheme;\n /** Button text variant. */\n text?: GoogleSignInText;\n /** Button shape. */\n shape?: GoogleSignInShape;\n /** Button size. */\n size?: GoogleSignInSize;\n /** Disable Google's \"One Tap\" auto-prompt. Default `false`. */\n disableOneTap?: boolean;\n /** Optional `width` override (px) — Google's button is fixed-width. */\n width?: number;\n /** Optional className applied to the wrapper. */\n className?: string;\n /** Optional inline style applied to the wrapper. */\n style?: CSSProperties;\n}\n\n/**\n * Thin wrapper over `@react-oauth/google`'s `<GoogleLogin>` that:\n *\n * 1. Normalises the success payload into [[OAuthCredential]] (`idToken`,\n * `provider: \"google\"`, `raw`).\n * 2. Normalises errors into [[OAuthError]].\n * 3. Lets you pass `GoogleLogin` via the `component` prop, so the SDK does\n * not declare `@react-oauth/google` as a peer dep — apps that don't use\n * Google never pay for it.\n *\n * @example\n * import { GoogleLogin, GoogleOAuthProvider } from \"@react-oauth/google\";\n * import { GoogleSignIn } from \"tempest-react-sdk\";\n *\n * <GoogleOAuthProvider clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID}>\n * <GoogleSignIn\n * component={GoogleLogin}\n * onSuccess={async ({ idToken }) => {\n * await api.post(\"/auth/google\", { body: { id_token: idToken } });\n * }}\n * onError={(err) => toast.error(err.message)}\n * />\n * </GoogleOAuthProvider>\n */\nexport function GoogleSignIn({\n component: Component,\n onSuccess,\n onError,\n locale,\n theme,\n text,\n shape,\n size,\n disableOneTap,\n width,\n className,\n style,\n}: GoogleSignInProps) {\n return (\n <div className={className} style={style}>\n {Component({\n onSuccess: (response: { credential?: string }) => {\n if (!response.credential) {\n onError?.({\n provider: \"google\",\n message: \"Google returned no credential\",\n raw: response,\n });\n return;\n }\n return onSuccess({\n idToken: response.credential,\n provider: \"google\",\n raw: response,\n });\n },\n onError: () => {\n onError?.({\n provider: \"google\",\n message: \"Google login failed\",\n });\n },\n locale,\n theme,\n text,\n shape,\n size,\n useOneTap: !disableOneTap,\n width,\n })}\n </div>\n );\n}\n"],"mappings":";;AAqEA,SAAgB,EAAa,EACzB,WAAW,GACX,cACA,YACA,WACA,UACA,SACA,UACA,SACA,kBACA,UACA,cACA,YACkB;CAClB,OACI,kBAAC,OAAD;EAAgB;EAAkB;
|
|
1
|
+
{"version":3,"file":"GoogleSignIn.js","names":[],"sources":["../../src/oauth/GoogleSignIn.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — component, theme, text, shape, size and width are\n * Google's own button parameters, passed through with their names intact so the\n * reader can match them to Google's documentation;\n * onSuccess/onError/locale/disableOneTap are the wrapper's.\n */\nimport type { CSSProperties, ReactNode } from \"react\";\nimport type { OAuthCredential, OAuthError } from \"./types\";\n\nexport type GoogleSignInTheme = \"filled_blue\" | \"filled_black\" | \"outline\";\nexport type GoogleSignInText = \"signin_with\" | \"signup_with\" | \"continue_with\" | \"signin\";\nexport type GoogleSignInShape = \"rectangular\" | \"pill\" | \"circle\" | \"square\";\nexport type GoogleSignInSize = \"large\" | \"medium\" | \"small\";\n\nexport interface GoogleSignInProps {\n /**\n * `GoogleLogin` component from `@react-oauth/google`. The caller imports\n * it and passes it through so the SDK doesn't take `@react-oauth/google`\n * as a peer dep.\n */\n component: (props: Record<string, unknown>) => ReactNode;\n /** Fired with the validated credential on success. */\n onSuccess: (credential: OAuthCredential) => void | Promise<void>;\n /** Fired with a normalised error on failure. */\n onError?: (error: OAuthError) => void;\n /** Optional locale override (e.g. `\"pt-BR\"`). Falls back to browser locale. */\n locale?: string;\n /** Visual theme — passed through to `<GoogleLogin>`. */\n theme?: GoogleSignInTheme;\n /** Button text variant. */\n text?: GoogleSignInText;\n /** Button shape. */\n shape?: GoogleSignInShape;\n /** Button size. */\n size?: GoogleSignInSize;\n /** Disable Google's \"One Tap\" auto-prompt. Default `false`. */\n disableOneTap?: boolean;\n /** Optional `width` override (px) — Google's button is fixed-width. */\n width?: number;\n /** Optional className applied to the wrapper. */\n className?: string;\n /** Optional inline style applied to the wrapper. */\n style?: CSSProperties;\n}\n\n/**\n * Thin wrapper over `@react-oauth/google`'s `<GoogleLogin>` that:\n *\n * 1. Normalises the success payload into [[OAuthCredential]] (`idToken`,\n * `provider: \"google\"`, `raw`).\n * 2. Normalises errors into [[OAuthError]].\n * 3. Lets you pass `GoogleLogin` via the `component` prop, so the SDK does\n * not declare `@react-oauth/google` as a peer dep — apps that don't use\n * Google never pay for it.\n *\n * @example\n * import { GoogleLogin, GoogleOAuthProvider } from \"@react-oauth/google\";\n * import { GoogleSignIn } from \"tempest-react-sdk\";\n *\n * <GoogleOAuthProvider clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID}>\n * <GoogleSignIn\n * component={GoogleLogin}\n * onSuccess={async ({ idToken }) => {\n * await api.post(\"/auth/google\", { body: { id_token: idToken } });\n * }}\n * onError={(err) => toast.error(err.message)}\n * />\n * </GoogleOAuthProvider>\n */\nexport function GoogleSignIn({\n component: Component,\n onSuccess,\n onError,\n locale,\n theme,\n text,\n shape,\n size,\n disableOneTap,\n width,\n className,\n style,\n}: GoogleSignInProps) {\n return (\n <div className={className} style={style}>\n {Component({\n onSuccess: (response: { credential?: string }) => {\n if (!response.credential) {\n onError?.({\n provider: \"google\",\n message: \"Google returned no credential\",\n raw: response,\n });\n return;\n }\n return onSuccess({\n idToken: response.credential,\n provider: \"google\",\n raw: response,\n });\n },\n onError: () => {\n onError?.({\n provider: \"google\",\n message: \"Google login failed\",\n });\n },\n locale,\n theme,\n text,\n shape,\n size,\n useOneTap: !disableOneTap,\n width,\n })}\n </div>\n );\n}\n"],"mappings":";;AAqEA,SAAgB,EAAa,EACzB,WAAW,GACX,cACA,YACA,WACA,UACA,SACA,UACA,SACA,kBACA,UACA,cACA,YACkB;CAClB,OACI,kBAAC,OAAD;EAAgB;EAAkB;EAC7B,UAAA,EAAU;GACP,YAAY,MAAsC;IAC9C,IAAI,CAAC,EAAS,YAAY;KACtB,IAAU;MACN,UAAU;MACV,SAAS;MACT,KAAK;KACT,CAAC;KACD;IACJ;IACA,OAAO,EAAU;KACb,SAAS,EAAS;KAClB,UAAU;KACV,KAAK;IACT,CAAC;GACL;GACA,eAAe;IACX,IAAU;KACN,UAAU;KACV,SAAS;IACb,CAAC;GACL;GACA;GACA;GACA;GACA;GACA;GACA,WAAW,CAAC;GACZ;EACJ,CAAC;CACA,CAAA;AAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-oauth-callback.cjs","names":[],"sources":["../../src/oauth/use-oauth-callback.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\n\nexport interface UseOAuthCallbackOptions<T> {\n /** Function that exchanges the provider response for an app session. Called once on mount. */\n exchange: () => Promise<T>;\n /** Fired with the result on success. Receives the value resolved by `exchange`. */\n onSuccess?: (result: T) => void | Promise<void>;\n /** Fired when `exchange` throws or rejects. */\n onError?: (error: unknown) => void;\n}\n\nexport interface UseOAuthCallbackResult<T> {\n /** `true` while the exchange promise is pending. */\n loading: boolean;\n /** Last resolved value, when `status === \"success\"`. */\n data: T | null;\n /** Last rejection reason, when `status === \"error\"`. */\n error: unknown;\n /** Aggregated state — `\"pending\"`, `\"success\"`, or `\"error\"`. */\n status: \"pending\" | \"success\" | \"error\";\n}\n\n/**\n * Run an OAuth-callback \"exchange\" exactly once on mount. Designed for\n * `/callback` routes that receive provider redirects and need to swap a\n * code/token for an app session via the backend.\n *\n * Survives React StrictMode double-mounting in dev — uses a ref guard to\n * ensure `exchange` runs once.\n *\n * @example\n * function OAuthCallback() {\n * const { loading, error } = useOAuthCallback({\n * exchange: async () => {\n * const code = new URLSearchParams(location.search).get(\"code\")!;\n * return api.post(\"/auth/google/exchange\", { body: { code } });\n * },\n * onSuccess: ({ token, user }) => {\n * useAuthStore.getState().setSession({ user, token });\n * navigate(\"/dashboard\", { replace: true });\n * },\n * onError: () => navigate(\"/login?error=oauth\", { replace: true }),\n * });\n *\n * if (loading) return <Spinner />;\n * if (error) return <ErrorState description=\"OAuth falhou\" />;\n * return null;\n * }\n */\nexport function useOAuthCallback<T>(\n options: UseOAuthCallbackOptions<T>,\n): UseOAuthCallbackResult<T> {\n const { exchange, onSuccess, onError } = options;\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<unknown>(null);\n const [status, setStatus] = useState<\"pending\" | \"success\" | \"error\">(\"pending\");\n const ranRef = useRef<boolean>(false);\n\n useEffect(() => {\n if (ranRef.current) return;\n ranRef.current = true;\n let cancelled = false;\n exchange()\n .then((result) => {\n if (cancelled) return;\n setData(result);\n setStatus(\"success\");\n return onSuccess?.(result);\n })\n .catch((err) => {\n if (cancelled) return;\n setError(err);\n setStatus(\"error\");\n onError?.(err);\n });\n return () => {\n cancelled = true;\n };\n }, [exchange, onSuccess, onError]);\n\n return { loading: status === \"pending\", data, error, status };\n}\n"],"mappings":"uBAiDA,SAAgB,EACZ,EACyB,CACzB,GAAM,CAAE,WAAU,YAAW,WAAY,EACnC,CAAC,EAAM,IAAA,
|
|
1
|
+
{"version":3,"file":"use-oauth-callback.cjs","names":[],"sources":["../../src/oauth/use-oauth-callback.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\n\nexport interface UseOAuthCallbackOptions<T> {\n /** Function that exchanges the provider response for an app session. Called once on mount. */\n exchange: () => Promise<T>;\n /** Fired with the result on success. Receives the value resolved by `exchange`. */\n onSuccess?: (result: T) => void | Promise<void>;\n /** Fired when `exchange` throws or rejects. */\n onError?: (error: unknown) => void;\n}\n\nexport interface UseOAuthCallbackResult<T> {\n /** `true` while the exchange promise is pending. */\n loading: boolean;\n /** Last resolved value, when `status === \"success\"`. */\n data: T | null;\n /** Last rejection reason, when `status === \"error\"`. */\n error: unknown;\n /** Aggregated state — `\"pending\"`, `\"success\"`, or `\"error\"`. */\n status: \"pending\" | \"success\" | \"error\";\n}\n\n/**\n * Run an OAuth-callback \"exchange\" exactly once on mount. Designed for\n * `/callback` routes that receive provider redirects and need to swap a\n * code/token for an app session via the backend.\n *\n * Survives React StrictMode double-mounting in dev — uses a ref guard to\n * ensure `exchange` runs once.\n *\n * @example\n * function OAuthCallback() {\n * const { loading, error } = useOAuthCallback({\n * exchange: async () => {\n * const code = new URLSearchParams(location.search).get(\"code\")!;\n * return api.post(\"/auth/google/exchange\", { body: { code } });\n * },\n * onSuccess: ({ token, user }) => {\n * useAuthStore.getState().setSession({ user, token });\n * navigate(\"/dashboard\", { replace: true });\n * },\n * onError: () => navigate(\"/login?error=oauth\", { replace: true }),\n * });\n *\n * if (loading) return <Spinner />;\n * if (error) return <ErrorState description=\"OAuth falhou\" />;\n * return null;\n * }\n */\nexport function useOAuthCallback<T>(\n options: UseOAuthCallbackOptions<T>,\n): UseOAuthCallbackResult<T> {\n const { exchange, onSuccess, onError } = options;\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<unknown>(null);\n const [status, setStatus] = useState<\"pending\" | \"success\" | \"error\">(\"pending\");\n const ranRef = useRef<boolean>(false);\n\n useEffect(() => {\n if (ranRef.current) return;\n ranRef.current = true;\n let cancelled = false;\n exchange()\n .then((result) => {\n if (cancelled) return;\n setData(result);\n setStatus(\"success\");\n return onSuccess?.(result);\n })\n .catch((err) => {\n if (cancelled) return;\n setError(err);\n setStatus(\"error\");\n onError?.(err);\n });\n return () => {\n cancelled = true;\n };\n }, [exchange, onSuccess, onError]);\n\n return { loading: status === \"pending\", data, error, status };\n}\n"],"mappings":"uBAiDA,SAAgB,EACZ,EACyB,CACzB,GAAM,CAAE,WAAU,YAAW,WAAY,EACnC,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAmB,IAAI,EACzC,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAkB,IAAI,EAC1C,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAA0C,SAAS,EACzE,GAAA,EAAS,EAAA,OAAA,CAAgB,EAAK,EAwBpC,OAtBA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,EAAO,QAAS,OACpB,EAAO,QAAU,GACjB,IAAI,EAAY,GAchB,OAbA,EAAS,CAAC,CACL,KAAM,GAAW,CACV,MAGJ,OAFA,EAAQ,CAAM,EACd,EAAU,SAAS,EACZ,IAAY,CAAM,CAC7B,CAAC,CAAC,CACD,MAAO,GAAQ,CACR,IACJ,EAAS,CAAG,EACZ,EAAU,OAAO,EACjB,IAAU,CAAG,EACjB,CAAC,MACQ,CACT,EAAY,EAChB,CACJ,EAAG,CAAC,EAAU,EAAW,CAAO,CAAC,EAE1B,CAAE,QAAS,IAAW,UAAW,OAAM,QAAO,QAAO,CAChE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-offline-sync.cjs","names":[],"sources":["../../src/offline/use-offline-sync.ts"],"sourcesContent":["import { useEffect, useRef, useSyncExternalStore } from \"react\";\nimport type {\n OfflineSync,\n OutboxOp,\n SyncRunSummary,\n SyncState,\n SyncTrigger,\n} from \"./create-offline-sync\";\n\n/**\n * Options for {@link useOfflineSync}. All auto-flush triggers are opt-in except\n * `flushOnOnline`, which defaults to `true` so the app catches up the moment\n * connectivity returns.\n */\nexport interface UseOfflineSyncOptions {\n /** Flush once when the component mounts (trigger `\"boot\"`). Default `false`. */\n flushOnMount?: boolean;\n /**\n * Flush when the browser fires the `online` event (trigger\n * `\"online-event\"`). Default `true`.\n */\n flushOnOnline?: boolean;\n /**\n * Flush on this interval in milliseconds (trigger `\"interval\"`). `0`\n * disables the timer. Default `0`.\n */\n intervalMs?: number;\n}\n\n/**\n * Reactive view of an {@link OfflineSync} engine, plus its imperative actions.\n * Extends {@link SyncState} so `phase`, `pending`, `lastSummary`, `lastError`\n * and `lastSyncedAt` are read directly off the result.\n *\n * @typeParam TPayload - Record snapshot carried by outbox entries.\n */\nexport interface UseOfflineSyncResult<TPayload> extends SyncState {\n /** `true` while a flush run is in progress (`phase === \"syncing\"`). */\n syncing: boolean;\n /** Queue a mutation. Mirrors {@link OfflineSync.enqueue}. */\n enqueue: (op: OutboxOp, recordId: string, payload?: TPayload) => Promise<string>;\n /** Trigger a flush run. Mirrors {@link OfflineSync.flush}. */\n flush: (trigger?: SyncTrigger) => Promise<SyncRunSummary>;\n}\n\n/**\n * Subscribe a React component to an {@link OfflineSync} engine.\n *\n * Re-renders whenever the engine's {@link SyncState} changes (enqueue, flush\n * transition, outbox clear) via `useSyncExternalStore`, and optionally wires\n * auto-flush on mount, on the `online` event and on an interval. The engine\n * itself is created once by the app and passed in — the hook never owns it, so\n * multiple components can observe the same queue.\n *\n * @typeParam TPayload - Record snapshot carried by outbox entries.\n * @param sync - The engine returned by `createOfflineSync`.\n * @param options - Auto-flush triggers.\n * @returns The reactive state merged with `enqueue`/`flush` actions.\n *\n * @example\n * const { pending, syncing, phase, flush } = useOfflineSync(notesSync, {\n * flushOnMount: true,\n * intervalMs: 30_000,\n * });\n * return <SyncStatusBadge pending={pending} syncing={syncing} phase={phase} />;\n */\nexport function useOfflineSync<TPayload>(\n sync: OfflineSync<TPayload>,\n options: UseOfflineSyncOptions = {},\n): UseOfflineSyncResult<TPayload> {\n const { flushOnMount = false, flushOnOnline = true, intervalMs = 0 } = options;\n\n const state = useSyncExternalStore(sync.subscribe, sync.getState, sync.getState);\n\n const flushRef = useRef(sync.flush);\n useEffect(() => {\n flushRef.current = sync.flush;\n }, [sync.flush]);\n\n useEffect(() => {\n if (flushOnMount) void flushRef.current(\"boot\");\n }, [flushOnMount]);\n\n useEffect(() => {\n if (!flushOnOnline || typeof window === \"undefined\") return;\n const handleOnline = (): void => {\n void flushRef.current(\"online-event\");\n };\n window.addEventListener(\"online\", handleOnline);\n return () => window.removeEventListener(\"online\", handleOnline);\n }, [flushOnOnline]);\n\n useEffect(() => {\n if (intervalMs <= 0 || typeof window === \"undefined\") return;\n const id = window.setInterval(() => {\n void flushRef.current(\"interval\");\n }, intervalMs);\n return () => window.clearInterval(id);\n }, [intervalMs]);\n\n return {\n ...state,\n syncing: state.phase === \"syncing\",\n enqueue: sync.enqueue,\n flush: sync.flush,\n };\n}\n\n/**\n * Presentation-ready tone for a sync status indicator. Collapses the engine's\n * {@link SyncState} into the single most relevant signal for a badge/pill.\n */\nexport type SyncTone = \"idle\" | \"syncing\" | \"pending\" | \"offline\" | \"error\";\n\n/**\n * Compact status snapshot for a status badge/pill. Prefer this over\n * {@link useOfflineSync} when a component only needs to *display* status and\n * does not trigger flushes itself.\n */\nexport interface SyncStatus {\n /** The dominant tone to render. */\n tone: SyncTone;\n /** Number of mutations still queued. */\n pending: number;\n /** `true` while a flush run is in progress. */\n syncing: boolean;\n /** `true` when the last run was skipped for being offline. */\n offline: boolean;\n /** Message of the most recent delivery failure, or `null`. */\n lastError: string | null;\n}\n\n/**\n * Derive the dominant {@link SyncTone} from an engine snapshot.\n *\n * Precedence: syncing → offline → error → pending → idle. `syncing` wins so an\n * in-flight run always reads as active even while entries remain queued.\n *\n * @param state - The engine {@link SyncState}.\n * @returns The tone to render.\n */\nfunction toneFromState(state: SyncState): SyncTone {\n if (state.phase === \"syncing\") return \"syncing\";\n if (state.phase === \"offline\") return \"offline\";\n if (state.phase === \"error\") return \"error\";\n if (state.pending > 0) return \"pending\";\n return \"idle\";\n}\n\n/**\n * Subscribe to an {@link OfflineSync} engine and return a display-only\n * {@link SyncStatus}. A thin read-only companion to {@link useOfflineSync},\n * intended to feed a `<SyncStatusBadge>` without exposing flush actions.\n *\n * @typeParam TPayload - Record snapshot carried by outbox entries.\n * @param sync - The engine returned by `createOfflineSync`.\n * @returns The compact status snapshot.\n */\nexport function useSyncStatus<TPayload>(sync: OfflineSync<TPayload>): SyncStatus {\n const state = useSyncExternalStore(sync.subscribe, sync.getState, sync.getState);\n return {\n tone: toneFromState(state),\n pending: state.pending,\n syncing: state.phase === \"syncing\",\n offline: state.phase === \"offline\",\n lastError: state.lastError,\n };\n}\n"],"mappings":"uBAkEA,SAAgB,EACZ,EACA,EAAiC,CAAC,EACJ,CAC9B,GAAM,CAAE,eAAe,GAAO,gBAAgB,GAAM,aAAa,GAAM,EAEjE,GAAA,
|
|
1
|
+
{"version":3,"file":"use-offline-sync.cjs","names":[],"sources":["../../src/offline/use-offline-sync.ts"],"sourcesContent":["import { useEffect, useRef, useSyncExternalStore } from \"react\";\nimport type {\n OfflineSync,\n OutboxOp,\n SyncRunSummary,\n SyncState,\n SyncTrigger,\n} from \"./create-offline-sync\";\n\n/**\n * Options for {@link useOfflineSync}. All auto-flush triggers are opt-in except\n * `flushOnOnline`, which defaults to `true` so the app catches up the moment\n * connectivity returns.\n */\nexport interface UseOfflineSyncOptions {\n /** Flush once when the component mounts (trigger `\"boot\"`). Default `false`. */\n flushOnMount?: boolean;\n /**\n * Flush when the browser fires the `online` event (trigger\n * `\"online-event\"`). Default `true`.\n */\n flushOnOnline?: boolean;\n /**\n * Flush on this interval in milliseconds (trigger `\"interval\"`). `0`\n * disables the timer. Default `0`.\n */\n intervalMs?: number;\n}\n\n/**\n * Reactive view of an {@link OfflineSync} engine, plus its imperative actions.\n * Extends {@link SyncState} so `phase`, `pending`, `lastSummary`, `lastError`\n * and `lastSyncedAt` are read directly off the result.\n *\n * @typeParam TPayload - Record snapshot carried by outbox entries.\n */\nexport interface UseOfflineSyncResult<TPayload> extends SyncState {\n /** `true` while a flush run is in progress (`phase === \"syncing\"`). */\n syncing: boolean;\n /** Queue a mutation. Mirrors {@link OfflineSync.enqueue}. */\n enqueue: (op: OutboxOp, recordId: string, payload?: TPayload) => Promise<string>;\n /** Trigger a flush run. Mirrors {@link OfflineSync.flush}. */\n flush: (trigger?: SyncTrigger) => Promise<SyncRunSummary>;\n}\n\n/**\n * Subscribe a React component to an {@link OfflineSync} engine.\n *\n * Re-renders whenever the engine's {@link SyncState} changes (enqueue, flush\n * transition, outbox clear) via `useSyncExternalStore`, and optionally wires\n * auto-flush on mount, on the `online` event and on an interval. The engine\n * itself is created once by the app and passed in — the hook never owns it, so\n * multiple components can observe the same queue.\n *\n * @typeParam TPayload - Record snapshot carried by outbox entries.\n * @param sync - The engine returned by `createOfflineSync`.\n * @param options - Auto-flush triggers.\n * @returns The reactive state merged with `enqueue`/`flush` actions.\n *\n * @example\n * const { pending, syncing, phase, flush } = useOfflineSync(notesSync, {\n * flushOnMount: true,\n * intervalMs: 30_000,\n * });\n * return <SyncStatusBadge pending={pending} syncing={syncing} phase={phase} />;\n */\nexport function useOfflineSync<TPayload>(\n sync: OfflineSync<TPayload>,\n options: UseOfflineSyncOptions = {},\n): UseOfflineSyncResult<TPayload> {\n const { flushOnMount = false, flushOnOnline = true, intervalMs = 0 } = options;\n\n const state = useSyncExternalStore(sync.subscribe, sync.getState, sync.getState);\n\n const flushRef = useRef(sync.flush);\n useEffect(() => {\n flushRef.current = sync.flush;\n }, [sync.flush]);\n\n useEffect(() => {\n if (flushOnMount) void flushRef.current(\"boot\");\n }, [flushOnMount]);\n\n useEffect(() => {\n if (!flushOnOnline || typeof window === \"undefined\") return;\n const handleOnline = (): void => {\n void flushRef.current(\"online-event\");\n };\n window.addEventListener(\"online\", handleOnline);\n return () => window.removeEventListener(\"online\", handleOnline);\n }, [flushOnOnline]);\n\n useEffect(() => {\n if (intervalMs <= 0 || typeof window === \"undefined\") return;\n const id = window.setInterval(() => {\n void flushRef.current(\"interval\");\n }, intervalMs);\n return () => window.clearInterval(id);\n }, [intervalMs]);\n\n return {\n ...state,\n syncing: state.phase === \"syncing\",\n enqueue: sync.enqueue,\n flush: sync.flush,\n };\n}\n\n/**\n * Presentation-ready tone for a sync status indicator. Collapses the engine's\n * {@link SyncState} into the single most relevant signal for a badge/pill.\n */\nexport type SyncTone = \"idle\" | \"syncing\" | \"pending\" | \"offline\" | \"error\";\n\n/**\n * Compact status snapshot for a status badge/pill. Prefer this over\n * {@link useOfflineSync} when a component only needs to *display* status and\n * does not trigger flushes itself.\n */\nexport interface SyncStatus {\n /** The dominant tone to render. */\n tone: SyncTone;\n /** Number of mutations still queued. */\n pending: number;\n /** `true` while a flush run is in progress. */\n syncing: boolean;\n /** `true` when the last run was skipped for being offline. */\n offline: boolean;\n /** Message of the most recent delivery failure, or `null`. */\n lastError: string | null;\n}\n\n/**\n * Derive the dominant {@link SyncTone} from an engine snapshot.\n *\n * Precedence: syncing → offline → error → pending → idle. `syncing` wins so an\n * in-flight run always reads as active even while entries remain queued.\n *\n * @param state - The engine {@link SyncState}.\n * @returns The tone to render.\n */\nfunction toneFromState(state: SyncState): SyncTone {\n if (state.phase === \"syncing\") return \"syncing\";\n if (state.phase === \"offline\") return \"offline\";\n if (state.phase === \"error\") return \"error\";\n if (state.pending > 0) return \"pending\";\n return \"idle\";\n}\n\n/**\n * Subscribe to an {@link OfflineSync} engine and return a display-only\n * {@link SyncStatus}. A thin read-only companion to {@link useOfflineSync},\n * intended to feed a `<SyncStatusBadge>` without exposing flush actions.\n *\n * @typeParam TPayload - Record snapshot carried by outbox entries.\n * @param sync - The engine returned by `createOfflineSync`.\n * @returns The compact status snapshot.\n */\nexport function useSyncStatus<TPayload>(sync: OfflineSync<TPayload>): SyncStatus {\n const state = useSyncExternalStore(sync.subscribe, sync.getState, sync.getState);\n return {\n tone: toneFromState(state),\n pending: state.pending,\n syncing: state.phase === \"syncing\",\n offline: state.phase === \"offline\",\n lastError: state.lastError,\n };\n}\n"],"mappings":"uBAkEA,SAAgB,EACZ,EACA,EAAiC,CAAC,EACJ,CAC9B,GAAM,CAAE,eAAe,GAAO,gBAAgB,GAAM,aAAa,GAAM,EAEjE,GAAA,EAAQ,EAAA,qBAAA,CAAqB,EAAK,UAAW,EAAK,SAAU,EAAK,QAAQ,EAEzE,GAAA,EAAW,EAAA,OAAA,CAAO,EAAK,KAAK,EA0BlC,OAzBA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAS,QAAU,EAAK,KAC5B,EAAG,CAAC,EAAK,KAAK,CAAC,GAEf,EAAA,EAAA,UAAA,KAAgB,CACR,GAAc,EAAc,QAAQ,MAAM,CAClD,EAAG,CAAC,CAAY,CAAC,GAEjB,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,GAAiB,OAAO,OAAW,IAAa,OACrD,IAAM,MAA2B,CAC7B,EAAc,QAAQ,cAAc,CACxC,EAEA,OADA,OAAO,iBAAiB,SAAU,CAAY,MACjC,OAAO,oBAAoB,SAAU,CAAY,CAClE,EAAG,CAAC,CAAa,CAAC,GAElB,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,GAAc,GAAK,OAAO,OAAW,IAAa,OACtD,IAAM,EAAK,OAAO,gBAAkB,CAChC,EAAc,QAAQ,UAAU,CACpC,EAAG,CAAU,EACb,UAAa,OAAO,cAAc,CAAE,CACxC,EAAG,CAAC,CAAU,CAAC,EAER,CACH,GAAG,EACH,QAAS,EAAM,QAAU,UACzB,QAAS,EAAK,QACd,MAAO,EAAK,KAChB,CACJ,CAmCA,SAAS,EAAc,EAA4B,CAK/C,OAJI,EAAM,QAAU,UAAkB,UAClC,EAAM,QAAU,UAAkB,UAClC,EAAM,QAAU,QAAgB,QAChC,EAAM,QAAU,EAAU,UACvB,MACX,CAWA,SAAgB,EAAwB,EAAyC,CAC7E,IAAM,GAAA,EAAQ,EAAA,qBAAA,CAAqB,EAAK,UAAW,EAAK,SAAU,EAAK,QAAQ,EAC/E,MAAO,CACH,KAAM,EAAc,CAAK,EACzB,QAAS,EAAM,QACf,QAAS,EAAM,QAAU,UACzB,QAAS,EAAM,QAAU,UACzB,UAAW,EAAM,SACrB,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-push-subscription.cjs","names":[],"sources":["../../src/push/use-push-subscription.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the ref is read inside deferred callbacks, not during render */\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { WebPushClient, type WebPushClientConfig } from \"./web-push-client\";\nimport { isPushSupported } from \"./utils\";\n\nexport interface UsePushSubscriptionResult {\n /** True when the browser exposes the Push + Notification APIs. */\n supported: boolean;\n /** Current `Notification.permission` value, or `\"unsupported\"`. */\n permission: NotificationPermission | \"unsupported\";\n /** True when a subscription is active on this device. */\n subscribed: boolean;\n /** True while a subscribe/unsubscribe call is in-flight. */\n loading: boolean;\n /** Last error thrown by subscribe/unsubscribe, if any. */\n error: Error | null;\n subscribe: () => Promise<void>;\n unsubscribe: () => Promise<void>;\n /** Re-read the current subscription/permission state from the browser. */\n refresh: () => Promise<void>;\n}\n\n/**\n * React hook around {@link WebPushClient}. Tracks subscription, permission and\n * loading state; exposes imperative `subscribe`/`unsubscribe` actions.\n *\n * The hook does NOT register the service worker — the host app must do that\n * (e.g. via `vite-plugin-pwa` or a manual `navigator.serviceWorker.register`).\n *\n * @example\n * const push = usePushSubscription({\n * vapidPublicKey: import.meta.env.VITE_VAPID_PUBLIC_KEY,\n * onSubscribe: (sub) => api.post(\"/webpush/subscribe\", { body: sub }),\n * onUnsubscribe: () => api.delete(\"/webpush/my\"),\n * });\n */\nexport function usePushSubscription(config: WebPushClientConfig): UsePushSubscriptionResult {\n const configRef = useRef(config);\n useEffect(() => {\n configRef.current = config;\n }, [config]);\n\n const client = useMemo(\n () =>\n new WebPushClient({\n vapidPublicKey: config.vapidPublicKey,\n onSubscribe: (sub) => configRef.current.onSubscribe(sub),\n onUnsubscribe: (sub) => configRef.current.onUnsubscribe?.(sub),\n getRegistration: config.getRegistration,\n }),\n [config.vapidPublicKey, config.getRegistration],\n );\n\n const [supported] = useState<boolean>(() => isPushSupported());\n const [permission, setPermission] = useState<NotificationPermission | \"unsupported\">(() =>\n client.permission(),\n );\n const [subscribed, setSubscribed] = useState<boolean>(false);\n const [loading, setLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n\n const refresh = useCallback(async () => {\n if (!supported) return;\n setPermission(client.permission());\n try {\n const active = await client.isSubscribed();\n setSubscribed(active);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n }, [client, supported]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const subscribe = useCallback(async () => {\n if (!supported) return;\n setLoading(true);\n setError(null);\n try {\n await client.subscribe();\n setSubscribed(true);\n setPermission(client.permission());\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n throw err;\n } finally {\n setLoading(false);\n }\n }, [client, supported]);\n\n const unsubscribe = useCallback(async () => {\n if (!supported) return;\n setLoading(true);\n setError(null);\n try {\n await client.unsubscribe();\n setSubscribed(false);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n throw err;\n } finally {\n setLoading(false);\n }\n }, [client, supported]);\n\n return {\n supported,\n permission,\n subscribed,\n loading,\n error,\n subscribe,\n unsubscribe,\n refresh,\n };\n}\n"],"mappings":"yFAoCA,SAAgB,EAAoB,EAAwD,CACxF,IAAM,GAAA,
|
|
1
|
+
{"version":3,"file":"use-push-subscription.cjs","names":[],"sources":["../../src/push/use-push-subscription.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the ref is read inside deferred callbacks, not during render */\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { WebPushClient, type WebPushClientConfig } from \"./web-push-client\";\nimport { isPushSupported } from \"./utils\";\n\nexport interface UsePushSubscriptionResult {\n /** True when the browser exposes the Push + Notification APIs. */\n supported: boolean;\n /** Current `Notification.permission` value, or `\"unsupported\"`. */\n permission: NotificationPermission | \"unsupported\";\n /** True when a subscription is active on this device. */\n subscribed: boolean;\n /** True while a subscribe/unsubscribe call is in-flight. */\n loading: boolean;\n /** Last error thrown by subscribe/unsubscribe, if any. */\n error: Error | null;\n subscribe: () => Promise<void>;\n unsubscribe: () => Promise<void>;\n /** Re-read the current subscription/permission state from the browser. */\n refresh: () => Promise<void>;\n}\n\n/**\n * React hook around {@link WebPushClient}. Tracks subscription, permission and\n * loading state; exposes imperative `subscribe`/`unsubscribe` actions.\n *\n * The hook does NOT register the service worker — the host app must do that\n * (e.g. via `vite-plugin-pwa` or a manual `navigator.serviceWorker.register`).\n *\n * @example\n * const push = usePushSubscription({\n * vapidPublicKey: import.meta.env.VITE_VAPID_PUBLIC_KEY,\n * onSubscribe: (sub) => api.post(\"/webpush/subscribe\", { body: sub }),\n * onUnsubscribe: () => api.delete(\"/webpush/my\"),\n * });\n */\nexport function usePushSubscription(config: WebPushClientConfig): UsePushSubscriptionResult {\n const configRef = useRef(config);\n useEffect(() => {\n configRef.current = config;\n }, [config]);\n\n const client = useMemo(\n () =>\n new WebPushClient({\n vapidPublicKey: config.vapidPublicKey,\n onSubscribe: (sub) => configRef.current.onSubscribe(sub),\n onUnsubscribe: (sub) => configRef.current.onUnsubscribe?.(sub),\n getRegistration: config.getRegistration,\n }),\n [config.vapidPublicKey, config.getRegistration],\n );\n\n const [supported] = useState<boolean>(() => isPushSupported());\n const [permission, setPermission] = useState<NotificationPermission | \"unsupported\">(() =>\n client.permission(),\n );\n const [subscribed, setSubscribed] = useState<boolean>(false);\n const [loading, setLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n\n const refresh = useCallback(async () => {\n if (!supported) return;\n setPermission(client.permission());\n try {\n const active = await client.isSubscribed();\n setSubscribed(active);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n }, [client, supported]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const subscribe = useCallback(async () => {\n if (!supported) return;\n setLoading(true);\n setError(null);\n try {\n await client.subscribe();\n setSubscribed(true);\n setPermission(client.permission());\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n throw err;\n } finally {\n setLoading(false);\n }\n }, [client, supported]);\n\n const unsubscribe = useCallback(async () => {\n if (!supported) return;\n setLoading(true);\n setError(null);\n try {\n await client.unsubscribe();\n setSubscribed(false);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n throw err;\n } finally {\n setLoading(false);\n }\n }, [client, supported]);\n\n return {\n supported,\n permission,\n subscribed,\n loading,\n error,\n subscribe,\n unsubscribe,\n refresh,\n };\n}\n"],"mappings":"yFAoCA,SAAgB,EAAoB,EAAwD,CACxF,IAAM,GAAA,EAAY,EAAA,OAAA,CAAO,CAAM,GAC/B,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAU,QAAU,CACxB,EAAG,CAAC,CAAM,CAAC,EAEX,IAAM,GAAA,EAAS,EAAA,QAAA,KAEP,IAAI,EAAA,cAAc,CACd,eAAgB,EAAO,eACvB,YAAc,GAAQ,EAAU,QAAQ,YAAY,CAAG,EACvD,cAAgB,GAAQ,EAAU,QAAQ,gBAAgB,CAAG,EAC7D,gBAAiB,EAAO,eAC5B,CAAC,EACL,CAAC,EAAO,eAAgB,EAAO,eAAe,CAClD,EAEM,CAAC,IAAA,EAAa,EAAA,SAAA,KAAwB,EAAA,gBAAgB,CAAC,EACvD,CAAC,EAAY,IAAA,EAAiB,EAAA,SAAA,KAChC,EAAO,WAAW,CACtB,EACM,CAAC,EAAY,IAAA,EAAiB,EAAA,SAAA,CAAkB,EAAK,EACrD,CAAC,EAAS,IAAA,EAAc,EAAA,SAAA,CAAkB,EAAK,EAC/C,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAuB,IAAI,EAE/C,GAAA,EAAU,EAAA,YAAA,CAAY,SAAY,CAC/B,KACL,GAAc,EAAO,WAAW,CAAC,EACjC,GAAI,CACA,IAAM,EAAS,MAAM,EAAO,aAAa,EACzC,EAAc,CAAM,CACxB,OAAS,EAAK,CACV,EAAS,aAAe,MAAQ,EAAU,MAAM,OAAO,CAAG,CAAC,CAAC,CAChE,CANiC,CAOrC,EAAG,CAAC,EAAQ,CAAS,CAAC,EAqCtB,OAnCA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAa,CACjB,EAAG,CAAC,CAAO,CAAC,EAiCL,CACH,YACA,aACA,aACA,UACA,QACA,WAAA,EArCc,EAAA,YAAA,CAAY,SAAY,CACjC,KAEL,CADA,EAAW,EAAI,EACf,EAAS,IAAI,EACb,GAAI,CACA,MAAM,EAAO,UAAU,EACvB,EAAc,EAAI,EAClB,EAAc,EAAO,WAAW,CAAC,CACrC,OAAS,EAAK,CAEV,MADA,EAAS,aAAe,MAAQ,EAAU,MAAM,OAAO,CAAG,CAAC,CAAC,EACtD,CACV,QAAU,CACN,EAAW,EAAK,CACpB,CAVa,CAWjB,EAAG,CAAC,EAAQ,CAAS,CAuBjB,EACA,aAAA,EAtBgB,EAAA,YAAA,CAAY,SAAY,CACnC,KAEL,CADA,EAAW,EAAI,EACf,EAAS,IAAI,EACb,GAAI,CACA,MAAM,EAAO,YAAY,EACzB,EAAc,EAAK,CACvB,OAAS,EAAK,CAEV,MADA,EAAS,aAAe,MAAQ,EAAU,MAAM,OAAO,CAAG,CAAC,CAAC,EACtD,CACV,QAAU,CACN,EAAW,EAAK,CACpB,CATa,CAUjB,EAAG,CAAC,EAAQ,CAAS,CASjB,EACA,SACJ,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QueryProvider.cjs","names":[],"sources":["../../src/query/QueryProvider.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport type { DefaultOptions } from \"@tanstack/react-query\";\nimport { STALE_TIME, CACHE_TIME } from \"./constants\";\n\nexport interface QueryProviderProps {\n children: ReactNode;\n /** Pass an existing client to share it across roots. */\n client?: QueryClient;\n /** Overrides for the default options applied when no `client` is provided. */\n defaultOptions?: DefaultOptions;\n}\n\n/**\n * Wrapper around `QueryClientProvider` that bootstraps a `QueryClient` with\n * sane SDK defaults (5-minute stale time, 30-minute gc time, 1 retry).\n */\nexport function QueryProvider({ children, client, defaultOptions }: QueryProviderProps) {\n const [internalClient] = useState<QueryClient>(\n () =>\n client ??\n new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: STALE_TIME.DEFAULT,\n gcTime: CACHE_TIME.DEFAULT,\n retry: 1,\n refetchOnWindowFocus: false,\n ...(defaultOptions?.queries ?? {}),\n },\n mutations: {\n retry: 0,\n ...(defaultOptions?.mutations ?? {}),\n },\n },\n }),\n );\n\n return <QueryClientProvider client={internalClient}>{children}</QueryClientProvider>;\n}\n"],"mappings":"4HAkBA,SAAgB,EAAc,CAAE,WAAU,SAAQ,kBAAsC,CACpF,GAAM,CAAC,IAAA,
|
|
1
|
+
{"version":3,"file":"QueryProvider.cjs","names":[],"sources":["../../src/query/QueryProvider.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport type { DefaultOptions } from \"@tanstack/react-query\";\nimport { STALE_TIME, CACHE_TIME } from \"./constants\";\n\nexport interface QueryProviderProps {\n children: ReactNode;\n /** Pass an existing client to share it across roots. */\n client?: QueryClient;\n /** Overrides for the default options applied when no `client` is provided. */\n defaultOptions?: DefaultOptions;\n}\n\n/**\n * Wrapper around `QueryClientProvider` that bootstraps a `QueryClient` with\n * sane SDK defaults (5-minute stale time, 30-minute gc time, 1 retry).\n */\nexport function QueryProvider({ children, client, defaultOptions }: QueryProviderProps) {\n const [internalClient] = useState<QueryClient>(\n () =>\n client ??\n new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: STALE_TIME.DEFAULT,\n gcTime: CACHE_TIME.DEFAULT,\n retry: 1,\n refetchOnWindowFocus: false,\n ...(defaultOptions?.queries ?? {}),\n },\n mutations: {\n retry: 0,\n ...(defaultOptions?.mutations ?? {}),\n },\n },\n }),\n );\n\n return <QueryClientProvider client={internalClient}>{children}</QueryClientProvider>;\n}\n"],"mappings":"4HAkBA,SAAgB,EAAc,CAAE,WAAU,SAAQ,kBAAsC,CACpF,GAAM,CAAC,IAAA,EAAkB,EAAA,SAAA,KAEjB,GACA,IAAI,EAAA,YAAY,CACZ,eAAgB,CACZ,QAAS,CACL,UAAW,EAAA,WAAW,QACtB,OAAQ,EAAA,WAAW,QACnB,MAAO,EACP,qBAAsB,GACtB,GAAI,GAAgB,SAAW,CAAC,CACpC,EACA,UAAW,CACP,MAAO,EACP,GAAI,GAAgB,WAAa,CAAC,CACtC,CACJ,CACJ,CAAC,CACT,EAEA,OAAO,EAAA,EAAA,IAAA,CAAC,EAAA,oBAAD,CAAqB,OAAQ,EAAiB,UAA8B,CAAA,CACvF"}
|
package/dist/query/constants.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e={SHORT:
|
|
1
|
+
var e={SHORT:3e4,DEFAULT:3e5,LONG:18e5,INFINITE:1/0},t={SHORT:3e5,DEFAULT:18e5,LONG:36e5},n={REALTIME:5e3,FAST:3e4,DEFAULT:6e4,SLOW:3e5};exports.CACHE_TIME=t,exports.REFETCH_TIME=n,exports.STALE_TIME=e;
|
|
2
2
|
//# sourceMappingURL=constants.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.cjs","names":[],"sources":["../../src/query/constants.ts"],"sourcesContent":["/** Recommended `staleTime` presets (milliseconds). */\nexport const STALE_TIME = {\n SHORT: 30 * 1000,\n DEFAULT: 5 * 60 * 1000,\n LONG: 30 * 60 * 1000,\n INFINITE: Infinity,\n} as const;\n\n/** Recommended `gcTime` presets (milliseconds). */\nexport const CACHE_TIME = {\n SHORT: 5 * 60 * 1000,\n DEFAULT: 30 * 60 * 1000,\n LONG: 60 * 60 * 1000,\n} as const;\n\n/** Recommended refetch intervals (milliseconds). */\nexport const REFETCH_TIME = {\n REALTIME: 5 * 1000,\n FAST: 30 * 1000,\n DEFAULT: 60 * 1000,\n SLOW: 5 * 60 * 1000,\n} as const;\n"],"mappings":"AACA,IAAa,EAAa,CACtB,MAAO,
|
|
1
|
+
{"version":3,"file":"constants.cjs","names":[],"sources":["../../src/query/constants.ts"],"sourcesContent":["/** Recommended `staleTime` presets (milliseconds). */\nexport const STALE_TIME = {\n SHORT: 30 * 1000,\n DEFAULT: 5 * 60 * 1000,\n LONG: 30 * 60 * 1000,\n INFINITE: Infinity,\n} as const;\n\n/** Recommended `gcTime` presets (milliseconds). */\nexport const CACHE_TIME = {\n SHORT: 5 * 60 * 1000,\n DEFAULT: 30 * 60 * 1000,\n LONG: 60 * 60 * 1000,\n} as const;\n\n/** Recommended refetch intervals (milliseconds). */\nexport const REFETCH_TIME = {\n REALTIME: 5 * 1000,\n FAST: 30 * 1000,\n DEFAULT: 60 * 1000,\n SLOW: 5 * 60 * 1000,\n} as const;\n"],"mappings":"AACA,IAAa,EAAa,CACtB,MAAO,IACP,QAAS,IACT,KAAM,KACN,SAAU,GACd,EAGa,EAAa,CACtB,MAAO,IACP,QAAS,KACT,KAAM,IACV,EAGa,EAAe,CACxB,SAAU,IACV,KAAM,IACN,QAAS,IACT,KAAM,GACV"}
|
package/dist/query/constants.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
//#region src/query/constants.ts
|
|
2
2
|
var e = {
|
|
3
|
-
SHORT:
|
|
4
|
-
DEFAULT:
|
|
5
|
-
LONG:
|
|
3
|
+
SHORT: 3e4,
|
|
4
|
+
DEFAULT: 3e5,
|
|
5
|
+
LONG: 18e5,
|
|
6
6
|
INFINITE: Infinity
|
|
7
7
|
}, t = {
|
|
8
|
-
SHORT:
|
|
9
|
-
DEFAULT:
|
|
10
|
-
LONG:
|
|
8
|
+
SHORT: 3e5,
|
|
9
|
+
DEFAULT: 18e5,
|
|
10
|
+
LONG: 36e5
|
|
11
11
|
}, n = {
|
|
12
|
-
REALTIME:
|
|
13
|
-
FAST:
|
|
14
|
-
DEFAULT:
|
|
15
|
-
SLOW:
|
|
12
|
+
REALTIME: 5e3,
|
|
13
|
+
FAST: 3e4,
|
|
14
|
+
DEFAULT: 6e4,
|
|
15
|
+
SLOW: 3e5
|
|
16
16
|
};
|
|
17
17
|
//#endregion
|
|
18
18
|
export { t as CACHE_TIME, n as REFETCH_TIME, e as STALE_TIME };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","names":[],"sources":["../../src/query/constants.ts"],"sourcesContent":["/** Recommended `staleTime` presets (milliseconds). */\nexport const STALE_TIME = {\n SHORT: 30 * 1000,\n DEFAULT: 5 * 60 * 1000,\n LONG: 30 * 60 * 1000,\n INFINITE: Infinity,\n} as const;\n\n/** Recommended `gcTime` presets (milliseconds). */\nexport const CACHE_TIME = {\n SHORT: 5 * 60 * 1000,\n DEFAULT: 30 * 60 * 1000,\n LONG: 60 * 60 * 1000,\n} as const;\n\n/** Recommended refetch intervals (milliseconds). */\nexport const REFETCH_TIME = {\n REALTIME: 5 * 1000,\n FAST: 30 * 1000,\n DEFAULT: 60 * 1000,\n SLOW: 5 * 60 * 1000,\n} as const;\n"],"mappings":";AACA,IAAa,IAAa;CACtB,OAAO
|
|
1
|
+
{"version":3,"file":"constants.js","names":[],"sources":["../../src/query/constants.ts"],"sourcesContent":["/** Recommended `staleTime` presets (milliseconds). */\nexport const STALE_TIME = {\n SHORT: 30 * 1000,\n DEFAULT: 5 * 60 * 1000,\n LONG: 30 * 60 * 1000,\n INFINITE: Infinity,\n} as const;\n\n/** Recommended `gcTime` presets (milliseconds). */\nexport const CACHE_TIME = {\n SHORT: 5 * 60 * 1000,\n DEFAULT: 30 * 60 * 1000,\n LONG: 60 * 60 * 1000,\n} as const;\n\n/** Recommended refetch intervals (milliseconds). */\nexport const REFETCH_TIME = {\n REALTIME: 5 * 1000,\n FAST: 30 * 1000,\n DEFAULT: 60 * 1000,\n SLOW: 5 * 60 * 1000,\n} as const;\n"],"mappings":";AACA,IAAa,IAAa;CACtB,OAAO;CACP,SAAS;CACT,MAAM;CACN,UAAU;AACd,GAGa,IAAa;CACtB,OAAO;CACP,SAAS;CACT,MAAM;AACV,GAGa,IAAe;CACxB,UAAU;CACV,MAAM;CACN,SAAS;CACT,MAAM;AACV"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function e(e,t){let n={all:[e]};for(let[r,i]of Object.entries(t))typeof i==`function`?
|
|
1
|
+
function e(e,t){let n={all:[e]};for(let[r,i]of Object.entries(t))n[r]=typeof i==`function`?(...t)=>[e,...i(...t)]:[e,...i];return n}exports.createQueryKeys=e;
|
|
2
2
|
//# sourceMappingURL=create-query-keys.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-query-keys.cjs","names":[],"sources":["../../src/query/create-query-keys.ts"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype KeyBuilder = (...args: any[]) => readonly unknown[];\n\n/**\n * Build a typed query-key factory for a domain. Each entry can be a static\n * tuple or a function returning a tuple. The returned object is `as const`\n * so TanStack Query infers literal keys.\n *\n * @example\n * const userKeys = createQueryKeys(\"user\", {\n * me: () => [\"me\"] as const,\n * byId: (id: string) => [id] as const,\n * list: (filters: { page: number }) => [\"list\", filters] as const,\n * });\n * // userKeys.byId(\"42\") === [\"user\", \"42\"]\n */\nexport function createQueryKeys<\n TKey extends string,\n TEntries extends Record<string, KeyBuilder | readonly unknown[]>,\n>(scope: TKey, entries: TEntries) {\n const output = { all: [scope] as const } as { all: readonly [TKey] } & {\n [K in keyof TEntries]: TEntries[K] extends KeyBuilder\n ? (...args: Parameters<TEntries[K]>) => readonly [TKey, ...ReturnType<TEntries[K]>]\n : TEntries[K] extends readonly unknown[]\n ? readonly [TKey, ...TEntries[K]]\n : never;\n };\n\n for (const [name, entry] of Object.entries(entries) as [\n keyof TEntries,\n KeyBuilder | readonly unknown[],\n ][]) {\n if (typeof entry === \"function\") {\n (output as Record<string, unknown>)[name as string] = (...args: unknown[]) =>\n [scope, ...entry(...args)] as const;\n } else {\n (output as Record<string, unknown>)[name as string] = [scope, ...entry] as const;\n }\n }\n\n return output;\n}\n"],"mappings":"AAgBA,SAAgB,EAGd,EAAa,EAAmB,CAC9B,IAAM,EAAS,CAAE,IAAK,CAAC,CAAK,CAAW,EAQvC,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,CAAO,
|
|
1
|
+
{"version":3,"file":"create-query-keys.cjs","names":[],"sources":["../../src/query/create-query-keys.ts"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype KeyBuilder = (...args: any[]) => readonly unknown[];\n\n/**\n * Build a typed query-key factory for a domain. Each entry can be a static\n * tuple or a function returning a tuple. The returned object is `as const`\n * so TanStack Query infers literal keys.\n *\n * @example\n * const userKeys = createQueryKeys(\"user\", {\n * me: () => [\"me\"] as const,\n * byId: (id: string) => [id] as const,\n * list: (filters: { page: number }) => [\"list\", filters] as const,\n * });\n * // userKeys.byId(\"42\") === [\"user\", \"42\"]\n */\nexport function createQueryKeys<\n TKey extends string,\n TEntries extends Record<string, KeyBuilder | readonly unknown[]>,\n>(scope: TKey, entries: TEntries) {\n const output = { all: [scope] as const } as { all: readonly [TKey] } & {\n [K in keyof TEntries]: TEntries[K] extends KeyBuilder\n ? (...args: Parameters<TEntries[K]>) => readonly [TKey, ...ReturnType<TEntries[K]>]\n : TEntries[K] extends readonly unknown[]\n ? readonly [TKey, ...TEntries[K]]\n : never;\n };\n\n for (const [name, entry] of Object.entries(entries) as [\n keyof TEntries,\n KeyBuilder | readonly unknown[],\n ][]) {\n if (typeof entry === \"function\") {\n (output as Record<string, unknown>)[name as string] = (...args: unknown[]) =>\n [scope, ...entry(...args)] as const;\n } else {\n (output as Record<string, unknown>)[name as string] = [scope, ...entry] as const;\n }\n }\n\n return output;\n}\n"],"mappings":"AAgBA,SAAgB,EAGd,EAAa,EAAmB,CAC9B,IAAM,EAAS,CAAE,IAAK,CAAC,CAAK,CAAW,EAQvC,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,CAAO,EAI9C,AAII,EAAoC,GAJpC,OAAO,GAAU,YACsC,GAAG,IACtD,CAAC,EAAO,GAAG,EAAM,GAAG,CAAI,CAAC,EAEyB,CAAC,EAAO,GAAG,CAAK,EAI9E,OAAO,CACX"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//#region src/query/create-query-keys.ts
|
|
2
2
|
function e(e, t) {
|
|
3
3
|
let n = { all: [e] };
|
|
4
|
-
for (let [r, i] of Object.entries(t)) typeof i == "function" ?
|
|
4
|
+
for (let [r, i] of Object.entries(t)) n[r] = typeof i == "function" ? (...t) => [e, ...i(...t)] : [e, ...i];
|
|
5
5
|
return n;
|
|
6
6
|
}
|
|
7
7
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-query-keys.js","names":[],"sources":["../../src/query/create-query-keys.ts"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype KeyBuilder = (...args: any[]) => readonly unknown[];\n\n/**\n * Build a typed query-key factory for a domain. Each entry can be a static\n * tuple or a function returning a tuple. The returned object is `as const`\n * so TanStack Query infers literal keys.\n *\n * @example\n * const userKeys = createQueryKeys(\"user\", {\n * me: () => [\"me\"] as const,\n * byId: (id: string) => [id] as const,\n * list: (filters: { page: number }) => [\"list\", filters] as const,\n * });\n * // userKeys.byId(\"42\") === [\"user\", \"42\"]\n */\nexport function createQueryKeys<\n TKey extends string,\n TEntries extends Record<string, KeyBuilder | readonly unknown[]>,\n>(scope: TKey, entries: TEntries) {\n const output = { all: [scope] as const } as { all: readonly [TKey] } & {\n [K in keyof TEntries]: TEntries[K] extends KeyBuilder\n ? (...args: Parameters<TEntries[K]>) => readonly [TKey, ...ReturnType<TEntries[K]>]\n : TEntries[K] extends readonly unknown[]\n ? readonly [TKey, ...TEntries[K]]\n : never;\n };\n\n for (const [name, entry] of Object.entries(entries) as [\n keyof TEntries,\n KeyBuilder | readonly unknown[],\n ][]) {\n if (typeof entry === \"function\") {\n (output as Record<string, unknown>)[name as string] = (...args: unknown[]) =>\n [scope, ...entry(...args)] as const;\n } else {\n (output as Record<string, unknown>)[name as string] = [scope, ...entry] as const;\n }\n }\n\n return output;\n}\n"],"mappings":";AAgBA,SAAgB,EAGd,GAAa,GAAmB;CAC9B,IAAM,IAAS,EAAE,KAAK,CAAC,CAAK,EAAW;CAQvC,KAAK,IAAM,CAAC,GAAM,MAAU,OAAO,QAAQ,CAAO,GAI9C,
|
|
1
|
+
{"version":3,"file":"create-query-keys.js","names":[],"sources":["../../src/query/create-query-keys.ts"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype KeyBuilder = (...args: any[]) => readonly unknown[];\n\n/**\n * Build a typed query-key factory for a domain. Each entry can be a static\n * tuple or a function returning a tuple. The returned object is `as const`\n * so TanStack Query infers literal keys.\n *\n * @example\n * const userKeys = createQueryKeys(\"user\", {\n * me: () => [\"me\"] as const,\n * byId: (id: string) => [id] as const,\n * list: (filters: { page: number }) => [\"list\", filters] as const,\n * });\n * // userKeys.byId(\"42\") === [\"user\", \"42\"]\n */\nexport function createQueryKeys<\n TKey extends string,\n TEntries extends Record<string, KeyBuilder | readonly unknown[]>,\n>(scope: TKey, entries: TEntries) {\n const output = { all: [scope] as const } as { all: readonly [TKey] } & {\n [K in keyof TEntries]: TEntries[K] extends KeyBuilder\n ? (...args: Parameters<TEntries[K]>) => readonly [TKey, ...ReturnType<TEntries[K]>]\n : TEntries[K] extends readonly unknown[]\n ? readonly [TKey, ...TEntries[K]]\n : never;\n };\n\n for (const [name, entry] of Object.entries(entries) as [\n keyof TEntries,\n KeyBuilder | readonly unknown[],\n ][]) {\n if (typeof entry === \"function\") {\n (output as Record<string, unknown>)[name as string] = (...args: unknown[]) =>\n [scope, ...entry(...args)] as const;\n } else {\n (output as Record<string, unknown>)[name as string] = [scope, ...entry] as const;\n }\n }\n\n return output;\n}\n"],"mappings":";AAgBA,SAAgB,EAGd,GAAa,GAAmB;CAC9B,IAAM,IAAS,EAAE,KAAK,CAAC,CAAK,EAAW;CAQvC,KAAK,IAAM,CAAC,GAAM,MAAU,OAAO,QAAQ,CAAO,GAI9C,AAII,EAAoC,KAJpC,OAAO,KAAU,cACsC,GAAG,MACtD,CAAC,GAAO,GAAG,EAAM,GAAG,CAAI,CAAC,IAEyB,CAAC,GAAO,GAAG,CAAK;CAI9E,OAAO;AACX"}
|