@ryuzaki13/react-foundation-ui 1.8.6 → 1.8.7
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/chunks/date-input-g6l1iDmf.js.map +1 -1
- package/package.json +4 -4
- package/storybook-static/assets/{Badge-CV8uR5lW.js → Badge-C4U4sxtF.js} +1 -1
- package/storybook-static/assets/{Badge.stories-DqTFDEB6.js → Badge.stories-DHmJTpdf.js} +1 -1
- package/storybook-static/assets/{BadgeList-CVWvhSS7.js → BadgeList-BOs5ycWn.js} +1 -1
- package/storybook-static/assets/{Button-C5Z6nJ9s.js → Button-BTtYeCS_.js} +1 -1
- package/storybook-static/assets/{Button-DtySOeyD.js → Button-Bkb3wQw6.js} +1 -1
- package/storybook-static/assets/{Button.stories--Wr67DaY.js → Button.stories-DgFLAdcg.js} +1 -1
- package/storybook-static/assets/{CalendarView-ZzqQ8UIe.js → CalendarView-BIFHbOTr.js} +1 -1
- package/storybook-static/assets/{CheckBox-BfNP6vO6.js → CheckBox-76M-N56J.js} +1 -1
- package/storybook-static/assets/{CheckBox-BBNTe4u8.js → CheckBox-D4u-58g8.js} +1 -1
- package/storybook-static/assets/{CheckBox.module-CoYygdZx.js → CheckBox.module-BOAVN-Ny.js} +1 -1
- package/storybook-static/assets/{CheckBox.stories-B4Sv2TXp.js → CheckBox.stories-Caw6-hSQ.js} +1 -1
- package/storybook-static/assets/{Color-6AOWCN3U-Ce5w8Y2l.js → Color-6AOWCN3U-CPYGaU3y.js} +1 -1
- package/storybook-static/assets/{ContextMenu-D-lV53Zc.js → ContextMenu-CUWt-eg1.js} +1 -1
- package/storybook-static/assets/{ContextMenu-Boi0R7-s.js → ContextMenu-gfYYiVdR.js} +1 -1
- package/storybook-static/assets/{ContextMenu.stories-D_rxDbLk.js → ContextMenu.stories-BuQS57wW.js} +1 -1
- package/storybook-static/assets/{CustomOptionButton-VYfjUfhW.js → CustomOptionButton-Be7sd0Lb.js} +1 -1
- package/storybook-static/assets/{DateInput-f-RcBdOb.js → DateInput-B4oviF7J.js} +1 -1
- package/storybook-static/assets/{DateInput-q2hPsJcH.js → DateInput-BQGUMYsW.js} +1 -1
- package/storybook-static/assets/{DateInput.stories-zQzjIsfx.js → DateInput.stories-D6q7J1C4.js} +1 -1
- package/storybook-static/assets/{DateRangePresetSelect-DQBOF8f5.js → DateRangePresetSelect-BAxaG0HP.js} +1 -1
- package/storybook-static/assets/{DateRangePresetSelect-Ciikik1_.js → DateRangePresetSelect-Cq6pOZPu.js} +1 -1
- package/storybook-static/assets/{DateRangePresetSelect.stories-BF8IP5Ep.js → DateRangePresetSelect.stories-d4HFbao6.js} +1 -1
- package/storybook-static/assets/{Detail-CEl8URNQ.js → Detail-DlETcWVp.js} +1 -1
- package/storybook-static/assets/{Detail.stories-Mr3ypLAA.js → Detail.stories-BEwFXEZH.js} +1 -1
- package/storybook-static/assets/{Dialog-Dj2y3EO1.js → Dialog-Bk8aoiOf.js} +1 -1
- package/storybook-static/assets/{Dialog-BydPFV17.js → Dialog-D63TJr1M.js} +1 -1
- package/storybook-static/assets/{Dialog.stories-C1WlZdyp.js → Dialog.stories-BR7NqeZj.js} +1 -1
- package/storybook-static/assets/{Disclosure-DH77E5hY.js → Disclosure-zQK54WKU.js} +1 -1
- package/storybook-static/assets/{Disclosure.stories-B9ZVczUX.js → Disclosure.stories-CNLpB5YJ.js} +1 -1
- package/storybook-static/assets/{DisclosureGroup-CIoFeosY.js → DisclosureGroup-C_nZSLZw.js} +1 -1
- package/storybook-static/assets/{DocsRenderer-JROSPFPF-CbJN3X6f.js → DocsRenderer-JROSPFPF-CoU8mWAw.js} +2 -2
- package/storybook-static/assets/{DropZone-CR7xRn_b.js → DropZone-CUz1Q4bA.js} +1 -1
- package/storybook-static/assets/{DropZone-DTBV-X-J.js → DropZone-Y5hvED_x.js} +1 -1
- package/storybook-static/assets/{DropZone.stories-Cw_og1Cs.js → DropZone.stories-BH4FSM4P.js} +1 -1
- package/storybook-static/assets/{DropdownMenu-CVIeHT8N.js → DropdownMenu-Bi6-5Gqf.js} +1 -1
- package/storybook-static/assets/{ExpandableActionPanel-D8U6v8X7.js → ExpandableActionPanel-BfWj887r.js} +1 -1
- package/storybook-static/assets/{ExpandableActionPanel.stories-D42CZyOc.js → ExpandableActionPanel.stories-CGxcwRMU.js} +1 -1
- package/storybook-static/assets/{Flex-Bcsl0U5O.js → Flex-Dm-Qfvzk.js} +1 -1
- package/storybook-static/assets/{Flex.stories-C0vSa3mh.js → Flex.stories-B3zMz03s.js} +1 -1
- package/storybook-static/assets/{FlexContainer-LmZIdhq2.js → FlexContainer-BWyPlTMB.js} +1 -1
- package/storybook-static/assets/{FlexItem-vkDYa5tA.js → FlexItem-pu3E11Sf.js} +1 -1
- package/storybook-static/assets/{FloatingPopover-DvU3oc_q.js → FloatingPopover-NMRTnqfo.js} +1 -1
- package/storybook-static/assets/FormattedTableCell-BISjHrBJ.js +15 -0
- package/storybook-static/assets/{Grid-BCzygAqy.js → Grid-CzO0Ae1r.js} +1 -1
- package/storybook-static/assets/{Grid-Wl0Y0YAh.js → Grid-vZyA-712.js} +1 -1
- package/storybook-static/assets/{Grid.stories-G6gMmKCP.js → Grid.stories-BQqK0zom.js} +1 -1
- package/storybook-static/assets/{GridContainer-qDguE1HS.js → GridContainer-fKYgDsLl.js} +1 -1
- package/storybook-static/assets/{ImageCropper.stories-u6rOvDbd.js → ImageCropper.stories-Ca4MZWOW.js} +3 -3
- package/storybook-static/assets/{ImageRenderer-BYIRHN5G.js → ImageRenderer-BWIk_76e.js} +1 -1
- package/storybook-static/assets/{ImageRenderer.stories-BMEHEIVH.js → ImageRenderer.stories-C1FfOcFD.js} +1 -1
- package/storybook-static/assets/{ImageView.stories-DiEA93Yz.js → ImageView.stories-DyuR7kdq.js} +1 -1
- package/storybook-static/assets/{ImageViewer.stories-DyxctGxM.js → ImageViewer.stories-PcvOFYqC.js} +2 -2
- package/storybook-static/assets/{ImageViewerRuntime--h_3C2s0.js → ImageViewerRuntime-BiI7Xukq.js} +2 -2
- package/storybook-static/assets/{Input-CViRQ_rj.js → Input-CsD-x1BQ.js} +1 -1
- package/storybook-static/assets/{Input-B2kOwWOU.js → Input-D_4GoCLl.js} +1 -1
- package/storybook-static/assets/{Input.stories-D9eo9MWc.js → Input.stories-D-1urO1N.js} +1 -1
- package/storybook-static/assets/{InputDateTime-T9_iyG4G.js → InputDateTime-CFQanHxB.js} +1 -1
- package/storybook-static/assets/{InputDateTime.stories-DzegljaN.js → InputDateTime.stories-Q4Ao5Tat.js} +1 -1
- package/storybook-static/assets/{InputFile-CA3YpXnm.js → InputFile-Bj8PXGY3.js} +1 -1
- package/storybook-static/assets/{InputFile-Cvdj6RfC.js → InputFile-DM6fBXUx.js} +1 -1
- package/storybook-static/assets/{InputFile.stories-C18UaXPS.js → InputFile.stories-DC27BYoQ.js} +1 -1
- package/storybook-static/assets/{InputFiles-nS_qsXY7.js → InputFiles-D59MGS0Z.js} +1 -1
- package/storybook-static/assets/{InputFiles-C4OkTG9M.js → InputFiles-teFj6f6X.js} +1 -1
- package/storybook-static/assets/{InputFiles.stories-BYd0Pe0R.js → InputFiles.stories-DX5UEvYR.js} +1 -1
- package/storybook-static/assets/{InputFilesList-CXPf7Ui_.js → InputFilesList-BD4Y0gN-.js} +1 -1
- package/storybook-static/assets/{InputImage-kd50L1Gf.js → InputImage-DmsFGNLJ.js} +1 -1
- package/storybook-static/assets/{InputImage-jDVIxP7S.js → InputImage-Du6jGDmY.js} +1 -1
- package/storybook-static/assets/{InputImage.stories-D-xmS4Qj.js → InputImage.stories-u0hrr-Da.js} +1 -1
- package/storybook-static/assets/{InteractiveComponents-D13nzSK7.js → InteractiveComponents-BLQiSO3p.js} +1 -1
- package/storybook-static/assets/{InteractiveComponents.stories-yITzDh-Y.js → InteractiveComponents.stories-BxIVvRz5.js} +1 -1
- package/storybook-static/assets/{LayoutPicker-BfYXGIKb.js → LayoutPicker-TecE4x6w.js} +1 -1
- package/storybook-static/assets/{LayoutPicker-9FVo0bF3.js → LayoutPicker-xUooYcbS.js} +1 -1
- package/storybook-static/assets/{LayoutPicker.stories-B-UnuGFM.js → LayoutPicker.stories-YDMnmhJ4.js} +1 -1
- package/storybook-static/assets/{Listbox-9LVlsDzw.js → Listbox-D2k8QfTs.js} +1 -1
- package/storybook-static/assets/{Listbox-C53-i-L6.js → Listbox-DplAn6ND.js} +1 -1
- package/storybook-static/assets/{Listbox.stories-CCN72ABE.js → Listbox.stories-Bg3_UQD4.js} +1 -1
- package/storybook-static/assets/{Modal-CoXpuByh.js → Modal-4OdkCwCv.js} +1 -1
- package/storybook-static/assets/{Modal.stories-DAj_oPSt.js → Modal.stories-BwawDSGR.js} +1 -1
- package/storybook-static/assets/{ModalManagerProvider-C7pGT0B3.js → ModalManagerProvider-CYR_xbpx.js} +1 -1
- package/storybook-static/assets/{MultiSelect-DqeGNK15.js → MultiSelect-CXn1CY9G.js} +1 -1
- package/storybook-static/assets/{MultiSelect-DZ2krz-D.js → MultiSelect-iwbCRD9H.js} +1 -1
- package/storybook-static/assets/{MultiSelect.stories-BoVHXMbn.js → MultiSelect.stories-BSjAL2ua.js} +1 -1
- package/storybook-static/assets/{NoData-N6Pbu0LD.js → NoData-6Scjk2OD.js} +1 -1
- package/storybook-static/assets/{NotificationsHost--uPz1G3j.js → NotificationsHost-DK3A-iq7.js} +1 -1
- package/storybook-static/assets/{NotificationsHost.stories-B-X-Ljxs.js → NotificationsHost.stories-BRmPf9Q7.js} +1 -1
- package/storybook-static/assets/{ODataMultiSelect-7U6nJdqB.js → ODataMultiSelect-CuNGV1QU.js} +1 -1
- package/storybook-static/assets/{ODataMultiSelect.stories-B-t6MsOr.js → ODataMultiSelect.stories-CyAonfZC.js} +1 -1
- package/storybook-static/assets/{ODataSelect-Dy-fSgE6.js → ODataSelect-vhh4NJS_.js} +1 -1
- package/storybook-static/assets/{ODataSelect.stories-DhqHtcmD.js → ODataSelect.stories-aJBFF45H.js} +1 -1
- package/storybook-static/assets/{ODataTreeMultiSelect-Ol0nuytU.js → ODataTreeMultiSelect-vPtlTloF.js} +1 -1
- package/storybook-static/assets/{ODataTreeMultiSelect.stories-BupVzhbY.js → ODataTreeMultiSelect.stories-DTBAF628.js} +1 -1
- package/storybook-static/assets/{ODataTreeSelect-EkpDmDyk.js → ODataTreeSelect-BCVVLB9M.js} +1 -1
- package/storybook-static/assets/{ODataTreeSelect.stories-CFdbwyEg.js → ODataTreeSelect.stories-CptC0p6O.js} +1 -1
- package/storybook-static/assets/{OneStepScroller-rmyPl9f5.js → OneStepScroller-DerjCiL3.js} +1 -1
- package/storybook-static/assets/{Option-72PrSDkj.js → Option-DkAw9S3l.js} +1 -1
- package/storybook-static/assets/{OptionButton-Br8QoJL7.js → OptionButton-CGurT23Z.js} +1 -1
- package/storybook-static/assets/{PeriodSelect-C5Xk-g4Y.js → PeriodSelect-DOO139ke.js} +1 -1
- package/storybook-static/assets/{PeriodSelect-DEZVGWGR.js → PeriodSelect-TrGTMIid.js} +1 -1
- package/storybook-static/assets/{PeriodSelect.stories-BVBP97HW.js → PeriodSelect.stories-B3GEgBZ1.js} +1 -1
- package/storybook-static/assets/{PickerTriggerActions-DSmRHX1I.js → PickerTriggerActions-CKVPuKEC.js} +1 -1
- package/storybook-static/assets/{Popover-BpbeIq6-.js → Popover-BNItmUQ9.js} +1 -1
- package/storybook-static/assets/{Popover-1KQwBxEJ.js → Popover-DgXqO0PN.js} +1 -1
- package/storybook-static/assets/{Popover.stories-6RDZ0TND.js → Popover.stories-bX5S41pH.js} +1 -1
- package/storybook-static/assets/{PresetRangeDateInput-BdkKzY69.js → PresetRangeDateInput-B4FYPy8g.js} +1 -1
- package/storybook-static/assets/{PresetRangeDateInput-CDhgmyFs.js → PresetRangeDateInput-GSnnpZzy.js} +1 -1
- package/storybook-static/assets/{PresetRangeDateInput.stories-BoE41IUK.js → PresetRangeDateInput.stories-Ca0mIo33.js} +1 -1
- package/storybook-static/assets/{PresetSelect-CA5AyOOy.js → PresetSelect-B_KRl4SQ.js} +1 -1
- package/storybook-static/assets/README-1RIcD7RV.js +694 -0
- package/storybook-static/assets/README-B1XUsXZj.js +334 -0
- package/storybook-static/assets/README-BH78sIZ_.js +288 -0
- package/storybook-static/assets/README-BgeYwR6t.js +347 -0
- package/storybook-static/assets/README-BhsUMTSk.js +860 -0
- package/storybook-static/assets/README-Bj88Ecb7.js +876 -0
- package/storybook-static/assets/README-C9saKIyr.js +417 -0
- package/storybook-static/assets/README-CP1S4LYF.js +1309 -0
- package/storybook-static/assets/README-Cc-guFb0.js +1487 -0
- package/storybook-static/assets/README-CheO0E1A.js +282 -0
- package/storybook-static/assets/README-CiuVVUgE.js +420 -0
- package/storybook-static/assets/README-CvRg9lqh.js +273 -0
- package/storybook-static/assets/README-DCdV2RQL.js +1093 -0
- package/storybook-static/assets/README-DyNQqotW.js +338 -0
- package/storybook-static/assets/README-W1rhe8Sk.js +648 -0
- package/storybook-static/assets/{RadioButton-IDzSzftl.js → RadioButton-CA9DN5bY.js} +1 -1
- package/storybook-static/assets/{RadioButton-DCoo55ld.js → RadioButton-DcXbcFW1.js} +1 -1
- package/storybook-static/assets/{RadioButton.stories-BEk1uc2x.js → RadioButton.stories-1_kzxnXY.js} +1 -1
- package/storybook-static/assets/{RadioGroup-CbBmuFtC.js → RadioGroup-bEwTHCyS.js} +1 -1
- package/storybook-static/assets/{RadioGroup-VSiqFUI1.js → RadioGroup-mfYgdfXM.js} +1 -1
- package/storybook-static/assets/{RadioGroup.stories-DR7Vskyy.js → RadioGroup.stories-CecdqPms.js} +1 -1
- package/storybook-static/assets/{Scrollable-CDZ18AWy.js → Scrollable-1fdTDJHK.js} +1 -1
- package/storybook-static/assets/{Select-IWQpo0LQ.js → Select-CvQZGS1G.js} +1 -1
- package/storybook-static/assets/{Select-YjugVUKX.js → Select-Df5Md2Cm.js} +1 -1
- package/storybook-static/assets/{Select.stories-4JpnXfaB.js → Select.stories-M4M80KTq.js} +1 -1
- package/storybook-static/assets/{SerializableSelect-oxnbV26a.js → SerializableSelect-CnsGo9Ep.js} +1 -1
- package/storybook-static/assets/{SerializableSelect.stories-CjVcuU4t.js → SerializableSelect.stories-BJ6MlNDx.js} +1 -1
- package/storybook-static/assets/{SingleDateTimeInput-DFPke7bv.js → SingleDateTimeInput-BCV0gfB9.js} +1 -1
- package/storybook-static/assets/{SingleDateTimeInput-CQ1_ABKA.js → SingleDateTimeInput-DJH7zSIz.js} +1 -1
- package/storybook-static/assets/{SingleDateTimeInput.stories--Nky_yBh.js → SingleDateTimeInput.stories-DgEwAFe8.js} +1 -1
- package/storybook-static/assets/{Slider-DGVl0dbV.js → Slider-D504FPH3.js} +1 -1
- package/storybook-static/assets/{Slider-L7pqW7xG.js → Slider-cLRAHPLi.js} +1 -1
- package/storybook-static/assets/{Slider.stories-DkqUavTk.js → Slider.stories-EI314Iff.js} +1 -1
- package/storybook-static/assets/{SliderInput-B7U8M2UN.js → SliderInput-Bt0H8rgN.js} +1 -1
- package/storybook-static/assets/{SliderInput-Dn5-Dt4w.js → SliderInput-E5p3n3VY.js} +1 -1
- package/storybook-static/assets/{SliderInput.stories-B9WBQOiA.js → SliderInput.stories-CDYPq7fP.js} +1 -1
- package/storybook-static/assets/{Sortable-DxdXjG1a.js → Sortable-Bzd7JK4E.js} +1 -1
- package/storybook-static/assets/{Sortable.stories-D1rp2l9M.js → Sortable.stories-DNGbmn2a.js} +1 -1
- package/storybook-static/assets/{Splitter-yZpkev96.js → Splitter-DSf48AOW.js} +1 -1
- package/storybook-static/assets/{Splitter.stories-C3TCKNhu.js → Splitter.stories-DUKsOqNN.js} +1 -1
- package/storybook-static/assets/{StateSelect-DZjPblfU.js → StateSelect-BxuKIsLa.js} +1 -1
- package/storybook-static/assets/{StateSelect-CaAFL9Jr.js → StateSelect-HKOT6kq9.js} +1 -1
- package/storybook-static/assets/{StateSelect.stories-D7EwT0sO.js → StateSelect.stories-CbLvq7ou.js} +1 -1
- package/storybook-static/assets/{StringToggle.stories-DuaWy2LK.js → StringToggle.stories-DOKL-aWy.js} +1 -1
- package/storybook-static/assets/{Switch-C0GQ2oXw.js → Switch-CLAfhk73.js} +1 -1
- package/storybook-static/assets/{Switch-DCNQT47U.js → Switch-CVTjSEq1.js} +1 -1
- package/storybook-static/assets/{Switch.stories-CAIEfQb6.js → Switch.stories-CL5Xe_HQ.js} +1 -1
- package/storybook-static/assets/{Table-Be1uta4G.js → Table-DKc19RME.js} +1 -1
- package/storybook-static/assets/Table.stories-CJ9DR1S-.js +207 -0
- package/storybook-static/assets/{Tabs-Co7Z92UK.js → Tabs-De_4F0H3.js} +1 -1
- package/storybook-static/assets/{Tabs.stories-D84U8aJM.js → Tabs.stories-DPAvvkh9.js} +2 -2
- package/storybook-static/assets/{TabsBox-CUpOXGdh.js → TabsBox-Cy5CICcb.js} +1 -1
- package/storybook-static/assets/{TagInput-BbLK6uxZ.js → TagInput-7n98ftvD.js} +1 -1
- package/storybook-static/assets/{TagInput.stories-D34JCfyM.js → TagInput.stories-B6JAb3XA.js} +1 -1
- package/storybook-static/assets/{Text-DtJw3wMU.js → Text-CjSG8mvD.js} +1 -1
- package/storybook-static/assets/{Textarea-Cz6RWJdh.js → Textarea-CQqwaJ0Q.js} +1 -1
- package/storybook-static/assets/{Textarea-C9MWeqLC.js → Textarea-Dea29_yQ.js} +1 -1
- package/storybook-static/assets/{Textarea.stories-DqK16XGS.js → Textarea.stories-BZf3Cu4S.js} +1 -1
- package/storybook-static/assets/{TimePanel-DcZ-UUgO.js → TimePanel-CPZ4jOw2.js} +1 -1
- package/storybook-static/assets/{TimePanel.stories-BafOmX3I.js → TimePanel.stories-y5Lp9sMG.js} +1 -1
- package/storybook-static/assets/{Toggle-CgU6o7qM.js → Toggle-CJHzo9mB.js} +1 -1
- package/storybook-static/assets/{Toggle-oYTkCbEJ.js → Toggle-s7SzQ11W.js} +1 -1
- package/storybook-static/assets/{Toggle.stories-CbmOBWIu.js → Toggle.stories-Dk3C5Le5.js} +1 -1
- package/storybook-static/assets/{TreeMultiSelect-B4WfXc4Z.js → TreeMultiSelect-CxXF7pQE.js} +1 -1
- package/storybook-static/assets/{TreeMultiSelect-DN_CYg-F.js → TreeMultiSelect-DfeggvLr.js} +1 -1
- package/storybook-static/assets/{TreeMultiSelect.stories-DYVj6Rm6.js → TreeMultiSelect.stories-DKiHT_6L.js} +1 -1
- package/storybook-static/assets/{TreePickerBase-8gA_hIXn.js → TreePickerBase-DKJJDVC_.js} +1 -1
- package/storybook-static/assets/{TreeSelect-CTtmHF-B.js → TreeSelect-2E1KhJMi.js} +1 -1
- package/storybook-static/assets/{TreeSelect-2bjYl5ox.js → TreeSelect-h23orAoA.js} +1 -1
- package/storybook-static/assets/{TreeSelect.stories-BEAY61pJ.js → TreeSelect.stories-DO06yaS0.js} +1 -1
- package/storybook-static/assets/{TreeTable-B4BaHybr.js → TreeTable-ByV2_E0S.js} +1 -1
- package/storybook-static/assets/{TreeTable.stories-lLwH2Gzl.js → TreeTable.stories-D4gbQx59.js} +2 -2
- package/storybook-static/assets/{WithTooltip-7X4HQR7L-Cakt_JaX.js → WithTooltip-7X4HQR7L-DQ4Qthn2.js} +1 -1
- package/storybook-static/assets/{array-D-_yMMUK-CIRmlF93.js → array-D-_yMMUK-BQ0uu1hZ.js} +1 -1
- package/storybook-static/assets/{async-DFALSYBD.js → async-BI7SlYp0.js} +1 -1
- package/storybook-static/assets/axe-_9eNhXQJ.js +20 -0
- package/storybook-static/assets/{blocks-Dr191OF5.js → blocks-B8F5vBhs.js} +6 -6
- package/storybook-static/assets/check-mlY_aVEI.js +1 -0
- package/storybook-static/assets/chevron-down-C7c-TcEc.js +1 -0
- package/storybook-static/assets/chevron-left-2LrGBhDY.js +1 -0
- package/storybook-static/assets/chevron-right-D6NN1_UQ.js +1 -0
- package/storybook-static/assets/{chunk-242VQQM5-BMWWiybC.js → chunk-242VQQM5-bHL6qIJq.js} +1 -1
- package/storybook-static/assets/{chunk-DRQKCCAG-CqE-Zrl9.js → chunk-DRQKCCAG-DvsmU7ha.js} +1 -1
- package/storybook-static/assets/{chunk-IMSF75WX-yvVUwIQZ.js → chunk-IMSF75WX-CGYlPTyG.js} +1 -1
- package/storybook-static/assets/{chunk-KJHJLCBK-D-yQfkOi.js → chunk-KJHJLCBK-yRlRMfNs.js} +1 -1
- package/storybook-static/assets/{chunk-QQBDHPVZ-B98qboss.js → chunk-QQBDHPVZ-DC5n6Pix.js} +1 -1
- package/storybook-static/assets/{chunk-W22LQPXL-BR_vFDlt.js → chunk-W22LQPXL-5sD3hVeE.js} +1 -1
- package/storybook-static/assets/{circle-alert-Is-jAV5H.js → circle-alert-H_7GSXFp.js} +1 -1
- package/storybook-static/assets/circle-check-B0voRIBT.js +1 -0
- package/storybook-static/assets/{components-DqFIc_sf.js → components-Bw-2TeQP.js} +12 -12
- package/storybook-static/assets/{contextMenu-CWKJO78E.js → contextMenu-B8JusItT.js} +1 -1
- package/storybook-static/assets/{createControlledStoryRender-DHsyXc1B.js → createControlledStoryRender-BouhvX-R.js} +1 -1
- package/storybook-static/assets/{date-segments-D3HouZ37.js → date-segments-DeoMkKv9.js} +1 -1
- package/storybook-static/assets/{defaultRenderers-V5HI39JH.js → defaultRenderers-BHXboKuy.js} +1 -1
- package/storybook-static/assets/{dom-B3G_GtJw.js → dom-DBd_DWF8.js} +1 -1
- package/storybook-static/assets/{download-B61X2hm8.js → download-CSY7aIz0.js} +1 -1
- package/storybook-static/assets/{error-BZrNlEUO.js → error-De9R4E_F.js} +1 -1
- package/storybook-static/assets/{file-vPGnhPr-.js → file-CwRoWxeB.js} +1 -1
- package/storybook-static/assets/floating-ui.react-D7RwPYeO.js +1 -0
- package/storybook-static/assets/formatter-L2YTDE3Q-CRGuSxAF.js +1 -0
- package/storybook-static/assets/{formatters-CBgrBeSA-DY6OIAc8.js → formatters-CBgrBeSA-CqB1pZxS.js} +1 -1
- package/storybook-static/assets/{foundationLib-10USAmEi.js → foundationLib-BoYGVeXe.js} +1 -1
- package/storybook-static/assets/{hooks-C8GJ9Cvf.js → hooks-DQXvhAPr.js} +2 -2
- package/storybook-static/assets/iframe-B8PAuhnL.js +1 -0
- package/storybook-static/assets/{iframe-Bu7ux79g.js → iframe-CViB2sFm.js} +23 -23
- package/storybook-static/assets/{info-CnuHFdaB.js → info-MrQcS8jn.js} +1 -1
- package/storybook-static/assets/{matchers-JIUQ3HVY-DvR2XErZ.js → matchers-JIUQ3HVY-C789_MAK.js} +1 -1
- package/storybook-static/assets/{media-BycWpjAl.js → media-D9cE2tzi.js} +1 -1
- package/storybook-static/assets/odata-CBGaUmRT.js +1 -0
- package/storybook-static/assets/{odata-service-B6TxBSLm-BW9QLSrS.js → odata-service-B6TxBSLm-Bq8Ya67E.js} +1 -1
- package/storybook-static/assets/{odataFetchFn-CAzdQ6ar.js → odataFetchFn-CPO8XIYX.js} +1 -1
- package/storybook-static/assets/{odataStoryFixtures-CX6euJaD.js → odataStoryFixtures--SWu2yC_.js} +2 -2
- package/storybook-static/assets/{persisted-BKBboERB.js → persisted-B2dfNRCz.js} +1 -1
- package/storybook-static/assets/{preload-helper-BHmFeTtP.js → preload-helper-wdlQj8DP.js} +1 -1
- package/storybook-static/assets/react-18-D4o7zN8K.js +9 -0
- package/storybook-static/assets/{react-C21x__mS.js → react-BZJXY1be.js} +1 -1
- package/storybook-static/assets/{react-qN2cStNd.js → react-Bl2r1tuC.js} +1 -1
- package/storybook-static/assets/{react-CvkUhHUq.js → react-GkniIHEK.js} +1 -1
- package/storybook-static/assets/{react-dom-2o2phaJG.js → react-dom-BzEl8usk.js} +1 -1
- package/storybook-static/assets/react-iQQwtFPL.js +9 -0
- package/storybook-static/assets/{refresh-cw-ByNiehPJ.js → refresh-cw-_wTFfhR2.js} +1 -1
- package/storybook-static/assets/{rolldown-runtime-C0FnF6B9.js → rolldown-runtime-DkW27tQK.js} +1 -1
- package/storybook-static/assets/{settings-2-BGG8WBgX.js → settings-2-BqduNJjJ.js} +1 -1
- package/storybook-static/assets/{shim-BUMlC1GG.js → shim-DlYORV19.js} +1 -1
- package/storybook-static/assets/{sortable-6C6S-hq-.js → sortable-D-m6SEp7.js} +1 -1
- package/storybook-static/assets/syntaxhighlighter-QEQV7K2Y-CEo9I1qZ.js +6 -0
- package/storybook-static/assets/{theming-mTWhffkp.js → theming-Cvtrnj7n.js} +1 -1
- package/storybook-static/assets/{trash-2-EYPGQg68.js → trash-2-65ZF92zp.js} +1 -1
- package/storybook-static/assets/{treeStoryFixtures-Ci7STpiz.js → treeStoryFixtures-DXHh9GL3.js} +1 -1
- package/storybook-static/assets/{triangle-alert-qcSLqRkW.js → triangle-alert-k_CRNi0k.js} +1 -1
- package/storybook-static/assets/{ui.module-D4CuGr8r.js → ui.module-Cat84Nza.js} +1 -1
- package/storybook-static/assets/{uiClasses-CVGbL980.js → uiClasses-B9c6dZuu.js} +1 -1
- package/storybook-static/assets/{useFlexClasses-Gn9spXEB.js → useFlexClasses-SgVG37Bb.js} +1 -1
- package/storybook-static/assets/{useODataSelectBase-LQU24QnI.js → useODataSelectBase-Ck_hduYv.js} +1 -1
- package/storybook-static/assets/{useODataTreeData-0I485gNf.js → useODataTreeData-DTrxPY_k.js} +1 -1
- package/storybook-static/assets/usePickerDefaultFilter-5Q1pAV4d.js +1 -0
- package/storybook-static/assets/{usePickerQuery-B0a5nSeM.js → usePickerQuery-DdeWtJWc.js} +1 -1
- package/storybook-static/assets/{usePickerTriggerController-DMRm063p.js → usePickerTriggerController-D8hxWmGo.js} +1 -1
- package/storybook-static/assets/{utils-DUM89eIj.js → utils-tg3ibbzi.js} +1 -1
- package/storybook-static/assets/x-C5XG0bD4.js +1 -0
- package/storybook-static/iframe.html +7 -7
- package/storybook-static/index.json +1 -1
- package/storybook-static/project.json +1 -1
- package/storybook-static/sb-addons/chromatic-com-storybook-1/manager-bundle.js +35 -33
- package/storybook-static/sb-manager/globals-runtime.js +7 -7
- package/storybook-static/sb-manager/globals.js +1 -1
- package/storybook-static/sb-manager/runtime.js +88 -89
- package/storybook-static/vite-inject-mocker-entry.js +2 -2
- package/storybook-static/assets/FormattedTableCell-CGREP0wz.js +0 -15
- package/storybook-static/assets/Table.stories-ByUJbaZl.js +0 -207
- package/storybook-static/assets/axe-Bq-2xuso.js +0 -20
- package/storybook-static/assets/browserAndReact-BDUP5jYY.js +0 -70
- package/storybook-static/assets/check-D56EYchd.js +0 -1
- package/storybook-static/assets/chevron-down-CzEzTRkp.js +0 -1
- package/storybook-static/assets/chevron-left-Dl69q13G.js +0 -1
- package/storybook-static/assets/chevron-right-CJNjsAmt.js +0 -1
- package/storybook-static/assets/circle-check-6tyIZ3Kl.js +0 -1
- package/storybook-static/assets/filesAndExcel-Bs3kqlsI.js +0 -92
- package/storybook-static/assets/floating-ui.react-bhmHzD7p.js +0 -1
- package/storybook-static/assets/formatter-L2YTDE3Q-x0raBH34.js +0 -1
- package/storybook-static/assets/formatters-NGPd1d-h.js +0 -202
- package/storybook-static/assets/formatters-runtime-CCfGWt-f.js +0 -329
- package/storybook-static/assets/iframe-BEvUkaBB.js +0 -1
- package/storybook-static/assets/odata-BslPtJet.js +0 -1
- package/storybook-static/assets/odataAndTables-Cyl3UJOz.js +0 -77
- package/storybook-static/assets/pureUtilities-Bj6GraNW.js +0 -30
- package/storybook-static/assets/react-18-CgkwBzTo.js +0 -9
- package/storybook-static/assets/react-CjYyitxm.js +0 -9
- package/storybook-static/assets/runtimeAndState-mq-BaPyx.js +0 -93
- package/storybook-static/assets/syntaxhighlighter-T5IQBZDB-B5rzfkHU.js +0 -6
- package/storybook-static/assets/usePickerDefaultFilter-Be420WNF.js +0 -1
- package/storybook-static/assets/x-FZLq51ZK.js +0 -1
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
import{n as e}from"./rolldown-runtime-DkW27tQK.js";import{n as t}from"./iframe-CViB2sFm.js";import{i as n,r}from"./react-Bl2r1tuC.js";import{l as i,m as a}from"./blocks-B8F5vBhs.js";function o(e){let t={a:`a`,code:`code`,h1:`h1`,h2:`h2`,h3:`h3`,li:`li`,ol:`ol`,p:`p`,pre:`pre`,table:`table`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,...n(),...e.components};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(i,{title:`Foundation Lib/API/Crypto`}),`
|
|
2
|
+
`,(0,c.jsxs)(t.h1,{id:`идентификаторы-и-строковые-hash-через-ryuzaki13react-foundation-libcrypto`,children:[`Идентификаторы и строковые hash через `,(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/crypto`})]}),`
|
|
3
|
+
`,(0,c.jsxs)(t.p,{children:[`Модуль `,(0,c.jsx)(t.code,{children:`crypto`}),` предоставляет небольшие функции для двух разных задач:`]}),`
|
|
4
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
5
|
+
`,(0,c.jsx)(t.li,{children:`получить детерминированный компактный hash из строки;`}),`
|
|
6
|
+
`,(0,c.jsx)(t.li,{children:`создать случайный UUID версии 4.`}),`
|
|
7
|
+
`]}),`
|
|
8
|
+
`,(0,c.jsx)(t.p,{children:`Детерминированный результат всегда повторяется для той же строки. Случайный UUID, наоборот, создаётся заново при каждом вызове. Это принципиально разные виды идентификаторов, и выбирать между ними нужно по смыслу задачи.`}),`
|
|
9
|
+
`,(0,c.jsx)(t.p,{children:`Несмотря на имя entrypoint, строковые hash этого модуля не являются криптографическими. Они не подходят для паролей, подписей, access token, проверки безопасности или сокрытия персональных данных.`}),`
|
|
10
|
+
`,(0,c.jsx)(t.p,{children:`README рассчитан на разработчика, который использует опубликованный пакет без доступа к исходному коду. Здесь объясняются базовые понятия, точные форматы результатов, browser runtime, Unicode, коллизии, React/SSR, тестирование и безопасные границы применения.`}),`
|
|
11
|
+
`,(0,c.jsx)(t.h2,{id:`содержание`,children:`Содержание`}),`
|
|
12
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
13
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BD%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B8-%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D1%8B-%D0%BC%D0%BE%D0%B4%D1%83%D0%BB%D1%8F`,children:`Назначение и границы модуля`})}),`
|
|
14
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-%D0%B8-%D0%B8%D0%BC%D0%BF%D0%BE%D1%80%D1%82`,children:`Установка и импорт`})}),`
|
|
15
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BC%D0%B8%D0%BD%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-%D0%BF%D0%BE%D0%BD%D1%8F%D1%82%D0%B8%D1%8F`,children:`Минимальные понятия`})}),`
|
|
16
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BA%D0%B0%D0%BA-%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D1%82%D1%8C-api`,children:`Как выбрать API`})}),`
|
|
17
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B1%D1%8B%D1%81%D1%82%D1%80%D1%8B%D0%B9-%D1%81%D1%82%D0%B0%D1%80%D1%82`,children:`Быстрый старт`})}),`
|
|
18
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BE%D0%B1%D1%89%D0%B8%D0%B5-%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D0%B0-%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%BE%D0%B2%D1%8B%D1%85-hash`,children:`Общие правила строковых hash`})}),`
|
|
19
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#hashstring`,children:(0,c.jsx)(t.code,{children:`hashString`})})}),`
|
|
20
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#hashstring128`,children:(0,c.jsx)(t.code,{children:`hashString128`})})}),`
|
|
21
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#hashstring128base64url`,children:(0,c.jsx)(t.code,{children:`hashString128Base64Url`})})}),`
|
|
22
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#stringtoelementid`,children:(0,c.jsx)(t.code,{children:`stringToElementId`})})}),`
|
|
23
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#uuidv4`,children:(0,c.jsx)(t.code,{children:`uuidv4`})})}),`
|
|
24
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BF%D0%BE%D0%B4%D0%B3%D0%BE%D1%82%D0%BE%D0%B2%D0%BA%D0%B0-%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D1%8B%D1%85-%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85`,children:`Подготовка сложных данных`})}),`
|
|
25
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B1%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D0%BE%D1%81%D1%82%D1%8C`,children:`Безопасность`})}),`
|
|
26
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BA%D0%BE%D0%BB%D0%BB%D0%B8%D0%B7%D0%B8%D0%B8-%D0%B8-%D0%BE%D0%B1%D1%80%D0%B5%D0%B7%D0%B0%D0%BD%D0%B8%D0%B5-%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%B0`,children:`Коллизии и обрезание результата`})}),`
|
|
27
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#browser-nodejs-%D0%B8-ssr`,children:`Browser, Node.js и SSR`})}),`
|
|
28
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#react-%D1%80%D0%B5%D1%86%D0%B5%D0%BF%D1%82%D1%8B`,children:`React-рецепты`})}),`
|
|
29
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5`,children:`Тестирование`})}),`
|
|
30
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%BE%D0%B4%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C`,children:`Производительность`})}),`
|
|
31
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BE%D0%B3%D1%80%D0%B0%D0%BD%D0%B8%D1%87%D0%B5%D0%BD%D0%B8%D1%8F-%D0%B8-%D1%87%D0%B0%D1%81%D1%82%D1%8B%D0%B5-%D0%BE%D1%88%D0%B8%D0%B1%D0%BA%D0%B8`,children:`Ограничения и частые ошибки`})}),`
|
|
32
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BA%D1%80%D0%B0%D1%82%D0%BA%D0%B8%D0%B9-%D1%81%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D1%87%D0%BD%D0%B8%D0%BA-api`,children:`Краткий справочник API`})}),`
|
|
33
|
+
`,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%B2%D0%BE%D0%BF%D1%80%D0%BE%D1%81%D1%8B-%D0%B8-%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%8B`,children:`Вопросы и ответы`})}),`
|
|
34
|
+
`]}),`
|
|
35
|
+
`,(0,c.jsx)(t.h2,{id:`назначение-и-границы-модуля`,children:`Назначение и границы модуля`}),`
|
|
36
|
+
`,(0,c.jsxs)(t.h3,{id:`что-делает-crypto`,children:[`Что делает `,(0,c.jsx)(t.code,{children:`crypto`})]}),`
|
|
37
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
38
|
+
`,(0,c.jsx)(t.li,{children:`строит короткий 32-bit hash в base36;`}),`
|
|
39
|
+
`,(0,c.jsx)(t.li,{children:`строит более длинный строковый hash в 32-символьном hexadecimal формате;`}),`
|
|
40
|
+
`,(0,c.jsx)(t.li,{children:`кодирует тот же длинный hash в компактный URL-safe base64 без padding;`}),`
|
|
41
|
+
`,(0,c.jsx)(t.li,{children:`добавляет префикс к длинному hash для получения DOM-like ID;`}),`
|
|
42
|
+
`,(0,c.jsx)(t.li,{children:`создаёт UUID v4 через Web Crypto;`}),`
|
|
43
|
+
`,(0,c.jsxs)(t.li,{children:[`использует native `,(0,c.jsx)(t.code,{children:`crypto.randomUUID`}),`, если он доступен;`]}),`
|
|
44
|
+
`,(0,c.jsxs)(t.li,{children:[`имеет fallback UUID через `,(0,c.jsx)(t.code,{children:`crypto.getRandomValues`}),`.`]}),`
|
|
45
|
+
`]}),`
|
|
46
|
+
`,(0,c.jsx)(t.h3,{id:`чего-модуль-не-делает`,children:`Чего модуль не делает`}),`
|
|
47
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
48
|
+
`,(0,c.jsx)(t.li,{children:`не шифрует и не расшифровывает данные;`}),`
|
|
49
|
+
`,(0,c.jsx)(t.li,{children:`не вычисляет SHA-256, SHA-512, HMAC или цифровую подпись;`}),`
|
|
50
|
+
`,(0,c.jsx)(t.li,{children:`не хеширует пароли безопасным password hashing алгоритмом;`}),`
|
|
51
|
+
`,(0,c.jsx)(t.li,{children:`не гарантирует отсутствие коллизий;`}),`
|
|
52
|
+
`,(0,c.jsx)(t.li,{children:`не проверяет уникальность результата по базе данных;`}),`
|
|
53
|
+
`,(0,c.jsx)(t.li,{children:`не нормализует регистр, пробелы, Unicode или JSON;`}),`
|
|
54
|
+
`,(0,c.jsx)(t.li,{children:`не сериализует объект автоматически;`}),`
|
|
55
|
+
`,(0,c.jsx)(t.li,{children:`не хранит созданные UUID;`}),`
|
|
56
|
+
`,(0,c.jsxs)(t.li,{children:[`не создаёт React hydration-safe ID вместо `,(0,c.jsx)(t.code,{children:`useId`}),`;`]}),`
|
|
57
|
+
`,(0,c.jsx)(t.li,{children:`не превращает UUID в право доступа или security token.`}),`
|
|
58
|
+
`]}),`
|
|
59
|
+
`,(0,c.jsx)(t.h3,{id:`две-группы-api`,children:`Две группы API`}),`
|
|
60
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Группа`}),(0,c.jsx)(t.th,{children:`Функции`}),(0,c.jsx)(t.th,{children:`Свойство`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Детерминированные hash`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`hashString`}),`, `,(0,c.jsx)(t.code,{children:`hashString128`}),`, `,(0,c.jsx)(t.code,{children:`hashString128Base64Url`}),`, `,(0,c.jsx)(t.code,{children:`stringToElementId`})]}),(0,c.jsx)(t.td,{children:`Та же точная строка даёт тот же результат.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Случайный идентификатор`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`uuidv4`})}),(0,c.jsx)(t.td,{children:`Каждый вызов должен создавать новое случайное значение.`})]})]})]}),`
|
|
61
|
+
`,(0,c.jsx)(t.h2,{id:`установка-и-импорт`,children:`Установка и импорт`}),`
|
|
62
|
+
`,(0,c.jsx)(t.p,{children:`Установите пакет:`}),`
|
|
63
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-bash`,children:`npm install @ryuzaki13/react-foundation-lib
|
|
64
|
+
`})}),`
|
|
65
|
+
`,(0,c.jsxs)(t.p,{children:[`Импортируйте функции только из опубликованного subpath `,(0,c.jsx)(t.code,{children:`/crypto`}),`:`]}),`
|
|
66
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { hashString, hashString128, hashString128Base64Url, stringToElementId, uuidv4 } from "@ryuzaki13/react-foundation-lib/crypto";
|
|
67
|
+
`})}),`
|
|
68
|
+
`,(0,c.jsx)(t.p,{children:`Корневой импорт пакета не поддерживается:`}),`
|
|
69
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`// Неправильно.
|
|
70
|
+
import { uuidv4 } from "@ryuzaki13/react-foundation-lib";
|
|
71
|
+
|
|
72
|
+
// Правильно.
|
|
73
|
+
import { uuidv4 } from "@ryuzaki13/react-foundation-lib/crypto";
|
|
74
|
+
`})}),`
|
|
75
|
+
`,(0,c.jsxs)(t.p,{children:[`Entrypoint `,(0,c.jsx)(t.code,{children:`/crypto`}),` не импортирует React и не содержит hooks.`]}),`
|
|
76
|
+
`,(0,c.jsx)(t.p,{children:`Модуль поставляется как ESM. Часть API использует browser globals:`}),`
|
|
77
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Требуемые возможности runtime`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`hashString`})}),(0,c.jsxs)(t.td,{children:[`Обычные `,(0,c.jsx)(t.code,{children:`String`}),`, `,(0,c.jsx)(t.code,{children:`Number`}),` и bitwise operations`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`hashString128`})}),(0,c.jsxs)(t.td,{children:[`Дополнительно `,(0,c.jsx)(t.code,{children:`Math.imul`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`hashString128Base64Url`})}),(0,c.jsxs)(t.td,{children:[`Дополнительно глобальный `,(0,c.jsx)(t.code,{children:`btoa`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`stringToElementId`})}),(0,c.jsxs)(t.td,{children:[`Глобальный `,(0,c.jsx)(t.code,{children:`btoa`}),`, потому что вызывает предыдущую функцию`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`uuidv4`})}),(0,c.jsxs)(t.td,{children:[`Глобальный Web Crypto `,(0,c.jsx)(t.code,{children:`crypto`})]})]})]})]}),`
|
|
78
|
+
`,(0,c.jsx)(t.p,{children:`Наличие одноимённых API в конкретной версии Node.js не является частью browser-oriented контракта пакета. Проверяйте среду, если вызываете эти функции вне браузера.`}),`
|
|
79
|
+
`,(0,c.jsx)(t.h2,{id:`минимальные-понятия`,children:`Минимальные понятия`}),`
|
|
80
|
+
`,(0,c.jsx)(t.h3,{id:`идентификатор`,children:`Идентификатор`}),`
|
|
81
|
+
`,(0,c.jsx)(t.p,{children:`Идентификатор — строка, по которой приложение отличает одну сущность от другой:`}),`
|
|
82
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const userId = "user-42";
|
|
83
|
+
`})}),`
|
|
84
|
+
`,(0,c.jsx)(t.p,{children:`Хороший идентификатор должен соответствовать задаче. Иногда он должен повторяться для одинакового входа, а иногда должен быть новым для каждой созданной сущности.`}),`
|
|
85
|
+
`,(0,c.jsx)(t.h3,{id:`детерминированность`,children:`Детерминированность`}),`
|
|
86
|
+
`,(0,c.jsx)(t.p,{children:`Детерминированная функция всегда возвращает одинаковый результат для одинакового входа:`}),`
|
|
87
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`hashString128("user:42") === hashString128("user:42"); // true
|
|
88
|
+
`})}),`
|
|
89
|
+
`,(0,c.jsx)(t.p,{children:`Это удобно для:`}),`
|
|
90
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
91
|
+
`,(0,c.jsx)(t.li,{children:`cache-like ключей;`}),`
|
|
92
|
+
`,(0,c.jsx)(t.li,{children:`стабильных DOM ID по известному business key;`}),`
|
|
93
|
+
`,(0,c.jsx)(t.li,{children:`fingerprint нормализованной конфигурации;`}),`
|
|
94
|
+
`,(0,c.jsx)(t.li,{children:`группировки или быстрого предварительного сравнения.`}),`
|
|
95
|
+
`]}),`
|
|
96
|
+
`,(0,c.jsx)(t.h3,{id:`случайность`,children:`Случайность`}),`
|
|
97
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`uuidv4()`}),` использует криптографически стойкий генератор случайных байтов среды:`]}),`
|
|
98
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const first = uuidv4();
|
|
99
|
+
const second = uuidv4();
|
|
100
|
+
|
|
101
|
+
console.log(first === second); // Практически ожидается false.
|
|
102
|
+
`})}),`
|
|
103
|
+
`,(0,c.jsx)(t.p,{children:`Случайный UUID удобен, когда новая сущность должна получить новый ID независимо от её содержимого.`}),`
|
|
104
|
+
`,(0,c.jsx)(t.h3,{id:`hash`,children:`Hash`}),`
|
|
105
|
+
`,(0,c.jsx)(t.p,{children:`Hash-функция превращает вход произвольной длины в результат ограниченной длины:`}),`
|
|
106
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`"user:42" → "fa3326027b653e8049d56072c88378f0"
|
|
107
|
+
`})}),`
|
|
108
|
+
`,(0,c.jsx)(t.p,{children:`Поскольку возможных входных строк бесконечно много, а возможных результатов конечное количество, две разные строки теоретически могут получить одинаковый hash. Это называется коллизией.`}),`
|
|
109
|
+
`,(0,c.jsx)(t.h3,{id:`hash-не-является-шифрованием`,children:`Hash не является шифрованием`}),`
|
|
110
|
+
`,(0,c.jsx)(t.p,{children:`Шифрование предполагает возможность расшифровать данные при наличии ключа. Эти hash-функции не имеют операции обратного восстановления.`}),`
|
|
111
|
+
`,(0,c.jsx)(t.p,{children:`Но отсутствие обратной функции не делает их безопасным способом скрыть данные. Короткий или предсказуемый вход можно подобрать перебором:`}),`
|
|
112
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`hashString128("user@example.com"); // Это не безопасная анонимизация email.
|
|
113
|
+
`})}),`
|
|
114
|
+
`,(0,c.jsx)(t.h3,{id:`base36`,children:`Base36`}),`
|
|
115
|
+
`,(0,c.jsxs)(t.p,{children:[`Base36 использует цифры `,(0,c.jsx)(t.code,{children:`0-9`}),` и латинские буквы `,(0,c.jsx)(t.code,{children:`a-z`}),`. Например, десятичное число можно записать короче за счёт 36 возможных символов.`]}),`
|
|
116
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`hashString`}),` возвращает именно такой формат:`]}),`
|
|
117
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`d5nw5e
|
|
118
|
+
`})}),`
|
|
119
|
+
`,(0,c.jsx)(t.h3,{id:`hexadecimal`,children:`Hexadecimal`}),`
|
|
120
|
+
`,(0,c.jsx)(t.p,{children:`Hexadecimal, или base16, использует:`}),`
|
|
121
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`0123456789abcdef
|
|
122
|
+
`})}),`
|
|
123
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`hashString128`}),` всегда возвращает 32 lowercase hex-символа.`]}),`
|
|
124
|
+
`,(0,c.jsx)(t.h3,{id:`base64-url-safe`,children:`Base64 URL-safe`}),`
|
|
125
|
+
`,(0,c.jsxs)(t.p,{children:[`Обычный base64 может содержать `,(0,c.jsx)(t.code,{children:`+`}),`, `,(0,c.jsx)(t.code,{children:`/`}),` и padding `,(0,c.jsx)(t.code,{children:`=`}),`. URL-safe вариант заменяет проблемные символы:`]}),`
|
|
126
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
127
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`+`}),` → `,(0,c.jsx)(t.code,{children:`-`}),`;`]}),`
|
|
128
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`/`}),` → `,(0,c.jsx)(t.code,{children:`_`}),`;`]}),`
|
|
129
|
+
`,(0,c.jsxs)(t.li,{children:[`завершающие `,(0,c.jsx)(t.code,{children:`=`}),` удаляются.`]}),`
|
|
130
|
+
`]}),`
|
|
131
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`hashString128Base64Url`}),` возвращает 22 символа из набора:`]}),`
|
|
132
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`A-Z a-z 0-9 - _
|
|
133
|
+
`})}),`
|
|
134
|
+
`,(0,c.jsx)(t.h3,{id:`uuid-v4`,children:`UUID v4`}),`
|
|
135
|
+
`,(0,c.jsx)(t.p,{children:`UUID v4 — случайный идентификатор стандартного формата:`}),`
|
|
136
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
|
137
|
+
`})}),`
|
|
138
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
139
|
+
`,(0,c.jsx)(t.li,{children:`общая длина — 36 символов;`}),`
|
|
140
|
+
`,(0,c.jsx)(t.li,{children:`дефисы находятся в фиксированных позициях;`}),`
|
|
141
|
+
`,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`4`}),` обозначает версию 4;`]}),`
|
|
142
|
+
`,(0,c.jsxs)(t.li,{children:[`первый символ группы `,(0,c.jsx)(t.code,{children:`y`}),` соответствует UUID variant.`]}),`
|
|
143
|
+
`]}),`
|
|
144
|
+
`,(0,c.jsx)(t.p,{children:`Пример:`}),`
|
|
145
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`550e8400-e29b-41d4-a716-446655440000
|
|
146
|
+
`})}),`
|
|
147
|
+
`,(0,c.jsx)(t.h2,{id:`как-выбрать-api`,children:`Как выбрать API`}),`
|
|
148
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Задача`}),(0,c.jsx)(t.th,{children:`API`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Нужен очень короткий non-security hash`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`hashString`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Нужен стабильный длинный hash для сравнения/config fingerprint`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`hashString128`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Нужен компактный URL-safe вариант длинного hash`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`hashString128Base64Url`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Нужен стабильный DOM-like ID по строке`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`stringToElementId`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Нужен новый случайный ID для создаваемой сущности`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`uuidv4`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Нужна связь label/input внутри React-компонента с SSR`}),(0,c.jsxs)(t.td,{children:[`React `,(0,c.jsx)(t.code,{children:`useId`}),`, не этот модуль`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Нужен password hash`}),(0,c.jsx)(t.td,{children:`Специализированный password hasher`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Нужна криптографическая целостность или подпись`}),(0,c.jsx)(t.td,{children:`Web Crypto/серверная crypto-система`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Нужен уникальный business ID с централизованной проверкой`}),(0,c.jsx)(t.td,{children:`Backend/database contract`})]})]})]}),`
|
|
149
|
+
`,(0,c.jsx)(t.h2,{id:`быстрый-старт`,children:`Быстрый старт`}),`
|
|
150
|
+
`,(0,c.jsx)(t.h3,{id:`стабильный-hash`,children:`Стабильный hash`}),`
|
|
151
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { hashString128 } from "@ryuzaki13/react-foundation-lib/crypto";
|
|
152
|
+
|
|
153
|
+
const fingerprint = hashString128("customer:42");
|
|
154
|
+
|
|
155
|
+
console.log(fingerprint); // Одна и та же строка всегда даст тот же результат.
|
|
156
|
+
`})}),`
|
|
157
|
+
`,(0,c.jsx)(t.h3,{id:`стабильный-id-dom-элемента`,children:`Стабильный ID DOM-элемента`}),`
|
|
158
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { stringToElementId } from "@ryuzaki13/react-foundation-lib/crypto";
|
|
159
|
+
|
|
160
|
+
const rowId = stringToElementId("customer:42");
|
|
161
|
+
const cellId = stringToElementId("customer:42:name", "cell");
|
|
162
|
+
`})}),`
|
|
163
|
+
`,(0,c.jsx)(t.p,{children:`Примерный формат:`}),`
|
|
164
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`row-<22 URL-safe символа>
|
|
165
|
+
cell-<22 URL-safe символа>
|
|
166
|
+
`})}),`
|
|
167
|
+
`,(0,c.jsx)(t.h3,{id:`новый-uuid`,children:`Новый UUID`}),`
|
|
168
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { uuidv4 } from "@ryuzaki13/react-foundation-lib/crypto";
|
|
169
|
+
|
|
170
|
+
const draft = {
|
|
171
|
+
id: uuidv4(),
|
|
172
|
+
title: "Новая запись"
|
|
173
|
+
};
|
|
174
|
+
`})}),`
|
|
175
|
+
`,(0,c.jsx)(t.h2,{id:`общие-правила-строковых-hash`,children:`Общие правила строковых hash`}),`
|
|
176
|
+
`,(0,c.jsx)(t.h3,{id:`вход-всегда-строка`,children:`Вход всегда строка`}),`
|
|
177
|
+
`,(0,c.jsxs)(t.p,{children:[`Все hash-функции принимают `,(0,c.jsx)(t.code,{children:`string`}),`:`]}),`
|
|
178
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`hashString128("42"); // Корректно.
|
|
179
|
+
hashString128(42); // Ошибка TypeScript.
|
|
180
|
+
`})}),`
|
|
181
|
+
`,(0,c.jsxs)(t.p,{children:[`Число `,(0,c.jsx)(t.code,{children:`42`}),` и строка `,(0,c.jsx)(t.code,{children:`"42"`}),` — разные типы. Если вы осознанно преобразуете значение, делайте это явно:`]}),`
|
|
182
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`hashString128(String(42));
|
|
183
|
+
`})}),`
|
|
184
|
+
`,(0,c.jsx)(t.h3,{id:`обрабатывается-точная-последовательность-utf-16-code-units`,children:`Обрабатывается точная последовательность UTF-16 code units`}),`
|
|
185
|
+
`,(0,c.jsxs)(t.p,{children:[`JavaScript хранит строки как последовательности UTF-16 code units. Реализация проходит строку через `,(0,c.jsx)(t.code,{children:`charCodeAt`}),`.`]}),`
|
|
186
|
+
`,(0,c.jsx)(t.p,{children:`Поэтому имеют значение:`}),`
|
|
187
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
188
|
+
`,(0,c.jsx)(t.li,{children:`регистр букв;`}),`
|
|
189
|
+
`,(0,c.jsx)(t.li,{children:`пробелы и переводы строк;`}),`
|
|
190
|
+
`,(0,c.jsx)(t.li,{children:`порядок символов;`}),`
|
|
191
|
+
`,(0,c.jsx)(t.li,{children:`composed/decomposed форма Unicode;`}),`
|
|
192
|
+
`,(0,c.jsx)(t.li,{children:`каждый code unit surrogate pair.`}),`
|
|
193
|
+
`]}),`
|
|
194
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`hashString128("User") !== hashString128("user");
|
|
195
|
+
hashString128("A") !== hashString128(" A ");
|
|
196
|
+
`})}),`
|
|
197
|
+
`,(0,c.jsx)(t.h3,{id:`unicode-не-нормализуется`,children:`Unicode не нормализуется`}),`
|
|
198
|
+
`,(0,c.jsx)(t.p,{children:`Визуально одинаковые строки могут иметь разное внутреннее представление:`}),`
|
|
199
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const composed = "Café";
|
|
200
|
+
const decomposed = "Cafe\\u0301";
|
|
201
|
+
|
|
202
|
+
console.log(composed === decomposed); // false
|
|
203
|
+
console.log(hashString128(composed) === hashString128(decomposed)); // false
|
|
204
|
+
`})}),`
|
|
205
|
+
`,(0,c.jsx)(t.p,{children:`Если доменная модель считает их одинаковыми, нормализуйте строку на явно выбранной boundary:`}),`
|
|
206
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const canonical = input.normalize("NFC");
|
|
207
|
+
const hash = hashString128(canonical);
|
|
208
|
+
`})}),`
|
|
209
|
+
`,(0,c.jsx)(t.p,{children:`Если буквальная форма важна, не нормализуйте её.`}),`
|
|
210
|
+
`,(0,c.jsx)(t.h3,{id:`пустая-строка-допустима`,children:`Пустая строка допустима`}),`
|
|
211
|
+
`,(0,c.jsx)(t.p,{children:`Функции не отклоняют пустой вход:`}),`
|
|
212
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`hashString("");
|
|
213
|
+
hashString128("");
|
|
214
|
+
hashString128Base64Url("");
|
|
215
|
+
`})}),`
|
|
216
|
+
`,(0,c.jsx)(t.p,{children:`Пустая строка имеет свой стабильный hash. Проверять обязательность входа должен вызывающий код.`}),`
|
|
217
|
+
`,(0,c.jsx)(t.h3,{id:`разные-hash-не-означают-разную-силу-защиты`,children:`Разные hash не означают разную силу защиты`}),`
|
|
218
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`hashString128`}),` и `,(0,c.jsx)(t.code,{children:`hashString128Base64Url`}),` кодируют один и тот же внутренний результат разными способами. Base64Url-версия короче, но не безопаснее и не слабее hex-версии с точки зрения коллизий.`]}),`
|
|
219
|
+
`,(0,c.jsx)(t.h2,{id:`hashstring`,children:(0,c.jsx)(t.code,{children:`hashString`})}),`
|
|
220
|
+
`,(0,c.jsx)(t.p,{children:`Возвращает компактный FNV-1a-like 32-bit hash в base36.`}),`
|
|
221
|
+
`,(0,c.jsx)(t.h3,{id:`сигнатура`,children:`Сигнатура`}),`
|
|
222
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare function hashString(input: string): string;
|
|
223
|
+
`})}),`
|
|
224
|
+
`,(0,c.jsx)(t.h3,{id:`формат-результата`,children:`Формат результата`}),`
|
|
225
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
226
|
+
`,(0,c.jsxs)(t.li,{children:[`только символы `,(0,c.jsx)(t.code,{children:`0-9`}),` и `,(0,c.jsx)(t.code,{children:`a-z`}),`;`]}),`
|
|
227
|
+
`,(0,c.jsx)(t.li,{children:`lowercase;`}),`
|
|
228
|
+
`,(0,c.jsx)(t.li,{children:`переменная длина от 1 до 7 символов;`}),`
|
|
229
|
+
`,(0,c.jsx)(t.li,{children:`результат соответствует unsigned 32-bit значению, записанному в base36.`}),`
|
|
230
|
+
`]}),`
|
|
231
|
+
`,(0,c.jsx)(t.h3,{id:`примеры-текущей-реализации`,children:`Примеры текущей реализации`}),`
|
|
232
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`hashString(""); // "ztntfp"
|
|
233
|
+
hashString("user:42"); // "d5nw5e"
|
|
234
|
+
hashString("строка"); // "hb03a2"
|
|
235
|
+
`})}),`
|
|
236
|
+
`,(0,c.jsx)(t.h3,{id:`когда-использовать`,children:`Когда использовать`}),`
|
|
237
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
238
|
+
`,(0,c.jsx)(t.li,{children:`компактный ключ небольшой UI-коллекции;`}),`
|
|
239
|
+
`,(0,c.jsx)(t.li,{children:`некритичный short fingerprint;`}),`
|
|
240
|
+
`,(0,c.jsx)(t.li,{children:`bucket/group key, где коллизия дополнительно обрабатывается;`}),`
|
|
241
|
+
`,(0,c.jsx)(t.li,{children:`диагностическая метка, не являющаяся источником истины.`}),`
|
|
242
|
+
`]}),`
|
|
243
|
+
`,(0,c.jsx)(t.h3,{id:`когда-не-использовать`,children:`Когда не использовать`}),`
|
|
244
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
245
|
+
`,(0,c.jsx)(t.li,{children:`primary key;`}),`
|
|
246
|
+
`,(0,c.jsx)(t.li,{children:`проверка уникальности большого набора;`}),`
|
|
247
|
+
`,(0,c.jsx)(t.li,{children:`security decision;`}),`
|
|
248
|
+
`,(0,c.jsx)(t.li,{children:`контроль целостности данных;`}),`
|
|
249
|
+
`,(0,c.jsx)(t.li,{children:`password/token/signature;`}),`
|
|
250
|
+
`,(0,c.jsx)(t.li,{children:`ситуация, где коллизия приводит к потере или перезаписи данных.`}),`
|
|
251
|
+
`]}),`
|
|
252
|
+
`,(0,c.jsx)(t.p,{children:`32-bit пространство сравнительно мало. Не делайте вывод, что разные входы обязательно имеют разные результаты.`}),`
|
|
253
|
+
`,(0,c.jsx)(t.h2,{id:`hashstring128`,children:(0,c.jsx)(t.code,{children:`hashString128`})}),`
|
|
254
|
+
`,(0,c.jsx)(t.p,{children:`Возвращает длинный non-cryptographic hash в hexadecimal формате.`}),`
|
|
255
|
+
`,(0,c.jsx)(t.h3,{id:`сигнатура-1`,children:`Сигнатура`}),`
|
|
256
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare function hashString128(input: string): string;
|
|
257
|
+
`})}),`
|
|
258
|
+
`,(0,c.jsx)(t.h3,{id:`формат-результата-1`,children:`Формат результата`}),`
|
|
259
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
260
|
+
`,(0,c.jsx)(t.li,{children:`ровно 32 символа;`}),`
|
|
261
|
+
`,(0,c.jsxs)(t.li,{children:[`только `,(0,c.jsx)(t.code,{children:`0-9`}),` и `,(0,c.jsx)(t.code,{children:`a-f`}),`;`]}),`
|
|
262
|
+
`,(0,c.jsx)(t.li,{children:`lowercase;`}),`
|
|
263
|
+
`,(0,c.jsx)(t.li,{children:`четыре внутренних unsigned 32-bit слова записываются по 8 hex-символов.`}),`
|
|
264
|
+
`]}),`
|
|
265
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = hashString128("user:42");
|
|
266
|
+
|
|
267
|
+
console.log(result); // "fa3326027b653e8049d56072c88378f0"
|
|
268
|
+
console.log(result.length); // 32
|
|
269
|
+
`})}),`
|
|
270
|
+
`,(0,c.jsx)(t.h3,{id:`когда-использовать-1`,children:`Когда использовать`}),`
|
|
271
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
272
|
+
`,(0,c.jsx)(t.li,{children:`fingerprint канонической строки конфигурации;`}),`
|
|
273
|
+
`,(0,c.jsx)(t.li,{children:`стабильный cache-like key;`}),`
|
|
274
|
+
`,(0,c.jsx)(t.li,{children:`сравнение версий данных с допустимым non-adversarial collision risk;`}),`
|
|
275
|
+
`,(0,c.jsx)(t.li,{children:`стабильная техническая метка, для которой 32-bit hash слишком короток.`}),`
|
|
276
|
+
`]}),`
|
|
277
|
+
`,(0,c.jsx)(t.h3,{id:`важное-ограничение`,children:`Важное ограничение`}),`
|
|
278
|
+
`,(0,c.jsxs)(t.p,{children:[`Название `,(0,c.jsx)(t.code,{children:`128`}),` описывает формат из 128 output bits, а не криптографическую стойкость. Внутри используется собственное быстрое смешивание четырёх 32-bit состояний. Не приравнивайте функцию к MD5, SHA-256 или другому cryptographic digest.`]}),`
|
|
279
|
+
`,(0,c.jsx)(t.h2,{id:`hashstring128base64url`,children:(0,c.jsx)(t.code,{children:`hashString128Base64Url`})}),`
|
|
280
|
+
`,(0,c.jsx)(t.p,{children:`Возвращает тот же длинный внутренний hash в более компактной URL-safe форме.`}),`
|
|
281
|
+
`,(0,c.jsx)(t.h3,{id:`сигнатура-2`,children:`Сигнатура`}),`
|
|
282
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare function hashString128Base64Url(input: string): string;
|
|
283
|
+
`})}),`
|
|
284
|
+
`,(0,c.jsx)(t.h3,{id:`формат-результата-2`,children:`Формат результата`}),`
|
|
285
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
286
|
+
`,(0,c.jsx)(t.li,{children:`ровно 22 символа;`}),`
|
|
287
|
+
`,(0,c.jsxs)(t.li,{children:[`символы `,(0,c.jsx)(t.code,{children:`A-Z`}),`, `,(0,c.jsx)(t.code,{children:`a-z`}),`, `,(0,c.jsx)(t.code,{children:`0-9`}),`, `,(0,c.jsx)(t.code,{children:`-`}),`, `,(0,c.jsx)(t.code,{children:`_`}),`;`]}),`
|
|
288
|
+
`,(0,c.jsxs)(t.li,{children:[`без `,(0,c.jsx)(t.code,{children:`+`}),`, `,(0,c.jsx)(t.code,{children:`/`}),` и padding `,(0,c.jsx)(t.code,{children:`=`}),`;`]}),`
|
|
289
|
+
`,(0,c.jsx)(t.li,{children:`регистр имеет значение.`}),`
|
|
290
|
+
`]}),`
|
|
291
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const result = hashString128Base64Url("user:42");
|
|
292
|
+
|
|
293
|
+
console.log(result); // "-jMmAntlPoBJ1WByyIN48A"
|
|
294
|
+
console.log(result.length); // 22
|
|
295
|
+
`})}),`
|
|
296
|
+
`,(0,c.jsxs)(t.p,{children:[`Результат может начинаться с `,(0,c.jsx)(t.code,{children:`-`}),` или `,(0,c.jsx)(t.code,{children:`_`}),`. Это допустимая часть Base64Url-алфавита.`]}),`
|
|
297
|
+
`,(0,c.jsx)(t.h3,{id:`когда-использовать-2`,children:`Когда использовать`}),`
|
|
298
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
299
|
+
`,(0,c.jsx)(t.li,{children:`segment технического URL;`}),`
|
|
300
|
+
`,(0,c.jsx)(t.li,{children:`компактный ключ для browser storage;`}),`
|
|
301
|
+
`,(0,c.jsx)(t.li,{children:`DOM-like ID вместе с безопасным префиксом;`}),`
|
|
302
|
+
`,(0,c.jsx)(t.li,{children:`data attribute;`}),`
|
|
303
|
+
`,(0,c.jsxs)(t.li,{children:[`короткое представление того же результата, который можно было записать через `,(0,c.jsx)(t.code,{children:`hashString128`}),`.`]}),`
|
|
304
|
+
`]}),`
|
|
305
|
+
`,(0,c.jsx)(t.h3,{id:`runtime`,children:`Runtime`}),`
|
|
306
|
+
`,(0,c.jsxs)(t.p,{children:[`Функция преобразует 16 байтов через глобальный `,(0,c.jsx)(t.code,{children:`btoa`}),`. В среде без `,(0,c.jsx)(t.code,{children:`btoa`}),` будет выброшена ошибка. Функция не содержит fallback и не перехватывает её.`]}),`
|
|
307
|
+
`,(0,c.jsx)(t.h3,{id:`это-не-кодирование-исходной-строки`,children:`Это не кодирование исходной строки`}),`
|
|
308
|
+
`,(0,c.jsxs)(t.p,{children:[`Base64Url здесь кодирует байты hash, а не исходный `,(0,c.jsx)(t.code,{children:`input`}),`. Декодирование результата вернёт 16 байтов hash и не восстановит исходную строку.`]}),`
|
|
309
|
+
`,(0,c.jsx)(t.h2,{id:`stringtoelementid`,children:(0,c.jsx)(t.code,{children:`stringToElementId`})}),`
|
|
310
|
+
`,(0,c.jsxs)(t.p,{children:[`Добавляет префикс к результату `,(0,c.jsx)(t.code,{children:`hashString128Base64Url`}),`.`]}),`
|
|
311
|
+
`,(0,c.jsx)(t.h3,{id:`сигнатура-3`,children:`Сигнатура`}),`
|
|
312
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare function stringToElementId(input: string, prefix?: string): string;
|
|
313
|
+
`})}),`
|
|
314
|
+
`,(0,c.jsx)(t.h3,{id:`параметры`,children:`Параметры`}),`
|
|
315
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Параметр`}),(0,c.jsx)(t.th,{children:`Тип`}),(0,c.jsx)(t.th,{children:`По умолчанию`}),(0,c.jsx)(t.th,{children:`Назначение`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`input`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`string`})}),(0,c.jsx)(t.td,{children:`—`}),(0,c.jsx)(t.td,{children:`Строка, определяющая стабильную часть ID.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`prefix`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`string`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"row"`})}),(0,c.jsx)(t.td,{children:`Читаемая часть перед hash.`})]})]})]}),`
|
|
316
|
+
`,(0,c.jsx)(t.h3,{id:`формула-результата`,children:`Формула результата`}),`
|
|
317
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:"`${prefix}-${hashString128Base64Url(input)}`;\n"})}),`
|
|
318
|
+
`,(0,c.jsx)(t.h3,{id:`примеры-текущей-реализации-1`,children:`Примеры текущей реализации`}),`
|
|
319
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`stringToElementId("user:42");
|
|
320
|
+
// "row--jMmAntlPoBJ1WByyIN48A"
|
|
321
|
+
|
|
322
|
+
stringToElementId("user:42", "cell");
|
|
323
|
+
// "cell--jMmAntlPoBJ1WByyIN48A"
|
|
324
|
+
`})}),`
|
|
325
|
+
`,(0,c.jsx)(t.p,{children:`Двойной дефис в примере корректен: первый добавлен между prefix и hash, второй является первым символом Base64Url hash.`}),`
|
|
326
|
+
`,(0,c.jsx)(t.h3,{id:`стабильность`,children:`Стабильность`}),`
|
|
327
|
+
`,(0,c.jsxs)(t.p,{children:[`Одинаковые `,(0,c.jsx)(t.code,{children:`input`}),` и `,(0,c.jsx)(t.code,{children:`prefix`}),` дают одинаковый результат:`]}),`
|
|
328
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`stringToElementId("row:42", "cell") === stringToElementId("row:42", "cell"); // true
|
|
329
|
+
`})}),`
|
|
330
|
+
`,(0,c.jsx)(t.p,{children:`Разные префиксы разводят namespaces:`}),`
|
|
331
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`stringToElementId("42", "row");
|
|
332
|
+
stringToElementId("42", "cell");
|
|
333
|
+
`})}),`
|
|
334
|
+
`,(0,c.jsx)(t.h3,{id:`prefix-не-очищается`,children:`Prefix не очищается`}),`
|
|
335
|
+
`,(0,c.jsxs)(t.p,{children:[`Функция вставляет `,(0,c.jsx)(t.code,{children:`prefix`}),` буквально. Она не удаляет пробелы и не экранирует CSS:`]}),`
|
|
336
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`stringToElementId("42", "my row"); // Содержит пробел в ID.
|
|
337
|
+
stringToElementId("42", ""); // Начинается с дефиса.
|
|
338
|
+
`})}),`
|
|
339
|
+
`,(0,c.jsxs)(t.p,{children:[`Используйте короткий контролируемый prefix из букв, цифр, `,(0,c.jsx)(t.code,{children:`_`}),` или `,(0,c.jsx)(t.code,{children:`-`}),`. Не передавайте произвольный пользовательский ввод как prefix.`]}),`
|
|
340
|
+
`,(0,c.jsx)(t.h3,{id:`поиск-элемента`,children:`Поиск элемента`}),`
|
|
341
|
+
`,(0,c.jsx)(t.p,{children:`При default или безопасном prefix можно использовать:`}),`
|
|
342
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const id = stringToElementId("row:42");
|
|
343
|
+
const element = document.getElementById(id);
|
|
344
|
+
`})}),`
|
|
345
|
+
`,(0,c.jsx)(t.p,{children:`Если ID участвует в CSS selector и его состав не полностью контролируется, экранируйте значение:`}),`
|
|
346
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:"const element = document.querySelector(`#${CSS.escape(id)}`);\n"})}),`
|
|
347
|
+
`,(0,c.jsxs)(t.h3,{id:`когда-выбрать-react-useid`,children:[`Когда выбрать React `,(0,c.jsx)(t.code,{children:`useId`})]}),`
|
|
348
|
+
`,(0,c.jsxs)(t.p,{children:[`Для связи `,(0,c.jsx)(t.code,{children:`label`}),`, `,(0,c.jsx)(t.code,{children:`input`}),`, `,(0,c.jsx)(t.code,{children:`aria-describedby`}),` и других элементов внутри React-компонента обычно лучше `,(0,c.jsx)(t.code,{children:`useId`}),`:`]}),`
|
|
349
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const id = useId();
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<>
|
|
353
|
+
<label htmlFor={id}>Имя</label>
|
|
354
|
+
<input id={id} />
|
|
355
|
+
</>
|
|
356
|
+
);
|
|
357
|
+
`})}),`
|
|
358
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`useId`}),` согласован с React SSR и hydration. `,(0,c.jsx)(t.code,{children:`stringToElementId`}),` нужен, когда ID должен определяться внешним стабильным business/technical key, одинаковым в разных местах.`]}),`
|
|
359
|
+
`,(0,c.jsx)(t.h3,{id:`коллизия-всё-ещё-возможна`,children:`Коллизия всё ещё возможна`}),`
|
|
360
|
+
`,(0,c.jsx)(t.p,{children:`Добавление prefix не делает hash уникальным. Если два input получили одинаковый hash и prefix совпадает, ID тоже совпадёт.`}),`
|
|
361
|
+
`,(0,c.jsx)(t.h2,{id:`uuidv4`,children:(0,c.jsx)(t.code,{children:`uuidv4`})}),`
|
|
362
|
+
`,(0,c.jsx)(t.p,{children:`Создаёт случайный UUID версии 4 через глобальный Web Crypto API.`}),`
|
|
363
|
+
`,(0,c.jsx)(t.h3,{id:`сигнатура-4`,children:`Сигнатура`}),`
|
|
364
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare function uuidv4(): string;
|
|
365
|
+
`})}),`
|
|
366
|
+
`,(0,c.jsx)(t.h3,{id:`алгоритм-выбора`,children:`Алгоритм выбора`}),`
|
|
367
|
+
`,(0,c.jsxs)(t.ol,{children:[`
|
|
368
|
+
`,(0,c.jsxs)(t.li,{children:[`Если `,(0,c.jsx)(t.code,{children:`crypto.randomUUID`}),` доступен, функция сразу возвращает его результат.`]}),`
|
|
369
|
+
`,(0,c.jsxs)(t.li,{children:[`Иначе создаёт `,(0,c.jsx)(t.code,{children:`Uint8Array(16)`}),`.`]}),`
|
|
370
|
+
`,(0,c.jsxs)(t.li,{children:[`Заполняет его через `,(0,c.jsx)(t.code,{children:`crypto.getRandomValues`}),`.`]}),`
|
|
371
|
+
`,(0,c.jsx)(t.li,{children:`Устанавливает version bits для UUID v4.`}),`
|
|
372
|
+
`,(0,c.jsx)(t.li,{children:`Устанавливает variant bits.`}),`
|
|
373
|
+
`,(0,c.jsxs)(t.li,{children:[`Форматирует байты как lowercase строку `,(0,c.jsx)(t.code,{children:`8-4-4-4-12`}),`.`]}),`
|
|
374
|
+
`]}),`
|
|
375
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`xxxxxxxx-xxxx-4xxx-[89ab]xxx-xxxxxxxxxxxx
|
|
376
|
+
`})}),`
|
|
377
|
+
`,(0,c.jsx)(t.h3,{id:`пример-создания-сущности`,children:`Пример создания сущности`}),`
|
|
378
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type Draft = {
|
|
379
|
+
id: string;
|
|
380
|
+
title: string;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
export function createDraft(title: string): Draft {
|
|
384
|
+
return {
|
|
385
|
+
id: uuidv4(),
|
|
386
|
+
title
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
`})}),`
|
|
390
|
+
`,(0,c.jsx)(t.h3,{id:`функция-не-проверяет-результат-native-api`,children:`Функция не проверяет результат native API`}),`
|
|
391
|
+
`,(0,c.jsxs)(t.p,{children:[`Если существует `,(0,c.jsx)(t.code,{children:`crypto.randomUUID`}),`, его результат возвращается как есть. Дополнительная проверка UUID-формата не выполняется. В обычном браузере Web Crypto соблюдает контракт; в тестовом mock ответственность лежит на тесте.`]}),`
|
|
392
|
+
`,(0,c.jsx)(t.h3,{id:`ошибки-не-перехватываются`,children:`Ошибки не перехватываются`}),`
|
|
393
|
+
`,(0,c.jsxs)(t.p,{children:[`Если глобальный `,(0,c.jsx)(t.code,{children:`crypto`}),` отсутствует либо недоступны и `,(0,c.jsx)(t.code,{children:`randomUUID`}),`, и `,(0,c.jsx)(t.code,{children:`getRandomValues`}),`, функция выбросит runtime error. Она не имеет Math.random fallback и не возвращает `,(0,c.jsx)(t.code,{children:`null`}),`.`]}),`
|
|
394
|
+
`,(0,c.jsx)(t.p,{children:`Это правильная граница: слабая псевдослучайность не подменяет Web Crypto незаметно.`}),`
|
|
395
|
+
`,(0,c.jsx)(t.h3,{id:`uuid-не-гарантируется-базой-данных`,children:`UUID не гарантируется базой данных`}),`
|
|
396
|
+
`,(0,c.jsx)(t.p,{children:`Вероятность случайного совпадения очень мала, но математическая гарантия отсутствует. Для authoritative storage всё равно используйте unique constraint и корректно обрабатывайте конфликт.`}),`
|
|
397
|
+
`,(0,c.jsx)(t.h3,{id:`не-обрезайте-uuid-без-оценки-риска`,children:`Не обрезайте UUID без оценки риска`}),`
|
|
398
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`uuidv4().slice(0, 8);
|
|
399
|
+
`})}),`
|
|
400
|
+
`,(0,c.jsx)(t.p,{children:`Такой результат содержит только небольшую часть UUID и резко уменьшает пространство вариантов. Это может быть приемлемо для временной UI-метки, но не для долговечного уникального ключа.`}),`
|
|
401
|
+
`,(0,c.jsx)(t.h3,{id:`uuid-не-является-автоматически-security-token`,children:`UUID не является автоматически security token`}),`
|
|
402
|
+
`,(0,c.jsx)(t.p,{children:`UUID v4 создаётся из криптографической случайности, но его назначение — идентификация. Требования к session token, reset link или access credential включают не только случайность, но также хранение, срок жизни, отзыв, transport и threat model. Используйте контракт системы безопасности, а не предположение «UUID выглядит случайным».`}),`
|
|
403
|
+
`,(0,c.jsx)(t.h2,{id:`подготовка-сложных-данных`,children:`Подготовка сложных данных`}),`
|
|
404
|
+
`,(0,c.jsx)(t.h3,{id:`объект-нельзя-передать-напрямую`,children:`Объект нельзя передать напрямую`}),`
|
|
405
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const config = { page: 1, sort: "name" };
|
|
406
|
+
|
|
407
|
+
hashString128(config); // Ошибка TypeScript: ожидается string.
|
|
408
|
+
`})}),`
|
|
409
|
+
`,(0,c.jsx)(t.p,{children:`Сначала нужен детерминированный serializer.`}),`
|
|
410
|
+
`,(0,c.jsxs)(t.h3,{id:`почему-обычный-jsonstringify-может-быть-недостаточен`,children:[`Почему обычный `,(0,c.jsx)(t.code,{children:`JSON.stringify`}),` может быть недостаточен`]}),`
|
|
411
|
+
`,(0,c.jsx)(t.p,{children:`Порядок object keys может различаться:`}),`
|
|
412
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const first = { page: 1, sort: "name" };
|
|
413
|
+
const second = { sort: "name", page: 1 };
|
|
414
|
+
|
|
415
|
+
JSON.stringify(first) !== JSON.stringify(second);
|
|
416
|
+
`})}),`
|
|
417
|
+
`,(0,c.jsxs)(t.p,{children:[`Если модель считает эти объекты одинаковыми, используйте каноническое представление. В foundation-пакете для JSON-like технических значений есть `,(0,c.jsx)(t.code,{children:`stableStringify`}),`:`]}),`
|
|
418
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { hashString128 } from "@ryuzaki13/react-foundation-lib/crypto";
|
|
419
|
+
import { stableStringify } from "@ryuzaki13/react-foundation-lib/utils";
|
|
420
|
+
|
|
421
|
+
const fingerprint = hashString128(stableStringify(config));
|
|
422
|
+
`})}),`
|
|
423
|
+
`,(0,c.jsx)(t.p,{children:`Перед использованием определите доменные правила:`}),`
|
|
424
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
425
|
+
`,(0,c.jsx)(t.li,{children:`важен ли порядок массивов;`}),`
|
|
426
|
+
`,(0,c.jsx)(t.li,{children:`нужно ли сохранять внешний whitespace;`}),`
|
|
427
|
+
`,(0,c.jsx)(t.li,{children:`одинаковы ли uppercase/lowercase;`}),`
|
|
428
|
+
`,(0,c.jsx)(t.li,{children:`как представляются даты;`}),`
|
|
429
|
+
`,(0,c.jsxs)(t.li,{children:[`допустимы ли `,(0,c.jsx)(t.code,{children:`undefined`}),`, функции или циклические ссылки;`]}),`
|
|
430
|
+
`,(0,c.jsx)(t.li,{children:`на какой boundary выполняется нормализация.`}),`
|
|
431
|
+
`]}),`
|
|
432
|
+
`,(0,c.jsx)(t.p,{children:`Hash не исправляет неоднозначную сериализацию. Он только детерминированно обрабатывает полученную строку.`}),`
|
|
433
|
+
`,(0,c.jsx)(t.h3,{id:`составной-строковый-ключ`,children:`Составной строковый ключ`}),`
|
|
434
|
+
`,(0,c.jsx)(t.p,{children:`Простое объединение может быть неоднозначным:`}),`
|
|
435
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:"`${first}:${second}`;\n"})}),`
|
|
436
|
+
`,(0,c.jsxs)(t.p,{children:[`Если значения сами содержат `,(0,c.jsx)(t.code,{children:`:`}),`, разные пары способны сформировать одинаковую строку. Используйте однозначную serialization scheme:`]}),`
|
|
437
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const canonical = stableStringify([first, second]);
|
|
438
|
+
const key = hashString128Base64Url(canonical);
|
|
439
|
+
`})}),`
|
|
440
|
+
`,(0,c.jsx)(t.h2,{id:`безопасность`,children:`Безопасность`}),`
|
|
441
|
+
`,(0,c.jsx)(t.h3,{id:`матрица-допустимости`,children:`Матрица допустимости`}),`
|
|
442
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Задача`}),(0,c.jsx)(t.th,{children:`Подходит ли модуль`}),(0,c.jsx)(t.th,{children:`Почему`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`UI/cache fingerprint некритичных данных`}),(0,c.jsx)(t.td,{children:`Да`}),(0,c.jsx)(t.td,{children:`Быстро и детерминированно.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Stable DOM-like ID`}),(0,c.jsx)(t.td,{children:`Да, с учётом коллизий`}),(0,c.jsx)(t.td,{children:`Есть компактный URL-safe формат и prefix.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Случайный ID новой сущности`}),(0,c.jsx)(t.td,{children:`Да`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`uuidv4`}),` использует Web Crypto.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Проверка пароля`}),(0,c.jsx)(t.td,{children:`Нет`}),(0,c.jsx)(t.td,{children:`Нужен slow password hashing с salt и политикой параметров.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Подпись запроса`}),(0,c.jsx)(t.td,{children:`Нет`}),(0,c.jsx)(t.td,{children:`Нужен keyed cryptographic MAC/signature.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Проверка загруженного файла по trusted digest`}),(0,c.jsx)(t.td,{children:`Нет`}),(0,c.jsx)(t.td,{children:`Нужен cryptographic digest и доверенный expected value.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Сокрытие email/телефона`}),(0,c.jsx)(t.td,{children:`Нет`}),(0,c.jsx)(t.td,{children:`Предсказуемый вход можно подобрать перебором.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Access token`}),(0,c.jsx)(t.td,{children:`Не автоматически`}),(0,c.jsx)(t.td,{children:`UUID не заменяет полный security contract.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Уникальность записи в базе`}),(0,c.jsx)(t.td,{children:`Недостаточно`}),(0,c.jsx)(t.td,{children:`Нужен unique constraint/authoritative allocation.`})]})]})]}),`
|
|
443
|
+
`,(0,c.jsx)(t.h3,{id:`не-принимайте-security-decision-по-этим-hash`,children:`Не принимайте security decision по этим hash`}),`
|
|
444
|
+
`,(0,c.jsx)(t.p,{children:`Нельзя предоставлять доступ, считать payload доверенным или пропускать validation только потому, что hash совпал. Non-cryptographic hash не защищён от намеренно подобранных коллизий.`}),`
|
|
445
|
+
`,(0,c.jsx)(t.h3,{id:`не-храните-hash-как-доказательство-исходного-секрета`,children:`Не храните hash как доказательство исходного секрета`}),`
|
|
446
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`hashString128(password);
|
|
447
|
+
`})}),`
|
|
448
|
+
`,(0,c.jsx)(t.p,{children:`Так хранить пароль нельзя. Функция быстрая, не использует salt и специально не предназначена для сопротивления перебору.`}),`
|
|
449
|
+
`,(0,c.jsx)(t.h3,{id:`не-путайте-base64url-с-безопасностью`,children:`Не путайте Base64Url с безопасностью`}),`
|
|
450
|
+
`,(0,c.jsx)(t.p,{children:`Base64Url — только формат записи байтов. Он не шифрует и не подписывает значение.`}),`
|
|
451
|
+
`,(0,c.jsx)(t.h2,{id:`коллизии-и-обрезание-результата`,children:`Коллизии и обрезание результата`}),`
|
|
452
|
+
`,(0,c.jsx)(t.h3,{id:`что-гарантирует-детерминированность`,children:`Что гарантирует детерминированность`}),`
|
|
453
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
454
|
+
`,(0,c.jsx)(t.li,{children:`одинаковая точная строка даёт одинаковый hash;`}),`
|
|
455
|
+
`,(0,c.jsx)(t.li,{children:`если два hash различаются, исходные строки точно различались;`}),`
|
|
456
|
+
`,(0,c.jsx)(t.li,{children:`если два hash совпали, исходные строки могли быть одинаковыми или попасть в коллизию.`}),`
|
|
457
|
+
`]}),`
|
|
458
|
+
`,(0,c.jsx)(t.p,{children:`Последний пункт особенно важен: совпадение hash не доказывает равенство исходных данных.`}),`
|
|
459
|
+
`,(0,c.jsx)(t.h3,{id:`сохраняйте-исходный-ключ-если-ошибка-недопустима`,children:`Сохраняйте исходный ключ, если ошибка недопустима`}),`
|
|
460
|
+
`,(0,c.jsx)(t.p,{children:`Для map/cache можно хранить bucket по hash, а внутри дополнительно сравнивать каноническую строку:`}),`
|
|
461
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type Entry<T> = {
|
|
462
|
+
canonical: string;
|
|
463
|
+
value: T;
|
|
464
|
+
};
|
|
465
|
+
`})}),`
|
|
466
|
+
`,(0,c.jsx)(t.p,{children:`Так коллизия hash не смешает разные данные.`}),`
|
|
467
|
+
`,(0,c.jsx)(t.h3,{id:`не-обрезайте-длинный-hash-без-причины`,children:`Не обрезайте длинный hash без причины`}),`
|
|
468
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`hashString128Base64Url(input).slice(0, 8);
|
|
469
|
+
`})}),`
|
|
470
|
+
`,(0,c.jsx)(t.p,{children:`Обрезание уменьшает число возможных результатов и повышает вероятность коллизии. Если короткая форма обязательна, оцените размер набора, последствия совпадения и наличие secondary equality check.`}),`
|
|
471
|
+
`,(0,c.jsx)(t.h3,{id:`hex-и-base64url-имеют-одинаковую-collision-policy`,children:`Hex и Base64Url имеют одинаковую collision policy`}),`
|
|
472
|
+
`,(0,c.jsx)(t.p,{children:`32 hex-символа и 22 Base64Url-символа представляют один внутренний результат. Выбирайте по требованиям к формату, а не по предполагаемой «надёжности».`}),`
|
|
473
|
+
`,(0,c.jsx)(t.h3,{id:`версионируйте-долговечный-fingerprint`,children:`Версионируйте долговечный fingerprint`}),`
|
|
474
|
+
`,(0,c.jsx)(t.p,{children:`Если результат сохраняется в БД, URL или файле на годы, алгоритм становится частью формата данных. Добавьте версию схемы:`}),`
|
|
475
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:"const fingerprint = `v1:${hashString128(canonical)}`;\n"})}),`
|
|
476
|
+
`,(0,c.jsxs)(t.p,{children:[`Тогда будущая замена алгоритма сможет использовать `,(0,c.jsx)(t.code,{children:`v2:`}),` и явную миграцию. Не предполагайте, что внутренний алгоритм любой utility-функции никогда не изменится между major versions.`]}),`
|
|
477
|
+
`,(0,c.jsx)(t.h2,{id:`browser-nodejs-и-ssr`,children:`Browser, Node.js и SSR`}),`
|
|
478
|
+
`,(0,c.jsx)(t.h3,{id:`чистые-строковые-варианты`,children:`Чистые строковые варианты`}),`
|
|
479
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`hashString`}),` и `,(0,c.jsx)(t.code,{children:`hashString128`}),` используют стандартные операции JavaScript и не обращаются к DOM или Web Crypto.`]}),`
|
|
480
|
+
`,(0,c.jsxs)(t.h3,{id:`btoa-boundary`,children:[(0,c.jsx)(t.code,{children:`btoa`}),` boundary`]}),`
|
|
481
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`hashString128Base64Url`}),` и `,(0,c.jsx)(t.code,{children:`stringToElementId`}),` используют глобальный `,(0,c.jsx)(t.code,{children:`btoa`}),`. В browser он обычно доступен. В server/test runtime его наличие нужно проверить или предоставить совместимый polyfill на уровне среды.`]}),`
|
|
482
|
+
`,(0,c.jsx)(t.p,{children:`Не добавляйте случайный локальный encoder с другим byte order: server и client должны выдавать одинаковый результат.`}),`
|
|
483
|
+
`,(0,c.jsx)(t.h3,{id:`web-crypto-boundary`,children:`Web Crypto boundary`}),`
|
|
484
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`uuidv4`}),` обращается к глобальному `,(0,c.jsx)(t.code,{children:`crypto`}),`. В runtime без Web Crypto функция завершится ошибкой.`]}),`
|
|
485
|
+
`,(0,c.jsx)(t.h3,{id:`hydration`,children:`Hydration`}),`
|
|
486
|
+
`,(0,c.jsxs)(t.p,{children:[`Не вызывайте `,(0,c.jsx)(t.code,{children:`uuidv4()`}),` прямо во время server и client render одного и того же компонента:`]}),`
|
|
487
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`// Неправильно для SSR и нестабильно при повторных рендерах.
|
|
488
|
+
const id = uuidv4();
|
|
489
|
+
`})}),`
|
|
490
|
+
`,(0,c.jsx)(t.p,{children:`Server и client создадут разные строки, а каждый render создаст новый UUID.`}),`
|
|
491
|
+
`,(0,c.jsxs)(t.p,{children:[`Для DOM/ARIA ID используйте `,(0,c.jsx)(t.code,{children:`useId`}),`. Для ID новой domain entity вызывайте `,(0,c.jsx)(t.code,{children:`uuidv4`}),` в момент пользовательской команды или на authoritative creation boundary.`]}),`
|
|
492
|
+
`,(0,c.jsxs)(t.p,{children:[`Детерминированный `,(0,c.jsx)(t.code,{children:`stringToElementId`}),` может совпасть на server и client только если:`]}),`
|
|
493
|
+
`,(0,c.jsxs)(t.ul,{children:[`
|
|
494
|
+
`,(0,c.jsx)(t.li,{children:`обе стороны получили совершенно одинаковый input и prefix;`}),`
|
|
495
|
+
`,(0,c.jsx)(t.li,{children:`обе стороны используют ту же версию алгоритма;`}),`
|
|
496
|
+
`,(0,c.jsxs)(t.li,{children:[`обе среды поддерживают нужный `,(0,c.jsx)(t.code,{children:`btoa`}),` contract.`]}),`
|
|
497
|
+
`]}),`
|
|
498
|
+
`,(0,c.jsx)(t.h2,{id:`react-рецепты`,children:`React-рецепты`}),`
|
|
499
|
+
`,(0,c.jsx)(t.h3,{id:`новый-id-при-добавлении-элемента`,children:`Новый ID при добавлении элемента`}),`
|
|
500
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`function DraftList() {
|
|
501
|
+
const [items, setItems] = useState<Array<{ id: string; title: string }>>([]);
|
|
502
|
+
|
|
503
|
+
const addItem = () => {
|
|
504
|
+
setItems((current) => [
|
|
505
|
+
...current,
|
|
506
|
+
{
|
|
507
|
+
id: uuidv4(),
|
|
508
|
+
title: "Новый элемент"
|
|
509
|
+
}
|
|
510
|
+
]);
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
return (
|
|
514
|
+
<button type="button" onClick={addItem}>
|
|
515
|
+
Добавить
|
|
516
|
+
</button>
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
`})}),`
|
|
520
|
+
`,(0,c.jsx)(t.p,{children:`UUID создаётся только при команде добавления, а не при каждом render.`}),`
|
|
521
|
+
`,(0,c.jsx)(t.h3,{id:`client-only-id-сохранённый-в-state`,children:`Client-only ID, сохранённый в state`}),`
|
|
522
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`function ClientOnlyDraft() {
|
|
523
|
+
const [id] = useState(() => uuidv4());
|
|
524
|
+
|
|
525
|
+
return <span>{id}</span>;
|
|
526
|
+
}
|
|
527
|
+
`})}),`
|
|
528
|
+
`,(0,c.jsx)(t.p,{children:`Такой вариант сохраняет ID между client renders, но не решает SSR hydration. В development Strict Mode initializer может быть вызван повторно для проверки чистоты; использовать UUID как side effect вне state нельзя.`}),`
|
|
529
|
+
`,(0,c.jsx)(t.h3,{id:`стабильный-row-id-по-business-key`,children:`Стабильный row ID по business key`}),`
|
|
530
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`function CustomerRow({ customerId }: { customerId: string }) {
|
|
531
|
+
const rowId = stringToElementId(customerId, "customer-row");
|
|
532
|
+
|
|
533
|
+
return <tr id={rowId}>{/* Ячейки строки. */}</tr>;
|
|
534
|
+
}
|
|
535
|
+
`})}),`
|
|
536
|
+
`,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`customerId`}),` изменится, изменится и DOM ID. Если две строки имеют одинаковый customer ID, они получат одинаковый DOM ID.`]}),`
|
|
537
|
+
`,(0,c.jsx)(t.h2,{id:`тестирование`,children:`Тестирование`}),`
|
|
538
|
+
`,(0,c.jsx)(t.h3,{id:`проверяйте-детерминированность-и-формат`,children:`Проверяйте детерминированность и формат`}),`
|
|
539
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { describe, expect, it } from "vitest";
|
|
540
|
+
import { hashString128, hashString128Base64Url } from "@ryuzaki13/react-foundation-lib/crypto";
|
|
541
|
+
|
|
542
|
+
describe("fingerprint", () => {
|
|
543
|
+
it("стабилен для одинакового входа", () => {
|
|
544
|
+
expect(hashString128("row:42")).toBe(hashString128("row:42"));
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it("имеет ожидаемый формат", () => {
|
|
548
|
+
expect(hashString128("row:42")).toMatch(/^[0-9a-f]{32}$/);
|
|
549
|
+
expect(hashString128Base64Url("row:42")).toMatch(/^[0-9A-Za-z_-]{22}$/);
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
`})}),`
|
|
553
|
+
`,(0,c.jsxs)(t.p,{children:[`Тест `,(0,c.jsx)(t.code,{children:`hash(a) !== hash(b)`}),` для одной выбранной пары не доказывает отсутствие коллизий вообще.`]}),`
|
|
554
|
+
`,(0,c.jsx)(t.h3,{id:`test-vectors-текущей-реализации`,children:`Test vectors текущей реализации`}),`
|
|
555
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Input`}),(0,c.jsx)(t.th,{children:(0,c.jsx)(t.code,{children:`hashString`})}),(0,c.jsx)(t.th,{children:(0,c.jsx)(t.code,{children:`hashString128`})}),(0,c.jsx)(t.th,{children:(0,c.jsx)(t.code,{children:`hashString128Base64Url`})})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`""`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`ztntfp`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`c431d55b3afb3e003e41d50fc08b3e54`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`xDHVWzr7PgA-QdUPwIs-VA`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"user:42"`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`d5nw5e`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`fa3326027b653e8049d56072c88378f0`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`-jMmAntlPoBJ1WByyIN48A`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"строка"`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`hb03a2`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`86aac3ec19e5093dc3b2f1985cfd3b49`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`hqrD7BnlCT3DsvGYXP07SQ`})})]})]})]}),`
|
|
556
|
+
`,(0,c.jsx)(t.p,{children:`Exact vectors полезны, если hash является частью сериализуемого формата. Если формат не долговечный, часто достаточно проверять детерминированность и regex.`}),`
|
|
557
|
+
`,(0,c.jsxs)(t.h3,{id:`mock-native-randomuuid`,children:[`Mock native `,(0,c.jsx)(t.code,{children:`randomUUID`})]}),`
|
|
558
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { afterEach, expect, it, vi } from "vitest";
|
|
559
|
+
|
|
560
|
+
afterEach(() => {
|
|
561
|
+
vi.unstubAllGlobals();
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it("использует native randomUUID", () => {
|
|
565
|
+
const randomUUID = vi.fn(() => "00000000-0000-4000-8000-000000000000");
|
|
566
|
+
vi.stubGlobal("crypto", { randomUUID } as unknown as Crypto);
|
|
567
|
+
|
|
568
|
+
expect(uuidv4()).toBe("00000000-0000-4000-8000-000000000000");
|
|
569
|
+
expect(randomUUID).toHaveBeenCalledOnce();
|
|
570
|
+
});
|
|
571
|
+
`})}),`
|
|
572
|
+
`,(0,c.jsxs)(t.h3,{id:`mock-fallback-getrandomvalues`,children:[`Mock fallback `,(0,c.jsx)(t.code,{children:`getRandomValues`})]}),`
|
|
573
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`it("устанавливает version и variant bits", () => {
|
|
574
|
+
const getRandomValues = vi.fn((bytes: Uint8Array) => {
|
|
575
|
+
bytes.set([0, 1, 2, 3, 4, 5, 0xff, 7, 0xff, 9, 10, 11, 12, 13, 14, 15]);
|
|
576
|
+
return bytes;
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
vi.stubGlobal("crypto", { getRandomValues } as unknown as Crypto);
|
|
580
|
+
|
|
581
|
+
expect(uuidv4()).toBe("00010203-0405-4f07-bf09-0a0b0c0d0e0f");
|
|
582
|
+
});
|
|
583
|
+
`})}),`
|
|
584
|
+
`,(0,c.jsx)(t.p,{children:`Всегда восстанавливайте global mocks после теста, иначе они повлияют на другие test cases.`}),`
|
|
585
|
+
`,(0,c.jsx)(t.h3,{id:`проверка-реального-uuid-без-mock`,children:`Проверка реального UUID без mock`}),`
|
|
586
|
+
`,(0,c.jsx)(t.p,{children:`Если среда предоставляет Web Crypto:`}),`
|
|
587
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`expect(uuidv4()).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
|
|
588
|
+
`})}),`
|
|
589
|
+
`,(0,c.jsx)(t.p,{children:`Не фиксируйте конкретное случайное значение без mock.`}),`
|
|
590
|
+
`,(0,c.jsx)(t.h2,{id:`производительность`,children:`Производительность`}),`
|
|
591
|
+
`,(0,c.jsx)(t.h3,{id:`сложность-hash`,children:`Сложность hash`}),`
|
|
592
|
+
`,(0,c.jsxs)(t.p,{children:[`Каждая hash-функция проходит строку один раз. Временная сложность — `,(0,c.jsx)(t.code,{children:`O(n)`}),`, где `,(0,c.jsx)(t.code,{children:`n`}),` является числом UTF-16 code units.`]}),`
|
|
593
|
+
`,(0,c.jsx)(t.p,{children:`Алгоритмы работают быстро и синхронно. Но длинная строка всё равно блокирует текущий JavaScript thread на время обработки.`}),`
|
|
594
|
+
`,(0,c.jsx)(t.h3,{id:`не-хешируйте-одно-и-то-же-на-каждом-render-без-необходимости`,children:`Не хешируйте одно и то же на каждом render без необходимости`}),`
|
|
595
|
+
`,(0,c.jsx)(t.p,{children:`Для большого canonical payload можно вычислять fingerprint на boundary изменения или мемоизировать по стабильной ссылке:`}),`
|
|
596
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`const fingerprint = useMemo(() => hashString128(stableStringify(config)), [config]);
|
|
597
|
+
`})}),`
|
|
598
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`useMemo`}),` помогает только если зависимость `,(0,c.jsx)(t.code,{children:`config`}),` действительно стабильна между неизменившимися renders.`]}),`
|
|
599
|
+
`,(0,c.jsx)(t.h3,{id:`uuid-и-hex-table`,children:`UUID и hex table`}),`
|
|
600
|
+
`,(0,c.jsxs)(t.p,{children:[`Fallback `,(0,c.jsx)(t.code,{children:`uuidv4`}),` создаёт таблицу из 256 hex-строк при каждом вызове. Для обычного создания ID это несущественно. Не используйте функцию как tight-loop генератор миллионов значений на UI thread без измерений.`]}),`
|
|
601
|
+
`,(0,c.jsx)(t.h2,{id:`ограничения-и-частые-ошибки`,children:`Ограничения и частые ошибки`}),`
|
|
602
|
+
`,(0,c.jsxs)(t.h3,{id:`использование-hashstring-как-уникального-primary-key`,children:[`Использование `,(0,c.jsx)(t.code,{children:`hashString`}),` как уникального primary key`]}),`
|
|
603
|
+
`,(0,c.jsx)(t.p,{children:`32-bit hash слишком мал для такого обещания. Используйте UUID/backend ID или храните исходный ключ и обрабатывайте коллизии.`}),`
|
|
604
|
+
`,(0,c.jsx)(t.h3,{id:`hash-пароля-или-токена`,children:`Hash пароля или токена`}),`
|
|
605
|
+
`,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const storedPassword = hashString128(password); // Небезопасно.
|
|
606
|
+
`})}),`
|
|
607
|
+
`,(0,c.jsx)(t.p,{children:`Эта функция быстрая, несолёная и non-cryptographic.`}),`
|
|
608
|
+
`,(0,c.jsx)(t.h3,{id:`нестабильная-сериализация-объекта`,children:`Нестабильная сериализация объекта`}),`
|
|
609
|
+
`,(0,c.jsx)(t.p,{children:`Если порядок ключей или нормализация входа плавают, hash тоже будет меняться. Сначала создайте canonical string.`}),`
|
|
610
|
+
`,(0,c.jsx)(t.h3,{id:`ожидание-одинакового-hash-для-визуально-одинакового-unicode`,children:`Ожидание одинакового hash для визуально одинакового Unicode`}),`
|
|
611
|
+
`,(0,c.jsx)(t.p,{children:`Unicode normalization не выполняется. Выберите NFC/NFD policy явно, если это требуется доменом.`}),`
|
|
612
|
+
`,(0,c.jsx)(t.h3,{id:`обрезание-uuid-или-hash`,children:`Обрезание UUID или hash`}),`
|
|
613
|
+
`,(0,c.jsxs)(t.p,{children:[`Обрезание уменьшает пространство результатов. Нельзя сохранять прежние гарантии после `,(0,c.jsx)(t.code,{children:`.slice(...)`}),`.`]}),`
|
|
614
|
+
`,(0,c.jsxs)(t.h3,{id:`uuidv4-внутри-render`,children:[(0,c.jsx)(t.code,{children:`uuidv4()`}),` внутри render`]}),`
|
|
615
|
+
`,(0,c.jsxs)(t.p,{children:[`Новый UUID будет создаваться при каждом render и может нарушить SSR hydration. Создавайте entity ID на command boundary; для DOM relationships используйте `,(0,c.jsx)(t.code,{children:`useId`}),`.`]}),`
|
|
616
|
+
`,(0,c.jsx)(t.h3,{id:`небезопасный-prefix`,children:`Небезопасный prefix`}),`
|
|
617
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`stringToElementId`}),` не очищает prefix. Пробелы, кавычки или CSS-special characters останутся в строке.`]}),`
|
|
618
|
+
`,(0,c.jsx)(t.h3,{id:`ожидание-server-portability`,children:`Ожидание server portability`}),`
|
|
619
|
+
`,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`btoa`}),` и global Web Crypto могут отсутствовать в server/test runtime. Тестируйте целевую среду и не подменяйте алгоритм несовместимой реализацией.`]}),`
|
|
620
|
+
`,(0,c.jsx)(t.h3,{id:`сравнение-только-по-hash`,children:`Сравнение только по hash`}),`
|
|
621
|
+
`,(0,c.jsx)(t.p,{children:`Совпавший hash не доказывает полное равенство. Для критичных данных сравнивайте исходную canonical string или содержимое.`}),`
|
|
622
|
+
`,(0,c.jsx)(t.h2,{id:`краткий-справочник-api`,children:`Краткий справочник API`}),`
|
|
623
|
+
`,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`API`}),(0,c.jsx)(t.th,{children:`Вход`}),(0,c.jsx)(t.th,{children:`Результат`}),(0,c.jsx)(t.th,{children:`Детерминированность`}),(0,c.jsx)(t.th,{children:`Runtime`}),(0,c.jsx)(t.th,{children:`Security`})]})}),(0,c.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`hashString`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`string`})}),(0,c.jsx)(t.td,{children:`1–7 base36 символов`}),(0,c.jsx)(t.td,{children:`Да`}),(0,c.jsx)(t.td,{children:`Обычный JS`}),(0,c.jsx)(t.td,{children:`Non-cryptographic, 32-bit`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`hashString128`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`string`})}),(0,c.jsx)(t.td,{children:`32 lowercase hex-символа`}),(0,c.jsx)(t.td,{children:`Да`}),(0,c.jsxs)(t.td,{children:[`Обычный JS + `,(0,c.jsx)(t.code,{children:`Math.imul`})]}),(0,c.jsx)(t.td,{children:`Non-cryptographic`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`hashString128Base64Url`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`string`})}),(0,c.jsx)(t.td,{children:`22 URL-safe символа`}),(0,c.jsx)(t.td,{children:`Да`}),(0,c.jsxs)(t.td,{children:[`Дополнительно `,(0,c.jsx)(t.code,{children:`btoa`})]}),(0,c.jsx)(t.td,{children:`Тот же hash, другой формат`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`stringToElementId`})}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`input`}),`, optional `,(0,c.jsx)(t.code,{children:`prefix`})]}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:"${prefix}-${22 symbols}"})}),(0,c.jsx)(t.td,{children:`Да`}),(0,c.jsxs)(t.td,{children:[`Дополнительно `,(0,c.jsx)(t.code,{children:`btoa`})]}),(0,c.jsx)(t.td,{children:`Коллизия возможна`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`uuidv4`})}),(0,c.jsx)(t.td,{children:`Нет`}),(0,c.jsx)(t.td,{children:`UUID v4 string`}),(0,c.jsx)(t.td,{children:`Нет`}),(0,c.jsx)(t.td,{children:`Global Web Crypto`}),(0,c.jsx)(t.td,{children:`Random ID, не security system`})]})]})]}),`
|
|
624
|
+
`,(0,c.jsx)(t.h2,{id:`вопросы-и-ответы`,children:`Вопросы и ответы`}),`
|
|
625
|
+
`,(0,c.jsx)(t.h3,{id:`какую-hash-функцию-выбрать-по-умолчанию`,children:`Какую hash-функцию выбрать по умолчанию?`}),`
|
|
626
|
+
`,(0,c.jsxs)(t.p,{children:[`Для некритичного устойчивого fingerprint обычно выбирайте `,(0,c.jsx)(t.code,{children:`hashString128`}),`. Если важна компактность и URL-safe формат — `,(0,c.jsx)(t.code,{children:`hashString128Base64Url`}),`. `,(0,c.jsx)(t.code,{children:`hashString`}),` оставляйте для сценариев, где 32-bit коллизии безопасно обрабатываются.`]}),`
|
|
627
|
+
`,(0,c.jsx)(t.h3,{id:`почему-base64url-результат-короче-hex`,children:`Почему Base64Url-результат короче hex?`}),`
|
|
628
|
+
`,(0,c.jsx)(t.p,{children:`Hex кодирует 4 bits одним символом, а Base64 — 6 bits. Поэтому те же 16 байтов помещаются в 22 Base64Url-символа вместо 32 hex-символов.`}),`
|
|
629
|
+
`,(0,c.jsx)(t.h3,{id:`можно-ли-восстановить-исходную-строку-из-hash`,children:`Можно ли восстановить исходную строку из hash?`}),`
|
|
630
|
+
`,(0,c.jsx)(t.p,{children:`Прямой операции восстановления нет. Но короткие и предсказуемые значения можно подобрать перебором, поэтому hash не является безопасным сокрытием данных.`}),`
|
|
631
|
+
`,(0,c.jsxs)(t.h3,{id:`гарантирует-ли-hashstring128-что-коллизий-не-будет`,children:[`Гарантирует ли `,(0,c.jsx)(t.code,{children:`hashString128`}),`, что коллизий не будет?`]}),`
|
|
632
|
+
`,(0,c.jsx)(t.p,{children:`Нет. Более длинный результат уменьшает практический риск для non-adversarial сценариев, но математическая и security-гарантия отсутствует.`}),`
|
|
633
|
+
`,(0,c.jsxs)(t.h3,{id:`почему-café-иногда-даёт-другой-hash`,children:[`Почему `,(0,c.jsx)(t.code,{children:`Café`}),` иногда даёт другой hash?`]}),`
|
|
634
|
+
`,(0,c.jsx)(t.p,{children:`В Unicode визуально одинаковый текст может состоять из разных code points/code units. Модуль не выполняет normalization. Если домен требует равенства, приведите обе строки к одной форме, например NFC, до хеширования.`}),`
|
|
635
|
+
`,(0,c.jsxs)(t.h3,{id:`можно-ли-использовать-stringtoelementid-для-react-labelinput`,children:[`Можно ли использовать `,(0,c.jsx)(t.code,{children:`stringToElementId`}),` для React label/input?`]}),`
|
|
636
|
+
`,(0,c.jsxs)(t.p,{children:[`Можно при стабильном внешнем key, но для локальной связи внутри SSR React-компонента предпочтительнее `,(0,c.jsx)(t.code,{children:`useId`}),`.`]}),`
|
|
637
|
+
`,(0,c.jsxs)(t.h3,{id:`уникален-ли-stringtoelementid-во-всём-документе`,children:[`Уникален ли `,(0,c.jsx)(t.code,{children:`stringToElementId`}),` во всём документе?`]}),`
|
|
638
|
+
`,(0,c.jsx)(t.p,{children:`Только если вызывающий код обеспечивает уникальные input/prefix и принимает риск hash-коллизии. Функция не проверяет уже существующие DOM IDs.`}),`
|
|
639
|
+
`,(0,c.jsx)(t.h3,{id:`почему-id-иногда-содержит-два-дефиса-подряд`,children:`Почему ID иногда содержит два дефиса подряд?`}),`
|
|
640
|
+
`,(0,c.jsxs)(t.p,{children:[`Один дефис добавляется после prefix, а Base64Url hash сам может начинаться с `,(0,c.jsx)(t.code,{children:`-`}),`. Это нормальный результат.`]}),`
|
|
641
|
+
`,(0,c.jsxs)(t.h3,{id:`можно-ли-вызвать-uuidv4-на-сервере`,children:[`Можно ли вызвать `,(0,c.jsx)(t.code,{children:`uuidv4`}),` на сервере?`]}),`
|
|
642
|
+
`,(0,c.jsx)(t.p,{children:`Только если server runtime предоставляет совместимый global Web Crypto contract. Для переносимой server-логики используйте принятый в серверном проекте crypto API и проверьте, что формат/политика совпадают.`}),`
|
|
643
|
+
`,(0,c.jsxs)(t.h3,{id:`можно-ли-использовать-uuid-как-react-key`,children:[`Можно ли использовать UUID как React `,(0,c.jsx)(t.code,{children:`key`}),`?`]}),`
|
|
644
|
+
`,(0,c.jsxs)(t.p,{children:[`Да, если UUID один раз сохранён в самой entity. Нельзя генерировать новый `,(0,c.jsx)(t.code,{children:`uuidv4()`}),` прямо внутри `,(0,c.jsx)(t.code,{children:`.map()`}),` на каждом render: ключи будут постоянно меняться, и React станет пересоздавать элементы.`]}),`
|
|
645
|
+
`,(0,c.jsx)(t.h3,{id:`нужно-ли-проверять-uuid-на-конфликт-в-базе`,children:`Нужно ли проверять UUID на конфликт в базе?`}),`
|
|
646
|
+
`,(0,c.jsx)(t.p,{children:`Да. Очень малая вероятность не заменяет unique constraint у authoritative storage.`}),`
|
|
647
|
+
`,(0,c.jsx)(t.h3,{id:`стабильны-ли-exact-hash-между-версиями-пакета`,children:`Стабильны ли exact hash между версиями пакета?`}),`
|
|
648
|
+
`,(0,c.jsx)(t.p,{children:`В рамках текущей реализации — да. Если вы сохраняете результат надолго или используете его как внешний протокол, версионируйте формат и зафиксируйте test vectors. Это позволит безопасно обнаружить или мигрировать будущую смену алгоритма.`})]})}function s(e={}){let{wrapper:t}={...n(),...e.components};return t?(0,c.jsx)(t,{...e,children:(0,c.jsx)(o,{...e})}):o(e)}var c;function l(){return(l=e((()=>{c=t(),r(),a()})))()}l();export{s as default};
|