@shapio/cms 0.3.0 → 0.3.1

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 (270) hide show
  1. package/dist/admin/apple-touch-icon.png +0 -0
  2. package/dist/admin/assets/{AssistError-BITJ_tpg.js → AssistError-BYR085vj.js} +2 -2
  3. package/dist/admin/assets/{AssistError-BITJ_tpg.js.map → AssistError-BYR085vj.js.map} +1 -1
  4. package/dist/admin/assets/{BlockMenuItems-CcluK965.js → BlockMenuItems-BnLF3Efc.js} +2 -2
  5. package/dist/admin/assets/{BlockMenuItems-CcluK965.js.map → BlockMenuItems-BnLF3Efc.js.map} +1 -1
  6. package/dist/admin/assets/{Body-BXa9DhtH.js → Body-CCOZq2q9.js} +2 -2
  7. package/dist/admin/assets/{Body-BXa9DhtH.js.map → Body-CCOZq2q9.js.map} +1 -1
  8. package/dist/admin/assets/{Builder-DcpRfXuc.js → Builder-BZy9mphs.js} +2 -2
  9. package/dist/admin/assets/{Builder-DcpRfXuc.js.map → Builder-BZy9mphs.js.map} +1 -1
  10. package/dist/admin/assets/{ConfirmDialog-B9nWhIIz.js → ConfirmDialog-Tu-gjEwm.js} +2 -2
  11. package/dist/admin/assets/{ConfirmDialog-B9nWhIIz.js.map → ConfirmDialog-Tu-gjEwm.js.map} +1 -1
  12. package/dist/admin/assets/{CopyButton-BoWb-xi1.js → CopyButton-BuBaIlCk.js} +2 -2
  13. package/dist/admin/assets/{CopyButton-BoWb-xi1.js.map → CopyButton-BuBaIlCk.js.map} +1 -1
  14. package/dist/admin/assets/{CursorPager-BnxmTk47.js → CursorPager-Bvu3CXpD.js} +2 -2
  15. package/dist/admin/assets/{CursorPager-BnxmTk47.js.map → CursorPager-Bvu3CXpD.js.map} +1 -1
  16. package/dist/admin/assets/{ExternalLink-Eeb9mONJ.js → ExternalLink-CCr5oRXI.js} +2 -2
  17. package/dist/admin/assets/{ExternalLink-Eeb9mONJ.js.map → ExternalLink-CCr5oRXI.js.map} +1 -1
  18. package/dist/admin/assets/Form-Doc0JEzF.js +2 -0
  19. package/dist/admin/assets/{Form-DZRLD3vH.js.map → Form-Doc0JEzF.js.map} +1 -1
  20. package/dist/admin/assets/{FormCheckboxGroup-uIC2XQIL.js → FormCheckboxGroup-eg39IJvf.js} +2 -2
  21. package/dist/admin/assets/{FormCheckboxGroup-uIC2XQIL.js.map → FormCheckboxGroup-eg39IJvf.js.map} +1 -1
  22. package/dist/admin/assets/{FormRadioGroup-hd95vPOu.js → FormRadioGroup-ZppSTZK6.js} +2 -2
  23. package/dist/admin/assets/{FormRadioGroup-hd95vPOu.js.map → FormRadioGroup-ZppSTZK6.js.map} +1 -1
  24. package/dist/admin/assets/{FormSelectField-D7VHmnx5.js → FormSelectField-Cosnxiz-.js} +2 -2
  25. package/dist/admin/assets/{FormSelectField-D7VHmnx5.js.map → FormSelectField-Cosnxiz-.js.map} +1 -1
  26. package/dist/admin/assets/{FormSwitchField-ft8Jbe6E.js → FormSwitchField-DsKQEZw6.js} +2 -2
  27. package/dist/admin/assets/{FormSwitchField-ft8Jbe6E.js.map → FormSwitchField-DsKQEZw6.js.map} +1 -1
  28. package/dist/admin/assets/{FormTextareaField-DzyOzfZb.js → FormTextareaField-BXidSgIE.js} +2 -2
  29. package/dist/admin/assets/{FormTextareaField-DzyOzfZb.js.map → FormTextareaField-BXidSgIE.js.map} +1 -1
  30. package/dist/admin/assets/{HintedLabel-CvC46uy_.js → HintedLabel-USQku9Yt.js} +2 -2
  31. package/dist/admin/assets/{HintedLabel-CvC46uy_.js.map → HintedLabel-USQku9Yt.js.map} +1 -1
  32. package/dist/admin/assets/{InfoHint-GJLRVumb.js → InfoHint-Bz_yvehr.js} +2 -2
  33. package/dist/admin/assets/{InfoHint-GJLRVumb.js.map → InfoHint-Bz_yvehr.js.map} +1 -1
  34. package/dist/admin/assets/{InsertMenuItem-D976G0Rj.js → InsertMenuItem-DJXglsEd.js} +2 -2
  35. package/dist/admin/assets/{InsertMenuItem-D976G0Rj.js.map → InsertMenuItem-DJXglsEd.js.map} +1 -1
  36. package/dist/admin/assets/{LinkTabs-BZSlA57U.js → LinkTabs-D4U-rJyK.js} +2 -2
  37. package/dist/admin/assets/{LinkTabs-BZSlA57U.js.map → LinkTabs-D4U-rJyK.js.map} +1 -1
  38. package/dist/admin/assets/{Match-DdxTHHM4.js → Match-UAw1Ss8a.js} +2 -2
  39. package/dist/admin/assets/{Match-DdxTHHM4.js.map → Match-UAw1Ss8a.js.map} +1 -1
  40. package/dist/admin/assets/{Panel-iIKjEURi.js → Panel-BdjqCkKs.js} +2 -2
  41. package/dist/admin/assets/{Panel-iIKjEURi.js.map → Panel-BdjqCkKs.js.map} +1 -1
  42. package/dist/admin/assets/{QueryView-D6aZQJRN.js → QueryView-Bkyizh1P.js} +2 -2
  43. package/dist/admin/assets/{QueryView-D6aZQJRN.js.map → QueryView-Bkyizh1P.js.map} +1 -1
  44. package/dist/admin/assets/{RadioTile-BpB79rIK.js → RadioTile-VHTPl88w.js} +2 -2
  45. package/dist/admin/assets/RadioTile-VHTPl88w.js.map +1 -0
  46. package/dist/admin/assets/{RichTextField-BuHCONPu.js → RichTextField-B21FqrT5.js} +4 -4
  47. package/dist/admin/assets/{RichTextField-BuHCONPu.js.map → RichTextField-B21FqrT5.js.map} +1 -1
  48. package/dist/admin/assets/{RowTitle--uFCpx8n.js → RowTitle-BD8QPs_v.js} +2 -2
  49. package/dist/admin/assets/{RowTitle--uFCpx8n.js.map → RowTitle-BD8QPs_v.js.map} +1 -1
  50. package/dist/admin/assets/{SecretReveal-DFpzeNE0.js → SecretReveal-BfJKUxXW.js} +2 -2
  51. package/dist/admin/assets/{SecretReveal-DFpzeNE0.js.map → SecretReveal-BfJKUxXW.js.map} +1 -1
  52. package/dist/admin/assets/{SharedGlyph-Cqkx30tn.js → SharedGlyph-DDPdvsm_.js} +2 -2
  53. package/dist/admin/assets/{SharedGlyph-Cqkx30tn.js.map → SharedGlyph-DDPdvsm_.js.map} +1 -1
  54. package/dist/admin/assets/{StepList-BY5MuJPJ.js → StepList-DKhkw1hN.js} +2 -2
  55. package/dist/admin/assets/{StepList-BY5MuJPJ.js.map → StepList-DKhkw1hN.js.map} +1 -1
  56. package/dist/admin/assets/Structure-C7dr00Bp.js +2 -0
  57. package/dist/admin/assets/{Structure-CqRa2t7z.js.map → Structure-C7dr00Bp.js.map} +1 -1
  58. package/dist/admin/assets/{SubNavLink-B2o4kSZ9.js → SubNavLink-BAF2-wuF.js} +2 -2
  59. package/dist/admin/assets/{SubNavLink-B2o4kSZ9.js.map → SubNavLink-BAF2-wuF.js.map} +1 -1
  60. package/dist/admin/assets/{SuggestAltButton-4rf2xhEJ.js → SuggestAltButton-DEsbHvs4.js} +2 -2
  61. package/dist/admin/assets/{SuggestAltButton-4rf2xhEJ.js.map → SuggestAltButton-DEsbHvs4.js.map} +1 -1
  62. package/dist/admin/assets/{TextLink-D15oyXlc.js → TextLink-CegO3jom.js} +2 -2
  63. package/dist/admin/assets/{TextLink-D15oyXlc.js.map → TextLink-CegO3jom.js.map} +1 -1
  64. package/dist/admin/assets/{Thumbnail-CP5mfgNY.js → Thumbnail-CTpKlrxR.js} +2 -2
  65. package/dist/admin/assets/{Thumbnail-CP5mfgNY.js.map → Thumbnail-CTpKlrxR.js.map} +1 -1
  66. package/dist/admin/assets/{UnsavedChangesGuard-DgOASh0p.js → UnsavedChangesGuard-Cbfg-Bgj.js} +2 -2
  67. package/dist/admin/assets/{UnsavedChangesGuard-DgOASh0p.js.map → UnsavedChangesGuard-Cbfg-Bgj.js.map} +1 -1
  68. package/dist/admin/assets/{UserAvatar-BocvNVL-.js → UserAvatar-CiZ00-cr.js} +2 -2
  69. package/dist/admin/assets/{UserAvatar-BocvNVL-.js.map → UserAvatar-CiZ00-cr.js.map} +1 -1
  70. package/dist/admin/assets/{arrow-up-gf_j6SVQ.js → arrow-up-DwMJqZVV.js} +2 -2
  71. package/dist/admin/assets/{arrow-up-gf_j6SVQ.js.map → arrow-up-DwMJqZVV.js.map} +1 -1
  72. package/dist/admin/assets/{auth-pcS9CCXu.js → auth-BaVAo0u_.js} +2 -2
  73. package/dist/admin/assets/{auth-pcS9CCXu.js.map → auth-BaVAo0u_.js.map} +1 -1
  74. package/dist/admin/assets/{badge-DD0mE96i.js → badge-IBeVOhKd.js} +2 -2
  75. package/dist/admin/assets/{badge-DD0mE96i.js.map → badge-IBeVOhKd.js.map} +1 -1
  76. package/dist/admin/assets/{boxes-DcK-kaSa.js → boxes-BH0MDegO.js} +2 -2
  77. package/dist/admin/assets/{boxes-DcK-kaSa.js.map → boxes-BH0MDegO.js.map} +1 -1
  78. package/dist/admin/assets/{calendar-clock-DEwACe0M.js → calendar-clock-DKUk-w81.js} +2 -2
  79. package/dist/admin/assets/{calendar-clock-DEwACe0M.js.map → calendar-clock-DKUk-w81.js.map} +1 -1
  80. package/dist/admin/assets/{changeSets-BjUEfYbj.js → changeSets-o_G67qPk.js} +2 -2
  81. package/dist/admin/assets/{changeSets-BjUEfYbj.js.map → changeSets-o_G67qPk.js.map} +1 -1
  82. package/dist/admin/assets/chevron-left-D259hWJn.js +2 -0
  83. package/dist/admin/assets/{chevron-left-BsQYDUEC.js.map → chevron-left-D259hWJn.js.map} +1 -1
  84. package/dist/admin/assets/{circle-check-Dq1owihe.js → circle-check-Cidzt1Ww.js} +2 -2
  85. package/dist/admin/assets/{circle-check-Dq1owihe.js.map → circle-check-Cidzt1Ww.js.map} +1 -1
  86. package/dist/admin/assets/{circle-x-Dx6zIZko.js → circle-x-c1RwmC6j.js} +2 -2
  87. package/dist/admin/assets/{circle-x-Dx6zIZko.js.map → circle-x-c1RwmC6j.js.map} +1 -1
  88. package/dist/admin/assets/{clock-DQvkffqQ.js → clock-BOtPNp9-.js} +2 -2
  89. package/dist/admin/assets/{clock-DQvkffqQ.js.map → clock-BOtPNp9-.js.map} +1 -1
  90. package/dist/admin/assets/cn-M4_WHiur.js +2 -0
  91. package/dist/admin/assets/cn-M4_WHiur.js.map +1 -0
  92. package/dist/admin/assets/coerce-DWI5sgLW.js +2 -0
  93. package/dist/admin/assets/{coerce-OsqQRj19.js.map → coerce-DWI5sgLW.js.map} +1 -1
  94. package/dist/admin/assets/{constants-SrImR6C3.js → constants-CgUdkWmf.js} +2 -2
  95. package/dist/admin/assets/{constants-SrImR6C3.js.map → constants-CgUdkWmf.js.map} +1 -1
  96. package/dist/admin/assets/content-D25ainqV.js +3 -0
  97. package/dist/admin/assets/{content-_SLbbnfX.js.map → content-D25ainqV.js.map} +1 -1
  98. package/dist/admin/assets/{contentCounts-Ch3uMXVU.js → contentCounts-T-LmvUf5.js} +2 -2
  99. package/dist/admin/assets/{contentCounts-Ch3uMXVU.js.map → contentCounts-T-LmvUf5.js.map} +1 -1
  100. package/dist/admin/assets/{contentTypes-BCOdUK_Z.js → contentTypes-BSlp81l1.js} +2 -2
  101. package/dist/admin/assets/{contentTypes-BCOdUK_Z.js.map → contentTypes-BSlp81l1.js.map} +1 -1
  102. package/dist/admin/assets/{copy-BDc3r0fO.js → copy-DT7dAzVm.js} +2 -2
  103. package/dist/admin/assets/{copy-BDc3r0fO.js.map → copy-DT7dAzVm.js.map} +1 -1
  104. package/dist/admin/assets/{dateTimeLocal-C92tyKwz.js → dateTimeLocal-DRq7DuYO.js} +2 -2
  105. package/dist/admin/assets/{dateTimeLocal-C92tyKwz.js.map → dateTimeLocal-DRq7DuYO.js.map} +1 -1
  106. package/dist/admin/assets/{develop-DS9TlDmv.js → develop-qF10t9tM.js} +6 -6
  107. package/dist/admin/assets/{develop-DS9TlDmv.js.map → develop-qF10t9tM.js.map} +1 -1
  108. package/dist/admin/assets/{dropdown-menu-gmP2Rp3n.js → dropdown-menu-Cl20LCvY.js} +2 -2
  109. package/dist/admin/assets/{dropdown-menu-gmP2Rp3n.js.map → dropdown-menu-Cl20LCvY.js.map} +1 -1
  110. package/dist/admin/assets/{ellipsis-NK0pWgqU.js → ellipsis-xDmHl1SK.js} +2 -2
  111. package/dist/admin/assets/{ellipsis-NK0pWgqU.js.map → ellipsis-xDmHl1SK.js.map} +1 -1
  112. package/dist/admin/assets/{external-link-D3Aoi2yX.js → external-link-DtJAaOWk.js} +2 -2
  113. package/dist/admin/assets/{external-link-D3Aoi2yX.js.map → external-link-DtJAaOWk.js.map} +1 -1
  114. package/dist/admin/assets/{file-text-DxXAmmxL.js → file-text-BJkQMeQO.js} +2 -2
  115. package/dist/admin/assets/{file-text-DxXAmmxL.js.map → file-text-BJkQMeQO.js.map} +1 -1
  116. package/dist/admin/assets/{fileRoute-C2tOrKhO.js → fileRoute-Buucm0CU.js} +2 -2
  117. package/dist/admin/assets/{fileRoute-C2tOrKhO.js.map → fileRoute-Buucm0CU.js.map} +1 -1
  118. package/dist/admin/assets/{followPlural-CN6cQfNk.js → followPlural-Cuocm4Sv.js} +2 -2
  119. package/dist/admin/assets/{followPlural-CN6cQfNk.js.map → followPlural-Cuocm4Sv.js.map} +1 -1
  120. package/dist/admin/assets/{globe-CuElSilX.js → globe-CAhrwBgv.js} +2 -2
  121. package/dist/admin/assets/{globe-CuElSilX.js.map → globe-CAhrwBgv.js.map} +1 -1
  122. package/dist/admin/assets/{image-CsH8dZve.js → image-CGN6ffEu.js} +2 -2
  123. package/dist/admin/assets/{image-CsH8dZve.js.map → image-CGN6ffEu.js.map} +1 -1
  124. package/dist/admin/assets/{image-off-B_uBCsYu.js → image-off-0zX_kODH.js} +2 -2
  125. package/dist/admin/assets/{image-off-B_uBCsYu.js.map → image-off-0zX_kODH.js.map} +1 -1
  126. package/dist/admin/assets/{inbox-DsuXNaKE.js → inbox-CWq1hHqK.js} +2 -2
  127. package/dist/admin/assets/{inbox-DsuXNaKE.js.map → inbox-CWq1hHqK.js.map} +1 -1
  128. package/dist/admin/assets/{inbox-3iAHL3pC.js → inbox-CukkApHK.js} +2 -2
  129. package/dist/admin/assets/{inbox-3iAHL3pC.js.map → inbox-CukkApHK.js.map} +1 -1
  130. package/dist/admin/assets/index-C9uJxg4J.js +4 -0
  131. package/dist/admin/assets/index-C9uJxg4J.js.map +1 -0
  132. package/dist/admin/assets/index-Cz3C-LxB.css +2 -0
  133. package/dist/admin/assets/{key-round-B17HiT3K.js → key-round-DnE-2gr0.js} +2 -2
  134. package/dist/admin/assets/{key-round-B17HiT3K.js.map → key-round-DnE-2gr0.js.map} +1 -1
  135. package/dist/admin/assets/{link-2-off-hWGS8LW6.js → link-2-off-Cbpvb3w4.js} +2 -2
  136. package/dist/admin/assets/{link-2-off-hWGS8LW6.js.map → link-2-off-Cbpvb3w4.js.map} +1 -1
  137. package/dist/admin/assets/{link-bhHmG4Tc.js → link-DPniuk00.js} +2 -2
  138. package/dist/admin/assets/{link-bhHmG4Tc.js.map → link-DPniuk00.js.map} +1 -1
  139. package/dist/admin/assets/{list-DRtRdjBC.js → list-CkHF_4xh.js} +2 -2
  140. package/dist/admin/assets/{list-DRtRdjBC.js.map → list-CkHF_4xh.js.map} +1 -1
  141. package/dist/admin/assets/{locales-BzPbz8FF.js → locales-DFLQ17ow.js} +2 -2
  142. package/dist/admin/assets/{locales-BzPbz8FF.js.map → locales-DFLQ17ow.js.map} +1 -1
  143. package/dist/admin/assets/{media-Xzot1ueY.js → media-CKTs3j83.js} +2 -2
  144. package/dist/admin/assets/{media-Xzot1ueY.js.map → media-CKTs3j83.js.map} +1 -1
  145. package/dist/admin/assets/{media-WLnWaL7U.js → media-CUfV74JZ.js} +2 -2
  146. package/dist/admin/assets/{media-WLnWaL7U.js.map → media-CUfV74JZ.js.map} +1 -1
  147. package/dist/admin/assets/{network-DHGqLwji.js → network-Fri1cjMt.js} +2 -2
  148. package/dist/admin/assets/{network-DHGqLwji.js.map → network-Fri1cjMt.js.map} +1 -1
  149. package/dist/admin/assets/{pencil-Bdp2z9D_.js → pencil-BqAgKC9v.js} +2 -2
  150. package/dist/admin/assets/{pencil-Bdp2z9D_.js.map → pencil-BqAgKC9v.js.map} +1 -1
  151. package/dist/admin/assets/{plus-BN_iSsnp.js → plus-DLRcpVhJ.js} +2 -2
  152. package/dist/admin/assets/{plus-BN_iSsnp.js.map → plus-DLRcpVhJ.js.map} +1 -1
  153. package/dist/admin/assets/{preload-helper-DAbCfXID.js → preload-helper-BHNBIF88.js} +2 -2
  154. package/dist/admin/assets/{preload-helper-DAbCfXID.js.map → preload-helper-BHNBIF88.js.map} +1 -1
  155. package/dist/admin/assets/{publishing-d9yhMwgA.js → publishing-DaaE8PaU.js} +2 -2
  156. package/dist/admin/assets/{publishing-d9yhMwgA.js.map → publishing-DaaE8PaU.js.map} +1 -1
  157. package/dist/admin/assets/{radio-group-DHIxK-IU.js → radio-group-CA3XlQcc.js} +2 -2
  158. package/dist/admin/assets/{radio-group-DHIxK-IU.js.map → radio-group-CA3XlQcc.js.map} +1 -1
  159. package/dist/admin/assets/{rotate-ccw-CBoZle-b.js → rotate-ccw-CfrkcgYa.js} +2 -2
  160. package/dist/admin/assets/{rotate-ccw-CBoZle-b.js.map → rotate-ccw-CfrkcgYa.js.map} +1 -1
  161. package/dist/admin/assets/{schedules-Dy89Nm6P.js → schedules-hJQkjEhN.js} +2 -2
  162. package/dist/admin/assets/{schedules-Dy89Nm6P.js.map → schedules-hJQkjEhN.js.map} +1 -1
  163. package/dist/admin/assets/{schema-B4smPFcR.js → schema--xSR7cf9.js} +2 -2
  164. package/dist/admin/assets/{schema-B4smPFcR.js.map → schema--xSR7cf9.js.map} +1 -1
  165. package/dist/admin/assets/{scroll-text-UbyzpWh-.js → scroll-text-BA9yh7Za.js} +2 -2
  166. package/dist/admin/assets/{scroll-text-UbyzpWh-.js.map → scroll-text-BA9yh7Za.js.map} +1 -1
  167. package/dist/admin/assets/{search-_YlNpPvU.js → search--71HzPvh.js} +2 -2
  168. package/dist/admin/assets/{search-_YlNpPvU.js.map → search--71HzPvh.js.map} +1 -1
  169. package/dist/admin/assets/{searchSchema-CvOhn4FV.js → searchSchema-Ypl6gq9g.js} +2 -2
  170. package/dist/admin/assets/{searchSchema-CvOhn4FV.js.map → searchSchema-Ypl6gq9g.js.map} +1 -1
  171. package/dist/admin/assets/{searchSchemas-BxRnxenn.js → searchSchemas-C-nQbyhd.js} +2 -2
  172. package/dist/admin/assets/{searchSchemas-BxRnxenn.js.map → searchSchemas-C-nQbyhd.js.map} +1 -1
  173. package/dist/admin/assets/sections-BBN2JErq.js +2 -0
  174. package/dist/admin/assets/sections-BBN2JErq.js.map +1 -0
  175. package/dist/admin/assets/{sections-Bb3Q4bcY.js → sections-D0OZozD2.js} +2 -2
  176. package/dist/admin/assets/{sections-Bb3Q4bcY.js.map → sections-D0OZozD2.js.map} +1 -1
  177. package/dist/admin/assets/{select-tHVWX85X.js → select-De48SQdZ.js} +2 -2
  178. package/dist/admin/assets/{select-tHVWX85X.js.map → select-De48SQdZ.js.map} +1 -1
  179. package/dist/admin/assets/{send-BqECHYi6.js → send-NuSmLVSD.js} +2 -2
  180. package/dist/admin/assets/{send-BqECHYi6.js.map → send-NuSmLVSD.js.map} +1 -1
  181. package/dist/admin/assets/settings-CYXU73dP.js +2 -0
  182. package/dist/admin/assets/settings-CYXU73dP.js.map +1 -0
  183. package/dist/admin/assets/skeleton-CCwdO9oq.js +42 -0
  184. package/dist/admin/assets/skeleton-CCwdO9oq.js.map +1 -0
  185. package/dist/admin/assets/{snapshots-BQrhsCmC.js → snapshots-BQeFK5cj.js} +2 -2
  186. package/dist/admin/assets/{snapshots-BQrhsCmC.js.map → snapshots-BQeFK5cj.js.map} +1 -1
  187. package/dist/admin/assets/{sparkles-BW4CbD6W.js → sparkles-B-0I44es.js} +2 -2
  188. package/dist/admin/assets/{sparkles-BW4CbD6W.js.map → sparkles-B-0I44es.js.map} +1 -1
  189. package/dist/admin/assets/{src-XaU0N8C-.js → src-CQu-YxUM.js} +2 -2
  190. package/dist/admin/assets/src-CQu-YxUM.js.map +1 -0
  191. package/dist/admin/assets/{stableId-C9Yq5iUw.js → stableId-rPnYVF-A.js} +2 -2
  192. package/dist/admin/assets/{stableId-C9Yq5iUw.js.map → stableId-rPnYVF-A.js.map} +1 -1
  193. package/dist/admin/assets/{store-BphwUzV5.js → store-Bqd9L9DK.js} +2 -2
  194. package/dist/admin/assets/{store-BphwUzV5.js.map → store-Bqd9L9DK.js.map} +1 -1
  195. package/dist/admin/assets/{switch-BVy02Vc8.js → switch-JmfyFAQG.js} +2 -2
  196. package/dist/admin/assets/{switch-BVy02Vc8.js.map → switch-JmfyFAQG.js.map} +1 -1
  197. package/dist/admin/assets/{table-CZYtdoHn.js → table-BEDv9IRM.js} +2 -2
  198. package/dist/admin/assets/{table-CZYtdoHn.js.map → table-BEDv9IRM.js.map} +1 -1
  199. package/dist/admin/assets/{tabs-quHbeVxA.js → tabs-DSS1c-Kf.js} +4 -4
  200. package/dist/admin/assets/{tabs-quHbeVxA.js.map → tabs-DSS1c-Kf.js.map} +1 -1
  201. package/dist/admin/assets/{textarea-B8Vl5gJ1.js → textarea-eatLAWE_.js} +2 -2
  202. package/dist/admin/assets/{textarea-B8Vl5gJ1.js.map → textarea-eatLAWE_.js.map} +1 -1
  203. package/dist/admin/assets/{titles-B2z5ckxG.js → titles-Bi2fWNN4.js} +2 -2
  204. package/dist/admin/assets/{titles-B2z5ckxG.js.map → titles-Bi2fWNN4.js.map} +1 -1
  205. package/dist/admin/assets/{tokens-Fh_OeIlf.js → tokens-C2E7swse.js} +2 -2
  206. package/dist/admin/assets/{tokens-Fh_OeIlf.js.map → tokens-C2E7swse.js.map} +1 -1
  207. package/dist/admin/assets/{tooltip-DwqM3opE.js → tooltip-Bji9fmSr.js} +2 -2
  208. package/dist/admin/assets/{tooltip-DwqM3opE.js.map → tooltip-Bji9fmSr.js.map} +1 -1
  209. package/dist/admin/assets/{undo-2-D4Fz4JMZ.js → undo-2-BU-uqm7e.js} +2 -2
  210. package/dist/admin/assets/{undo-2-D4Fz4JMZ.js.map → undo-2-BU-uqm7e.js.map} +1 -1
  211. package/dist/admin/assets/{useAuditLabels-DKrsrciQ.js → useAuditLabels-D5nmbXqN.js} +2 -2
  212. package/dist/admin/assets/{useAuditLabels-DKrsrciQ.js.map → useAuditLabels-D5nmbXqN.js.map} +1 -1
  213. package/dist/admin/assets/{useConfirmTarget-Dp8mCulm.js → useConfirmTarget-Y7_8kVX5.js} +2 -2
  214. package/dist/admin/assets/{useConfirmTarget-Dp8mCulm.js.map → useConfirmTarget-Y7_8kVX5.js.map} +1 -1
  215. package/dist/admin/assets/useContentSchema-DJTqjlUk.js +2 -0
  216. package/dist/admin/assets/{useContentSchema-Bv4_6VJR.js.map → useContentSchema-DJTqjlUk.js.map} +1 -1
  217. package/dist/admin/assets/{useCursorPager-B1upg5EU.js → useCursorPager-CiJroOo7.js} +2 -2
  218. package/dist/admin/assets/{useCursorPager-B1upg5EU.js.map → useCursorPager-CiJroOo7.js.map} +1 -1
  219. package/dist/admin/assets/{useDiscardGuard-34ynGJIk.js → useDiscardGuard-t_N3l9TQ.js} +2 -2
  220. package/dist/admin/assets/{useDiscardGuard-34ynGJIk.js.map → useDiscardGuard-t_N3l9TQ.js.map} +1 -1
  221. package/dist/admin/assets/{useMediaUploader-QXSjZStv.js → useMediaUploader-CLHmKCwq.js} +2 -2
  222. package/dist/admin/assets/{useMediaUploader-QXSjZStv.js.map → useMediaUploader-CLHmKCwq.js.map} +1 -1
  223. package/dist/admin/assets/{usePaletteItems-62-TZhy7.js → usePaletteItems-BVjWCdow.js} +2 -2
  224. package/dist/admin/assets/{usePaletteItems-62-TZhy7.js.map → usePaletteItems-BVjWCdow.js.map} +1 -1
  225. package/dist/admin/assets/{usePlaces-CY6f2DmI.js → usePlaces-CnSOZwtO.js} +2 -2
  226. package/dist/admin/assets/{usePlaces-CY6f2DmI.js.map → usePlaces-CnSOZwtO.js.map} +1 -1
  227. package/dist/admin/assets/{useQueries-BuKJBA0M.js → useQueries-BRolGUiw.js} +2 -2
  228. package/dist/admin/assets/{useQueries-BuKJBA0M.js.map → useQueries-BRolGUiw.js.map} +1 -1
  229. package/dist/admin/assets/{useSchemaScopeAccess-DUBeS9kU.js → useSchemaScopeAccess-bd8RQxuu.js} +2 -2
  230. package/dist/admin/assets/{useSchemaScopeAccess-DUBeS9kU.js.map → useSchemaScopeAccess-bd8RQxuu.js.map} +1 -1
  231. package/dist/admin/assets/{users-BOn2ohJQ.js → users-CbErtM7w.js} +2 -2
  232. package/dist/admin/assets/{users-BOn2ohJQ.js.map → users-CbErtM7w.js.map} +1 -1
  233. package/dist/admin/assets/{users-BkKc4MNB.js → users-bDGYpSXg.js} +2 -2
  234. package/dist/admin/assets/{users-BkKc4MNB.js.map → users-bDGYpSXg.js.map} +1 -1
  235. package/dist/admin/assets/{zod-BHAd1wTC.js → zod-Gtw4e0qi.js} +2 -2
  236. package/dist/admin/assets/{zod-BHAd1wTC.js.map → zod-Gtw4e0qi.js.map} +1 -1
  237. package/dist/admin/favicon-32.png +0 -0
  238. package/dist/admin/favicon.svg +2 -2
  239. package/dist/admin/index.html +60 -56
  240. package/dist/admin/theme-init.js +41 -9
  241. package/dist/cli.js +30 -6
  242. package/dist/cli.js.map +1 -1
  243. package/dist/config.d.ts +2 -2
  244. package/dist/config.js.map +1 -1
  245. package/dist/index.d.ts +11 -224
  246. package/dist/index.js +1 -1
  247. package/dist/{public-CGO8RBn3.d.ts → public-B7fFHMNr.d.ts} +251 -5
  248. package/dist/{worker-BLXy8aRW.js → worker-BAmQPoTJ.js} +574 -108
  249. package/dist/worker-BAmQPoTJ.js.map +1 -0
  250. package/package.json +5 -5
  251. package/dist/admin/assets/Form-DZRLD3vH.js +0 -2
  252. package/dist/admin/assets/RadioTile-BpB79rIK.js.map +0 -1
  253. package/dist/admin/assets/Structure-CqRa2t7z.js +0 -2
  254. package/dist/admin/assets/chevron-left-BsQYDUEC.js +0 -2
  255. package/dist/admin/assets/cn-BQ3voEft.js +0 -2
  256. package/dist/admin/assets/cn-BQ3voEft.js.map +0 -1
  257. package/dist/admin/assets/coerce-OsqQRj19.js +0 -2
  258. package/dist/admin/assets/content-_SLbbnfX.js +0 -3
  259. package/dist/admin/assets/index-BLYOgCKc.css +0 -2
  260. package/dist/admin/assets/index-BSzXA60W.js +0 -4
  261. package/dist/admin/assets/index-BSzXA60W.js.map +0 -1
  262. package/dist/admin/assets/sections-DiySZZHb.js +0 -2
  263. package/dist/admin/assets/sections-DiySZZHb.js.map +0 -1
  264. package/dist/admin/assets/settings-D-v8gL8F.js +0 -2
  265. package/dist/admin/assets/settings-D-v8gL8F.js.map +0 -1
  266. package/dist/admin/assets/skeleton-C3QLIh7W.js +0 -42
  267. package/dist/admin/assets/skeleton-C3QLIh7W.js.map +0 -1
  268. package/dist/admin/assets/src-XaU0N8C-.js.map +0 -1
  269. package/dist/admin/assets/useContentSchema-Bv4_6VJR.js +0 -2
  270. package/dist/worker-BLXy8aRW.js.map +0 -1
@@ -6,10 +6,10 @@ import { dirname, join, relative, resolve, sep } from "node:path";
6
6
  import Fastify from "fastify";
7
7
  import { AliasNode, AndNode, BinaryOperationNode, ColumnNode, ColumnUpdateNode, IdentifierNode, InsertQueryNode, Kysely, ListNode, MysqlAdapter, MysqlIntrospector, MysqlQueryCompiler, OnDuplicateKeyNode, OperationNodeTransformer, OperatorNode, OrNode, OrderByItemNode, ParensNode, PostgresDialect, PrimitiveValueListNode, RawNode, ReferenceNode, SelectAllNode, SelectModifierNode, SelectQueryNode, SelectionNode, SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler, TableNode, UpdateQueryNode, ValueListNode, ValueNode, ValuesNode, WhereNode, sql } from "kysely";
8
8
  import pg from "pg";
9
+ import { AsyncLocalStorage } from "node:async_hooks";
9
10
  import mysql from "mysql2";
10
11
  import { EventEmitter } from "node:events";
11
12
  import { DatabaseSync } from "node:sqlite";
12
- import { AsyncLocalStorage } from "node:async_hooks";
13
13
  import { Type } from "typebox";
14
14
  import { Value } from "typebox/value";
15
15
  import { isDeepStrictEqual } from "node:util";
@@ -116,6 +116,44 @@ const purgeDeletedEntriesOfModels = async (modelIds, trx) => {
116
116
  await trx.deleteFrom("entries").where("model_id", "in", modelIds).where("deleted_at", "is not", null).execute();
117
117
  };
118
118
  //#endregion
119
+ //#region src/db/connectionScope.ts
120
+ const scopes$1 = new AsyncLocalStorage();
121
+ var NestedPoolAcquireError = class extends Error {
122
+ constructor() {
123
+ super("A query inside an open transaction asked the pool for a second connection; under load every connection waits for another. Run it on the transaction (trx) instead.");
124
+ this.name = "NestedPoolAcquireError";
125
+ }
126
+ };
127
+ const holdsConnection = (scope) => {
128
+ for (let current = scope; current; current = current.parent) if (current.acquired) return true;
129
+ return false;
130
+ };
131
+ /**
132
+ * Called by a driver before it takes a pooled connection. The first acquisition of a scope is the
133
+ * transaction's own connection; any later one, or one inside a scope that already holds a connection, is
134
+ * nested.
135
+ */
136
+ const assertNotNested = () => {
137
+ const scope = scopes$1.getStore();
138
+ if (!scope) return;
139
+ if (holdsConnection(scope)) throw new NestedPoolAcquireError();
140
+ scope.acquired = true;
141
+ };
142
+ /**
143
+ * Runs `fn` as if no transaction were open: for work that does not belong to the enclosing transaction and
144
+ * that it never waits for (notifications published at commit, a test's independent request).
145
+ */
146
+ const outsideConnectionScope = (fn) => scopes$1.exit(fn);
147
+ /** Runs every `execute` of a transaction builder (and of the builders it returns) in a connection scope. */
148
+ const withConnectionScope = (builder) => new Proxy(builder, { get(target, property, receiver) {
149
+ if (property === "execute") return (callback) => scopes$1.run({
150
+ acquired: false,
151
+ parent: scopes$1.getStore()
152
+ }, () => target.execute(callback));
153
+ if (property === "setIsolationLevel" || property === "setAccessMode") return (value) => withConnectionScope(target[property](value));
154
+ return Reflect.get(target, property, receiver);
155
+ } });
156
+ //#endregion
119
157
  //#region src/db/dialect.ts
120
158
  const SQLITE_PREFIX = "sqlite:";
121
159
  const MEMORY = ":memory:";
@@ -2509,6 +2547,63 @@ const subscribeNotifications = (databaseKey, handler) => {
2509
2547
  };
2510
2548
  };
2511
2549
  //#endregion
2550
+ //#region src/helpers/appError.ts
2551
+ /**
2552
+ * An error with a stable machine-readable code, rendered by the central error handler as
2553
+ * `{ error: { code, message, details? } }`. Services throw these; they carry no HTTP objects.
2554
+ */
2555
+ var AppError = class extends Error {
2556
+ statusCode;
2557
+ code;
2558
+ details;
2559
+ constructor(statusCode, code, message, details, options) {
2560
+ super(message, options);
2561
+ this.name = "AppError";
2562
+ this.statusCode = statusCode;
2563
+ this.code = code;
2564
+ this.details = details;
2565
+ }
2566
+ };
2567
+ //#endregion
2568
+ //#region src/db/poolAcquire.ts
2569
+ /**
2570
+ * How long a query waits for a free pooled connection (PostgreSQL and MySQL) before it fails with
2571
+ * `PoolAcquireTimeoutError`. Without a bound, an exhausted pool queues requests forever and the server
2572
+ * stops answering until it restarts. SQLite has no such wait (see `db/sqlite/driver.ts`).
2573
+ */
2574
+ const DEFAULT_POOL_ACQUIRE_TIMEOUT_MS = 1e4;
2575
+ /**
2576
+ * No pooled connection became free in time. A 503 the client may retry; the central error handler logs it
2577
+ * as a failed request, so an exhausted pool shows in the logs instead of as a silent hang.
2578
+ */
2579
+ var PoolAcquireTimeoutError = class extends AppError {
2580
+ constructor(timeoutMs, poolMax, options) {
2581
+ super(503, "DATABASE_BUSY", `No database connection became free within ${timeoutMs} ms (pool of ${poolMax}); try again shortly`, void 0, options);
2582
+ this.name = "PoolAcquireTimeoutError";
2583
+ }
2584
+ };
2585
+ /**
2586
+ * Opening a new connection took longer than the acquire timeout: the database is unreachable or overloaded.
2587
+ * A 503 the client may retry, logged by the central error handler like `PoolAcquireTimeoutError`.
2588
+ */
2589
+ var DatabaseUnavailableError = class extends AppError {
2590
+ constructor(timeoutMs, options) {
2591
+ super(503, "DATABASE_UNAVAILABLE", `Could not connect to the database within ${timeoutMs} ms; try again shortly`, void 0, options);
2592
+ this.name = "DatabaseUnavailableError";
2593
+ }
2594
+ };
2595
+ /** pg-pool's error when `connectionTimeoutMillis` passes while the request waits in the pool's queue. */
2596
+ const PG_QUEUE_TIMEOUT_MESSAGE = "timeout exceeded when trying to connect";
2597
+ /** pg-pool's error when `connectionTimeoutMillis` passes while it opens a new connection. */
2598
+ const PG_CONNECT_TIMEOUT_MESSAGE = "Connection terminated due to connection timeout";
2599
+ /** The 503 for a pg-pool acquisition error, or the error itself when it is not a timeout. */
2600
+ const pgAcquireError = (error, poolMax, timeoutMs) => {
2601
+ if (!(error instanceof Error)) return error;
2602
+ if (error.message === PG_QUEUE_TIMEOUT_MESSAGE) return new PoolAcquireTimeoutError(timeoutMs, poolMax, { cause: error });
2603
+ if (error.message === PG_CONNECT_TIMEOUT_MESSAGE) return new DatabaseUnavailableError(timeoutMs, { cause: error });
2604
+ return error;
2605
+ };
2606
+ //#endregion
2512
2607
  //#region src/db/mysql/codec.ts
2513
2608
  /**
2514
2609
  * The MySQL value codec (ADR 0001, "MySQL"). Shapio's PostgreSQL types map to MySQL like this, and rows
@@ -2691,7 +2786,9 @@ var MysqlConnection = class {
2691
2786
  this.#pending = [];
2692
2787
  this.#inTransaction = false;
2693
2788
  await Promise.all([this.#query(command), this.#query(UNPIN_TIMESTAMP)]);
2694
- if (command === "commit") for (const { channel, payload, id } of pending) publishNotification(this.#databaseKey, channel, payload, id);
2789
+ if (command === "commit") outsideConnectionScope(() => {
2790
+ for (const { channel, payload, id } of pending) publishNotification(this.#databaseKey, channel, payload, id);
2791
+ });
2695
2792
  }
2696
2793
  async #nextSequenceValue(name) {
2697
2794
  const table = sequenceTableOf(name);
@@ -2775,6 +2872,9 @@ var MysqlConnection = class {
2775
2872
  var MysqlDriver = class {
2776
2873
  #options;
2777
2874
  databaseKey;
2875
+ get strict() {
2876
+ return this.#options.strict ?? false;
2877
+ }
2778
2878
  #pool;
2779
2879
  #connections = /* @__PURE__ */ new WeakMap();
2780
2880
  constructor(options) {
@@ -2798,10 +2898,29 @@ var MysqlDriver = class {
2798
2898
  if (!this.#pool) throw new Error("The MySQL driver is not initialised");
2799
2899
  return this.#pool;
2800
2900
  }
2801
- async acquireConnection() {
2802
- const raw = await new Promise((resolve, reject) => {
2803
- this.#pooled().getConnection((error, connection) => error ? reject(error) : resolve(connection));
2901
+ /**
2902
+ * A pooled connection, or `PoolAcquireTimeoutError` once `acquireTimeoutMs` passes. mysql2 cannot withdraw
2903
+ * a queued request, so a connection that arrives after the timeout goes straight back to the pool.
2904
+ */
2905
+ #getConnection() {
2906
+ const { acquireTimeoutMs = DEFAULT_POOL_ACQUIRE_TIMEOUT_MS, poolMax } = this.#options;
2907
+ return new Promise((resolve, reject) => {
2908
+ let timedOut = false;
2909
+ const timer = setTimeout(() => {
2910
+ timedOut = true;
2911
+ reject(new PoolAcquireTimeoutError(acquireTimeoutMs, poolMax));
2912
+ }, acquireTimeoutMs);
2913
+ this.#pooled().getConnection((error, connection) => {
2914
+ clearTimeout(timer);
2915
+ if (timedOut) connection?.release();
2916
+ else if (error) reject(error);
2917
+ else resolve(connection);
2918
+ });
2804
2919
  });
2920
+ }
2921
+ async acquireConnection() {
2922
+ if (this.#options.strict) assertNotNested();
2923
+ const raw = await this.#getConnection();
2805
2924
  let connection = this.#connections.get(raw);
2806
2925
  if (!connection) {
2807
2926
  connection = new MysqlConnection(raw, this.databaseKey);
@@ -3246,6 +3365,11 @@ var MysqlKysely = class extends Kysely {
3246
3365
  });
3247
3366
  this.mysqlDriver = driver;
3248
3367
  }
3368
+ /** Strict mode runs transactions in a connection scope (`db/connectionScope.ts`). */
3369
+ transaction() {
3370
+ const builder = super.transaction();
3371
+ return this.mysqlDriver.strict ? withConnectionScope(builder) : builder;
3372
+ }
3249
3373
  };
3250
3374
  const createMysqlDb = (options) => new MysqlKysely(new MysqlDriver(options));
3251
3375
  /** The MySQL driver behind a handle, or undefined for another dialect. */
@@ -4272,6 +4396,29 @@ const sqliteDriverOf = (db) => db instanceof SqliteKysely ? db.sqliteDriver : vo
4272
4396
  //#endregion
4273
4397
  //#region src/db/index.ts
4274
4398
  const warnIdleConnectionError = (error) => process.emitWarning(`PostgreSQL connection lost while idle: ${error.message}`);
4399
+ /**
4400
+ * The pg pool as Kysely sees it: an exhausted pool's queue timeout becomes `PoolAcquireTimeoutError`, a
4401
+ * connection that cannot be opened in time `DatabaseUnavailableError`, and in strict mode a nested
4402
+ * acquisition fails at once.
4403
+ */
4404
+ const acquiringPool = (pool, poolMax, acquireTimeoutMs, strict) => ({
4405
+ options: pool.options,
4406
+ connect: async () => {
4407
+ if (strict) assertNotNested();
4408
+ try {
4409
+ return await pool.connect();
4410
+ } catch (error) {
4411
+ throw pgAcquireError(error, poolMax, acquireTimeoutMs);
4412
+ }
4413
+ },
4414
+ end: () => pool.end()
4415
+ });
4416
+ /** Kysely on PostgreSQL in strict mode: transactions run in a connection scope (`db/connectionScope.ts`). */
4417
+ var StrictPostgresKysely = class extends Kysely {
4418
+ transaction() {
4419
+ return withConnectionScope(super.transaction());
4420
+ }
4421
+ };
4275
4422
  const warnIdleMysqlConnectionError = (error) => process.emitWarning(`MySQL connection lost while idle: ${error.message}`);
4276
4423
  /**
4277
4424
  * The database handle for DATABASE_URL: PostgreSQL through a `pg` pool, MySQL (`mysql://…`) through a
@@ -4279,7 +4426,7 @@ const warnIdleMysqlConnectionError = (error) => process.emitWarning(`MySQL conne
4279
4426
  * number of read connections (one connection writes).
4280
4427
  * Sets the process dialect (`db/dialect.ts`) that SQL builders read.
4281
4428
  */
4282
- const createDb = ({ connectionString, poolMax, applicationName = "shapio", onIdleConnectionError, strict = false }) => {
4429
+ const createDb = ({ connectionString, poolMax, acquireTimeoutMs = DEFAULT_POOL_ACQUIRE_TIMEOUT_MS, applicationName = "shapio", onIdleConnectionError, strict = false }) => {
4283
4430
  if (dialectOfUrl(connectionString) === "sqlite") {
4284
4431
  setCurrentDialect("sqlite");
4285
4432
  return createSqliteDb({
@@ -4293,21 +4440,28 @@ const createDb = ({ connectionString, poolMax, applicationName = "shapio", onIdl
4293
4440
  return createMysqlDb({
4294
4441
  url: connectionString,
4295
4442
  poolMax,
4443
+ acquireTimeoutMs,
4296
4444
  applicationName,
4297
- onIdleConnectionError: onIdleConnectionError ?? warnIdleMysqlConnectionError
4445
+ onIdleConnectionError: onIdleConnectionError ?? warnIdleMysqlConnectionError,
4446
+ strict
4298
4447
  });
4299
4448
  }
4300
4449
  setCurrentDialect("postgres");
4301
4450
  const pool = new pg.Pool({
4302
4451
  connectionString,
4303
4452
  max: poolMax,
4453
+ connectionTimeoutMillis: acquireTimeoutMs,
4304
4454
  application_name: applicationName
4305
4455
  });
4306
4456
  pool.on("error", onIdleConnectionError ?? warnIdleConnectionError);
4307
4457
  pool.on("connect", (client) => {
4308
4458
  client.on("error", () => void 0);
4309
4459
  });
4310
- return new Kysely({ dialect: new PostgresDialect({ pool }) });
4460
+ const dialect = new PostgresDialect({
4461
+ pool: acquiringPool(pool, poolMax, acquireTimeoutMs, strict),
4462
+ controlClient: pg.Client
4463
+ });
4464
+ return strict ? new StrictPostgresKysely({ dialect }) : new Kysely({ dialect });
4311
4465
  };
4312
4466
  /**
4313
4467
  * The process-wide database handle used as the default executor by repositories.
@@ -7786,23 +7940,182 @@ const plainTextToRichText = (text) => {
7786
7940
  }
7787
7941
  };
7788
7942
  };
7789
- //#endregion
7790
- //#region src/helpers/appError.ts
7791
7943
  /**
7792
- * An error with a stable machine-readable code, rendered by the central error handler as
7793
- * `{ error: { code, message, details? } }`. Services throw these; they carry no HTTP objects.
7944
+ * Admin themes (docs/plans/themes.md): a theme is a named, complete set of these tokens for a light variant, a
7945
+ * dark variant or both. One list, shared by the admin's stylesheets (and their tests) and the API, which
7946
+ * validates themes declared by extensions (`shapio.config` `themes`).
7947
+ */
7948
+ /** The UI tokens: every theme variant sets all of them. */
7949
+ const THEME_SEMANTIC_TOKENS = [
7950
+ "background",
7951
+ "foreground",
7952
+ "card",
7953
+ "card-foreground",
7954
+ "popover",
7955
+ "popover-foreground",
7956
+ "primary",
7957
+ "primary-foreground",
7958
+ "primary-hover",
7959
+ "secondary",
7960
+ "secondary-foreground",
7961
+ "muted",
7962
+ "muted-foreground",
7963
+ "accent",
7964
+ "accent-foreground",
7965
+ "destructive",
7966
+ "destructive-foreground",
7967
+ "destructive-muted",
7968
+ "success",
7969
+ "success-muted",
7970
+ "warning",
7971
+ "warning-muted",
7972
+ "info",
7973
+ "info-muted",
7974
+ "border",
7975
+ "input",
7976
+ "ring",
7977
+ "link",
7978
+ "overlay",
7979
+ "sidebar",
7980
+ "sidebar-foreground",
7981
+ "sidebar-primary",
7982
+ "sidebar-primary-foreground",
7983
+ "sidebar-accent",
7984
+ "sidebar-accent-foreground",
7985
+ "sidebar-border",
7986
+ "sidebar-ring"
7987
+ ];
7988
+ /**
7989
+ * The logo (tile, S, letters) and the signed-out screens' brand panel. Built-in themes set them all; an
7990
+ * extension theme may leave them out and gets Shapio's for that variant.
7991
+ */
7992
+ const THEME_BRAND_TOKENS = [
7993
+ "brand-logo",
7994
+ "brand-logo-foreground",
7995
+ "brand-letters",
7996
+ "brand-panel-from",
7997
+ "brand-panel-to",
7998
+ "brand-panel-foreground",
7999
+ "brand-panel-mark"
8000
+ ];
8001
+ [...THEME_SEMANTIC_TOKENS, ...THEME_BRAND_TOKENS];
8002
+ const THEME_VARIANTS = ["light", "dark"];
8003
+ /** A theme key: used in `data-theme` and in CSS selectors, so deliberately narrow. */
8004
+ const THEME_KEY_PATTERN = /^[a-z][a-z0-9-]{0,40}$/;
8005
+ /** The themes the admin ships; an extension theme may not reuse these keys. */
8006
+ const BUILT_IN_THEME_KEYS = [
8007
+ "shapio",
8008
+ "classic",
8009
+ "murdered-out",
8010
+ "snowed"
8011
+ ];
8012
+ /**
8013
+ * A token value an extension may use: a hex colour or a colour function whose arguments are numbers, units
8014
+ * and separators only (no `;`, quotes, braces or nested functions such as `url()` or `var()`), so a value
8015
+ * can never escape its declaration.
7794
8016
  */
7795
- var AppError = class extends Error {
7796
- statusCode;
7797
- code;
7798
- details;
7799
- constructor(statusCode, code, message, details, options) {
7800
- super(message, options);
7801
- this.name = "AppError";
7802
- this.statusCode = statusCode;
7803
- this.code = code;
7804
- this.details = details;
7805
- }
8017
+ const THEME_COLOUR_PATTERN = /^(?:#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|(?:rgba?|hsla?|oklch|oklab)\([0-9a-z.,%\s/+-]{1,80}\))$/;
8018
+ const SURFACES = [
8019
+ "background",
8020
+ "card",
8021
+ "popover"
8022
+ ];
8023
+ const THEME_TEXT_PAIRS = [
8024
+ ...SURFACES.map((surface) => ["foreground", surface]),
8025
+ ...[
8026
+ ...SURFACES,
8027
+ "muted",
8028
+ "secondary",
8029
+ "accent",
8030
+ "sidebar"
8031
+ ].map((surface) => ["muted-foreground", surface]),
8032
+ ["primary-foreground", "primary"],
8033
+ ["primary-foreground", "primary-hover"],
8034
+ ["secondary-foreground", "secondary"],
8035
+ ["accent-foreground", "accent"],
8036
+ ["destructive-foreground", "destructive"],
8037
+ ["destructive", "background"],
8038
+ ["destructive", "card"],
8039
+ ["success", "background"],
8040
+ ["success", "card"],
8041
+ ["success", "success-muted"],
8042
+ ["warning", "warning-muted"],
8043
+ ["warning", "card"],
8044
+ ["info", "info-muted"],
8045
+ ["info", "card"],
8046
+ ["destructive", "destructive-muted"],
8047
+ ["foreground", "muted"],
8048
+ ["link", "background"],
8049
+ ["link", "card"],
8050
+ ["sidebar-foreground", "sidebar"],
8051
+ ["sidebar-primary-foreground", "sidebar-primary"],
8052
+ ["sidebar-accent-foreground", "sidebar-accent"],
8053
+ ["sidebar-foreground", "sidebar-accent"],
8054
+ ["muted-foreground", "sidebar-accent"],
8055
+ ...[
8056
+ "destructive",
8057
+ "success",
8058
+ "warning",
8059
+ "link"
8060
+ ].flatMap((text) => [[text, "popover"], [text, "muted"]]),
8061
+ ["brand-panel-foreground", "brand-panel-from"],
8062
+ ["brand-panel-foreground", "brand-panel-to"]
8063
+ ];
8064
+ const THEME_UI_PAIRS = [
8065
+ ...[
8066
+ ...SURFACES,
8067
+ "muted",
8068
+ "secondary"
8069
+ ].map((surface) => ["input", surface]),
8070
+ ...SURFACES.map((surface) => ["ring", surface]),
8071
+ ["ring", "muted"],
8072
+ ["ring", "accent"],
8073
+ ["primary", "background"],
8074
+ ["primary", "card"],
8075
+ ["primary", "popover"],
8076
+ ["primary", "muted"],
8077
+ ["info", "muted"],
8078
+ ["sidebar-ring", "sidebar"],
8079
+ ["sidebar-primary", "sidebar"],
8080
+ ["sidebar-primary", "sidebar-accent"]
8081
+ ];
8082
+ /**
8083
+ * Dark only: control borders on the selection surface. In light, selected table rows use accent/40, which
8084
+ * passes, and other selected surfaces hold no controls.
8085
+ */
8086
+ const THEME_DARK_UI_PAIRS = [["input", "accent"]];
8087
+ const HEX = /^#[0-9a-fA-F]{6}$/;
8088
+ const channel = (value) => {
8089
+ const c = value / 255;
8090
+ return c <= .03928 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
8091
+ };
8092
+ const luminance = (hex) => {
8093
+ const [r, g, b] = [
8094
+ 1,
8095
+ 3,
8096
+ 5
8097
+ ].map((index) => channel(parseInt(hex.slice(index, index + 2), 16)));
8098
+ return .2126 * (r ?? 0) + .7152 * (g ?? 0) + .0722 * (b ?? 0);
8099
+ };
8100
+ /** The WCAG contrast ratio of two `#rrggbb` colours. */
8101
+ const contrastRatio = (a, b) => {
8102
+ const [high = 0, low = 0] = [luminance(a), luminance(b)].sort((x, y) => y - x);
8103
+ return (high + .05) / (low + .05);
8104
+ };
8105
+ /** Every pair the variant must meet, with its ratio. Missing tokens report an undefined ratio. */
8106
+ const checkThemeContrast = (tokens, variant) => {
8107
+ const uiPairs = variant === "dark" ? [...THEME_UI_PAIRS, ...THEME_DARK_UI_PAIRS] : THEME_UI_PAIRS;
8108
+ const measure = ([foreground, background], required) => {
8109
+ const a = tokens[foreground];
8110
+ const b = tokens[background];
8111
+ return {
8112
+ foreground,
8113
+ background,
8114
+ required,
8115
+ ratio: a && b && HEX.test(a) && HEX.test(b) ? contrastRatio(a, b) : void 0
8116
+ };
8117
+ };
8118
+ return [...THEME_TEXT_PAIRS.map((pair) => measure(pair, 4.5)), ...uiPairs.map((pair) => measure(pair, 3))];
7806
8119
  };
7807
8120
  //#endregion
7808
8121
  //#region src/content/model.ts
@@ -10648,9 +10961,10 @@ const toAssetView = async (deps, asset, variants) => {
10648
10961
  /**
10649
10962
  * API views of assets with their variants (one query for all variants) and URLs: stable for public
10650
10963
  * assets, signed and expiring for private ones. Package E can use this to resolve media fields.
10964
+ * `executor`: the caller's open transaction, when there is one.
10651
10965
  */
10652
- const toAssetViews = async (deps, assets) => {
10653
- const variants = await listForAssets(assets.map((asset) => asset.id));
10966
+ const toAssetViews = async (deps, assets, executor) => {
10967
+ const variants = await listForAssets(assets.map((asset) => asset.id), executor);
10654
10968
  const byAsset = Map.groupBy(variants, (variant) => variant.asset_id);
10655
10969
  return Promise.all(assets.map((asset) => toAssetView(deps, asset, byAsset.get(asset.id) ?? [])));
10656
10970
  };
@@ -11636,38 +11950,40 @@ const groupByRole = (grants) => {
11636
11950
  /**
11637
11951
  * Every role's grants, held in memory and reloaded when `system_versions.permissions_version` moves.
11638
11952
  * The version is read on every lookup: that durable check is what makes a permission change visible to
11639
- * every instance at once (NOTIFY would only be an optimisation). Concurrent reloads share one query.
11953
+ * every instance at once (NOTIFY would only be an optimisation). Lookups on the pool share one reload; a
11954
+ * lookup inside a transaction reads (and, when stale, reloads) through that transaction alone, never
11955
+ * through a reload running on another request's transaction.
11640
11956
  */
11641
11957
  const createPermissionCache = (database) => {
11642
11958
  let snapshot;
11643
11959
  let reloading;
11644
- const reload = async (version) => {
11645
- const grants = [...await listAllPermissions$1(database), ...await listAllPermissions(database)].map(toGrant$1).filter((grant) => grant !== void 0);
11646
- const bindings = groupBindings(await listAll(database));
11960
+ const reload = async (version, executor) => {
11961
+ const grants = [...await listAllPermissions$1(executor), ...await listAllPermissions(executor)].map(toGrant$1).filter((grant) => grant !== void 0);
11962
+ const bindings = groupBindings(await listAll(executor));
11647
11963
  return {
11648
11964
  version,
11649
11965
  byRole: groupByRole(grants),
11650
11966
  bindings
11651
11967
  };
11652
11968
  };
11653
- const current = async () => {
11654
- const version = await getPermissionsVersion(database);
11969
+ const sharedReload = (version) => reloading ??= reload(version, database).finally(() => {
11970
+ reloading = void 0;
11971
+ });
11972
+ const current = async (executor) => {
11973
+ const version = await getPermissionsVersion(executor ?? database);
11655
11974
  if (snapshot && snapshot.version === version) return snapshot;
11656
- reloading ??= reload(version).finally(() => {
11657
- reloading = void 0;
11658
- });
11659
- const loaded = await reloading;
11975
+ const loaded = await (executor ? reload(version, executor) : sharedReload(version));
11660
11976
  if (!snapshot || loaded.version >= snapshot.version) snapshot = loaded;
11661
11977
  return loaded;
11662
11978
  };
11663
11979
  return {
11664
- getGrants: async (roleIds) => {
11980
+ getGrants: async (roleIds, executor) => {
11665
11981
  if (roleIds.length === 0) return [];
11666
- const { byRole } = await current();
11982
+ const { byRole } = await current(executor);
11667
11983
  return roleIds.flatMap((roleId) => byRole.get(roleId) ?? []);
11668
11984
  },
11669
- getSiteAppRoleIds: async (siteId, audience) => {
11670
- const { bindings } = await current();
11985
+ getSiteAppRoleIds: async (siteId, audience, executor) => {
11986
+ const { bindings } = await current(executor);
11671
11987
  return bindings.get(bindingKey(siteId, audience)) ?? [];
11672
11988
  }
11673
11989
  };
@@ -11758,7 +12074,7 @@ const APP_ACTIONS = new Set(APP_CONTENT_ACTIONS);
11758
12074
  * outside a site holds no role). Both are delivery audiences: they see `public: false` fields only where a
11759
12075
  * grant names them.
11760
12076
  */
11761
- const rolesOf = async (principal, grants) => {
12077
+ const rolesOf = async (principal, grants, executor) => {
11762
12078
  switch (principal.kind) {
11763
12079
  case "system": return "all";
11764
12080
  case "admin": return {
@@ -11779,13 +12095,13 @@ const rolesOf = async (principal, grants) => {
11779
12095
  actions: READ_ONLY$1
11780
12096
  };
11781
12097
  case "appUser": return {
11782
- roleIds: [...await grants.getSiteAppRoleIds(principal.siteId, "authenticated"), ...principal.roleIds],
12098
+ roleIds: [...await grants.getSiteAppRoleIds(principal.siteId, "authenticated", executor), ...principal.roleIds],
11783
12099
  networkRoleIds: [],
11784
12100
  audience: "delivery",
11785
12101
  actions: APP_ACTIONS
11786
12102
  };
11787
12103
  case "anonymous": return {
11788
- roleIds: principal.siteId === null ? [] : await grants.getSiteAppRoleIds(principal.siteId, "public"),
12104
+ roleIds: principal.siteId === null ? [] : await grants.getSiteAppRoleIds(principal.siteId, "public", executor),
11789
12105
  networkRoleIds: [],
11790
12106
  audience: "delivery",
11791
12107
  actions: APP_ACTIONS
@@ -11808,33 +12124,33 @@ const actsOnSite = (principal, siteId) => {
11808
12124
  * Deny by default at every step.
11809
12125
  */
11810
12126
  const createPermissionEvaluator = ({ grants, fields }) => ({
11811
- evaluate: async (principal, request) => {
11812
- const roles = await rolesOf(principal, grants);
12127
+ evaluate: async (principal, request, executor) => {
12128
+ const roles = await rolesOf(principal, grants, executor);
11813
12129
  if (roles === "all") return ALLOW_ALL_POLICY;
11814
12130
  if (roles.actions !== "all" && !roles.actions.has(request.action)) return DENIED_POLICY;
11815
12131
  let roleIds = roles.roleIds;
11816
12132
  if (NETWORK_CONTENT_ACTIONS.has(request.action)) {
11817
- const modelSite = await fields.getModelSite(request.modelId);
12133
+ const modelSite = await fields.getModelSite(request.modelId, executor);
11818
12134
  roleIds = modelSite !== null && actsOnSite(principal, modelSite) ? roles.roleIds : roles.networkRoleIds;
11819
12135
  }
11820
- const held = await grants.getGrants(roleIds);
12136
+ const held = await grants.getGrants(roleIds, executor);
11821
12137
  if (held.length === 0) return DENIED_POLICY;
11822
- const modelFields = roles.audience === "delivery" ? await fields.getModelFields(request.modelId) : void 0;
12138
+ const modelFields = roles.audience === "delivery" ? await fields.getModelFields(request.modelId, executor) : void 0;
11823
12139
  return buildPolicy(held, request, roles.audience, modelFields);
11824
12140
  },
11825
- canPerform: async (principal, action) => {
11826
- const roles = await rolesOf(principal, grants);
12141
+ canPerform: async (principal, action, executor) => {
12142
+ const roles = await rolesOf(principal, grants, executor);
11827
12143
  if (roles === "all") return true;
11828
12144
  if (roles.audience === "delivery") return false;
11829
12145
  const roleIds = NETWORK_ACTION_SET.has(action) ? roles.networkRoleIds : roles.roleIds;
11830
- return allowsGlobalAction(await grants.getGrants(roleIds), action);
12146
+ return allowsGlobalAction(await grants.getGrants(roleIds, executor), action);
11831
12147
  },
11832
- canPerformOnSite: async (principal, action, siteId) => {
11833
- const roles = await rolesOf(principal, grants);
12148
+ canPerformOnSite: async (principal, action, siteId, executor) => {
12149
+ const roles = await rolesOf(principal, grants, executor);
11834
12150
  if (roles === "all") return true;
11835
12151
  if (roles.audience === "delivery") return false;
11836
12152
  const counted = !NETWORK_ACTION_SET.has(action) || SITE_GRANTABLE_ACTIONS.has(action) && actsOnSite(principal, siteId) ? roles.roleIds : roles.networkRoleIds;
11837
- return allowsGlobalAction(await grants.getGrants(counted), action);
12153
+ return allowsGlobalAction(await grants.getGrants(counted, executor), action);
11838
12154
  }
11839
12155
  });
11840
12156
  //#endregion
@@ -11846,17 +12162,17 @@ const createPermissionEvaluator = ({ grants, fields }) => ({
11846
12162
  * hidden from the API unless a role names them explicitly. Components are not delivery models.
11847
12163
  */
11848
12164
  const createSchemaFieldVisibility = (registry) => {
11849
- const activeModel = async (modelId) => {
11850
- const active = (await registry.getSnapshot()).byId.get(modelId);
12165
+ const activeModel = async (modelId, executor) => {
12166
+ const active = (await registry.getSnapshot(executor)).byId.get(modelId);
11851
12167
  return active && active.definition.kind !== "component" ? active.definition : void 0;
11852
12168
  };
11853
12169
  return {
11854
- getModelFields: async (modelId) => (await activeModel(modelId))?.fields.map((field) => ({
12170
+ getModelFields: async (modelId, executor) => (await activeModel(modelId, executor))?.fields.map((field) => ({
11855
12171
  id: field.id,
11856
12172
  public: field.public && !field.deprecated
11857
12173
  })),
11858
- hasModel: async (modelId) => await activeModel(modelId) !== void 0,
11859
- getModelSite: async (modelId) => (await registry.getSnapshot()).scopeOf(modelId) ?? null
12174
+ hasModel: async (modelId, executor) => await activeModel(modelId, executor) !== void 0,
12175
+ getModelSite: async (modelId, executor) => (await registry.getSnapshot(executor)).scopeOf(modelId) ?? null
11860
12176
  };
11861
12177
  };
11862
12178
  //#endregion
@@ -11898,10 +12214,10 @@ const createSchemaRegistry = ({ db, log }) => {
11898
12214
  }
11899
12215
  };
11900
12216
  return {
11901
- getSnapshot: async () => {
11902
- const version = await getSchemaVersion(db);
12217
+ getSnapshot: async (executor) => {
12218
+ const version = await getSchemaVersion(executor ?? db);
11903
12219
  if (cached && cached.version >= version) return cached;
11904
- return loadAtLeast(version);
12220
+ return executor ? readSnapshot(executor) : loadAtLeast(version);
11905
12221
  },
11906
12222
  peek: () => cached,
11907
12223
  hint: (version) => {
@@ -12619,6 +12935,21 @@ const RESERVED_SERVICE_NAMES = /* @__PURE__ */ new Set([
12619
12935
  "logger"
12620
12936
  ]);
12621
12937
  const modelHooksSchema = Type.Object(Object.fromEntries([...BEFORE_EVENTS, ...AFTER_EVENTS].map((event) => [event, Type.Optional(anyFunction)])), closed$11);
12938
+ /** One theme variant: every UI token, brand tokens optional; values are checked in findThemeProblems. */
12939
+ const themeTokensSchema = Type.Object({
12940
+ ...Object.fromEntries(THEME_SEMANTIC_TOKENS.map((token) => [token, Type.String()])),
12941
+ ...Object.fromEntries(THEME_BRAND_TOKENS.map((token) => [token, Type.Optional(Type.String())]))
12942
+ }, closed$11);
12943
+ const themeSchema = Type.Object({
12944
+ key: Type.String(),
12945
+ name: Type.String({
12946
+ minLength: 1,
12947
+ maxLength: 60
12948
+ }),
12949
+ description: Type.Optional(Type.String({ maxLength: 200 })),
12950
+ light: Type.Optional(themeTokensSchema),
12951
+ dark: Type.Optional(themeTokensSchema)
12952
+ }, closed$11);
12622
12953
  const shapioConfigSchema = Type.Object({
12623
12954
  hooks: Type.Optional(Type.Record(Type.String(), modelHooksSchema)),
12624
12955
  routes: Type.Optional(Type.Array(Type.Object({
@@ -12627,7 +12958,8 @@ const shapioConfigSchema = Type.Object({
12627
12958
  }, closed$11))),
12628
12959
  services: Type.Optional(Type.Record(Type.String(), anyFunction)),
12629
12960
  editors: Type.Optional(Type.Array(Type.String())),
12630
- jobs: Type.Optional(Type.Record(Type.String(), anyFunction))
12961
+ jobs: Type.Optional(Type.Record(Type.String(), anyFunction)),
12962
+ themes: Type.Optional(Type.Array(themeSchema))
12631
12963
  }, closed$11);
12632
12964
  const isRecord$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
12633
12965
  const TOP_LEVEL_KEYS$1 = Object.keys(shapioConfigSchema.properties);
@@ -12638,6 +12970,7 @@ const unknownKeyMessage = (path) => {
12638
12970
  const key = segments.at(-1) ?? "";
12639
12971
  if (segments.length === 1) return `unknown setting "${key}"; allowed: ${TOP_LEVEL_KEYS$1.join(", ")}`;
12640
12972
  if (segments[0] === "hooks" && segments.length === 3) return `unknown hook "${key}"; allowed: ${HOOK_EVENTS.join(", ")}`;
12973
+ if (segments[0] === "themes" && segments.length === 4) return `unknown theme token "${key}"; the tokens are listed in documentation/extensions.md`;
12641
12974
  return `unknown key "${key}"`;
12642
12975
  };
12643
12976
  const keysOf = (value) => isRecord$6(value) ? Object.keys(value) : [];
@@ -12673,6 +13006,43 @@ const findNamingProblems = (config) => {
12673
13006
  path: `/jobs/${name}`,
12674
13007
  message: "must start with a letter and use letters, digits, \".\", \"_\" or \"-\""
12675
13008
  });
13009
+ return [...problems, ...findThemeProblems(config.themes)];
13010
+ };
13011
+ const BUILT_IN_THEMES = new Set(BUILT_IN_THEME_KEYS);
13012
+ /** Theme keys (pattern, built-in, unique), at least one variant, and colour values (no CSS injection). */
13013
+ const findThemeProblems = (themes) => {
13014
+ const problems = [];
13015
+ const keys = /* @__PURE__ */ new Set();
13016
+ (Array.isArray(themes) ? themes : []).forEach((theme, index) => {
13017
+ if (!isRecord$6(theme)) return;
13018
+ const path = `/themes/${index}`;
13019
+ if (typeof theme.key === "string") {
13020
+ if (!THEME_KEY_PATTERN.test(theme.key)) problems.push({
13021
+ path: `${path}/key`,
13022
+ message: "must start with a lower-case letter and use lower-case letters, digits and dashes (41 at most)"
13023
+ });
13024
+ else if (BUILT_IN_THEMES.has(theme.key)) problems.push({
13025
+ path: `${path}/key`,
13026
+ message: `"${theme.key}" is a built-in theme`
13027
+ });
13028
+ else if (keys.has(theme.key)) problems.push({
13029
+ path: `${path}/key`,
13030
+ message: `"${theme.key}" is used twice`
13031
+ });
13032
+ keys.add(theme.key);
13033
+ }
13034
+ if (!THEME_VARIANTS.some((variant) => theme[variant] !== void 0)) problems.push({
13035
+ path,
13036
+ message: "needs a light or a dark variant (or both)"
13037
+ });
13038
+ for (const variant of THEME_VARIANTS) {
13039
+ const tokens = theme[variant];
13040
+ for (const [token, value] of Object.entries(isRecord$6(tokens) ? tokens : {})) if (typeof value === "string" && !THEME_COLOUR_PATTERN.test(value)) problems.push({
13041
+ path: `${path}/${variant}/${token}`,
13042
+ message: `"${value}" is not a colour: use #rrggbb, rgb(), hsl(), oklch() or oklab()`
13043
+ });
13044
+ }
13045
+ });
12676
13046
  return problems;
12677
13047
  };
12678
13048
  /** Every problem with a loaded config export, or none. */
@@ -13736,10 +14106,11 @@ const populateRelations = async (env, rows, fields, tree) => {
13736
14106
  };
13737
14107
  //#endregion
13738
14108
  //#region src/content/read.ts
14109
+ /** Evaluated through the read's executor: a read inside a transaction never asks the pool for a second connection. */
13739
14110
  const readPolicy = (env, modelId) => env.permissions.evaluate(env.actor, {
13740
14111
  action: "read",
13741
14112
  modelId
13742
- });
14113
+ }, env.executor);
13743
14114
  /**
13744
14115
  * Heads of the given entries that the policy lets the caller read, served per the locale chain (or per
13745
14116
  * `locales` when given: GraphQL's `fallback: false` and `localizations`).
@@ -13784,7 +14155,7 @@ const loadMediaViews = async (env, model, rows, fields) => {
13784
14155
  const ids = mediaIdsOf(model, rows.map((row) => row.data), fields);
13785
14156
  if (ids.size === 0) return /* @__PURE__ */ new Map();
13786
14157
  const assets = await findLiveManyOnSite(env.siteId, [...ids], env.executor);
13787
- const views = await toAssetViews(env.media, assets);
14158
+ const views = await toAssetViews(env.media, assets, env.executor);
13788
14159
  return new Map(views.map((view) => [view.id, env.audience === "delivery" ? toDeliveryAsset(view) : view]));
13789
14160
  };
13790
14161
  const toDeliveryAsset = (view) => ({
@@ -13850,13 +14221,16 @@ const ACTION_LABELS = {
13850
14221
  publish: "publishing",
13851
14222
  schemaManage: "managing"
13852
14223
  };
13853
- /** Resolves the model and the caller's policy for one action; 403 when not allowed (CONTRIBUTING.md rule 5). */
13854
- const modelWithPolicy = async (context, modelKey, action) => {
14224
+ /**
14225
+ * Resolves the model and the caller's policy for one action; 403 when not allowed (CONTRIBUTING.md rule 5).
14226
+ * `executor`: the caller's open transaction, when there is one.
14227
+ */
14228
+ const modelWithPolicy = async (context, modelKey, action, executor) => {
13855
14229
  const model = resolveModel(context.snapshot, modelKey);
13856
14230
  const policy = await context.permissions.evaluate(context.actor, {
13857
14231
  action,
13858
14232
  modelId: model.definition.id
13859
- });
14233
+ }, executor);
13860
14234
  assertAllowed(policy, `${ACTION_LABELS[action]} "${modelKey}" entries`);
13861
14235
  return {
13862
14236
  model,
@@ -14292,6 +14666,27 @@ const loadExtensionRuntime = async (options) => {
14292
14666
  });
14293
14667
  };
14294
14668
  //#endregion
14669
+ //#region src/extensions/themeStylesheet.ts
14670
+ const block = (selector, tokens) => `${selector} {\n${Object.entries(tokens).map(([token, value]) => ` --${token}: ${value};`).join("\n")}\n}\n`;
14671
+ const variantsOf = (theme) => THEME_VARIANTS.filter((variant) => theme[variant] !== void 0);
14672
+ const themeCss = (theme) => {
14673
+ const variants = variantsOf(theme);
14674
+ return variants.map((variant) => {
14675
+ const tokens = theme[variant] ?? {};
14676
+ const selector = variant === "dark" && variants.length > 1 ? `[data-theme='${theme.key}'].dark` : `[data-theme='${theme.key}']`;
14677
+ return block(selector, tokens);
14678
+ }).join("\n");
14679
+ };
14680
+ const buildThemeCatalogue = (themes) => ({
14681
+ items: themes.map((theme) => ({
14682
+ key: theme.key,
14683
+ name: theme.name,
14684
+ description: theme.description,
14685
+ variants: variantsOf(theme)
14686
+ })),
14687
+ stylesheet: themes.length > 0 ? `/* Shapio extension themes (shapio.config) */\n${themes.map(themeCss).join("\n")}` : ""
14688
+ });
14689
+ //#endregion
14295
14690
  //#region src/helpers/publicUrl.ts
14296
14691
  var InvalidUrlPathError = class extends Error {
14297
14692
  constructor(path) {
@@ -15342,7 +15737,7 @@ const isApiTokenFormat = (value) => value.startsWith(API_TOKEN_PREFIX);
15342
15737
  * Network tokens (no site) are visible and revocable only by admins who could mint them (`users.manage`, a
15343
15738
  * network action); everyone else sees their site's tokens only.
15344
15739
  */
15345
- const managesNetworkTokens = (context, permissions) => permissions.canPerform(context.actor, "users.manage");
15740
+ const managesNetworkTokens = (context, permissions, executor) => permissions.canPerform(context.actor, "users.manage", executor);
15346
15741
  /** The request site's tokens, plus network tokens for those who manage them. */
15347
15742
  const listApiTokens = async (context, permissions) => (await listForSite(context.site.id, await managesNetworkTokens(context, permissions))).map(toApiTokenView);
15348
15743
  /**
@@ -15351,13 +15746,13 @@ const listApiTokens = async (context, permissions) => (await listForSite(context
15351
15746
  * into network actions, so only someone who may assign roles on every site (`users.manage`, a network
15352
15747
  * action) may mint one. Delivery tokens always belong to a site.
15353
15748
  */
15354
- const isNetworkToken = async (context, permissions, roleKind, requested) => {
15749
+ const isNetworkToken = async (context, permissions, roleKind, requested, trx) => {
15355
15750
  if (roleKind === "delivery") {
15356
15751
  if (requested === true) throw new AppError(400, "INVALID_TOKEN_SITE", "Delivery tokens always belong to one site");
15357
15752
  return false;
15358
15753
  }
15359
15754
  if (requested !== true) return false;
15360
- if (!await managesNetworkTokens(context, permissions)) throw new AppError(403, "FORBIDDEN", "Only admins who manage users on every site can create network tokens");
15755
+ if (!await managesNetworkTokens(context, permissions, trx)) throw new AppError(403, "FORBIDDEN", "Only admins who manage users on every site can create network tokens");
15361
15756
  return true;
15362
15757
  };
15363
15758
  /**
@@ -15372,7 +15767,7 @@ const createApiToken = async (context, permissions, input) => {
15372
15767
  const role = await findById$14(input.roleId, trx);
15373
15768
  if (!role) throw new AppError(400, "INVALID_ROLES", "The role does not exist");
15374
15769
  if (role.key === SYSTEM_ROLE_KEYS.owner) throw new AppError(400, "INVALID_ROLES", "API tokens cannot hold the owner role");
15375
- const network = await isNetworkToken(context, permissions, role.kind, input.network);
15770
+ const network = await isNetworkToken(context, permissions, role.kind, input.network, trx);
15376
15771
  const { id } = await insert$14({
15377
15772
  name: input.name.trim(),
15378
15773
  token_hash: hashToken(token),
@@ -17916,23 +18311,35 @@ const createLoaders = (siteId, content) => {
17916
18311
  /**
17917
18312
  * The request's evaluator, memoized per (model, action): a GraphQL request touches the same models many
17918
18313
  * times (lists, relation batches, localizations), and evaluates each policy once (ADR 0005). Calls for
17919
- * another principal (never made by content services today) bypass the memo.
18314
+ * another principal (never made by content services today) bypass the memo. The memo is kept per executor:
18315
+ * a policy evaluated inside one root field's read transaction is never awaited from another's.
17920
18316
  */
17921
18317
  const memoizePermissions = (base, principal) => {
17922
- const policies = /* @__PURE__ */ new Map();
18318
+ const pooled = /* @__PURE__ */ new Map();
18319
+ const byTransaction = /* @__PURE__ */ new WeakMap();
18320
+ const memoFor = (executor) => {
18321
+ if (!executor) return pooled;
18322
+ let memo = byTransaction.get(executor);
18323
+ if (!memo) {
18324
+ memo = /* @__PURE__ */ new Map();
18325
+ byTransaction.set(executor, memo);
18326
+ }
18327
+ return memo;
18328
+ };
17923
18329
  return {
17924
- evaluate: (caller, request) => {
17925
- if (caller !== principal) return base.evaluate(caller, request);
18330
+ evaluate: (caller, request, executor) => {
18331
+ if (caller !== principal) return base.evaluate(caller, request, executor);
18332
+ const memo = memoFor(executor);
17926
18333
  const key = `${request.action}:${request.modelId}`;
17927
- let policy = policies.get(key);
18334
+ let policy = memo.get(key);
17928
18335
  if (!policy) {
17929
- policy = base.evaluate(caller, request);
17930
- policies.set(key, policy);
18336
+ policy = base.evaluate(caller, request, executor);
18337
+ memo.set(key, policy);
17931
18338
  }
17932
18339
  return policy;
17933
18340
  },
17934
- canPerform: (caller, action) => base.canPerform(caller, action),
17935
- canPerformOnSite: (caller, action, siteId) => base.canPerformOnSite(caller, action, siteId)
18341
+ canPerform: (caller, action, executor) => base.canPerform(caller, action, executor),
18342
+ canPerformOnSite: (caller, action, siteId, executor) => base.canPerformOnSite(caller, action, siteId, executor)
17936
18343
  };
17937
18344
  };
17938
18345
  //#endregion
@@ -20544,6 +20951,12 @@ const staticAdminPlugin = fp(async (app, { distPath, urls, frameSources }) => {
20544
20951
  if (urls.basePath !== "") app.get(urls.basePath, async (_request, reply) => reply.redirect(adminPrefix, 302));
20545
20952
  }, { name: "shapio-static-admin" });
20546
20953
  //#endregion
20954
+ //#region src/plugins/themeCatalogue.ts
20955
+ /** The catalogue is built from the project config by the app (extensions/themeStylesheet.ts). */
20956
+ const themeCataloguePlugin = fp(async (app, { catalogue }) => {
20957
+ app.decorate("themeCatalogue", catalogue);
20958
+ }, { name: "shapio-theme-catalogue" });
20959
+ //#endregion
20547
20960
  //#region src/repositories/usage.ts
20548
20961
  const upsertFieldReads = async (rows, trx = db) => {
20549
20962
  if (rows.length === 0) return;
@@ -21163,10 +21576,13 @@ const validatePermissions = (kind, permissions) => {
21163
21576
  seen.add(key);
21164
21577
  });
21165
21578
  };
21166
- /** Every model a grant names must exist (the registry decides; package D implements the lookup). */
21167
- const assertModelsExist = async (permissions, schema) => {
21579
+ /**
21580
+ * Every model a grant names must exist (the registry decides; package D implements the lookup).
21581
+ * `executor`: the caller's open transaction, when there is one.
21582
+ */
21583
+ const assertModelsExist = async (permissions, schema, executor) => {
21168
21584
  const modelIds = [...new Set(permissions.flatMap((p) => p.modelId === null ? [] : [p.modelId]))];
21169
- const exists = await Promise.all(modelIds.map((modelId) => schema.hasModel(modelId)));
21585
+ const exists = await Promise.all(modelIds.map((modelId) => schema.hasModel(modelId, executor)));
21170
21586
  const unknown = modelIds.filter((_modelId, index) => !exists[index]);
21171
21587
  if (unknown.length > 0) throw new AppError(400, "UNKNOWN_MODEL", "Grants name models that do not exist", { modelIds: unknown });
21172
21588
  };
@@ -21233,7 +21649,7 @@ const updateRole$1 = async (context, id, input, schema) => db.transaction().exec
21233
21649
  const kind = role.kind === "delivery" ? "delivery" : "admin";
21234
21650
  if (input.permissions) {
21235
21651
  validatePermissions(kind, input.permissions);
21236
- await assertModelsExist(input.permissions, schema);
21652
+ await assertModelsExist(input.permissions, schema, trx);
21237
21653
  }
21238
21654
  if (!await updateIfVersion$1(id, input.expectedVersion, {
21239
21655
  ...input.name !== void 0 ? { name: input.name.trim() } : {},
@@ -21372,7 +21788,7 @@ const updateAppRole$1 = async (context, id, input, schema) => db.transaction().e
21372
21788
  if (role.is_system && (input.name !== void 0 || input.description !== void 0)) throw new AppError(409, "SYSTEM_ROLE", "Built-in app roles keep their name; only their permissions change");
21373
21789
  if (input.permissions) {
21374
21790
  validateAppPermissions(input.permissions);
21375
- await assertModelsExist(input.permissions, schema);
21791
+ await assertModelsExist(input.permissions, schema, trx);
21376
21792
  }
21377
21793
  if (!await updateIfVersion(id, input.expectedVersion, {
21378
21794
  ...input.name !== void 0 ? { name: input.name.trim() } : {},
@@ -23439,6 +23855,11 @@ const configSchema = Type.Object({
23439
23855
  minimum: 2,
23440
23856
  maximum: 200
23441
23857
  }),
23858
+ /**
23859
+ * How long a request waits for a free pooled connection (PostgreSQL and MySQL) before it fails with
23860
+ * 503 `DATABASE_BUSY` and a logged error, instead of queueing forever. SQLite never waits for one.
23861
+ */
23862
+ DATABASE_POOL_ACQUIRE_TIMEOUT_MS: Type.Integer({ minimum: 100 }),
23442
23863
  MIGRATE_ON_START: Type.Boolean(),
23443
23864
  /**
23444
23865
  * LISTEN for schema-change notifications to refresh caches early. Turn off where LISTEN does not work
@@ -23655,6 +24076,7 @@ const CONFIG_DEFAULTS = {
23655
24076
  MEDIA_RATE_LIMIT_MAX: 6e3,
23656
24077
  TLS_RELOAD_INTERVAL_MS: 6e4,
23657
24078
  DATABASE_POOL_MAX: 10,
24079
+ DATABASE_POOL_ACQUIRE_TIMEOUT_MS: DEFAULT_POOL_ACQUIRE_TIMEOUT_MS,
23658
24080
  MIGRATE_ON_START: true,
23659
24081
  SETUP_REQUIRE_TOKEN: false,
23660
24082
  SCHEMA_LISTEN: true,
@@ -24809,20 +25231,21 @@ const requestChange = (db, request) => db.transaction().execute(async (trx) => {
24809
25231
  * services safe to call from anywhere (CONTRIBUTING.md rule 5).
24810
25232
  */
24811
25233
  const forbidden$1 = (message) => new AppError(403, "FORBIDDEN", message);
24812
- const assertCanManage = async (context, definitionId) => {
25234
+ /** `executor` (here and below): the caller's open transaction, when there is one. */
25235
+ const assertCanManage = async (context, definitionId, executor) => {
24813
25236
  if (!(await context.permissions.evaluate(context.actor, {
24814
25237
  action: "schemaManage",
24815
25238
  modelId: definitionId
24816
- })).allowed) throw forbidden$1("Your role does not allow changing this definition");
25239
+ }, executor)).allowed) throw forbidden$1("Your role does not allow changing this definition");
24817
25240
  };
24818
25241
  /**
24819
25242
  * Creating a definition in `scope` (plan site-schema, rule 4): a site's own definition needs `schema.create`
24820
25243
  * from the roles that apply on that site; a shared one (null) from roles on every site. The scope of a
24821
25244
  * site definition is always the request's site: a view never creates on another site.
24822
25245
  */
24823
- const assertCanCreate = async (context, scope) => {
25246
+ const assertCanCreate = async (context, scope, executor) => {
24824
25247
  if (scope !== null && scope !== context.snapshot.siteId) throw forbidden$1("A definition can only be created on the site of the request");
24825
- if (!(scope === null ? await context.permissions.canPerform(context.actor, "schema.create") : await context.permissions.canPerformOnSite(context.actor, "schema.create", scope))) throw forbidden$1(scope === null ? "Sharing a definition with all sites needs schema.create on every site" : "Your role does not allow schema.create");
25248
+ if (!(scope === null ? await context.permissions.canPerform(context.actor, "schema.create", executor) : await context.permissions.canPerformOnSite(context.actor, "schema.create", scope, executor))) throw forbidden$1(scope === null ? "Sharing a definition with all sites needs schema.create on every site" : "Your role does not allow schema.create");
24826
25249
  };
24827
25250
  /** Instance-wide schema settings (locales, the read-only lock): `schema.create` from roles on every site. */
24828
25251
  const assertCanChangeNetworkSchema = async (context) => {
@@ -25682,6 +26105,30 @@ const getEditorFile = async (request, reply) => {
25682
26105
  return reply.type("text/javascript; charset=utf-8").header("cache-control", "private, max-age=31536000, immutable").send(await readFile(entry.path));
25683
26106
  };
25684
26107
  //#endregion
26108
+ //#region src/controllers/themeCatalogue.ts
26109
+ /** The project's extension themes, for the admin's theme menu. */
26110
+ const listThemes = async (request) => ({ items: request.server.themeCatalogue.items });
26111
+ let stylesheetEtag;
26112
+ const etagOf = (stylesheet) => {
26113
+ if (stylesheetEtag?.stylesheet !== stylesheet) stylesheetEtag = {
26114
+ stylesheet,
26115
+ etag: `"${createHash("sha256").update(stylesheet).digest("base64url").slice(0, 22)}"`
26116
+ };
26117
+ return stylesheetEtag.etag;
26118
+ };
26119
+ /**
26120
+ * Their CSS, linked render-blocking from the admin's index.html (empty without extension themes). Public:
26121
+ * it holds colours only, and the signed-out screens render in the chosen theme too. Revalidated on each
26122
+ * load (it changes only with a restart), so a changed theme shows without a stale cache.
26123
+ */
26124
+ const getThemeStylesheet = async (request, reply) => {
26125
+ const { stylesheet } = request.server.themeCatalogue;
26126
+ const etag = etagOf(stylesheet);
26127
+ reply.header("etag", etag).header("cache-control", "no-cache").type("text/css; charset=utf-8");
26128
+ if (request.headers["if-none-match"] === etag) return reply.code(304).send();
26129
+ return reply.send(stylesheet);
26130
+ };
26131
+ //#endregion
25685
26132
  //#region src/routes/admin/extensions/schemas.ts
25686
26133
  const closed$3 = { additionalProperties: false };
25687
26134
  const listEditorsSchema = { response: { 200: Type.Object({ items: Type.Array(Type.Object({
@@ -25696,9 +26143,18 @@ const getEditorFileSchema = {
25696
26143
  }) }, closed$3),
25697
26144
  querystring: Type.Object({ v: Type.Optional(Type.String({ maxLength: 64 })) }, closed$3)
25698
26145
  };
26146
+ const listThemesSchema = { response: { 200: Type.Object({ items: Type.Array(Type.Object({
26147
+ key: Type.String(),
26148
+ name: Type.String(),
26149
+ description: Type.Optional(Type.String()),
26150
+ variants: Type.Array(Type.Union([Type.Literal("light"), Type.Literal("dark")]))
26151
+ })) }) } };
25699
26152
  //#endregion
25700
26153
  //#region src/routes/admin/extensions/index.ts
25701
- /** /api/admin/extensions: what the project installed for the admin (custom field editors, ADR 0009). */
26154
+ /**
26155
+ * /api/admin/extensions: what the project installed for the admin (custom field editors, ADR 0009; colour
26156
+ * themes, docs/plans/themes.md).
26157
+ */
25702
26158
  const adminExtensionsRoutes = async (app) => {
25703
26159
  declareSiteScope(app, "network");
25704
26160
  const admin = { preHandler: app.requireAdmin };
@@ -25710,6 +26166,8 @@ const adminExtensionsRoutes = async (app) => {
25710
26166
  ...admin,
25711
26167
  schema: getEditorFileSchema
25712
26168
  }, getEditorFile);
26169
+ app.get("/themes", { schema: listThemesSchema }, listThemes);
26170
+ app.get("/themes.css", getThemeStylesheet);
25713
26171
  };
25714
26172
  //#endregion
25715
26173
  //#region src/repositories/auditLog.ts
@@ -30622,8 +31080,8 @@ const staleDraft = (plans) => {
30622
31080
  activeVersion: stale.activeVersion
30623
31081
  });
30624
31082
  };
30625
- const authorizeDrafts = async (context, plans) => {
30626
- for (const planned of plans) await (planned.activeVersion === null ? assertCanCreate(context, draftScopeOf(context, planned.draft)) : assertCanManage(context, planned.draft.definition_id));
31083
+ const authorizeDrafts = async (context, plans, executor) => {
31084
+ for (const planned of plans) await (planned.activeVersion === null ? assertCanCreate(context, draftScopeOf(context, planned.draft), executor) : assertCanManage(context, planned.draft.definition_id, executor));
30627
31085
  };
30628
31086
  /** Loads and checks a set's items for shipping, with the acknowledgements given (interactive or stored). */
30629
31087
  /**
@@ -30631,11 +31089,11 @@ const authorizeDrafts = async (context, plans) => {
30631
31089
  * can prepare change sets without making them live. Checked here so interactive, scheduled and job-run
30632
31090
  * ships all check it, with the actor that asked for the ship.
30633
31091
  */
30634
- const assertCanShip = async (context) => {
30635
- if (!await context.permissions.canPerform(context.actor, "changes.ship")) throw new AppError(403, "FORBIDDEN", "Your role does not allow changes.ship");
31092
+ const assertCanShip = async (context, executor) => {
31093
+ if (!await context.permissions.canPerform(context.actor, "changes.ship", executor)) throw new AppError(403, "FORBIDDEN", "Your role does not allow changes.ship");
30636
31094
  };
30637
31095
  const planShip = async (context, row, ack, executor) => {
30638
- await assertCanShip(context);
31096
+ await assertCanShip(context, executor);
30639
31097
  const items = await listForSet$1(row.id, executor);
30640
31098
  const entryItems = items.filter((item) => item.kind === "entry");
30641
31099
  const drafts = await listForSet(row.id, executor);
@@ -30646,9 +31104,13 @@ const planShip = async (context, row, ack, executor) => {
30646
31104
  schema: [],
30647
31105
  needsJob: false
30648
31106
  };
30649
- await assertWritable(context);
30650
- const plans = await planDrafts(context, drafts);
30651
- await authorizeDrafts(context, plans);
31107
+ const inTransaction = {
31108
+ ...context,
31109
+ db: executor
31110
+ };
31111
+ await assertWritable(inTransaction);
31112
+ const plans = await planDrafts(inTransaction, drafts);
31113
+ await authorizeDrafts(context, plans, executor);
30652
31114
  const stale = staleDraft(plans);
30653
31115
  if (stale) throw stale;
30654
31116
  const invalid = plans.flatMap((planned) => planned.issues);
@@ -32239,13 +32701,13 @@ var PublicationItemError = class extends AppError {
32239
32701
  this.ref = ref;
32240
32702
  }
32241
32703
  };
32242
- const resolveModels = async (context, items) => {
32704
+ const resolveModels = async (context, trx, items) => {
32243
32705
  const models = /* @__PURE__ */ new Map();
32244
32706
  for (const modelId of new Set(items.map((item) => item.modelId))) {
32245
32707
  const model = resolveModelById(context.snapshot, modelId);
32246
32708
  if (!model) throw new PublicationItemError(items.find((item) => item.modelId === modelId)?.ref ?? modelId, modelNotFound(modelId));
32247
32709
  if (!model.definition.draftAndPublish) throw new PublicationItemError(items.find((item) => item.modelId === modelId)?.ref ?? modelId, publishingDisabled(model.definition.apiKey));
32248
- models.set(modelId, await modelWithPolicy(context, model.definition.apiKey, "publish"));
32710
+ models.set(modelId, await modelWithPolicy(context, model.definition.apiKey, "publish", trx));
32249
32711
  }
32250
32712
  return models;
32251
32713
  };
@@ -32404,7 +32866,7 @@ const processEntry = async (context, trx, resolved, entry, via) => {
32404
32866
  * order and publishes everything with the one number from `seq`.
32405
32867
  */
32406
32868
  const runPublicationBatchInTransaction = async (context, trx, items, via, seq) => {
32407
- const models = await resolveModels(context, items);
32869
+ const models = await resolveModels(context, trx, items);
32408
32870
  await guardModelVersions(trx, [...models.values()].map((resolved) => resolved.model), context.site.id);
32409
32871
  await lockItemLocales(trx, items, models);
32410
32872
  const byEntry = /* @__PURE__ */ new Map();
@@ -37363,7 +37825,7 @@ const openStoredBundle = (storage, stored) => storage.get(stored.driver).getStre
37363
37825
  const removeStoredBundle = (storage, stored) => storage.get(stored.driver).delete(stored.key);
37364
37826
  //#endregion
37365
37827
  //#region src/constants/version.ts
37366
- const SHAPIO_VERSION = "0.3.0";
37828
+ const SHAPIO_VERSION = "0.3.1";
37367
37829
  //#endregion
37368
37830
  //#region src/repositories/transferExport.ts
37369
37831
  /**
@@ -44944,12 +45406,12 @@ const createPreviewPermissions = (base, creator, fields, binding) => {
44944
45406
  siteId: binding.siteId
44945
45407
  } : void 0;
44946
45408
  return {
44947
- evaluate: async (_principal, request) => {
45409
+ evaluate: async (_principal, request, executor) => {
44948
45410
  if (request.action !== "read") return DENIED_POLICY;
44949
- const policy = await base.evaluate(creator, request);
44950
- const modelFields = await fields.getModelFields(request.modelId);
45411
+ const policy = await base.evaluate(creator, request, executor);
45412
+ const modelFields = await fields.getModelFields(request.modelId, executor);
44951
45413
  if (!policy.allowed || !modelFields) return DENIED_POLICY;
44952
- const sitePolicy = site ? await base.evaluate(site, request) : void 0;
45414
+ const sitePolicy = site ? await base.evaluate(site, request, executor) : void 0;
44953
45415
  if (sitePolicy && !sitePolicy.allowed) return DENIED_POLICY;
44954
45416
  const visible = modelFields.filter((field) => allows(policy.readMask, field.id) && (sitePolicy ? allows(sitePolicy.readMask, field.id) : field.public));
44955
45417
  return {
@@ -45291,6 +45753,7 @@ const buildApp = async (config, deps) => {
45291
45753
  await app.register(contentHooksPlugin, { hooks: deps.contentHooks ?? extensions.hooks });
45292
45754
  await app.register(adminContentRoutes, { prefix: urls.withBasePath("/api/admin/content") });
45293
45755
  await app.register(editorManifestPlugin, { manifest: deps.editorManifest ?? buildEditorManifest(extensions.loaded.projectDir, extensions.loaded.config.editors ?? [], app.log.child({ component: "extensions" })) });
45756
+ await app.register(themeCataloguePlugin, { catalogue: buildThemeCatalogue(extensions.loaded.config.themes ?? []) });
45294
45757
  await app.register(adminExtensionsRoutes, { prefix: urls.withBasePath("/api/admin/extensions") });
45295
45758
  await app.register(deliveryRoutes, { prefix: urls.withBasePath("/api/content") });
45296
45759
  await app.register(snapshotsRoutes, { prefix: urls.withBasePath("/api/snapshots") });
@@ -45605,6 +46068,7 @@ const toAppConfig = (raw) => {
45605
46068
  database: {
45606
46069
  url: raw.DATABASE_URL,
45607
46070
  poolMax: raw.DATABASE_POOL_MAX,
46071
+ acquireTimeoutMs: raw.DATABASE_POOL_ACQUIRE_TIMEOUT_MS,
45608
46072
  migrateOnStart: raw.MIGRATE_ON_START
45609
46073
  },
45610
46074
  schema: { listen: raw.SCHEMA_LISTEN },
@@ -47322,6 +47786,7 @@ const prepareTls = async ({ config, urls, log }) => {
47322
47786
  const openDatabase = (config, role, logger) => createDb({
47323
47787
  connectionString: config.database.url,
47324
47788
  poolMax: config.database.poolMax,
47789
+ acquireTimeoutMs: config.database.acquireTimeoutMs,
47325
47790
  applicationName: `shapio-${role}`,
47326
47791
  onIdleConnectionError: (error) => logger.warn({ err: error }, "database connection lost; it will be replaced on the next query")
47327
47792
  });
@@ -47496,6 +47961,7 @@ const startDedicatedWorker = async (config) => {
47496
47961
  const db = createDb({
47497
47962
  connectionString: config.database.url,
47498
47963
  poolMax: Math.max(2, config.worker.concurrency + 1),
47964
+ acquireTimeoutMs: config.database.acquireTimeoutMs,
47499
47965
  applicationName: "shapio-worker",
47500
47966
  onIdleConnectionError: (error) => logger.warn({ err: error }, "database connection lost; it will be replaced on the next query")
47501
47967
  });
@@ -47527,6 +47993,6 @@ const startDedicatedWorker = async (config) => {
47527
47993
  return worker;
47528
47994
  };
47529
47995
  //#endregion
47530
- export { validateSchema as $, update$8 as A, hashDefinition as B, BEFORE_EVENTS as C, listForAssets as D, buildEditorManifest as E, RESERVED_FIELD_API_KEYS as F, parseLockFile as G, lockEntriesForSite as H, RESERVED_TYPE_NAMES as I, serializeDefinition as J, schemaFilePath as K, SCHEMA_KIND_DIRECTORIES as L, describeError as M, LOCK_FILE_PATH as N, listLiveByDriver as O, RESERVED_DEFINITION_API_KEYS as P, upgradeLockFile as Q, SITES_DIRECTORY as R, AFTER_EVENTS as S, EXTENSION_ROUTE_ROOT as T, mergePulledLock as U, lockCoversSite as V, parseDefinition as W, suggestPlural as X, serializeLockFile as Y, toTypeName as Z, createLogger as _, loadConfig as a, createDb as at, SITE_KEY_PATTERN as b, getPendingMigrations as c, createMediaStorage as d, withSiteCovered as et, createSite$1 as f, generateToken as g, SYSTEM_CLI_ACTOR as h, assertDatabaseReachable as i, validateRichText as it, STORAGE_DRIVERS$1 as j, lockById$5 as k, migrateToLatest as l, createAdminDirect as m, startServer as n, SAFE_LINK_PATTERN as nt, loadEnvFileIfPresent as o, sqliteDriverOf as ot, listAllSites as p, scopeDirectory as q, copyObjectVerified as r, createStableId as rt, buildApp as s, startDedicatedWorker as t, RICHTEXT_FORMAT as tt, SHAPIO_VERSION as u, createUrlBuilder as v, EXTENSION_JOB_PREFIX as w, loadProjectConfig as x, loopbackUrl as y, foldApiKey as z };
47996
+ export { toTypeName as $, update$8 as A, checkThemeContrast as B, BEFORE_EVENTS as C, listForAssets as D, buildEditorManifest as E, RESERVED_FIELD_API_KEYS as F, mergePulledLock as G, hashDefinition as H, RESERVED_TYPE_NAMES as I, schemaFilePath as J, parseDefinition as K, SCHEMA_KIND_DIRECTORIES as L, describeError as M, LOCK_FILE_PATH as N, listLiveByDriver as O, RESERVED_DEFINITION_API_KEYS as P, suggestPlural as Q, SITES_DIRECTORY as R, AFTER_EVENTS as S, EXTENSION_ROUTE_ROOT as T, lockCoversSite as U, foldApiKey as V, lockEntriesForSite as W, serializeDefinition as X, scopeDirectory as Y, serializeLockFile as Z, createLogger as _, loadConfig as a, createStableId as at, SITE_KEY_PATTERN as b, getPendingMigrations as c, sqliteDriverOf as ct, createMediaStorage as d, upgradeLockFile as et, createSite$1 as f, generateToken as g, SYSTEM_CLI_ACTOR as h, assertDatabaseReachable as i, SAFE_LINK_PATTERN as it, STORAGE_DRIVERS$1 as j, lockById$5 as k, migrateToLatest as l, createAdminDirect as m, startServer as n, withSiteCovered as nt, loadEnvFileIfPresent as o, validateRichText as ot, listAllSites as p, parseLockFile as q, copyObjectVerified as r, RICHTEXT_FORMAT as rt, buildApp as s, createDb as st, startDedicatedWorker as t, validateSchema as tt, SHAPIO_VERSION as u, createUrlBuilder as v, EXTENSION_JOB_PREFIX as w, loadProjectConfig as x, loopbackUrl as y, THEME_VARIANTS as z };
47531
47997
 
47532
- //# sourceMappingURL=worker-BLXy8aRW.js.map
47998
+ //# sourceMappingURL=worker-BAmQPoTJ.js.map