@ryuzaki13/react-foundation-ui 1.8.10 → 1.8.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/dist/advanced-search/index.js +1 -1
  2. package/dist/chunks/{defaultRenderers-Dbhg2hHc.js → defaultRenderers-u_Vep6Pj.js} +2 -2
  3. package/dist/chunks/defaultRenderers-u_Vep6Pj.js.map +1 -0
  4. package/dist/chunks/{multi-select-CHZhWoq6.js → multi-select-DtmRtX6v.js} +2 -2
  5. package/dist/chunks/multi-select-DtmRtX6v.js.map +1 -0
  6. package/dist/chunks/select-MN6zDPKM.js.map +1 -1
  7. package/dist/chunks/useODataSelectBase-DR4PrY1j.js.map +1 -1
  8. package/dist/multi-select/index.js +1 -1
  9. package/dist/multi-select/model/useODataMultiSelect.d.ts +1 -1
  10. package/dist/multi-select/model/useODataMultiSelect.d.ts.map +1 -1
  11. package/dist/multi-select/ui/MultiSelect.d.ts +2 -2
  12. package/dist/multi-select/ui/MultiSelect.d.ts.map +1 -1
  13. package/dist/multi-select/ui/SerializableMultiSelect.d.ts.map +1 -1
  14. package/dist/multi-select/ui/defaultRenderers.d.ts +2 -2
  15. package/dist/multi-select/ui/defaultRenderers.d.ts.map +1 -1
  16. package/dist/select/model/useODataSelect.d.ts +1 -1
  17. package/dist/select/model/useODataSelect.d.ts.map +1 -1
  18. package/dist/select/model/useODataSelectBase.d.ts +1 -1
  19. package/dist/select/model/useODataSelectBase.d.ts.map +1 -1
  20. package/dist/tree-select/index.js +1 -1
  21. package/dist/tree-select/index.js.map +1 -1
  22. package/dist/tree-select/model/buildODataTreeNodes.d.ts +1 -1
  23. package/dist/tree-select/model/buildODataTreeNodes.d.ts.map +1 -1
  24. package/package.json +3 -3
  25. package/storybook-static/assets/{Badge-BqQpQw_L.js → Badge-CveIo2LZ.js} +1 -1
  26. package/storybook-static/assets/{Badge.stories-BLEc3pho.js → Badge.stories-Bd8M64xJ.js} +1 -1
  27. package/storybook-static/assets/{BadgeList-DfMo6yit.js → BadgeList-xT41a0VL.js} +1 -1
  28. package/storybook-static/assets/{Button-D1w_se49.js → Button-C5bBSb3k.js} +1 -1
  29. package/storybook-static/assets/{Button-D6MwkfU8.js → Button-DkDgfpHL.js} +1 -1
  30. package/storybook-static/assets/{Button.stories-DsEGxbz-.js → Button.stories-CeKPeGK5.js} +1 -1
  31. package/storybook-static/assets/{CalendarView-FWwbhHMn.js → CalendarView-CEqcrblx.js} +1 -1
  32. package/storybook-static/assets/{CheckBox-BIeKlLsQ.js → CheckBox-DKeFkdOt.js} +1 -1
  33. package/storybook-static/assets/{CheckBox-krtKLFK6.js → CheckBox-OVvAarW-.js} +1 -1
  34. package/storybook-static/assets/{CheckBox.stories-gV23d8F9.js → CheckBox.stories-CfZ0p1yo.js} +1 -1
  35. package/storybook-static/assets/{Color-6AOWCN3U-DF3lA8Cg.js → Color-6AOWCN3U-BWW7zrfs.js} +1 -1
  36. package/storybook-static/assets/{ContextMenu-Jid_pp7z.js → ContextMenu-kia7is1N.js} +1 -1
  37. package/storybook-static/assets/{ContextMenu-CvOaSUdS.js → ContextMenu-xn2GtiLR.js} +1 -1
  38. package/storybook-static/assets/{ContextMenu.stories-BMRQo225.js → ContextMenu.stories-CYuy9uXF.js} +1 -1
  39. package/storybook-static/assets/{CustomOptionButton-B_3l2oEp.js → CustomOptionButton-C4w-APws.js} +1 -1
  40. package/storybook-static/assets/DateInput-BiUFIihm.js +1 -0
  41. package/storybook-static/assets/{DateInput-Dmfu-QPI.js → DateInput-DAzE5mCE.js} +1 -1
  42. package/storybook-static/assets/{DateInput.stories-BcZKAAUH.js → DateInput.stories-BcowGCRD.js} +1 -1
  43. package/storybook-static/assets/{DateRangePresetSelect-Dpjei_qk.js → DateRangePresetSelect-CQm4dNLR.js} +1 -1
  44. package/storybook-static/assets/{DateRangePresetSelect-C4QK2uI5.js → DateRangePresetSelect-RIyqNBk9.js} +1 -1
  45. package/storybook-static/assets/{DateRangePresetSelect.stories-HuFMQCzg.js → DateRangePresetSelect.stories-ypGyuGfy.js} +1 -1
  46. package/storybook-static/assets/{Detail-mSGrPHPd.js → Detail-CEypL2Nh.js} +1 -1
  47. package/storybook-static/assets/{Detail.stories-CNmeyajk.js → Detail.stories-DNkT7B-u.js} +1 -1
  48. package/storybook-static/assets/{Dialog-CNPtztC5.js → Dialog-BVFFQJDH.js} +1 -1
  49. package/storybook-static/assets/{Dialog-C4lB95kT.js → Dialog-DD0grLG3.js} +1 -1
  50. package/storybook-static/assets/{Dialog.stories-DGC2CdmD.js → Dialog.stories-DSCypHqR.js} +1 -1
  51. package/storybook-static/assets/{Disclosure-Bg9Uurzq.js → Disclosure-DauUgrev.js} +1 -1
  52. package/storybook-static/assets/{Disclosure.stories-52zq3d3b.js → Disclosure.stories-B1qB8vQZ.js} +1 -1
  53. package/storybook-static/assets/{DisclosureGroup-htBmjuHs.js → DisclosureGroup-D9oCCeHf.js} +1 -1
  54. package/storybook-static/assets/{DocsRenderer-JROSPFPF-CfCr8YB1.js → DocsRenderer-JROSPFPF-CmSf6tXp.js} +1 -1
  55. package/storybook-static/assets/{DropZone-DHl97f8Y.js → DropZone-D42ueN1w.js} +1 -1
  56. package/storybook-static/assets/{DropZone-BsjXindv.js → DropZone-D8hbPCAv.js} +1 -1
  57. package/storybook-static/assets/{DropZone.stories-UxmcssH9.js → DropZone.stories-DO-T_Bh4.js} +1 -1
  58. package/storybook-static/assets/{DropdownMenu-Bq4Z7eyk.js → DropdownMenu-CknJDoji.js} +1 -1
  59. package/storybook-static/assets/{ExpandableActionPanel-Buvm-uo_.js → ExpandableActionPanel-Dqi_bo3c.js} +1 -1
  60. package/storybook-static/assets/{ExpandableActionPanel.stories-DHjq73og.js → ExpandableActionPanel.stories-BD9QZGZN.js} +1 -1
  61. package/storybook-static/assets/{Flex-B9W01d7p.js → Flex-CaVn1IOP.js} +1 -1
  62. package/storybook-static/assets/{Flex.stories-Y8vGrgl5.js → Flex.stories-Bp9Y6q6b.js} +1 -1
  63. package/storybook-static/assets/{FloatingPopover-C06XhNb8.js → FloatingPopover-U7tx-Jlw.js} +1 -1
  64. package/storybook-static/assets/{FormattedTableCell-DnBQJjql.js → FormattedTableCell-pmmhsAbN.js} +1 -1
  65. package/storybook-static/assets/{Grid-BNsdOQE3.js → Grid-CEpMNurH.js} +1 -1
  66. package/storybook-static/assets/{Grid.stories-hwxpk_k9.js → Grid.stories-BM-gO5DN.js} +1 -1
  67. package/storybook-static/assets/{ImageCropper.stories-L6AaNzcC.js → ImageCropper.stories-BWz5XU6U.js} +1 -1
  68. package/storybook-static/assets/{ImageRenderer-DA4vCb7C.js → ImageRenderer-BTYZEX5t.js} +1 -1
  69. package/storybook-static/assets/{ImageRenderer.stories-eWJHpmcc.js → ImageRenderer.stories-Dz2STM95.js} +1 -1
  70. package/storybook-static/assets/{ImageView.stories-CDZmePLC.js → ImageView.stories-DBVe0an3.js} +1 -1
  71. package/storybook-static/assets/{ImageViewer.stories-c1s8XOKG.js → ImageViewer.stories-C4Px97lD.js} +2 -2
  72. package/storybook-static/assets/{ImageViewerRuntime-CB58FU7X.js → ImageViewerRuntime-TJtlJVfa.js} +1 -1
  73. package/storybook-static/assets/{Input-k78uABnO.js → Input-CIPgijvW.js} +1 -1
  74. package/storybook-static/assets/{Input-CPh1-5MN.js → Input-D8io9jVS.js} +1 -1
  75. package/storybook-static/assets/{Input.stories-Bjo-quWE.js → Input.stories-SUk3aYaC.js} +1 -1
  76. package/storybook-static/assets/{InputDateTime-D6wx8NHg.js → InputDateTime-DA5pIDuE.js} +1 -1
  77. package/storybook-static/assets/{InputDateTime.stories-DKYu3IHZ.js → InputDateTime.stories-DeudzDSU.js} +1 -1
  78. package/storybook-static/assets/{InputFile-B_d9cuuZ.js → InputFile-C5mp5Y5I.js} +1 -1
  79. package/storybook-static/assets/{InputFile-jyLYajsV.js → InputFile-CGAC7ruP.js} +1 -1
  80. package/storybook-static/assets/{InputFile.stories-BdefvDig.js → InputFile.stories-Cx1xQ6PE.js} +1 -1
  81. package/storybook-static/assets/{InputFiles-Bk5cxV_q.js → InputFiles-B7OBcPJN.js} +1 -1
  82. package/storybook-static/assets/{InputFiles-8NO4ybbw.js → InputFiles-Hzl89Ghf.js} +1 -1
  83. package/storybook-static/assets/{InputFiles.stories-ByWo1zY4.js → InputFiles.stories-CG6QjDZH.js} +1 -1
  84. package/storybook-static/assets/{InputFilesList-D4SB2px7.js → InputFilesList-B6htWHF1.js} +1 -1
  85. package/storybook-static/assets/{InputImage-Zoi-dxR0.js → InputImage-BTa4S4Wp.js} +1 -1
  86. package/storybook-static/assets/{InputImage-DehD0dUv.js → InputImage-CQnoblAE.js} +1 -1
  87. package/storybook-static/assets/{InputImage.stories-Bm6vjE22.js → InputImage.stories-CEI44Eyz.js} +1 -1
  88. package/storybook-static/assets/{InteractiveComponents-B1kglKoK.js → InteractiveComponents-B0ypWz1x.js} +1 -1
  89. package/storybook-static/assets/{InteractiveComponents.stories-HmO6jYBZ.js → InteractiveComponents.stories-BadApta9.js} +1 -1
  90. package/storybook-static/assets/{LayoutPicker-BdaZMF1N.js → LayoutPicker-COB9GlV_.js} +1 -1
  91. package/storybook-static/assets/{LayoutPicker-C__JzlAL.js → LayoutPicker-C_Uz7Tnj.js} +1 -1
  92. package/storybook-static/assets/{LayoutPicker.stories-Q1MsCb_7.js → LayoutPicker.stories-Btp0IcaL.js} +1 -1
  93. package/storybook-static/assets/{Listbox-_2Lhh6GE.js → Listbox-AAaZGSAl.js} +1 -1
  94. package/storybook-static/assets/{Listbox-HDTB2yAv.js → Listbox-CyfuZw7w.js} +1 -1
  95. package/storybook-static/assets/{Listbox.stories-C5uX1wxr.js → Listbox.stories-NDKQmjyQ.js} +1 -1
  96. package/storybook-static/assets/{Modal-Blv9Namu.js → Modal-BJDnJ4zv.js} +1 -1
  97. package/storybook-static/assets/{Modal.stories-Bh5G9FLw.js → Modal.stories-D_X5lTLO.js} +1 -1
  98. package/storybook-static/assets/{ModalManagerProvider-DedM4XGI.js → ModalManagerProvider-ShDjozpW.js} +1 -1
  99. package/storybook-static/assets/{MultiSelect-B_2TcTLI.js → MultiSelect-B5CElQ3-.js} +1 -1
  100. package/storybook-static/assets/{MultiSelect-C9a9z0VK.js → MultiSelect-DqXfrjcH.js} +1 -1
  101. package/storybook-static/assets/{MultiSelect.stories-D5Ly4DPj.js → MultiSelect.stories-fB3mcFmx.js} +1 -1
  102. package/storybook-static/assets/{NoData-CD8_tH4f.js → NoData-DDb0G7T8.js} +1 -1
  103. package/storybook-static/assets/{NotificationsHost-MW4gDQQK.js → NotificationsHost-CIGL-pKX.js} +1 -1
  104. package/storybook-static/assets/NotificationsHost.stories-B_MRb3fs.js +29 -0
  105. package/storybook-static/assets/{ODataMultiSelect-DjVOp5BB.js → ODataMultiSelect-DQAitH4N.js} +1 -1
  106. package/storybook-static/assets/{ODataMultiSelect.stories-Cpxe83Z0.js → ODataMultiSelect.stories-DwW221S9.js} +2 -2
  107. package/storybook-static/assets/{ODataSelect-C_2GMQva.js → ODataSelect-tiZhOIhE.js} +1 -1
  108. package/storybook-static/assets/{ODataSelect.stories-C6Wo7X-M.js → ODataSelect.stories-BhaatPjK.js} +2 -2
  109. package/storybook-static/assets/{ODataTreeMultiSelect-1PxW1yEH.js → ODataTreeMultiSelect-B9WqJ-Un.js} +1 -1
  110. package/storybook-static/assets/{ODataTreeMultiSelect.stories-AetogHrA.js → ODataTreeMultiSelect.stories-CAfKuhfU.js} +1 -1
  111. package/storybook-static/assets/{ODataTreeSelect-vPvVKIRZ.js → ODataTreeSelect-Cel1phxZ.js} +1 -1
  112. package/storybook-static/assets/{ODataTreeSelect.stories-Cvyu6C2q.js → ODataTreeSelect.stories-BpBCpBA9.js} +1 -1
  113. package/storybook-static/assets/{OneStepScroller-BtSUY6R7.js → OneStepScroller-BSeGbz02.js} +1 -1
  114. package/storybook-static/assets/{Option-D5HVST0Y.js → Option-BBtQ3GdK.js} +1 -1
  115. package/storybook-static/assets/{OptionButton-DMZn3qex.js → OptionButton-B9nZuqqW.js} +1 -1
  116. package/storybook-static/assets/{PeriodSelect-ccgbEHCS.js → PeriodSelect-BGvhR3to.js} +1 -1
  117. package/storybook-static/assets/{PeriodSelect-BirdSdPT.js → PeriodSelect-DA_zjcdA.js} +1 -1
  118. package/storybook-static/assets/{PeriodSelect.stories-LnR8MJQQ.js → PeriodSelect.stories-DDP1eXyM.js} +1 -1
  119. package/storybook-static/assets/{PickerTriggerActions-DEXowtVB.js → PickerTriggerActions-BgvzqRBd.js} +1 -1
  120. package/storybook-static/assets/{Popover-CfT-5TUj.js → Popover-BQql18MF.js} +1 -1
  121. package/storybook-static/assets/{Popover-B9Pbbd8S.js → Popover-C0NebUMz.js} +1 -1
  122. package/storybook-static/assets/{Popover.stories-DkIh7SkO.js → Popover.stories-CIyDUtlz.js} +1 -1
  123. package/storybook-static/assets/{PresetRangeDateInput-BshHlO-z.js → PresetRangeDateInput-BsgzPwCV.js} +1 -1
  124. package/storybook-static/assets/{PresetRangeDateInput-rHxPNjnG.js → PresetRangeDateInput-htbxc3ni.js} +1 -1
  125. package/storybook-static/assets/{PresetRangeDateInput.stories-BToClw-C.js → PresetRangeDateInput.stories-ClHM5Kah.js} +1 -1
  126. package/storybook-static/assets/{PresetSelect-BND50bCx.js → PresetSelect-Dcx0BOTx.js} +1 -1
  127. package/storybook-static/assets/README--LCKbep-.js +110 -0
  128. package/storybook-static/assets/{README-Du53oOl4.js → README-5M8ajrJp.js} +1 -1
  129. package/storybook-static/assets/README-5NhzH8VL.js +95 -0
  130. package/storybook-static/assets/README-Am6Fa6-b.js +69 -0
  131. package/storybook-static/assets/README-B3bFglqG.js +82 -0
  132. package/storybook-static/assets/README-B45Cwj2a.js +95 -0
  133. package/storybook-static/assets/README-B6Nq7NCU.js +416 -0
  134. package/storybook-static/assets/{README-s-T48I9S.js → README-B6ltJiw3.js} +1 -1
  135. package/storybook-static/assets/{README-DUA1kfLE.js → README-BB_JPejH.js} +1 -1
  136. package/storybook-static/assets/README-BIq-FXcN.js +2124 -0
  137. package/storybook-static/assets/README-BSh5XUR9.js +110 -0
  138. package/storybook-static/assets/README-BXJneZAJ.js +147 -0
  139. package/storybook-static/assets/README-BYSgTTZt.js +296 -0
  140. package/storybook-static/assets/{README-BRNFl9WV.js → README-BmhhWWEk.js} +1 -1
  141. package/storybook-static/assets/README-Bqnqu1bC.js +118 -0
  142. package/storybook-static/assets/README-BuMMG4_N.js +192 -0
  143. package/storybook-static/assets/README-BxjYCoYA.js +186 -0
  144. package/storybook-static/assets/{README-BsxKAG_a.js → README-ByALXSEH.js} +1 -1
  145. package/storybook-static/assets/README-Byo6DxIF.js +101 -0
  146. package/storybook-static/assets/README-C3UcjOGB.js +208 -0
  147. package/storybook-static/assets/README-C6BOdXNV.js +322 -0
  148. package/storybook-static/assets/README-C8E1O7DQ.js +230 -0
  149. package/storybook-static/assets/README-C9eh0JLs.js +64 -0
  150. package/storybook-static/assets/README-CAGQnLsv.js +90 -0
  151. package/storybook-static/assets/README-CC00hlZK.js +527 -0
  152. package/storybook-static/assets/README-CH_6ZIO2.js +64 -0
  153. package/storybook-static/assets/README-CHbo4O3Z.js +75 -0
  154. package/storybook-static/assets/{README-Y0nGf_0E.js → README-CIGfHKKE.js} +1 -1
  155. package/storybook-static/assets/{README-B7XIrdXq.js → README-CM-7E-Jf.js} +1 -1
  156. package/storybook-static/assets/{README-8Lm1JpdJ.js → README-CNHemCS2.js} +1 -1
  157. package/storybook-static/assets/{README-fxPT-NzI.js → README-CWfPiPIB.js} +1 -1
  158. package/storybook-static/assets/{README-Dw1C16bT.js → README-CeK4kfFO.js} +1 -1
  159. package/storybook-static/assets/README-Cj2e6HrF.js +178 -0
  160. package/storybook-static/assets/README-CsoHGRve.js +1844 -0
  161. package/storybook-static/assets/README-D9OypaFB.js +208 -0
  162. package/storybook-static/assets/{README-zAGDQqES.js → README-DDBqoI-T.js} +1 -1
  163. package/storybook-static/assets/README-DE2wk9-v.js +1282 -0
  164. package/storybook-static/assets/README-DEj2wSK0.js +264 -0
  165. package/storybook-static/assets/{README-DJlxGqOL.js → README-DIbfpC-2.js} +1 -1
  166. package/storybook-static/assets/{README-D5VR4x0m.js → README-DJHVgrpj.js} +1 -1
  167. package/storybook-static/assets/README-DN9HKtp_.js +124 -0
  168. package/storybook-static/assets/README-DNricOkt.js +136 -0
  169. package/storybook-static/assets/README-DV4fCpqN.js +1146 -0
  170. package/storybook-static/assets/{README-BMmR1B7a.js → README-DjCArsA5.js} +1 -1
  171. package/storybook-static/assets/README-Dl92E69Z.js +1704 -0
  172. package/storybook-static/assets/README-DnA-4W65.js +226 -0
  173. package/storybook-static/assets/README-DoCA4JXf.js +273 -0
  174. package/storybook-static/assets/{README-DUxV9GhP.js → README-DvAlDsh9.js} +1 -1
  175. package/storybook-static/assets/README-Ss-3t1nX.js +158 -0
  176. package/storybook-static/assets/README-eHCINzP3.js +154 -0
  177. package/storybook-static/assets/README-qHKxXU95.js +98 -0
  178. package/storybook-static/assets/README-tm9QH9cF.js +103 -0
  179. package/storybook-static/assets/README-ui-uanyg.js +672 -0
  180. package/storybook-static/assets/{RadioButton-BKvo6LIi.js → RadioButton-CWtjclwT.js} +1 -1
  181. package/storybook-static/assets/{RadioButton-D8KuponB.js → RadioButton-YIQXt0wx.js} +1 -1
  182. package/storybook-static/assets/{RadioButton.stories-BabeLrwX.js → RadioButton.stories-Ah-CBpeC.js} +1 -1
  183. package/storybook-static/assets/{RadioGroup-CsWjbEp2.js → RadioGroup-BnO-GL7A.js} +1 -1
  184. package/storybook-static/assets/{RadioGroup-jl5PxZpJ.js → RadioGroup-CUS0etBD.js} +1 -1
  185. package/storybook-static/assets/{RadioGroup.stories-BGoWy9-B.js → RadioGroup.stories-DGwCawtU.js} +1 -1
  186. package/storybook-static/assets/{Scrollable-CmPEHdW8.js → Scrollable-D85fA6pO.js} +1 -1
  187. package/storybook-static/assets/{Select-DiN4WVRM.js → Select-CGCxJjL9.js} +1 -1
  188. package/storybook-static/assets/{Select-BGM16rM6.js → Select-gRnfvfGh.js} +1 -1
  189. package/storybook-static/assets/{Select.stories-BEO6vXD_.js → Select.stories-CMwt4F9i.js} +1 -1
  190. package/storybook-static/assets/{SerializableSelect-B0DuNSqT.js → SerializableSelect-BLXfKJ38.js} +1 -1
  191. package/storybook-static/assets/{SerializableSelect.stories-DcI6UQCk.js → SerializableSelect.stories-Bca1a9ZU.js} +1 -1
  192. package/storybook-static/assets/{SingleDateTimeInput-C_Itd_An.js → SingleDateTimeInput-CyeQN0Rd.js} +1 -1
  193. package/storybook-static/assets/{SingleDateTimeInput-CwYhFl03.js → SingleDateTimeInput-xrmZckzw.js} +1 -1
  194. package/storybook-static/assets/SingleDateTimeInput.stories-BYFTQM9z.js +48 -0
  195. package/storybook-static/assets/{Slider-DCEQ39ar.js → Slider-1yJ3uVeR.js} +1 -1
  196. package/storybook-static/assets/{Slider-r4NefX-L.js → Slider-D8GtXVGI.js} +1 -1
  197. package/storybook-static/assets/{Slider.stories-B3M87sif.js → Slider.stories-CyQ3ns_C.js} +1 -1
  198. package/storybook-static/assets/{SliderInput-BmLucI1O.js → SliderInput-BOzHhEUA.js} +1 -1
  199. package/storybook-static/assets/{SliderInput-2ThnJzQ4.js → SliderInput-ISSx9vH_.js} +1 -1
  200. package/storybook-static/assets/{SliderInput.stories-B64rkErN.js → SliderInput.stories-7ooTghrS.js} +1 -1
  201. package/storybook-static/assets/{Sortable-Bx0rDd7W.js → Sortable-CCLiMmfZ.js} +1 -1
  202. package/storybook-static/assets/{Sortable.stories-BizbKIHw.js → Sortable.stories-B36szI6t.js} +1 -1
  203. package/storybook-static/assets/{Splitter-DGCB2owx.js → Splitter-DQGhgRwx.js} +1 -1
  204. package/storybook-static/assets/{Splitter.stories-8Oh14fbm.js → Splitter.stories-Hsq1wYaq.js} +1 -1
  205. package/storybook-static/assets/{StateSelect-Dw0Qm14-.js → StateSelect-C6hldFgO.js} +1 -1
  206. package/storybook-static/assets/{StateSelect-BSQUSTgI.js → StateSelect-DieNn4VC.js} +1 -1
  207. package/storybook-static/assets/{StateSelect.stories-C8WPRJNx.js → StateSelect.stories-CvAhDMix.js} +1 -1
  208. package/storybook-static/assets/{StringToggle.stories-K9u9rdE8.js → StringToggle.stories-CUT1tvt-.js} +1 -1
  209. package/storybook-static/assets/{Switch-BBuNsdNx.js → Switch-CX9p_uYU.js} +1 -1
  210. package/storybook-static/assets/{Switch-CtpvrECR.js → Switch-nEMRtcWe.js} +1 -1
  211. package/storybook-static/assets/{Switch.stories-Bcr4zA7L.js → Switch.stories-CPsHa2_m.js} +1 -1
  212. package/storybook-static/assets/{Table-iYeIie6s.js → Table-BhtITVtj.js} +1 -1
  213. package/storybook-static/assets/{Table.stories-BESdXqZx.js → Table.stories-nWNhSydG.js} +2 -2
  214. package/storybook-static/assets/{Tabs-aqWeCgs4.js → Tabs-DMQFh2BJ.js} +1 -1
  215. package/storybook-static/assets/{Tabs.stories-BSnrKg5G.js → Tabs.stories-D23_xvom.js} +1 -1
  216. package/storybook-static/assets/{TabsBox-CvRq3QA7.js → TabsBox-B5BsD_j_.js} +1 -1
  217. package/storybook-static/assets/{TagInput-CfKrOpJJ.js → TagInput-DO17z100.js} +1 -1
  218. package/storybook-static/assets/{TagInput.stories-CvXZl_qX.js → TagInput.stories-CMMFnIzb.js} +1 -1
  219. package/storybook-static/assets/{Text-_hP7L82D.js → Text-ClnbDqRk.js} +1 -1
  220. package/storybook-static/assets/{Textarea-Ujw8jml9.js → Textarea-C2IO_zsP.js} +1 -1
  221. package/storybook-static/assets/{Textarea-C6X20K4X.js → Textarea-DkhohiNc.js} +1 -1
  222. package/storybook-static/assets/{Textarea.stories-Bi4BQMxQ.js → Textarea.stories-NL6Aq8Vc.js} +1 -1
  223. package/storybook-static/assets/{TimePanel-B3eqkZj8.js → TimePanel-DNEPDlSW.js} +1 -1
  224. package/storybook-static/assets/{TimePanel.stories-BHXkDSMy.js → TimePanel.stories-DlfdPsz1.js} +1 -1
  225. package/storybook-static/assets/{Toggle-DS-Q4jwI.js → Toggle-3U5CceQN.js} +1 -1
  226. package/storybook-static/assets/{Toggle-6sjykxYk.js → Toggle-Cc6Ca-HI.js} +1 -1
  227. package/storybook-static/assets/{Toggle.stories-BhM1so-Z.js → Toggle.stories-BR-hVLDp.js} +1 -1
  228. package/storybook-static/assets/{TreeMultiSelect-D2lWYAa2.js → TreeMultiSelect-B0oBc-4y.js} +1 -1
  229. package/storybook-static/assets/{TreeMultiSelect-BudM77CT.js → TreeMultiSelect-D9hMQN2O.js} +1 -1
  230. package/storybook-static/assets/{TreeMultiSelect.stories-Bhh1zaAQ.js → TreeMultiSelect.stories-CslpbvPR.js} +1 -1
  231. package/storybook-static/assets/{TreePickerBase-CJfbJ6Ap.js → TreePickerBase-B_dMCe45.js} +1 -1
  232. package/storybook-static/assets/{TreeSelect-BFD6AKMu.js → TreeSelect-2lj0Q8gm.js} +1 -1
  233. package/storybook-static/assets/{TreeSelect-B7SG9PV7.js → TreeSelect-DEKNYF2b.js} +1 -1
  234. package/storybook-static/assets/{TreeSelect.stories-C-bHnrPH.js → TreeSelect.stories-CVDxn8Vm.js} +1 -1
  235. package/storybook-static/assets/{TreeTable-D-z37qlF.js → TreeTable-CBIpzYRL.js} +1 -1
  236. package/storybook-static/assets/{TreeTable.stories-CF_mH9Zz.js → TreeTable.stories-BMzrr2Kc.js} +2 -2
  237. package/storybook-static/assets/{blocks-Ddv0ZNIG.js → blocks-B5YBxaaS.js} +3 -3
  238. package/storybook-static/assets/{check-CgNbDbzr.js → check-BjtdCRaW.js} +1 -1
  239. package/storybook-static/assets/{chevron-down-0EnY_Ds_.js → chevron-down-B4u-y8Z1.js} +1 -1
  240. package/storybook-static/assets/{chevron-left-D5M5fFYk.js → chevron-left-BQfWs7Ah.js} +1 -1
  241. package/storybook-static/assets/{chevron-right-Uz5Iy_m-.js → chevron-right-ChokKd-f.js} +1 -1
  242. package/storybook-static/assets/{chunk-W22LQPXL-THpLoWLV.js → chunk-W22LQPXL-meOkhzk4.js} +1 -1
  243. package/storybook-static/assets/{circle-alert-B6Z--uoZ.js → circle-alert-B1AnDRf_.js} +1 -1
  244. package/storybook-static/assets/{circle-check-DMzkBUqN.js → circle-check-JYnlcC1F.js} +1 -1
  245. package/storybook-static/assets/{components-BmKs7c49.js → components-C_wl1sL5.js} +3 -3
  246. package/storybook-static/assets/{contextMenu-Dct-XhfD.js → contextMenu-B-TUGs_1.js} +1 -1
  247. package/storybook-static/assets/{defaultRenderers-BHXboKuy.js → defaultRenderers-DfmnIuod.js} +1 -1
  248. package/storybook-static/assets/{download-wey0TOZ0.js → download-eR3X8I-h.js} +1 -1
  249. package/storybook-static/assets/{floating-ui.react-COVT_I6r.js → floating-ui.react-D95VPQ0b.js} +1 -1
  250. package/storybook-static/assets/formatters-D33Uh3si-CxrG0jsB.js +1 -0
  251. package/storybook-static/assets/foundationApi-QhHXpYLB.js +59 -0
  252. package/storybook-static/assets/foundationLib-CEAjnstw.js +30 -0
  253. package/storybook-static/assets/{hooks-Baxp-XYz.js → hooks-awde7r2r.js} +1 -1
  254. package/storybook-static/assets/iframe-DzQq_POR.js +1 -0
  255. package/storybook-static/assets/{iframe-BOu-P2xF.js → iframe-FSp_AjJa.js} +4 -4
  256. package/storybook-static/assets/{info-gF0pk8a1.js → info-12vmb1sT.js} +1 -1
  257. package/storybook-static/assets/odata-C0Kpvttz.js +1 -0
  258. package/storybook-static/assets/odata-service-CWkHASdw-DlmSiioz.js +3 -0
  259. package/storybook-static/assets/{odataStoryFixtures-BUio-7WG.js → odataStoryFixtures-BXZnk_C7.js} +1 -1
  260. package/storybook-static/assets/{react-CN73PS7L.js → react-BlCqWuXr.js} +1 -1
  261. package/storybook-static/assets/{refresh-cw-BZ2qxodL.js → refresh-cw-Bf5ufPte.js} +1 -1
  262. package/storybook-static/assets/{settings-2-B33BG8wi.js → settings-2-DR-dTXIC.js} +1 -1
  263. package/storybook-static/assets/{sortable-CmC4atFR.js → sortable-B5e5yb_-.js} +1 -1
  264. package/storybook-static/assets/{syntaxhighlighter-QEQV7K2Y-Di0K55k7.js → syntaxhighlighter-QEQV7K2Y-EVJARTec.js} +1 -1
  265. package/storybook-static/assets/{trash-2-TnhWnKGO.js → trash-2-QP7TS18H.js} +1 -1
  266. package/storybook-static/assets/{triangle-alert-hZRwHRWu.js → triangle-alert-qw19mxHr.js} +1 -1
  267. package/storybook-static/assets/{useODataSelectBase-bTXYj3rC.js → useODataSelectBase-Csu_Cct9.js} +1 -1
  268. package/storybook-static/assets/useODataTreeData-BAv-Twqf.js +1 -0
  269. package/storybook-static/assets/{usePickerQuery-BMGBq6pi.js → usePickerQuery-Da-XM_Y0.js} +1 -1
  270. package/storybook-static/assets/{usePickerTriggerController-CB_TIrYx.js → usePickerTriggerController-C0TdC2Pz.js} +1 -1
  271. package/storybook-static/assets/{x-D86ykZGt.js → x-aCjYTirW.js} +1 -1
  272. package/storybook-static/iframe.html +2 -2
  273. package/storybook-static/index.json +1 -1
  274. package/storybook-static/project.json +1 -1
  275. package/dist/chunks/defaultRenderers-Dbhg2hHc.js.map +0 -1
  276. package/dist/chunks/multi-select-CHZhWoq6.js.map +0 -1
  277. package/storybook-static/assets/DateInput-DSwx7eI6.js +0 -1
  278. package/storybook-static/assets/NotificationsHost.stories-BZJG9Nvq.js +0 -29
  279. package/storybook-static/assets/SingleDateTimeInput.stories-kdKdW6al.js +0 -48
  280. package/storybook-static/assets/async-D1NpMm55.js +0 -625
  281. package/storybook-static/assets/formatters-CBgrBeSA-CqB1pZxS.js +0 -1
  282. package/storybook-static/assets/foundationLib-CRTVZVS8.js +0 -25
  283. package/storybook-static/assets/iframe-CDj936Ul.js +0 -1
  284. package/storybook-static/assets/odata-D9Wmxqmj.js +0 -1
  285. package/storybook-static/assets/odata-service-B6TxBSLm-Bq8Ya67E.js +0 -3
  286. package/storybook-static/assets/odataFetchFn-DfJwHTw8.js +0 -763
  287. package/storybook-static/assets/persisted-4qmfJLE0.js +0 -600
  288. package/storybook-static/assets/useODataTreeData-ZcX73Wdh.js +0 -1
@@ -0,0 +1,1704 @@
1
+ import{n as e}from"./rolldown-runtime-DkW27tQK.js";import{n as t}from"./iframe-FSp_AjJa.js";import{i as n,r}from"./react-Bl2r1tuC.js";import{l as i,m as a}from"./blocks-B5YBxaaS.js";function o(e){let t={a:`a`,blockquote:`blockquote`,code:`code`,h1:`h1`,h2:`h2`,h3:`h3`,h4:`h4`,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/Infrastructure/Query Client`}),`
2
+ `,(0,c.jsx)(t.h1,{id:`query-client`,children:`Query Client`}),`
3
+ `,(0,c.jsxs)(t.p,{children:[`Модуль `,(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/query-client`}),` предоставляет общую инфраструктуру TanStack Query для browser-приложения:`]}),`
4
+ `,(0,c.jsxs)(t.ul,{children:[`
5
+ `,(0,c.jsxs)(t.li,{children:[`создаёт настроенный `,(0,c.jsx)(t.code,{children:`QueryClient`}),`;`]}),`
6
+ `,(0,c.jsx)(t.li,{children:`подключает глобальную обработку query/mutation errors;`}),`
7
+ `,(0,c.jsx)(t.li,{children:`сохраняет только явно разрешённые query в IndexedDB;`}),`
8
+ `,(0,c.jsx)(t.li,{children:`разделяет публичный persistent cache и данные текущей серверной сессии;`}),`
9
+ `,(0,c.jsx)(t.li,{children:`сбрасывает session-scoped query при смене пользователя или авторизации;`}),`
10
+ `,(0,c.jsx)(t.li,{children:`синхронизирует cache data и invalidation между вкладками;`}),`
11
+ `,(0,c.jsx)(t.li,{children:`предоставляет низкоуровневые helpers для optimistic update и rollback.`}),`
12
+ `]}),`
13
+ `,(0,c.jsx)(t.p,{children:`README рассчитан на разработчика без доступа к исходному коду и с минимальным знанием JavaScript, TypeScript и TanStack Query. Здесь описаны установка, инициализация, жизненный цикл данных, точное поведение функций, ограничения browser API и полный публичный API.`}),`
14
+ `,(0,c.jsxs)(t.blockquote,{children:[`
15
+ `,(0,c.jsx)(t.p,{children:`Ранее optimistic update был описан отдельным документом. Теперь весь смысл, примеры, ограничения и API находятся только на этой странице.`}),`
16
+ `]}),`
17
+ `,(0,c.jsx)(t.h2,{id:`содержание`,children:`Содержание`}),`
18
+ `,(0,c.jsxs)(t.ul,{children:[`
19
+ `,(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`,children:`Назначение и границы`})}),`
20
+ `,(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:`Установка и импорт`})}),`
21
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BE%D0%B1%D1%8F%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-build-time-constants`,children:`Обязательные build-time constants`})}),`
22
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%BD%D1%8B%D0%B5-%D0%BF%D0%BE%D0%BD%D1%8F%D1%82%D0%B8%D1%8F-tanstack-query`,children:`Основные понятия TanStack Query`})}),`
23
+ `,(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-cache-strategy`,children:`Как выбрать cache strategy`})}),`
24
+ `,(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:`Быстрый старт`})}),`
25
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-queryclient`,children:`Создание QueryClient`})}),`
26
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#query-metadata`,children:`Query metadata`})}),`
27
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BD%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0-project-adapter`,children:`Настройка project adapter`})}),`
28
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#indexeddb-persistence`,children:`IndexedDB persistence`})}),`
29
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#session-scoped-query`,children:`Session-scoped query`})}),`
30
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F-%D0%BC%D0%B5%D0%B6%D0%B4%D1%83-%D0%B2%D0%BA%D0%BB%D0%B0%D0%B4%D0%BA%D0%B0%D0%BC%D0%B8`,children:`Синхронизация между вкладками`})}),`
31
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#optimistic-update`,children:`Optimistic update`})}),`
32
+ `,(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-%D0%B8-%D1%81%D0%B5%D1%80%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F`,children:`Безопасность и сериализация`})}),`
33
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#browser-ssr-%D0%B8-lifecycle`,children:`Browser, SSR и lifecycle`})}),`
34
+ `,(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-consumer-%D0%BA%D0%BE%D0%B4%D0%B0`,children:`Тестирование consumer-кода`})}),`
35
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D0%BF%D0%BE%D0%BB%D0%BD%D0%B0%D1%8F-%D0%BA%D0%B0%D1%80%D1%82%D0%B0-api`,children:`Полная карта API`})}),`
36
+ `,(0,c.jsx)(t.li,{children:(0,c.jsx)(t.a,{href:`#%D1%87%D0%B0%D1%81%D1%82%D1%8B%D0%B5-%D0%B2%D0%BE%D0%BF%D1%80%D0%BE%D1%81%D1%8B`,children:`Частые вопросы`})}),`
37
+ `]}),`
38
+ `,(0,c.jsx)(t.h2,{id:`назначение-и-границы`,children:`Назначение и границы`}),`
39
+ `,(0,c.jsx)(t.h3,{id:`что-делает-модуль`,children:`Что делает модуль`}),`
40
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`queryOptions/useQuery/useMutation
41
+
42
+
43
+ QueryClient
44
+ / │ \\
45
+ / │ \\
46
+ IndexedDB Broadcast session reset
47
+ persistence channels и optimistic cache
48
+ `})}),`
49
+ `,(0,c.jsx)(t.p,{children:`Модуль управляет инфраструктурным поведением TanStack Query cache. Он знает, как создать client, где хранить opt-in query, как передать cache event другой вкладке и как откатить optimistic patch.`}),`
50
+ `,(0,c.jsx)(t.h3,{id:`что-модуль-не-делает`,children:`Что модуль не делает`}),`
51
+ `,(0,c.jsx)(t.p,{children:`Модуль не является transport или бизнес-слоем:`}),`
52
+ `,(0,c.jsxs)(t.ul,{children:[`
53
+ `,(0,c.jsxs)(t.li,{children:[`не выполняет `,(0,c.jsx)(t.code,{children:`fetch`}),` сам по себе;`]}),`
54
+ `,(0,c.jsx)(t.li,{children:`не знает URL backend;`}),`
55
+ `,(0,c.jsx)(t.li,{children:`не добавляет SSO credentials и X-CSRF token;`}),`
56
+ `,(0,c.jsxs)(t.li,{children:[`не строит OData path, `,(0,c.jsx)(t.code,{children:`$filter`}),` или payload;`]}),`
57
+ `,(0,c.jsx)(t.li,{children:`не создаёт query keys конкретной entity;`}),`
58
+ `,(0,c.jsx)(t.li,{children:`не решает, какие данные безопасно сохранять;`}),`
59
+ `,(0,c.jsx)(t.li,{children:`не показывает notification пользователю;`}),`
60
+ `,(0,c.jsxs)(t.li,{children:[`не заменяет `,(0,c.jsx)(t.code,{children:`QueryClientProvider`}),`;`]}),`
61
+ `,(0,c.jsx)(t.li,{children:`не создаёт singleton автоматически.`}),`
62
+ `]}),`
63
+ `,(0,c.jsx)(t.p,{children:`Используйте правильного владельца:`}),`
64
+ `,(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.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`QueryClient, persistence, broadcast, session reset`}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/query-client`})})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`HTTP/OData transport и operation`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-api/odata`}),` или `,(0,c.jsx)(t.code,{children:`/http`})]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Query key и query options сущности`}),(0,c.jsx)(t.td,{children:`entity/feature API consumer-проекта`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`UI loading/error/empty state`}),(0,c.jsx)(t.td,{children:`component/feature consumer-проекта`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Service Worker HTTP cache`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/pwa`}),` и host SW policy`]})]})]})]}),`
65
+ `,(0,c.jsx)(t.h3,{id:`три-разных-cache-механизма`,children:`Три разных cache-механизма`}),`
66
+ `,(0,c.jsx)(t.p,{children:`Не смешивайте:`}),`
67
+ `,(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:`Query cache`}),(0,c.jsx)(t.td,{children:`Backend snapshot и состояние query`}),(0,c.jsx)(t.td,{children:`В памяти текущей вкладки`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Query persistence`}),(0,c.jsx)(t.td,{children:`Выбранные уже полученные query data`}),(0,c.jsx)(t.td,{children:`IndexedDB`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Service Worker cache`}),(0,c.jsx)(t.td,{children:`HTTP response/request на transport-уровне`}),(0,c.jsx)(t.td,{children:`Cache Storage в Service Worker`})]})]})]}),`
68
+ `,(0,c.jsx)(t.p,{children:`Broadcast не является четвёртым хранилищем. Он только передаёт изменения между уже работающими вкладками.`}),`
69
+ `,(0,c.jsx)(t.h2,{id:`установка-и-импорт`,children:`Установка и импорт`}),`
70
+ `,(0,c.jsx)(t.p,{children:`Установите foundation-пакет и совместимые peer dependencies:`}),`
71
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-bash`,children:`npm install @ryuzaki13/react-foundation-lib
72
+ npm install react @tanstack/react-query \\
73
+ @tanstack/query-persist-client-core \\
74
+ @tanstack/query-broadcast-client-experimental
75
+ `})}),`
76
+ `,(0,c.jsx)(t.p,{children:`Публичный импорт:`}),`
77
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { createQueryClient, persistedQueryMeta, sessionScopedQueryMeta } from "@ryuzaki13/react-foundation-lib/query-client";
78
+ `})}),`
79
+ `,(0,c.jsxs)(t.p,{children:[`Типы импортируйте через `,(0,c.jsx)(t.code,{children:`import type`}),`:`]}),`
80
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import type { AppQueryMeta, OptimisticContext, QueryBroadcastOptions } from "@ryuzaki13/react-foundation-lib/query-client";
81
+ `})}),`
82
+ `,(0,c.jsx)(t.p,{children:`Неверные импорты:`}),`
83
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`// Корневой entrypoint пакета не опубликован.
84
+ import { createQueryClient } from "@ryuzaki13/react-foundation-lib";
85
+
86
+ // Внутреннее устройство dist не является публичным API.
87
+ import { createQueryClient } from "@ryuzaki13/react-foundation-lib/dist/query-client/queryClient";
88
+
89
+ // Это локальный alias чужого приложения, а не package contract.
90
+ import { createQueryClient } from "@/shared/lib";
91
+ `})}),`
92
+ `,(0,c.jsxs)(t.p,{children:[`Модуль распространяется как ESM. Используйте `,(0,c.jsx)(t.code,{children:`import`}),`, а не CommonJS `,(0,c.jsx)(t.code,{children:`require`}),`.`]}),`
93
+ `,(0,c.jsx)(t.h2,{id:`обязательные-build-time-constants`,children:`Обязательные build-time constants`}),`
94
+ `,(0,c.jsx)(t.p,{children:`Entry point использует constants, которые host-сборка должна определить:`}),`
95
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`declare const __APP_ID__: string;
96
+ declare const __DEV__: boolean;
97
+ declare const __REACT_QUERY_PERSISTENCE_BUSTER__: string;
98
+ `})}),`
99
+ `,(0,c.jsx)(t.p,{children:`Они нужны для namespacing IndexedDB и cache busting.`}),`
100
+ `,(0,c.jsx)(t.h3,{id:`пример-vite-define`,children:`Пример Vite define`}),`
101
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { defineConfig } from "vite";
102
+
103
+ export default defineConfig(({ mode }) => ({
104
+ define: {
105
+ __APP_ID__: JSON.stringify("sales-app"),
106
+ __DEV__: JSON.stringify(mode === "development"),
107
+ __REACT_QUERY_PERSISTENCE_BUSTER__: JSON.stringify("sales-app.query-cache.v1")
108
+ }
109
+ }));
110
+ `})}),`
111
+ `,(0,c.jsx)(t.p,{children:`Это compile-time replacement. Не нужно писать:`}),`
112
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`window.__APP_ID__ = "sales-app";
113
+ `})}),`
114
+ `,(0,c.jsxs)(t.p,{children:[`Такое присваивание не заменяет Vite `,(0,c.jsx)(t.code,{children:`define`}),`.`]}),`
115
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`REACT_QUERY_PERSISTENCE_BUSTER`}),` вычисляется на верхнем уровне модуля из `,(0,c.jsx)(t.code,{children:`__REACT_QUERY_PERSISTENCE_BUSTER__`}),`. Поэтому buster должен быть определён уже при сборке/import entrypoint, даже если persistence позже не включится из-за отсутствия IndexedDB.`]}),`
116
+ `,(0,c.jsx)(t.h3,{id:`как-выбирать-buster`,children:`Как выбирать buster`}),`
117
+ `,(0,c.jsx)(t.p,{children:`Buster — строковая версия формата persistent cache:`}),`
118
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`sales-app.query-cache.v1
119
+ sales-app.query-cache.v2
120
+ `})}),`
121
+ `,(0,c.jsx)(t.p,{children:`Повышайте версию, когда старые записи несовместимы с новым приложением:`}),`
122
+ `,(0,c.jsxs)(t.ul,{children:[`
123
+ `,(0,c.jsx)(t.li,{children:`изменилась структура persisted data;`}),`
124
+ `,(0,c.jsx)(t.li,{children:`изменился mapping ответа;`}),`
125
+ `,(0,c.jsx)(t.li,{children:`изменился смысл query key;`}),`
126
+ `,(0,c.jsx)(t.li,{children:`старый cache нельзя безопасно использовать.`}),`
127
+ `]}),`
128
+ `,(0,c.jsx)(t.p,{children:`Не меняйте buster при каждой сборке без причины: это уничтожит пользу persistence.`}),`
129
+ `,(0,c.jsx)(t.h2,{id:`основные-понятия-tanstack-query`,children:`Основные понятия TanStack Query`}),`
130
+ `,(0,c.jsx)(t.h3,{id:`query`,children:`Query`}),`
131
+ `,(0,c.jsx)(t.p,{children:`Query — описание чтения backend data:`}),`
132
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const options = {
133
+ queryKey: ["products", { company: "1000" }],
134
+ queryFn: fetchProducts
135
+ };
136
+ `})}),`
137
+ `,(0,c.jsx)(t.p,{children:`Query содержит key, функцию загрузки, состояние, data, timestamps и metadata.`}),`
138
+ `,(0,c.jsx)(t.h3,{id:`query-key`,children:`Query key`}),`
139
+ `,(0,c.jsx)(t.p,{children:`Query key — identity данных в cache:`}),`
140
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const productsKey = ["products", { company: "1000" }] as const;
141
+ `})}),`
142
+ `,(0,c.jsx)(t.p,{children:`Все параметры, влияющие на результат, должны входить в key. Иначе разные запросы могут записать данные в одну cache entry.`}),`
143
+ `,(0,c.jsx)(t.p,{children:`Хороший key:`}),`
144
+ `,(0,c.jsxs)(t.ul,{children:[`
145
+ `,(0,c.jsx)(t.li,{children:`стабилен;`}),`
146
+ `,(0,c.jsx)(t.li,{children:`сериализуем;`}),`
147
+ `,(0,c.jsx)(t.li,{children:`не содержит token, password или session secret;`}),`
148
+ `,(0,c.jsx)(t.li,{children:`различает все варианты результата;`}),`
149
+ `,(0,c.jsx)(t.li,{children:`принадлежит owner-у query.`}),`
150
+ `]}),`
151
+ `,(0,c.jsx)(t.h3,{id:`queryclient`,children:`QueryClient`}),`
152
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`QueryClient`}),` управляет query/mutation cache:`]}),`
153
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`queryClient.getQueryData(queryKey);
154
+ queryClient.setQueryData(queryKey, nextData);
155
+ queryClient.invalidateQueries({ queryKey });
156
+ queryClient.cancelQueries({ queryKey });
157
+ `})}),`
158
+ `,(0,c.jsxs)(t.p,{children:[`В SPA обычно создают один экземпляр на всё приложение и передают в `,(0,c.jsx)(t.code,{children:`QueryClientProvider`}),`.`]}),`
159
+ `,(0,c.jsx)(t.h3,{id:`observer-и-active-query`,children:`Observer и active query`}),`
160
+ `,(0,c.jsxs)(t.p,{children:[`Query считается active, когда у неё есть активный observer — обычно смонтированный `,(0,c.jsx)(t.code,{children:`useQuery`}),` с `,(0,c.jsx)(t.code,{children:`enabled !== false`}),`.`]}),`
161
+ `,(0,c.jsx)(t.p,{children:`Query может оставаться в cache без component-подписки. Тогда она inactive.`}),`
162
+ `,(0,c.jsx)(t.p,{children:`Различие важно для:`}),`
163
+ `,(0,c.jsxs)(t.ul,{children:[`
164
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`refetchType`}),`;`]}),`
165
+ `,(0,c.jsx)(t.li,{children:`session reset;`}),`
166
+ `,(0,c.jsx)(t.li,{children:`garbage collection;`}),`
167
+ `,(0,c.jsx)(t.li,{children:`фонового обновления.`}),`
168
+ `]}),`
169
+ `,(0,c.jsx)(t.h3,{id:`fresh-stale-и-invalidated`,children:`Fresh, stale и invalidated`}),`
170
+ `,(0,c.jsxs)(t.ul,{children:[`
171
+ `,(0,c.jsxs)(t.li,{children:[`Fresh: данные моложе `,(0,c.jsx)(t.code,{children:`staleTime`}),`.`]}),`
172
+ `,(0,c.jsx)(t.li,{children:`Stale: данные можно показывать, но TanStack Query может обновить их.`}),`
173
+ `,(0,c.jsx)(t.li,{children:`Invalidated: client явно отметил query устаревшей.`}),`
174
+ `]}),`
175
+ `,(0,c.jsx)(t.p,{children:`Stale не означает «удалена». Data может продолжать отображаться до refetch.`}),`
176
+ `,(0,c.jsxs)(t.h3,{id:`staletime-и-gctime`,children:[(0,c.jsx)(t.code,{children:`staleTime`}),` и `,(0,c.jsx)(t.code,{children:`gcTime`})]}),`
177
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`staleTime`}),` отвечает за свежесть.`]}),`
178
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`gcTime`}),` отвечает за время хранения неактивной query в памяти до garbage collection.`]}),`
179
+ `,(0,c.jsxs)(t.p,{children:[`Они не задают срок хранения IndexedDB. Persistent срок отдельно задаёт `,(0,c.jsx)(t.code,{children:`REACT_QUERY_PERSISTENCE_MAX_AGE`}),`.`]}),`
180
+ `,(0,c.jsx)(t.h3,{id:`query-data--backend-state`,children:`Query data — backend state`}),`
181
+ `,(0,c.jsx)(t.p,{children:`Не копируйте backend snapshot одновременно в Query cache, Zustand и React state. Обычно:`}),`
182
+ `,(0,c.jsxs)(t.ul,{children:[`
183
+ `,(0,c.jsx)(t.li,{children:`backend data живёт в Query cache;`}),`
184
+ `,(0,c.jsx)(t.li,{children:`draft формы — в React/form state;`}),`
185
+ `,(0,c.jsx)(t.li,{children:`URL filters — в Router search;`}),`
186
+ `,(0,c.jsx)(t.li,{children:`устойчивые пользовательские preferences — в Zustand/store.`}),`
187
+ `]}),`
188
+ `,(0,c.jsx)(t.h2,{id:`как-выбрать-cache-strategy`,children:`Как выбрать cache strategy`}),`
189
+ `,(0,c.jsx)(t.p,{children:`Перед созданием query ответьте на вопросы:`}),`
190
+ `,(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.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Нужны данные только пока открыта вкладка?`}),(0,c.jsxs)(t.td,{children:[`Обычная query без `,(0,c.jsx)(t.code,{children:`persistedQueryMeta`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Данные публичные, тяжёлые и могут пережить reload?`}),(0,c.jsxs)(t.td,{children:[`Рассмотреть `,(0,c.jsx)(t.code,{children:`persistedQueryMeta`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Данные зависят от пользователя, роли или server session?`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`sessionScopedQueryMeta`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Нужно передать обновление другой открытой вкладке?`}),(0,c.jsx)(t.td,{children:`Broadcast installation и подходящее cache event.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`UI должен измениться до ответа mutation?`}),(0,c.jsx)(t.td,{children:`Optimistic update с обязательным rollback policy.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`Server response авторитетен и уже содержит новое состояние?`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`setQueryData`}),`/`,(0,c.jsx)(t.code,{children:`onSuccessOptimistic`}),`.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:`После mutation нужен свежий server snapshot?`}),(0,c.jsxs)(t.td,{children:[`Scoped `,(0,c.jsx)(t.code,{children:`invalidateQueries`}),` с выбранным `,(0,c.jsx)(t.code,{children:`refetchType`}),`.`]})]})]})]}),`
191
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`persistedQueryMeta`}),` и `,(0,c.jsx)(t.code,{children:`sessionScopedQueryMeta`}),` взаимоисключающие. Session data не должна переживать смену пользователя.`]}),`
192
+ `,(0,c.jsx)(t.h2,{id:`быстрый-старт`,children:`Быстрый старт`}),`
193
+ `,(0,c.jsx)(t.h3,{id:`bootstrap`,children:`Bootstrap`}),`
194
+ `,(0,c.jsx)(t.p,{children:`Инициализация выполняется один раз до обычного render приложения:`}),`
195
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`import { StrictMode } from "react";
196
+ import { createRoot } from "react-dom/client";
197
+ import { QueryClientProvider } from "@tanstack/react-query";
198
+
199
+ import {
200
+ configureQueryPersistenceProjectAdapter,
201
+ createQueryClient,
202
+ installReactQueryBroadcast,
203
+ installSessionScopedQueryReset,
204
+ setBroadcastFn
205
+ } from "@ryuzaki13/react-foundation-lib/query-client";
206
+
207
+ configureQueryPersistenceProjectAdapter({
208
+ persistenceBuster: "sales-app.query-cache.v1",
209
+ resolveSystemIdentifier: () => "production:300"
210
+ });
211
+
212
+ const queryClient = createQueryClient({
213
+ onQueryError: (error, query) => {
214
+ console.error("Query завершилась ошибкой", query.queryKey, error);
215
+ },
216
+ onMutationError: (error) => {
217
+ console.error("Mutation завершилась ошибкой", error);
218
+ }
219
+ });
220
+
221
+ const cacheBroadcast = installReactQueryBroadcast(queryClient, {
222
+ queriesChannel: "sales-app:query-data",
223
+ eventsChannel: "sales-app:query-events"
224
+ });
225
+
226
+ setBroadcastFn(cacheBroadcast.broadcast);
227
+
228
+ const sessionReset = installSessionScopedQueryReset(queryClient, {
229
+ channelName: "sales-app:session-query-reset"
230
+ });
231
+
232
+ createRoot(document.getElementById("root")!).render(
233
+ <StrictMode>
234
+ <QueryClientProvider client={queryClient}>
235
+ <App />
236
+ </QueryClientProvider>
237
+ </StrictMode>
238
+ );
239
+ `})}),`
240
+ `,(0,c.jsx)(t.p,{children:`В обычной SPA эти installations живут до закрытия страницы. В тесте, story, microfrontend или явно размонтируемом shell вызовите cleanup:`}),`
241
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`cacheBroadcast.cleanup();
242
+ sessionReset.cleanup();
243
+ setBroadcastFn(() => undefined);
244
+ queryClient.clear();
245
+ `})}),`
246
+ `,(0,c.jsx)(t.h3,{id:`обычная-query`,children:`Обычная query`}),`
247
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { queryOptions, useQuery } from "@tanstack/react-query";
248
+
249
+ export function productsQueryOptions(company: string) {
250
+ return queryOptions({
251
+ queryKey: ["products", { company }] as const,
252
+ queryFn: () => fetchProducts(company)
253
+ });
254
+ }
255
+
256
+ export function useProducts(company: string) {
257
+ return useQuery(productsQueryOptions(company));
258
+ }
259
+ `})}),`
260
+ `,(0,c.jsx)(t.p,{children:`Она использует default options созданного client, но не сохраняется в IndexedDB без явной meta.`}),`
261
+ `,(0,c.jsx)(t.h2,{id:`создание-queryclient`,children:`Создание QueryClient`}),`
262
+ `,(0,c.jsx)(t.h3,{id:`createqueryclient`,children:(0,c.jsx)(t.code,{children:`createQueryClient`})}),`
263
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function createQueryClient(options: QueryBroadcastOptions): QueryClient;
264
+ `})}),`
265
+ `,(0,c.jsxs)(t.p,{children:[`Несмотря на старый комментарий «создаёт singleton», функция не кеширует экземпляр. Каждый вызов возвращает новый `,(0,c.jsx)(t.code,{children:`QueryClient`}),`:`]}),`
266
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const first = createQueryClient({});
267
+ const second = createQueryClient({});
268
+
269
+ console.log(first === second); // false
270
+ `})}),`
271
+ `,(0,c.jsx)(t.p,{children:`Singleton — ответственность host bootstrap:`}),`
272
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`export const queryClient = createQueryClient({});
273
+ `})}),`
274
+ `,(0,c.jsxs)(t.p,{children:[`Не вызывайте `,(0,c.jsx)(t.code,{children:`createQueryClient`}),` внутри React component. Иначе render может создать новый cache и потерять предыдущие данные.`]}),`
275
+ `,(0,c.jsx)(t.h3,{id:`default-query-options`,children:`Default query options`}),`
276
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`{
277
+ staleTime: 10 * 60 * 1000,
278
+ gcTime: 30 * 60 * 1000,
279
+ throwOnError: false,
280
+ retry: false,
281
+ persister: /* только если доступен IndexedDB */
282
+ }
283
+ `})}),`
284
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Option`}),(0,c.jsx)(t.th,{children:`Default`}),(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:`staleTime`})}),(0,c.jsx)(t.td,{children:`10 минут`}),(0,c.jsx)(t.td,{children:`Данные считаются fresh после успеха.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`gcTime`})}),(0,c.jsx)(t.td,{children:`30 минут`}),(0,c.jsx)(t.td,{children:`Неактивная query остаётся в памяти до GC.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`throwOnError`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`false`})}),(0,c.jsx)(t.td,{children:`Query error не пробрасывается в render/Error Boundary автоматически.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`retry`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`false`})}),(0,c.jsx)(t.td,{children:`Автоматический повтор query отключён.`})]})]})]}),`
285
+ `,(0,c.jsx)(t.p,{children:`Конкретная query может переопределить defaults:`}),`
286
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`useQuery({
287
+ queryKey,
288
+ queryFn,
289
+ staleTime: Infinity
290
+ });
291
+ `})}),`
292
+ `,(0,c.jsx)(t.h3,{id:`default-mutation-options`,children:`Default mutation options`}),`
293
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`{
294
+ throwOnError: false,
295
+ retry: false
296
+ }
297
+ `})}),`
298
+ `,(0,c.jsx)(t.p,{children:`Mutation не повторяется автоматически. Это особенно важно для create/update/delete: повтор небезопасной операции может создать дубль.`}),`
299
+ `,(0,c.jsx)(t.h3,{id:`глобальные-error-callbacks`,children:`Глобальные error callbacks`}),`
300
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type QueryBroadcastOptions = {
301
+ onQueryError?: (error: DefaultError, query: Query<unknown, unknown, unknown>) => void;
302
+
303
+ onMutationError?: (
304
+ error: DefaultError,
305
+ variables: unknown,
306
+ onMutateResult: unknown,
307
+ mutation: Mutation<unknown, unknown, unknown>,
308
+ context: MutationFunctionContext
309
+ ) => Promise<unknown> | unknown;
310
+ };
311
+ `})}),`
312
+ `,(0,c.jsxs)(t.p,{children:[`Callbacks устанавливаются в `,(0,c.jsx)(t.code,{children:`QueryCache`}),` и `,(0,c.jsx)(t.code,{children:`MutationCache`}),`. Они подходят для общей диагностики, auth recovery и единой error policy.`]}),`
313
+ `,(0,c.jsx)(t.p,{children:`Не помещайте в log:`}),`
314
+ `,(0,c.jsxs)(t.ul,{children:[`
315
+ `,(0,c.jsx)(t.li,{children:`access/session tokens;`}),`
316
+ `,(0,c.jsx)(t.li,{children:`raw credentials;`}),`
317
+ `,(0,c.jsx)(t.li,{children:`персональные payloads;`}),`
318
+ `,(0,c.jsx)(t.li,{children:`секреты из mutation variables;`}),`
319
+ `,(0,c.jsx)(t.li,{children:`полный cache dump.`}),`
320
+ `]}),`
321
+ `,(0,c.jsxs)(t.p,{children:[`Локальный `,(0,c.jsx)(t.code,{children:`onError`}),` mutation/query и global cache callback — разные уровни. Локальный handler может выполнить rollback/UI recovery, а глобальный — записать diagnostics. Не считайте один автоматической заменой другого.`]}),`
322
+ `,(0,c.jsx)(t.h3,{id:`persistence-подключается-автоматически`,children:`Persistence подключается автоматически`}),`
323
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`createQueryClient`}),` вызывает `,(0,c.jsx)(t.code,{children:`createReactQueryPersister`}),`.`]}),`
324
+ `,(0,c.jsxs)(t.ul,{children:[`
325
+ `,(0,c.jsxs)(t.li,{children:[`Если `,(0,c.jsx)(t.code,{children:`indexedDB`}),` доступен, `,(0,c.jsx)(t.code,{children:`persisterFn`}),` становится default query persister.`]}),`
326
+ `,(0,c.jsxs)(t.li,{children:[`Если `,(0,c.jsx)(t.code,{children:`indexedDB`}),` отсутствует, client создаётся без persister.`]}),`
327
+ `,(0,c.jsx)(t.li,{children:`Даже с persister сохраняются только opt-in query.`}),`
328
+ `]}),`
329
+ `,(0,c.jsx)(t.h2,{id:`query-metadata`,children:`Query metadata`}),`
330
+ `,(0,c.jsx)(t.h3,{id:`appquerymeta`,children:(0,c.jsx)(t.code,{children:`AppQueryMeta`})}),`
331
+ `,(0,c.jsxs)(t.p,{children:[`Модуль расширяет TanStack Query `,(0,c.jsx)(t.code,{children:`Register.queryMeta`}),`:`]}),`
332
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type AppQueryMeta = {
333
+ readonly persist?: boolean;
334
+ readonly sessionScoped?: boolean;
335
+ } & Record<string, unknown>;
336
+ `})}),`
337
+ `,(0,c.jsxs)(t.p,{children:[`После импорта package TypeScript понимает эти fields в `,(0,c.jsx)(t.code,{children:`queryOptions`}),`, `,(0,c.jsx)(t.code,{children:`useQuery`}),` и Query object:`]}),`
338
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`queryOptions({
339
+ queryKey: ["dictionary"],
340
+ queryFn: fetchDictionary,
341
+ meta: {
342
+ persist: true,
343
+ source: "dictionary"
344
+ }
345
+ });
346
+ `})}),`
347
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`Record<string, unknown>`}),` разрешает consumer-проекту добавлять собственную metadata. Infrastructure этого модуля читает только `,(0,c.jsx)(t.code,{children:`persist`}),` и `,(0,c.jsx)(t.code,{children:`sessionScoped`}),`.`]}),`
348
+ `,(0,c.jsx)(t.h3,{id:`persistedquerymeta`,children:(0,c.jsx)(t.code,{children:`persistedQueryMeta`})}),`
349
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const persistedQueryMeta = { persist: true } as const;
350
+ `})}),`
351
+ `,(0,c.jsx)(t.p,{children:`Применение:`}),`
352
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { queryOptions } from "@tanstack/react-query";
353
+ import { persistedQueryMeta } from "@ryuzaki13/react-foundation-lib/query-client";
354
+
355
+ export function dictionaryQueryOptions(dictionaryId: string) {
356
+ return queryOptions({
357
+ queryKey: ["dictionary", dictionaryId] as const,
358
+ queryFn: () => fetchDictionary(dictionaryId),
359
+ meta: persistedQueryMeta,
360
+ staleTime: 8 * 60 * 60 * 1000
361
+ });
362
+ }
363
+ `})}),`
364
+ `,(0,c.jsx)(t.p,{children:`Meta только разрешает persistence. Она не гарантирует, что IndexedDB доступен и запись уже создана.`}),`
365
+ `,(0,c.jsx)(t.h3,{id:`sessionscopedquerymeta`,children:(0,c.jsx)(t.code,{children:`sessionScopedQueryMeta`})}),`
366
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const sessionScopedQueryMeta = { sessionScoped: true } as const;
367
+ `})}),`
368
+ `,(0,c.jsx)(t.p,{children:`Используйте для data, содержимое или доступность которых зависит от server session, пользователя или роли:`}),`
369
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`queryOptions({
370
+ queryKey: ["current-user", "permissions"] as const,
371
+ queryFn: fetchCurrentPermissions,
372
+ meta: sessionScopedQueryMeta
373
+ });
374
+ `})}),`
375
+ `,(0,c.jsxs)(t.p,{children:[`При смене сессии host обязан вызвать `,(0,c.jsx)(t.code,{children:`resetSessionScopedQueries`}),`.`]}),`
376
+ `,(0,c.jsx)(t.h3,{id:`meta-нельзя-совмещать`,children:`Meta нельзя совмещать`}),`
377
+ `,(0,c.jsx)(t.p,{children:`Неверно:`}),`
378
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`meta: {
379
+ persist: true,
380
+ sessionScoped: true
381
+ }
382
+ `})}),`
383
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`shouldPersistQuery`}),` всё равно не сохранит такую query, потому что session scope имеет приоритет безопасности.`]}),`
384
+ `,(0,c.jsx)(t.p,{children:`Используйте один смысл:`}),`
385
+ `,(0,c.jsxs)(t.ul,{children:[`
386
+ `,(0,c.jsxs)(t.li,{children:[`публичный/безопасный долгоживущий справочник → `,(0,c.jsx)(t.code,{children:`persistedQueryMeta`}),`;`]}),`
387
+ `,(0,c.jsxs)(t.li,{children:[`auth/session dependent data → `,(0,c.jsx)(t.code,{children:`sessionScopedQueryMeta`}),`;`]}),`
388
+ `,(0,c.jsx)(t.li,{children:`обычные transient data → meta не нужна.`}),`
389
+ `]}),`
390
+ `,(0,c.jsx)(t.h2,{id:`настройка-project-adapter`,children:`Настройка project adapter`}),`
391
+ `,(0,c.jsx)(t.h3,{id:`зачем-нужен-adapter`,children:`Зачем нужен adapter`}),`
392
+ `,(0,c.jsxs)(t.p,{children:[`Foundation-пакет не знает имя SAP system, client, tenant или окружение конкретного приложения. `,(0,c.jsx)(t.code,{children:`QueryPersistenceProjectAdapter`}),` позволяет host передать устойчивый namespace и собственный buster:`]}),`
393
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type QueryPersistenceProjectAdapter = {
394
+ persistenceBuster?: string;
395
+ resolveSystemIdentifier?: () => string | null | undefined;
396
+ };
397
+ `})}),`
398
+ `,(0,c.jsx)(t.h3,{id:`configurequerypersistenceprojectadapter`,children:(0,c.jsx)(t.code,{children:`configureQueryPersistenceProjectAdapter`})}),`
399
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`configureQueryPersistenceProjectAdapter({
400
+ persistenceBuster: "sales-app.query.v2",
401
+ resolveSystemIdentifier: () => {
402
+ return "sap-prd:300";
403
+ }
404
+ });
405
+ `})}),`
406
+ `,(0,c.jsx)(t.p,{children:`Вызов заменяет весь текущий adapter. Это global module state, а не merge:`}),`
407
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`configureQueryPersistenceProjectAdapter({
408
+ persistenceBuster: "v2"
409
+ });
410
+
411
+ // Предыдущий resolveSystemIdentifier больше не установлен.
412
+ `})}),`
413
+ `,(0,c.jsx)(t.h3,{id:`порядок-инициализации`,children:`Порядок инициализации`}),`
414
+ `,(0,c.jsxs)(t.p,{children:[`Настройте adapter до первого `,(0,c.jsx)(t.code,{children:`createQueryClient`}),` или `,(0,c.jsx)(t.code,{children:`createIndexedDbQueryStorage`}),`:`]}),`
415
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`configureQueryPersistenceProjectAdapter(adapter);
416
+ const queryClient = createQueryClient({});
417
+ `})}),`
418
+ `,(0,c.jsx)(t.p,{children:`Иначе storage/persister уже получит default namespace и buster.`}),`
419
+ `,(0,c.jsx)(t.h3,{id:`getquerypersistenceprojectadapter`,children:(0,c.jsx)(t.code,{children:`getQueryPersistenceProjectAdapter`})}),`
420
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const adapter = getQueryPersistenceProjectAdapter();
421
+ `})}),`
422
+ `,(0,c.jsxs)(t.p,{children:[`Возвращает текущий object по ссылке. Не мутируйте его. Для изменения вызывайте `,(0,c.jsx)(t.code,{children:`configureQueryPersistenceProjectAdapter`}),` с новым object.`]}),`
423
+ `,(0,c.jsx)(t.h3,{id:`выбор-system-identifier`,children:`Выбор system identifier`}),`
424
+ `,(0,c.jsx)(t.p,{children:`Правило по умолчанию:`}),`
425
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`development: dev:{APP_ID}
426
+
427
+ production:
428
+ adapter.resolveSystemIdentifier() → если вернулась непустая строка
429
+ иначе {APP_ID}:{window.location.hostname}
430
+
431
+ server-like runtime без window:
432
+ {APP_ID}:server
433
+ `})}),`
434
+ `,(0,c.jsx)(t.p,{children:`Примеры:`}),`
435
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`dev:sales-app
436
+ sap-prd:300
437
+ sales-app:portal.company.local
438
+ sales-app:server
439
+ `})}),`
440
+ `,(0,c.jsx)(t.p,{children:`Identifier становится именем IndexedDB database и prefix persisted query keys. Он должен разделять окружения, между которыми нельзя переиспользовать data.`}),`
441
+ `,(0,c.jsx)(t.p,{children:`Resolver может быть вызван больше одного раза при создании storage и persister. Он должен быть чистым и возвращать одинаковый identifier в рамках одного application runtime.`}),`
442
+ `,(0,c.jsx)(t.p,{children:`Не включайте в identifier username, token или персональные данные. Для пользовательских данных persistence вообще не должна использоваться.`}),`
443
+ `,(0,c.jsx)(t.h3,{id:`выбор-buster`,children:`Выбор buster`}),`
444
+ `,(0,c.jsx)(t.p,{children:`При создании persister используется:`}),`
445
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`adapter.persistenceBuster ?? REACT_QUERY_PERSISTENCE_BUSTER
446
+ `})}),`
447
+ `,(0,c.jsxs)(t.p,{children:[`Пустая строка считается явным adapter value, потому что используется `,(0,c.jsx)(t.code,{children:`??`}),`, а не truthy-проверка. На практике задавайте непустую версионированную строку.`]}),`
448
+ `,(0,c.jsx)(t.h2,{id:`indexeddb-persistence`,children:`IndexedDB persistence`}),`
449
+ `,(0,c.jsx)(t.h3,{id:`зачем-indexeddb`,children:`Зачем IndexedDB`}),`
450
+ `,(0,c.jsxs)(t.p,{children:[`IndexedDB подходит для больших асинхронных browser data лучше, чем `,(0,c.jsx)(t.code,{children:`localStorage`}),`:`]}),`
451
+ `,(0,c.jsxs)(t.ul,{children:[`
452
+ `,(0,c.jsx)(t.li,{children:`операции асинхронные;`}),`
453
+ `,(0,c.jsx)(t.li,{children:`значения хранятся через structured clone;`}),`
454
+ `,(0,c.jsx)(t.li,{children:`можно хранить object без ручного JSON stringify;`}),`
455
+ `,(0,c.jsxs)(t.li,{children:[`storage не блокирует основной поток синхронным API `,(0,c.jsx)(t.code,{children:`localStorage`}),`;`]}),`
456
+ `,(0,c.jsx)(t.li,{children:`объём обычно лучше подходит тяжёлым справочникам.`}),`
457
+ `]}),`
458
+ `,(0,c.jsx)(t.p,{children:`Persistence не означает, что любой большой payload автоматически безопасен. Объём, чувствительность и свежесть остаются решением consumer-а.`}),`
459
+ `,(0,c.jsx)(t.h3,{id:`opt-in-правило`,children:`Opt-in правило`}),`
460
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function shouldPersistQuery(query: Pick<Query, "meta">): boolean;
461
+ `})}),`
462
+ `,(0,c.jsxs)(t.p,{children:[`Результат `,(0,c.jsx)(t.code,{children:`true`}),` только когда:`]}),`
463
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`query.meta?.persist === true && query.meta.sessionScoped !== true;
464
+ `})}),`
465
+ `,(0,c.jsx)(t.p,{children:`Примеры:`}),`
466
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Meta`}),(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:`undefined`})}),(0,c.jsx)(t.td,{children:`Нет`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`{ persist: false }`})}),(0,c.jsx)(t.td,{children:`Нет`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`{ persist: true }`})}),(0,c.jsx)(t.td,{children:`Да`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`{ sessionScoped: true }`})}),(0,c.jsx)(t.td,{children:`Нет`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`{ persist: true, sessionScoped: true }`})}),(0,c.jsx)(t.td,{children:`Нет`})]})]})]}),`
467
+ `,(0,c.jsx)(t.h3,{id:`createindexeddbquerystorage`,children:(0,c.jsx)(t.code,{children:`createIndexedDbQueryStorage`})}),`
468
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const storage = createIndexedDbQueryStorage<MyValue>({
469
+ dbName: "sales-query-cache",
470
+ storeName: "queries",
471
+ indexedDB: customFactory
472
+ });
473
+ `})}),`
474
+ `,(0,c.jsx)(t.p,{children:`Options structurally имеют форму:`}),`
475
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type Options = {
476
+ dbName?: string;
477
+ storeName?: string;
478
+ indexedDB?: IDBFactory;
479
+ };
480
+ `})}),`
481
+ `,(0,c.jsx)(t.p,{children:`Сам тип options не экспортируется, но TypeScript выводит его из signature.`}),`
482
+ `,(0,c.jsxs)(t.p,{children:[`Если explicit `,(0,c.jsx)(t.code,{children:`indexedDB`}),` не передан, используется `,(0,c.jsx)(t.code,{children:`globalThis.indexedDB`}),`.`]}),`
483
+ `,(0,c.jsxs)(t.p,{children:[`Если factory отсутствует, возвращается `,(0,c.jsx)(t.code,{children:`undefined`}),`:`]}),`
484
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const storage = createIndexedDbQueryStorage();
485
+
486
+ if (!storage) {
487
+ console.info("IndexedDB недоступен, persistence отключена");
488
+ }
489
+ `})}),`
490
+ `,(0,c.jsx)(t.h3,{id:`default-database-и-object-store`,children:`Default database и object store`}),`
491
+ `,(0,c.jsxs)(t.ul,{children:[`
492
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`dbName`}),` по умолчанию равен system identifier;`]}),`
493
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`storeName`}),` по умолчанию равен `,(0,c.jsx)(t.code,{children:`queries`}),`;`]}),`
494
+ `,(0,c.jsxs)(t.li,{children:[`database открывается с версией `,(0,c.jsx)(t.code,{children:`1`}),`;`]}),`
495
+ `,(0,c.jsxs)(t.li,{children:[`object store создаётся в `,(0,c.jsx)(t.code,{children:`onupgradeneeded`}),`;`]}),`
496
+ `,(0,c.jsx)(t.li,{children:`открытая database promise переиспользуется внутри storage instance.`}),`
497
+ `]}),`
498
+ `,(0,c.jsxs)(t.p,{children:[`Если вы выбираете custom `,(0,c.jsx)(t.code,{children:`storeName`}),` для уже существующей database версии `,(0,c.jsx)(t.code,{children:`1`}),`, browser может не вызвать upgrade, и store не будет создан. Для custom layout используйте отдельный `,(0,c.jsx)(t.code,{children:`dbName`}),` либо заранее управляемую migration/version strategy.`]}),`
499
+ `,(0,c.jsxs)(t.p,{children:[`У storage нет публичного `,(0,c.jsx)(t.code,{children:`close()`}),`. Обычно instance живёт вместе с persister всё время работы SPA.`]}),`
500
+ `,(0,c.jsx)(t.h3,{id:`storage-api`,children:`Storage API`}),`
501
+ `,(0,c.jsxs)(t.p,{children:[`Возвращаемый adapter совместим с TanStack `,(0,c.jsx)(t.code,{children:`AsyncStorage`}),`:`]}),`
502
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type QueryStorage<T> = {
503
+ getItem(key: string): Promise<T | undefined | null>;
504
+ setItem(key: string, value: T): Promise<unknown>;
505
+ removeItem(key: string): Promise<void>;
506
+ entries?(): Promise<Array<[key: string, value: T]>>;
507
+ };
508
+ `})}),`
509
+ `,(0,c.jsx)(t.p,{children:`Пример прямой работы:`}),`
510
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const storage = createIndexedDbQueryStorage<string>({
511
+ dbName: "query-storage-demo"
512
+ });
513
+
514
+ if (storage) {
515
+ await storage.setItem("first", "один");
516
+ const value = await storage.getItem("first");
517
+ const all = await storage.entries?.();
518
+ await storage.removeItem("first");
519
+ }
520
+ `})}),`
521
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`setItem`}),` завершается после окончания write transaction, а не сразу после `,(0,c.jsx)(t.code,{children:`store.put`}),`.`]}),`
522
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`entries`}),` читает object store через cursor. Не завязывайте бизнес-логику на предполагаемый insertion order: порядок определяется IndexedDB keys/cursor.`]}),`
523
+ `,(0,c.jsx)(t.h3,{id:`ошибки-indexeddb`,children:`Ошибки IndexedDB`}),`
524
+ `,(0,c.jsx)(t.p,{children:`Storage не скрывает ошибки. Promise отклоняется, если:`}),`
525
+ `,(0,c.jsxs)(t.ul,{children:[`
526
+ `,(0,c.jsx)(t.li,{children:`database open заблокирован;`}),`
527
+ `,(0,c.jsx)(t.li,{children:`browser отказал в storage;`}),`
528
+ `,(0,c.jsx)(t.li,{children:`transaction завершилась error/abort;`}),`
529
+ `,(0,c.jsx)(t.li,{children:`значение не поддерживает structured clone;`}),`
530
+ `,(0,c.jsx)(t.li,{children:`object store отсутствует;`}),`
531
+ `,(0,c.jsx)(t.li,{children:`превышена quota.`}),`
532
+ `]}),`
533
+ `,(0,c.jsx)(t.p,{children:`Не превращайте такую ошибку молча в успешную запись. На уровне приложения выберите diagnostics и degradation policy.`}),`
534
+ `,(0,c.jsx)(t.h3,{id:`createreactquerypersister`,children:(0,c.jsx)(t.code,{children:`createReactQueryPersister`})}),`
535
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const persister = createReactQueryPersister();
536
+ `})}),`
537
+ `,(0,c.jsxs)(t.p,{children:[`Возвращает результат `,(0,c.jsx)(t.code,{children:`experimental_createQueryPersister`}),` либо `,(0,c.jsx)(t.code,{children:`undefined`}),`, если IndexedDB недоступен.`]}),`
538
+ `,(0,c.jsx)(t.p,{children:`Настройки foundation wrapper:`}),`
539
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`{
540
+ serialize: (query) => query,
541
+ deserialize: (query) => query,
542
+ prefix: systemIdentifier,
543
+ buster: adapterBuster ?? buildTimeBuster,
544
+ maxAge: 90 * 24 * 60 * 60 * 1000,
545
+ refetchOnRestore: true,
546
+ filters: { predicate: shouldPersistQuery }
547
+ }
548
+ `})}),`
549
+ `,(0,c.jsxs)(t.p,{children:[`API TanStack помечено `,(0,c.jsx)(t.code,{children:`experimental`}),`. При обновлении major/minor версии peer dependency повторно проверяйте совместимость.`]}),`
550
+ `,(0,c.jsx)(t.h3,{id:`constants`,children:`Constants`}),`
551
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`REACT_QUERY_PERSISTENCE_BUSTER;
552
+ REACT_QUERY_PERSISTENCE_MAX_AGE;
553
+ `})}),`
554
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`REACT_QUERY_PERSISTENCE_MAX_AGE`}),` равен 90 дням в миллисекундах:`]}),`
555
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`90 * 24 * 60 * 60 * 1000;
556
+ `})}),`
557
+ `,(0,c.jsxs)(t.p,{children:[`Это максимальный возраст persisted записи, а не `,(0,c.jsx)(t.code,{children:`staleTime`}),` и не `,(0,c.jsx)(t.code,{children:`gcTime`}),`.`]}),`
558
+ `,(0,c.jsx)(t.h3,{id:`ключ-записи`,children:`Ключ записи`}),`
559
+ `,(0,c.jsx)(t.p,{children:`TanStack persister использует форму:`}),`
560
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`{prefix}-{queryHash}
561
+ `})}),`
562
+ `,(0,c.jsx)(t.p,{children:`Например:`}),`
563
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`sap-prd:300-["odata","metadata",{"service":"REPORT_SRV"}]
564
+ `})}),`
565
+ `,(0,c.jsxs)(t.p,{children:[`Фактический `,(0,c.jsx)(t.code,{children:`queryHash`}),` создаёт TanStack Query. Не собирайте persistent key вручную.`]}),`
566
+ `,(0,c.jsx)(t.h3,{id:`что-хранится`,children:`Что хранится`}),`
567
+ `,(0,c.jsxs)(t.p,{children:[`Каждая запись содержит `,(0,c.jsx)(t.code,{children:`PersistedQuery`}),` с query key, hash, state и buster. `,(0,c.jsx)(t.code,{children:`state`}),` включает data и timestamps TanStack Query.`]}),`
568
+ `,(0,c.jsx)(t.p,{children:`Wrapper не делает JSON serialization:`}),`
569
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`serialize: (query) => query;
570
+ deserialize: (query) => query;
571
+ `})}),`
572
+ `,(0,c.jsx)(t.p,{children:`Следствие: data должна поддерживать browser structured clone.`}),`
573
+ `,(0,c.jsx)(t.p,{children:`Не сохраняйте:`}),`
574
+ `,(0,c.jsxs)(t.ul,{children:[`
575
+ `,(0,c.jsx)(t.li,{children:`functions;`}),`
576
+ `,(0,c.jsx)(t.li,{children:`DOM nodes;`}),`
577
+ `,(0,c.jsx)(t.li,{children:`React elements;`}),`
578
+ `,(0,c.jsx)(t.li,{children:`QueryClient;`}),`
579
+ `,(0,c.jsx)(t.li,{children:`AbortController/Signal как часть data;`}),`
580
+ `,(0,c.jsx)(t.li,{children:`class instances с важным prototype behavior;`}),`
581
+ `,(0,c.jsx)(t.li,{children:`несериализуемые runtime handles.`}),`
582
+ `]}),`
583
+ `,(0,c.jsx)(t.h3,{id:`жизненный-цикл-восстановления`,children:`Жизненный цикл восстановления`}),`
584
+ `,(0,c.jsxs)(t.p,{children:[`Для query с `,(0,c.jsx)(t.code,{children:`meta: persistedQueryMeta`}),`:`]}),`
585
+ `,(0,c.jsxs)(t.ol,{children:[`
586
+ `,(0,c.jsx)(t.li,{children:`TanStack Query создаёт query в памяти.`}),`
587
+ `,(0,c.jsxs)(t.li,{children:[`Persister проверяет `,(0,c.jsx)(t.code,{children:`shouldPersistQuery`}),`.`]}),`
588
+ `,(0,c.jsxs)(t.li,{children:[`По `,(0,c.jsx)(t.code,{children:`prefix-queryHash`}),` читается IndexedDB.`]}),`
589
+ `,(0,c.jsx)(t.li,{children:`Проверяются buster и возраст записи.`}),`
590
+ `,(0,c.jsx)(t.li,{children:`Подходящее state восстанавливается в Query cache.`}),`
591
+ `,(0,c.jsxs)(t.li,{children:[`Если восстановленная query stale, `,(0,c.jsx)(t.code,{children:`refetchOnRestore: true`}),` запускает обновление.`]}),`
592
+ `,(0,c.jsxs)(t.li,{children:[`Если записи нет/она протухла/не совпал buster, выполняется обычный `,(0,c.jsx)(t.code,{children:`queryFn`}),`.`]}),`
593
+ `,(0,c.jsx)(t.li,{children:`Успешный результат планируется к записи в IndexedDB.`}),`
594
+ `]}),`
595
+ `,(0,c.jsx)(t.p,{children:`Восстановление per-query ленивое. Модуль не гидратирует весь persistent cache целиком при старте приложения.`}),`
596
+ `,(0,c.jsx)(t.h3,{id:`freshness-три-разных-времени`,children:`Freshness: три разных времени`}),`
597
+ `,(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.jsxs)(t.tbody,{children:[(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`staleTime`}),` query`]}),(0,c.jsx)(t.td,{children:`Когда data перестаёт считаться fresh.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`gcTime`}),` query`]}),(0,c.jsx)(t.td,{children:`Когда неактивная query удаляется из memory cache.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`REACT_QUERY_PERSISTENCE_MAX_AGE`})}),(0,c.jsx)(t.td,{children:`Когда IndexedDB запись слишком стара для restore.`})]})]})]}),`
598
+ `,(0,c.jsx)(t.p,{children:`Пример:`}),`
599
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`queryOptions({
600
+ queryKey: ["dictionary", "materials"],
601
+ queryFn: fetchMaterials,
602
+ meta: persistedQueryMeta,
603
+ staleTime: 2 * 60 * 60 * 1000,
604
+ gcTime: 30 * 60 * 1000
605
+ });
606
+ `})}),`
607
+ `,(0,c.jsxs)(t.ul,{children:[`
608
+ `,(0,c.jsx)(t.li,{children:`Через reload в течение двух часов data может восстановиться fresh без немедленного refetch.`}),`
609
+ `,(0,c.jsx)(t.li,{children:`После двух часов data может восстановиться, но stale query будет обновлена.`}),`
610
+ `,(0,c.jsx)(t.li,{children:`После 90 дней запись не должна использоваться.`}),`
611
+ `]}),`
612
+ `,(0,c.jsx)(t.h3,{id:`хорошие-кандидаты-для-persistence`,children:`Хорошие кандидаты для persistence`}),`
613
+ `,(0,c.jsxs)(t.ul,{children:[`
614
+ `,(0,c.jsx)(t.li,{children:`публичные справочники;`}),`
615
+ `,(0,c.jsx)(t.li,{children:`metadata/schema, одинаковые для пользователей в namespace;`}),`
616
+ `,(0,c.jsx)(t.li,{children:`тяжёлые редко меняющиеся reference data;`}),`
617
+ `,(0,c.jsx)(t.li,{children:`data с ясной invalidation/version strategy;`}),`
618
+ `,(0,c.jsx)(t.li,{children:`payload без чувствительной информации.`}),`
619
+ `]}),`
620
+ `,(0,c.jsx)(t.h3,{id:`плохие-кандидаты`,children:`Плохие кандидаты`}),`
621
+ `,(0,c.jsxs)(t.ul,{children:[`
622
+ `,(0,c.jsx)(t.li,{children:`текущий пользователь и permissions;`}),`
623
+ `,(0,c.jsx)(t.li,{children:`персональные списки;`}),`
624
+ `,(0,c.jsx)(t.li,{children:`form draft;`}),`
625
+ `,(0,c.jsx)(t.li,{children:`результаты поиска с большим числом уникальных keys;`}),`
626
+ `,(0,c.jsx)(t.li,{children:`быстро меняющиеся balances/statuses;`}),`
627
+ `,(0,c.jsx)(t.li,{children:`auth/session dependent response;`}),`
628
+ `,(0,c.jsx)(t.li,{children:`data без понятного срока свежести;`}),`
629
+ `,(0,c.jsx)(t.li,{children:`огромные таблицы, сохраняемые «на всякий случай».`}),`
630
+ `]}),`
631
+ `,(0,c.jsx)(t.h3,{id:`devtools`,children:`DevTools`}),`
632
+ `,(0,c.jsx)(t.p,{children:`В browser DevTools:`}),`
633
+ `,(0,c.jsxs)(t.ol,{children:[`
634
+ `,(0,c.jsxs)(t.li,{children:[`Откройте `,(0,c.jsx)(t.code,{children:`Application`}),`.`]}),`
635
+ `,(0,c.jsxs)(t.li,{children:[`Выберите `,(0,c.jsx)(t.code,{children:`IndexedDB`}),`.`]}),`
636
+ `,(0,c.jsx)(t.li,{children:`Найдите database по system identifier.`}),`
637
+ `,(0,c.jsxs)(t.li,{children:[`Откройте object store `,(0,c.jsx)(t.code,{children:`queries`}),`.`]}),`
638
+ `,(0,c.jsx)(t.li,{children:`Проверьте keys, buster и query state.`}),`
639
+ `]}),`
640
+ `,(0,c.jsx)(t.p,{children:`Удаление database в DevTools — ручной способ очистить persistent cache текущего namespace. Не делайте автоматическую очистку всех databases другого приложения.`}),`
641
+ `,(0,c.jsx)(t.h2,{id:`session-scoped-query`,children:`Session-scoped query`}),`
642
+ `,(0,c.jsx)(t.h3,{id:`когда-нужна-session-scope`,children:`Когда нужна session scope`}),`
643
+ `,(0,c.jsx)(t.p,{children:`Query считается session-scoped, если data или право на чтение меняется вместе с server session:`}),`
644
+ `,(0,c.jsxs)(t.ul,{children:[`
645
+ `,(0,c.jsx)(t.li,{children:`текущий пользователь;`}),`
646
+ `,(0,c.jsx)(t.li,{children:`permissions/roles;`}),`
647
+ `,(0,c.jsx)(t.li,{children:`персональные настройки с backend;`}),`
648
+ `,(0,c.jsx)(t.li,{children:`доступные пользователю операции;`}),`
649
+ `,(0,c.jsx)(t.li,{children:`auth-dependent списки;`}),`
650
+ `,(0,c.jsx)(t.li,{children:`данные, которые нельзя показывать следующему вошедшему пользователю.`}),`
651
+ `]}),`
652
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { sessionScopedQueryMeta } from "@ryuzaki13/react-foundation-lib/query-client";
653
+
654
+ const permissionsOptions = queryOptions({
655
+ queryKey: ["session", "permissions"] as const,
656
+ queryFn: fetchPermissions,
657
+ meta: sessionScopedQueryMeta,
658
+ staleTime: Infinity
659
+ });
660
+ `})}),`
661
+ `,(0,c.jsx)(t.p,{children:`Meta сама ничего не сбрасывает. Host должен вызвать reset на boundary смены сессии.`}),`
662
+ `,(0,c.jsx)(t.h3,{id:`issessionscopedquery`,children:(0,c.jsx)(t.code,{children:`isSessionScopedQuery`})}),`
663
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function isSessionScopedQuery(query: Pick<Query, "meta">): boolean;
664
+ `})}),`
665
+ `,(0,c.jsxs)(t.p,{children:[`Возвращает `,(0,c.jsx)(t.code,{children:`true`}),` только для строгого `,(0,c.jsx)(t.code,{children:`meta.sessionScoped === true`}),`.`]}),`
666
+ `,(0,c.jsx)(t.h3,{id:`resetsessionscopedqueries`,children:(0,c.jsx)(t.code,{children:`resetSessionScopedQueries`})}),`
667
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`await resetSessionScopedQueries(queryClient);
668
+ `})}),`
669
+ `,(0,c.jsx)(t.p,{children:`Вызывайте после:`}),`
670
+ `,(0,c.jsxs)(t.ul,{children:[`
671
+ `,(0,c.jsx)(t.li,{children:`успешного входа другим пользователем;`}),`
672
+ `,(0,c.jsx)(t.li,{children:`выхода;`}),`
673
+ `,(0,c.jsx)(t.li,{children:`обновления server session, меняющего доступ;`}),`
674
+ `,(0,c.jsx)(t.li,{children:`потери авторизации;`}),`
675
+ `,(0,c.jsx)(t.li,{children:`смены роли/контекста, если старые данные более недопустимы.`}),`
676
+ `]}),`
677
+ `,(0,c.jsx)(t.p,{children:`Порядок работы:`}),`
678
+ `,(0,c.jsxs)(t.ol,{children:[`
679
+ `,(0,c.jsxs)(t.li,{children:[`Находит только query с `,(0,c.jsx)(t.code,{children:`sessionScoped === true`}),`.`]}),`
680
+ `,(0,c.jsx)(t.li,{children:`Отменяет их незавершённые requests.`}),`
681
+ `,(0,c.jsx)(t.li,{children:`Неактивные query полностью удаляет из QueryCache.`}),`
682
+ `,(0,c.jsxs)(t.li,{children:[`Query с observer очищает: reset + `,(0,c.jsx)(t.code,{children:`data: undefined`}),`, `,(0,c.jsx)(t.code,{children:`dataUpdatedAt: 0`}),`.`]}),`
683
+ `,(0,c.jsx)(t.li,{children:`Повторно запрашивает только active session-scoped query.`}),`
684
+ `,(0,c.jsx)(t.li,{children:`По умолчанию отправляет другим вкладкам команду такого же reset.`}),`
685
+ `]}),`
686
+ `,(0,c.jsx)(t.p,{children:`Обычные публичные query остаются нетронутыми.`}),`
687
+ `,(0,c.jsx)(t.h3,{id:`почему-observed-query-не-удаляется`,children:`Почему observed query не удаляется`}),`
688
+ `,(0,c.jsxs)(t.p,{children:[`Смонтированный `,(0,c.jsx)(t.code,{children:`useQuery`}),` владеет observer. Удаление query прямо из-под активного observer может привести к сложному lifecycle. Поэтому entry сохраняется, но старые data явно очищаются и active query перезапрашивается.`]}),`
689
+ `,(0,c.jsx)(t.h3,{id:`disabled-query`,children:`Disabled query`}),`
690
+ `,(0,c.jsxs)(t.p,{children:[`Query с observer и `,(0,c.jsx)(t.code,{children:`enabled: false`}),` очищается, но не refetch-ится:`]}),`
691
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`до reset: cache data есть, observer disabled
692
+ после reset: query entry есть, data = undefined
693
+ queryFn повторно не вызвана
694
+ `})}),`
695
+ `,(0,c.jsx)(t.p,{children:`Это ожидаемо: disabled query не считается active для refetch.`}),`
696
+ `,(0,c.jsx)(t.h3,{id:`hydrated-data`,children:`Hydrated data`}),`
697
+ `,(0,c.jsxs)(t.p,{children:[`Модуль явно обнуляет `,(0,c.jsx)(t.code,{children:`data`}),` после `,(0,c.jsx)(t.code,{children:`query.reset()`}),`. Это защищает от ситуации, когда hydrated initial state прежней сессии мог бы вернуться после обычного reset.`]}),`
698
+ `,(0,c.jsx)(t.p,{children:`Foundation-пакет не включает SSR самостоятельно, но helper безопасно очищает такой cache state, если consumer использует TanStack dehydration/hydration.`}),`
699
+ `,(0,c.jsx)(t.h3,{id:`options`,children:`Options`}),`
700
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type ResetSessionScopedQueriesOptions = {
701
+ readonly broadcast?: boolean;
702
+ };
703
+ `})}),`
704
+ `,(0,c.jsxs)(t.p,{children:[`По умолчанию `,(0,c.jsx)(t.code,{children:`broadcast`}),` включён:`]}),`
705
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`await resetSessionScopedQueries(queryClient);
706
+ `})}),`
707
+ `,(0,c.jsxs)(t.p,{children:[`Для обработки уже полученной remote-команды нужен `,(0,c.jsx)(t.code,{children:`false`}),`, чтобы не создать цикл:`]}),`
708
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`await resetSessionScopedQueries(queryClient, { broadcast: false });
709
+ `})}),`
710
+ `,(0,c.jsxs)(t.p,{children:[`Обычно consumer вручную не передаёт `,(0,c.jsx)(t.code,{children:`false`}),`: это делает `,(0,c.jsx)(t.code,{children:`installSessionScopedQueryReset`}),` внутри message handler.`]}),`
711
+ `,(0,c.jsx)(t.h3,{id:`installsessionscopedqueryreset`,children:(0,c.jsx)(t.code,{children:`installSessionScopedQueryReset`})}),`
712
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const installed = installSessionScopedQueryReset(queryClient, {
713
+ channelName: "sales-app:session-cache-reset"
714
+ });
715
+ `})}),`
716
+ `,(0,c.jsx)(t.p,{children:`Типы:`}),`
717
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type InstallSessionScopedQueryResetOptions = {
718
+ readonly channelName: string;
719
+ };
720
+
721
+ type InstalledSessionScopedQueryReset = {
722
+ readonly cleanup: () => void;
723
+ };
724
+ `})}),`
725
+ `,(0,c.jsx)(t.p,{children:`Канал передаёт только command:`}),`
726
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`{
727
+ type: "session-scoped-query-cache-reset";
728
+ }
729
+ `})}),`
730
+ `,(0,c.jsx)(t.p,{children:`Он не передаёт:`}),`
731
+ `,(0,c.jsxs)(t.ul,{children:[`
732
+ `,(0,c.jsx)(t.li,{children:`query data;`}),`
733
+ `,(0,c.jsx)(t.li,{children:`user id;`}),`
734
+ `,(0,c.jsx)(t.li,{children:`auth payload;`}),`
735
+ `,(0,c.jsx)(t.li,{children:`token;`}),`
736
+ `,(0,c.jsx)(t.li,{children:`session details.`}),`
737
+ `]}),`
738
+ `,(0,c.jsx)(t.p,{children:`Это безопаснее полной передачи session cache между вкладками.`}),`
739
+ `,(0,c.jsx)(t.h3,{id:`browser-fallback`,children:`Browser fallback`}),`
740
+ `,(0,c.jsxs)(t.p,{children:[`Если отсутствует `,(0,c.jsx)(t.code,{children:`window`}),` или `,(0,c.jsx)(t.code,{children:`BroadcastChannel`}),`, installation возвращает no-op cleanup:`]}),`
741
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const { cleanup } = installSessionScopedQueryReset(queryClient, options);
742
+ cleanup(); // безопасно
743
+ `})}),`
744
+ `,(0,c.jsxs)(t.p,{children:[`Локальный `,(0,c.jsx)(t.code,{children:`resetSessionScopedQueries`}),` всё равно работает без browser channel.`]}),`
745
+ `,(0,c.jsx)(t.h3,{id:`lifecycle-и-global-sender`,children:`Lifecycle и global sender`}),`
746
+ `,(0,c.jsxs)(t.p,{children:[`Внутри модуля хранится одна global функция рассылки session reset. Последняя успешная installation становится активной для `,(0,c.jsx)(t.code,{children:`resetSessionScopedQueries`}),`.`]}),`
747
+ `,(0,c.jsx)(t.p,{children:`Поэтому в одном application runtime обычно устанавливают один session channel.`}),`
748
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`cleanup()`}),`:`]}),`
749
+ `,(0,c.jsxs)(t.ul,{children:[`
750
+ `,(0,c.jsxs)(t.li,{children:[`удаляет `,(0,c.jsx)(t.code,{children:`onmessage`}),`;`]}),`
751
+ `,(0,c.jsx)(t.li,{children:`закрывает BroadcastChannel;`}),`
752
+ `,(0,c.jsx)(t.li,{children:`очищает global sender, если он всё ещё принадлежит этой installation.`}),`
753
+ `]}),`
754
+ `,(0,c.jsx)(t.h3,{id:`пример-auth-boundary`,children:`Пример auth boundary`}),`
755
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`async function completeLogin() {
756
+ await establishServerSession();
757
+ await resetSessionScopedQueries(queryClient);
758
+ }
759
+
760
+ async function logout() {
761
+ await closeServerSession();
762
+ await resetSessionScopedQueries(queryClient);
763
+ }
764
+ `})}),`
765
+ `,(0,c.jsx)(t.p,{children:`Сначала измените server session, затем перезапрашивайте active query. Иначе refetch может снова получить data старой сессии.`}),`
766
+ `,(0,c.jsx)(t.h3,{id:`почему-session-query-нельзя-persist`,children:`Почему session query нельзя persist`}),`
767
+ `,(0,c.jsx)(t.p,{children:`IndexedDB переживает reload и может остаться после logout. Даже если UI очищает memory cache, persisted персональная запись представляет риск утечки следующей сессии.`}),`
768
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`shouldPersistQuery`}),` защищает от случайного сочетания flags, но правильная metadata остаётся ответственностью owner-а query.`]}),`
769
+ `,(0,c.jsx)(t.h2,{id:`синхронизация-между-вкладками`,children:`Синхронизация между вкладками`}),`
770
+ `,(0,c.jsx)(t.h3,{id:`две-задачи-два-канала`,children:`Две задачи, два канала`}),`
771
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`installReactQueryBroadcast`}),` создаёт два разных механизма:`]}),`
772
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`queriesChannel
773
+ └─ experimental broadcastQueryClient
774
+ синхронизирует query cache data/state
775
+
776
+ eventsChannel
777
+ └─ собственный BroadcastChannel
778
+ передаёт invalidate/setQueryData commands
779
+ `})}),`
780
+ `,(0,c.jsx)(t.p,{children:`Разделение нужно потому, что experimental TanStack broadcast не обязан передавать прикладное invalidation так, как требуется host-приложению.`}),`
781
+ `,(0,c.jsx)(t.h3,{id:`установка`,children:`Установка`}),`
782
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const installed = installReactQueryBroadcast(queryClient, {
783
+ queriesChannel: "sales-app:query-data",
784
+ eventsChannel: "sales-app:query-events"
785
+ });
786
+ `})}),`
787
+ `,(0,c.jsx)(t.p,{children:`Структурные options:`}),`
788
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type InstallBroadcastOptions = {
789
+ queriesChannel: string;
790
+ eventsChannel: string;
791
+ };
792
+ `})}),`
793
+ `,(0,c.jsx)(t.p,{children:`Возвращается:`}),`
794
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type InstalledReactQueryBroadcast = {
795
+ broadcast: (event: CacheSyncEvent) => void;
796
+ cleanup: () => void;
797
+ };
798
+ `})}),`
799
+ `,(0,c.jsx)(t.p,{children:`Эти types не экспортируются отдельными именами, но TypeScript выводит их из function signature.`}),`
800
+ `,(0,c.jsx)(t.h3,{id:`имена-каналов`,children:`Имена каналов`}),`
801
+ `,(0,c.jsx)(t.p,{children:`Выбирайте стабильные и уникальные имена:`}),`
802
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`{app}:{environment}:query-data
803
+ {app}:{environment}:query-events
804
+ `})}),`
805
+ `,(0,c.jsx)(t.p,{children:`Не используйте один channel name для несвязанных приложений на одном origin. Иначе они могут прочитать чужие cache events.`}),`
806
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`queriesChannel`}),` и `,(0,c.jsx)(t.code,{children:`eventsChannel`}),` должны различаться: у них разные wire protocols.`]}),`
807
+ `,(0,c.jsx)(t.h3,{id:`cache-data-sync`,children:`Cache data sync`}),`
808
+ `,(0,c.jsx)(t.p,{children:`Foundation wrapper вызывает:`}),`
809
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`broadcastQueryClient({
810
+ queryClient,
811
+ broadcastChannel: queriesChannel
812
+ });
813
+ `})}),`
814
+ `,(0,c.jsxs)(t.p,{children:[`Успешные изменения Query cache, включая подходящий `,(0,c.jsx)(t.code,{children:`setQueryData`}),`, могут появиться в другой вкладке через experimental TanStack adapter.`]}),`
815
+ `,(0,c.jsx)(t.p,{children:`Это не сетевой refetch. Data передаётся browser channel между вкладками одного origin.`}),`
816
+ `,(0,c.jsx)(t.h3,{id:`автоматическая-invalidation`,children:`Автоматическая invalidation`}),`
817
+ `,(0,c.jsxs)(t.p,{children:[`Wrapper подписывается на `,(0,c.jsx)(t.code,{children:`queryClient.getQueryCache()`}),`.`]}),`
818
+ `,(0,c.jsxs)(t.p,{children:[`Когда локальная query получает action `,(0,c.jsx)(t.code,{children:`invalidate`}),`, wrapper автоматически отправляет event:`]}),`
819
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`{
820
+ type: "invalidate",
821
+ keys: [query.queryKey],
822
+ refetchType: "none"
823
+ }
824
+ `})}),`
825
+ `,(0,c.jsxs)(t.p,{children:[`Remote вкладка вызывает `,(0,c.jsx)(t.code,{children:`invalidateQueries`}),` для каждого key.`]}),`
826
+ `,(0,c.jsx)(t.p,{children:`Широкая локальная invalidation превращается в набор точечных events для query, которые реально обновились в cache отправителя.`}),`
827
+ `,(0,c.jsxs)(t.p,{children:[`Автоматическая отправка всегда использует `,(0,c.jsx)(t.code,{children:`refetchType: "none"`}),`. Если другой вкладке нужен active/inactive/all refetch, отправьте explicit event.`]}),`
828
+ `,(0,c.jsxs)(t.h3,{id:`event-invalidate`,children:[`Event `,(0,c.jsx)(t.code,{children:`invalidate`})]}),`
829
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`installed.broadcast({
830
+ type: "invalidate",
831
+ keys: [["products"], ["products", "summary"]],
832
+ refetchType: "active"
833
+ });
834
+ `})}),`
835
+ `,(0,c.jsxs)(t.p,{children:[`Поддерживаемые `,(0,c.jsx)(t.code,{children:`refetchType`}),`:`]}),`
836
+ `,(0,c.jsxs)(t.ul,{children:[`
837
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`"none"`}),`;`]}),`
838
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`"active"`}),`;`]}),`
839
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`"inactive"`}),`;`]}),`
840
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`"all"`}),`.`]}),`
841
+ `]}),`
842
+ `,(0,c.jsxs)(t.p,{children:[`Если field отсутствует, receiver использует `,(0,c.jsx)(t.code,{children:`"none"`}),`.`]}),`
843
+ `,(0,c.jsxs)(t.h3,{id:`event-setquerydata`,children:[`Event `,(0,c.jsx)(t.code,{children:`setQueryData`})]}),`
844
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`installed.broadcast({
845
+ type: "setQueryData",
846
+ key: ["products", "summary"],
847
+ data: { count: 42 }
848
+ });
849
+ `})}),`
850
+ `,(0,c.jsx)(t.p,{children:`Receiver вызывает:`}),`
851
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`queryClient.setQueryData(event.key, event.data);
852
+ `})}),`
853
+ `,(0,c.jsxs)(t.p,{children:[`Event принимает `,(0,c.jsx)(t.code,{children:`data: unknown`}),`. TypeScript не может доказать, что data соответствует cache type receiver-а. Sender обязан использовать правильный query key/data contract.`]}),`
854
+ `,(0,c.jsx)(t.h3,{id:`глобальный-imperative-api`,children:`Глобальный imperative API`}),`
855
+ `,(0,c.jsxs)(t.p,{children:[`Чтобы не передавать `,(0,c.jsx)(t.code,{children:`installed.broadcast`}),` через все слои:`]}),`
856
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`setBroadcastFn(installed.broadcast);
857
+
858
+ broadcastCacheEvent({
859
+ type: "invalidate",
860
+ keys: [["products"]],
861
+ refetchType: "none"
862
+ });
863
+ `})}),`
864
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`setBroadcastFn`}),` заменяет одну global handler-функцию. Это singleton module state.`]}),`
865
+ `,(0,c.jsxs)(t.p,{children:[`До установки handler `,(0,c.jsx)(t.code,{children:`broadcastCacheEvent`}),` ничего не делает:`]}),`
866
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`broadcastCacheEvent(event); // no-op, если handler ещё null
867
+ `})}),`
868
+ `,(0,c.jsx)(t.h3,{id:`cleanup-global-handler`,children:`Cleanup global handler`}),`
869
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`installed.cleanup()`}),` закрывает каналы, но не знает, передавали ли вы `,(0,c.jsx)(t.code,{children:`installed.broadcast`}),` в `,(0,c.jsx)(t.code,{children:`setBroadcastFn`}),`.`]}),`
870
+ `,(0,c.jsx)(t.p,{children:`Перед/после cleanup сбросьте global handler:`}),`
871
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`setBroadcastFn(() => undefined);
872
+ installed.cleanup();
873
+ `})}),`
874
+ `,(0,c.jsxs)(t.p,{children:[`Иначе поздний `,(0,c.jsx)(t.code,{children:`broadcastCacheEvent`}),` может попытаться использовать функцию, связанную с уже закрытым channel.`]}),`
875
+ `,(0,c.jsx)(t.h3,{id:`что-делает-cleanup`,children:`Что делает cleanup`}),`
876
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`installed.cleanup();
877
+ `})}),`
878
+ `,(0,c.jsx)(t.p,{children:`Cleanup:`}),`
879
+ `,(0,c.jsxs)(t.ul,{children:[`
880
+ `,(0,c.jsx)(t.li,{children:`отписывается от QueryCache events;`}),`
881
+ `,(0,c.jsx)(t.li,{children:`вызывает cleanup TanStack broadcast adapter;`}),`
882
+ `,(0,c.jsx)(t.li,{children:`закрывает custom BroadcastChannel.`}),`
883
+ `]}),`
884
+ `,(0,c.jsx)(t.p,{children:`Вызывайте его в тестах, stories, microfrontend teardown и при повторной installation.`}),`
885
+ `,(0,c.jsx)(t.h3,{id:`защита-от-циклов`,children:`Защита от циклов`}),`
886
+ `,(0,c.jsxs)(t.p,{children:[`Каждая installation получает `,(0,c.jsx)(t.code,{children:`tabId`}),`.`]}),`
887
+ `,(0,c.jsx)(t.p,{children:`Receiver:`}),`
888
+ `,(0,c.jsxs)(t.ul,{children:[`
889
+ `,(0,c.jsxs)(t.li,{children:[`игнорирует message с собственным `,(0,c.jsx)(t.code,{children:`tabId`}),`;`]}),`
890
+ `,(0,c.jsxs)(t.li,{children:[`на время применения remote event включает `,(0,c.jsx)(t.code,{children:`isApplyingRemoteEvent`}),`;`]}),`
891
+ `,(0,c.jsx)(t.li,{children:`не пересылает вызванную remote message invalidation обратно.`}),`
892
+ `]}),`
893
+ `,(0,c.jsx)(t.p,{children:`Это предотвращает бесконечное эхо между вкладками.`}),`
894
+ `,(0,c.jsx)(t.h3,{id:`проверка-входящих-messages`,children:`Проверка входящих messages`}),`
895
+ `,(0,c.jsx)(t.p,{children:`Модуль проверяет:`}),`
896
+ `,(0,c.jsxs)(t.ul,{children:[`
897
+ `,(0,c.jsx)(t.li,{children:`message является record;`}),`
898
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`tabId`}),` является string;`]}),`
899
+ `,(0,c.jsxs)(t.li,{children:[`event имеет известный `,(0,c.jsx)(t.code,{children:`type`}),`;`]}),`
900
+ `,(0,c.jsxs)(t.li,{children:[`invalidate содержит array `,(0,c.jsx)(t.code,{children:`keys`}),`;`]}),`
901
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`refetchType`}),` входит в allowlist;`]}),`
902
+ `,(0,c.jsxs)(t.li,{children:[`setQueryData содержит fields `,(0,c.jsx)(t.code,{children:`key`}),` и `,(0,c.jsx)(t.code,{children:`data`}),`.`]}),`
903
+ `]}),`
904
+ `,(0,c.jsxs)(t.p,{children:[`Проверка намеренно не является полной schema validation QueryKey/data. Например, каждый элемент `,(0,c.jsx)(t.code,{children:`keys`}),` отдельно глубоко не валидируется.`]}),`
905
+ `,(0,c.jsx)(t.p,{children:`Это защита от случайных/устаревших messages, но не security boundary между приложениями одного origin. Используйте уникальные channel names и не отправляйте secrets.`}),`
906
+ `,(0,c.jsx)(t.h3,{id:`browser-support`,children:`Browser support`}),`
907
+ `,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`window`}),` отсутствует, `,(0,c.jsx)(t.code,{children:`installReactQueryBroadcast`}),` возвращает no-op `,(0,c.jsx)(t.code,{children:`broadcast`}),` и `,(0,c.jsx)(t.code,{children:`cleanup`}),`.`]}),`
908
+ `,(0,c.jsxs)(t.p,{children:[`Но если `,(0,c.jsx)(t.code,{children:`window`}),` существует, а `,(0,c.jsx)(t.code,{children:`BroadcastChannel`}),` отсутствует, текущая implementation попытается вызвать constructor и может выбросить `,(0,c.jsx)(t.code,{children:`ReferenceError`}),`.`]}),`
909
+ `,(0,c.jsx)(t.p,{children:`Для старого/ограниченного browser сделайте feature check до installation:`}),`
910
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const installed =
911
+ typeof BroadcastChannel === "undefined"
912
+ ? { broadcast: () => undefined, cleanup: () => undefined }
913
+ : installReactQueryBroadcast(queryClient, options);
914
+ `})}),`
915
+ `,(0,c.jsx)(t.h3,{id:`structured-clone`,children:`Structured clone`}),`
916
+ `,(0,c.jsxs)(t.p,{children:[`BroadcastChannel использует structured clone. В `,(0,c.jsx)(t.code,{children:`setQueryData.data`}),` не передавайте:`]}),`
917
+ `,(0,c.jsxs)(t.ul,{children:[`
918
+ `,(0,c.jsx)(t.li,{children:`functions;`}),`
919
+ `,(0,c.jsx)(t.li,{children:`DOM nodes;`}),`
920
+ `,(0,c.jsx)(t.li,{children:`React elements;`}),`
921
+ `,(0,c.jsx)(t.li,{children:`QueryClient;`}),`
922
+ `,(0,c.jsx)(t.li,{children:`non-cloneable runtime handles.`}),`
923
+ `]}),`
924
+ `,(0,c.jsxs)(t.p,{children:[`Ошибка clone может возникнуть при `,(0,c.jsx)(t.code,{children:`postMessage`}),`.`]}),`
925
+ `,(0,c.jsx)(t.h3,{id:`same-origin-граница`,children:`Same-origin граница`}),`
926
+ `,(0,c.jsx)(t.p,{children:`BroadcastChannel работает между browsing contexts одного origin и channel name. Он не синхронизирует разные domains, browser profiles или devices.`}),`
927
+ `,(0,c.jsx)(t.h3,{id:`когда-нужен-ручной-event`,children:`Когда нужен ручной event`}),`
928
+ `,(0,c.jsxs)(t.p,{children:[`Обычный `,(0,c.jsx)(t.code,{children:`queryClient.setQueryData`}),` уже может синхронизироваться TanStack adapter-ом. Не дублируйте manual `,(0,c.jsx)(t.code,{children:`setQueryData`}),` event автоматически везде.`]}),`
929
+ `,(0,c.jsx)(t.p,{children:`Ручной event нужен, когда:`}),`
930
+ `,(0,c.jsxs)(t.ul,{children:[`
931
+ `,(0,c.jsx)(t.li,{children:`требуется explicit прикладная команда;`}),`
932
+ `,(0,c.jsxs)(t.li,{children:[`нужен особый `,(0,c.jsx)(t.code,{children:`refetchType`}),`;`]}),`
933
+ `,(0,c.jsx)(t.li,{children:`нужно одной командой перечислить несколько keys;`}),`
934
+ `,(0,c.jsx)(t.li,{children:`consumer сознательно не полагается только на experimental data sync.`}),`
935
+ `]}),`
936
+ `,(0,c.jsx)(t.h3,{id:`broadcast-и-session-data`,children:`Broadcast и session data`}),`
937
+ `,(0,c.jsxs)(t.p,{children:[`Полная cache synchronization потенциально передаёт query data между вкладками. Для команды очистки auth/session cache используйте отдельный `,(0,c.jsx)(t.code,{children:`installSessionScopedQueryReset`}),`: он передаёт только reset command.`]}),`
938
+ `,(0,c.jsxs)(t.p,{children:[`Не отправляйте session token или чувствительный payload через `,(0,c.jsx)(t.code,{children:`broadcastCacheEvent`}),`.`]}),`
939
+ `,(0,c.jsx)(t.h2,{id:`optimistic-update`,children:`Optimistic update`}),`
940
+ `,(0,c.jsx)(t.h3,{id:`что-это-такое`,children:`Что это такое`}),`
941
+ `,(0,c.jsx)(t.p,{children:`Обычная mutation:`}),`
942
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`действие пользователя
943
+
944
+ HTTP request
945
+
946
+ server response
947
+
948
+ обновление UI
949
+ `})}),`
950
+ `,(0,c.jsx)(t.p,{children:`Пользователь ждёт сеть до визуального результата.`}),`
951
+ `,(0,c.jsx)(t.p,{children:`Optimistic update сначала меняет локальный Query cache:`}),`
952
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`действие пользователя
953
+
954
+ UI/cache меняются сразу
955
+
956
+ HTTP request
957
+ ├─ успех → подтвердить/слить server data
958
+ └─ ошибка → откатить предыдущий snapshot
959
+ `})}),`
960
+ `,(0,c.jsx)(t.p,{children:`Мы предполагаем, что server примет изменение. Если предположение неверно, cache возвращается к предыдущему состоянию.`}),`
961
+ `,(0,c.jsx)(t.h3,{id:`когда-использовать`,children:`Когда использовать`}),`
962
+ `,(0,c.jsx)(t.p,{children:`Хорошие сценарии:`}),`
963
+ `,(0,c.jsxs)(t.ul,{children:[`
964
+ `,(0,c.jsx)(t.li,{children:`toggle/checkbox;`}),`
965
+ `,(0,c.jsx)(t.li,{children:`изменение видимости;`}),`
966
+ `,(0,c.jsx)(t.li,{children:`простое inline-edit;`}),`
967
+ `,(0,c.jsx)(t.li,{children:`drag-and-drop reorder;`}),`
968
+ `,(0,c.jsx)(t.li,{children:`добавление элемента с временным id;`}),`
969
+ `,(0,c.jsx)(t.li,{children:`действие с высокой вероятностью успеха и понятным rollback.`}),`
970
+ `]}),`
971
+ `,(0,c.jsx)(t.p,{children:`Не используйте автоматически для:`}),`
972
+ `,(0,c.jsxs)(t.ul,{children:[`
973
+ `,(0,c.jsx)(t.li,{children:`финансовых операций;`}),`
974
+ `,(0,c.jsx)(t.li,{children:`сложной server validation;`}),`
975
+ `,(0,c.jsx)(t.li,{children:`действия с большим количеством side effects;`}),`
976
+ `,(0,c.jsx)(t.li,{children:`mutation, где server радикально меняет результат;`}),`
977
+ `,(0,c.jsx)(t.li,{children:`нескольких параллельных изменений одного cache key без concurrency strategy;`}),`
978
+ `,(0,c.jsx)(t.li,{children:`операции, для которой невозможно корректно восстановить UI.`}),`
979
+ `]}),`
980
+ `,(0,c.jsx)(t.p,{children:`Optimistic UI не означает, что операция завершилась успешно. Показывайте error/recovery, если server отказал.`}),`
981
+ `,(0,c.jsx)(t.h3,{id:`роль-lifecycle-handlers`,children:`Роль lifecycle handlers`}),`
982
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`mutate(variables)
983
+
984
+
985
+ onMutate
986
+ 1. cancelQueries
987
+ 2. snapshot previous cache
988
+ 3. optimistic patch
989
+ 4. return { previous }
990
+
991
+
992
+ mutationFn
993
+ ├─ success → onSuccess
994
+ │ server data merge, если нужно
995
+
996
+ ├─ error → onError
997
+ │ rollback previous
998
+
999
+ └─ always → onSettled
1000
+ scoped invalidation/refetch policy
1001
+ `})}),`
1002
+ `,(0,c.jsxs)(t.p,{children:[`TanStack Query передаёт результат `,(0,c.jsx)(t.code,{children:`onMutate`}),` в следующие lifecycle callbacks.`]}),`
1003
+ `,(0,c.jsx)(t.h3,{id:`публичные-helpers`,children:`Публичные helpers`}),`
1004
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Helper`}),(0,c.jsx)(t.th,{children:`Lifecycle`}),(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:`onMutateOptimistic`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`onMutate`})}),(0,c.jsx)(t.td,{children:`Cancel + snapshot + patch.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`onErrorOptimistic`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`onError`})}),(0,c.jsx)(t.td,{children:`Restore предыдущего snapshot.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`onSuccessOptimistic`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`onSuccess`})}),(0,c.jsx)(t.td,{children:`Merge authoritative server response.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`onSettledOptimistic`})}),(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`onSettled`})}),(0,c.jsx)(t.td,{children:`Invalidate один или несколько keys.`})]})]})]}),`
1005
+ `,(0,c.jsxs)(t.p,{children:[`Helpers низкоуровневые: они не создают `,(0,c.jsx)(t.code,{children:`useMutation`}),`, не выполняют request, не показывают notification и не выбирают query key.`]}),`
1006
+ `,(0,c.jsx)(t.h3,{id:`optimisticcontext`,children:(0,c.jsx)(t.code,{children:`OptimisticContext`})}),`
1007
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type OptimisticContext<TQueryCacheData> = {
1008
+ previous: TQueryCacheData | undefined;
1009
+ };
1010
+ `})}),`
1011
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`TQueryCacheData`}),` — тип data именно в Query cache, не request variables и не обязательно server response.`]}),`
1012
+ `,(0,c.jsx)(t.p,{children:`Пример:`}),`
1013
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type Task = {
1014
+ id: string;
1015
+ title: string;
1016
+ done: boolean;
1017
+ };
1018
+
1019
+ type TaskCache = Task[];
1020
+ `})}),`
1021
+ `,(0,c.jsx)(t.h3,{id:`onmutateoptimistic`,children:(0,c.jsx)(t.code,{children:`onMutateOptimistic`})}),`
1022
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const context = await onMutateOptimistic<TaskCache>(queryClient, tasksQueryKey, (oldCache) => {
1023
+ return oldCache?.map((task) => (task.id === taskId ? { ...task, done: true } : task));
1024
+ });
1025
+ `})}),`
1026
+ `,(0,c.jsx)(t.p,{children:`Signature:`}),`
1027
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`async function onMutateOptimistic<TQueryCacheData>(
1028
+ queryClient: QueryClient,
1029
+ queryKey: QueryKey,
1030
+ patcher: (oldCache: TQueryCacheData | undefined) => TQueryCacheData | undefined
1031
+ ): Promise<OptimisticContext<TQueryCacheData>>;
1032
+ `})}),`
1033
+ `,(0,c.jsx)(t.p,{children:`Три шага выполняются последовательно:`}),`
1034
+ `,(0,c.jsxs)(t.ol,{children:[`
1035
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`cancelQueries({ queryKey })`}),`;`]}),`
1036
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`getQueryData<TQueryCacheData>(queryKey)`}),`;`]}),`
1037
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`setQueryData<TQueryCacheData>(queryKey, patcher)`}),`.`]}),`
1038
+ `]}),`
1039
+ `,(0,c.jsxs)(t.h3,{id:`зачем-cancelqueries`,children:[`Зачем `,(0,c.jsx)(t.code,{children:`cancelQueries`})]}),`
1040
+ `,(0,c.jsx)(t.p,{children:`Без отмены background refetch может завершиться после optimistic patch и записать старый server snapshot:`}),`
1041
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`cache: done=false
1042
+ background request начат
1043
+ optimistic patch: done=true
1044
+ background response: done=false ← patch потерян
1045
+ `})}),`
1046
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`cancelQueries`}),` уменьшает эту race condition.`]}),`
1047
+ `,(0,c.jsx)(t.p,{children:`Он не отменяет другие mutation. Параллельные writes требуют отдельной стратегии.`}),`
1048
+ `,(0,c.jsx)(t.h3,{id:`exact-и-prefix-key`,children:`Exact и prefix key`}),`
1049
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`cancelQueries({ queryKey })`}),` по TanStack defaults может затронуть query, совпадающие по prefix.`]}),`
1050
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`getQueryData`}),` и `,(0,c.jsx)(t.code,{children:`setQueryData`}),` работают с конкретным cache key.`]}),`
1051
+ `,(0,c.jsx)(t.p,{children:`Поэтому для optimistic patch передавайте exact owner key, а не слишком широкий prefix:`}),`
1052
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const key = ["tasks", { projectId }] as const;
1053
+ `})}),`
1054
+ `,(0,c.jsx)(t.h3,{id:`patcher-должен-быть-immutable`,children:`Patcher должен быть immutable`}),`
1055
+ `,(0,c.jsx)(t.p,{children:`Неверно:`}),`
1056
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`(old) => {
1057
+ const task = old?.find((item) => item.id === taskId);
1058
+ if (task) task.done = true;
1059
+ return old;
1060
+ };
1061
+ `})}),`
1062
+ `,(0,c.jsx)(t.p,{children:`Объект из Query cache мутирован по существующей ссылке.`}),`
1063
+ `,(0,c.jsx)(t.p,{children:`Правильно:`}),`
1064
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`(old) => old?.map((task) => (task.id === taskId ? { ...task, done: true } : task));
1065
+ `})}),`
1066
+ `,(0,c.jsx)(t.p,{children:`Создаётся новый массив и новый изменённый object.`}),`
1067
+ `,(0,c.jsxs)(t.h3,{id:`что-означает-undefined-из-patcher`,children:[`Что означает `,(0,c.jsx)(t.code,{children:`undefined`}),` из patcher`]}),`
1068
+ `,(0,c.jsxs)(t.p,{children:[`Patcher получает `,(0,c.jsx)(t.code,{children:`undefined`}),`, если cache entry/data отсутствует.`]}),`
1069
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`(old) => old?.map(...)
1070
+ `})}),`
1071
+ `,(0,c.jsxs)(t.p,{children:[`вернёт `,(0,c.jsx)(t.code,{children:`undefined`}),` и не создаст data.`]}),`
1072
+ `,(0,c.jsx)(t.p,{children:`Если нужно создать initial optimistic data:`}),`
1073
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`(old) => [...(old ?? []), optimisticTask];
1074
+ `})}),`
1075
+ `,(0,c.jsx)(t.p,{children:`Но тогда обязательно учтите ограничение rollback при отсутствующем previous snapshot.`}),`
1076
+ `,(0,c.jsx)(t.h3,{id:`onerroroptimistic`,children:(0,c.jsx)(t.code,{children:`onErrorOptimistic`})}),`
1077
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`onErrorOptimistic(queryClient, tasksQueryKey, context);
1078
+ `})}),`
1079
+ `,(0,c.jsx)(t.p,{children:`Signature:`}),`
1080
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function onErrorOptimistic<TQueryCacheData>(
1081
+ queryClient: QueryClient,
1082
+ queryKey: QueryKey,
1083
+ context: OptimisticContext<TQueryCacheData> | undefined
1084
+ ): void;
1085
+ `})}),`
1086
+ `,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`context?.previous !== undefined`}),`, helper вызывает:`]}),`
1087
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`queryClient.setQueryData(queryKey, context.previous);
1088
+ `})}),`
1089
+ `,(0,c.jsxs)(t.p,{children:[`Если context отсутствует, ничего не происходит. Это безопасно, когда `,(0,c.jsx)(t.code,{children:`onMutate`}),` сам завершился ошибкой до возврата context.`]}),`
1090
+ `,(0,c.jsx)(t.h3,{id:`важное-ограничение-rollback`,children:`Важное ограничение rollback`}),`
1091
+ `,(0,c.jsxs)(t.p,{children:[`Если до mutation data была `,(0,c.jsx)(t.code,{children:`undefined`}),`, context содержит:`]}),`
1092
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`{
1093
+ previous: undefined;
1094
+ }
1095
+ `})}),`
1096
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`onErrorOptimistic`}),` ничего не делает. Он не удаляет созданную optimistic entry.`]}),`
1097
+ `,(0,c.jsx)(t.p,{children:`Проблемный сценарий:`}),`
1098
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`// Cache до mutation отсутствует.
1099
+ onMutateOptimistic<Task[]>(queryClient, key, (old) => [...(old ?? []), optimisticTask]);
1100
+ `})}),`
1101
+ `,(0,c.jsx)(t.p,{children:`При error optimistic task останется, если вызвать только standard helper.`}),`
1102
+ `,(0,c.jsx)(t.p,{children:`Варианты решения:`}),`
1103
+ `,(0,c.jsxs)(t.ol,{children:[`
1104
+ `,(0,c.jsxs)(t.li,{children:[`До optimistic create заранее обеспечить существующий cache snapshot, например `,(0,c.jsx)(t.code,{children:`[]`}),`.`]}),`
1105
+ `,(0,c.jsxs)(t.li,{children:[`В `,(0,c.jsx)(t.code,{children:`onError`}),` отдельно удалить/очистить query, если `,(0,c.jsx)(t.code,{children:`context.previous === undefined`}),`.`]}),`
1106
+ `,(0,c.jsx)(t.li,{children:`Не применять optimistic create для изначально отсутствующей query.`}),`
1107
+ `]}),`
1108
+ `,(0,c.jsx)(t.p,{children:`Пример explicit rollback:`}),`
1109
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`onError: (_error, _variables, context) => {
1110
+ if (context?.previous === undefined) {
1111
+ queryClient.removeQueries({ queryKey: tasksQueryKey, exact: true });
1112
+ return;
1113
+ }
1114
+
1115
+ onErrorOptimistic(queryClient, tasksQueryKey, context);
1116
+ };
1117
+ `})}),`
1118
+ `,(0,c.jsx)(t.p,{children:`Выбирайте удаление только для exact key, которым владеет mutation.`}),`
1119
+ `,(0,c.jsx)(t.h3,{id:`onsuccessoptimistic`,children:(0,c.jsx)(t.code,{children:`onSuccessOptimistic`})}),`
1120
+ `,(0,c.jsx)(t.p,{children:`Когда server возвращает authoritative data:`}),`
1121
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`onSuccessOptimistic<Task[], Task>(queryClient, tasksQueryKey, serverTask, (cache, task) => {
1122
+ return cache?.map((item) => (item.id === task.id ? task : item));
1123
+ });
1124
+ `})}),`
1125
+ `,(0,c.jsx)(t.p,{children:`Signature:`}),`
1126
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`function onSuccessOptimistic<TQueryCacheData, TServerData>(
1127
+ queryClient: QueryClient,
1128
+ queryKey: QueryKey,
1129
+ serverData: TServerData,
1130
+ merger: (cache: TQueryCacheData | undefined, serverData: TServerData) => TQueryCacheData | undefined
1131
+ ): void;
1132
+ `})}),`
1133
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`TServerData`}),` может отличаться от cache data. Merger является явным type-safe мостом.`]}),`
1134
+ `,(0,c.jsx)(t.h3,{id:`полная-замена-ответом-server`,children:`Полная замена ответом server`}),`
1135
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`onSuccessOptimistic<Task[], Task[]>(queryClient, tasksQueryKey, serverTasks, (_cache, data) => data);
1136
+ `})}),`
1137
+ `,(0,c.jsx)(t.h3,{id:`merge-одного-элемента`,children:`Merge одного элемента`}),`
1138
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`onSuccessOptimistic<Task[], Task>(queryClient, tasksQueryKey, updatedTask, (cache, data) =>
1139
+ cache?.map((task) => (task.id === data.id ? data : task))
1140
+ );
1141
+ `})}),`
1142
+ `,(0,c.jsx)(t.p,{children:`Merger тоже должен быть immutable.`}),`
1143
+ `,(0,c.jsxs)(t.h3,{id:`server-возвращает-void`,children:[`Server возвращает `,(0,c.jsx)(t.code,{children:`void`})]}),`
1144
+ `,(0,c.jsxs)(t.p,{children:[`Если request только подтверждает изменение, а optimistic cache уже отражает точное состояние, `,(0,c.jsx)(t.code,{children:`onSuccessOptimistic`}),` не нужен:`]}),`
1145
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`onSuccess: () => {
1146
+ // Нечего сливать: server response void.
1147
+ };
1148
+ `})}),`
1149
+ `,(0,c.jsxs)(t.p,{children:[`После этого выберите onSettled invalidation strategy. Не вызывайте merger с `,(0,c.jsx)(t.code,{children:`void`}),` только ради формальности.`]}),`
1150
+ `,(0,c.jsx)(t.h3,{id:`onsettledoptimistic`,children:(0,c.jsx)(t.code,{children:`onSettledOptimistic`})}),`
1151
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`await onSettledOptimistic(queryClient, tasksQueryKey);
1152
+ `})}),`
1153
+ `,(0,c.jsx)(t.p,{children:`Или несколько keys:`}),`
1154
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`await onSettledOptimistic(queryClient, [tasksQueryKey, taskSummaryQueryKey], { refetchType: "active" });
1155
+ `})}),`
1156
+ `,(0,c.jsx)(t.p,{children:`Signature:`}),`
1157
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type OnSettledOptimisticOptions = {
1158
+ refetchType?: "none" | "active" | "inactive" | "all";
1159
+ };
1160
+
1161
+ async function onSettledOptimistic(
1162
+ queryClient: QueryClient,
1163
+ queryKeys: QueryKey | QueryKey[],
1164
+ options?: OnSettledOptimisticOptions
1165
+ ): Promise<void>;
1166
+ `})}),`
1167
+ `,(0,c.jsxs)(t.p,{children:[`Helper вызывает `,(0,c.jsx)(t.code,{children:`invalidateQueries`}),` для всех normalized keys через `,(0,c.jsx)(t.code,{children:`Promise.all`}),`.`]}),`
1168
+ `,(0,c.jsx)(t.h3,{id:`refetchtype`,children:(0,c.jsx)(t.code,{children:`refetchType`})}),`
1169
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Value`}),(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:`"none"`})}),(0,c.jsx)(t.td,{children:`Только invalidation, немедленного refetch нет. Default helper-а.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"active"`})}),(0,c.jsx)(t.td,{children:`Refetch active matching query.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"inactive"`})}),(0,c.jsx)(t.td,{children:`Refetch inactive matching query.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`"all"`})}),(0,c.jsx)(t.td,{children:`Refetch active и inactive matching query.`})]})]})]}),`
1170
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`"none"`}),` подходит, когда optimistic/server merge уже дал достаточное текущее состояние.`]}),`
1171
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`"active"`}),` нужен, если server применяет дополнительную логику и видимый UI должен быстро получить authoritative snapshot.`]}),`
1172
+ `,(0,c.jsxs)(t.p,{children:[`Не ставьте `,(0,c.jsx)(t.code,{children:`"all"`}),` автоматически: это может создать много HTTP requests.`]}),`
1173
+ `,(0,c.jsx)(t.h3,{id:`как-передавать-один-и-несколько-keys`,children:`Как передавать один и несколько keys`}),`
1174
+ `,(0,c.jsx)(t.p,{children:`Один key:`}),`
1175
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`onSettledOptimistic(queryClient, ["tasks"]);
1176
+ `})}),`
1177
+ `,(0,c.jsx)(t.p,{children:`Несколько keys:`}),`
1178
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`onSettledOptimistic(queryClient, [["tasks"], ["task-summary"]]);
1179
+ `})}),`
1180
+ `,(0,c.jsx)(t.p,{children:`Runtime различает формы по первому элементу: если первый элемент сам array, значение считается массивом keys.`}),`
1181
+ `,(0,c.jsx)(t.p,{children:`Избегайте необычного одиночного QueryKey, у которого первый segment является array:`}),`
1182
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const ambiguousKey = [["nested"]] as const;
1183
+ `})}),`
1184
+ `,(0,c.jsx)(t.p,{children:`Он будет воспринят как список query keys.`}),`
1185
+ `,(0,c.jsxs)(t.h3,{id:`полный-пример-toggle-server-возвращает-void`,children:[`Полный пример: toggle, server возвращает `,(0,c.jsx)(t.code,{children:`void`})]}),`
1186
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { useMutation, useQueryClient } from "@tanstack/react-query";
1187
+ import { onErrorOptimistic, onMutateOptimistic, onSettledOptimistic } from "@ryuzaki13/react-foundation-lib/query-client";
1188
+
1189
+ type Task = {
1190
+ id: string;
1191
+ title: string;
1192
+ done: boolean;
1193
+ };
1194
+
1195
+ type ToggleTaskInput = {
1196
+ id: string;
1197
+ done: boolean;
1198
+ };
1199
+
1200
+ const tasksQueryKey = ["tasks"] as const;
1201
+
1202
+ export function useToggleTaskMutation() {
1203
+ const queryClient = useQueryClient();
1204
+
1205
+ return useMutation({
1206
+ mutationFn: updateTaskOnServer,
1207
+
1208
+ onMutate: async (input: ToggleTaskInput) => {
1209
+ return onMutateOptimistic<Task[]>(queryClient, tasksQueryKey, (old) =>
1210
+ old?.map((task) => (task.id === input.id ? { ...task, done: input.done } : task))
1211
+ );
1212
+ },
1213
+
1214
+ onError: (_error, _input, context) => {
1215
+ onErrorOptimistic(queryClient, tasksQueryKey, context);
1216
+ },
1217
+
1218
+ onSettled: () => {
1219
+ return onSettledOptimistic(queryClient, tasksQueryKey);
1220
+ }
1221
+ });
1222
+ }
1223
+ `})}),`
1224
+ `,(0,c.jsx)(t.p,{children:`Cache timeline:`}),`
1225
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`до: [{ id: "1", done: false }]
1226
+ onMutate: [{ id: "1", done: true }]
1227
+
1228
+ success: optimistic data остаётся, query помечается stale
1229
+ error: previous snapshot восстанавливается
1230
+ `})}),`
1231
+ `,(0,c.jsx)(t.h3,{id:`полный-пример-create-с-authoritative-object`,children:`Полный пример: create с authoritative object`}),`
1232
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type Task = {
1233
+ id: string;
1234
+ title: string;
1235
+ createdAt: string;
1236
+ };
1237
+
1238
+ type NewTask = {
1239
+ title: string;
1240
+ };
1241
+
1242
+ const TEMP_ID = "optimistic:task";
1243
+
1244
+ export function useCreateTaskMutation() {
1245
+ const queryClient = useQueryClient();
1246
+
1247
+ return useMutation({
1248
+ mutationFn: createTaskOnServer,
1249
+
1250
+ onMutate: async (input: NewTask) => {
1251
+ return onMutateOptimistic<Task[]>(queryClient, tasksQueryKey, (old) => [
1252
+ ...(old ?? []),
1253
+ { id: TEMP_ID, title: input.title, createdAt: "" }
1254
+ ]);
1255
+ },
1256
+
1257
+ onError: (_error, _input, context) => {
1258
+ if (context?.previous === undefined) {
1259
+ queryClient.removeQueries({
1260
+ queryKey: tasksQueryKey,
1261
+ exact: true
1262
+ });
1263
+ return;
1264
+ }
1265
+
1266
+ onErrorOptimistic(queryClient, tasksQueryKey, context);
1267
+ },
1268
+
1269
+ onSuccess: (createdTask: Task) => {
1270
+ onSuccessOptimistic<Task[], Task>(
1271
+ queryClient,
1272
+ tasksQueryKey,
1273
+ createdTask,
1274
+ (cache, serverTask) => cache?.map((task) => (task.id === TEMP_ID ? serverTask : task)) ?? [serverTask]
1275
+ );
1276
+ },
1277
+
1278
+ onSettled: () => {
1279
+ return onSettledOptimistic(queryClient, tasksQueryKey, { refetchType: "active" });
1280
+ }
1281
+ });
1282
+ }
1283
+ `})}),`
1284
+ `,(0,c.jsx)(t.p,{children:`Timeline:`}),`
1285
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`до:
1286
+ [{ id: "server-1", title: "Первая", createdAt: "..." }]
1287
+
1288
+ optimistic:
1289
+ [
1290
+ { id: "server-1", ... },
1291
+ { id: "optimistic:task", title: "Вторая", createdAt: "" }
1292
+ ]
1293
+
1294
+ server success:
1295
+ [
1296
+ { id: "server-1", ... },
1297
+ { id: "server-2", title: "Вторая", createdAt: "2026-08-18..." }
1298
+ ]
1299
+ `})}),`
1300
+ `,(0,c.jsx)(t.h3,{id:`несколько-связанных-keys`,children:`Несколько связанных keys`}),`
1301
+ `,(0,c.jsx)(t.p,{children:`Mutation может влиять на detail, list и counters:`}),`
1302
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`onSettled: () =>
1303
+ onSettledOptimistic(
1304
+ queryClient,
1305
+ [
1306
+ ["orders", orderId],
1307
+ ["orders", "list"],
1308
+ ["orders", "counters"]
1309
+ ],
1310
+ { refetchType: "active" }
1311
+ );
1312
+ `})}),`
1313
+ `,(0,c.jsx)(t.p,{children:`Указывайте только keys реальных owners. Не инвалидируйте весь cache приложения.`}),`
1314
+ `,(0,c.jsx)(t.h3,{id:`optimistic-update-и-broadcast`,children:`Optimistic update и broadcast`}),`
1315
+ `,(0,c.jsxs)(t.p,{children:[`Optimistic helpers сами не вызывают `,(0,c.jsx)(t.code,{children:`broadcastCacheEvent`}),`.`]}),`
1316
+ `,(0,c.jsxs)(t.p,{children:[`При установленном `,(0,c.jsx)(t.code,{children:`installReactQueryBroadcast`}),`:`]}),`
1317
+ `,(0,c.jsxs)(t.ul,{children:[`
1318
+ `,(0,c.jsx)(t.li,{children:`обычный Query cache update может уйти через TanStack data channel;`}),`
1319
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`onSettledOptimistic`}),` вызывает local invalidation;`]}),`
1320
+ `,(0,c.jsxs)(t.li,{children:[`wrapper автоматически передаст конкретную invalidation другой вкладке с `,(0,c.jsx)(t.code,{children:`refetchType: "none"`}),`.`]}),`
1321
+ `]}),`
1322
+ `,(0,c.jsx)(t.p,{children:`Manual event нужен только для осознанного другого поведения:`}),`
1323
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`onSettled: async () => {
1324
+ await onSettledOptimistic(queryClient, tasksQueryKey, { refetchType: "active" });
1325
+
1326
+ broadcastCacheEvent({
1327
+ type: "invalidate",
1328
+ keys: [tasksQueryKey],
1329
+ refetchType: "active"
1330
+ });
1331
+ };
1332
+ `})}),`
1333
+ `,(0,c.jsxs)(t.p,{children:[`Такой explicit event может дублировать automatic `,(0,c.jsx)(t.code,{children:`"none"`}),` invalidation message. Делайте это только если remote active refetch действительно требуется.`]}),`
1334
+ `,(0,c.jsx)(t.h3,{id:`параллельные-mutation-одного-key`,children:`Параллельные mutation одного key`}),`
1335
+ `,(0,c.jsx)(t.p,{children:`Helpers хранят один snapshot на каждую mutation, но не координируют порядок нескольких writes.`}),`
1336
+ `,(0,c.jsx)(t.p,{children:`Проблема:`}),`
1337
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-text`,children:`cache = A
1338
+
1339
+ mutation 1 snapshot A → patch B
1340
+ mutation 2 snapshot B → patch C
1341
+ mutation 2 success
1342
+ mutation 1 error → rollback A
1343
+
1344
+ успешный результат mutation 2 потерян
1345
+ `})}),`
1346
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`cancelQueries`}),` отменяет reads, а не соседние mutations.`]}),`
1347
+ `,(0,c.jsx)(t.p,{children:`Для concurrent writes выберите стратегию:`}),`
1348
+ `,(0,c.jsxs)(t.ul,{children:[`
1349
+ `,(0,c.jsx)(t.li,{children:`сериализовать mutation по entity/key;`}),`
1350
+ `,(0,c.jsx)(t.li,{children:`блокировать повторное действие до settlement;`}),`
1351
+ `,(0,c.jsx)(t.li,{children:`хранить более богатый optimistic context/patch stack;`}),`
1352
+ `,(0,c.jsx)(t.li,{children:`использовать mutation id и условный rollback;`}),`
1353
+ `,(0,c.jsx)(t.li,{children:`отказаться от optimistic update и refetch server snapshot.`}),`
1354
+ `]}),`
1355
+ `,(0,c.jsx)(t.p,{children:`Текущие helpers лучше подходят одной последовательной mutation для одного exact cache key.`}),`
1356
+ `,(0,c.jsx)(t.h3,{id:`server-validation-и-merge`,children:`Server validation и merge`}),`
1357
+ `,(0,c.jsx)(t.p,{children:`Если server:`}),`
1358
+ `,(0,c.jsxs)(t.ul,{children:[`
1359
+ `,(0,c.jsx)(t.li,{children:`округляет число;`}),`
1360
+ `,(0,c.jsx)(t.li,{children:`добавляет id/timestamp;`}),`
1361
+ `,(0,c.jsx)(t.li,{children:`меняет status;`}),`
1362
+ `,(0,c.jsx)(t.li,{children:`пересчитывает totals;`}),`
1363
+ `,(0,c.jsx)(t.li,{children:`сортирует list;`}),`
1364
+ `,(0,c.jsx)(t.li,{children:`применяет permissions/business rules,`}),`
1365
+ `]}),`
1366
+ `,(0,c.jsxs)(t.p,{children:[`то optimistic value не является authoritative. Используйте `,(0,c.jsx)(t.code,{children:`onSuccessOptimistic`}),` либо active invalidation.`]}),`
1367
+ `,(0,c.jsx)(t.h3,{id:`error-handling`,children:`Error handling`}),`
1368
+ `,(0,c.jsxs)(t.p,{children:[`Rollback восстанавливает cache, но не объясняет ошибку пользователю. В `,(0,c.jsx)(t.code,{children:`onError`}),` обычно нужны:`]}),`
1369
+ `,(0,c.jsxs)(t.ul,{children:[`
1370
+ `,(0,c.jsx)(t.li,{children:`rollback;`}),`
1371
+ `,(0,c.jsx)(t.li,{children:`безопасное notification;`}),`
1372
+ `,(0,c.jsx)(t.li,{children:`diagnostics без sensitive data;`}),`
1373
+ `,(0,c.jsx)(t.li,{children:`понятная возможность повторить действие.`}),`
1374
+ `]}),`
1375
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`onError: (error, _variables, context) => {
1376
+ onErrorOptimistic(queryClient, key, context);
1377
+ notifyError("Изменение не сохранено");
1378
+ reportError(error);
1379
+ };
1380
+ `})}),`
1381
+ `,(0,c.jsxs)(t.p,{children:[`Не оставляйте пустой `,(0,c.jsx)(t.code,{children:`catch`}),` и не показывайте success до фактического server confirmation, если это может ввести пользователя в заблуждение.`]}),`
1382
+ `,(0,c.jsx)(t.h3,{id:`типовые-ошибки`,children:`Типовые ошибки`}),`
1383
+ `,(0,c.jsx)(t.h4,{id:`мутировать-old-cache`,children:`Мутировать old cache`}),`
1384
+ `,(0,c.jsx)(t.p,{children:`Всегда возвращайте новые изменённые containers/objects.`}),`
1385
+ `,(0,c.jsx)(t.h4,{id:`забыть-rollback`,children:`Забыть rollback`}),`
1386
+ `,(0,c.jsxs)(t.p,{children:[`Optimistic patch без `,(0,c.jsx)(t.code,{children:`onError`}),` оставит ложное состояние при отказе server.`]}),`
1387
+ `,(0,c.jsxs)(t.h4,{id:`считать-context-обязательным`,children:[`Считать `,(0,c.jsx)(t.code,{children:`context`}),` обязательным`]}),`
1388
+ `,(0,c.jsxs)(t.p,{children:[`Если `,(0,c.jsx)(t.code,{children:`onMutate`}),` выбросил ошибку, context может быть `,(0,c.jsx)(t.code,{children:`undefined`}),`.`]}),`
1389
+ `,(0,c.jsxs)(t.h4,{id:`не-учесть-previous-undefined`,children:[`Не учесть `,(0,c.jsx)(t.code,{children:`previous: undefined`})]}),`
1390
+ `,(0,c.jsx)(t.p,{children:`Standard rollback helper не удаляет новую cache entry.`}),`
1391
+ `,(0,c.jsxs)(t.h4,{id:`refetch-all-после-каждой-mutation`,children:[`Refetch `,(0,c.jsx)(t.code,{children:`all`}),` после каждой mutation`]}),`
1392
+ `,(0,c.jsx)(t.p,{children:`Создаёт лишний network traffic и может ухудшить UX сильнее обычной non-optimistic mutation.`}),`
1393
+ `,(0,c.jsx)(t.h4,{id:`использовать-широкий-query-prefix-как-exact-data-key`,children:`Использовать широкий query prefix как exact data key`}),`
1394
+ `,(0,c.jsx)(t.p,{children:`Cancel и patch начнут относиться к разным наборам cache entries.`}),`
1395
+ `,(0,c.jsx)(t.h4,{id:`игнорировать-параллельные-writes`,children:`Игнорировать параллельные writes`}),`
1396
+ `,(0,c.jsx)(t.p,{children:`Поздний rollback старой mutation может затереть более новое успешное изменение.`}),`
1397
+ `,(0,c.jsx)(t.h2,{id:`безопасность-и-сериализация`,children:`Безопасность и сериализация`}),`
1398
+ `,(0,c.jsx)(t.h3,{id:`query-key-не-должен-содержать-secrets`,children:`Query key не должен содержать secrets`}),`
1399
+ `,(0,c.jsx)(t.p,{children:`Query key может попасть:`}),`
1400
+ `,(0,c.jsxs)(t.ul,{children:[`
1401
+ `,(0,c.jsx)(t.li,{children:`в memory cache;`}),`
1402
+ `,(0,c.jsx)(t.li,{children:`в IndexedDB persistent key/state;`}),`
1403
+ `,(0,c.jsx)(t.li,{children:`в broadcast message;`}),`
1404
+ `,(0,c.jsx)(t.li,{children:`в DevTools;`}),`
1405
+ `,(0,c.jsx)(t.li,{children:`в diagnostics/error report.`}),`
1406
+ `]}),`
1407
+ `,(0,c.jsx)(t.p,{children:`Нельзя помещать:`}),`
1408
+ `,(0,c.jsxs)(t.ul,{children:[`
1409
+ `,(0,c.jsx)(t.li,{children:`token;`}),`
1410
+ `,(0,c.jsx)(t.li,{children:`password;`}),`
1411
+ `,(0,c.jsx)(t.li,{children:`session cookie;`}),`
1412
+ `,(0,c.jsx)(t.li,{children:`raw authorization header;`}),`
1413
+ `,(0,c.jsx)(t.li,{children:`секретный payload.`}),`
1414
+ `]}),`
1415
+ `,(0,c.jsx)(t.p,{children:`Используйте безопасные business identifiers и нормализованные параметры.`}),`
1416
+ `,(0,c.jsx)(t.h3,{id:`persistence--явное-разрешение`,children:`Persistence — явное разрешение`}),`
1417
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`persistedQueryMeta`}),` означает, что data может пережить reload и оставаться в browser storage долгое время.`]}),`
1418
+ `,(0,c.jsx)(t.p,{children:`Перед включением проверьте:`}),`
1419
+ `,(0,c.jsxs)(t.ol,{children:[`
1420
+ `,(0,c.jsx)(t.li,{children:`Data не персональная и не session-scoped.`}),`
1421
+ `,(0,c.jsx)(t.li,{children:`Namespace разделяет несовместимые system/client/tenant.`}),`
1422
+ `,(0,c.jsx)(t.li,{children:`Query key включает все влияющие параметры.`}),`
1423
+ `,(0,c.jsx)(t.li,{children:`Есть freshness/invalidation policy.`}),`
1424
+ `,(0,c.jsx)(t.li,{children:`Data поддерживает structured clone.`}),`
1425
+ `,(0,c.jsx)(t.li,{children:`Объём не создаёт неконтролируемый storage growth.`}),`
1426
+ `]}),`
1427
+ `,(0,c.jsx)(t.h3,{id:`namespace-не-является-authorization`,children:`Namespace не является authorization`}),`
1428
+ `,(0,c.jsxs)(t.p,{children:[`Разные `,(0,c.jsx)(t.code,{children:`dbName`}),`/prefix уменьшают случайное смешение environments, но не защищают от JavaScript того же origin.`]}),`
1429
+ `,(0,c.jsx)(t.p,{children:`При XSS вредоносный code может читать browser storage и channels. Не сохраняйте sensitive data, полагаясь только на необычное имя database.`}),`
1430
+ `,(0,c.jsx)(t.h3,{id:`broadcast-не-является-доверенным-transport`,children:`Broadcast не является доверенным transport`}),`
1431
+ `,(0,c.jsx)(t.p,{children:`Message приходит от другого context того же origin. Runtime guards фильтруют явный мусор, но не проверяют business authorization или точный data schema.`}),`
1432
+ `,(0,c.jsx)(t.p,{children:`Не используйте broadcast вместо backend confirmation.`}),`
1433
+ `,(0,c.jsx)(t.h3,{id:`structured-clone-ограничения`,children:`Structured clone ограничения`}),`
1434
+ `,(0,c.jsx)(t.p,{children:`И IndexedDB, и BroadcastChannel используют structured clone. Plain serializable data — самый предсказуемый вариант:`}),`
1435
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type SafeCacheData = {
1436
+ id: string;
1437
+ count: number;
1438
+ labels: string[];
1439
+ updatedAt: string;
1440
+ };
1441
+ `})}),`
1442
+ `,(0,c.jsx)(t.p,{children:`Сложные instances могут потерять ожидаемое поведение или не клонироваться.`}),`
1443
+ `,(0,c.jsx)(t.h3,{id:`error-callbacks-и-персональные-данные`,children:`Error callbacks и персональные данные`}),`
1444
+ `,(0,c.jsx)(t.p,{children:`Global error handler получает query/mutation objects и variables. Не логируйте их целиком без sanitization.`}),`
1445
+ `,(0,c.jsx)(t.p,{children:`Хороший diagnostics context:`}),`
1446
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`{
1447
+ operation: "update-task",
1448
+ queryKeyOwner: "tasks",
1449
+ errorName: error.name,
1450
+ message: error.message
1451
+ }
1452
+ `})}),`
1453
+ `,(0,c.jsx)(t.p,{children:`Плохой context:`}),`
1454
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`{
1455
+ mutationVariables,
1456
+ allQueryCache: queryClient.getQueryCache().getAll(),
1457
+ token
1458
+ }
1459
+ `})}),`
1460
+ `,(0,c.jsx)(t.h2,{id:`browser-ssr-и-lifecycle`,children:`Browser, SSR и lifecycle`}),`
1461
+ `,(0,c.jsx)(t.h3,{id:`основная-среда--browser-spa`,children:`Основная среда — browser SPA`}),`
1462
+ `,(0,c.jsx)(t.p,{children:`Persistence и broadcast предназначены для browser runtime:`}),`
1463
+ `,(0,c.jsxs)(t.ul,{children:[`
1464
+ `,(0,c.jsx)(t.li,{children:`IndexedDB;`}),`
1465
+ `,(0,c.jsx)(t.li,{children:`BroadcastChannel;`}),`
1466
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`window.location.hostname`}),`;`]}),`
1467
+ `,(0,c.jsx)(t.li,{children:`несколько вкладок одного origin.`}),`
1468
+ `]}),`
1469
+ `,(0,c.jsx)(t.p,{children:`Pure QueryClient/optimistic helpers сами используют TanStack Query API, но единый entrypoint всё равно требует корректных build-time constants.`}),`
1470
+ `,(0,c.jsx)(t.h3,{id:`поведение-без-indexeddb`,children:`Поведение без IndexedDB`}),`
1471
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`createIndexedDbQueryStorage(); // undefined
1472
+ createReactQueryPersister(); // undefined
1473
+ createQueryClient({}); // работает без default persister
1474
+ `})}),`
1475
+ `,(0,c.jsx)(t.p,{children:`Приложение продолжает работать с обычным memory cache.`}),`
1476
+ `,(0,c.jsxs)(t.h3,{id:`поведение-без-window`,children:[`Поведение без `,(0,c.jsx)(t.code,{children:`window`})]}),`
1477
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`installReactQueryBroadcast`}),` возвращает no-op.`]}),`
1478
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`installSessionScopedQueryReset`}),` возвращает no-op cleanup.`]}),`
1479
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`resetSessionScopedQueries`}),` продолжает локально работать с QueryClient.`]}),`
1480
+ `,(0,c.jsxs)(t.p,{children:[`Production system identifier fallback без window — `,(0,c.jsx)(t.code,{children:`{APP_ID}:server`}),`.`]}),`
1481
+ `,(0,c.jsxs)(t.h3,{id:`window-есть-broadcastchannel-нет`,children:[(0,c.jsx)(t.code,{children:`window`}),` есть, BroadcastChannel нет`]}),`
1482
+ `,(0,c.jsx)(t.p,{children:`Session reset installation проверяет оба globals и деградирует в no-op.`}),`
1483
+ `,(0,c.jsxs)(t.p,{children:[`Общая cache broadcast installation проверяет только `,(0,c.jsx)(t.code,{children:`window`}),`. Сделайте дополнительный feature check в host, если поддерживаются browsers без BroadcastChannel.`]}),`
1484
+ `,(0,c.jsx)(t.h3,{id:`global-module-state`,children:`Global module state`}),`
1485
+ `,(0,c.jsx)(t.p,{children:`Модуль хранит:`}),`
1486
+ `,(0,c.jsxs)(t.ul,{children:[`
1487
+ `,(0,c.jsx)(t.li,{children:`текущий persistence project adapter;`}),`
1488
+ `,(0,c.jsx)(t.li,{children:`global cache broadcast function;`}),`
1489
+ `,(0,c.jsx)(t.li,{children:`global session-reset sender.`}),`
1490
+ `]}),`
1491
+ `,(0,c.jsx)(t.p,{children:`Это process/module singletons. В обычной SPA они удобны, но в tests, HMR и microfrontends требуют lifecycle discipline.`}),`
1492
+ `,(0,c.jsx)(t.h3,{id:`повторная-installation`,children:`Повторная installation`}),`
1493
+ `,(0,c.jsx)(t.p,{children:`Перед повторной установкой:`}),`
1494
+ `,(0,c.jsxs)(t.ol,{children:[`
1495
+ `,(0,c.jsx)(t.li,{children:`сбросьте imperative broadcast handler;`}),`
1496
+ `,(0,c.jsx)(t.li,{children:`вызовите cleanup предыдущего broadcast;`}),`
1497
+ `,(0,c.jsx)(t.li,{children:`вызовите cleanup session channel;`}),`
1498
+ `,(0,c.jsx)(t.li,{children:`установите новые channels;`}),`
1499
+ `,(0,c.jsx)(t.li,{children:`сохраните новый handler.`}),`
1500
+ `]}),`
1501
+ `,(0,c.jsx)(t.p,{children:`Иначе останутся дублированные subscriptions/channels.`}),`
1502
+ `,(0,c.jsx)(t.h3,{id:`react-provider`,children:`React Provider`}),`
1503
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`createQueryClient`}),` не рендерит Provider:`]}),`
1504
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-tsx`,children:`<QueryClientProvider client={queryClient}>
1505
+ <App />
1506
+ </QueryClientProvider>
1507
+ `})}),`
1508
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`useQueryClient`}),`, `,(0,c.jsx)(t.code,{children:`useQuery`}),` и `,(0,c.jsx)(t.code,{children:`useMutation`}),` требуют этот provider в component tree.`]}),`
1509
+ `,(0,c.jsx)(t.h3,{id:`react-strict-mode`,children:`React Strict Mode`}),`
1510
+ `,(0,c.jsx)(t.p,{children:`Не устанавливайте channels внутри component render/effect без уверенного cleanup. В development Strict Mode effect setup может выполняться повторно.`}),`
1511
+ `,(0,c.jsx)(t.p,{children:`Предпочтительно устанавливать application-wide QueryClient/channels в bootstrap module или в отдельной lifecycle boundary с симметричным cleanup.`}),`
1512
+ `,(0,c.jsx)(t.h2,{id:`тестирование-consumer-кода`,children:`Тестирование consumer-кода`}),`
1513
+ `,(0,c.jsx)(t.h3,{id:`test-queryclient-без-retry`,children:`Test QueryClient без retry`}),`
1514
+ `,(0,c.jsx)(t.p,{children:`Для unit test создавайте отдельный client:`}),`
1515
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { QueryClient } from "@tanstack/react-query";
1516
+
1517
+ export function createTestQueryClient() {
1518
+ return new QueryClient({
1519
+ defaultOptions: {
1520
+ queries: { retry: false },
1521
+ mutations: { retry: false }
1522
+ }
1523
+ });
1524
+ }
1525
+ `})}),`
1526
+ `,(0,c.jsx)(t.p,{children:`Это изолирует cache каждого test.`}),`
1527
+ `,(0,c.jsx)(t.p,{children:`После test:`}),`
1528
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`queryClient.clear();
1529
+ `})}),`
1530
+ `,(0,c.jsxs)(t.h3,{id:`test-createqueryclient`,children:[`Test `,(0,c.jsx)(t.code,{children:`createQueryClient`})]}),`
1531
+ `,(0,c.jsx)(t.p,{children:`Проверьте defaults и error callbacks:`}),`
1532
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const client = createQueryClient({});
1533
+
1534
+ expect(client.getDefaultOptions().queries).toMatchObject({
1535
+ staleTime: 10 * 60 * 1000,
1536
+ gcTime: 30 * 60 * 1000,
1537
+ retry: false,
1538
+ throwOnError: false
1539
+ });
1540
+ `})}),`
1541
+ `,(0,c.jsxs)(t.p,{children:[`В environment без IndexedDB `,(0,c.jsx)(t.code,{children:`persister`}),` должен отсутствовать.`]}),`
1542
+ `,(0,c.jsx)(t.h3,{id:`indexeddb-test`,children:`IndexedDB test`}),`
1543
+ `,(0,c.jsx)(t.p,{children:`Передайте factory явно, не полагаясь на browser global:`}),`
1544
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`import { indexedDB } from "fake-indexeddb";
1545
+
1546
+ const storage = createIndexedDbQueryStorage<string>({
1547
+ indexedDB,
1548
+ dbName: "query-client-test"
1549
+ });
1550
+
1551
+ expect(storage).toBeDefined();
1552
+ await storage?.setItem("key", "value");
1553
+ await expect(storage?.getItem("key")).resolves.toBe("value");
1554
+ `})}),`
1555
+ `,(0,c.jsxs)(t.p,{children:[`Используйте уникальный `,(0,c.jsx)(t.code,{children:`dbName`}),` между несвязанными tests, чтобы data не протекала.`]}),`
1556
+ `,(0,c.jsx)(t.h3,{id:`test-opt-in-persistence`,children:`Test opt-in persistence`}),`
1557
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`expect(shouldPersistQuery({ meta: persistedQueryMeta })).toBe(true);
1558
+ expect(shouldPersistQuery({ meta: undefined })).toBe(false);
1559
+ expect(shouldPersistQuery({ meta: sessionScopedQueryMeta })).toBe(false);
1560
+ expect(
1561
+ shouldPersistQuery({
1562
+ meta: { persist: true, sessionScoped: true }
1563
+ })
1564
+ ).toBe(false);
1565
+ `})}),`
1566
+ `,(0,c.jsx)(t.h3,{id:`test-optimistic-rollback`,children:`Test optimistic rollback`}),`
1567
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`const client = createTestQueryClient();
1568
+ const key = ["tasks"] as const;
1569
+ const initial = [{ id: "1", done: false }];
1570
+
1571
+ client.setQueryData(key, initial);
1572
+
1573
+ const context = await onMutateOptimistic<typeof initial>(client, key, (old) => old?.map((task) => ({ ...task, done: true })));
1574
+
1575
+ expect(client.getQueryData(key)).toEqual([{ id: "1", done: true }]);
1576
+
1577
+ onErrorOptimistic(client, key, context);
1578
+ expect(client.getQueryData(key)).toEqual(initial);
1579
+ `})}),`
1580
+ `,(0,c.jsxs)(t.p,{children:[`Отдельно тестируйте `,(0,c.jsx)(t.code,{children:`previous: undefined`}),`, если mutation способна создать cache entry.`]}),`
1581
+ `,(0,c.jsx)(t.h3,{id:`test-session-reset`,children:`Test session reset`}),`
1582
+ `,(0,c.jsx)(t.p,{children:`Создайте public и session query, выполните reset и проверьте их отдельно:`}),`
1583
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`await client.fetchQuery({
1584
+ queryKey: ["public"],
1585
+ queryFn: () => "public"
1586
+ });
1587
+
1588
+ await client.fetchQuery({
1589
+ queryKey: ["session"],
1590
+ queryFn: () => "private",
1591
+ meta: sessionScopedQueryMeta
1592
+ });
1593
+
1594
+ await resetSessionScopedQueries(client, { broadcast: false });
1595
+
1596
+ expect(client.getQueryData(["public"])).toBe("public");
1597
+ expect(client.getQueryData(["session"])).toBeUndefined();
1598
+ `})}),`
1599
+ `,(0,c.jsx)(t.h3,{id:`test-broadcastchannel`,children:`Test BroadcastChannel`}),`
1600
+ `,(0,c.jsx)(t.p,{children:`Для unit test предоставьте fake implementation, которая:`}),`
1601
+ `,(0,c.jsxs)(t.ul,{children:[`
1602
+ `,(0,c.jsx)(t.li,{children:`регистрирует channels по имени;`}),`
1603
+ `,(0,c.jsx)(t.li,{children:`отправляет message другим instances, но не себе;`}),`
1604
+ `,(0,c.jsxs)(t.li,{children:[`вызывает `,(0,c.jsx)(t.code,{children:`onmessage`}),`;`]}),`
1605
+ `,(0,c.jsxs)(t.li,{children:[`умеет `,(0,c.jsx)(t.code,{children:`close`}),`;`]}),`
1606
+ `,(0,c.jsx)(t.li,{children:`очищает registry после test.`}),`
1607
+ `]}),`
1608
+ `,(0,c.jsx)(t.p,{children:`Всегда вызывайте installation cleanup.`}),`
1609
+ `,(0,c.jsx)(t.h3,{id:`сброс-global-state-между-tests`,children:`Сброс global state между tests`}),`
1610
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`setBroadcastFn(() => undefined);
1611
+ configureQueryPersistenceProjectAdapter({});
1612
+ installedBroadcast?.cleanup();
1613
+ installedSessionReset?.cleanup();
1614
+ queryClient.clear();
1615
+ `})}),`
1616
+ `,(0,c.jsx)(t.p,{children:`Без cleanup test order может влиять на результат.`}),`
1617
+ `,(0,c.jsx)(t.h3,{id:`проверки-самого-пакета`,children:`Проверки самого пакета`}),`
1618
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-bash`,children:`npm run typecheck
1619
+ npm run lint
1620
+ node_modules/.bin/vitest run src/query-client
1621
+ npm run build
1622
+ `})}),`
1623
+ `,(0,c.jsx)(t.p,{children:`Profile tests подтверждают:`}),`
1624
+ `,(0,c.jsxs)(t.ul,{children:[`
1625
+ `,(0,c.jsx)(t.li,{children:`IndexedDB CRUD и opt-in filtering;`}),`
1626
+ `,(0,c.jsx)(t.li,{children:`условное подключение persister;`}),`
1627
+ `,(0,c.jsx)(t.li,{children:`broadcast no-op вне browser;`}),`
1628
+ `,(0,c.jsx)(t.li,{children:`cache data/invalidation sync;`}),`
1629
+ `,(0,c.jsx)(t.li,{children:`игнорирование невалидных messages;`}),`
1630
+ `,(0,c.jsx)(t.li,{children:`отсутствие remote event loop;`}),`
1631
+ `,(0,c.jsx)(t.li,{children:`optimistic snapshot/patch/rollback/merge/invalidation;`}),`
1632
+ `,(0,c.jsx)(t.li,{children:`session reset active/inactive/disabled/hydrated query;`}),`
1633
+ `,(0,c.jsx)(t.li,{children:`межвкладочную reset command без cache data.`}),`
1634
+ `]}),`
1635
+ `,(0,c.jsx)(t.h2,{id:`полная-карта-api`,children:`Полная карта API`}),`
1636
+ `,(0,c.jsx)(t.p,{children:`Раздел перечисляет все публичные symbols entrypoint. Не импортируйте другие names через private path.`}),`
1637
+ `,(0,c.jsx)(t.h3,{id:`queryclient-1`,children:`QueryClient`}),`
1638
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Symbol`}),(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:`QueryBroadcastOptions`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`Global query/mutation error callbacks.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`createQueryClient`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`Создаёт новый настроенный QueryClient.`})]})]})]}),`
1639
+ `,(0,c.jsx)(t.h3,{id:`query-metadata-1`,children:`Query metadata`}),`
1640
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Symbol`}),(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:`AppQueryMeta`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsxs)(t.td,{children:[(0,c.jsx)(t.code,{children:`persist`}),`, `,(0,c.jsx)(t.code,{children:`sessionScoped`}),` и consumer metadata.`]})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`persistedQueryMeta`})}),(0,c.jsx)(t.td,{children:`const`}),(0,c.jsx)(t.td,{children:`Opt-in public persistence marker.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`sessionScopedQueryMeta`})}),(0,c.jsx)(t.td,{children:`const`}),(0,c.jsx)(t.td,{children:`Session reset marker.`})]})]})]}),`
1641
+ `,(0,c.jsx)(t.h3,{id:`persistence-project-adapter`,children:`Persistence project adapter`}),`
1642
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Symbol`}),(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:`QueryPersistenceProjectAdapter`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`Buster и system identifier resolver.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`configureQueryPersistenceProjectAdapter`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`Заменяет global adapter.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`getQueryPersistenceProjectAdapter`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`Возвращает текущий adapter.`})]})]})]}),`
1643
+ `,(0,c.jsx)(t.h3,{id:`indexeddb-persistence-1`,children:`IndexedDB persistence`}),`
1644
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Symbol`}),(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:`REACT_QUERY_PERSISTENCE_BUSTER`})}),(0,c.jsx)(t.td,{children:`const`}),(0,c.jsx)(t.td,{children:`Build-time default buster.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`REACT_QUERY_PERSISTENCE_MAX_AGE`})}),(0,c.jsx)(t.td,{children:`const`}),(0,c.jsx)(t.td,{children:`Persistent max age: 90 дней.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`shouldPersistQuery`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`Opt-in predicate с session safety.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`createIndexedDbQueryStorage`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`AsyncStorage поверх IndexedDB.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`createReactQueryPersister`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`Настроенный per-query TanStack persister.`})]})]})]}),`
1645
+ `,(0,c.jsxs)(t.p,{children:[`Options type `,(0,c.jsx)(t.code,{children:`createIndexedDbQueryStorage`}),` не экспортируется отдельным symbol; используйте структурный object или `,(0,c.jsx)(t.code,{children:`Parameters<typeof createIndexedDbQueryStorage>[0]`}),`.`]}),`
1646
+ `,(0,c.jsx)(t.h3,{id:`broadcast`,children:`Broadcast`}),`
1647
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Symbol`}),(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:`installReactQueryBroadcast`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`Устанавливает data + event channels.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`setBroadcastFn`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`Устанавливает global imperative sender.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`broadcastCacheEvent`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`Отправляет event через текущий sender.`})]})]})]}),`
1648
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`CacheSyncEvent`}),`, installation options и result types не экспортируются отдельными именами. Они доступны через inference:`]}),`
1649
+ `,(0,c.jsx)(t.pre,{children:(0,c.jsx)(t.code,{className:`language-ts`,children:`type BroadcastEvent = Parameters<typeof broadcastCacheEvent>[0];
1650
+ type BroadcastOptions = Parameters<typeof installReactQueryBroadcast>[1];
1651
+ type InstalledBroadcast = ReturnType<typeof installReactQueryBroadcast>;
1652
+ `})}),`
1653
+ `,(0,c.jsx)(t.h3,{id:`session-scoped-cache`,children:`Session-scoped cache`}),`
1654
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Symbol`}),(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:`ResetSessionScopedQueriesOptions`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`Управляет broadcast reset.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`InstallSessionScopedQueryResetOptions`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`Имя command channel.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`InstalledSessionScopedQueryReset`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`Cleanup contract.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`isSessionScopedQuery`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`Predicate по query meta.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`resetSessionScopedQueries`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`Cancel/clear/refetch session query.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`installSessionScopedQueryReset`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`Межвкладочная reset-command installation.`})]})]})]}),`
1655
+ `,(0,c.jsx)(t.h3,{id:`optimistic-update-1`,children:`Optimistic update`}),`
1656
+ `,(0,c.jsxs)(t.table,{children:[(0,c.jsx)(t.thead,{children:(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.th,{children:`Symbol`}),(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:`OptimisticContext`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`Previous cache snapshot.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`OnSettledOptimisticOptions`})}),(0,c.jsx)(t.td,{children:`type`}),(0,c.jsx)(t.td,{children:`Invalidation refetchType.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`onMutateOptimistic`})}),(0,c.jsx)(t.td,{children:`async function`}),(0,c.jsx)(t.td,{children:`Cancel, snapshot и optimistic patch.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`onErrorOptimistic`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`Rollback defined previous snapshot.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`onSuccessOptimistic`})}),(0,c.jsx)(t.td,{children:`function`}),(0,c.jsx)(t.td,{children:`Merge server data в текущий cache.`})]}),(0,c.jsxs)(t.tr,{children:[(0,c.jsx)(t.td,{children:(0,c.jsx)(t.code,{children:`onSettledOptimistic`})}),(0,c.jsx)(t.td,{children:`async function`}),(0,c.jsx)(t.td,{children:`Invalidate один/несколько keys.`})]})]})]}),`
1657
+ `,(0,c.jsx)(t.h2,{id:`частые-вопросы`,children:`Частые вопросы`}),`
1658
+ `,(0,c.jsxs)(t.h3,{id:`createqueryclient-действительно-singleton`,children:[(0,c.jsx)(t.code,{children:`createQueryClient`}),` действительно singleton?`]}),`
1659
+ `,(0,c.jsx)(t.p,{children:`Нет. Функция создаёт новый client при каждом вызове. Host должен вызвать её один раз и экспортировать instance.`}),`
1660
+ `,(0,c.jsxs)(t.h3,{id:`нужно-ли-помечать-каждую-query-persistedquerymeta`,children:[`Нужно ли помечать каждую query `,(0,c.jsx)(t.code,{children:`persistedQueryMeta`}),`?`]}),`
1661
+ `,(0,c.jsx)(t.p,{children:`Нет. Persistence строго opt-in и подходит только проверенным публичным долгоживущим data.`}),`
1662
+ `,(0,c.jsxs)(t.h3,{id:`почему-query-с-persistedquerymeta-не-появилась-в-indexeddb`,children:[`Почему query с `,(0,c.jsx)(t.code,{children:`persistedQueryMeta`}),` не появилась в IndexedDB?`]}),`
1663
+ `,(0,c.jsx)(t.p,{children:`Проверьте:`}),`
1664
+ `,(0,c.jsxs)(t.ul,{children:[`
1665
+ `,(0,c.jsx)(t.li,{children:`доступен ли IndexedDB;`}),`
1666
+ `,(0,c.jsx)(t.li,{children:`query завершилась ли успешно;`}),`
1667
+ `,(0,c.jsxs)(t.li,{children:[`meta действительно `,(0,c.jsx)(t.code,{children:`persist: true`}),`;`]}),`
1668
+ `,(0,c.jsxs)(t.li,{children:[`нет ли `,(0,c.jsx)(t.code,{children:`sessionScoped: true`}),`;`]}),`
1669
+ `,(0,c.jsx)(t.li,{children:`не возникла ли quota/transaction error;`}),`
1670
+ `,(0,c.jsx)(t.li,{children:`вызывается ли query через client с подключённым default persister;`}),`
1671
+ `,(0,c.jsx)(t.li,{children:`под каким dbName/system identifier открыт storage.`}),`
1672
+ `]}),`
1673
+ `,(0,c.jsx)(t.h3,{id:`почему-после-restore-всё-равно-идёт-request`,children:`Почему после restore всё равно идёт request?`}),`
1674
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`refetchOnRestore: true`}),` обновляет восстановленную stale query. Проверьте `,(0,c.jsx)(t.code,{children:`staleTime`}),`, возраст data, buster и maxAge.`]}),`
1675
+ `,(0,c.jsx)(t.h3,{id:`можно-ли-persist-permissions`,children:`Можно ли persist permissions?`}),`
1676
+ `,(0,c.jsxs)(t.p,{children:[`Нет. Permissions зависят от session/role. Используйте `,(0,c.jsx)(t.code,{children:`sessionScopedQueryMeta`}),` и reset при смене сессии.`]}),`
1677
+ `,(0,c.jsxs)(t.h3,{id:`чем-sessionscopedquerymeta-отличается-от-gctime-0`,children:[`Чем `,(0,c.jsx)(t.code,{children:`sessionScopedQueryMeta`}),` отличается от `,(0,c.jsx)(t.code,{children:`gcTime: 0`}),`?`]}),`
1678
+ `,(0,c.jsxs)(t.p,{children:[(0,c.jsx)(t.code,{children:`gcTime`}),` управляет garbage collection неактивной query. Session reset является явной security/lifecycle boundary: cancel, очистка old data, refetch active query и команда другим вкладкам.`]}),`
1679
+ `,(0,c.jsxs)(t.h3,{id:`нужно-ли-вручную-broadcast-обычный-setquerydata`,children:[`Нужно ли вручную broadcast обычный `,(0,c.jsx)(t.code,{children:`setQueryData`}),`?`]}),`
1680
+ `,(0,c.jsx)(t.p,{children:`Не всегда. Установленный TanStack broadcast синхронизирует cache data. Manual event используйте только для explicit policy.`}),`
1681
+ `,(0,c.jsx)(t.h3,{id:`почему-другая-вкладка-только-стала-stale-но-не-сделала-request`,children:`Почему другая вкладка только стала stale, но не сделала request?`}),`
1682
+ `,(0,c.jsxs)(t.p,{children:[`Automatic invalidation использует `,(0,c.jsx)(t.code,{children:`refetchType: "none"`}),`. Для немедленного remote refetch отправьте explicit invalidate event с `,(0,c.jsx)(t.code,{children:`"active"`}),` или другой подходящей policy.`]}),`
1683
+ `,(0,c.jsx)(t.h3,{id:`почему-broadcast-не-работает-в-другом-domain`,children:`Почему broadcast не работает в другом domain?`}),`
1684
+ `,(0,c.jsx)(t.p,{children:`BroadcastChannel ограничен одним origin и channel name.`}),`
1685
+ `,(0,c.jsx)(t.h3,{id:`можно-ли-передавать-date-в-indexeddb`,children:`Можно ли передавать Date в IndexedDB?`}),`
1686
+ `,(0,c.jsxs)(t.p,{children:[`Browser structured clone поддерживает `,(0,c.jsx)(t.code,{children:`Date`}),`, но убедитесь, что consumer mapping и cache contract ожидают Date после restore. Для transport/domain snapshots часто предсказуемее serializable ISO string.`]}),`
1687
+ `,(0,c.jsx)(t.h3,{id:`почему-rollback-не-удалил-созданный-optimistic-item`,children:`Почему rollback не удалил созданный optimistic item?`}),`
1688
+ `,(0,c.jsxs)(t.p,{children:[`Если до mutation cache была `,(0,c.jsx)(t.code,{children:`undefined`}),`, standard context не отличает «entry отсутствовала» от undefined data и `,(0,c.jsx)(t.code,{children:`onErrorOptimistic`}),` ничего не делает. Добавьте explicit exact-key removal или заранее создайте empty cache snapshot.`]}),`
1689
+ `,(0,c.jsxs)(t.h3,{id:`достаточно-ли-cancelqueries-для-параллельных-mutation`,children:[`Достаточно ли `,(0,c.jsx)(t.code,{children:`cancelQueries`}),` для параллельных mutation?`]}),`
1690
+ `,(0,c.jsx)(t.p,{children:`Нет. Оно отменяет matching queries/refetches, а не mutation. Параллельные writes требуют serialization, mutation id, patch stack или отказа от optimistic update.`}),`
1691
+ `,(0,c.jsxs)(t.h3,{id:`нужно-ли-всегда-вызывать-onsettledoptimistic`,children:[`Нужно ли всегда вызывать `,(0,c.jsx)(t.code,{children:`onSettledOptimistic`}),`?`]}),`
1692
+ `,(0,c.jsxs)(t.p,{children:[`Не механически. Выберите cache strategy. Если server response полностью записан через `,(0,c.jsx)(t.code,{children:`setQueryData`}),` и invalidation не нужна, дополнительная invalidation может быть лишней. Если server мог изменить другие fields/data, invalidation полезна.`]}),`
1693
+ `,(0,c.jsx)(t.h3,{id:`что-произойдёт-без-build-time-buster`,children:`Что произойдёт без build-time buster?`}),`
1694
+ `,(0,c.jsxs)(t.p,{children:[`Import entrypoint может завершиться `,(0,c.jsx)(t.code,{children:`ReferenceError`}),`, потому что constant читается на верхнем уровне. Настройте host bundler `,(0,c.jsx)(t.code,{children:`define`}),`.`]}),`
1695
+ `,(0,c.jsx)(t.h3,{id:`как-очистить-channels-при-hmrtest`,children:`Как очистить channels при HMR/test?`}),`
1696
+ `,(0,c.jsx)(t.p,{children:`Сбросьте global broadcast handler, вызовите оба cleanup и очистите test QueryClient.`}),`
1697
+ `,(0,c.jsx)(t.h2,{id:`связанные-модули`,children:`Связанные модули`}),`
1698
+ `,(0,c.jsxs)(t.ul,{children:[`
1699
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-api/odata`}),` — OData reads/mutations и TanStack integration.`]}),`
1700
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-api/http`}),` — обычные HTTP query/mutation functions.`]}),`
1701
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/odata-service`}),` — чистые metadata/value/path/filter helpers.`]}),`
1702
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/pwa`}),` — Service Worker update и HTTP cache policy.`]}),`
1703
+ `,(0,c.jsxs)(t.li,{children:[(0,c.jsx)(t.code,{children:`@ryuzaki13/react-foundation-lib/error-report`}),` — подготовка и диагностика error reports.`]}),`
1704
+ `]})]})}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};