@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.
- package/dist/admin/apple-touch-icon.png +0 -0
- package/dist/admin/assets/{AssistError-BITJ_tpg.js → AssistError-BYR085vj.js} +2 -2
- package/dist/admin/assets/{AssistError-BITJ_tpg.js.map → AssistError-BYR085vj.js.map} +1 -1
- package/dist/admin/assets/{BlockMenuItems-CcluK965.js → BlockMenuItems-BnLF3Efc.js} +2 -2
- package/dist/admin/assets/{BlockMenuItems-CcluK965.js.map → BlockMenuItems-BnLF3Efc.js.map} +1 -1
- package/dist/admin/assets/{Body-BXa9DhtH.js → Body-CCOZq2q9.js} +2 -2
- package/dist/admin/assets/{Body-BXa9DhtH.js.map → Body-CCOZq2q9.js.map} +1 -1
- package/dist/admin/assets/{Builder-DcpRfXuc.js → Builder-BZy9mphs.js} +2 -2
- package/dist/admin/assets/{Builder-DcpRfXuc.js.map → Builder-BZy9mphs.js.map} +1 -1
- package/dist/admin/assets/{ConfirmDialog-B9nWhIIz.js → ConfirmDialog-Tu-gjEwm.js} +2 -2
- package/dist/admin/assets/{ConfirmDialog-B9nWhIIz.js.map → ConfirmDialog-Tu-gjEwm.js.map} +1 -1
- package/dist/admin/assets/{CopyButton-BoWb-xi1.js → CopyButton-BuBaIlCk.js} +2 -2
- package/dist/admin/assets/{CopyButton-BoWb-xi1.js.map → CopyButton-BuBaIlCk.js.map} +1 -1
- package/dist/admin/assets/{CursorPager-BnxmTk47.js → CursorPager-Bvu3CXpD.js} +2 -2
- package/dist/admin/assets/{CursorPager-BnxmTk47.js.map → CursorPager-Bvu3CXpD.js.map} +1 -1
- package/dist/admin/assets/{ExternalLink-Eeb9mONJ.js → ExternalLink-CCr5oRXI.js} +2 -2
- package/dist/admin/assets/{ExternalLink-Eeb9mONJ.js.map → ExternalLink-CCr5oRXI.js.map} +1 -1
- package/dist/admin/assets/Form-Doc0JEzF.js +2 -0
- package/dist/admin/assets/{Form-DZRLD3vH.js.map → Form-Doc0JEzF.js.map} +1 -1
- package/dist/admin/assets/{FormCheckboxGroup-uIC2XQIL.js → FormCheckboxGroup-eg39IJvf.js} +2 -2
- package/dist/admin/assets/{FormCheckboxGroup-uIC2XQIL.js.map → FormCheckboxGroup-eg39IJvf.js.map} +1 -1
- package/dist/admin/assets/{FormRadioGroup-hd95vPOu.js → FormRadioGroup-ZppSTZK6.js} +2 -2
- package/dist/admin/assets/{FormRadioGroup-hd95vPOu.js.map → FormRadioGroup-ZppSTZK6.js.map} +1 -1
- package/dist/admin/assets/{FormSelectField-D7VHmnx5.js → FormSelectField-Cosnxiz-.js} +2 -2
- package/dist/admin/assets/{FormSelectField-D7VHmnx5.js.map → FormSelectField-Cosnxiz-.js.map} +1 -1
- package/dist/admin/assets/{FormSwitchField-ft8Jbe6E.js → FormSwitchField-DsKQEZw6.js} +2 -2
- package/dist/admin/assets/{FormSwitchField-ft8Jbe6E.js.map → FormSwitchField-DsKQEZw6.js.map} +1 -1
- package/dist/admin/assets/{FormTextareaField-DzyOzfZb.js → FormTextareaField-BXidSgIE.js} +2 -2
- package/dist/admin/assets/{FormTextareaField-DzyOzfZb.js.map → FormTextareaField-BXidSgIE.js.map} +1 -1
- package/dist/admin/assets/{HintedLabel-CvC46uy_.js → HintedLabel-USQku9Yt.js} +2 -2
- package/dist/admin/assets/{HintedLabel-CvC46uy_.js.map → HintedLabel-USQku9Yt.js.map} +1 -1
- package/dist/admin/assets/{InfoHint-GJLRVumb.js → InfoHint-Bz_yvehr.js} +2 -2
- package/dist/admin/assets/{InfoHint-GJLRVumb.js.map → InfoHint-Bz_yvehr.js.map} +1 -1
- package/dist/admin/assets/{InsertMenuItem-D976G0Rj.js → InsertMenuItem-DJXglsEd.js} +2 -2
- package/dist/admin/assets/{InsertMenuItem-D976G0Rj.js.map → InsertMenuItem-DJXglsEd.js.map} +1 -1
- package/dist/admin/assets/{LinkTabs-BZSlA57U.js → LinkTabs-D4U-rJyK.js} +2 -2
- package/dist/admin/assets/{LinkTabs-BZSlA57U.js.map → LinkTabs-D4U-rJyK.js.map} +1 -1
- package/dist/admin/assets/{Match-DdxTHHM4.js → Match-UAw1Ss8a.js} +2 -2
- package/dist/admin/assets/{Match-DdxTHHM4.js.map → Match-UAw1Ss8a.js.map} +1 -1
- package/dist/admin/assets/{Panel-iIKjEURi.js → Panel-BdjqCkKs.js} +2 -2
- package/dist/admin/assets/{Panel-iIKjEURi.js.map → Panel-BdjqCkKs.js.map} +1 -1
- package/dist/admin/assets/{QueryView-D6aZQJRN.js → QueryView-Bkyizh1P.js} +2 -2
- package/dist/admin/assets/{QueryView-D6aZQJRN.js.map → QueryView-Bkyizh1P.js.map} +1 -1
- package/dist/admin/assets/{RadioTile-BpB79rIK.js → RadioTile-VHTPl88w.js} +2 -2
- package/dist/admin/assets/RadioTile-VHTPl88w.js.map +1 -0
- package/dist/admin/assets/{RichTextField-BuHCONPu.js → RichTextField-B21FqrT5.js} +4 -4
- package/dist/admin/assets/{RichTextField-BuHCONPu.js.map → RichTextField-B21FqrT5.js.map} +1 -1
- package/dist/admin/assets/{RowTitle--uFCpx8n.js → RowTitle-BD8QPs_v.js} +2 -2
- package/dist/admin/assets/{RowTitle--uFCpx8n.js.map → RowTitle-BD8QPs_v.js.map} +1 -1
- package/dist/admin/assets/{SecretReveal-DFpzeNE0.js → SecretReveal-BfJKUxXW.js} +2 -2
- package/dist/admin/assets/{SecretReveal-DFpzeNE0.js.map → SecretReveal-BfJKUxXW.js.map} +1 -1
- package/dist/admin/assets/{SharedGlyph-Cqkx30tn.js → SharedGlyph-DDPdvsm_.js} +2 -2
- package/dist/admin/assets/{SharedGlyph-Cqkx30tn.js.map → SharedGlyph-DDPdvsm_.js.map} +1 -1
- package/dist/admin/assets/{StepList-BY5MuJPJ.js → StepList-DKhkw1hN.js} +2 -2
- package/dist/admin/assets/{StepList-BY5MuJPJ.js.map → StepList-DKhkw1hN.js.map} +1 -1
- package/dist/admin/assets/Structure-C7dr00Bp.js +2 -0
- package/dist/admin/assets/{Structure-CqRa2t7z.js.map → Structure-C7dr00Bp.js.map} +1 -1
- package/dist/admin/assets/{SubNavLink-B2o4kSZ9.js → SubNavLink-BAF2-wuF.js} +2 -2
- package/dist/admin/assets/{SubNavLink-B2o4kSZ9.js.map → SubNavLink-BAF2-wuF.js.map} +1 -1
- package/dist/admin/assets/{SuggestAltButton-4rf2xhEJ.js → SuggestAltButton-DEsbHvs4.js} +2 -2
- package/dist/admin/assets/{SuggestAltButton-4rf2xhEJ.js.map → SuggestAltButton-DEsbHvs4.js.map} +1 -1
- package/dist/admin/assets/{TextLink-D15oyXlc.js → TextLink-CegO3jom.js} +2 -2
- package/dist/admin/assets/{TextLink-D15oyXlc.js.map → TextLink-CegO3jom.js.map} +1 -1
- package/dist/admin/assets/{Thumbnail-CP5mfgNY.js → Thumbnail-CTpKlrxR.js} +2 -2
- package/dist/admin/assets/{Thumbnail-CP5mfgNY.js.map → Thumbnail-CTpKlrxR.js.map} +1 -1
- package/dist/admin/assets/{UnsavedChangesGuard-DgOASh0p.js → UnsavedChangesGuard-Cbfg-Bgj.js} +2 -2
- package/dist/admin/assets/{UnsavedChangesGuard-DgOASh0p.js.map → UnsavedChangesGuard-Cbfg-Bgj.js.map} +1 -1
- package/dist/admin/assets/{UserAvatar-BocvNVL-.js → UserAvatar-CiZ00-cr.js} +2 -2
- package/dist/admin/assets/{UserAvatar-BocvNVL-.js.map → UserAvatar-CiZ00-cr.js.map} +1 -1
- package/dist/admin/assets/{arrow-up-gf_j6SVQ.js → arrow-up-DwMJqZVV.js} +2 -2
- package/dist/admin/assets/{arrow-up-gf_j6SVQ.js.map → arrow-up-DwMJqZVV.js.map} +1 -1
- package/dist/admin/assets/{auth-pcS9CCXu.js → auth-BaVAo0u_.js} +2 -2
- package/dist/admin/assets/{auth-pcS9CCXu.js.map → auth-BaVAo0u_.js.map} +1 -1
- package/dist/admin/assets/{badge-DD0mE96i.js → badge-IBeVOhKd.js} +2 -2
- package/dist/admin/assets/{badge-DD0mE96i.js.map → badge-IBeVOhKd.js.map} +1 -1
- package/dist/admin/assets/{boxes-DcK-kaSa.js → boxes-BH0MDegO.js} +2 -2
- package/dist/admin/assets/{boxes-DcK-kaSa.js.map → boxes-BH0MDegO.js.map} +1 -1
- package/dist/admin/assets/{calendar-clock-DEwACe0M.js → calendar-clock-DKUk-w81.js} +2 -2
- package/dist/admin/assets/{calendar-clock-DEwACe0M.js.map → calendar-clock-DKUk-w81.js.map} +1 -1
- package/dist/admin/assets/{changeSets-BjUEfYbj.js → changeSets-o_G67qPk.js} +2 -2
- package/dist/admin/assets/{changeSets-BjUEfYbj.js.map → changeSets-o_G67qPk.js.map} +1 -1
- package/dist/admin/assets/chevron-left-D259hWJn.js +2 -0
- package/dist/admin/assets/{chevron-left-BsQYDUEC.js.map → chevron-left-D259hWJn.js.map} +1 -1
- package/dist/admin/assets/{circle-check-Dq1owihe.js → circle-check-Cidzt1Ww.js} +2 -2
- package/dist/admin/assets/{circle-check-Dq1owihe.js.map → circle-check-Cidzt1Ww.js.map} +1 -1
- package/dist/admin/assets/{circle-x-Dx6zIZko.js → circle-x-c1RwmC6j.js} +2 -2
- package/dist/admin/assets/{circle-x-Dx6zIZko.js.map → circle-x-c1RwmC6j.js.map} +1 -1
- package/dist/admin/assets/{clock-DQvkffqQ.js → clock-BOtPNp9-.js} +2 -2
- package/dist/admin/assets/{clock-DQvkffqQ.js.map → clock-BOtPNp9-.js.map} +1 -1
- package/dist/admin/assets/cn-M4_WHiur.js +2 -0
- package/dist/admin/assets/cn-M4_WHiur.js.map +1 -0
- package/dist/admin/assets/coerce-DWI5sgLW.js +2 -0
- package/dist/admin/assets/{coerce-OsqQRj19.js.map → coerce-DWI5sgLW.js.map} +1 -1
- package/dist/admin/assets/{constants-SrImR6C3.js → constants-CgUdkWmf.js} +2 -2
- package/dist/admin/assets/{constants-SrImR6C3.js.map → constants-CgUdkWmf.js.map} +1 -1
- package/dist/admin/assets/content-D25ainqV.js +3 -0
- package/dist/admin/assets/{content-_SLbbnfX.js.map → content-D25ainqV.js.map} +1 -1
- package/dist/admin/assets/{contentCounts-Ch3uMXVU.js → contentCounts-T-LmvUf5.js} +2 -2
- package/dist/admin/assets/{contentCounts-Ch3uMXVU.js.map → contentCounts-T-LmvUf5.js.map} +1 -1
- package/dist/admin/assets/{contentTypes-BCOdUK_Z.js → contentTypes-BSlp81l1.js} +2 -2
- package/dist/admin/assets/{contentTypes-BCOdUK_Z.js.map → contentTypes-BSlp81l1.js.map} +1 -1
- package/dist/admin/assets/{copy-BDc3r0fO.js → copy-DT7dAzVm.js} +2 -2
- package/dist/admin/assets/{copy-BDc3r0fO.js.map → copy-DT7dAzVm.js.map} +1 -1
- package/dist/admin/assets/{dateTimeLocal-C92tyKwz.js → dateTimeLocal-DRq7DuYO.js} +2 -2
- package/dist/admin/assets/{dateTimeLocal-C92tyKwz.js.map → dateTimeLocal-DRq7DuYO.js.map} +1 -1
- package/dist/admin/assets/{develop-DS9TlDmv.js → develop-qF10t9tM.js} +6 -6
- package/dist/admin/assets/{develop-DS9TlDmv.js.map → develop-qF10t9tM.js.map} +1 -1
- package/dist/admin/assets/{dropdown-menu-gmP2Rp3n.js → dropdown-menu-Cl20LCvY.js} +2 -2
- package/dist/admin/assets/{dropdown-menu-gmP2Rp3n.js.map → dropdown-menu-Cl20LCvY.js.map} +1 -1
- package/dist/admin/assets/{ellipsis-NK0pWgqU.js → ellipsis-xDmHl1SK.js} +2 -2
- package/dist/admin/assets/{ellipsis-NK0pWgqU.js.map → ellipsis-xDmHl1SK.js.map} +1 -1
- package/dist/admin/assets/{external-link-D3Aoi2yX.js → external-link-DtJAaOWk.js} +2 -2
- package/dist/admin/assets/{external-link-D3Aoi2yX.js.map → external-link-DtJAaOWk.js.map} +1 -1
- package/dist/admin/assets/{file-text-DxXAmmxL.js → file-text-BJkQMeQO.js} +2 -2
- package/dist/admin/assets/{file-text-DxXAmmxL.js.map → file-text-BJkQMeQO.js.map} +1 -1
- package/dist/admin/assets/{fileRoute-C2tOrKhO.js → fileRoute-Buucm0CU.js} +2 -2
- package/dist/admin/assets/{fileRoute-C2tOrKhO.js.map → fileRoute-Buucm0CU.js.map} +1 -1
- package/dist/admin/assets/{followPlural-CN6cQfNk.js → followPlural-Cuocm4Sv.js} +2 -2
- package/dist/admin/assets/{followPlural-CN6cQfNk.js.map → followPlural-Cuocm4Sv.js.map} +1 -1
- package/dist/admin/assets/{globe-CuElSilX.js → globe-CAhrwBgv.js} +2 -2
- package/dist/admin/assets/{globe-CuElSilX.js.map → globe-CAhrwBgv.js.map} +1 -1
- package/dist/admin/assets/{image-CsH8dZve.js → image-CGN6ffEu.js} +2 -2
- package/dist/admin/assets/{image-CsH8dZve.js.map → image-CGN6ffEu.js.map} +1 -1
- package/dist/admin/assets/{image-off-B_uBCsYu.js → image-off-0zX_kODH.js} +2 -2
- package/dist/admin/assets/{image-off-B_uBCsYu.js.map → image-off-0zX_kODH.js.map} +1 -1
- package/dist/admin/assets/{inbox-DsuXNaKE.js → inbox-CWq1hHqK.js} +2 -2
- package/dist/admin/assets/{inbox-DsuXNaKE.js.map → inbox-CWq1hHqK.js.map} +1 -1
- package/dist/admin/assets/{inbox-3iAHL3pC.js → inbox-CukkApHK.js} +2 -2
- package/dist/admin/assets/{inbox-3iAHL3pC.js.map → inbox-CukkApHK.js.map} +1 -1
- package/dist/admin/assets/index-C9uJxg4J.js +4 -0
- package/dist/admin/assets/index-C9uJxg4J.js.map +1 -0
- package/dist/admin/assets/index-Cz3C-LxB.css +2 -0
- package/dist/admin/assets/{key-round-B17HiT3K.js → key-round-DnE-2gr0.js} +2 -2
- package/dist/admin/assets/{key-round-B17HiT3K.js.map → key-round-DnE-2gr0.js.map} +1 -1
- package/dist/admin/assets/{link-2-off-hWGS8LW6.js → link-2-off-Cbpvb3w4.js} +2 -2
- package/dist/admin/assets/{link-2-off-hWGS8LW6.js.map → link-2-off-Cbpvb3w4.js.map} +1 -1
- package/dist/admin/assets/{link-bhHmG4Tc.js → link-DPniuk00.js} +2 -2
- package/dist/admin/assets/{link-bhHmG4Tc.js.map → link-DPniuk00.js.map} +1 -1
- package/dist/admin/assets/{list-DRtRdjBC.js → list-CkHF_4xh.js} +2 -2
- package/dist/admin/assets/{list-DRtRdjBC.js.map → list-CkHF_4xh.js.map} +1 -1
- package/dist/admin/assets/{locales-BzPbz8FF.js → locales-DFLQ17ow.js} +2 -2
- package/dist/admin/assets/{locales-BzPbz8FF.js.map → locales-DFLQ17ow.js.map} +1 -1
- package/dist/admin/assets/{media-Xzot1ueY.js → media-CKTs3j83.js} +2 -2
- package/dist/admin/assets/{media-Xzot1ueY.js.map → media-CKTs3j83.js.map} +1 -1
- package/dist/admin/assets/{media-WLnWaL7U.js → media-CUfV74JZ.js} +2 -2
- package/dist/admin/assets/{media-WLnWaL7U.js.map → media-CUfV74JZ.js.map} +1 -1
- package/dist/admin/assets/{network-DHGqLwji.js → network-Fri1cjMt.js} +2 -2
- package/dist/admin/assets/{network-DHGqLwji.js.map → network-Fri1cjMt.js.map} +1 -1
- package/dist/admin/assets/{pencil-Bdp2z9D_.js → pencil-BqAgKC9v.js} +2 -2
- package/dist/admin/assets/{pencil-Bdp2z9D_.js.map → pencil-BqAgKC9v.js.map} +1 -1
- package/dist/admin/assets/{plus-BN_iSsnp.js → plus-DLRcpVhJ.js} +2 -2
- package/dist/admin/assets/{plus-BN_iSsnp.js.map → plus-DLRcpVhJ.js.map} +1 -1
- package/dist/admin/assets/{preload-helper-DAbCfXID.js → preload-helper-BHNBIF88.js} +2 -2
- package/dist/admin/assets/{preload-helper-DAbCfXID.js.map → preload-helper-BHNBIF88.js.map} +1 -1
- package/dist/admin/assets/{publishing-d9yhMwgA.js → publishing-DaaE8PaU.js} +2 -2
- package/dist/admin/assets/{publishing-d9yhMwgA.js.map → publishing-DaaE8PaU.js.map} +1 -1
- package/dist/admin/assets/{radio-group-DHIxK-IU.js → radio-group-CA3XlQcc.js} +2 -2
- package/dist/admin/assets/{radio-group-DHIxK-IU.js.map → radio-group-CA3XlQcc.js.map} +1 -1
- package/dist/admin/assets/{rotate-ccw-CBoZle-b.js → rotate-ccw-CfrkcgYa.js} +2 -2
- package/dist/admin/assets/{rotate-ccw-CBoZle-b.js.map → rotate-ccw-CfrkcgYa.js.map} +1 -1
- package/dist/admin/assets/{schedules-Dy89Nm6P.js → schedules-hJQkjEhN.js} +2 -2
- package/dist/admin/assets/{schedules-Dy89Nm6P.js.map → schedules-hJQkjEhN.js.map} +1 -1
- package/dist/admin/assets/{schema-B4smPFcR.js → schema--xSR7cf9.js} +2 -2
- package/dist/admin/assets/{schema-B4smPFcR.js.map → schema--xSR7cf9.js.map} +1 -1
- package/dist/admin/assets/{scroll-text-UbyzpWh-.js → scroll-text-BA9yh7Za.js} +2 -2
- package/dist/admin/assets/{scroll-text-UbyzpWh-.js.map → scroll-text-BA9yh7Za.js.map} +1 -1
- package/dist/admin/assets/{search-_YlNpPvU.js → search--71HzPvh.js} +2 -2
- package/dist/admin/assets/{search-_YlNpPvU.js.map → search--71HzPvh.js.map} +1 -1
- package/dist/admin/assets/{searchSchema-CvOhn4FV.js → searchSchema-Ypl6gq9g.js} +2 -2
- package/dist/admin/assets/{searchSchema-CvOhn4FV.js.map → searchSchema-Ypl6gq9g.js.map} +1 -1
- package/dist/admin/assets/{searchSchemas-BxRnxenn.js → searchSchemas-C-nQbyhd.js} +2 -2
- package/dist/admin/assets/{searchSchemas-BxRnxenn.js.map → searchSchemas-C-nQbyhd.js.map} +1 -1
- package/dist/admin/assets/sections-BBN2JErq.js +2 -0
- package/dist/admin/assets/sections-BBN2JErq.js.map +1 -0
- package/dist/admin/assets/{sections-Bb3Q4bcY.js → sections-D0OZozD2.js} +2 -2
- package/dist/admin/assets/{sections-Bb3Q4bcY.js.map → sections-D0OZozD2.js.map} +1 -1
- package/dist/admin/assets/{select-tHVWX85X.js → select-De48SQdZ.js} +2 -2
- package/dist/admin/assets/{select-tHVWX85X.js.map → select-De48SQdZ.js.map} +1 -1
- package/dist/admin/assets/{send-BqECHYi6.js → send-NuSmLVSD.js} +2 -2
- package/dist/admin/assets/{send-BqECHYi6.js.map → send-NuSmLVSD.js.map} +1 -1
- package/dist/admin/assets/settings-CYXU73dP.js +2 -0
- package/dist/admin/assets/settings-CYXU73dP.js.map +1 -0
- package/dist/admin/assets/skeleton-CCwdO9oq.js +42 -0
- package/dist/admin/assets/skeleton-CCwdO9oq.js.map +1 -0
- package/dist/admin/assets/{snapshots-BQrhsCmC.js → snapshots-BQeFK5cj.js} +2 -2
- package/dist/admin/assets/{snapshots-BQrhsCmC.js.map → snapshots-BQeFK5cj.js.map} +1 -1
- package/dist/admin/assets/{sparkles-BW4CbD6W.js → sparkles-B-0I44es.js} +2 -2
- package/dist/admin/assets/{sparkles-BW4CbD6W.js.map → sparkles-B-0I44es.js.map} +1 -1
- package/dist/admin/assets/{src-XaU0N8C-.js → src-CQu-YxUM.js} +2 -2
- package/dist/admin/assets/src-CQu-YxUM.js.map +1 -0
- package/dist/admin/assets/{stableId-C9Yq5iUw.js → stableId-rPnYVF-A.js} +2 -2
- package/dist/admin/assets/{stableId-C9Yq5iUw.js.map → stableId-rPnYVF-A.js.map} +1 -1
- package/dist/admin/assets/{store-BphwUzV5.js → store-Bqd9L9DK.js} +2 -2
- package/dist/admin/assets/{store-BphwUzV5.js.map → store-Bqd9L9DK.js.map} +1 -1
- package/dist/admin/assets/{switch-BVy02Vc8.js → switch-JmfyFAQG.js} +2 -2
- package/dist/admin/assets/{switch-BVy02Vc8.js.map → switch-JmfyFAQG.js.map} +1 -1
- package/dist/admin/assets/{table-CZYtdoHn.js → table-BEDv9IRM.js} +2 -2
- package/dist/admin/assets/{table-CZYtdoHn.js.map → table-BEDv9IRM.js.map} +1 -1
- package/dist/admin/assets/{tabs-quHbeVxA.js → tabs-DSS1c-Kf.js} +4 -4
- package/dist/admin/assets/{tabs-quHbeVxA.js.map → tabs-DSS1c-Kf.js.map} +1 -1
- package/dist/admin/assets/{textarea-B8Vl5gJ1.js → textarea-eatLAWE_.js} +2 -2
- package/dist/admin/assets/{textarea-B8Vl5gJ1.js.map → textarea-eatLAWE_.js.map} +1 -1
- package/dist/admin/assets/{titles-B2z5ckxG.js → titles-Bi2fWNN4.js} +2 -2
- package/dist/admin/assets/{titles-B2z5ckxG.js.map → titles-Bi2fWNN4.js.map} +1 -1
- package/dist/admin/assets/{tokens-Fh_OeIlf.js → tokens-C2E7swse.js} +2 -2
- package/dist/admin/assets/{tokens-Fh_OeIlf.js.map → tokens-C2E7swse.js.map} +1 -1
- package/dist/admin/assets/{tooltip-DwqM3opE.js → tooltip-Bji9fmSr.js} +2 -2
- package/dist/admin/assets/{tooltip-DwqM3opE.js.map → tooltip-Bji9fmSr.js.map} +1 -1
- package/dist/admin/assets/{undo-2-D4Fz4JMZ.js → undo-2-BU-uqm7e.js} +2 -2
- package/dist/admin/assets/{undo-2-D4Fz4JMZ.js.map → undo-2-BU-uqm7e.js.map} +1 -1
- package/dist/admin/assets/{useAuditLabels-DKrsrciQ.js → useAuditLabels-D5nmbXqN.js} +2 -2
- package/dist/admin/assets/{useAuditLabels-DKrsrciQ.js.map → useAuditLabels-D5nmbXqN.js.map} +1 -1
- package/dist/admin/assets/{useConfirmTarget-Dp8mCulm.js → useConfirmTarget-Y7_8kVX5.js} +2 -2
- package/dist/admin/assets/{useConfirmTarget-Dp8mCulm.js.map → useConfirmTarget-Y7_8kVX5.js.map} +1 -1
- package/dist/admin/assets/useContentSchema-DJTqjlUk.js +2 -0
- package/dist/admin/assets/{useContentSchema-Bv4_6VJR.js.map → useContentSchema-DJTqjlUk.js.map} +1 -1
- package/dist/admin/assets/{useCursorPager-B1upg5EU.js → useCursorPager-CiJroOo7.js} +2 -2
- package/dist/admin/assets/{useCursorPager-B1upg5EU.js.map → useCursorPager-CiJroOo7.js.map} +1 -1
- package/dist/admin/assets/{useDiscardGuard-34ynGJIk.js → useDiscardGuard-t_N3l9TQ.js} +2 -2
- package/dist/admin/assets/{useDiscardGuard-34ynGJIk.js.map → useDiscardGuard-t_N3l9TQ.js.map} +1 -1
- package/dist/admin/assets/{useMediaUploader-QXSjZStv.js → useMediaUploader-CLHmKCwq.js} +2 -2
- package/dist/admin/assets/{useMediaUploader-QXSjZStv.js.map → useMediaUploader-CLHmKCwq.js.map} +1 -1
- package/dist/admin/assets/{usePaletteItems-62-TZhy7.js → usePaletteItems-BVjWCdow.js} +2 -2
- package/dist/admin/assets/{usePaletteItems-62-TZhy7.js.map → usePaletteItems-BVjWCdow.js.map} +1 -1
- package/dist/admin/assets/{usePlaces-CY6f2DmI.js → usePlaces-CnSOZwtO.js} +2 -2
- package/dist/admin/assets/{usePlaces-CY6f2DmI.js.map → usePlaces-CnSOZwtO.js.map} +1 -1
- package/dist/admin/assets/{useQueries-BuKJBA0M.js → useQueries-BRolGUiw.js} +2 -2
- package/dist/admin/assets/{useQueries-BuKJBA0M.js.map → useQueries-BRolGUiw.js.map} +1 -1
- package/dist/admin/assets/{useSchemaScopeAccess-DUBeS9kU.js → useSchemaScopeAccess-bd8RQxuu.js} +2 -2
- package/dist/admin/assets/{useSchemaScopeAccess-DUBeS9kU.js.map → useSchemaScopeAccess-bd8RQxuu.js.map} +1 -1
- package/dist/admin/assets/{users-BOn2ohJQ.js → users-CbErtM7w.js} +2 -2
- package/dist/admin/assets/{users-BOn2ohJQ.js.map → users-CbErtM7w.js.map} +1 -1
- package/dist/admin/assets/{users-BkKc4MNB.js → users-bDGYpSXg.js} +2 -2
- package/dist/admin/assets/{users-BkKc4MNB.js.map → users-bDGYpSXg.js.map} +1 -1
- package/dist/admin/assets/{zod-BHAd1wTC.js → zod-Gtw4e0qi.js} +2 -2
- package/dist/admin/assets/{zod-BHAd1wTC.js.map → zod-Gtw4e0qi.js.map} +1 -1
- package/dist/admin/favicon-32.png +0 -0
- package/dist/admin/favicon.svg +2 -2
- package/dist/admin/index.html +60 -56
- package/dist/admin/theme-init.js +41 -9
- package/dist/cli.js +30 -6
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +11 -224
- package/dist/index.js +1 -1
- package/dist/{public-CGO8RBn3.d.ts → public-B7fFHMNr.d.ts} +251 -5
- package/dist/{worker-BLXy8aRW.js → worker-BAmQPoTJ.js} +574 -108
- package/dist/worker-BAmQPoTJ.js.map +1 -0
- package/package.json +5 -5
- package/dist/admin/assets/Form-DZRLD3vH.js +0 -2
- package/dist/admin/assets/RadioTile-BpB79rIK.js.map +0 -1
- package/dist/admin/assets/Structure-CqRa2t7z.js +0 -2
- package/dist/admin/assets/chevron-left-BsQYDUEC.js +0 -2
- package/dist/admin/assets/cn-BQ3voEft.js +0 -2
- package/dist/admin/assets/cn-BQ3voEft.js.map +0 -1
- package/dist/admin/assets/coerce-OsqQRj19.js +0 -2
- package/dist/admin/assets/content-_SLbbnfX.js +0 -3
- package/dist/admin/assets/index-BLYOgCKc.css +0 -2
- package/dist/admin/assets/index-BSzXA60W.js +0 -4
- package/dist/admin/assets/index-BSzXA60W.js.map +0 -1
- package/dist/admin/assets/sections-DiySZZHb.js +0 -2
- package/dist/admin/assets/sections-DiySZZHb.js.map +0 -1
- package/dist/admin/assets/settings-D-v8gL8F.js +0 -2
- package/dist/admin/assets/settings-D-v8gL8F.js.map +0 -1
- package/dist/admin/assets/skeleton-C3QLIh7W.js +0 -42
- package/dist/admin/assets/skeleton-C3QLIh7W.js.map +0 -1
- package/dist/admin/assets/src-XaU0N8C-.js.map +0 -1
- package/dist/admin/assets/useContentSchema-Bv4_6VJR.js +0 -2
- 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")
|
|
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
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
7793
|
-
*
|
|
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
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
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).
|
|
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(
|
|
11646
|
-
const bindings = groupBindings(await listAll(
|
|
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
|
|
11654
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
13854
|
-
|
|
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
|
|
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 =
|
|
18334
|
+
let policy = memo.get(key);
|
|
17928
18335
|
if (!policy) {
|
|
17929
|
-
policy = base.evaluate(caller, request);
|
|
17930
|
-
|
|
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
|
-
/**
|
|
21167
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
30650
|
-
|
|
30651
|
-
|
|
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.
|
|
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 {
|
|
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-
|
|
47998
|
+
//# sourceMappingURL=worker-BAmQPoTJ.js.map
|