@shapio/cms 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/assets/{AssetTile-C-Mu0BRF.js → AssetTile-zOf0K0nN.js} +2 -2
- package/dist/admin/assets/{AssetTile-C-Mu0BRF.js.map → AssetTile-zOf0K0nN.js.map} +1 -1
- package/dist/admin/assets/{AssistError-B-bnaHhJ.js → AssistError-D_vY2CO0.js} +2 -2
- package/dist/admin/assets/{AssistError-B-bnaHhJ.js.map → AssistError-D_vY2CO0.js.map} +1 -1
- package/dist/admin/assets/{BlockMenuItems-Dn9WULjL.js → BlockMenuItems-BEnSpClj.js} +2 -2
- package/dist/admin/assets/{BlockMenuItems-Dn9WULjL.js.map → BlockMenuItems-BEnSpClj.js.map} +1 -1
- package/dist/admin/assets/{Body-C0SwduLi.js → Body-B_IVN5wp.js} +2 -2
- package/dist/admin/assets/{Body-C0SwduLi.js.map → Body-B_IVN5wp.js.map} +1 -1
- package/dist/admin/assets/Builder-CnEtRudv.js +2 -0
- package/dist/admin/assets/Builder-CnEtRudv.js.map +1 -0
- package/dist/admin/assets/{ConfirmDialog-BfpN2MQx.js → ConfirmDialog-BW-l3Whg.js} +2 -2
- package/dist/admin/assets/{ConfirmDialog-BfpN2MQx.js.map → ConfirmDialog-BW-l3Whg.js.map} +1 -1
- package/dist/admin/assets/{CopyButton-Dxbzp7hC.js → CopyButton-CyoUMStc.js} +2 -2
- package/dist/admin/assets/{CopyButton-Dxbzp7hC.js.map → CopyButton-CyoUMStc.js.map} +1 -1
- package/dist/admin/assets/{CursorPager-DkZs6rPg.js → CursorPager-BW2Dl_hn.js} +2 -2
- package/dist/admin/assets/{CursorPager-DkZs6rPg.js.map → CursorPager-BW2Dl_hn.js.map} +1 -1
- package/dist/admin/assets/{ExternalLink-KdO72pTj.js → ExternalLink-BCI0q9ib.js} +2 -2
- package/dist/admin/assets/{ExternalLink-KdO72pTj.js.map → ExternalLink-BCI0q9ib.js.map} +1 -1
- package/dist/admin/assets/{FocalPoint-r1MG_y-r.js → FocalPoint-CxicvlJO.js} +2 -2
- package/dist/admin/assets/{FocalPoint-r1MG_y-r.js.map → FocalPoint-CxicvlJO.js.map} +1 -1
- package/dist/admin/assets/{Form-D8vp4Kd_.js → Form-DB03drS9.js} +2 -2
- package/dist/admin/assets/{Form-D8vp4Kd_.js.map → Form-DB03drS9.js.map} +1 -1
- package/dist/admin/assets/{FormCheckboxGroup-8ObPh9cH.js → FormCheckboxGroup-JP1xzReQ.js} +2 -2
- package/dist/admin/assets/{FormCheckboxGroup-8ObPh9cH.js.map → FormCheckboxGroup-JP1xzReQ.js.map} +1 -1
- package/dist/admin/assets/{FormRadioGroup-CAB3qzVS.js → FormRadioGroup-BvSC8SRm.js} +2 -2
- package/dist/admin/assets/{FormRadioGroup-CAB3qzVS.js.map → FormRadioGroup-BvSC8SRm.js.map} +1 -1
- package/dist/admin/assets/{FormSelectField-C7hE05Wq.js → FormSelectField-BDIVhZh6.js} +2 -2
- package/dist/admin/assets/{FormSelectField-C7hE05Wq.js.map → FormSelectField-BDIVhZh6.js.map} +1 -1
- package/dist/admin/assets/{FormSwitchField-7EEYkGOH.js → FormSwitchField-OM4F8ln4.js} +2 -2
- package/dist/admin/assets/{FormSwitchField-7EEYkGOH.js.map → FormSwitchField-OM4F8ln4.js.map} +1 -1
- package/dist/admin/assets/{FormTextareaField-BasuRoXW.js → FormTextareaField-Bw7EkC4H.js} +2 -2
- package/dist/admin/assets/{FormTextareaField-BasuRoXW.js.map → FormTextareaField-Bw7EkC4H.js.map} +1 -1
- package/dist/admin/assets/{HintedLabel-BDV81kIT.js → HintedLabel-BGidukgy.js} +2 -2
- package/dist/admin/assets/{HintedLabel-BDV81kIT.js.map → HintedLabel-BGidukgy.js.map} +1 -1
- package/dist/admin/assets/{InfoHint-DWn9kLf1.js → InfoHint-D3uwei-E.js} +2 -2
- package/dist/admin/assets/{InfoHint-DWn9kLf1.js.map → InfoHint-D3uwei-E.js.map} +1 -1
- package/dist/admin/assets/{InsertMenuItem-DhJk5BeH.js → InsertMenuItem-DAihGvbw.js} +2 -2
- package/dist/admin/assets/{InsertMenuItem-DhJk5BeH.js.map → InsertMenuItem-DAihGvbw.js.map} +1 -1
- package/dist/admin/assets/{LinkTabs-stM3j4aH.js → LinkTabs-DBXbfzME.js} +2 -2
- package/dist/admin/assets/{LinkTabs-stM3j4aH.js.map → LinkTabs-DBXbfzME.js.map} +1 -1
- package/dist/admin/assets/{Match-Zu4pwfCC.js → Match-CgfZTzqs.js} +2 -2
- package/dist/admin/assets/{Match-Zu4pwfCC.js.map → Match-CgfZTzqs.js.map} +1 -1
- package/dist/admin/assets/{Panel-ClY9b33D.js → Panel-DDky5_x9.js} +2 -2
- package/dist/admin/assets/{Panel-ClY9b33D.js.map → Panel-DDky5_x9.js.map} +1 -1
- package/dist/admin/assets/{QueryView-Cu-KPtQI.js → QueryView-C2d392ly.js} +2 -2
- package/dist/admin/assets/{QueryView-Cu-KPtQI.js.map → QueryView-C2d392ly.js.map} +1 -1
- package/dist/admin/assets/{RadioTile-Ci0LucKI.js → RadioTile-CcP78eb_.js} +2 -2
- package/dist/admin/assets/{RadioTile-Ci0LucKI.js.map → RadioTile-CcP78eb_.js.map} +1 -1
- package/dist/admin/assets/{RichTextField-BcYOFkeW.js → RichTextField-CeztaFET.js} +2 -2
- package/dist/admin/assets/{RichTextField-BcYOFkeW.js.map → RichTextField-CeztaFET.js.map} +1 -1
- package/dist/admin/assets/{RowTitle-BdDgA5Lb.js → RowTitle-mkKp4yfl.js} +2 -2
- package/dist/admin/assets/{RowTitle-BdDgA5Lb.js.map → RowTitle-mkKp4yfl.js.map} +1 -1
- package/dist/admin/assets/{SecretReveal-ai1uMW_b.js → SecretReveal-mYCzxF96.js} +2 -2
- package/dist/admin/assets/{SecretReveal-ai1uMW_b.js.map → SecretReveal-mYCzxF96.js.map} +1 -1
- package/dist/admin/assets/{SharedGlyph-y6ZsA_gz.js → SharedGlyph-CuDT7OUU.js} +2 -2
- package/dist/admin/assets/{SharedGlyph-y6ZsA_gz.js.map → SharedGlyph-CuDT7OUU.js.map} +1 -1
- package/dist/admin/assets/{StepList-xmD9CPZl.js → StepList-BDO0Q3Wc.js} +2 -2
- package/dist/admin/assets/{StepList-xmD9CPZl.js.map → StepList-BDO0Q3Wc.js.map} +1 -1
- package/dist/admin/assets/Structure-CL_0hVci.js +2 -0
- package/dist/admin/assets/{Structure-DGt-TDk5.js.map → Structure-CL_0hVci.js.map} +1 -1
- package/dist/admin/assets/{SubNavLink-YjjaDN40.js → SubNavLink-C4eSybWd.js} +2 -2
- package/dist/admin/assets/{SubNavLink-YjjaDN40.js.map → SubNavLink-C4eSybWd.js.map} +1 -1
- package/dist/admin/assets/{SuggestAltButton-D1-v_0wB.js → SuggestAltButton-CxYlhwyd.js} +2 -2
- package/dist/admin/assets/{SuggestAltButton-D1-v_0wB.js.map → SuggestAltButton-CxYlhwyd.js.map} +1 -1
- package/dist/admin/assets/{TextLink-DIqr5zkJ.js → TextLink-DzNaAft6.js} +2 -2
- package/dist/admin/assets/{TextLink-DIqr5zkJ.js.map → TextLink-DzNaAft6.js.map} +1 -1
- package/dist/admin/assets/{Thumbnail-cjA7fSWh.js → Thumbnail-DSPArZ9h.js} +2 -2
- package/dist/admin/assets/{Thumbnail-cjA7fSWh.js.map → Thumbnail-DSPArZ9h.js.map} +1 -1
- package/dist/admin/assets/{UnsavedChangesGuard-B57D0278.js → UnsavedChangesGuard-BPOCBD5T.js} +2 -2
- package/dist/admin/assets/{UnsavedChangesGuard-B57D0278.js.map → UnsavedChangesGuard-BPOCBD5T.js.map} +1 -1
- package/dist/admin/assets/{UserAvatar--pMv2i3c.js → UserAvatar-BHNWHDVb.js} +2 -2
- package/dist/admin/assets/{UserAvatar--pMv2i3c.js.map → UserAvatar-BHNWHDVb.js.map} +1 -1
- package/dist/admin/assets/{auth-YVfK3SxF.js → auth-BzCgJtgr.js} +2 -2
- package/dist/admin/assets/{auth-YVfK3SxF.js.map → auth-BzCgJtgr.js.map} +1 -1
- package/dist/admin/assets/{badge-C5oMRtwJ.js → badge-DBoaS0nQ.js} +2 -2
- package/dist/admin/assets/{badge-C5oMRtwJ.js.map → badge-DBoaS0nQ.js.map} +1 -1
- package/dist/admin/assets/{boxes-DRvd25_u.js → boxes-DhN0ctLo.js} +2 -2
- package/dist/admin/assets/{boxes-DRvd25_u.js.map → boxes-DhN0ctLo.js.map} +1 -1
- package/dist/admin/assets/{calendar-clock-VENf4s1R.js → calendar-clock-BEA3tG17.js} +2 -2
- package/dist/admin/assets/{calendar-clock-VENf4s1R.js.map → calendar-clock-BEA3tG17.js.map} +1 -1
- package/dist/admin/assets/{changeSets-CObZV8H2.js → changeSets-C54Ku-X9.js} +2 -2
- package/dist/admin/assets/{changeSets-CObZV8H2.js.map → changeSets-C54Ku-X9.js.map} +1 -1
- package/dist/admin/assets/chevron-left-BeJdUVAX.js +2 -0
- package/dist/admin/assets/{chevron-left-D2DnQyeV.js.map → chevron-left-BeJdUVAX.js.map} +1 -1
- package/dist/admin/assets/{circle-check-jmRQz1Jq.js → circle-check-ktACw37r.js} +2 -2
- package/dist/admin/assets/{circle-check-jmRQz1Jq.js.map → circle-check-ktACw37r.js.map} +1 -1
- package/dist/admin/assets/{clock-DrSTMGjp.js → clock-BKNNASKp.js} +2 -2
- package/dist/admin/assets/{clock-DrSTMGjp.js.map → clock-BKNNASKp.js.map} +1 -1
- package/dist/admin/assets/cn-h5ORT3LK.js +2 -0
- package/dist/admin/assets/cn-h5ORT3LK.js.map +1 -0
- package/dist/admin/assets/coerce-DDQ1vGLx.js +2 -0
- package/dist/admin/assets/{coerce-B4MJTGg1.js.map → coerce-DDQ1vGLx.js.map} +1 -1
- package/dist/admin/assets/{constants-DT4WPUvz.js → constants-DH7oqfO_.js} +2 -2
- package/dist/admin/assets/{constants-DT4WPUvz.js.map → constants-DH7oqfO_.js.map} +1 -1
- package/dist/admin/assets/content-BptqQO8c.js +3 -0
- package/dist/admin/assets/content-BptqQO8c.js.map +1 -0
- package/dist/admin/assets/{contentCounts-DzIvdeXR.js → contentCounts-BpGlmyeZ.js} +2 -2
- package/dist/admin/assets/{contentCounts-DzIvdeXR.js.map → contentCounts-BpGlmyeZ.js.map} +1 -1
- package/dist/admin/assets/{contentTypes-ArLEfM29.js → contentTypes-B_mAlIcH.js} +2 -2
- package/dist/admin/assets/{contentTypes-ArLEfM29.js.map → contentTypes-B_mAlIcH.js.map} +1 -1
- package/dist/admin/assets/{context-4Jl20mYc.js → context-DyyMW7Md.js} +2 -2
- package/dist/admin/assets/{context-4Jl20mYc.js.map → context-DyyMW7Md.js.map} +1 -1
- package/dist/admin/assets/{copy-t6kAFw-O.js → copy-DjzlFY9u.js} +2 -2
- package/dist/admin/assets/{copy-t6kAFw-O.js.map → copy-DjzlFY9u.js.map} +1 -1
- package/dist/admin/assets/{dateTimeLocal-IDUOKulz.js → dateTimeLocal-DFXY779D.js} +2 -2
- package/dist/admin/assets/{dateTimeLocal-IDUOKulz.js.map → dateTimeLocal-DFXY779D.js.map} +1 -1
- package/dist/admin/assets/{develop-DvKYqVeK.js → develop-DJap_z-1.js} +3 -3
- package/dist/admin/assets/{develop-DvKYqVeK.js.map → develop-DJap_z-1.js.map} +1 -1
- package/dist/admin/assets/{dropdown-menu-DPzDthV7.js → dropdown-menu-DGSMTf23.js} +2 -2
- package/dist/admin/assets/{dropdown-menu-DPzDthV7.js.map → dropdown-menu-DGSMTf23.js.map} +1 -1
- package/dist/admin/assets/{ellipsis-DEHUaCkn.js → ellipsis-BMP-j3yO.js} +2 -2
- package/dist/admin/assets/{ellipsis-DEHUaCkn.js.map → ellipsis-BMP-j3yO.js.map} +1 -1
- package/dist/admin/assets/{external-link-B8UFREQ_.js → external-link-DzOkt97S.js} +2 -2
- package/dist/admin/assets/{external-link-B8UFREQ_.js.map → external-link-DzOkt97S.js.map} +1 -1
- package/dist/admin/assets/{fileRoute-sJvv4Ipn.js → fileRoute-CG0Dufvt.js} +2 -2
- package/dist/admin/assets/{fileRoute-sJvv4Ipn.js.map → fileRoute-CG0Dufvt.js.map} +1 -1
- package/dist/admin/assets/{folder-plus-iDg1hhew.js → folder-plus-C-LhI4a2.js} +2 -2
- package/dist/admin/assets/{folder-plus-iDg1hhew.js.map → folder-plus-C-LhI4a2.js.map} +1 -1
- package/dist/admin/assets/{followPlural-tOh6gDgT.js → followPlural-D-8kzFOe.js} +2 -2
- package/dist/admin/assets/{followPlural-tOh6gDgT.js.map → followPlural-D-8kzFOe.js.map} +1 -1
- package/dist/admin/assets/{globe-TooNSp9V.js → globe-DOoGBy23.js} +2 -2
- package/dist/admin/assets/{globe-TooNSp9V.js.map → globe-DOoGBy23.js.map} +1 -1
- package/dist/admin/assets/{image-CXZXXH58.js → image-D9p_gGE2.js} +2 -2
- package/dist/admin/assets/{image-CXZXXH58.js.map → image-D9p_gGE2.js.map} +1 -1
- package/dist/admin/assets/{image-off-cxva5G-Y.js → image-off-G0M2Nh1I.js} +2 -2
- package/dist/admin/assets/{image-off-cxva5G-Y.js.map → image-off-G0M2Nh1I.js.map} +1 -1
- package/dist/admin/assets/{inbox-B8c9Kj_z.js → inbox-CMzYVMZo.js} +2 -2
- package/dist/admin/assets/{inbox-B8c9Kj_z.js.map → inbox-CMzYVMZo.js.map} +1 -1
- package/dist/admin/assets/{inbox-nfR2K7f6.js → inbox-XaM-nvsM.js} +2 -2
- package/dist/admin/assets/{inbox-nfR2K7f6.js.map → inbox-XaM-nvsM.js.map} +1 -1
- package/dist/admin/assets/index-6iVBpUhu.css +2 -0
- package/dist/admin/assets/index-DHmfMpqz.js +4 -0
- package/dist/admin/assets/index-DHmfMpqz.js.map +1 -0
- package/dist/admin/assets/{key-round-CFJ61iCd.js → key-round-HBvMrmMo.js} +2 -2
- package/dist/admin/assets/{key-round-CFJ61iCd.js.map → key-round-HBvMrmMo.js.map} +1 -1
- package/dist/admin/assets/{languages-eh7U4anI.js → languages-BOv0jYhe.js} +2 -2
- package/dist/admin/assets/{languages-eh7U4anI.js.map → languages-BOv0jYhe.js.map} +1 -1
- package/dist/admin/assets/{layers-mGfmMsKE.js → layers-CkZyCaeb.js} +2 -2
- package/dist/admin/assets/{layers-mGfmMsKE.js.map → layers-CkZyCaeb.js.map} +1 -1
- package/dist/admin/assets/{link-2-off-DbZzRN_w.js → link-2-off-UUkSmPIL.js} +2 -2
- package/dist/admin/assets/{link-2-off-DbZzRN_w.js.map → link-2-off-UUkSmPIL.js.map} +1 -1
- package/dist/admin/assets/{link-b2pFaN-A.js → link-Bvqcg7db.js} +2 -2
- package/dist/admin/assets/{link-b2pFaN-A.js.map → link-Bvqcg7db.js.map} +1 -1
- package/dist/admin/assets/{locales-oBD0q1pN.js → locales-DbrcdhZi.js} +2 -2
- package/dist/admin/assets/{locales-oBD0q1pN.js.map → locales-DbrcdhZi.js.map} +1 -1
- package/dist/admin/assets/{media-BzivlEh2.js → media-CO0HSS3b.js} +2 -2
- package/dist/admin/assets/{media-BzivlEh2.js.map → media-CO0HSS3b.js.map} +1 -1
- package/dist/admin/assets/{media-5ks_0Rre.js → media-ugEyLgwN.js} +2 -2
- package/dist/admin/assets/{media-5ks_0Rre.js.map → media-ugEyLgwN.js.map} +1 -1
- package/dist/admin/assets/{network-Cj04CpFi.js → network-BfDaUb7D.js} +2 -2
- package/dist/admin/assets/{network-Cj04CpFi.js.map → network-BfDaUb7D.js.map} +1 -1
- package/dist/admin/assets/{pencil-C7eD5mlZ.js → pencil-BLNGNJmL.js} +2 -2
- package/dist/admin/assets/{pencil-C7eD5mlZ.js.map → pencil-BLNGNJmL.js.map} +1 -1
- package/dist/admin/assets/{plus-BDZHQBlU.js → plus-BXMBYPoB.js} +2 -2
- package/dist/admin/assets/{plus-BDZHQBlU.js.map → plus-BXMBYPoB.js.map} +1 -1
- package/dist/admin/assets/{preload-helper-CJNJ4S3k.js → preload-helper-DEbJbZwX.js} +2 -2
- package/dist/admin/assets/{preload-helper-CJNJ4S3k.js.map → preload-helper-DEbJbZwX.js.map} +1 -1
- package/dist/admin/assets/{publishing-ChHkrwvr.js → publishing-DKrATBaj.js} +2 -2
- package/dist/admin/assets/{publishing-ChHkrwvr.js.map → publishing-DKrATBaj.js.map} +1 -1
- package/dist/admin/assets/{radio-group-DSK5LPaM.js → radio-group-BIhT_uEK.js} +2 -2
- package/dist/admin/assets/{radio-group-DSK5LPaM.js.map → radio-group-BIhT_uEK.js.map} +1 -1
- package/dist/admin/assets/{rotate-ccw-DCWwb4Ga.js → rotate-ccw-DqbffhiC.js} +2 -2
- package/dist/admin/assets/{rotate-ccw-DCWwb4Ga.js.map → rotate-ccw-DqbffhiC.js.map} +1 -1
- package/dist/admin/assets/{schedules-DXrBSypK.js → schedules-CETl2eLe.js} +2 -2
- package/dist/admin/assets/{schedules-DXrBSypK.js.map → schedules-CETl2eLe.js.map} +1 -1
- package/dist/admin/assets/{schema-B7L9OJrX.js → schema-CWHuez0q.js} +2 -2
- package/dist/admin/assets/{schema-B7L9OJrX.js.map → schema-CWHuez0q.js.map} +1 -1
- package/dist/admin/assets/{scroll-text-BLThe4Ki.js → scroll-text-DlcDEi2k.js} +2 -2
- package/dist/admin/assets/{scroll-text-BLThe4Ki.js.map → scroll-text-DlcDEi2k.js.map} +1 -1
- package/dist/admin/assets/{search-CcJQqtRw.js → search-Dy2dtwyR.js} +2 -2
- package/dist/admin/assets/{search-CcJQqtRw.js.map → search-Dy2dtwyR.js.map} +1 -1
- package/dist/admin/assets/{searchSchemas-BdwAFaVO.js → searchSchemas-Cz4P1iku.js} +2 -2
- package/dist/admin/assets/{searchSchemas-BdwAFaVO.js.map → searchSchemas-Cz4P1iku.js.map} +1 -1
- package/dist/admin/assets/{sections-iNOzo7pL.js → sections-BfG6DTFr.js} +2 -2
- package/dist/admin/assets/{sections-iNOzo7pL.js.map → sections-BfG6DTFr.js.map} +1 -1
- package/dist/admin/assets/{sections-TIN_Nl1b.js → sections-EgXyXN3-.js} +2 -2
- package/dist/admin/assets/{sections-TIN_Nl1b.js.map → sections-EgXyXN3-.js.map} +1 -1
- package/dist/admin/assets/{select-DyD7qIkJ.js → select--mePTdz8.js} +2 -2
- package/dist/admin/assets/{select-DyD7qIkJ.js.map → select--mePTdz8.js.map} +1 -1
- package/dist/admin/assets/{send-CbP0Rc9_.js → send-DVgq5Iwg.js} +2 -2
- package/dist/admin/assets/{send-CbP0Rc9_.js.map → send-DVgq5Iwg.js.map} +1 -1
- package/dist/admin/assets/{seo-uMm2fGPB.js → seo-Zhb62Ojz.js} +2 -2
- package/dist/admin/assets/{seo-uMm2fGPB.js.map → seo-Zhb62Ojz.js.map} +1 -1
- package/dist/admin/assets/{settings-CJVORrIV.js → settings-Mscp6tN5.js} +2 -2
- package/dist/admin/assets/{settings-CJVORrIV.js.map → settings-Mscp6tN5.js.map} +1 -1
- package/dist/admin/assets/{skeleton-B5GJgv7y.js → skeleton-DJVukcn6.js} +2 -2
- package/dist/admin/assets/skeleton-DJVukcn6.js.map +1 -0
- package/dist/admin/assets/{snapshots-DkyyrWX3.js → snapshots-BpLhILlt.js} +2 -2
- package/dist/admin/assets/{snapshots-DkyyrWX3.js.map → snapshots-BpLhILlt.js.map} +1 -1
- package/dist/admin/assets/{sparkles-D88gf3vX.js → sparkles-Dd6S_roX.js} +2 -2
- package/dist/admin/assets/{sparkles-D88gf3vX.js.map → sparkles-Dd6S_roX.js.map} +1 -1
- package/dist/admin/assets/src-Be3Ds5Dc.js +4 -0
- package/dist/admin/assets/src-Be3Ds5Dc.js.map +1 -0
- package/dist/admin/assets/{stableId-DlNKh3HM.js → stableId-DFVM13gJ.js} +2 -2
- package/dist/admin/assets/{stableId-DlNKh3HM.js.map → stableId-DFVM13gJ.js.map} +1 -1
- package/dist/admin/assets/{store-jUWyOXDG.js → store-BP-xStwU.js} +2 -2
- package/dist/admin/assets/{store-jUWyOXDG.js.map → store-BP-xStwU.js.map} +1 -1
- package/dist/admin/assets/{switch-DbIrjP3h.js → switch-skVypg5O.js} +2 -2
- package/dist/admin/assets/{switch-DbIrjP3h.js.map → switch-skVypg5O.js.map} +1 -1
- package/dist/admin/assets/{table-DQ5ILlSh.js → table-BMbbRUK9.js} +2 -2
- package/dist/admin/assets/{table-DQ5ILlSh.js.map → table-BMbbRUK9.js.map} +1 -1
- package/dist/admin/assets/tabs-C1lgOg1B.js +5 -0
- package/dist/admin/assets/tabs-C1lgOg1B.js.map +1 -0
- package/dist/admin/assets/{textarea-FwSvwmyT.js → textarea-CffBBxoQ.js} +2 -2
- package/dist/admin/assets/{textarea-FwSvwmyT.js.map → textarea-CffBBxoQ.js.map} +1 -1
- package/dist/admin/assets/{theme-C0Tv6M38.js → theme-RHTVnR9j.js} +2 -2
- package/dist/admin/assets/{theme-C0Tv6M38.js.map → theme-RHTVnR9j.js.map} +1 -1
- package/dist/admin/assets/{titles-uL13bpi8.js → titles-DWiPmNvn.js} +2 -2
- package/dist/admin/assets/{titles-uL13bpi8.js.map → titles-DWiPmNvn.js.map} +1 -1
- package/dist/admin/assets/{tokens-C-20gLAT.js → tokens-BqyeN1D-.js} +2 -2
- package/dist/admin/assets/{tokens-C-20gLAT.js.map → tokens-BqyeN1D-.js.map} +1 -1
- package/dist/admin/assets/{tooltip-BK5FqmJ0.js → tooltip-BfSF6EyR.js} +2 -2
- package/dist/admin/assets/{tooltip-BK5FqmJ0.js.map → tooltip-BfSF6EyR.js.map} +1 -1
- package/dist/admin/assets/{undo-2-CLy2zklL.js → undo-2-p4YlNhBp.js} +2 -2
- package/dist/admin/assets/{undo-2-CLy2zklL.js.map → undo-2-p4YlNhBp.js.map} +1 -1
- package/dist/admin/assets/{useAuditLabels-BJF7M5RY.js → useAuditLabels-DV5gcaaG.js} +2 -2
- package/dist/admin/assets/{useAuditLabels-BJF7M5RY.js.map → useAuditLabels-DV5gcaaG.js.map} +1 -1
- package/dist/admin/assets/{useConfirmTarget-BUvlrGSW.js → useConfirmTarget-DCvnxNVg.js} +2 -2
- package/dist/admin/assets/{useConfirmTarget-BUvlrGSW.js.map → useConfirmTarget-DCvnxNVg.js.map} +1 -1
- package/dist/admin/assets/{useContentSchema-BCdLYsKa.js → useContentSchema-z6tNif86.js} +2 -2
- package/dist/admin/assets/{useContentSchema-BCdLYsKa.js.map → useContentSchema-z6tNif86.js.map} +1 -1
- package/dist/admin/assets/{useCursorPager-CzB6NDOB.js → useCursorPager-CQGX41_L.js} +2 -2
- package/dist/admin/assets/{useCursorPager-CzB6NDOB.js.map → useCursorPager-CQGX41_L.js.map} +1 -1
- package/dist/admin/assets/{useDiscardGuard-CGFgC3xY.js → useDiscardGuard-Dhbif7VL.js} +2 -2
- package/dist/admin/assets/{useDiscardGuard-CGFgC3xY.js.map → useDiscardGuard-Dhbif7VL.js.map} +1 -1
- package/dist/admin/assets/{useMediaPickerDialog-BOkGEyG7.js → useMediaPickerDialog-D-5uKsMG.js} +2 -2
- package/dist/admin/assets/{useMediaPickerDialog-BOkGEyG7.js.map → useMediaPickerDialog-D-5uKsMG.js.map} +1 -1
- package/dist/admin/assets/{useMediaUploader-D1ouV8jc.js → useMediaUploader-BtLoimmQ.js} +2 -2
- package/dist/admin/assets/{useMediaUploader-D1ouV8jc.js.map → useMediaUploader-BtLoimmQ.js.map} +1 -1
- package/dist/admin/assets/usePaletteItems-D3PZsW0Q.js +2 -0
- package/dist/admin/assets/usePaletteItems-D3PZsW0Q.js.map +1 -0
- package/dist/admin/assets/{usePlaces-6iTN15Xy.js → usePlaces-Cj5PljTy.js} +2 -2
- package/dist/admin/assets/{usePlaces-6iTN15Xy.js.map → usePlaces-Cj5PljTy.js.map} +1 -1
- package/dist/admin/assets/{useQueries-CYfcxI0Q.js → useQueries-CeDzli4Z.js} +2 -2
- package/dist/admin/assets/{useQueries-CYfcxI0Q.js.map → useQueries-CeDzli4Z.js.map} +1 -1
- package/dist/admin/assets/{useResolvedScheme-D0moXjaP.js → useResolvedScheme-DHD-zisP.js} +2 -2
- package/dist/admin/assets/{useResolvedScheme-D0moXjaP.js.map → useResolvedScheme-DHD-zisP.js.map} +1 -1
- package/dist/admin/assets/{useSchemaScopeAccess-WQKmj8_3.js → useSchemaScopeAccess-BNeVslcv.js} +2 -2
- package/dist/admin/assets/{useSchemaScopeAccess-WQKmj8_3.js.map → useSchemaScopeAccess-BNeVslcv.js.map} +1 -1
- package/dist/admin/assets/{users-BgTNDPXx.js → users-BRefbzuS.js} +2 -2
- package/dist/admin/assets/{users-BgTNDPXx.js.map → users-BRefbzuS.js.map} +1 -1
- package/dist/admin/assets/{users-B01EH47o.js → users-CppI8ctO.js} +2 -2
- package/dist/admin/assets/{users-B01EH47o.js.map → users-CppI8ctO.js.map} +1 -1
- package/dist/admin/assets/{zod-CS0EvbwG.js → zod-Dcwk-m8X.js} +2 -2
- package/dist/admin/assets/{zod-CS0EvbwG.js.map → zod-Dcwk-m8X.js.map} +1 -1
- package/dist/admin/index.html +57 -57
- package/dist/cli.js +110 -18
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{public-BSbOGbK-.d.ts → public-DYR4mtVQ.d.ts} +3 -1
- package/dist/s3Adapter-C8AgFVYw.js +125 -0
- package/dist/s3Adapter-C8AgFVYw.js.map +1 -0
- package/dist/{worker-i1rY2TIZ.js → worker-jz_0yAmS.js} +691 -458
- package/dist/worker-jz_0yAmS.js.map +1 -0
- package/package.json +8 -5
- package/dist/admin/assets/Builder-C11yUgu9.js +0 -2
- package/dist/admin/assets/Builder-C11yUgu9.js.map +0 -1
- package/dist/admin/assets/Structure-DGt-TDk5.js +0 -2
- package/dist/admin/assets/chevron-left-D2DnQyeV.js +0 -2
- package/dist/admin/assets/cn-CL1YHRrS.js +0 -2
- package/dist/admin/assets/cn-CL1YHRrS.js.map +0 -1
- package/dist/admin/assets/coerce-B4MJTGg1.js +0 -2
- package/dist/admin/assets/content-HvisCB5m.js +0 -3
- package/dist/admin/assets/content-HvisCB5m.js.map +0 -1
- package/dist/admin/assets/index-Bbb2ER3l.css +0 -2
- package/dist/admin/assets/index-C6FrF34G.js +0 -4
- package/dist/admin/assets/index-C6FrF34G.js.map +0 -1
- package/dist/admin/assets/skeleton-B5GJgv7y.js.map +0 -1
- package/dist/admin/assets/src-B2qcqjNM.js +0 -4
- package/dist/admin/assets/src-B2qcqjNM.js.map +0 -1
- package/dist/admin/assets/tabs-BJhfL-lw.js +0 -5
- package/dist/admin/assets/tabs-BJhfL-lw.js.map +0 -1
- package/dist/admin/assets/usePaletteItems-neKzBlys.js +0 -2
- package/dist/admin/assets/usePaletteItems-neKzBlys.js.map +0 -1
- package/dist/worker-i1rY2TIZ.js.map +0 -1
|
@@ -7,7 +7,6 @@ import Fastify, { LogController } 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
9
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
|
-
import mysql from "mysql2";
|
|
11
10
|
import { EventEmitter } from "node:events";
|
|
12
11
|
import { DatabaseSync } from "node:sqlite";
|
|
13
12
|
import { Type } from "typebox";
|
|
@@ -15,9 +14,6 @@ import { Value } from "typebox/value";
|
|
|
15
14
|
import { isDeepStrictEqual } from "node:util";
|
|
16
15
|
import { createJiti } from "jiti";
|
|
17
16
|
import { pino } from "pino";
|
|
18
|
-
import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, PutObjectCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
19
|
-
import { createPresignedPost } from "@aws-sdk/s3-presigned-post";
|
|
20
|
-
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
21
17
|
import fp from "fastify-plugin";
|
|
22
18
|
import { hash, verify } from "@node-rs/argon2";
|
|
23
19
|
import cookie from "@fastify/cookie";
|
|
@@ -177,6 +173,20 @@ const sqliteLocationOfUrl = (url) => {
|
|
|
177
173
|
path
|
|
178
174
|
};
|
|
179
175
|
};
|
|
176
|
+
/** `host:port/database` (PostgreSQL, MySQL) or the file path (SQLite) from DATABASE_URL, never the credentials. */
|
|
177
|
+
const describeDatabaseTarget = (connectionString) => {
|
|
178
|
+
if (dialectOfUrl(connectionString) === "sqlite") {
|
|
179
|
+
const location = sqliteLocationOfUrl(connectionString);
|
|
180
|
+
return location.kind === "memory" ? "an in-memory database" : location.path;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const url = new URL(connectionString);
|
|
184
|
+
const defaultPort = dialectOfUrl(connectionString) === "mysql" ? "3306" : "5432";
|
|
185
|
+
return `${url.hostname}:${url.port || defaultPort}${url.pathname}`;
|
|
186
|
+
} catch {
|
|
187
|
+
return "the configured database";
|
|
188
|
+
}
|
|
189
|
+
};
|
|
180
190
|
let current;
|
|
181
191
|
/** The dialect queries are built for. PostgreSQL until a database handle says otherwise. */
|
|
182
192
|
const currentDialect = () => current ?? "postgres";
|
|
@@ -1084,13 +1094,15 @@ const MYSQL_TABLES = {
|
|
|
1084
1094
|
"change_seq",
|
|
1085
1095
|
"created_at",
|
|
1086
1096
|
"updated_at",
|
|
1087
|
-
"site_id"
|
|
1097
|
+
"site_id",
|
|
1098
|
+
"entry_created_at"
|
|
1088
1099
|
],
|
|
1089
|
-
nullable: ["autosaved_at"],
|
|
1100
|
+
nullable: ["autosaved_at", "entry_created_at"],
|
|
1090
1101
|
timestamps: [
|
|
1091
1102
|
"autosaved_at",
|
|
1092
1103
|
"created_at",
|
|
1093
|
-
"updated_at"
|
|
1104
|
+
"updated_at",
|
|
1105
|
+
"entry_created_at"
|
|
1094
1106
|
],
|
|
1095
1107
|
uniqueKeys: [{
|
|
1096
1108
|
name: "entry_heads_pkey",
|
|
@@ -1897,9 +1909,10 @@ const MYSQL_TABLES = {
|
|
|
1897
1909
|
"id",
|
|
1898
1910
|
"schema_version",
|
|
1899
1911
|
"permissions_version",
|
|
1900
|
-
"updated_at"
|
|
1912
|
+
"updated_at",
|
|
1913
|
+
"release"
|
|
1901
1914
|
],
|
|
1902
|
-
nullable: [],
|
|
1915
|
+
nullable: ["release"],
|
|
1903
1916
|
timestamps: ["updated_at"],
|
|
1904
1917
|
uniqueKeys: [{
|
|
1905
1918
|
name: "system_versions_pkey",
|
|
@@ -2682,6 +2695,13 @@ const typeCast = (field, next) => {
|
|
|
2682
2695
|
return next();
|
|
2683
2696
|
};
|
|
2684
2697
|
//#endregion
|
|
2698
|
+
//#region src/db/mysql/module.ts
|
|
2699
|
+
/**
|
|
2700
|
+
* mysql2, loaded on first use. A process on PostgreSQL never loads it, and a bundle of the read path (the
|
|
2701
|
+
* in-process delivery runtime) can leave it out as an optional dependency.
|
|
2702
|
+
*/
|
|
2703
|
+
const loadMysql = async () => (await import("mysql2")).default;
|
|
2704
|
+
//#endregion
|
|
2685
2705
|
//#region src/db/mysql/parameters.ts
|
|
2686
2706
|
/**
|
|
2687
2707
|
* Parameters the MySQL connection resolves itself just before a statement runs (ADR 0001, "MySQL").
|
|
@@ -2883,7 +2903,7 @@ var MysqlDriver = class {
|
|
|
2883
2903
|
this.databaseKey = mysqlDatabaseKey(options.url);
|
|
2884
2904
|
}
|
|
2885
2905
|
async init() {
|
|
2886
|
-
const pool =
|
|
2906
|
+
const pool = (await loadMysql()).createPool(mysqlPoolOptions(this.#options.url, this.#options.poolMax, this.#options.applicationName));
|
|
2887
2907
|
pool.on("connection", (connection) => {
|
|
2888
2908
|
connection.query(SESSION_SETUP, (error) => {
|
|
2889
2909
|
if (error) {
|
|
@@ -4590,6 +4610,8 @@ const countLiveByModel = (siteId, modelIds, executor = db) => modelIds.length ==
|
|
|
4590
4610
|
* Pass the outer query's executor; the subquery is compiled into the outer statement.
|
|
4591
4611
|
*/
|
|
4592
4612
|
const entrySiteOf = (executor, entryId) => executor.selectFrom("entries").select("entries.site_id").where("entries.id", "=", entryId);
|
|
4613
|
+
/** The entry's creation time, copied onto each head (`entry_heads.entry_created_at`, the default list order). */
|
|
4614
|
+
const entryCreatedAtOf = (executor, entryId) => executor.selectFrom("entries").select("entries.created_at").where("entries.id", "=", entryId);
|
|
4593
4615
|
/**
|
|
4594
4616
|
* Live entries of a model on sites other than `siteId`, per site (a definition can only move to one site
|
|
4595
4617
|
* while no other site holds entries of it: plan site-schema, rule 2). Deleted ones do not count: once the
|
|
@@ -4704,6 +4726,7 @@ const findForEntry = (entryId, executor = db) => executor.selectFrom("entry_head
|
|
|
4704
4726
|
const insert$28 = (head, trx = db) => trx.insertInto("entry_heads").values({
|
|
4705
4727
|
entry_id: head.entryId,
|
|
4706
4728
|
site_id: entrySiteOf(trx, head.entryId),
|
|
4729
|
+
entry_created_at: entryCreatedAtOf(trx, head.entryId),
|
|
4707
4730
|
model_id: head.modelId,
|
|
4708
4731
|
locale: head.locale,
|
|
4709
4732
|
state: head.state,
|
|
@@ -6345,6 +6368,11 @@ const effectiveTitleField = (definition) => {
|
|
|
6345
6368
|
};
|
|
6346
6369
|
/** Title types that the document edits inline as its H1; any other title type stays a property. */
|
|
6347
6370
|
const INLINE_TITLE_TYPES = /* @__PURE__ */ new Set(["string", "text"]);
|
|
6371
|
+
/**
|
|
6372
|
+
* The automatic rule: the fields written in the document while `display.canvasFieldIds` is unset (rich
|
|
6373
|
+
* text, dynamic zones, repeatable components, media with several files). Also what a canvas field can take
|
|
6374
|
+
* between blocks: only these hold blocks.
|
|
6375
|
+
*/
|
|
6348
6376
|
const isCanvasEligible = (field) => {
|
|
6349
6377
|
switch (field.type) {
|
|
6350
6378
|
case "richtext":
|
|
@@ -6354,6 +6382,12 @@ const isCanvasEligible = (field) => {
|
|
|
6354
6382
|
default: return false;
|
|
6355
6383
|
}
|
|
6356
6384
|
};
|
|
6385
|
+
/**
|
|
6386
|
+
* Whether `display.canvasFieldIds` may list the field: any field but the entry's title (`effectiveTitleField`),
|
|
6387
|
+
* which the document shows as its heading. A configured cover is refused by the validator; an automatically
|
|
6388
|
+
* chosen one placed in the document simply stops being the cover.
|
|
6389
|
+
*/
|
|
6390
|
+
const isDocumentPlaceable = (field, title) => field.id !== title?.id;
|
|
6357
6391
|
/** A single `media` field that may hold an image. */
|
|
6358
6392
|
const isCoverEligible = (field) => field.type === "media" && !field.settings.multiple && (field.settings.allowedKinds === void 0 || field.settings.allowedKinds.includes("image"));
|
|
6359
6393
|
const byIds = (live, ids) => (ids ?? []).map((id) => live.find((field) => field.id === id)).filter((field) => field !== void 0);
|
|
@@ -6378,16 +6412,16 @@ const coverOf = (model, live, canvas) => {
|
|
|
6378
6412
|
return live.find((field) => isCoverEligible(field) && !canvas.has(field.id));
|
|
6379
6413
|
};
|
|
6380
6414
|
/**
|
|
6381
|
-
* The entry document's layout: title, cover,
|
|
6382
|
-
* when they point at live, eligible fields; anything else falls back to the defaults (canvas: every
|
|
6383
|
-
*
|
|
6384
|
-
* skipped, so a stale definition never breaks the editor. Pure: no values, no I/O.
|
|
6415
|
+
* The entry document's layout: title, cover, document fields and properties. Configured `display` ids win
|
|
6416
|
+
* when they point at live, eligible fields; anything else falls back to the defaults (canvas: every block
|
|
6417
|
+
* field in field order; cover: the first single image field not in the canvas). Deprecated, unknown ids and
|
|
6418
|
+
* the title are skipped, so a stale definition never breaks the editor. Pure: no values, no I/O.
|
|
6385
6419
|
*/
|
|
6386
6420
|
const effectiveLayout = (model) => {
|
|
6387
6421
|
const live = model.fields.filter((field) => !field.deprecated);
|
|
6388
6422
|
const title = effectiveTitleField(model);
|
|
6389
6423
|
const titleInline = title !== void 0 && INLINE_TITLE_TYPES.has(title.type);
|
|
6390
|
-
const configuredCanvas = unique$1(byIds(live, model.display.canvasFieldIds).filter(
|
|
6424
|
+
const configuredCanvas = unique$1(byIds(live, model.display.canvasFieldIds).filter((field) => isDocumentPlaceable(field, title)));
|
|
6391
6425
|
const canvasCandidates = model.display.canvasFieldIds === void 0 ? live.filter(isCanvasEligible) : configuredCanvas;
|
|
6392
6426
|
const cover = coverOf(model, live, new Set(configuredCanvas.map((field) => field.id)));
|
|
6393
6427
|
const canvas = canvasCandidates.filter((field) => field.id !== cover?.id);
|
|
@@ -6974,17 +7008,18 @@ const checkFieldList = (ids, check) => {
|
|
|
6974
7008
|
});
|
|
6975
7009
|
};
|
|
6976
7010
|
/**
|
|
6977
|
-
* The entry document's layout (`effectiveLayout`):
|
|
6978
|
-
* are disjoint
|
|
6979
|
-
* entries are properties, so never the cover or a
|
|
7011
|
+
* The entry document's layout (`effectiveLayout`): document fields, cover and strip. Title, cover and
|
|
7012
|
+
* canvas are disjoint: any field but the title (configured or automatic) may be in the canvas, the
|
|
7013
|
+
* configured cover may not; strip entries are properties, so never the cover or a document field.
|
|
6980
7014
|
*/
|
|
6981
7015
|
const checkDocumentLayout = (model, fields) => {
|
|
6982
7016
|
const { canvasFieldIds, coverFieldId, stripFieldIds } = model.display;
|
|
6983
7017
|
const canvas = new Set(canvasFieldIds ?? []);
|
|
7018
|
+
const title = effectiveTitleField(model);
|
|
6984
7019
|
const issues = checkFieldList(canvasFieldIds, {
|
|
6985
7020
|
path: "/display/canvasFieldIds",
|
|
6986
7021
|
fields,
|
|
6987
|
-
reject: (field) =>
|
|
7022
|
+
reject: (field) => isDocumentPlaceable(field, title) ? void 0 : "the title is the heading of the document, not a field in it"
|
|
6988
7023
|
});
|
|
6989
7024
|
if (coverFieldId !== void 0) issues.push(...checkFieldList([coverFieldId], {
|
|
6990
7025
|
path: "/display/coverFieldId",
|
|
@@ -7001,11 +7036,6 @@ const checkDocumentLayout = (model, fields) => {
|
|
|
7001
7036
|
}));
|
|
7002
7037
|
return issues;
|
|
7003
7038
|
};
|
|
7004
|
-
const canvasRejection = (field) => {
|
|
7005
|
-
if (field.type === "component") return "only a repeatable component can be in the canvas";
|
|
7006
|
-
if (field.type === "media") return "only a media field with several files can be in the canvas";
|
|
7007
|
-
return `a ${field.type} field cannot be in the canvas`;
|
|
7008
|
-
};
|
|
7009
7039
|
const checkDisplay = (definition) => {
|
|
7010
7040
|
const fields = new Map(definition.fields.map((field) => [field.id, field]));
|
|
7011
7041
|
const fieldIds = new Set(fields.keys());
|
|
@@ -9423,6 +9453,10 @@ const bumpSchemaVersion = async (trx, now) => {
|
|
|
9423
9453
|
updated_at: now
|
|
9424
9454
|
})).returning("schema_version").executeTakeFirstOrThrow()).schema_version;
|
|
9425
9455
|
};
|
|
9456
|
+
/** Records the Shapio release this database is served by (in-process readers compare their own with it). */
|
|
9457
|
+
const setRelease = async (release, executor = db) => {
|
|
9458
|
+
await executor.updateTable("system_versions").set({ release }).execute();
|
|
9459
|
+
};
|
|
9426
9460
|
//#endregion
|
|
9427
9461
|
//#region src/schema/snapshot.ts
|
|
9428
9462
|
/**
|
|
@@ -12120,13 +12154,20 @@ const groupBindings = (rows) => {
|
|
|
12120
12154
|
return bindings;
|
|
12121
12155
|
};
|
|
12122
12156
|
const KNOWN_ACTIONS = /* @__PURE__ */ new Set([...CONTENT_ACTIONS, ...GLOBAL_ACTIONS]);
|
|
12157
|
+
const KNOWN_CONDITIONS = /* @__PURE__ */ new Set(["ownedByPrincipal"]);
|
|
12158
|
+
/**
|
|
12159
|
+
* A stored grant as this build understands it. Rows written by a newer Shapio may name an action or a row
|
|
12160
|
+
* condition this build does not know; they grant nothing here (fail closed). An unknown condition must never
|
|
12161
|
+
* read as "no condition", which would widen the grant to every row.
|
|
12162
|
+
*/
|
|
12123
12163
|
const toGrant$1 = (row) => {
|
|
12124
12164
|
if (!KNOWN_ACTIONS.has(row.action)) return;
|
|
12165
|
+
if (row.condition !== null && !KNOWN_CONDITIONS.has(row.condition)) return;
|
|
12125
12166
|
return {
|
|
12126
12167
|
roleId: row.role_id,
|
|
12127
12168
|
action: row.action,
|
|
12128
12169
|
modelId: row.model_id,
|
|
12129
|
-
condition: row.condition
|
|
12170
|
+
condition: row.condition,
|
|
12130
12171
|
fieldIds: row.field_ids
|
|
12131
12172
|
};
|
|
12132
12173
|
};
|
|
@@ -12561,9 +12602,11 @@ const mysqlContentDialect = {
|
|
|
12561
12602
|
},
|
|
12562
12603
|
snapshotCte: ({ name, siteId, modelId, seq }) => sql`with ${sql.id(name)} as (
|
|
12563
12604
|
select pl.entry_id, pl.site_id, pl.model_id, pl.locale, 'published' as state, r.data, 0 as version,
|
|
12564
|
-
r.id as revision_id, pl.published_at as updated_at, null as autosaved_at
|
|
12605
|
+
r.id as revision_id, pl.published_at as updated_at, null as autosaved_at,
|
|
12606
|
+
en.created_at as entry_created_at
|
|
12565
12607
|
from publication_log pl
|
|
12566
12608
|
join content_revisions r on r.id = pl.revision_id
|
|
12609
|
+
join entries en on en.id = pl.entry_id
|
|
12567
12610
|
where pl.site_id = ${siteId} and pl.model_id = ${modelId}
|
|
12568
12611
|
and pl.from_seq <= ${seq}
|
|
12569
12612
|
and (pl.to_seq is null or pl.to_seq > ${seq})
|
|
@@ -12586,8 +12629,9 @@ const mysqlContentDialect = {
|
|
|
12586
12629
|
//#region src/content/compiler/dialect/postgres.ts
|
|
12587
12630
|
/**
|
|
12588
12631
|
* PostgreSQL content SQL (ADR 0001): JSONB with a GIN `jsonb_path_ops` index serving containment, so
|
|
12589
|
-
*
|
|
12590
|
-
* built `CONCURRENTLY` with a
|
|
12632
|
+
* equality on a field without an index is emitted as `data @> {...}`; ranges, sorts and equality on indexed
|
|
12633
|
+
* fields (filterable or sortable) use per-field B-tree expression indexes built `CONCURRENTLY` with a
|
|
12634
|
+
* matching `CREATE STATISTICS` object.
|
|
12591
12635
|
*/
|
|
12592
12636
|
const DATA$1 = sql.ref(CONTENT_HEADS_COLUMNS.data);
|
|
12593
12637
|
const ARRAY_TYPES = {
|
|
@@ -12639,6 +12683,10 @@ const postgresContentDialect = {
|
|
|
12639
12683
|
castParameter: castParameter$2,
|
|
12640
12684
|
fieldEquals: (target, value) => {
|
|
12641
12685
|
if (target.numericString && target.cast) return sql`(${fieldValue$2(target.fieldId, target.cast)} = ${castParameter$2(value, target.cast)})`;
|
|
12686
|
+
if (target.indexed && target.cast) {
|
|
12687
|
+
const expression = fieldValue$2(target.fieldId, target.cast);
|
|
12688
|
+
return sql`(${expression} = ${castParameter$2(value, target.cast)} and ${expression} is not null)`;
|
|
12689
|
+
}
|
|
12642
12690
|
return containment({ [target.fieldId]: target.list ? [value] : value });
|
|
12643
12691
|
},
|
|
12644
12692
|
fieldMissing: (fieldId) => {
|
|
@@ -12651,9 +12699,11 @@ const postgresContentDialect = {
|
|
|
12651
12699
|
sortDirection: (direction) => direction === "desc" ? sql`desc` : sql`asc`,
|
|
12652
12700
|
snapshotCte: ({ name, siteId, modelId, seq }) => sql`with ${sql.id(name)} as (
|
|
12653
12701
|
select pl.entry_id, pl.site_id, pl.model_id, pl.locale, 'published'::text as state, r.data, 0 as version,
|
|
12654
|
-
r.id as revision_id, pl.published_at as updated_at, null::timestamptz as autosaved_at
|
|
12702
|
+
r.id as revision_id, pl.published_at as updated_at, null::timestamptz as autosaved_at,
|
|
12703
|
+
en.created_at as entry_created_at
|
|
12655
12704
|
from publication_log pl
|
|
12656
12705
|
join content_revisions r on r.id = pl.revision_id
|
|
12706
|
+
join entries en on en.id = pl.entry_id
|
|
12657
12707
|
where pl.site_id = ${siteId}::uuid and pl.model_id = ${modelId}
|
|
12658
12708
|
and pl.from_seq <= ${seq}::bigint
|
|
12659
12709
|
and (pl.to_seq is null or pl.to_seq > ${seq}::bigint)
|
|
@@ -12742,9 +12792,11 @@ const DIALECTS = {
|
|
|
12742
12792
|
},
|
|
12743
12793
|
snapshotCte: ({ name, siteId, modelId, seq }) => sql`with ${sql.id(name)} as (
|
|
12744
12794
|
select pl.entry_id, pl.site_id, pl.model_id, pl.locale, 'published' as state, r.data, 0 as version,
|
|
12745
|
-
r.id as revision_id, pl.published_at as updated_at, null as autosaved_at
|
|
12795
|
+
r.id as revision_id, pl.published_at as updated_at, null as autosaved_at,
|
|
12796
|
+
en.created_at as entry_created_at
|
|
12746
12797
|
from publication_log pl
|
|
12747
12798
|
join content_revisions r on r.id = pl.revision_id
|
|
12799
|
+
join entries en on en.id = pl.entry_id
|
|
12748
12800
|
where pl.site_id = ${siteId} and pl.model_id = ${modelId}
|
|
12749
12801
|
and pl.from_seq <= ${seq}
|
|
12750
12802
|
and (pl.to_seq is null or pl.to_seq > ${seq})
|
|
@@ -13377,6 +13429,20 @@ const SITE_QUERY_PARAMETER = "site";
|
|
|
13377
13429
|
* and the header that selects the site. CDNs that key on the URL only need `?site=` instead of the header.
|
|
13378
13430
|
*/
|
|
13379
13431
|
const DELIVERY_VARY = "Authorization, Cookie, Shapio-Site";
|
|
13432
|
+
//#endregion
|
|
13433
|
+
//#region src/services/contentContext.ts
|
|
13434
|
+
/**
|
|
13435
|
+
* The content service context for one call, the same however the call arrived: the site's view of the pinned
|
|
13436
|
+
* schema (plan site-schema: shared definitions and the site's own), scoped to the site.
|
|
13437
|
+
*/
|
|
13438
|
+
const buildContentContext = ({ network, hooks, media, requestId, ip, ...rest }) => ({
|
|
13439
|
+
...rest,
|
|
13440
|
+
snapshot: network.forSite(rest.site.id),
|
|
13441
|
+
hooks: hooks ?? createContentHooks(),
|
|
13442
|
+
...media ? { media } : {},
|
|
13443
|
+
...requestId !== void 0 ? { requestId } : {},
|
|
13444
|
+
...ip !== void 0 ? { ip } : {}
|
|
13445
|
+
});
|
|
13380
13446
|
const NUMERIC_TYPES = /* @__PURE__ */ new Set([
|
|
13381
13447
|
"number",
|
|
13382
13448
|
"integer",
|
|
@@ -13385,6 +13451,12 @@ const NUMERIC_TYPES = /* @__PURE__ */ new Set([
|
|
|
13385
13451
|
]);
|
|
13386
13452
|
/** Numbers stored as JSON strings (exact decimals, big integers). */
|
|
13387
13453
|
const NUMERIC_STRING_TYPES$1 = /* @__PURE__ */ new Set(["decimal", "biginteger"]);
|
|
13454
|
+
/**
|
|
13455
|
+
* Whether a field has a B-tree expression index: filterable or sortable, and not deprecated. The schema
|
|
13456
|
+
* planner builds an index for exactly these fields (`indexStepsOf`), and PostgreSQL equality compares on
|
|
13457
|
+
* the index expression for them (`EqualityTarget.indexed`).
|
|
13458
|
+
*/
|
|
13459
|
+
const hasFieldIndex = (field) => (field.filterable || field.sortable) && !field.deprecated;
|
|
13388
13460
|
const valueCastFor = (type) => {
|
|
13389
13461
|
if (NUMERIC_TYPES.has(type)) return "numeric";
|
|
13390
13462
|
return type === "boolean" ? "boolean" : "text";
|
|
@@ -13408,8 +13480,9 @@ const fieldValueExpression = (fieldId, type, dialect = contentDialect()) => {
|
|
|
13408
13480
|
return dialect.fieldValue(fieldId, valueCastFor(type), NUMERIC_STRING_TYPES$1.has(type));
|
|
13409
13481
|
};
|
|
13410
13482
|
/**
|
|
13411
|
-
* Equality on a top-level field (lists: "contains"). PostgreSQL
|
|
13412
|
-
*
|
|
13483
|
+
* Equality on a top-level field (lists: "contains"). PostgreSQL compares the field's index expression when
|
|
13484
|
+
* the field has one (`EqualityTarget.indexed`) and emits containment, which its GIN index serves, otherwise;
|
|
13485
|
+
* SQLite and MySQL compare the field's index expression.
|
|
13413
13486
|
*/
|
|
13414
13487
|
const fieldEqualsExpression = (target, value, dialect = contentDialect()) => {
|
|
13415
13488
|
assertStableId(target.fieldId, "a field ID");
|
|
@@ -13750,17 +13823,32 @@ const RANGE_SQL = {
|
|
|
13750
13823
|
$ne: sql`<>`
|
|
13751
13824
|
};
|
|
13752
13825
|
const anyOf = (parts) => parts.length === 0 ? sql`false` : sql`(${sql.join(parts, sql` or `)})`;
|
|
13753
|
-
const equalityTarget = ({ field }) =>
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
13826
|
+
const equalityTarget = ({ field }) => {
|
|
13827
|
+
const cast = SCALAR_DATA_TYPES.has(field.type) ? valueCastFor(field.type) : null;
|
|
13828
|
+
const list = isListValued(field);
|
|
13829
|
+
return {
|
|
13830
|
+
fieldId: field.id,
|
|
13831
|
+
cast,
|
|
13832
|
+
list,
|
|
13833
|
+
numericString: NUMERIC_STRING_TYPES.has(field.type),
|
|
13834
|
+
indexed: cast !== null && !list && hasFieldIndex(field)
|
|
13835
|
+
};
|
|
13836
|
+
};
|
|
13837
|
+
/** System filter columns. `createdAt` is the entry's, copied on every head (`h.entry_created_at`). */
|
|
13759
13838
|
const SYSTEM_COLUMNS = {
|
|
13760
13839
|
id: "h.entry_id",
|
|
13761
|
-
createdAt: "
|
|
13840
|
+
createdAt: "h.entry_created_at",
|
|
13762
13841
|
updatedAt: "h.updated_at"
|
|
13763
13842
|
};
|
|
13843
|
+
/** Whether a filter names a column of `entries` (the head query's `e`), so its COUNT must join entries. */
|
|
13844
|
+
const filterReadsEntries = (node) => {
|
|
13845
|
+
switch (node.kind) {
|
|
13846
|
+
case "and":
|
|
13847
|
+
case "or": return node.nodes.some(filterReadsEntries);
|
|
13848
|
+
case "not": return filterReadsEntries(node.node);
|
|
13849
|
+
case "condition": return node.target.kind === "system" && SYSTEM_COLUMNS[node.target.name].startsWith("e.");
|
|
13850
|
+
}
|
|
13851
|
+
};
|
|
13764
13852
|
const systemCondition = (name, operator, value, dialect) => {
|
|
13765
13853
|
const column = sql.ref(SYSTEM_COLUMNS[name]);
|
|
13766
13854
|
const element = name === "id" ? "uuid" : "timestamp";
|
|
@@ -13862,20 +13950,13 @@ const whereOf = (plan, table, dialect) => {
|
|
|
13862
13950
|
return sql.join(conditions, sql` and `);
|
|
13863
13951
|
};
|
|
13864
13952
|
/**
|
|
13865
|
-
* The joined entry's site and model, which always equal the head's. Written out on the row query of a plain
|
|
13866
|
-
* list in `entries` order (the default newest-first sort) so the planner can walk `entries_model_idx` in that
|
|
13867
|
-
* order and stop at the page limit instead of sorting every head of the model. Not added when there are
|
|
13868
|
-
* conditions or the sort is on the head: SQLite then prefers the entries index over a field index that
|
|
13869
|
-
* serves the filter or the sort. Left off the COUNT, which reads every match either way.
|
|
13870
|
-
*/
|
|
13871
|
-
const entryScope = (plan, dialect) => plan.orderedByEntry === true && plan.conditions.length === 0 ? sql` and e.site_id = ${dialect.uuid(plan.siteId)} and e.model_id = ${modelIdLiteral(plan.modelId)}` : sql``;
|
|
13872
|
-
/**
|
|
13873
13953
|
* The COUNT's FROM. Live heads exist only for live entries (deleting an entry removes its heads in the same
|
|
13874
|
-
* transaction; entries soft-deleted for having no head have none), so a
|
|
13875
|
-
*
|
|
13876
|
-
*
|
|
13954
|
+
* transaction; entries soft-deleted for having no head have none; `headLiveEntry.int.test.ts` pins it), so a
|
|
13955
|
+
* count of heads whose conditions name no `e` column needs no join: it is read from the heads' indexes alone
|
|
13956
|
+
* (the model index, or a field index for an indexed filter). A snapshot keeps the join: the publication log
|
|
13957
|
+
* still lists entries deleted since.
|
|
13877
13958
|
*/
|
|
13878
|
-
const countFrom = (plan, table, where, joined) => plan.source.kind === "heads" && plan.conditions.length
|
|
13959
|
+
const countFrom = (plan, table, where, joined) => plan.source.kind === "heads" && !(plan.conditionsReadEntries ?? plan.conditions.length > 0) ? sql`from ${table} h where ${where}` : joined;
|
|
13879
13960
|
const HEAD_COLUMNS$1 = sql`h.entry_id, h.locale, h.data, h.version, h.revision_id, h.updated_at,
|
|
13880
13961
|
h.autosaved_at, e.created_at, e.updated_at as entry_updated_at, e.created_by_admin_id, e.owner_app_user_id`;
|
|
13881
13962
|
/** The parts every statement over a plan's heads shares. */
|
|
@@ -13888,7 +13969,7 @@ const headQueryParts = (plan, dialect) => {
|
|
|
13888
13969
|
const offset = plan.offset ? sql` offset ${plan.offset}` : sql``;
|
|
13889
13970
|
return {
|
|
13890
13971
|
prefix: cte ?? sql``,
|
|
13891
|
-
rows: sql`${from}${
|
|
13972
|
+
rows: sql`${from}${orderBy}${limit}${offset}`,
|
|
13892
13973
|
countFrom: countFrom(plan, table, where, from)
|
|
13893
13974
|
};
|
|
13894
13975
|
};
|
|
@@ -14219,13 +14300,15 @@ const parseContentQuery = (tree, context) => {
|
|
|
14219
14300
|
/**
|
|
14220
14301
|
* ORDER BY for a content query. Field sorts use the same expression as the field's index (so the index can
|
|
14221
14302
|
* serve them); the entry ID is always the final tie-breaker so pages are stable. Without an explicit sort
|
|
14222
|
-
* the model's default sort applies when the caller may read that field, else newest first.
|
|
14223
|
-
*
|
|
14224
|
-
*
|
|
14303
|
+
* the model's default sort applies when the caller may read that field, else newest first. `createdAt` is the
|
|
14304
|
+
* entry's creation time copied on the head (`h.entry_created_at`), so the default order and its tie-breaker
|
|
14305
|
+
* are both head columns, which `entry_heads_entry_order_idx` (site, model, state, `entry_created_at desc`,
|
|
14306
|
+
* `entry_id`) serves: a page is read in index order and the scan stops at the limit (ADR 0001, "Delivery perf
|
|
14307
|
+
* 2").
|
|
14225
14308
|
*/
|
|
14226
14309
|
const SYSTEM_SORT_COLUMNS = {
|
|
14227
14310
|
id: "h.entry_id",
|
|
14228
|
-
createdAt: "
|
|
14311
|
+
createdAt: "h.entry_created_at",
|
|
14229
14312
|
updatedAt: "h.updated_at"
|
|
14230
14313
|
};
|
|
14231
14314
|
const termSql = (term, dialect) => {
|
|
@@ -14252,9 +14335,7 @@ const defaultSortTerms = (model, isReadable) => {
|
|
|
14252
14335
|
direction: "desc"
|
|
14253
14336
|
}];
|
|
14254
14337
|
};
|
|
14255
|
-
|
|
14256
|
-
const leadsWithEntryColumn = (terms) => terms[0]?.target.kind === "system" && SYSTEM_SORT_COLUMNS[terms[0].target.name].startsWith("e.");
|
|
14257
|
-
const compileOrderBy = (terms, dialect = contentDialect()) => [...terms.map((term) => termSql(term, dialect)), sql`e.id asc`];
|
|
14338
|
+
const compileOrderBy = (terms, dialect = contentDialect()) => [...terms.map((term) => termSql(term, dialect)), sql`h.entry_id asc`];
|
|
14258
14339
|
//#endregion
|
|
14259
14340
|
//#region src/content/errors.ts
|
|
14260
14341
|
/** Errors the content services throw; the central error handler renders them. */
|
|
@@ -14524,8 +14605,10 @@ const needsFollowUpReads = (env, model, rows, query, policy) => {
|
|
|
14524
14605
|
if (query.populate.size > 0) return true;
|
|
14525
14606
|
const fields = selectFields(model, policy.readMask, query.fields);
|
|
14526
14607
|
const documents = rows.map((row) => row.data);
|
|
14527
|
-
return env.audience === "delivery" && targetsByModel(model, documents, fields).size > 0 || env
|
|
14608
|
+
return env.audience === "delivery" && targetsByModel(model, documents, fields).size > 0 || needsMediaReads(env, model, rows, fields);
|
|
14528
14609
|
};
|
|
14610
|
+
/** Whether projecting these rows reads asset views (media fields, rich-text images, a default SEO image). */
|
|
14611
|
+
const needsMediaReads = (env, model, rows, fields) => env.media !== void 0 && (mediaIdsOf(model, rows.map((row) => row.data), fields).size > 0 || seoImageIdOf(env, fields) !== void 0);
|
|
14529
14612
|
/** The site's default social image a `?seo=resolved` read needs (when the response carries an SEO field). */
|
|
14530
14613
|
const seoImageIdOf = (env, fields) => env.seo?.imageId && seoFieldsOf(fields).length > 0 ? env.seo.imageId : void 0;
|
|
14531
14614
|
/** A default SEO image is shown only while it is a public image (never through a signed URL). */
|
|
@@ -14808,6 +14891,8 @@ const getAdminEntry = async (context, modelKey, id, locale) => {
|
|
|
14808
14891
|
return buildAdminEntryView(context, model, policy, entry, locale);
|
|
14809
14892
|
};
|
|
14810
14893
|
/** Filters, search and row filter of a parsed query as SQL conditions. */
|
|
14894
|
+
/** Whether `queryConditions` names an `entries` column (`HeadQueryPlan.conditionsReadEntries`). */
|
|
14895
|
+
const queryReadsEntries = (query, policy) => query.filter !== null && filterReadsEntries(query.filter) || query.author !== void 0 || policy.rowFilter !== null;
|
|
14811
14896
|
const queryConditions = (context, query, policy) => {
|
|
14812
14897
|
const rowFilter = compileRowFilter(policy.rowFilter, context.actor);
|
|
14813
14898
|
return [
|
|
@@ -14846,8 +14931,8 @@ const listAdminEntries = async (context, modelKey, rawQuery) => {
|
|
|
14846
14931
|
},
|
|
14847
14932
|
locales: readScopeFor(context.snapshot, model.definition, query.locale, { fallback: true }),
|
|
14848
14933
|
conditions: queryConditions(context, query, policy),
|
|
14934
|
+
conditionsReadEntries: queryReadsEntries(query, policy),
|
|
14849
14935
|
orderBy: compileOrderBy(sort),
|
|
14850
|
-
orderedByEntry: leadsWithEntryColumn(sort),
|
|
14851
14936
|
limit,
|
|
14852
14937
|
offset
|
|
14853
14938
|
});
|
|
@@ -14925,13 +15010,12 @@ const SYSTEM_PRINCIPAL = {
|
|
|
14925
15010
|
kind: "system",
|
|
14926
15011
|
component: EXTENSION_SYSTEM_COMPONENT
|
|
14927
15012
|
};
|
|
14928
|
-
const contentContext = async (environment, site, principal) => ({
|
|
15013
|
+
const contentContext = async (environment, site, principal) => buildContentContext({
|
|
14929
15014
|
db: environment.db,
|
|
14930
|
-
|
|
15015
|
+
network: await environment.registry.getSnapshot(),
|
|
14931
15016
|
permissions: environment.permissions,
|
|
14932
15017
|
actor: principal ?? SYSTEM_PRINCIPAL,
|
|
14933
|
-
site
|
|
14934
|
-
hooks: createContentHooks()
|
|
15018
|
+
site
|
|
14935
15019
|
});
|
|
14936
15020
|
const toReadEntry = (view) => ({
|
|
14937
15021
|
id: view.id,
|
|
@@ -15216,157 +15300,6 @@ const createLoggerOptions = (config) => ({
|
|
|
15216
15300
|
});
|
|
15217
15301
|
const createLogger = (config) => pino(createLoggerOptions(config));
|
|
15218
15302
|
//#endregion
|
|
15219
|
-
//#region src/media/contentDisposition.ts
|
|
15220
|
-
/**
|
|
15221
|
-
* A Content-Disposition value that names the file safely: an ASCII fallback plus the RFC 5987 UTF-8 form,
|
|
15222
|
-
* so quotes, semicolons or line breaks in a filename can never inject header parameters.
|
|
15223
|
-
*/
|
|
15224
|
-
const contentDisposition = (filename, type = "inline") => {
|
|
15225
|
-
return `${type}; filename="${filename.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_")}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
|
|
15226
|
-
};
|
|
15227
|
-
/** Types a browser only ever displays passively: opening one directly cannot run script. */
|
|
15228
|
-
const PASSIVE_INLINE_TYPE = /^(image\/(png|jpeg|gif|webp|avif|bmp|tiff|x-icon|vnd\.microsoft\.icon)|video\/.+|audio\/.+)$/;
|
|
15229
|
-
/**
|
|
15230
|
-
* Content-Disposition to store on an object in a bucket. Public objects can be served straight from the
|
|
15231
|
-
* bucket or a CDN, where Shapio's sandboxing headers are absent; anything that could run as a page there
|
|
15232
|
-
* (SVG, HTML, XML, PDF...) is stored as an attachment, so opening it downloads it. `<img>` tags still show
|
|
15233
|
-
* an SVG, since embedding ignores Content-Disposition.
|
|
15234
|
-
*/
|
|
15235
|
-
const bucketContentDisposition = (contentType) => PASSIVE_INLINE_TYPE.test(contentType.toLowerCase()) ? void 0 : "attachment";
|
|
15236
|
-
//#endregion
|
|
15237
|
-
//#region src/media/errors.ts
|
|
15238
|
-
/** Thrown by adapters when a key has no object, so callers can tell "missing" from "storage is down". */
|
|
15239
|
-
var ObjectNotFoundError = class extends Error {
|
|
15240
|
-
constructor(key, options) {
|
|
15241
|
-
super(`No stored object at ${key}`, options);
|
|
15242
|
-
this.name = "ObjectNotFoundError";
|
|
15243
|
-
}
|
|
15244
|
-
};
|
|
15245
|
-
var InvalidStorageKeyError = class extends Error {
|
|
15246
|
-
constructor(key) {
|
|
15247
|
-
super(`Not a valid media storage key: ${JSON.stringify(key)}`);
|
|
15248
|
-
this.name = "InvalidStorageKeyError";
|
|
15249
|
-
}
|
|
15250
|
-
};
|
|
15251
|
-
//#endregion
|
|
15252
|
-
//#region src/media/s3Adapter.ts
|
|
15253
|
-
/** R2 accepts any region name ("auto" by convention); AWS needs the bucket's real region. */
|
|
15254
|
-
const DEFAULT_REGION = "us-east-1";
|
|
15255
|
-
const isNotFound$2 = (error) => error instanceof S3ServiceException && (error.$metadata.httpStatusCode === 404 || error.name === "NotFound" || error.name === "NoSuchKey");
|
|
15256
|
-
const createS3Client = (config) => new S3Client({
|
|
15257
|
-
region: config.region ?? DEFAULT_REGION,
|
|
15258
|
-
...config.endpoint ? { endpoint: config.endpoint } : {},
|
|
15259
|
-
forcePathStyle: config.forcePathStyle,
|
|
15260
|
-
...config.accessKeyId && config.secretAccessKey ? { credentials: {
|
|
15261
|
-
accessKeyId: config.accessKeyId,
|
|
15262
|
-
secretAccessKey: config.secretAccessKey
|
|
15263
|
-
} } : {},
|
|
15264
|
-
requestChecksumCalculation: "WHEN_REQUIRED",
|
|
15265
|
-
responseChecksumValidation: "WHEN_REQUIRED"
|
|
15266
|
-
});
|
|
15267
|
-
/**
|
|
15268
|
-
* Any S3-compatible service (AWS S3, Cloudflare R2, B2, Spaces, Wasabi, versitygw). Private objects are read
|
|
15269
|
-
* through presigned GET URLs; browsers upload straight to the bucket with a presigned POST whose policy
|
|
15270
|
-
* limits size and content type, so Shapio never streams upload bytes.
|
|
15271
|
-
*/
|
|
15272
|
-
const createS3Adapter = ({ config, publicBaseUrl, client }) => {
|
|
15273
|
-
const s3 = client ?? createS3Client(config);
|
|
15274
|
-
const Bucket = config.bucket;
|
|
15275
|
-
const checkKey = (key) => {
|
|
15276
|
-
if (!isValidStorageKey(key)) throw new InvalidStorageKeyError(key);
|
|
15277
|
-
return key;
|
|
15278
|
-
};
|
|
15279
|
-
const head = async (key) => {
|
|
15280
|
-
try {
|
|
15281
|
-
const result = await s3.send(new HeadObjectCommand({
|
|
15282
|
-
Bucket,
|
|
15283
|
-
Key: checkKey(key)
|
|
15284
|
-
}));
|
|
15285
|
-
return {
|
|
15286
|
-
size: result.ContentLength ?? 0,
|
|
15287
|
-
contentType: result.ContentType
|
|
15288
|
-
};
|
|
15289
|
-
} catch (error) {
|
|
15290
|
-
if (isNotFound$2(error)) return;
|
|
15291
|
-
throw error;
|
|
15292
|
-
}
|
|
15293
|
-
};
|
|
15294
|
-
return {
|
|
15295
|
-
driver: "s3",
|
|
15296
|
-
put: async (key, body, { contentType, contentLength }) => {
|
|
15297
|
-
await s3.send(new PutObjectCommand({
|
|
15298
|
-
Bucket,
|
|
15299
|
-
Key: checkKey(key),
|
|
15300
|
-
Body: body,
|
|
15301
|
-
ContentType: contentType,
|
|
15302
|
-
ContentLength: Buffer.isBuffer(body) ? body.length : contentLength,
|
|
15303
|
-
ContentDisposition: bucketContentDisposition(contentType)
|
|
15304
|
-
}));
|
|
15305
|
-
},
|
|
15306
|
-
getStream: async (key, range) => {
|
|
15307
|
-
try {
|
|
15308
|
-
const result = await s3.send(new GetObjectCommand({
|
|
15309
|
-
Bucket,
|
|
15310
|
-
Key: checkKey(key),
|
|
15311
|
-
...range ? { Range: `bytes=${range.start}-${range.end}` } : {}
|
|
15312
|
-
}));
|
|
15313
|
-
if (!result.Body) throw new ObjectNotFoundError(key);
|
|
15314
|
-
return result.Body;
|
|
15315
|
-
} catch (error) {
|
|
15316
|
-
throw isNotFound$2(error) ? new ObjectNotFoundError(key, { cause: error }) : error;
|
|
15317
|
-
}
|
|
15318
|
-
},
|
|
15319
|
-
head,
|
|
15320
|
-
exists: async (key) => await head(key) !== void 0,
|
|
15321
|
-
delete: async (key) => {
|
|
15322
|
-
await s3.send(new DeleteObjectCommand({
|
|
15323
|
-
Bucket,
|
|
15324
|
-
Key: checkKey(key)
|
|
15325
|
-
}));
|
|
15326
|
-
},
|
|
15327
|
-
signedGetUrl: async (key, { expiresInSeconds, filename, contentType }) => getSignedUrl(s3, new GetObjectCommand({
|
|
15328
|
-
Bucket,
|
|
15329
|
-
Key: checkKey(key),
|
|
15330
|
-
ResponseContentType: contentType,
|
|
15331
|
-
ResponseContentDisposition: contentDisposition(filename)
|
|
15332
|
-
}), { expiresIn: expiresInSeconds }),
|
|
15333
|
-
publicUrl: (key) => publicBaseUrl ? `${publicBaseUrl}/${encodeKeyPath(checkKey(key))}` : void 0,
|
|
15334
|
-
presignedUpload: async ({ key, contentType, maxSizeBytes, expiresInSeconds }) => {
|
|
15335
|
-
const disposition = bucketContentDisposition(contentType);
|
|
15336
|
-
const { url, fields } = await createPresignedPost(s3, {
|
|
15337
|
-
Bucket,
|
|
15338
|
-
Key: checkKey(key),
|
|
15339
|
-
Conditions: [
|
|
15340
|
-
[
|
|
15341
|
-
"content-length-range",
|
|
15342
|
-
1,
|
|
15343
|
-
maxSizeBytes
|
|
15344
|
-
],
|
|
15345
|
-
[
|
|
15346
|
-
"eq",
|
|
15347
|
-
"$Content-Type",
|
|
15348
|
-
contentType
|
|
15349
|
-
],
|
|
15350
|
-
...disposition ? [[
|
|
15351
|
-
"eq",
|
|
15352
|
-
"$Content-Disposition",
|
|
15353
|
-
disposition
|
|
15354
|
-
]] : []
|
|
15355
|
-
],
|
|
15356
|
-
Fields: {
|
|
15357
|
-
"Content-Type": contentType,
|
|
15358
|
-
...disposition ? { "Content-Disposition": disposition } : {}
|
|
15359
|
-
},
|
|
15360
|
-
Expires: expiresInSeconds
|
|
15361
|
-
});
|
|
15362
|
-
return {
|
|
15363
|
-
url,
|
|
15364
|
-
fields
|
|
15365
|
-
};
|
|
15366
|
-
}
|
|
15367
|
-
};
|
|
15368
|
-
};
|
|
15369
|
-
//#endregion
|
|
15370
15303
|
//#region src/media/origins.ts
|
|
15371
15304
|
const SAMPLE_KEY = "public/00000000-0000-0000-0000-000000000000/sample/origin-probe";
|
|
15372
15305
|
const originOf = (url) => {
|
|
@@ -15387,6 +15320,7 @@ const resolveMediaOrigins = async (storage) => {
|
|
|
15387
15320
|
const base = originOf(storage.publicBaseUrl);
|
|
15388
15321
|
if (base) origins.add(base);
|
|
15389
15322
|
if (storage.s3) {
|
|
15323
|
+
const { createS3Adapter } = await import("./s3Adapter-C8AgFVYw.js");
|
|
15390
15324
|
const adapter = createS3Adapter({
|
|
15391
15325
|
config: storage.s3,
|
|
15392
15326
|
publicBaseUrl: void 0
|
|
@@ -16044,7 +15978,42 @@ const memoizePermissions = (base, principal) => {
|
|
|
16044
15978
|
};
|
|
16045
15979
|
};
|
|
16046
15980
|
//#endregion
|
|
15981
|
+
//#region src/permissions/scoped.ts
|
|
15982
|
+
const isLazy = (versions) => "read" in versions;
|
|
15983
|
+
/**
|
|
15984
|
+
* The evaluator at the scope's versions. Inside a transaction before a lazy scope read its versions, the
|
|
15985
|
+
* unbound evaluator checks the versions itself through the transaction, so no pooled connection is asked
|
|
15986
|
+
* for while the transaction holds one.
|
|
15987
|
+
*/
|
|
15988
|
+
const boundEvaluator = async (base, versions, executor) => {
|
|
15989
|
+
if (!isLazy(versions)) return base.atVersions?.(versions) ?? base;
|
|
15990
|
+
if (executor && !versions.isRead()) return base;
|
|
15991
|
+
return base.atVersions?.(await versions.read()) ?? base;
|
|
15992
|
+
};
|
|
15993
|
+
/**
|
|
15994
|
+
* One scope's evaluator (an HTTP request, an in-process delivery call): checked against the scope's durable
|
|
15995
|
+
* versions (ADR 0005: a revoke applies from the next scope, not mid-scope) and memoized per (principal, action,
|
|
15996
|
+
* model) for the scope (`memo.ts`).
|
|
15997
|
+
*/
|
|
15998
|
+
const scopePermissions = (base, principal, versions) => memoizePermissions({
|
|
15999
|
+
evaluate: async (caller, policyRequest, executor) => (await boundEvaluator(base, versions, executor)).evaluate(caller, policyRequest, executor),
|
|
16000
|
+
canPerform: async (caller, action, executor) => (await boundEvaluator(base, versions, executor)).canPerform(caller, action, executor),
|
|
16001
|
+
canPerformOnSite: async (caller, action, siteId, executor) => (await boundEvaluator(base, versions, executor)).canPerformOnSite(caller, action, siteId, executor)
|
|
16002
|
+
}, principal);
|
|
16003
|
+
//#endregion
|
|
16004
|
+
//#region src/constants/delivery.ts
|
|
16005
|
+
/** `system_settings` key of the delivery descriptor the server writes at start (services/deliveryDescriptor.ts). */
|
|
16006
|
+
const DELIVERY_DESCRIPTOR_SETTING = "delivery_descriptor";
|
|
16007
|
+
//#endregion
|
|
16047
16008
|
//#region src/repositories/requestState.ts
|
|
16009
|
+
/** The site a lookup names, joined to the one `system_versions` row (at most one row matches). */
|
|
16010
|
+
const siteJoin = (join, lookup) => {
|
|
16011
|
+
switch (lookup.by) {
|
|
16012
|
+
case "id": return join.on("sites.id", "=", lookup.id);
|
|
16013
|
+
case "key": return join.on("sites.key", "=", lookup.key);
|
|
16014
|
+
case "primary": return join.on("sites.is_primary", "=", true);
|
|
16015
|
+
}
|
|
16016
|
+
};
|
|
16048
16017
|
/**
|
|
16049
16018
|
* Everything a request checks before its work, in one statement: the durable schema and permissions versions
|
|
16050
16019
|
* (`system_versions`, one row) and, when asked, the request's site. Read once per request on the pool, before
|
|
@@ -16059,13 +16028,7 @@ const read = async (lookup, trx = db) => {
|
|
|
16059
16028
|
site: void 0
|
|
16060
16029
|
};
|
|
16061
16030
|
}
|
|
16062
|
-
const row = await trx.selectFrom("system_versions").leftJoin("sites", (join) =>
|
|
16063
|
-
switch (lookup.by) {
|
|
16064
|
-
case "id": return join.on("sites.id", "=", lookup.id);
|
|
16065
|
-
case "key": return join.on("sites.key", "=", lookup.key);
|
|
16066
|
-
case "primary": return join.on("sites.is_primary", "=", true);
|
|
16067
|
-
}
|
|
16068
|
-
}).select([
|
|
16031
|
+
const row = await trx.selectFrom("system_versions").leftJoin("sites", (join) => siteJoin(join, lookup)).select([
|
|
16069
16032
|
"system_versions.schema_version",
|
|
16070
16033
|
"system_versions.permissions_version",
|
|
16071
16034
|
"sites.id as site_id",
|
|
@@ -16108,27 +16071,15 @@ const getRequestState = (request, lookup) => {
|
|
|
16108
16071
|
/** Reads the request's site with its state, or on its own when the state was already read without it. */
|
|
16109
16072
|
const readRequestSite = async (request, lookup) => request.requestState ? findSite(lookup) : (await getRequestState(request, lookup)).site;
|
|
16110
16073
|
/**
|
|
16111
|
-
* The server's evaluator at the request's versions. Inside a transaction before the state was read (no route
|
|
16112
|
-
* does that today), the unbound evaluator checks the versions itself through the transaction, so no pooled
|
|
16113
|
-
* connection is asked for while the transaction holds one.
|
|
16114
|
-
*/
|
|
16115
|
-
const boundPermissions = async (request, executor) => {
|
|
16116
|
-
const base = request.server.permissions;
|
|
16117
|
-
if (executor && !request.requestState) return base;
|
|
16118
|
-
const { versions } = await getRequestState(request);
|
|
16119
|
-
return base.atVersions?.(versions) ?? base;
|
|
16120
|
-
};
|
|
16121
|
-
/**
|
|
16122
16074
|
* The request's evaluator: checked against the request's versions, and memoized per (principal, action,
|
|
16123
16075
|
* model) for the request (`permissions/memo.ts`), for REST as for GraphQL. Create it after site resolution,
|
|
16124
16076
|
* which settles the principal.
|
|
16125
16077
|
*/
|
|
16126
16078
|
const getRequestPermissions = (request) => {
|
|
16127
|
-
request.requestPermissions ??=
|
|
16128
|
-
|
|
16129
|
-
|
|
16130
|
-
|
|
16131
|
-
}, request.principal);
|
|
16079
|
+
request.requestPermissions ??= scopePermissions(request.server.permissions, request.principal, {
|
|
16080
|
+
read: async () => (await getRequestState(request)).versions,
|
|
16081
|
+
isRead: () => request.requestState !== null
|
|
16082
|
+
});
|
|
16132
16083
|
return request.requestPermissions;
|
|
16133
16084
|
};
|
|
16134
16085
|
//#endregion
|
|
@@ -16371,12 +16322,14 @@ const revokeApiToken = async (context, permissions, id) => {
|
|
|
16371
16322
|
});
|
|
16372
16323
|
});
|
|
16373
16324
|
};
|
|
16325
|
+
/** Whether a token with this expiry has expired at `now`. */
|
|
16326
|
+
const isTokenExpired = (expiresAt, now) => expiresAt !== null && expiresAt.getTime() <= now.getTime();
|
|
16374
16327
|
/** The principal for a bearer API token, or undefined when unknown, revoked or expired. */
|
|
16375
|
-
const resolveApiToken = async (value, now = /* @__PURE__ */ new Date()) => {
|
|
16376
|
-
const row = await findLiveByHash$1(hashToken(value));
|
|
16377
|
-
if (!row || row.expires_at
|
|
16328
|
+
const resolveApiToken = async (value, now = /* @__PURE__ */ new Date(), { recordUse = true, executor = db } = {}) => {
|
|
16329
|
+
const row = await findLiveByHash$1(hashToken(value), executor);
|
|
16330
|
+
if (!row || isTokenExpired(row.expires_at, now)) return;
|
|
16378
16331
|
const staleBefore = /* @__PURE__ */ new Date(now.getTime() - LAST_SEEN_WRITE_INTERVAL_MS);
|
|
16379
|
-
if (row.last_used_at === null || row.last_used_at.getTime() < staleBefore.getTime()) await touchLastUsed$1(row.id, now, staleBefore);
|
|
16332
|
+
if (recordUse && (row.last_used_at === null || row.last_used_at.getTime() < staleBefore.getTime())) await touchLastUsed$1(row.id, now, staleBefore, executor);
|
|
16380
16333
|
return {
|
|
16381
16334
|
kind: "token",
|
|
16382
16335
|
tokenId: row.id,
|
|
@@ -17548,7 +17501,7 @@ const editorManifestPlugin = fp(async (app, { manifest }) => {
|
|
|
17548
17501
|
app.decorate("editorManifest", manifest);
|
|
17549
17502
|
}, { name: "shapio-editor-manifest" });
|
|
17550
17503
|
//#endregion
|
|
17551
|
-
//#region src/
|
|
17504
|
+
//#region src/helpers/errorResponse.ts
|
|
17552
17505
|
const STATUS_CODES = {
|
|
17553
17506
|
400: "BAD_REQUEST",
|
|
17554
17507
|
401: "UNAUTHORIZED",
|
|
@@ -17565,6 +17518,10 @@ const STATUS_CODES = {
|
|
|
17565
17518
|
};
|
|
17566
17519
|
/** Errors raised by @fastify/csrf-protection (plugins/csrf.ts). */
|
|
17567
17520
|
const CSRF_ERROR_CODES$1 = /* @__PURE__ */ new Set(["FST_CSRF_INVALID_TOKEN", "FST_CSRF_MISSING_SECRET"]);
|
|
17521
|
+
/**
|
|
17522
|
+
* The one error shape every endpoint returns, `{ error: { code, message, details? } }`, and its status.
|
|
17523
|
+
* Server errors (5xx) never carry their internal text; the caller logs the original error.
|
|
17524
|
+
*/
|
|
17568
17525
|
const toErrorResponse = (error) => {
|
|
17569
17526
|
if (error instanceof AppError) {
|
|
17570
17527
|
const body = { error: {
|
|
@@ -17577,7 +17534,7 @@ const toErrorResponse = (error) => {
|
|
|
17577
17534
|
body
|
|
17578
17535
|
};
|
|
17579
17536
|
}
|
|
17580
|
-
if (CSRF_ERROR_CODES$1.has(error.code)) return {
|
|
17537
|
+
if (error.code !== void 0 && CSRF_ERROR_CODES$1.has(error.code)) return {
|
|
17581
17538
|
statusCode: 403,
|
|
17582
17539
|
body: { error: {
|
|
17583
17540
|
code: "CSRF_INVALID",
|
|
@@ -17612,6 +17569,8 @@ const toErrorResponse = (error) => {
|
|
|
17612
17569
|
} }
|
|
17613
17570
|
};
|
|
17614
17571
|
};
|
|
17572
|
+
//#endregion
|
|
17573
|
+
//#region src/plugins/errorHandler.ts
|
|
17615
17574
|
/** The single error handler: consistent `{ error: { code, message, details? } }` bodies and logging. */
|
|
17616
17575
|
const errorHandlerPlugin = fp(async (app) => {
|
|
17617
17576
|
app.decorateRequest("errorCode", void 0);
|
|
@@ -18181,6 +18140,14 @@ const createMysqlNotificationListener = (options) => {
|
|
|
18181
18140
|
});
|
|
18182
18141
|
};
|
|
18183
18142
|
const connect = async () => {
|
|
18143
|
+
if (closed) return;
|
|
18144
|
+
let mysql;
|
|
18145
|
+
try {
|
|
18146
|
+
mysql = await loadMysql();
|
|
18147
|
+
} catch (error) {
|
|
18148
|
+
log.error({ err: error }, "mysql2 could not be loaded; notifications are off");
|
|
18149
|
+
return;
|
|
18150
|
+
}
|
|
18184
18151
|
if (closed) return;
|
|
18185
18152
|
const current = mysql.createConnection(mysqlPoolOptions(connectionString, 1, options.applicationName ?? "shapio-listen"));
|
|
18186
18153
|
connection = current;
|
|
@@ -18693,7 +18660,8 @@ const getDeliverySite$1 = async (context) => {
|
|
|
18693
18660
|
* delivery-perf). The heads, the page total and the site's publication sequence come from one statement, so
|
|
18694
18661
|
* a read that needs nothing else (no relation, populate or asset follow-up) runs without a transaction.
|
|
18695
18662
|
* Anything that needs a follow-up statement runs in one REPEATABLE READ, read-only transaction, so every
|
|
18696
|
-
* statement of the response sees the moment `meta.snapshot` names
|
|
18663
|
+
* statement of the response sees the moment `meta.snapshot` names, except a live published read whose only
|
|
18664
|
+
* follow-up is relation visibility: that runs on the pool (`readConsistently` explains why it is safe).
|
|
18697
18665
|
*/
|
|
18698
18666
|
const deliveryEnvironment = (context, executor, source, locale, richText, seo) => ({
|
|
18699
18667
|
executor,
|
|
@@ -18715,6 +18683,11 @@ const environmentOf = (read, executor) => deliveryEnvironment(read.context, exec
|
|
|
18715
18683
|
*/
|
|
18716
18684
|
const followsUp = (read) => read.query.populate.size > 0 || selectFields(read.model, read.policy.readMask, read.query.fields).some((field) => field.type === "relation" || field.type === "media" || Boolean(read.seo?.imageId) && isSeoField(field));
|
|
18717
18685
|
/**
|
|
18686
|
+
* A live read of published heads (no `?snapshot`, not a preview or draft read, no extra conditions) without
|
|
18687
|
+
* populate: its only possible follow-up is relation visibility (asset views are checked on the rows).
|
|
18688
|
+
*/
|
|
18689
|
+
const isLiveVisibilityRead = (read) => read.source.kind === "heads" && read.source.state === "published" && read.query.populate.size === 0 && (read.conditions?.length ?? 0) === 0;
|
|
18690
|
+
/**
|
|
18718
18691
|
* Loads the site's SEO defaults when the query asks for resolved SEO. Read before the heads, outside their
|
|
18719
18692
|
* consistent moment: defaults are not part of a snapshot (`?snapshot=N&seo=resolved` uses today's).
|
|
18720
18693
|
*/
|
|
@@ -18729,20 +18702,29 @@ const seqOf = (seq) => {
|
|
|
18729
18702
|
/**
|
|
18730
18703
|
* Runs the plan's statement on the pool when the read cannot need a follow-up, and keeps that result when
|
|
18731
18704
|
* the rows indeed need none; otherwise reads again inside one REPEATABLE READ transaction. `finish` projects
|
|
18732
|
-
* the rows (through the same executor), so its own statements see the same moment.
|
|
18705
|
+
* the rows (through the same executor), so its own statements see the same moment. A live published read
|
|
18706
|
+
* whose only follow-up is relation visibility also stays on the pool (see below).
|
|
18733
18707
|
*/
|
|
18734
18708
|
const readConsistently = async (read, plan, options, finish) => {
|
|
18735
18709
|
const compiled = compileHeadPage(plan, options);
|
|
18736
18710
|
const { db } = read.context;
|
|
18737
|
-
|
|
18711
|
+
const live = isLiveVisibilityRead(read);
|
|
18712
|
+
if (!followsUp(read) || live) {
|
|
18738
18713
|
const env = environmentOf(read, db);
|
|
18739
18714
|
const { rows, meta } = await runHeadPage(compiled.rows, db);
|
|
18715
|
+
const fields = selectFields(read.model, read.policy.readMask, read.query.fields);
|
|
18740
18716
|
if (meta && !needsFollowUpReads(env, read.model, rows, read.query, read.policy)) return finish({
|
|
18741
18717
|
env,
|
|
18742
18718
|
rows,
|
|
18743
18719
|
total: meta.total,
|
|
18744
18720
|
seq: seqOf(meta.seq)
|
|
18745
18721
|
});
|
|
18722
|
+
if (meta && live && !needsMediaReads(env, read.model, rows, fields)) return finish({
|
|
18723
|
+
env,
|
|
18724
|
+
rows,
|
|
18725
|
+
total: meta.total,
|
|
18726
|
+
seq: seqOf(meta.seq)
|
|
18727
|
+
});
|
|
18746
18728
|
}
|
|
18747
18729
|
return withConsistentRead(async (trx) => {
|
|
18748
18730
|
const env = environmentOf(read, trx);
|
|
@@ -18776,8 +18758,8 @@ const readEntryPage = async (unloaded, options = { total: true }) => {
|
|
|
18776
18758
|
source: read.source,
|
|
18777
18759
|
locales: localesOf(read),
|
|
18778
18760
|
conditions: [...queryConditions(context, query, policy), ...read.conditions ?? []],
|
|
18761
|
+
conditionsReadEntries: queryReadsEntries(query, policy) || (read.conditions?.length ?? 0) > 0,
|
|
18779
18762
|
orderBy: compileOrderBy(sort),
|
|
18780
|
-
orderedByEntry: leadsWithEntryColumn(sort),
|
|
18781
18763
|
limit,
|
|
18782
18764
|
offset
|
|
18783
18765
|
};
|
|
@@ -18923,6 +18905,45 @@ const createLoaders = (siteId, content) => {
|
|
|
18923
18905
|
};
|
|
18924
18906
|
};
|
|
18925
18907
|
//#endregion
|
|
18908
|
+
//#region src/schema/codegen/graphql/requestContext.ts
|
|
18909
|
+
/**
|
|
18910
|
+
* The context every resolver of one operation receives, with its own DataLoaders. Carries no HTTP objects, so
|
|
18911
|
+
* an operation can run outside a request.
|
|
18912
|
+
*/
|
|
18913
|
+
const createGraphqlContext = ({ site, permissions, isAdmin, content }) => {
|
|
18914
|
+
let base;
|
|
18915
|
+
const contentAt = async (snapshot) => {
|
|
18916
|
+
base ??= content(snapshot);
|
|
18917
|
+
return {
|
|
18918
|
+
...await base,
|
|
18919
|
+
snapshot,
|
|
18920
|
+
permissions
|
|
18921
|
+
};
|
|
18922
|
+
};
|
|
18923
|
+
return {
|
|
18924
|
+
permissions,
|
|
18925
|
+
isAdmin,
|
|
18926
|
+
site,
|
|
18927
|
+
loaders: createLoaders(() => site.id, contentAt),
|
|
18928
|
+
content: contentAt
|
|
18929
|
+
};
|
|
18930
|
+
};
|
|
18931
|
+
//#endregion
|
|
18932
|
+
//#region src/helpers/siteQuery.ts
|
|
18933
|
+
const isSiteSegment = (segment) => {
|
|
18934
|
+
const key = segment.split("=", 1)[0] ?? "";
|
|
18935
|
+
try {
|
|
18936
|
+
return decodeURIComponent(key.replace(/\+/g, " ")) === SITE_QUERY_PARAMETER;
|
|
18937
|
+
} catch {
|
|
18938
|
+
return false;
|
|
18939
|
+
}
|
|
18940
|
+
};
|
|
18941
|
+
/**
|
|
18942
|
+
* A raw querystring (without `?`) without its `site` parameter: site resolution consumes it, and the content
|
|
18943
|
+
* compiler refuses unknown keys. The rest is left exactly as sent (bracket syntax is the compiler's to parse).
|
|
18944
|
+
*/
|
|
18945
|
+
const withoutSiteParameter = (rawQuery) => rawQuery.split("&").filter((segment) => !isSiteSegment(segment)).join("&");
|
|
18946
|
+
//#endregion
|
|
18926
18947
|
//#region src/controllers/contentContext.ts
|
|
18927
18948
|
/**
|
|
18928
18949
|
* The service context for a content request: the pinned snapshot, the principal and dependencies.
|
|
@@ -18930,9 +18951,9 @@ const createLoaders = (siteId, content) => {
|
|
|
18930
18951
|
* memoized per (principal, action, model), so the read, relation visibility, populate and usage recording
|
|
18931
18952
|
* evaluate each model's policy once.
|
|
18932
18953
|
*/
|
|
18933
|
-
const contentContextFor = async (request) => ({
|
|
18954
|
+
const contentContextFor = async (request) => buildContentContext({
|
|
18934
18955
|
db: request.server.db,
|
|
18935
|
-
|
|
18956
|
+
network: await getRequestNetworkSchema(request),
|
|
18936
18957
|
permissions: getRequestPermissions(request),
|
|
18937
18958
|
actor: request.principal,
|
|
18938
18959
|
site: getRequestSite(request),
|
|
@@ -18944,14 +18965,6 @@ const contentContextFor = async (request) => ({
|
|
|
18944
18965
|
requestId: request.id,
|
|
18945
18966
|
ip: request.ip
|
|
18946
18967
|
});
|
|
18947
|
-
const isSiteSegment = (segment) => {
|
|
18948
|
-
const key = segment.split("=", 1)[0] ?? "";
|
|
18949
|
-
try {
|
|
18950
|
-
return decodeURIComponent(key.replace(/\+/g, " ")) === SITE_QUERY_PARAMETER;
|
|
18951
|
-
} catch {
|
|
18952
|
-
return false;
|
|
18953
|
-
}
|
|
18954
|
-
};
|
|
18955
18968
|
/**
|
|
18956
18969
|
* The raw querystring (bracket syntax is parsed by the content compiler, not Fastify's flat parser), without
|
|
18957
18970
|
* `?site=`: site resolution (plugins/siteResolution.ts) consumed it, and the compiler refuses unknown keys.
|
|
@@ -18959,34 +18972,19 @@ const isSiteSegment = (segment) => {
|
|
|
18959
18972
|
const rawQueryOf = (request) => {
|
|
18960
18973
|
const index = request.url.indexOf("?");
|
|
18961
18974
|
if (index === -1) return "";
|
|
18962
|
-
return request.url.slice(index + 1)
|
|
18975
|
+
return withoutSiteParameter(request.url.slice(index + 1));
|
|
18963
18976
|
};
|
|
18964
18977
|
//#endregion
|
|
18965
18978
|
//#region src/controllers/graphqlContext.ts
|
|
18966
18979
|
/** Admin users and admin-scope API tokens: may read drafts. */
|
|
18967
18980
|
const isAdminPrincipal = (principal) => principal.kind === "admin" || principal.kind === "token" && principal.scope === "admin";
|
|
18968
18981
|
/** The context every resolver of one `/api/graphql` request receives (built after site resolution). */
|
|
18969
|
-
const createGraphqlRequestContext = (request) => {
|
|
18970
|
-
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
return {
|
|
18976
|
-
...await base,
|
|
18977
|
-
snapshot,
|
|
18978
|
-
permissions
|
|
18979
|
-
};
|
|
18980
|
-
};
|
|
18981
|
-
return {
|
|
18982
|
-
request,
|
|
18983
|
-
permissions,
|
|
18984
|
-
isAdmin: isAdminPrincipal(request.principal),
|
|
18985
|
-
site,
|
|
18986
|
-
loaders: createLoaders(() => site.id, content),
|
|
18987
|
-
content
|
|
18988
|
-
};
|
|
18989
|
-
};
|
|
18982
|
+
const createGraphqlRequestContext = (request) => createGraphqlContext({
|
|
18983
|
+
site: getRequestSite(request),
|
|
18984
|
+
permissions: getRequestPermissions(request),
|
|
18985
|
+
isAdmin: isAdminPrincipal(request.principal),
|
|
18986
|
+
content: () => contentContextFor(request)
|
|
18987
|
+
});
|
|
18990
18988
|
//#endregion
|
|
18991
18989
|
//#region src/controllers/graphql.ts
|
|
18992
18990
|
const introspectionDisabled = () => new AppError(403, "INTROSPECTION_DISABLED", "Schema introspection needs an admin session or an API token (or GRAPHQL_PUBLIC_INTROSPECTION=true)");
|
|
@@ -21491,6 +21489,21 @@ const graphqlPlugin = fp(async (app, { config, urls }) => {
|
|
|
21491
21489
|
]
|
|
21492
21490
|
});
|
|
21493
21491
|
//#endregion
|
|
21492
|
+
//#region src/media/errors.ts
|
|
21493
|
+
/** Thrown by adapters when a key has no object, so callers can tell "missing" from "storage is down". */
|
|
21494
|
+
var ObjectNotFoundError = class extends Error {
|
|
21495
|
+
constructor(key, options) {
|
|
21496
|
+
super(`No stored object at ${key}`, options);
|
|
21497
|
+
this.name = "ObjectNotFoundError";
|
|
21498
|
+
}
|
|
21499
|
+
};
|
|
21500
|
+
var InvalidStorageKeyError = class extends Error {
|
|
21501
|
+
constructor(key) {
|
|
21502
|
+
super(`Not a valid media storage key: ${JSON.stringify(key)}`);
|
|
21503
|
+
this.name = "InvalidStorageKeyError";
|
|
21504
|
+
}
|
|
21505
|
+
};
|
|
21506
|
+
//#endregion
|
|
21494
21507
|
//#region src/media/signing.ts
|
|
21495
21508
|
const sign = (secret, purpose, subject, expiresAt) => createHmac("sha256", secret).update(`${purpose}\n${subject}\n${expiresAt}`).digest("base64url");
|
|
21496
21509
|
const safeEqual = (a, b) => {
|
|
@@ -21608,9 +21621,10 @@ var StorageNotConfiguredError = class extends Error {
|
|
|
21608
21621
|
};
|
|
21609
21622
|
/**
|
|
21610
21623
|
* Every adapter this instance can reach. Local disk is always available; S3 whenever its bucket is set,
|
|
21611
|
-
* whatever STORAGE_DRIVER says, so assets keep being served while `shapio media migrate` moves them.
|
|
21624
|
+
* whatever STORAGE_DRIVER says, so assets keep being served while `shapio media migrate` moves them. The S3
|
|
21625
|
+
* adapter (and the AWS SDK) is loaded only then.
|
|
21612
21626
|
*/
|
|
21613
|
-
const createMediaStorage = (config, deps) => {
|
|
21627
|
+
const createMediaStorage = async (config, deps) => {
|
|
21614
21628
|
const adapters = /* @__PURE__ */ new Map();
|
|
21615
21629
|
adapters.set("local", createLocalAdapter({
|
|
21616
21630
|
root: config.mediaPath,
|
|
@@ -21618,10 +21632,13 @@ const createMediaStorage = (config, deps) => {
|
|
|
21618
21632
|
signingSecret: deps.signingSecret,
|
|
21619
21633
|
publicBaseUrl: config.publicBaseUrl
|
|
21620
21634
|
}));
|
|
21621
|
-
if (config.s3)
|
|
21622
|
-
|
|
21623
|
-
|
|
21624
|
-
|
|
21635
|
+
if (config.s3) {
|
|
21636
|
+
const { createS3Adapter } = await import("./s3Adapter-C8AgFVYw.js");
|
|
21637
|
+
adapters.set("s3", createS3Adapter({
|
|
21638
|
+
config: config.s3,
|
|
21639
|
+
publicBaseUrl: config.publicBaseUrl
|
|
21640
|
+
}));
|
|
21641
|
+
}
|
|
21625
21642
|
const get = (driver) => {
|
|
21626
21643
|
const adapter = adapters.get(driver);
|
|
21627
21644
|
if (!adapter) throw new StorageNotConfiguredError(driver);
|
|
@@ -21637,7 +21654,7 @@ const createMediaStorage = (config, deps) => {
|
|
|
21637
21654
|
//#region src/plugins/mediaStorage.ts
|
|
21638
21655
|
/** Exposes the media storage adapters to routes. Registered after the signing secret is resolved. */
|
|
21639
21656
|
const mediaStoragePlugin = fp(async (app, { storage, urls, signingSecret }) => {
|
|
21640
|
-
app.decorate("mediaStorage", createMediaStorage(storage, {
|
|
21657
|
+
app.decorate("mediaStorage", await createMediaStorage(storage, {
|
|
21641
21658
|
urls,
|
|
21642
21659
|
signingSecret
|
|
21643
21660
|
}));
|
|
@@ -24188,13 +24205,15 @@ const summarizePrompt = (input) => ({
|
|
|
24188
24205
|
//#region src/services/assist/summarize.ts
|
|
24189
24206
|
const notSummarizable = (fieldApiKey, reason) => new AppError(400, "ASSIST_FIELD_NOT_SUMMARIZABLE", `"${fieldApiKey}" cannot be summarized into: ${reason}`, { field: fieldApiKey });
|
|
24190
24207
|
/**
|
|
24191
|
-
* The target must be a string or text
|
|
24192
|
-
* canvas field
|
|
24208
|
+
* The target must be a live string or text field (not the title) of a model whose document has a rich-text
|
|
24209
|
+
* canvas field, wherever the field is placed (a property or in the document): the summary is made from
|
|
24210
|
+
* that canvas (plan §I).
|
|
24193
24211
|
*/
|
|
24194
24212
|
const summaryTarget = (definition, fieldApiKey) => {
|
|
24195
24213
|
const layout = effectiveLayout(definition);
|
|
24196
|
-
|
|
24197
|
-
|
|
24214
|
+
if (layout.title?.apiKey === fieldApiKey) throw notSummarizable(fieldApiKey, "it is the title");
|
|
24215
|
+
const field = definition.fields.find((candidate) => candidate.apiKey === fieldApiKey && !candidate.deprecated);
|
|
24216
|
+
if (!field) throw notSummarizable(fieldApiKey, "it is not a field of this model");
|
|
24198
24217
|
if (field.type !== "string" && field.type !== "text") throw notSummarizable(fieldApiKey, "only string and text fields hold summaries");
|
|
24199
24218
|
const canvas = layout.canvas.filter((candidate) => candidate.type === "richtext");
|
|
24200
24219
|
if (canvas.length === 0) throw notSummarizable(fieldApiKey, "the model has no rich-text body to summarize");
|
|
@@ -25551,11 +25570,11 @@ const scopedDefinitionsOf = (definitions) => definitions.map(({ definition, site
|
|
|
25551
25570
|
/** Site IDs to keys, so schema issues found in one site's view can name the site. */
|
|
25552
25571
|
const loadSiteKeys = async (executor) => new Map((await list$10(executor)).map((site) => [site.id, site.key]));
|
|
25553
25572
|
/** The most filterable or sortable fields the database can index, or undefined without a limit. */
|
|
25554
|
-
const maxFieldIndexes = () => isMysql() ?
|
|
25573
|
+
const maxFieldIndexes = () => isMysql() ? 55 : void 0;
|
|
25555
25574
|
//#endregion
|
|
25556
25575
|
//#region src/schema/planner/plan.ts
|
|
25557
25576
|
/** The field indexes a definition needs (filterable or sortable, not deprecated), in the current layout. */
|
|
25558
|
-
const indexStepsOf = (definition) => definition && definition.kind !== "component" ? definition.fields.filter((field) => (field
|
|
25577
|
+
const indexStepsOf = (definition) => definition && definition.kind !== "component" ? definition.fields.filter((field) => hasFieldIndex(field)).map((field) => {
|
|
25559
25578
|
const spec = {
|
|
25560
25579
|
modelId: definition.id,
|
|
25561
25580
|
fieldId: field.id,
|
|
@@ -33433,6 +33452,19 @@ const executeIdempotentDdl = async (db, statement) => {
|
|
|
33433
33452
|
}
|
|
33434
33453
|
};
|
|
33435
33454
|
//#endregion
|
|
33455
|
+
//#region src/db/entryOrderIndex.ts
|
|
33456
|
+
/**
|
|
33457
|
+
* `entry_heads_entry_order_idx` (ADR 0001, "Delivery perf 2"): the default newest-first list order on the
|
|
33458
|
+
* heads. The `20261005120000_entry_heads_entry_created_at` migration builds it on an empty table, else the
|
|
33459
|
+
* `schema.entryOrderIndex` job builds it here, `CONCURRENTLY` (PostgreSQL only: SQLite and MySQL build it in
|
|
33460
|
+
* their migrations).
|
|
33461
|
+
*/
|
|
33462
|
+
const ENTRY_ORDER_INDEX = "entry_heads_entry_order_idx";
|
|
33463
|
+
const createEntryOrderIndexConcurrently = (db) => sql`create index concurrently if not exists ${sql.id(ENTRY_ORDER_INDEX)}
|
|
33464
|
+
on entry_heads (site_id, model_id, state, entry_created_at desc, entry_id)`.execute(db);
|
|
33465
|
+
/** Drops an INVALID index left by an interrupted build, so the next build starts over. */
|
|
33466
|
+
const dropEntryOrderIndexConcurrently = (db) => sql`drop index concurrently if exists ${sql.id(ENTRY_ORDER_INDEX)}`.execute(db);
|
|
33467
|
+
//#endregion
|
|
33436
33468
|
//#region src/db/indexCatalog.ts
|
|
33437
33469
|
/** Whether a relation exists in the current schema (e.g. content tables before package E's migration). */
|
|
33438
33470
|
const tableExists = async (db, table) => {
|
|
@@ -33552,6 +33584,30 @@ const dropFieldIndex = (db, indexName) => withIndexBuildLock(db, async () => {
|
|
|
33552
33584
|
await executeIfAny(db, dropStatisticsStatement(indexName));
|
|
33553
33585
|
}
|
|
33554
33586
|
});
|
|
33587
|
+
/** The job that builds `entry_heads_entry_order_idx` on an instance upgraded with heads present (PostgreSQL). */
|
|
33588
|
+
const ENTRY_ORDER_INDEX_JOB = "schema.entryOrderIndex";
|
|
33589
|
+
/**
|
|
33590
|
+
* Builds the default list order's index `CONCURRENTLY`, under the same lock as field index builds (two
|
|
33591
|
+
* concurrent builds on `entry_heads` deadlock). Idempotent: a valid index is kept, an INVALID one dropped
|
|
33592
|
+
* and rebuilt.
|
|
33593
|
+
*/
|
|
33594
|
+
const buildEntryOrderIndex = (db, log) => withIndexBuildLock(db, async () => {
|
|
33595
|
+
const state = await getIndexState(db, ENTRY_ORDER_INDEX);
|
|
33596
|
+
if (state === "valid") return "exists";
|
|
33597
|
+
if (state === "invalid") {
|
|
33598
|
+
log.warn({ indexName: ENTRY_ORDER_INDEX }, "dropping invalid index left by an earlier build");
|
|
33599
|
+
await dropEntryOrderIndexConcurrently(db);
|
|
33600
|
+
}
|
|
33601
|
+
const startedAt = performance.now();
|
|
33602
|
+
await createEntryOrderIndexConcurrently(db);
|
|
33603
|
+
if (await getIndexState(db, "entry_heads_entry_order_idx") !== "valid") throw new Error(`Index ${ENTRY_ORDER_INDEX} is not valid after building`);
|
|
33604
|
+
await analyzeHeadsStatement().execute(db);
|
|
33605
|
+
log.info({
|
|
33606
|
+
indexName: ENTRY_ORDER_INDEX,
|
|
33607
|
+
durationMs: Math.round(performance.now() - startedAt)
|
|
33608
|
+
}, "entry order index built");
|
|
33609
|
+
return "built";
|
|
33610
|
+
});
|
|
33555
33611
|
//#endregion
|
|
33556
33612
|
//#region src/schema/planner/indexLayout.ts
|
|
33557
33613
|
/**
|
|
@@ -33735,7 +33791,8 @@ const createFollowUpHandler = (deps) => async (context) => {
|
|
|
33735
33791
|
const createSchemaJobHandlers = (deps) => [
|
|
33736
33792
|
[SCHEMA_CHANGE_JOB, createSchemaChangeHandler(deps)],
|
|
33737
33793
|
[SCHEMA_FOLLOW_UP_JOB, createFollowUpHandler(deps)],
|
|
33738
|
-
[FIELD_INDEX_LAYOUT_JOB, createFieldIndexLayoutHandler(deps.db)]
|
|
33794
|
+
[FIELD_INDEX_LAYOUT_JOB, createFieldIndexLayoutHandler(deps.db)],
|
|
33795
|
+
[ENTRY_ORDER_INDEX_JOB, async (context) => ({ outcome: await buildEntryOrderIndex(deps.db, context.log) })]
|
|
33739
33796
|
];
|
|
33740
33797
|
//#endregion
|
|
33741
33798
|
//#region src/publishing/failures.ts
|
|
@@ -38085,6 +38142,7 @@ const insertRevision = (revision, trx = db) => trx.insertInto("content_revisions
|
|
|
38085
38142
|
const insertHead = (head, trx = db) => trx.insertInto("entry_heads").values({
|
|
38086
38143
|
entry_id: head.entryId,
|
|
38087
38144
|
site_id: entrySiteOf(trx, head.entryId),
|
|
38145
|
+
entry_created_at: entryCreatedAtOf(trx, head.entryId),
|
|
38088
38146
|
model_id: head.modelId,
|
|
38089
38147
|
locale: head.locale,
|
|
38090
38148
|
state: head.state,
|
|
@@ -38290,7 +38348,7 @@ const EMPTY_LOCK = {
|
|
|
38290
38348
|
schemaVersion: 0,
|
|
38291
38349
|
definitions: {}
|
|
38292
38350
|
};
|
|
38293
|
-
const BATCH = 200;
|
|
38351
|
+
const BATCH$2 = 200;
|
|
38294
38352
|
const pushLimited = (list, item) => {
|
|
38295
38353
|
if (list.length < 100) list.push(item);
|
|
38296
38354
|
};
|
|
@@ -38437,9 +38495,9 @@ const scanBundle = async (context, input, acc) => {
|
|
|
38437
38495
|
const modelKey = (id) => definitionKeys.get(id) ?? context.snapshot.byId.get(id)?.definition.apiKey;
|
|
38438
38496
|
const knownLocale = (code) => bundleLocales.has(code) || context.snapshot.locales.some((locale) => locale.code === code);
|
|
38439
38497
|
const flush = async (force) => {
|
|
38440
|
-
if (users.length >= BATCH || force && users.length > 0) await planUsers(acc, users.splice(0));
|
|
38441
|
-
if (assets.length >= BATCH || force && assets.length > 0) await planAssets(acc, assets.splice(0));
|
|
38442
|
-
if (entries.length >= BATCH / 2 || force && entries.length > 0) await planEntries(acc, entries.splice(0), modelKey, knownLocale);
|
|
38498
|
+
if (users.length >= BATCH$2 || force && users.length > 0) await planUsers(acc, users.splice(0));
|
|
38499
|
+
if (assets.length >= BATCH$2 || force && assets.length > 0) await planAssets(acc, assets.splice(0));
|
|
38500
|
+
if (entries.length >= BATCH$2 / 2 || force && entries.length > 0) await planEntries(acc, entries.splice(0), modelKey, knownLocale);
|
|
38443
38501
|
};
|
|
38444
38502
|
for await (const { record } of readBundle(input)) {
|
|
38445
38503
|
switch (record.type) {
|
|
@@ -38901,7 +38959,7 @@ const openStoredBundle = (storage, stored) => storage.get(stored.driver).getStre
|
|
|
38901
38959
|
const removeStoredBundle = (storage, stored) => storage.get(stored.driver).delete(stored.key);
|
|
38902
38960
|
//#endregion
|
|
38903
38961
|
//#region src/constants/version.ts
|
|
38904
|
-
const SHAPIO_VERSION = "0.
|
|
38962
|
+
const SHAPIO_VERSION = "0.5.0";
|
|
38905
38963
|
//#endregion
|
|
38906
38964
|
//#region src/repositories/transferExport.ts
|
|
38907
38965
|
/**
|
|
@@ -39693,21 +39751,21 @@ const importContent = async (run, initial) => {
|
|
|
39693
39751
|
});
|
|
39694
39752
|
};
|
|
39695
39753
|
/** Deletes live target entries of the bundle's models that the bundle does not have. */
|
|
39696
|
-
const prune = async (run,
|
|
39754
|
+
const prune = async (run, network) => {
|
|
39697
39755
|
const keep = /* @__PURE__ */ new Set();
|
|
39698
39756
|
const models = /* @__PURE__ */ new Set();
|
|
39699
39757
|
for await (const { record } of readBundle(await open$1(run))) if (record.type === "entry") {
|
|
39700
39758
|
keep.add(record.id);
|
|
39701
39759
|
models.add(record.modelId);
|
|
39702
39760
|
}
|
|
39703
|
-
const context = {
|
|
39761
|
+
const context = buildContentContext({
|
|
39704
39762
|
db: run.deps.db,
|
|
39705
|
-
|
|
39763
|
+
network,
|
|
39706
39764
|
permissions: SYSTEM_PERMISSIONS,
|
|
39707
39765
|
actor: SYSTEM_ACTOR,
|
|
39708
|
-
site: await getSiteRef(run.payload.siteId, run.deps.db)
|
|
39709
|
-
|
|
39710
|
-
};
|
|
39766
|
+
site: await getSiteRef(run.payload.siteId, run.deps.db)
|
|
39767
|
+
});
|
|
39768
|
+
const { snapshot } = context;
|
|
39711
39769
|
let failed = [];
|
|
39712
39770
|
for (let round = 0; round < PRUNE_ROUNDS; round += 1) {
|
|
39713
39771
|
failed = [];
|
|
@@ -39743,7 +39801,7 @@ const runPhase = async (run, phase) => {
|
|
|
39743
39801
|
case "media": return importMedia(run);
|
|
39744
39802
|
case "entries": return importEntryRows(run, await loadSiteSnapshot(run));
|
|
39745
39803
|
case "content": return importContent(run, await loadSiteSnapshot(run));
|
|
39746
|
-
case "prune": return run.payload.prune ? prune(run, await
|
|
39804
|
+
case "prune": return run.payload.prune ? prune(run, await loadSnapshot(run.deps.db)) : void 0;
|
|
39747
39805
|
case "cleanup": return removeStoredBundle(run.deps.storage, run.payload.bundle).catch((error) => run.job.log.warn({
|
|
39748
39806
|
err: describeError(error),
|
|
39749
39807
|
key: run.payload.bundle.key
|
|
@@ -42195,14 +42253,14 @@ const docsRoutes = async (app) => {
|
|
|
42195
42253
|
//#endregion
|
|
42196
42254
|
//#region src/db/migrations/20261001120000_create_platform_tables.ts
|
|
42197
42255
|
var _20261001120000_create_platform_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42198
|
-
down: () => down$
|
|
42199
|
-
up: () => up$
|
|
42256
|
+
down: () => down$34,
|
|
42257
|
+
up: () => up$34
|
|
42200
42258
|
});
|
|
42201
42259
|
/**
|
|
42202
42260
|
* Foundation tables shared by every package: durable versions, the job queue, the transactional outbox
|
|
42203
42261
|
* and the audit log. Statuses are text with CHECK constraints so new states need only a constraint change.
|
|
42204
42262
|
*/
|
|
42205
|
-
const up$
|
|
42263
|
+
const up$34 = async (db) => {
|
|
42206
42264
|
await db.schema.createTable("system_versions").addColumn("id", "boolean", (col) => col.primaryKey().defaultTo(true).check(sql`id`)).addColumn("schema_version", "integer", (col) => col.notNull().defaultTo(0)).addColumn("permissions_version", "integer", (col) => col.notNull().defaultTo(0)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42207
42265
|
await sql`insert into system_versions default values`.execute(db);
|
|
42208
42266
|
await db.schema.createTable("jobs").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("type", "text", (col) => col.notNull()).addColumn("payload", "jsonb", (col) => col.notNull().defaultTo(sql`'{}'::jsonb`)).addColumn("status", "text", (col) => col.notNull().defaultTo("pending").check(sql`status in ('pending', 'running', 'succeeded', 'dead')`)).addColumn("priority", "integer", (col) => col.notNull().defaultTo(0)).addColumn("run_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("attempts", "integer", (col) => col.notNull().defaultTo(0)).addColumn("max_attempts", "integer", (col) => col.notNull().defaultTo(10).check(sql`max_attempts > 0`)).addColumn("idempotency_key", "text").addColumn("locked_by", "text").addColumn("locked_until", "timestamptz").addColumn("checkpoint", "jsonb").addColumn("result", "jsonb").addColumn("last_error", "text").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("finished_at", "timestamptz").execute();
|
|
@@ -42224,7 +42282,7 @@ const up$30 = async (db) => {
|
|
|
42224
42282
|
"occurred_at desc"
|
|
42225
42283
|
]).execute();
|
|
42226
42284
|
};
|
|
42227
|
-
const down$
|
|
42285
|
+
const down$34 = async (db) => {
|
|
42228
42286
|
await db.schema.dropTable("audit_events").execute();
|
|
42229
42287
|
await db.schema.dropTable("outbox_events").execute();
|
|
42230
42288
|
await db.schema.dropTable("jobs").execute();
|
|
@@ -42233,8 +42291,8 @@ const down$30 = async (db) => {
|
|
|
42233
42291
|
//#endregion
|
|
42234
42292
|
//#region src/db/migrations/20261001130000_create_admin_identity.ts
|
|
42235
42293
|
var _20261001130000_create_admin_identity_exports = /* @__PURE__ */ __exportAll({
|
|
42236
|
-
down: () => down$
|
|
42237
|
-
up: () => up$
|
|
42294
|
+
down: () => down$33,
|
|
42295
|
+
up: () => up$33
|
|
42238
42296
|
});
|
|
42239
42297
|
/**
|
|
42240
42298
|
* Admin identity (package B, ADR 0005): admin users, roles and their permission grants, server-side
|
|
@@ -42243,7 +42301,7 @@ var _20261001130000_create_admin_identity_exports = /* @__PURE__ */ __exportAll(
|
|
|
42243
42301
|
*/
|
|
42244
42302
|
const withTimestamps$3 = (table) => table.addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`));
|
|
42245
42303
|
const uuidPrimaryKey$3 = (table) => table.addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`));
|
|
42246
|
-
const up$
|
|
42304
|
+
const up$33 = async (db) => {
|
|
42247
42305
|
await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_users")).addColumn("email", "text", (col) => col.notNull().unique()).addColumn("name", "text", (col) => col.notNull().defaultTo("")).addColumn("password_hash", "text", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull().defaultTo("active").check(sql`status in ('active', 'disabled')`)).addColumn("last_login_at", "timestamptz").addColumn("password_changed_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`))).execute();
|
|
42248
42306
|
await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_roles")).addColumn("key", "text", (col) => col.notNull().unique().check(sql`key ~ '^[a-z][a-z0-9-]{0,62}$'`)).addColumn("name", "text", (col) => col.notNull()).addColumn("description", "text", (col) => col.notNull().defaultTo("")).addColumn("kind", "text", (col) => col.notNull().defaultTo("admin").check(sql`kind in ('admin', 'delivery')`)).addColumn("is_system", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("version", "integer", (col) => col.notNull().defaultTo(1))).execute();
|
|
42249
42307
|
await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_role_permissions")).addColumn("role_id", "uuid", (col) => col.notNull().references("admin_roles.id").onDelete("cascade")).addColumn("action", "text", (col) => col.notNull()).addColumn("model_id", "text").addColumn("condition", "text", (col) => col.check(sql`condition in ('ownedByPrincipal')`)).addColumn("field_ids", sql`text[]`).addUniqueConstraint("admin_role_permissions_grant_uq", [
|
|
@@ -42263,7 +42321,7 @@ const up$29 = async (db) => {
|
|
|
42263
42321
|
await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("api_tokens")).addColumn("name", "text", (col) => col.notNull()).addColumn("token_hash", "text", (col) => col.notNull().unique()).addColumn("token_prefix", "text", (col) => col.notNull()).addColumn("role_id", "uuid", (col) => col.notNull().references("admin_roles.id").onDelete("restrict")).addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("expires_at", "timestamptz").addColumn("last_used_at", "timestamptz").addColumn("revoked_at", "timestamptz")).execute();
|
|
42264
42322
|
await db.schema.createIndex("api_tokens_role_idx").on("api_tokens").column("role_id").execute();
|
|
42265
42323
|
};
|
|
42266
|
-
const down$
|
|
42324
|
+
const down$33 = async (db) => {
|
|
42267
42325
|
await db.schema.dropTable("api_tokens").execute();
|
|
42268
42326
|
await db.schema.dropTable("setup_tokens").execute();
|
|
42269
42327
|
await db.schema.dropTable("password_resets").execute();
|
|
@@ -42277,31 +42335,31 @@ const down$29 = async (db) => {
|
|
|
42277
42335
|
//#endregion
|
|
42278
42336
|
//#region src/db/migrations/20261001130500_create_system_settings.ts
|
|
42279
42337
|
var _20261001130500_create_system_settings_exports = /* @__PURE__ */ __exportAll({
|
|
42280
|
-
down: () => down$
|
|
42281
|
-
up: () => up$
|
|
42338
|
+
down: () => down$32,
|
|
42339
|
+
up: () => up$32
|
|
42282
42340
|
});
|
|
42283
42341
|
/**
|
|
42284
42342
|
* Instance-wide key/value settings that must survive restarts and be shared by every instance, e.g. the
|
|
42285
42343
|
* generated signing secret. General purpose: later packages add keys, not tables.
|
|
42286
42344
|
*/
|
|
42287
|
-
const up$
|
|
42345
|
+
const up$32 = async (db) => {
|
|
42288
42346
|
await db.schema.createTable("system_settings").addColumn("key", "text", (col) => col.primaryKey()).addColumn("value", "text", (col) => col.notNull()).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42289
42347
|
};
|
|
42290
|
-
const down$
|
|
42348
|
+
const down$32 = async (db) => {
|
|
42291
42349
|
await db.schema.dropTable("system_settings").execute();
|
|
42292
42350
|
};
|
|
42293
42351
|
//#endregion
|
|
42294
42352
|
//#region src/db/migrations/20261001140000_create_schema_tables.ts
|
|
42295
42353
|
var _20261001140000_create_schema_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42296
|
-
down: () => down$
|
|
42297
|
-
up: () => up$
|
|
42354
|
+
down: () => down$31,
|
|
42355
|
+
up: () => up$31
|
|
42298
42356
|
});
|
|
42299
42357
|
/**
|
|
42300
42358
|
* Schema registry (package D, ADR 0002): models and components (one table, `kind` discriminator), immutable
|
|
42301
42359
|
* schema revisions, the per-model active pointer, schema change jobs, locales and schema settings.
|
|
42302
42360
|
* Model definitions are data: creating or changing a model never runs DDL (CONTRIBUTING.md rule 1).
|
|
42303
42361
|
*/
|
|
42304
|
-
const up$
|
|
42362
|
+
const up$31 = async (db) => {
|
|
42305
42363
|
await db.schema.createTable("models").addColumn("id", "uuid", (col) => col.primaryKey()).addColumn("kind", "text", (col) => col.notNull().check(sql`kind in ('collection', 'singleton', 'component')`)).addColumn("api_key", "text", (col) => col.notNull()).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("deleted_at", "timestamptz").execute();
|
|
42306
42364
|
await sql`create unique index models_api_key_uq on models (lower(api_key)) where deleted_at is null`.execute(db);
|
|
42307
42365
|
await db.schema.createTable("schema_revisions").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("model_id", "uuid", (col) => col.notNull().references("models.id").onDelete("restrict")).addColumn("version", "integer", (col) => col.notNull().check(sql`version > 0`)).addColumn("definition", "jsonb", (col) => col.notNull()).addColumn("hash", "text", (col) => col.notNull()).addColumn("parent_revision_id", "uuid", (col) => col.references("schema_revisions.id")).addColumn("created_by_type", "text", (col) => col.notNull()).addColumn("created_by_id", "text").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addUniqueConstraint("schema_revisions_model_version_uq", ["model_id", "version"]).execute();
|
|
@@ -42329,7 +42387,7 @@ const up$27 = async (db) => {
|
|
|
42329
42387
|
await db.schema.createTable("schema_settings").addColumn("id", "boolean", (col) => col.primaryKey().defaultTo(true).check(sql`id`)).addColumn("read_only", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("read_only_reason", "text").addColumn("updated_by_type", "text").addColumn("updated_by_id", "text").addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42330
42388
|
await sql`insert into schema_settings default values`.execute(db);
|
|
42331
42389
|
};
|
|
42332
|
-
const down$
|
|
42390
|
+
const down$31 = async (db) => {
|
|
42333
42391
|
await db.schema.dropTable("schema_settings").execute();
|
|
42334
42392
|
await db.schema.dropTable("locales").execute();
|
|
42335
42393
|
await db.schema.dropTable("schema_change_jobs").execute();
|
|
@@ -42341,8 +42399,8 @@ const down$27 = async (db) => {
|
|
|
42341
42399
|
//#endregion
|
|
42342
42400
|
//#region src/db/migrations/20261001150000_create_content_tables.ts
|
|
42343
42401
|
var _20261001150000_create_content_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42344
|
-
down: () => down$
|
|
42345
|
-
up: () => up$
|
|
42402
|
+
down: () => down$30,
|
|
42403
|
+
up: () => up$30
|
|
42346
42404
|
});
|
|
42347
42405
|
/**
|
|
42348
42406
|
* Content storage (package E, ADR 0001): entries, immutable content revisions, mutable heads (one per
|
|
@@ -42350,7 +42408,7 @@ var _20261001150000_create_content_tables_exports = /* @__PURE__ */ __exportAll(
|
|
|
42350
42408
|
* and the publication log behind `?snapshot=N`. User content models never get tables of their own
|
|
42351
42409
|
* (CONTRIBUTING.md rule 1): their values are JSONB keyed by stable field IDs.
|
|
42352
42410
|
*/
|
|
42353
|
-
const up$
|
|
42411
|
+
const up$30 = async (db) => {
|
|
42354
42412
|
await db.schema.createTable("entries").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("model_id", "uuid", (col) => col.notNull().references("models.id").onDelete("restrict")).addColumn("owner_app_user_id", "uuid").addColumn("created_by_admin_id", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("deleted_at", "timestamptz").execute();
|
|
42355
42413
|
await sql`create index entries_model_idx on entries (model_id, created_at) where deleted_at is null`.execute(db);
|
|
42356
42414
|
await sql`create index entries_owner_idx on entries (owner_app_user_id) where owner_app_user_id is not null`.execute(db);
|
|
@@ -42414,7 +42472,7 @@ const up$26 = async (db) => {
|
|
|
42414
42472
|
await sql`create unique index publication_log_open_uq on publication_log (entry_id, locale) where to_seq is null`.execute(db);
|
|
42415
42473
|
await db.schema.createIndex("publication_log_model_seq_idx").on("publication_log").columns(["model_id", "from_seq"]).execute();
|
|
42416
42474
|
};
|
|
42417
|
-
const down$
|
|
42475
|
+
const down$30 = async (db) => {
|
|
42418
42476
|
await db.schema.dropTable("publication_log").execute();
|
|
42419
42477
|
await db.schema.dropTable("publication_state").execute();
|
|
42420
42478
|
await db.schema.dropTable("unique_values").execute();
|
|
@@ -42427,8 +42485,8 @@ const down$26 = async (db) => {
|
|
|
42427
42485
|
//#endregion
|
|
42428
42486
|
//#region src/db/migrations/20261001160000_create_media_tables.ts
|
|
42429
42487
|
var _20261001160000_create_media_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42430
|
-
down: () => down$
|
|
42431
|
-
up: () => up$
|
|
42488
|
+
down: () => down$29,
|
|
42489
|
+
up: () => up$29
|
|
42432
42490
|
});
|
|
42433
42491
|
/**
|
|
42434
42492
|
* Media library (package G): folders, assets, image variants, references from content and upload grants.
|
|
@@ -42439,7 +42497,7 @@ const withTimestamps$2 = (table) => table.addColumn("created_at", "timestamptz",
|
|
|
42439
42497
|
const uuidPrimaryKey$2 = (table) => table.addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`));
|
|
42440
42498
|
const STORAGE_DRIVER_CHECK = sql`storage_driver in ('local', 's3')`;
|
|
42441
42499
|
const VISIBILITY_CHECK = sql`visibility in ('public', 'private')`;
|
|
42442
|
-
const up$
|
|
42500
|
+
const up$29 = async (db) => {
|
|
42443
42501
|
await withTimestamps$2(uuidPrimaryKey$2(db.schema.createTable("media_folders")).addColumn("parent_id", "uuid", (col) => col.references("media_folders.id").onDelete("restrict")).addColumn("name", "text", (col) => col.notNull().check(sql`length(name) between 1 and 255`)).addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("version", "integer", (col) => col.notNull().defaultTo(1))).execute();
|
|
42444
42502
|
await sql`create unique index media_folders_sibling_name_uq on media_folders
|
|
42445
42503
|
(coalesce(parent_id, '00000000-0000-0000-0000-000000000000'::uuid), lower(name))`.execute(db);
|
|
@@ -42468,7 +42526,7 @@ const up$25 = async (db) => {
|
|
|
42468
42526
|
]).execute();
|
|
42469
42527
|
await db.schema.createTable("media_upload_grants").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("asset_id", "uuid", (col) => col.notNull()).addColumn("kind", "text", (col) => col.notNull().check(sql`kind in ('create', 'replace')`)).addColumn("storage_driver", "text", (col) => col.notNull().check(STORAGE_DRIVER_CHECK)).addColumn("storage_key", "text", (col) => col.notNull().unique()).addColumn("original_filename", "text", (col) => col.notNull()).addColumn("declared_mime_type", "text", (col) => col.notNull()).addColumn("max_size_bytes", "bigint", (col) => col.notNull()).addColumn("folder_id", "uuid").addColumn("visibility", "text", (col) => col.notNull().check(VISIBILITY_CHECK)).addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("created_by_principal", "text", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull().defaultTo("pending").check(sql`status in ('pending', 'consumed', 'rejected', 'expired')`)).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("consumed_at", "timestamptz").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42470
42528
|
};
|
|
42471
|
-
const down$
|
|
42529
|
+
const down$29 = async (db) => {
|
|
42472
42530
|
await db.schema.dropTable("media_upload_grants").execute();
|
|
42473
42531
|
await db.schema.dropTable("media_references").execute();
|
|
42474
42532
|
await db.schema.dropTable("media_variants").execute();
|
|
@@ -42478,8 +42536,8 @@ const down$25 = async (db) => {
|
|
|
42478
42536
|
//#endregion
|
|
42479
42537
|
//#region src/db/migrations/20261001170000_create_publishing_tables.ts
|
|
42480
42538
|
var _20261001170000_create_publishing_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42481
|
-
down: () => down$
|
|
42482
|
-
up: () => up$
|
|
42539
|
+
down: () => down$28,
|
|
42540
|
+
up: () => up$28
|
|
42483
42541
|
});
|
|
42484
42542
|
/**
|
|
42485
42543
|
* Publishing (package H): scheduled publications, releases, webhooks and their delivery log, deployment
|
|
@@ -42518,13 +42576,13 @@ const createPreviewTables = async (db) => {
|
|
|
42518
42576
|
await uuidPrimaryKey$1(db.schema.createTable("preview_tokens")).addColumn("token_hash", "text", (col) => col.notNull().unique()).addColumn("token_prefix", "text", (col) => col.notNull()).addColumn("model_id", "uuid", (col) => col.notNull()).addColumn("entry_id", "uuid", (col) => col.references("entries.id").onDelete("cascade")).addColumn("locale", "text").addColumn("connection_id", "uuid", (col) => col.references("deployment_connections.id").onDelete("set null")).addColumn("delivery_role_id", "uuid", (col) => col.references("admin_roles.id").onDelete("set null")).addColumn("created_by", "uuid", (col) => col.notNull().references("admin_users.id").onDelete("cascade")).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("revoked_at", "timestamptz").addColumn("last_used_at", "timestamptz").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42519
42577
|
await db.schema.createIndex("preview_tokens_entry_idx").on("preview_tokens").columns(["entry_id", "created_at desc"]).execute();
|
|
42520
42578
|
};
|
|
42521
|
-
const up$
|
|
42579
|
+
const up$28 = async (db) => {
|
|
42522
42580
|
await createSchedulingTables(db);
|
|
42523
42581
|
await createWebhookTables(db);
|
|
42524
42582
|
await createDeploymentTables(db);
|
|
42525
42583
|
await createPreviewTables(db);
|
|
42526
42584
|
};
|
|
42527
|
-
const down$
|
|
42585
|
+
const down$28 = async (db) => {
|
|
42528
42586
|
await db.schema.dropTable("preview_tokens").execute();
|
|
42529
42587
|
await db.schema.dropTable("deployment_runs").execute();
|
|
42530
42588
|
await db.schema.dropTable("deployment_connections").execute();
|
|
@@ -42537,8 +42595,8 @@ const down$24 = async (db) => {
|
|
|
42537
42595
|
//#endregion
|
|
42538
42596
|
//#region src/db/migrations/20261001180000_create_app_users.ts
|
|
42539
42597
|
var _20261001180000_create_app_users_exports = /* @__PURE__ */ __exportAll({
|
|
42540
|
-
down: () => down$
|
|
42541
|
-
up: () => up$
|
|
42598
|
+
down: () => down$27,
|
|
42599
|
+
up: () => up$27
|
|
42542
42600
|
});
|
|
42543
42601
|
/**
|
|
42544
42602
|
* App users (package I, ADR 0005): the end users of sites and apps built on Shapio, with their own roles
|
|
@@ -42589,7 +42647,7 @@ const createCredentials = async (db) => {
|
|
|
42589
42647
|
}
|
|
42590
42648
|
await db.schema.createTable("app_login_codes").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("app_user_id", "uuid", (col) => col.notNull().references("app_users.id").onDelete("cascade")).addColumn("code_hash", "text", (col) => col.notNull().unique()).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("used_at", "timestamptz").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
|
|
42591
42649
|
};
|
|
42592
|
-
const up$
|
|
42650
|
+
const up$27 = async (db) => {
|
|
42593
42651
|
await createUsers(db);
|
|
42594
42652
|
await createRoles(db);
|
|
42595
42653
|
await createCredentials(db);
|
|
@@ -42599,7 +42657,7 @@ const up$23 = async (db) => {
|
|
|
42599
42657
|
foreign key (owner_app_user_id) references app_users (id) on delete set null
|
|
42600
42658
|
`.execute(db);
|
|
42601
42659
|
};
|
|
42602
|
-
const down$
|
|
42660
|
+
const down$27 = async (db) => {
|
|
42603
42661
|
await sql`alter table entries drop constraint if exists entries_owner_app_user_fk`.execute(db);
|
|
42604
42662
|
await db.schema.dropTable("app_login_codes").execute();
|
|
42605
42663
|
await db.schema.dropTable("app_password_resets").execute();
|
|
@@ -42614,8 +42672,8 @@ const down$23 = async (db) => {
|
|
|
42614
42672
|
//#endregion
|
|
42615
42673
|
//#region src/db/migrations/20261002120000_create_extension_hook_runs.ts
|
|
42616
42674
|
var _20261002120000_create_extension_hook_runs_exports = /* @__PURE__ */ __exportAll({
|
|
42617
|
-
down: () => down$
|
|
42618
|
-
up: () => up$
|
|
42675
|
+
down: () => down$26,
|
|
42676
|
+
up: () => up$26
|
|
42619
42677
|
});
|
|
42620
42678
|
/**
|
|
42621
42679
|
* Extension points (package K, ADR 0009): the ledger of post-commit lifecycle hooks (`afterCreate`,
|
|
@@ -42623,17 +42681,17 @@ var _20261002120000_create_extension_hook_runs_exports = /* @__PURE__ */ __expor
|
|
|
42623
42681
|
* as the hook's own database work, so a hook re-run after a worker crash finds its row and is skipped:
|
|
42624
42682
|
* exactly once for everything the hook does through its transaction.
|
|
42625
42683
|
*/
|
|
42626
|
-
const up$
|
|
42684
|
+
const up$26 = async (db) => {
|
|
42627
42685
|
await db.schema.createTable("extension_hook_runs").addColumn("event_id", "uuid", (col) => col.notNull()).addColumn("hook", "text", (col) => col.notNull()).addColumn("job_id", "uuid").addColumn("completed_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addPrimaryKeyConstraint("extension_hook_runs_pkey", ["event_id", "hook"]).execute();
|
|
42628
42686
|
};
|
|
42629
|
-
const down$
|
|
42687
|
+
const down$26 = async (db) => {
|
|
42630
42688
|
await db.schema.dropTable("extension_hook_runs").execute();
|
|
42631
42689
|
};
|
|
42632
42690
|
//#endregion
|
|
42633
42691
|
//#region src/db/migrations/20261002130000_app_auth_token_version_and_login_code_pkce.ts
|
|
42634
42692
|
var _20261002130000_app_auth_token_version_and_login_code_pkce_exports = /* @__PURE__ */ __exportAll({
|
|
42635
|
-
down: () => down$
|
|
42636
|
-
up: () => up$
|
|
42693
|
+
down: () => down$25,
|
|
42694
|
+
up: () => up$25
|
|
42637
42695
|
});
|
|
42638
42696
|
/**
|
|
42639
42697
|
* App users (ADR 0005):
|
|
@@ -42644,20 +42702,20 @@ var _20261002130000_app_auth_token_version_and_login_code_pkce_exports = /* @__P
|
|
|
42644
42702
|
* sign-in; the one-time login code is only exchanged with the matching verifier. Codes live a minute, so
|
|
42645
42703
|
* any pending ones (issued without a challenge) are dropped rather than given a placeholder.
|
|
42646
42704
|
*/
|
|
42647
|
-
const up$
|
|
42705
|
+
const up$25 = async (db) => {
|
|
42648
42706
|
await db.schema.alterTable("app_users").addColumn("token_version", "integer", (col) => col.notNull().defaultTo(0)).execute();
|
|
42649
42707
|
await sql`delete from app_login_codes`.execute(db);
|
|
42650
42708
|
await db.schema.alterTable("app_login_codes").addColumn("code_challenge", "text", (col) => col.notNull()).execute();
|
|
42651
42709
|
};
|
|
42652
|
-
const down$
|
|
42710
|
+
const down$25 = async (db) => {
|
|
42653
42711
|
await db.schema.alterTable("app_login_codes").dropColumn("code_challenge").execute();
|
|
42654
42712
|
await db.schema.alterTable("app_users").dropColumn("token_version").execute();
|
|
42655
42713
|
};
|
|
42656
42714
|
//#endregion
|
|
42657
42715
|
//#region src/db/migrations/20261002180000_add_publication_log_diff_indexes.ts
|
|
42658
42716
|
var _20261002180000_add_publication_log_diff_indexes_exports = /* @__PURE__ */ __exportAll({
|
|
42659
|
-
down: () => down$
|
|
42660
|
-
up: () => up$
|
|
42717
|
+
down: () => down$24,
|
|
42718
|
+
up: () => up$24
|
|
42661
42719
|
});
|
|
42662
42720
|
/**
|
|
42663
42721
|
* The snapshot diff (repositories/snapshotDiff.ts) finds the (entry, locale) pairs whose live period opened
|
|
@@ -42666,7 +42724,7 @@ var _20261002180000_add_publication_log_diff_indexes_exports = /* @__PURE__ */ _
|
|
|
42666
42724
|
* - `(entry_id, locale, from_seq)`: "the row live at seq N" is the latest row starting at or before N.
|
|
42667
42725
|
* Periods that opened in the range use the existing `(model_id, from_seq)` index.
|
|
42668
42726
|
*/
|
|
42669
|
-
const up$
|
|
42727
|
+
const up$24 = async (db) => {
|
|
42670
42728
|
await sql`create index publication_log_to_seq_idx on publication_log (to_seq) where to_seq is not null`.execute(db);
|
|
42671
42729
|
await db.schema.createIndex("publication_log_entry_locale_from_idx").on("publication_log").columns([
|
|
42672
42730
|
"entry_id",
|
|
@@ -42674,15 +42732,15 @@ const up$20 = async (db) => {
|
|
|
42674
42732
|
"from_seq"
|
|
42675
42733
|
]).execute();
|
|
42676
42734
|
};
|
|
42677
|
-
const down$
|
|
42735
|
+
const down$24 = async (db) => {
|
|
42678
42736
|
await db.schema.dropIndex("publication_log_entry_locale_from_idx").execute();
|
|
42679
42737
|
await db.schema.dropIndex("publication_log_to_seq_idx").execute();
|
|
42680
42738
|
};
|
|
42681
42739
|
//#endregion
|
|
42682
42740
|
//#region src/db/migrations/20261002180100_create_usage_tables.ts
|
|
42683
42741
|
var _20261002180100_create_usage_tables_exports = /* @__PURE__ */ __exportAll({
|
|
42684
|
-
down: () => down$
|
|
42685
|
-
up: () => up$
|
|
42742
|
+
down: () => down$23,
|
|
42743
|
+
up: () => up$23
|
|
42686
42744
|
});
|
|
42687
42745
|
/**
|
|
42688
42746
|
* Field usage from delivery traffic (plan developer-face §5): daily counters written by the in-memory
|
|
@@ -42690,7 +42748,7 @@ var _20261002180100_create_usage_tables_exports = /* @__PURE__ */ __exportAll({
|
|
|
42690
42748
|
* payloads. `principal_key` is `token:<id>`, `app_users` or `anonymous`. Pruned by the retention job
|
|
42691
42749
|
* after USAGE_RETENTION_DAYS.
|
|
42692
42750
|
*/
|
|
42693
|
-
const up$
|
|
42751
|
+
const up$23 = async (db) => {
|
|
42694
42752
|
await db.schema.createTable("field_reads").addColumn("day", "date", (col) => col.notNull()).addColumn("model_id", "uuid", (col) => col.notNull()).addColumn("field_path", "text", (col) => col.notNull()).addColumn("principal_key", "text", (col) => col.notNull()).addColumn("selection", "text", (col) => col.notNull().check(sql`selection in ('explicit', 'implicit')`)).addColumn("reads", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_read_at", "timestamptz", (col) => col.notNull()).addPrimaryKeyConstraint("field_reads_pkey", [
|
|
42695
42753
|
"day",
|
|
42696
42754
|
"model_id",
|
|
@@ -42701,15 +42759,15 @@ const up$19 = async (db) => {
|
|
|
42701
42759
|
await db.schema.createIndex("field_reads_model_day_idx").on("field_reads").columns(["model_id", "day"]).execute();
|
|
42702
42760
|
await db.schema.createTable("token_reads").addColumn("day", "date", (col) => col.notNull()).addColumn("principal_key", "text", (col) => col.notNull()).addColumn("requests", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_snapshot", "bigint").addColumn("last_read_at", "timestamptz", (col) => col.notNull()).addPrimaryKeyConstraint("token_reads_pkey", ["day", "principal_key"]).execute();
|
|
42703
42761
|
};
|
|
42704
|
-
const down$
|
|
42762
|
+
const down$23 = async (db) => {
|
|
42705
42763
|
await db.schema.dropTable("token_reads").execute();
|
|
42706
42764
|
await db.schema.dropTable("field_reads").execute();
|
|
42707
42765
|
};
|
|
42708
42766
|
//#endregion
|
|
42709
42767
|
//#region src/db/migrations/20261002180200_create_change_sets.ts
|
|
42710
42768
|
var _20261002180200_create_change_sets_exports = /* @__PURE__ */ __exportAll({
|
|
42711
|
-
down: () => down$
|
|
42712
|
-
up: () => up$
|
|
42769
|
+
down: () => down$22,
|
|
42770
|
+
up: () => up$22
|
|
42713
42771
|
});
|
|
42714
42772
|
/**
|
|
42715
42773
|
* Change sets (developer-face plan §5, ADR 0010): schema drafts and entry publications that ship as ONE
|
|
@@ -42888,7 +42946,7 @@ const migrateReleases = async (db) => {
|
|
|
42888
42946
|
await sql`drop table release_items`.execute(db);
|
|
42889
42947
|
await sql`drop table releases`.execute(db);
|
|
42890
42948
|
};
|
|
42891
|
-
const up$
|
|
42949
|
+
const up$22 = async (db) => {
|
|
42892
42950
|
await createTables(db);
|
|
42893
42951
|
await linkExecutionRecords(db);
|
|
42894
42952
|
await allowConversionRevisions(db);
|
|
@@ -42960,7 +43018,7 @@ const restoreReleases = async (db) => {
|
|
|
42960
43018
|
await sql`update webhooks set events = array_remove(array_remove(events, 'change_set.shipping'),
|
|
42961
43019
|
'change_set.discarded')`.execute(db);
|
|
42962
43020
|
};
|
|
42963
|
-
const down$
|
|
43021
|
+
const down$22 = async (db) => {
|
|
42964
43022
|
await restoreReleases(db);
|
|
42965
43023
|
await sql`alter table content_revisions drop constraint content_revisions_reason_check`.execute(db);
|
|
42966
43024
|
await sql`alter table content_revisions add constraint content_revisions_reason_check
|
|
@@ -42981,8 +43039,8 @@ const down$18 = async (db) => {
|
|
|
42981
43039
|
//#endregion
|
|
42982
43040
|
//#region src/db/migrations/20261002190000_create_editor_presence_and_content_health.ts
|
|
42983
43041
|
var _20261002190000_create_editor_presence_and_content_health_exports = /* @__PURE__ */ __exportAll({
|
|
42984
|
-
down: () => down$
|
|
42985
|
-
up: () => up$
|
|
43042
|
+
down: () => down$21,
|
|
43043
|
+
up: () => up$21
|
|
42986
43044
|
});
|
|
42987
43045
|
/**
|
|
42988
43046
|
* Editor experience (plan editor-experience §9):
|
|
@@ -42993,7 +43051,7 @@ var _20261002190000_create_editor_presence_and_content_health_exports = /* @__PU
|
|
|
42993
43051
|
* - `content_health_findings`: open and recently resolved findings of the health rules per entry and
|
|
42994
43052
|
* locale (`*` for the entry as a whole). An evaluation upserts what it found and resolves the rest.
|
|
42995
43053
|
*/
|
|
42996
|
-
const up$
|
|
43054
|
+
const up$21 = async (db) => {
|
|
42997
43055
|
await sql`
|
|
42998
43056
|
create unlogged table editor_presence (
|
|
42999
43057
|
entry_id uuid not null,
|
|
@@ -43030,15 +43088,15 @@ const up$17 = async (db) => {
|
|
|
43030
43088
|
await sql`create index content_health_findings_resolved_idx on content_health_findings (resolved_at)
|
|
43031
43089
|
where resolved_at is not null`.execute(db);
|
|
43032
43090
|
};
|
|
43033
|
-
const down$
|
|
43091
|
+
const down$21 = async (db) => {
|
|
43034
43092
|
await sql`drop table content_health_findings`.execute(db);
|
|
43035
43093
|
await sql`drop table editor_presence`.execute(db);
|
|
43036
43094
|
};
|
|
43037
43095
|
//#endregion
|
|
43038
43096
|
//#region src/db/migrations/20261003120000_add_changes_ship_permission.ts
|
|
43039
43097
|
var _20261003120000_add_changes_ship_permission_exports = /* @__PURE__ */ __exportAll({
|
|
43040
|
-
down: () => down$
|
|
43041
|
-
up: () => up$
|
|
43098
|
+
down: () => down$20,
|
|
43099
|
+
up: () => up$20
|
|
43042
43100
|
});
|
|
43043
43101
|
/**
|
|
43044
43102
|
* `changes.ship` (agentic plan §I): shipping a change set (and scheduling a ship) becomes its own global
|
|
@@ -43046,7 +43104,7 @@ var _20261003120000_add_changes_ship_permission_exports = /* @__PURE__ */ __expo
|
|
|
43046
43104
|
* live. Every role that holds `changes.manage` today keeps what it could do: it gets `changes.ship` too.
|
|
43047
43105
|
* The permissions version moves only when grants changed, so running instances reload exactly then.
|
|
43048
43106
|
*/
|
|
43049
|
-
const up$
|
|
43107
|
+
const up$20 = async (db) => {
|
|
43050
43108
|
await sql`
|
|
43051
43109
|
with granted as (
|
|
43052
43110
|
insert into admin_role_permissions (role_id, action, model_id, condition, field_ids)
|
|
@@ -43060,7 +43118,7 @@ const up$16 = async (db) => {
|
|
|
43060
43118
|
where exists (select 1 from granted)
|
|
43061
43119
|
`.execute(db);
|
|
43062
43120
|
};
|
|
43063
|
-
const down$
|
|
43121
|
+
const down$20 = async (db) => {
|
|
43064
43122
|
await sql`
|
|
43065
43123
|
with revoked as (
|
|
43066
43124
|
delete from admin_role_permissions where action = 'changes.ship' returning 1
|
|
@@ -43072,8 +43130,8 @@ const down$16 = async (db) => {
|
|
|
43072
43130
|
//#endregion
|
|
43073
43131
|
//#region src/db/migrations/20261003130000_create_sites.ts
|
|
43074
43132
|
var _20261003130000_create_sites_exports = /* @__PURE__ */ __exportAll({
|
|
43075
|
-
down: () => down$
|
|
43076
|
-
up: () => up$
|
|
43133
|
+
down: () => down$19,
|
|
43134
|
+
up: () => up$19
|
|
43077
43135
|
});
|
|
43078
43136
|
/**
|
|
43079
43137
|
* Sites (plan agentic-ecosystem §H, ADR 0011): one instance hosts many sites with one schema and one login.
|
|
@@ -43261,7 +43319,7 @@ const siteRoleAssignments = async (db) => {
|
|
|
43261
43319
|
`.execute(db);
|
|
43262
43320
|
await sql`alter table admin_invitations drop column role_ids`.execute(db);
|
|
43263
43321
|
};
|
|
43264
|
-
const up$
|
|
43322
|
+
const up$19 = async (db) => {
|
|
43265
43323
|
await createSites(db);
|
|
43266
43324
|
await addSiteColumns(db);
|
|
43267
43325
|
await tieCopiesToEntries(db);
|
|
@@ -43274,7 +43332,7 @@ const assertSingleSite = async (db) => {
|
|
|
43274
43332
|
const { rows } = await sql`select count(*)::text as count from sites`.execute(db);
|
|
43275
43333
|
if (Number(rows[0]?.count ?? 0) > 1) throw new Error("Cannot roll back sites while more than one site exists: merging them would merge their snapshot ledgers and uniqueness and widen role assignments. Delete the other sites first.");
|
|
43276
43334
|
};
|
|
43277
|
-
const down$
|
|
43335
|
+
const down$19 = async (db) => {
|
|
43278
43336
|
await assertSingleSite(db);
|
|
43279
43337
|
await sql`alter table admin_invitations add column role_ids uuid[] not null default '{}'::uuid[]`.execute(db);
|
|
43280
43338
|
await sql`
|
|
@@ -43345,8 +43403,8 @@ const down$15 = async (db) => {
|
|
|
43345
43403
|
//#endregion
|
|
43346
43404
|
//#region src/db/migrations/20261003140200_enqueue_field_index_layout.ts
|
|
43347
43405
|
var _20261003140200_enqueue_field_index_layout_exports = /* @__PURE__ */ __exportAll({
|
|
43348
|
-
down: () => down$
|
|
43349
|
-
up: () => up$
|
|
43406
|
+
down: () => down$18,
|
|
43407
|
+
up: () => up$18
|
|
43350
43408
|
});
|
|
43351
43409
|
/**
|
|
43352
43410
|
* Field index layout v2 (sites plan §H): per-field expression indexes on `entry_heads` lead with `site_id`,
|
|
@@ -43356,12 +43414,12 @@ var _20261003140200_enqueue_field_index_layout_exports = /* @__PURE__ */ __expor
|
|
|
43356
43414
|
* old ones while the instance keeps serving. Nothing to rebuild (no field index yet, e.g. a new install):
|
|
43357
43415
|
* no job.
|
|
43358
43416
|
*/
|
|
43359
|
-
const JOB_TYPE = "schema.fieldIndexLayout";
|
|
43360
|
-
const IDEMPOTENCY_KEY = "schema.fieldIndexLayout:v2";
|
|
43361
|
-
const up$
|
|
43417
|
+
const JOB_TYPE$1 = "schema.fieldIndexLayout";
|
|
43418
|
+
const IDEMPOTENCY_KEY$1 = "schema.fieldIndexLayout:v2";
|
|
43419
|
+
const up$18 = async (db) => {
|
|
43362
43420
|
await sql`
|
|
43363
43421
|
insert into jobs (type, payload, max_attempts, idempotency_key)
|
|
43364
|
-
select ${JOB_TYPE}, '{}'::jsonb, 5, ${IDEMPOTENCY_KEY}
|
|
43422
|
+
select ${JOB_TYPE$1}, '{}'::jsonb, 5, ${IDEMPOTENCY_KEY$1}
|
|
43365
43423
|
where exists (
|
|
43366
43424
|
select 1 from pg_catalog.pg_index i
|
|
43367
43425
|
join pg_catalog.pg_class c on c.oid = i.indexrelid
|
|
@@ -43371,14 +43429,14 @@ const up$14 = async (db) => {
|
|
|
43371
43429
|
`.execute(db);
|
|
43372
43430
|
};
|
|
43373
43431
|
/** The layout job is idempotent; rolling back only forgets it if it has not run yet. */
|
|
43374
|
-
const down$
|
|
43375
|
-
await sql`delete from jobs where idempotency_key = ${IDEMPOTENCY_KEY} and status = 'pending'`.execute(db);
|
|
43432
|
+
const down$18 = async (db) => {
|
|
43433
|
+
await sql`delete from jobs where idempotency_key = ${IDEMPOTENCY_KEY$1} and status = 'pending'`.execute(db);
|
|
43376
43434
|
};
|
|
43377
43435
|
//#endregion
|
|
43378
43436
|
//#region src/db/migrations/20261003140500_change_set_items_site_and_preview_entry.ts
|
|
43379
43437
|
var _20261003140500_change_set_items_site_and_preview_entry_exports = /* @__PURE__ */ __exportAll({
|
|
43380
|
-
down: () => down$
|
|
43381
|
-
up: () => up$
|
|
43438
|
+
down: () => down$17,
|
|
43439
|
+
up: () => up$17
|
|
43382
43440
|
});
|
|
43383
43441
|
/**
|
|
43384
43442
|
* Sites, publishing (plan agentic-ecosystem §H, package G5).
|
|
@@ -43389,7 +43447,7 @@ var _20261003140500_change_set_items_site_and_preview_entry_exports = /* @__PURE
|
|
|
43389
43447
|
* - `preview_tokens.entry_id` becomes required: a preview token previews one entry of one site. Model-wide
|
|
43390
43448
|
* tokens (short-lived by design) are deleted; whoever needs one opens the entry's preview again.
|
|
43391
43449
|
*/
|
|
43392
|
-
const up$
|
|
43450
|
+
const up$17 = async (db) => {
|
|
43393
43451
|
await sql`alter table change_sets add constraint change_sets_id_site_uq unique (id, site_id)`.execute(db);
|
|
43394
43452
|
await sql`alter table change_set_items add column site_id uuid`.execute(db);
|
|
43395
43453
|
await sql`
|
|
@@ -43405,7 +43463,7 @@ const up$13 = async (db) => {
|
|
|
43405
43463
|
await sql`delete from preview_tokens where entry_id is null`.execute(db);
|
|
43406
43464
|
await sql`alter table preview_tokens alter column entry_id set not null`.execute(db);
|
|
43407
43465
|
};
|
|
43408
|
-
const down$
|
|
43466
|
+
const down$17 = async (db) => {
|
|
43409
43467
|
await sql`alter table preview_tokens alter column entry_id drop not null`.execute(db);
|
|
43410
43468
|
await sql`alter table change_set_items drop constraint change_set_items_entry_site_fk`.execute(db);
|
|
43411
43469
|
await sql`alter table change_set_items drop constraint change_set_items_set_site_fk`.execute(db);
|
|
@@ -43415,8 +43473,8 @@ const down$13 = async (db) => {
|
|
|
43415
43473
|
//#endregion
|
|
43416
43474
|
//#region src/db/migrations/20261003150000_widen_deployment_providers.ts
|
|
43417
43475
|
var _20261003150000_widen_deployment_providers_exports = /* @__PURE__ */ __exportAll({
|
|
43418
|
-
down: () => down$
|
|
43419
|
-
up: () => up$
|
|
43476
|
+
down: () => down$16,
|
|
43477
|
+
up: () => up$16
|
|
43420
43478
|
});
|
|
43421
43479
|
/**
|
|
43422
43480
|
* Deploy adapters (plan agentic-ecosystem §B2): deployment connections may use the Vercel and Netlify
|
|
@@ -43431,11 +43489,11 @@ const replaceCheck = async (db, check) => {
|
|
|
43431
43489
|
check (${check}) not valid`.execute(db);
|
|
43432
43490
|
await sql`alter table deployment_connections validate constraint deployment_connections_provider_check`.execute(db);
|
|
43433
43491
|
};
|
|
43434
|
-
const up$
|
|
43492
|
+
const up$16 = async (db) => {
|
|
43435
43493
|
await replaceCheck(db, WIDENED);
|
|
43436
43494
|
};
|
|
43437
43495
|
/** Refuses while Vercel or Netlify connections exist: a down migration never deletes user data. */
|
|
43438
|
-
const down$
|
|
43496
|
+
const down$16 = async (db) => {
|
|
43439
43497
|
const { rows } = await sql`
|
|
43440
43498
|
select count(*)::text as count from deployment_connections where provider in ('vercel', 'netlify')
|
|
43441
43499
|
`.execute(db);
|
|
@@ -43446,8 +43504,8 @@ const down$12 = async (db) => {
|
|
|
43446
43504
|
//#endregion
|
|
43447
43505
|
//#region src/db/migrations/20261003160000_create_assist_runs.ts
|
|
43448
43506
|
var _20261003160000_create_assist_runs_exports = /* @__PURE__ */ __exportAll({
|
|
43449
|
-
down: () => down$
|
|
43450
|
-
up: () => up$
|
|
43507
|
+
down: () => down$15,
|
|
43508
|
+
up: () => up$15
|
|
43451
43509
|
});
|
|
43452
43510
|
/**
|
|
43453
43511
|
* Editor assists (plan agentic-ecosystem §A0, §I): one `assist_runs` row per assist request (who, which
|
|
@@ -43463,14 +43521,14 @@ const replaceSourceCheck = async (db, check) => {
|
|
|
43463
43521
|
await sql`alter table change_sets add constraint change_sets_source_check check (${check}) not valid`.execute(db);
|
|
43464
43522
|
await sql`alter table change_sets validate constraint change_sets_source_check`.execute(db);
|
|
43465
43523
|
};
|
|
43466
|
-
const up$
|
|
43524
|
+
const up$15 = async (db) => {
|
|
43467
43525
|
await db.schema.createTable("assist_runs").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("site_id", "uuid", (col) => col.notNull().references("sites.id").onDelete("cascade")).addColumn("actor_type", "text", (col) => col.notNull()).addColumn("actor_id", "text", (col) => col.notNull()).addColumn("action", "text", (col) => col.notNull().check(ACTIONS)).addColumn("rule", "text").addColumn("provider", "text", (col) => col.notNull()).addColumn("model", "text", (col) => col.notNull()).addColumn("input_tokens", "integer", (col) => col.notNull().defaultTo(0)).addColumn("output_tokens", "integer", (col) => col.notNull().defaultTo(0)).addColumn("duration_ms", "integer", (col) => col.notNull().defaultTo(0)).addColumn("status", "text", (col) => col.notNull().check(sql`status in ('queued', 'running', 'succeeded', 'failed')`)).addColumn("error_code", "text").addColumn("job_id", "uuid", (col) => col.references("jobs.id").onDelete("set null")).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("finished_at", "timestamptz").execute();
|
|
43468
43526
|
await db.schema.createIndex("assist_runs_site_created_idx").on("assist_runs").columns(["site_id", "created_at"]).execute();
|
|
43469
43527
|
await db.schema.createIndex("assist_runs_created_idx").on("assist_runs").column("created_at").execute();
|
|
43470
43528
|
await replaceSourceCheck(db, SOURCES_WIDENED);
|
|
43471
43529
|
};
|
|
43472
43530
|
/** Refuses while change sets opened by assist exist: a down migration never deletes user data. */
|
|
43473
|
-
const down$
|
|
43531
|
+
const down$15 = async (db) => {
|
|
43474
43532
|
const { rows } = await sql`
|
|
43475
43533
|
select count(*)::text as count from change_sets where source = 'assist'
|
|
43476
43534
|
`.execute(db);
|
|
@@ -43482,8 +43540,8 @@ const down$11 = async (db) => {
|
|
|
43482
43540
|
//#endregion
|
|
43483
43541
|
//#region src/db/migrations/20261003170000_audit_events_seq.ts
|
|
43484
43542
|
var _20261003170000_audit_events_seq_exports$2 = /* @__PURE__ */ __exportAll({
|
|
43485
|
-
down: () => down$
|
|
43486
|
-
up: () => up$
|
|
43543
|
+
down: () => down$14,
|
|
43544
|
+
up: () => up$14
|
|
43487
43545
|
});
|
|
43488
43546
|
/**
|
|
43489
43547
|
* `audit_events.seq`: a monotonic tiebreaker after `occurred_at`, so events written in the same instant (one
|
|
@@ -43492,17 +43550,17 @@ var _20261003170000_audit_events_seq_exports$2 = /* @__PURE__ */ __exportAll({
|
|
|
43492
43550
|
* are numbered in the order the rewrite reads them. No index: the audit queries filter and sort by
|
|
43493
43551
|
* `occurred_at` first, and `seq` only breaks ties.
|
|
43494
43552
|
*/
|
|
43495
|
-
const up$
|
|
43553
|
+
const up$14 = async (db) => {
|
|
43496
43554
|
await db.schema.alterTable("audit_events").addColumn("seq", "bigint", (col) => col.notNull().generatedAlwaysAsIdentity()).execute();
|
|
43497
43555
|
};
|
|
43498
|
-
const down$
|
|
43556
|
+
const down$14 = async (db) => {
|
|
43499
43557
|
await db.schema.alterTable("audit_events").dropColumn("seq").execute();
|
|
43500
43558
|
};
|
|
43501
43559
|
//#endregion
|
|
43502
43560
|
//#region src/db/migrations/20261004120000_models_site_scope.ts
|
|
43503
43561
|
var _20261004120000_models_site_scope_exports$2 = /* @__PURE__ */ __exportAll({
|
|
43504
|
-
down: () => down$
|
|
43505
|
-
up: () => up$
|
|
43562
|
+
down: () => down$13,
|
|
43563
|
+
up: () => up$13
|
|
43506
43564
|
});
|
|
43507
43565
|
/**
|
|
43508
43566
|
* Per-site schema (plan site-schema): a definition belongs to one site (`models.site_id`) or is shared by every
|
|
@@ -43515,7 +43573,7 @@ var _20261004120000_models_site_scope_exports$2 = /* @__PURE__ */ __exportAll({
|
|
|
43515
43573
|
* over `lower(api_key)` cannot hold. Share or delete them first; then `down` is lossless.
|
|
43516
43574
|
*/
|
|
43517
43575
|
const NIL_UUID$1 = "00000000-0000-0000-0000-000000000000";
|
|
43518
|
-
const up$
|
|
43576
|
+
const up$13 = async (db) => {
|
|
43519
43577
|
await sql`alter table models add column site_id uuid references sites (id)`.execute(db);
|
|
43520
43578
|
await sql`create index models_site_idx on models (site_id)`.execute(db);
|
|
43521
43579
|
await sql`drop index models_api_key_uq`.execute(db);
|
|
@@ -43525,7 +43583,7 @@ const up$9 = async (db) => {
|
|
|
43525
43583
|
`.execute(db);
|
|
43526
43584
|
await sql`alter table schema_drafts add column shared boolean not null default false`.execute(db);
|
|
43527
43585
|
};
|
|
43528
|
-
const down$
|
|
43586
|
+
const down$13 = async (db) => {
|
|
43529
43587
|
const { rows } = await sql`select count(*)::text as count from models where site_id is not null`.execute(db);
|
|
43530
43588
|
if (Number(rows[0]?.count ?? 0) > 0) throw new Error("Cannot roll back per-site schema while definitions belong to a site: two sites may share an API ID. Share or delete the site definitions first.");
|
|
43531
43589
|
await sql`alter table schema_drafts drop column shared`.execute(db);
|
|
@@ -43537,8 +43595,8 @@ const down$9 = async (db) => {
|
|
|
43537
43595
|
//#endregion
|
|
43538
43596
|
//#region src/db/migrations/20261004130000_site_seo_settings.ts
|
|
43539
43597
|
var _20261004130000_site_seo_settings_exports$2 = /* @__PURE__ */ __exportAll({
|
|
43540
|
-
down: () => down$
|
|
43541
|
-
up: () => up$
|
|
43598
|
+
down: () => down$12,
|
|
43599
|
+
up: () => up$12
|
|
43542
43600
|
});
|
|
43543
43601
|
/**
|
|
43544
43602
|
* SEO defaults per site (plan seo-fields): `sites.seo_defaults` holds the site's default texts per locale, its
|
|
@@ -43549,7 +43607,7 @@ var _20261004130000_site_seo_settings_exports$2 = /* @__PURE__ */ __exportAll({
|
|
|
43549
43607
|
* about what a site publishes) gets it; the built-in owner and admin roles get it from `ensureSystemRoles`.
|
|
43550
43608
|
* The permissions version moves only when grants changed, so running instances reload exactly then.
|
|
43551
43609
|
*/
|
|
43552
|
-
const up$
|
|
43610
|
+
const up$12 = async (db) => {
|
|
43553
43611
|
await sql`alter table sites add column seo_defaults jsonb`.execute(db);
|
|
43554
43612
|
await sql`
|
|
43555
43613
|
with granted as (
|
|
@@ -43564,7 +43622,7 @@ const up$8 = async (db) => {
|
|
|
43564
43622
|
where exists (select 1 from granted)
|
|
43565
43623
|
`.execute(db);
|
|
43566
43624
|
};
|
|
43567
|
-
const down$
|
|
43625
|
+
const down$12 = async (db) => {
|
|
43568
43626
|
await sql`
|
|
43569
43627
|
with revoked as (
|
|
43570
43628
|
delete from admin_role_permissions where action = 'site.settings' returning 1
|
|
@@ -43575,10 +43633,74 @@ const down$8 = async (db) => {
|
|
|
43575
43633
|
await sql`alter table sites drop column seo_defaults`.execute(db);
|
|
43576
43634
|
};
|
|
43577
43635
|
//#endregion
|
|
43636
|
+
//#region src/db/migrations/20261005120000_entry_heads_entry_created_at.ts
|
|
43637
|
+
var _20261005120000_entry_heads_entry_created_at_exports$2 = /* @__PURE__ */ __exportAll({
|
|
43638
|
+
down: () => down$11,
|
|
43639
|
+
up: () => up$11
|
|
43640
|
+
});
|
|
43641
|
+
/**
|
|
43642
|
+
* Delivery perf 2 (ADR 0001 amendment): the entry's `created_at` on each head (`entry_created_at`), so a list
|
|
43643
|
+
* in the default newest-first order walks `entry_heads_entry_order_idx` and stops at the page instead of
|
|
43644
|
+
* joining every matching head to `entries` and sorting them. `entries.created_at` never changes, and every head
|
|
43645
|
+
* insert copies it (repositories `entryHeads.insert`, `transferImport.insertHead`).
|
|
43646
|
+
*
|
|
43647
|
+
* The column is added nullable (a catalog-only change) and backfilled here in batches. The index is built
|
|
43648
|
+
* here on an empty table (a new install). With heads present it is built `CONCURRENTLY`, which cannot run
|
|
43649
|
+
* in a migration's transaction, by the `schema.entryOrderIndex` job this migration enqueues
|
|
43650
|
+
* (schema/planner/indexes.ts), so writes continue while it builds; queries are correct without it.
|
|
43651
|
+
*/
|
|
43652
|
+
const INDEX$2 = "entry_heads_entry_order_idx";
|
|
43653
|
+
const JOB_TYPE = "schema.entryOrderIndex";
|
|
43654
|
+
const IDEMPOTENCY_KEY = "schema.entryOrderIndex:v1";
|
|
43655
|
+
const BATCH$1 = 5e3;
|
|
43656
|
+
const up$11 = async (db) => {
|
|
43657
|
+
await sql`alter table entry_heads add column entry_created_at timestamptz`.execute(db);
|
|
43658
|
+
for (;;) {
|
|
43659
|
+
const result = await sql`
|
|
43660
|
+
update entry_heads h set entry_created_at = e.created_at
|
|
43661
|
+
from entries e
|
|
43662
|
+
where e.id = h.entry_id and (h.entry_id, h.locale, h.state) in (
|
|
43663
|
+
select entry_id, locale, state from entry_heads where entry_created_at is null limit ${BATCH$1}
|
|
43664
|
+
)
|
|
43665
|
+
`.execute(db);
|
|
43666
|
+
if (Number(result.numAffectedRows ?? 0n) < BATCH$1) break;
|
|
43667
|
+
}
|
|
43668
|
+
const { rows } = await sql`select exists (select 1 from entry_heads) as present`.execute(db);
|
|
43669
|
+
if (rows[0]?.present) {
|
|
43670
|
+
await sql`
|
|
43671
|
+
insert into jobs (type, payload, max_attempts, idempotency_key)
|
|
43672
|
+
values (${JOB_TYPE}, '{}'::jsonb, 5, ${IDEMPOTENCY_KEY})
|
|
43673
|
+
on conflict do nothing
|
|
43674
|
+
`.execute(db);
|
|
43675
|
+
return;
|
|
43676
|
+
}
|
|
43677
|
+
await sql`
|
|
43678
|
+
create index ${sql.id(INDEX$2)} on entry_heads (site_id, model_id, state, entry_created_at desc, entry_id)
|
|
43679
|
+
`.execute(db);
|
|
43680
|
+
};
|
|
43681
|
+
const down$11 = async (db) => {
|
|
43682
|
+
await sql`delete from jobs where idempotency_key = ${IDEMPOTENCY_KEY} and status = 'pending'`.execute(db);
|
|
43683
|
+
await sql`drop index if exists ${sql.id(INDEX$2)}`.execute(db);
|
|
43684
|
+
await sql`alter table entry_heads drop column entry_created_at`.execute(db);
|
|
43685
|
+
};
|
|
43686
|
+
//#endregion
|
|
43687
|
+
//#region src/db/migrations/20261006120000_system_versions_release.ts
|
|
43688
|
+
var _20261006120000_system_versions_release_exports = /* @__PURE__ */ __exportAll({
|
|
43689
|
+
dialectNeutral: () => true,
|
|
43690
|
+
down: () => down$10,
|
|
43691
|
+
up: () => up$10
|
|
43692
|
+
});
|
|
43693
|
+
const up$10 = async (db) => {
|
|
43694
|
+
await db.schema.alterTable("system_versions").addColumn("release", "text").execute();
|
|
43695
|
+
};
|
|
43696
|
+
const down$10 = async (db) => {
|
|
43697
|
+
await db.schema.alterTable("system_versions").dropColumn("release").execute();
|
|
43698
|
+
};
|
|
43699
|
+
//#endregion
|
|
43578
43700
|
//#region src/db/migrations/mysql/0001_baseline.ts
|
|
43579
43701
|
var _0001_baseline_exports$1 = /* @__PURE__ */ __exportAll({
|
|
43580
|
-
down: () => down$
|
|
43581
|
-
up: () => up$
|
|
43702
|
+
down: () => down$9,
|
|
43703
|
+
up: () => up$9
|
|
43582
43704
|
});
|
|
43583
43705
|
/**
|
|
43584
43706
|
* MySQL baseline: Shapio's whole system schema as of PostgreSQL migration
|
|
@@ -44761,13 +44883,13 @@ const withoutForeignKeyChecks = async (db, statements) => db.connection().execut
|
|
|
44761
44883
|
await sql`set foreign_key_checks = 1`.execute(connection);
|
|
44762
44884
|
}
|
|
44763
44885
|
});
|
|
44764
|
-
const up$
|
|
44765
|
-
const down$
|
|
44886
|
+
const up$9 = async (db) => withoutForeignKeyChecks(db, STATEMENTS$1);
|
|
44887
|
+
const down$9 = async (db) => withoutForeignKeyChecks(db, [...TABLES$1].reverse().map((table) => `drop table if exists \`${table}\``));
|
|
44766
44888
|
//#endregion
|
|
44767
44889
|
//#region src/db/migrations/mysql/20261003170000_audit_events_seq.ts
|
|
44768
44890
|
var _20261003170000_audit_events_seq_exports$1 = /* @__PURE__ */ __exportAll({
|
|
44769
|
-
down: () => down$
|
|
44770
|
-
up: () => up$
|
|
44891
|
+
down: () => down$8,
|
|
44892
|
+
up: () => up$8
|
|
44771
44893
|
});
|
|
44772
44894
|
/**
|
|
44773
44895
|
* MySQL twin of `../20261003170000_audit_events_seq.ts`: `seq` is `AUTO_INCREMENT`, which InnoDB only allows
|
|
@@ -44775,26 +44897,26 @@ var _20261003170000_audit_events_seq_exports$1 = /* @__PURE__ */ __exportAll({
|
|
|
44775
44897
|
* tiebreaker). Existing rows are numbered in primary-key order. `db/mysql/tables.ts` lists `seq` as the
|
|
44776
44898
|
* table's identity, so an insert's emulated RETURNING finds its row by `LAST_INSERT_ID()`.
|
|
44777
44899
|
*/
|
|
44778
|
-
const up$
|
|
44900
|
+
const up$8 = async (db) => {
|
|
44779
44901
|
await sql`alter table \`audit_events\`
|
|
44780
44902
|
add column \`seq\` bigint not null auto_increment,
|
|
44781
44903
|
add unique key \`audit_events_seq_key\` (\`seq\`)`.execute(db);
|
|
44782
44904
|
};
|
|
44783
|
-
const down$
|
|
44905
|
+
const down$8 = async (db) => {
|
|
44784
44906
|
await sql`alter table \`audit_events\` drop column \`seq\``.execute(db);
|
|
44785
44907
|
};
|
|
44786
44908
|
//#endregion
|
|
44787
44909
|
//#region src/db/migrations/mysql/20261004120000_models_site_scope.ts
|
|
44788
44910
|
var _20261004120000_models_site_scope_exports$1 = /* @__PURE__ */ __exportAll({
|
|
44789
|
-
down: () => down$
|
|
44790
|
-
up: () => up$
|
|
44911
|
+
down: () => down$7,
|
|
44912
|
+
up: () => up$7
|
|
44791
44913
|
});
|
|
44792
44914
|
/**
|
|
44793
44915
|
* MySQL twin of `../20261004120000_models_site_scope.ts`. The per-scope API ID key uses functional key parts,
|
|
44794
44916
|
* like the baseline: `coalesce(site_id, '')` for the scope (MySQL has no `NULLS NOT DISTINCT`) and the
|
|
44795
44917
|
* case-folded API ID of live rows only (a deleted row's part is NULL, and NULLs never collide).
|
|
44796
44918
|
*/
|
|
44797
|
-
const up$
|
|
44919
|
+
const up$7 = async (db) => {
|
|
44798
44920
|
await sql`alter table \`models\` add column \`site_id\` char(36) character set ascii collate ascii_general_ci`.execute(db);
|
|
44799
44921
|
await sql`create index \`models_site_idx\` on \`models\` (\`site_id\`)`.execute(db);
|
|
44800
44922
|
await sql`alter table \`models\` add constraint \`models_site_id_fkey\` foreign key (\`site_id\`) references \`sites\` (\`id\`)`.execute(db);
|
|
@@ -44803,7 +44925,7 @@ const up$5 = async (db) => {
|
|
|
44803
44925
|
((coalesce(\`site_id\`, '')), (case when (\`deleted_at\` IS NULL) then lower(\`api_key\`) end))`.execute(db);
|
|
44804
44926
|
await sql`alter table \`schema_drafts\` add column \`shared\` tinyint(1) not null default 0`.execute(db);
|
|
44805
44927
|
};
|
|
44806
|
-
const down$
|
|
44928
|
+
const down$7 = async (db) => {
|
|
44807
44929
|
const { rows } = await sql`select count(*) as count from \`models\` where \`site_id\` is not null`.execute(db);
|
|
44808
44930
|
if (Number(rows[0]?.count ?? 0) > 0) throw new Error("Cannot roll back per-site schema while definitions belong to a site: two sites may share an API ID. Share or delete the site definitions first.");
|
|
44809
44931
|
await sql`alter table \`schema_drafts\` drop column \`shared\``.execute(db);
|
|
@@ -44817,8 +44939,8 @@ const down$5 = async (db) => {
|
|
|
44817
44939
|
//#endregion
|
|
44818
44940
|
//#region src/db/migrations/mysql/20261004130000_site_seo_settings.ts
|
|
44819
44941
|
var _20261004130000_site_seo_settings_exports$1 = /* @__PURE__ */ __exportAll({
|
|
44820
|
-
down: () => down$
|
|
44821
|
-
up: () => up$
|
|
44942
|
+
down: () => down$6,
|
|
44943
|
+
up: () => up$6
|
|
44822
44944
|
});
|
|
44823
44945
|
/**
|
|
44824
44946
|
* MySQL twin of `../20261004130000_site_seo_settings.ts`. `insert ignore` stands in for `on conflict do
|
|
@@ -44826,7 +44948,7 @@ var _20261004130000_site_seo_settings_exports$1 = /* @__PURE__ */ __exportAll({
|
|
|
44826
44948
|
*/
|
|
44827
44949
|
const bumpPermissionsVersion$1 = (db) => sql`update \`system_versions\` set \`permissions_version\` = \`permissions_version\` + 1,
|
|
44828
44950
|
\`updated_at\` = current_timestamp(6)`.execute(db);
|
|
44829
|
-
const up$
|
|
44951
|
+
const up$6 = async (db) => {
|
|
44830
44952
|
await sql`alter table \`sites\` add column \`seo_defaults\` json`.execute(db);
|
|
44831
44953
|
const granted = await sql`
|
|
44832
44954
|
insert ignore into \`admin_role_permissions\` (\`id\`, \`role_id\`, \`action\`, \`model_id\`, \`condition\`, \`field_ids\`)
|
|
@@ -44836,24 +44958,54 @@ const up$4 = async (db) => {
|
|
|
44836
44958
|
`.execute(db);
|
|
44837
44959
|
if (Number(granted.numAffectedRows ?? 0) > 0) await bumpPermissionsVersion$1(db);
|
|
44838
44960
|
};
|
|
44839
|
-
const down$
|
|
44961
|
+
const down$6 = async (db) => {
|
|
44840
44962
|
const revoked = await sql`delete from \`admin_role_permissions\` where \`action\` = 'site.settings'`.execute(db);
|
|
44841
44963
|
if (Number(revoked.numAffectedRows ?? 0) > 0) await bumpPermissionsVersion$1(db);
|
|
44842
44964
|
await sql`alter table \`sites\` drop column \`seo_defaults\``.execute(db);
|
|
44843
44965
|
};
|
|
44844
44966
|
//#endregion
|
|
44967
|
+
//#region src/db/migrations/mysql/20261005120000_entry_heads_entry_created_at.ts
|
|
44968
|
+
var _20261005120000_entry_heads_entry_created_at_exports$1 = /* @__PURE__ */ __exportAll({
|
|
44969
|
+
down: () => down$5,
|
|
44970
|
+
up: () => up$5
|
|
44971
|
+
});
|
|
44972
|
+
/**
|
|
44973
|
+
* MySQL twin of `../20261005120000_entry_heads_entry_created_at.ts`. The column is added `ALGORITHM=INSTANT`
|
|
44974
|
+
* and backfilled in batches; the index is built online (`ALGORITHM=INPLACE, LOCK=NONE`), so writes continue.
|
|
44975
|
+
* It is one more index of `entry_heads`, which is why `MYSQL_MAX_FIELD_INDEXES` (db/limits.ts) went down by one.
|
|
44976
|
+
*/
|
|
44977
|
+
const INDEX$1 = "entry_heads_entry_order_idx";
|
|
44978
|
+
const BATCH = 5e3;
|
|
44979
|
+
const up$5 = async (db) => {
|
|
44980
|
+
await sql`alter table \`entry_heads\` add column \`entry_created_at\` datetime(6), algorithm=instant`.execute(db);
|
|
44981
|
+
for (;;) {
|
|
44982
|
+
const result = await sql`update \`entry_heads\` set \`entry_created_at\` = (
|
|
44983
|
+
select e.\`created_at\` from \`entries\` e where e.\`id\` = \`entry_heads\`.\`entry_id\`
|
|
44984
|
+
) where \`entry_created_at\` is null limit ${sql.lit(BATCH)}`.execute(db);
|
|
44985
|
+
if (Number(result.numAffectedRows ?? 0n) < BATCH) break;
|
|
44986
|
+
}
|
|
44987
|
+
await sql`create index ${sql.id(INDEX$1)} on \`entry_heads\`
|
|
44988
|
+
(\`site_id\`, \`model_id\`, \`state\`, \`entry_created_at\` desc, \`entry_id\`) algorithm=inplace lock=none`.execute(db);
|
|
44989
|
+
};
|
|
44990
|
+
const down$5 = async (db) => {
|
|
44991
|
+
await sql`drop index ${sql.id(INDEX$1)} on \`entry_heads\``.execute(db);
|
|
44992
|
+
await sql`alter table \`entry_heads\` drop column \`entry_created_at\``.execute(db);
|
|
44993
|
+
};
|
|
44994
|
+
//#endregion
|
|
44845
44995
|
//#region src/db/migrations/mysql/index.ts
|
|
44846
44996
|
const MYSQL_MIGRATIONS = {
|
|
44847
44997
|
"0001_baseline": _0001_baseline_exports$1,
|
|
44848
44998
|
"20261003170000_audit_events_seq": _20261003170000_audit_events_seq_exports$1,
|
|
44849
44999
|
"20261004120000_models_site_scope": _20261004120000_models_site_scope_exports$1,
|
|
44850
|
-
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports$1
|
|
45000
|
+
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports$1,
|
|
45001
|
+
"20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports$1,
|
|
45002
|
+
"20261006120000_system_versions_release": _20261006120000_system_versions_release_exports
|
|
44851
45003
|
};
|
|
44852
45004
|
//#endregion
|
|
44853
45005
|
//#region src/db/migrations/sqlite/0001_baseline.ts
|
|
44854
45006
|
var _0001_baseline_exports = /* @__PURE__ */ __exportAll({
|
|
44855
|
-
down: () => down$
|
|
44856
|
-
up: () => up$
|
|
45007
|
+
down: () => down$4,
|
|
45008
|
+
up: () => up$4
|
|
44857
45009
|
});
|
|
44858
45010
|
/**
|
|
44859
45011
|
* SQLite baseline: Shapio's whole system schema as of PostgreSQL migration
|
|
@@ -45998,18 +46150,18 @@ const TABLES = [
|
|
|
45998
46150
|
"assist_runs",
|
|
45999
46151
|
"sequences"
|
|
46000
46152
|
];
|
|
46001
|
-
const up$
|
|
46153
|
+
const up$4 = async (db) => {
|
|
46002
46154
|
for (const statement of STATEMENTS) await sql.raw(statement).execute(db);
|
|
46003
46155
|
};
|
|
46004
|
-
const down$
|
|
46156
|
+
const down$4 = async (db) => {
|
|
46005
46157
|
await sql`pragma defer_foreign_keys = on`.execute(db);
|
|
46006
46158
|
for (const table of [...TABLES].reverse()) await sql.raw(`drop table if exists "${table}"`).execute(db);
|
|
46007
46159
|
};
|
|
46008
46160
|
//#endregion
|
|
46009
46161
|
//#region src/db/migrations/sqlite/20261003170000_audit_events_seq.ts
|
|
46010
46162
|
var _20261003170000_audit_events_seq_exports = /* @__PURE__ */ __exportAll({
|
|
46011
|
-
down: () => down$
|
|
46012
|
-
up: () => up$
|
|
46163
|
+
down: () => down$3,
|
|
46164
|
+
up: () => up$3
|
|
46013
46165
|
});
|
|
46014
46166
|
/**
|
|
46015
46167
|
* SQLite twin of `../20261003170000_audit_events_seq.ts`. SQLite cannot add a column whose default calls a
|
|
@@ -46036,7 +46188,7 @@ const INDEXES = [
|
|
|
46036
46188
|
`create index "audit_events_occurred_at_idx" on "audit_events" (occurred_at DESC)`,
|
|
46037
46189
|
`create index "audit_events_target_idx" on "audit_events" (target_type, target_id, occurred_at DESC)`
|
|
46038
46190
|
];
|
|
46039
|
-
const up$
|
|
46191
|
+
const up$3 = async (db) => {
|
|
46040
46192
|
await sql`insert into "sequences" (name, value) values ('audit_events_seq', 0)`.execute(db);
|
|
46041
46193
|
await sql`alter table "audit_events" rename to "audit_events_before_seq"`.execute(db);
|
|
46042
46194
|
await sql`create table "audit_events" (
|
|
@@ -46064,15 +46216,15 @@ const up$2 = async (db) => {
|
|
|
46064
46216
|
await sql`drop table "audit_events_before_seq"`.execute(db);
|
|
46065
46217
|
for (const statement of INDEXES) await sql.raw(statement).execute(db);
|
|
46066
46218
|
};
|
|
46067
|
-
const down$
|
|
46219
|
+
const down$3 = async (db) => {
|
|
46068
46220
|
await sql`alter table "audit_events" drop column "seq"`.execute(db);
|
|
46069
46221
|
await sql`delete from "sequences" where name = 'audit_events_seq'`.execute(db);
|
|
46070
46222
|
};
|
|
46071
46223
|
//#endregion
|
|
46072
46224
|
//#region src/db/migrations/sqlite/20261004120000_models_site_scope.ts
|
|
46073
46225
|
var _20261004120000_models_site_scope_exports = /* @__PURE__ */ __exportAll({
|
|
46074
|
-
down: () => down$
|
|
46075
|
-
up: () => up$
|
|
46226
|
+
down: () => down$2,
|
|
46227
|
+
up: () => up$2
|
|
46076
46228
|
});
|
|
46077
46229
|
/**
|
|
46078
46230
|
* SQLite twin of `../20261004120000_models_site_scope.ts`. `down` rebuilds `models` without `site_id`
|
|
@@ -46082,7 +46234,7 @@ var _20261004120000_models_site_scope_exports = /* @__PURE__ */ __exportAll({
|
|
|
46082
46234
|
* deletes nothing they reference.
|
|
46083
46235
|
*/
|
|
46084
46236
|
const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
46085
|
-
const up$
|
|
46237
|
+
const up$2 = async (db) => {
|
|
46086
46238
|
await sql`alter table "models" add column "site_id" text_uuid references sites (id)`.execute(db);
|
|
46087
46239
|
await sql`create index "models_site_idx" on "models" (site_id)`.execute(db);
|
|
46088
46240
|
await sql`drop index "models_api_key_uq"`.execute(db);
|
|
@@ -46092,7 +46244,7 @@ const up$1 = async (db) => {
|
|
|
46092
46244
|
`.execute(db);
|
|
46093
46245
|
await sql`alter table "schema_drafts" add column "shared" integer_boolean not null default 0`.execute(db);
|
|
46094
46246
|
};
|
|
46095
|
-
const down$
|
|
46247
|
+
const down$2 = async (db) => {
|
|
46096
46248
|
const { rows } = await sql`select count(*) as count from "models" where site_id is not null`.execute(db);
|
|
46097
46249
|
if (Number(rows[0]?.count ?? 0) > 0) throw new Error("Cannot roll back per-site schema while definitions belong to a site: two sites may share an API ID. Share or delete the site definitions first.");
|
|
46098
46250
|
await sql`alter table "schema_drafts" drop column "shared"`.execute(db);
|
|
@@ -46117,15 +46269,15 @@ const down$1 = async (db) => {
|
|
|
46117
46269
|
//#endregion
|
|
46118
46270
|
//#region src/db/migrations/sqlite/20261004130000_site_seo_settings.ts
|
|
46119
46271
|
var _20261004130000_site_seo_settings_exports = /* @__PURE__ */ __exportAll({
|
|
46120
|
-
down: () => down,
|
|
46121
|
-
up: () => up
|
|
46272
|
+
down: () => down$1,
|
|
46273
|
+
up: () => up$1
|
|
46122
46274
|
});
|
|
46123
46275
|
/**
|
|
46124
46276
|
* SQLite twin of `../20261004130000_site_seo_settings.ts`. `insert or ignore` stands in for `on conflict do
|
|
46125
46277
|
* nothing` (the grant's unique index); the permissions version moves only when a grant was added or removed.
|
|
46126
46278
|
*/
|
|
46127
46279
|
const bumpPermissionsVersion = (db) => sql`update "system_versions" set permissions_version = permissions_version + 1, updated_at = shapio_now()`.execute(db);
|
|
46128
|
-
const up = async (db) => {
|
|
46280
|
+
const up$1 = async (db) => {
|
|
46129
46281
|
await sql`alter table "sites" add column "seo_defaults" text_jsonb`.execute(db);
|
|
46130
46282
|
const granted = await sql`
|
|
46131
46283
|
insert or ignore into "admin_role_permissions" (role_id, action, model_id, condition, field_ids)
|
|
@@ -46135,18 +46287,43 @@ const up = async (db) => {
|
|
|
46135
46287
|
`.execute(db);
|
|
46136
46288
|
if (Number(granted.numAffectedRows ?? 0) > 0) await bumpPermissionsVersion(db);
|
|
46137
46289
|
};
|
|
46138
|
-
const down = async (db) => {
|
|
46290
|
+
const down$1 = async (db) => {
|
|
46139
46291
|
const revoked = await sql`delete from "admin_role_permissions" where action = 'site.settings'`.execute(db);
|
|
46140
46292
|
if (Number(revoked.numAffectedRows ?? 0) > 0) await bumpPermissionsVersion(db);
|
|
46141
46293
|
await sql`alter table "sites" drop column "seo_defaults"`.execute(db);
|
|
46142
46294
|
};
|
|
46143
46295
|
//#endregion
|
|
46296
|
+
//#region src/db/migrations/sqlite/20261005120000_entry_heads_entry_created_at.ts
|
|
46297
|
+
var _20261005120000_entry_heads_entry_created_at_exports = /* @__PURE__ */ __exportAll({
|
|
46298
|
+
down: () => down,
|
|
46299
|
+
up: () => up
|
|
46300
|
+
});
|
|
46301
|
+
/**
|
|
46302
|
+
* SQLite twin of `../20261005120000_entry_heads_entry_created_at.ts`. One process holds the write lock, so
|
|
46303
|
+
* the backfill is one statement and the index is built here (a plain `CREATE INDEX`; writers wait for it, the
|
|
46304
|
+
* documented single-process limitation of ADR 0001, "D2: as built").
|
|
46305
|
+
*/
|
|
46306
|
+
const INDEX = "entry_heads_entry_order_idx";
|
|
46307
|
+
const up = async (db) => {
|
|
46308
|
+
await sql`alter table "entry_heads" add column "entry_created_at" text_timestamptz`.execute(db);
|
|
46309
|
+
await sql`update "entry_heads" set entry_created_at = (
|
|
46310
|
+
select e.created_at from "entries" e where e.id = "entry_heads".entry_id
|
|
46311
|
+
) where entry_created_at is null`.execute(db);
|
|
46312
|
+
await sql`create index ${sql.id(INDEX)} on "entry_heads" (site_id, model_id, state, entry_created_at desc, entry_id)`.execute(db);
|
|
46313
|
+
};
|
|
46314
|
+
const down = async (db) => {
|
|
46315
|
+
await sql`drop index if exists ${sql.id(INDEX)}`.execute(db);
|
|
46316
|
+
await sql`alter table "entry_heads" drop column "entry_created_at"`.execute(db);
|
|
46317
|
+
};
|
|
46318
|
+
//#endregion
|
|
46144
46319
|
//#region src/db/migrations/sqlite/index.ts
|
|
46145
46320
|
const SQLITE_MIGRATIONS = {
|
|
46146
46321
|
"0001_baseline": _0001_baseline_exports,
|
|
46147
46322
|
"20261003170000_audit_events_seq": _20261003170000_audit_events_seq_exports,
|
|
46148
46323
|
"20261004120000_models_site_scope": _20261004120000_models_site_scope_exports,
|
|
46149
|
-
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports
|
|
46324
|
+
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports,
|
|
46325
|
+
"20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports,
|
|
46326
|
+
"20261006120000_system_versions_release": _20261006120000_system_versions_release_exports
|
|
46150
46327
|
};
|
|
46151
46328
|
//#endregion
|
|
46152
46329
|
//#region src/db/migrations/index.ts
|
|
@@ -46178,7 +46355,9 @@ const MIGRATIONS = {
|
|
|
46178
46355
|
"20261003160000_create_assist_runs": _20261003160000_create_assist_runs_exports,
|
|
46179
46356
|
"20261003170000_audit_events_seq": _20261003170000_audit_events_seq_exports$2,
|
|
46180
46357
|
"20261004120000_models_site_scope": _20261004120000_models_site_scope_exports$2,
|
|
46181
|
-
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports$2
|
|
46358
|
+
"20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports$2,
|
|
46359
|
+
"20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports$2,
|
|
46360
|
+
"20261006120000_system_versions_release": _20261006120000_system_versions_release_exports
|
|
46182
46361
|
};
|
|
46183
46362
|
const PROVIDERS = {
|
|
46184
46363
|
postgres: { getMigrations: () => Promise.resolve({ ...MIGRATIONS }) },
|
|
@@ -46481,6 +46660,24 @@ const parseByteRange = (header, size) => {
|
|
|
46481
46660
|
};
|
|
46482
46661
|
};
|
|
46483
46662
|
//#endregion
|
|
46663
|
+
//#region src/media/contentDisposition.ts
|
|
46664
|
+
/**
|
|
46665
|
+
* A Content-Disposition value that names the file safely: an ASCII fallback plus the RFC 5987 UTF-8 form,
|
|
46666
|
+
* so quotes, semicolons or line breaks in a filename can never inject header parameters.
|
|
46667
|
+
*/
|
|
46668
|
+
const contentDisposition = (filename, type = "inline") => {
|
|
46669
|
+
return `${type}; filename="${filename.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_")}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
|
|
46670
|
+
};
|
|
46671
|
+
/** Types a browser only ever displays passively: opening one directly cannot run script. */
|
|
46672
|
+
const PASSIVE_INLINE_TYPE = /^(image\/(png|jpeg|gif|webp|avif|bmp|tiff|x-icon|vnd\.microsoft\.icon)|video\/.+|audio\/.+)$/;
|
|
46673
|
+
/**
|
|
46674
|
+
* Content-Disposition to store on an object in a bucket. Public objects can be served straight from the
|
|
46675
|
+
* bucket or a CDN, where Shapio's sandboxing headers are absent; anything that could run as a page there
|
|
46676
|
+
* (SVG, HTML, XML, PDF...) is stored as an attachment, so opening it downloads it. `<img>` tags still show
|
|
46677
|
+
* an SVG, since embedding ignores Content-Disposition.
|
|
46678
|
+
*/
|
|
46679
|
+
const bucketContentDisposition = (contentType) => PASSIVE_INLINE_TYPE.test(contentType.toLowerCase()) ? void 0 : "attachment";
|
|
46680
|
+
//#endregion
|
|
46484
46681
|
//#region src/services/mediaFiles.ts
|
|
46485
46682
|
const notFound = () => new AppError(404, "NOT_FOUND", "Media file not found");
|
|
46486
46683
|
const findFile = async (key) => {
|
|
@@ -46908,6 +47105,16 @@ const insertIfAbsent = async (key, value, trx = db) => {
|
|
|
46908
47105
|
value
|
|
46909
47106
|
}).onConflict((oc) => oc.column("key").doNothing()).returning("key").executeTakeFirst() !== void 0;
|
|
46910
47107
|
};
|
|
47108
|
+
/** Creates or replaces a setting. */
|
|
47109
|
+
const upsertValue = async (key, value, trx = db) => {
|
|
47110
|
+
await trx.insertInto("system_settings").values({
|
|
47111
|
+
key,
|
|
47112
|
+
value
|
|
47113
|
+
}).onConflict((oc) => oc.column("key").doUpdateSet({
|
|
47114
|
+
value,
|
|
47115
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
47116
|
+
})).execute();
|
|
47117
|
+
};
|
|
46911
47118
|
//#endregion
|
|
46912
47119
|
//#region src/services/signingSecret.ts
|
|
46913
47120
|
const SIGNING_SECRET_SETTING = "signing_secret";
|
|
@@ -47413,20 +47620,6 @@ var StartupCheckError = class extends Error {
|
|
|
47413
47620
|
this.name = "StartupCheckError";
|
|
47414
47621
|
}
|
|
47415
47622
|
};
|
|
47416
|
-
/** `host:port/database` (PostgreSQL, MySQL) or the file path (SQLite) from DATABASE_URL, never the credentials. */
|
|
47417
|
-
const describeDatabaseTarget = (connectionString) => {
|
|
47418
|
-
if (dialectOfUrl(connectionString) === "sqlite") {
|
|
47419
|
-
const location = sqliteLocationOfUrl(connectionString);
|
|
47420
|
-
return location.kind === "memory" ? "an in-memory database" : location.path;
|
|
47421
|
-
}
|
|
47422
|
-
try {
|
|
47423
|
-
const url = new URL(connectionString);
|
|
47424
|
-
const defaultPort = dialectOfUrl(connectionString) === "mysql" ? "3306" : "5432";
|
|
47425
|
-
return `${url.hostname}:${url.port || defaultPort}${url.pathname}`;
|
|
47426
|
-
} catch {
|
|
47427
|
-
return "the configured database";
|
|
47428
|
-
}
|
|
47429
|
-
};
|
|
47430
47623
|
const NETWORK_REASONS = {
|
|
47431
47624
|
ECONNREFUSED: "connection refused",
|
|
47432
47625
|
ENOTFOUND: "host not found",
|
|
@@ -48924,6 +49117,45 @@ const createPublishingJobHandlers = (environment) => [
|
|
|
48924
49117
|
];
|
|
48925
49118
|
/** Outbox subscribers: webhook deliveries and deployment triggers follow domain events (ADR 0007). */
|
|
48926
49119
|
const PUBLISHING_OUTBOX_SUBSCRIBERS = [webhookOutboxSubscriber, deploymentOutboxSubscriber];
|
|
49120
|
+
Type.Object({
|
|
49121
|
+
format: Type.Literal(1),
|
|
49122
|
+
publicUrl: Type.String(),
|
|
49123
|
+
basePath: Type.String(),
|
|
49124
|
+
media: Type.Object({
|
|
49125
|
+
publicBaseUrl: Type.Union([Type.String(), Type.Null()]),
|
|
49126
|
+
s3: Type.Union([Type.Object({
|
|
49127
|
+
bucket: Type.String(),
|
|
49128
|
+
region: Type.Union([Type.String(), Type.Null()]),
|
|
49129
|
+
endpoint: Type.Union([Type.String(), Type.Null()]),
|
|
49130
|
+
forcePathStyle: Type.Boolean()
|
|
49131
|
+
}), Type.Null()])
|
|
49132
|
+
})
|
|
49133
|
+
});
|
|
49134
|
+
const toDeliveryDescriptor = ({ server, storage }) => ({
|
|
49135
|
+
format: 1,
|
|
49136
|
+
publicUrl: server.publicUrl,
|
|
49137
|
+
basePath: server.basePath,
|
|
49138
|
+
media: {
|
|
49139
|
+
publicBaseUrl: storage.publicBaseUrl ?? null,
|
|
49140
|
+
s3: storage.s3 ? {
|
|
49141
|
+
bucket: storage.s3.bucket,
|
|
49142
|
+
region: storage.s3.region ?? null,
|
|
49143
|
+
endpoint: storage.s3.endpoint ?? null,
|
|
49144
|
+
forcePathStyle: storage.s3.forcePathStyle
|
|
49145
|
+
} : null
|
|
49146
|
+
}
|
|
49147
|
+
});
|
|
49148
|
+
/**
|
|
49149
|
+
* Called by the server at every start, after migrating: records its release and its delivery descriptor
|
|
49150
|
+
* (rewritten only when it changed), in one transaction.
|
|
49151
|
+
*/
|
|
49152
|
+
const publishServerRelease = async (database, release, source) => {
|
|
49153
|
+
const descriptor = JSON.stringify(toDeliveryDescriptor(source));
|
|
49154
|
+
await database.transaction().execute(async (trx) => {
|
|
49155
|
+
await setRelease(release, trx);
|
|
49156
|
+
if (await findValue("delivery_descriptor", trx) !== descriptor) await upsertValue(DELIVERY_DESCRIPTOR_SETTING, descriptor, trx);
|
|
49157
|
+
});
|
|
49158
|
+
};
|
|
48927
49159
|
//#endregion
|
|
48928
49160
|
//#region src/tls/certificates.ts
|
|
48929
49161
|
/**
|
|
@@ -49084,7 +49316,7 @@ const logStartupSummary = (config, logger) => {
|
|
|
49084
49316
|
};
|
|
49085
49317
|
logger.info(summary, `Shapio ${summary.version} running at ${summary.url} (mode ${summary.mode}, storage ${summary.storage}, worker ${summary.worker}, tls ${summary.tls})`);
|
|
49086
49318
|
};
|
|
49087
|
-
const createConfiguredWorker = (config, db, logger, signingSecret, extensions) => {
|
|
49319
|
+
const createConfiguredWorker = async (config, db, logger, signingSecret, extensions) => {
|
|
49088
49320
|
const publishing = createPublishingJobEnvironment(createPublishingRuntime({
|
|
49089
49321
|
db,
|
|
49090
49322
|
signingSecret,
|
|
@@ -49097,7 +49329,7 @@ const createConfiguredWorker = (config, db, logger, signingSecret, extensions) =
|
|
|
49097
49329
|
urls: createUrlBuilder(config.server),
|
|
49098
49330
|
transport: createEmailTransport(config.email, logger.child({ component: "email" }))
|
|
49099
49331
|
};
|
|
49100
|
-
const storage = createMediaStorage(config.storage, { urls: createUrlBuilder(config.server) });
|
|
49332
|
+
const storage = await createMediaStorage(config.storage, { urls: createUrlBuilder(config.server) });
|
|
49101
49333
|
return createWorker({
|
|
49102
49334
|
db,
|
|
49103
49335
|
handlers: createJobHandlers([
|
|
@@ -49178,6 +49410,7 @@ const startServer = async (config) => {
|
|
|
49178
49410
|
log: logger
|
|
49179
49411
|
});
|
|
49180
49412
|
const signingSecret = await resolveSigningSecret(db, config.sessionSecret, logger);
|
|
49413
|
+
await publishServerRelease(db, SHAPIO_VERSION, config);
|
|
49181
49414
|
extensions = await startExtensions(config, db, logger, project);
|
|
49182
49415
|
const app = await buildApp(config, {
|
|
49183
49416
|
db,
|
|
@@ -49187,7 +49420,7 @@ const startServer = async (config) => {
|
|
|
49187
49420
|
schemaListen: config.schema.listen,
|
|
49188
49421
|
...tls.initial ? { tls: tls.initial } : {}
|
|
49189
49422
|
});
|
|
49190
|
-
const worker = config.worker.mode === "inline" ? createConfiguredWorker(config, db, logger, signingSecret, extensions) : void 0;
|
|
49423
|
+
const worker = config.worker.mode === "inline" ? await createConfiguredWorker(config, db, logger, signingSecret, extensions) : void 0;
|
|
49191
49424
|
const tlsRuntime = tls;
|
|
49192
49425
|
const extensionRuntime = extensions;
|
|
49193
49426
|
let closing;
|
|
@@ -49261,7 +49494,7 @@ const startDedicatedWorker = async (config) => {
|
|
|
49261
49494
|
}
|
|
49262
49495
|
const signingSecret = await resolveSigningSecret(db, config.sessionSecret, logger);
|
|
49263
49496
|
const extensions = await startExtensions(config, db, logger, project);
|
|
49264
|
-
const worker = createConfiguredWorker(config, db, logger, signingSecret, extensions);
|
|
49497
|
+
const worker = await createConfiguredWorker(config, db, logger, signingSecret, extensions);
|
|
49265
49498
|
await ensureRetentionScheduled(db);
|
|
49266
49499
|
await ensureHealthSweepScheduled(db);
|
|
49267
49500
|
worker.start();
|
|
@@ -49275,6 +49508,6 @@ const startDedicatedWorker = async (config) => {
|
|
|
49275
49508
|
return worker;
|
|
49276
49509
|
};
|
|
49277
49510
|
//#endregion
|
|
49278
|
-
export {
|
|
49511
|
+
export { parseLockFile as $, buildEditorManifest as A, RESERVED_DEFINITION_API_KEYS as B, loopbackUrl as C, BEFORE_EVENTS as D, AFTER_EVENTS as E, lockById$5 as F, THEME_VARIANTS as G, RESERVED_TYPE_NAMES as H, update$8 as I, hashDefinition as J, checkThemeContrast as K, STORAGE_DRIVERS$1 as L, isValidStorageKey as M, listForAssets as N, EXTENSION_JOB_PREFIX as O, listLiveByDriver as P, parseDefinition as Q, describeError as R, createUrlBuilder as S, loadProjectConfig as T, SCHEMA_KIND_DIRECTORIES as U, RESERVED_FIELD_API_KEYS as V, SITES_DIRECTORY as W, lockEntriesForSite as X, lockCoversSite as Y, mergePulledLock as Z, listAllSites as _, loadConfig as a, toTypeName as at, generateToken as b, bucketContentDisposition as c, withSiteCovered as ct, migrateToLatest as d, createStableId as dt, schemaFilePath as et, SHAPIO_VERSION as f, validateRichText as ft, createSite$1 as g, ObjectNotFoundError as h, assertDatabaseReachable as i, suggestPlural as it, encodeKeyPath as j, EXTENSION_ROUTE_ROOT as k, contentDisposition as l, RICHTEXT_FORMAT as lt, InvalidStorageKeyError as m, sqliteDriverOf as mt, startServer as n, serializeDefinition as nt, loadEnvFileIfPresent as o, upgradeLockFile as ot, createMediaStorage as p, createDb as pt, foldApiKey as q, copyObjectVerified as r, serializeLockFile as rt, buildApp as s, validateSchema as st, startDedicatedWorker as t, scopeDirectory as tt, getPendingMigrations as u, SAFE_LINK_PATTERN as ut, createAdminDirect as v, SITE_KEY_PATTERN as w, createLogger as x, SYSTEM_CLI_ACTOR as y, LOCK_FILE_PATH as z };
|
|
49279
49512
|
|
|
49280
|
-
//# sourceMappingURL=worker-
|
|
49513
|
+
//# sourceMappingURL=worker-jz_0yAmS.js.map
|