ghost 6.56.0 → 6.57.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/bin/validate-adapters.js +74 -0
- package/bin/validate-adapters.ts +87 -0
- package/bin/warm-compile-cache.sh +60 -0
- package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/components/tryghost-kg-html-to-lexical-1.3.3.tgz +0 -0
- package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
- package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
- package/core/app.js +1 -1
- package/core/boot.js +21 -25
- package/core/bridge.js +10 -12
- package/core/built/admin/assets/Combination-BDvCdg1t.js +41 -0
- package/core/built/admin/assets/about-C7O-AgRZ.js +1 -0
- package/core/built/admin/assets/{account-migration-D3O_yVck.js → account-migration-Cv_ol1ko.js} +1 -1
- package/core/built/admin/assets/action-list-lGzZLHc8.js +1 -0
- package/core/built/admin/assets/activitypub/{account-migration-D8FbP-Z-.js → account-migration-DHFbePl6.js} +7 -7
- package/core/built/admin/assets/activitypub/activitypub.js +3 -3
- package/core/built/admin/assets/activitypub/{avatar-11EA1VSs.js → avatar-CT4tS2_S.js} +2 -2
- package/core/built/admin/assets/activitypub/{avatar-flipboard-jfbLG-xe.js → avatar-flipboard-ByYWKipo.js} +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-f1jTQe2P.js → bluesky-sharing-srVnSHDz.js} +8 -8
- package/core/built/admin/assets/activitypub/{content-formatters-nmTOPY9M.js → content-formatters-C2sos5Ht.js} +3 -3
- package/core/built/admin/assets/activitypub/{copy-BCwo9ep3.js → copy-B1UBTOoZ.js} +2 -2
- package/core/built/admin/assets/activitypub/{dist-BEfX-0aW.js → dist-MPsb2WZ1.js} +4 -4
- package/core/built/admin/assets/activitypub/{domain-AaDlZlrA.js → domain-DzcM9Dzo.js} +9 -9
- package/core/built/admin/assets/activitypub/{edit-profile-DPi8Tl5M.js → edit-profile-CsQ_4xAk.js} +5 -5
- package/core/built/admin/assets/activitypub/{explore-CxTzjILQ.js → explore-CJe0DZBK.js} +8 -8
- package/core/built/admin/assets/activitypub/{feed-DPv5x2MX.js → feed-CKV3qWON.js} +9 -9
- package/core/built/admin/assets/activitypub/{field-DbgA8lat.js → field-DckHfDJf.js} +3 -3
- package/core/built/admin/assets/activitypub/{hash-BWuZoYkA.js → hash-Dlp8RM55.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-35VDQ2K2.js → inbox-CsPy4R0M.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-DSrF_i1O.js → inbox-DAsjzUUe.js} +10 -10
- package/core/built/admin/assets/activitypub/{label-CPjFBbo7.js → label-DWyo9E5T.js} +3 -3
- package/core/built/admin/assets/activitypub/layout-DM_3pqsU.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-BkDC1XVZ.js → moderation-B0Inc03M.js} +7 -7
- package/core/built/admin/assets/activitypub/{note-xEyH4xT-.js → note-CSE54lER.js} +7 -7
- package/core/built/admin/assets/activitypub/{notifications-DClaL3ui.js → notifications-RSwbKqdh.js} +9 -9
- package/core/built/admin/assets/activitypub/{preferences-DpnOa-4Z.js → preferences-CE231n2_.js} +11 -11
- package/core/built/admin/assets/activitypub/{profile-Dvi9mz-6.js → profile-aaQ1j1Eo.js} +13 -13
- package/core/built/admin/assets/activitypub/{reply-Cs9ySd6Z.js → reply-Cjb0Vs3m.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-DFBeTnfn.js → routes-Djb0mc9k.js} +26 -26
- package/core/built/admin/assets/activitypub/{separator-C5u_VjzY.js → separator-D8pROI5n.js} +3 -3
- package/core/built/admin/assets/activitypub/{settings-CV2fFu6D.js → settings-DMc32IQl.js} +5 -5
- package/core/built/admin/assets/activitypub/{site-_TgDd_EM.js → site-DVIcsohX.js} +4 -4
- package/core/built/admin/assets/activitypub/{step-1-BfnNL2oF.js → step-1-9Yey9usF.js} +5 -5
- package/core/built/admin/assets/activitypub/{step-2-DLndZkO-.js → step-2-DdYmKRXV.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-3-A-PZTtRE.js → step-3-hdSkcjZU.js} +10 -10
- package/core/built/admin/assets/activitypub/{tabs-BUbz7tea.js → tabs-CUqseYzQ.js} +6 -6
- package/core/built/admin/assets/activitypub/{topic-filter-BdX_xYYT.js → topic-filter-Coni6OXM.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-EwuRemHh.js → use-activity-pub-queries-MeHTPMQv.js} +2 -2
- package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-cy4ACBc4.js → use-navigate-with-base-path-B046AM38.js} +24 -7
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-CyFcGfan.js → use-reply-chain-data-1AO-Xob6.js} +5 -5
- package/core/built/admin/assets/activitypub/{x-7zk8mV2p.js → x-BRfRYd16.js} +2 -2
- package/core/built/admin/assets/add-integration-modal-m9Dv6f77.js +1 -0
- package/core/built/admin/assets/add-newsletter-modal-CPSVZL2H.js +1 -0
- package/core/built/admin/assets/add-recommendation-modal-Ci9Z5yat.js +1 -0
- package/core/built/admin/assets/analytics-DqSCVV8U.js +1 -0
- package/core/built/admin/assets/announcement-bar-modal-DU3avV7d.js +1 -0
- package/core/built/admin/assets/api-keys-BK0LylyK.js +1 -0
- package/core/built/admin/assets/api-keys-_iCT4dxr.js +1 -0
- package/core/built/admin/assets/{audience-CkBb2LlO.js → audience-DTJtXXWY.js} +1 -1
- package/core/built/admin/assets/automated-emails-PayPCT8L.js +1 -0
- package/core/built/admin/assets/{automations-DhKeS_U5.js → automations-162q5HjL.js} +1 -1
- package/core/built/admin/assets/{automations-cMllU545.js → automations-dtQ_MRZ7.js} +1 -1
- package/core/built/admin/assets/{bluesky-sharing-DPGpyD-G.js → bluesky-sharing-B8Es3AuV.js} +1 -1
- package/core/built/admin/assets/brand-icon-09YqFZjV.js +1 -0
- package/core/built/admin/assets/{breadcrumb-DIfcF5ow.js → breadcrumb-tg5zL--z.js} +1 -1
- package/core/built/admin/assets/{chart-helpers-DV0FkdcD.js → chart-helpers-CZElTGYL.js} +1 -1
- package/core/built/admin/assets/checkbox-Cyo-x7mC.js +1 -0
- package/core/built/admin/assets/{chunk.524.87b52f4fada529df2c4c.js → chunk.524.cd5498dd4e195f679266.js} +4 -4
- package/core/built/admin/assets/{chunk.582.340bcca873e806cbd4bb.js → chunk.582.6f92ec1f03734bf2ea3f.js} +6 -6
- package/core/built/admin/assets/{chunk.332.05991a101d67f732dec3.js → chunk.873.b8fc05a8ab877fb819ed.js} +28 -27
- package/core/built/admin/assets/code-editor-view-Dq1rQ4tS.js +1 -0
- package/core/built/admin/assets/color-picker-field-BonBgxhU.js +1 -0
- package/core/built/admin/assets/color-utils-BsrwXuDz.js +1 -0
- package/core/built/admin/assets/combobox-uX5r_paI.js +1 -0
- package/core/built/admin/assets/command-DxwBO2pF.js +1 -0
- package/core/built/admin/assets/comments-Dn-RO-nU.js +1 -0
- package/core/built/admin/assets/components-CwL6a6As-B0E0-NBJ.js +1 -0
- package/core/built/admin/assets/confirmation-modal-B6D6mk6R.js +1 -0
- package/core/built/admin/assets/content-api-modal-BxQ-7T6u.js +1 -0
- package/core/built/admin/assets/copy-field-BAxL4Zqn.js +1 -0
- package/core/built/admin/assets/currency-Dy4Tc2R2.js +1 -0
- package/core/built/admin/assets/custom-integration-modal---d_l7lc.js +1 -0
- package/core/built/admin/assets/{date-range-select-dXc-GsVe.js → date-range-select-PB2n1HZf.js} +1 -1
- package/core/built/admin/assets/debounce-Dg2fW5Rg.js +3 -0
- package/core/built/admin/assets/design-and-theme-modal-DYRT207a.js +23 -0
- package/core/built/admin/assets/dirty-confirm-dialog-BnDJ39ao.js +1 -0
- package/core/built/admin/assets/disabled-sources-indicator-iMHrkyV3.js +1 -0
- package/core/built/admin/assets/dist-B2PvRwLn.js +1 -0
- package/core/built/admin/assets/{dist-pHiOxyxa.js → dist-D5vhMdXf.js} +1 -1
- package/core/built/admin/assets/dist-DlCyLrOC.js +1 -0
- package/core/built/admin/assets/dist-WeOIvSYM.js +23 -0
- package/core/built/admin/assets/{dist-DyViJwIy.js → dist-cFzKLG7y.js} +3 -3
- package/core/built/admin/assets/dist-j1xxxnY8.js +1 -0
- package/core/built/admin/assets/dist-myQaY0ka.js +1 -0
- package/core/built/admin/assets/{domain-B5ogXUBa.js → domain-BR8FIdjD.js} +1 -1
- package/core/built/admin/assets/{dropzone-BnFG7dDP.js → dropzone-hNxmUvwD.js} +1 -1
- package/core/built/admin/assets/{edit-profile-B07jCZy9.js → edit-profile-CENrkY0l.js} +1 -1
- package/core/built/admin/assets/editor-B18D0lm8.js +8 -0
- package/core/built/admin/assets/embed-signup-form-modal-BsCYUMvQ.js +9 -0
- package/core/built/admin/assets/{explore-Ca1y8HW_.js → explore-BmrnljWw.js} +1 -1
- package/core/built/admin/assets/{feed-x4iFMBP4.js → feed-Dy4li6i8.js} +1 -1
- package/core/built/admin/assets/{field-ChCAabZg.js → field-Dh_lwk13.js} +1 -1
- package/core/built/admin/assets/{filter-sources-LdtH1e-T.js → filter-sources-D2zDuJH5.js} +1 -1
- package/core/built/admin/assets/filters-3JBQcNtJ.js +1 -0
- package/core/built/admin/assets/{filters-CjVKRP8U.js → filters-CFt_BDe4.js} +1 -1
- package/core/built/admin/assets/first-promoter-modal-D6Rjxugw.js +1 -0
- package/core/built/admin/assets/format-url-C-t8G6Qd.js +1 -0
- package/core/built/admin/assets/{ghost-7f01131115186d1af84ead76e79646dd.js → ghost-d683fbeb945b13155ea669caa5d8db41.js} +300 -298
- package/core/built/admin/assets/{gift-link-modal-Buqh2vXh.js → gift-link-modal-zMtlJhLQ.js} +1 -1
- package/core/built/admin/assets/{gift-links-CsUhefWG.js → gift-links-CVuqLhsZ.js} +1 -1
- package/core/built/admin/assets/global-data-provider-B949LDXJ.js +1 -0
- package/core/built/admin/assets/grid-CUciBfjf.js +1 -0
- package/core/built/admin/assets/{growth-Dy2UOSay.js → growth-C38Xtp8D.js} +2 -2
- package/core/built/admin/assets/{growth-DajOBdN0.js → growth-CFws67_i.js} +1 -1
- package/core/built/admin/assets/history-modal-ILAOkbxH.js +1 -0
- package/core/built/admin/assets/{hooks-CixO01lL.js → hooks-D6EE7BIt.js} +2 -2
- package/core/built/admin/assets/html-field-9a9Id_2q.js +1 -0
- package/core/built/admin/assets/iframe-buffering-e8vH4-Qn.js +1 -0
- package/core/built/admin/assets/images-CrQKVc9Q.js +1 -0
- package/core/built/admin/assets/{inbox-BUOLDoji.js → inbox-BrSRzyvz.js} +13 -13
- package/core/built/admin/assets/index-BHy2DZX-.js +67 -0
- package/core/built/admin/assets/index-Ds2iUoF6.css +1 -0
- package/core/built/admin/assets/index.es-B9tvf-t4.js +1 -0
- package/core/built/admin/assets/{input-group-uSK3XT7b.js → input-group-6vEPvX9S.js} +1 -1
- package/core/built/admin/assets/integration-header-B4DyXV06.js +1 -0
- package/core/built/admin/assets/integrations-BT6_iJvu.js +1 -0
- package/core/built/admin/assets/invite-user-modal-5xDCEw90.js +1 -0
- package/core/built/admin/assets/isEmail-BOfC647y.js +1 -0
- package/core/built/admin/assets/koenig-editor-base-BWLEB9Vf.js +1 -0
- package/core/built/admin/assets/{koenig-lexical-BFc4yFed.js → koenig-lexical-Cj62r_y9.js} +1 -1
- package/core/built/admin/assets/{kpi-card-BN-E6WYX.js → kpi-card-DOUnkZcN.js} +1 -1
- package/core/built/admin/assets/{kpi-card-CCt-qeWD.js → kpi-card-IHDo8Q56.js} +1 -1
- package/core/built/admin/assets/{kpi-helpers-DlxTbTs4.js → kpi-helpers-CjUdECAK.js} +1 -1
- package/core/built/admin/assets/{kpi-tabs-BlcUttpn.js → kpi-tabs-X3DkOYbI.js} +1 -1
- package/core/built/admin/assets/{label-AVrxwmsz.js → label-Dq6QaOuS.js} +1 -1
- package/core/built/admin/assets/label-picker-8YlhS2U3.js +1 -0
- package/core/built/admin/assets/{labels-DAIqKed7.js → labels-DAPjKPy2.js} +1 -1
- package/core/built/admin/assets/layout-TMj4P-vv.js +1 -0
- package/core/built/admin/assets/limit-modal-BRPsit_B.js +1 -0
- package/core/built/admin/assets/{link-helpers-D9LsDOkG.js → link-helpers-Bzkk68GD.js} +1 -1
- package/core/built/admin/assets/member-custom-fields-CmvsuuV_.js +1 -0
- package/core/built/admin/assets/member-detail-DCvRrkmJ.js +1 -0
- package/core/built/admin/assets/members-DxbSKngD.js +11 -0
- package/core/built/admin/assets/modal-page-BMTzFHVO.js +1 -0
- package/core/built/admin/assets/{moderation-CQ3urTFE.js → moderation-Da5lhhWh.js} +1 -1
- package/core/built/admin/assets/multi-select-combobox-CYdkH7KQ.js +1 -0
- package/core/built/admin/assets/navigation-modal-BIoTwtMy.js +1 -0
- package/core/built/admin/assets/newsletter-BptlVoJm.js +1 -0
- package/core/built/admin/assets/newsletter-detail-modal-BIvxqE0N.js +1 -0
- package/core/built/admin/assets/newsletter-emails-Bf8IZLsP.js +1 -0
- package/core/built/admin/assets/{newsletters-vt3Ni1Ov.js → newsletters-BgrIZk0V.js} +1 -1
- package/core/built/admin/assets/{newsletters-BXJbJgA-.js → newsletters-Du0lzskS.js} +1 -1
- package/core/built/admin/assets/{note-Dk877BTv.js → note-DYcLOmDb.js} +1 -1
- package/core/built/admin/assets/{notifications-CPlLjPxO.js → notifications-B2SPFIV9.js} +1 -1
- package/core/built/admin/assets/{offers-DEniVRD5.js → offers-Um6ab71O.js} +1 -1
- package/core/built/admin/assets/offers-container-modal-BZnZITMw.js +1 -0
- package/core/built/admin/assets/{onboarding-route-DdStOtjy.js → onboarding-route-DgdfxkQh.js} +1 -1
- package/core/built/admin/assets/overview-BYQJXSog.js +1 -0
- package/core/built/admin/assets/overview-Dt2_mR6U.js +1 -0
- package/core/built/admin/assets/{page-header-pp4dWmOS.js → page-header-CpmxJj5B.js} +1 -1
- package/core/built/admin/assets/{pages-B4goRPUn.js → pages-BVMnpI2O.js} +1 -1
- package/core/built/admin/assets/pintura-modal-B4MuBuuO.js +1 -0
- package/core/built/admin/assets/portal-frame-BF0NBIji.js +1 -0
- package/core/built/admin/assets/portal-modal-DMVbm0oy.js +1 -0
- package/core/built/admin/assets/{post-analytics-BMJrI81R.js → post-analytics-CXWBOGyP.js} +1 -1
- package/core/built/admin/assets/post-analytics-header-ChzjXVPt.js +1 -0
- package/core/built/admin/assets/{post-analytics-provider-Btml5vJI.js → post-analytics-provider-Cgw4U_fw.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-AcUZkbfL.js → post-share-modal-B9Vmk_XM.js} +1 -1
- package/core/built/admin/assets/{posts-DhXE7b6r.js → posts-CxG7v0-M.js} +1 -1
- package/core/built/admin/assets/preferences-BfzJmnsd.js +5 -0
- package/core/built/admin/assets/preview-chrome-BOVO6_PE.js +1 -0
- package/core/built/admin/assets/preview-modal-Dw_HtD6S.js +1 -0
- package/core/built/admin/assets/profile-BT5vtv9b.js +1 -0
- package/core/built/admin/assets/radio-group-I7ou7PoJ.js +1 -0
- package/core/built/admin/assets/{referrers-pmJT4k3-.js → referrers-B9QDAfYm.js} +1 -1
- package/core/built/admin/assets/{router-provider-BmEZf6c6.js → router-provider-DSQfprxH.js} +1 -1
- package/core/built/admin/assets/select-D9oU1IoM.js +1 -0
- package/core/built/admin/assets/settings-CfZFbyu_.js +4 -0
- package/core/built/admin/assets/{settings-BGgk7oCI.js → settings-CpvHWix7.js} +1 -1
- package/core/built/admin/assets/settings-breadcrumbs-K0USP2nw.js +1 -0
- package/core/built/admin/assets/{share-modal-BeuydaBf.js → share-modal-ClIyPzc5.js} +1 -1
- package/core/built/admin/assets/slack-modal-1XXqOfiw.js +1 -0
- package/core/built/admin/assets/social-urls-CHQ9cTAK.js +1 -0
- package/core/built/admin/assets/{sort-button-Df4t2AL8.js → sort-button-CdBsm40v.js} +1 -1
- package/core/built/admin/assets/{source-icon-ajoaKb41.js → source-icon-CEYmVLXR.js} +1 -1
- package/core/built/admin/assets/{stats-C_lfxZAw.js → stats-D8nZCTy5.js} +1 -1
- package/core/built/admin/assets/{stats-view-Cxot1iD8.js → stats-view-CugFVf0g.js} +1 -1
- package/core/built/admin/assets/{step-1-ogAA4lDr.js → step-1-DOYjrORn.js} +1 -1
- package/core/built/admin/assets/{step-2-C5Xini-7.js → step-2-vA6RsarH.js} +1 -1
- package/core/built/admin/assets/{step-3-CZv1FV81.js → step-3-BtIBxMId.js} +2 -2
- package/core/built/admin/assets/stripe-button-UFP4QRXd.js +1 -0
- package/core/built/admin/assets/stripe-connect-modal-C_L3uM6n.js +1 -0
- package/core/built/admin/assets/switch-CE2NuMn-.js +1 -0
- package/core/built/admin/assets/{table-DFj1wiYv.js → table-TMCgCQK7.js} +1 -1
- package/core/built/admin/assets/tabs-Dd6oOKbv.js +1 -0
- package/core/built/admin/assets/tag-detail-D3l-UJPR.js +1 -0
- package/core/built/admin/assets/tags-BwCgmr5v.js +1 -0
- package/core/built/admin/assets/tags-WdKQGWR8.js +1 -0
- package/core/built/admin/assets/testimonials-modal-CqWp-xXJ.js +1 -0
- package/core/built/admin/assets/tier-detail-modal-BkcR2u0A.js +1 -0
- package/core/built/admin/assets/{tiers-DYsa4DCo.js → tiers-Da6kxHQP.js} +1 -1
- package/core/built/admin/assets/toggle-group-Guorw93z.js +1 -0
- package/core/built/admin/assets/{topic-filter-DGDxDalo.js → topic-filter-CbgoWIHn.js} +1 -1
- package/core/built/admin/assets/transistor-modal-DuNnnpal.js +1 -0
- package/core/built/admin/assets/unsplash-modal-y2bVwTHH.js +1 -0
- package/core/built/admin/assets/{use-analytics-data-C76v4LIp.js → use-analytics-data-B-b4wNSC.js} +1 -1
- package/core/built/admin/assets/use-check-theme-limit-error-m09J38Mq.js +2 -0
- package/core/built/admin/assets/use-feature-flag-ud0Qz8vt.js +1 -0
- package/core/built/admin/assets/use-filter-params-BnbBDc7T.js +1 -0
- package/core/built/admin/assets/use-filterable-api-iMpDZBxD.js +1 -0
- package/core/built/admin/assets/use-form-vRjI0Kwk.js +1 -0
- package/core/built/admin/assets/{use-growth-stats-aNXJOmQr.js → use-growth-stats-DPU5rt3W.js} +1 -1
- package/core/built/admin/assets/use-hash-link-navigation-guard-B9j6HIrV.js +1 -0
- package/core/built/admin/assets/use-limiter-BkN3E4gw.js +2 -0
- package/core/built/admin/assets/use-pintura-config-C_z8HTG2.js +1 -0
- package/core/built/admin/assets/use-pintura-editor-DNgbkhGp.js +1 -0
- package/core/built/admin/assets/{use-post-referrers-CG0SZioj.js → use-post-referrers-C2mXyrp-.js} +1 -1
- package/core/built/admin/assets/use-query-params-DHhhu8wi.js +1 -0
- package/core/built/admin/assets/{use-reply-chain-data-RLdPKRmS.js → use-reply-chain-data-DaK8q2qQ.js} +1 -1
- package/core/built/admin/assets/use-setting-group-YlYcoNyP.js +1 -0
- package/core/built/admin/assets/use-settings-navigation-Bf1g1rEh.js +1 -0
- package/core/built/admin/assets/{use-simple-pagination-DgcF69KI.js → use-simple-pagination-D9ckKo5-.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-query-C1R_Kbmy.js → use-tinybird-query-GSa32hVw.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-C9fPjVM_.js → use-tinybird-token-CKLOZFsw.js} +1 -1
- package/core/built/admin/assets/use-url-input-z-p0Kewm.js +5 -0
- package/core/built/admin/assets/user-detail-modal-BMxOA4Sv.js +1 -0
- package/core/built/admin/assets/{users-Dkv8NQZe.js → users-B6hIds6n.js} +1 -1
- package/core/built/admin/assets/validator-Cr0sMKuW.js +7 -0
- package/core/built/admin/assets/{virtual-list-BmTUzeoF.js → virtual-list-RHeEL05L.js} +1 -1
- package/core/built/admin/assets/web-Bw2QMO5u.js +2 -0
- package/core/built/admin/assets/web-xJtcwMy4.js +1 -0
- package/core/built/admin/assets/zapier-modal-BP2f4-vY.js +1 -0
- package/core/built/admin/index.html +15 -13
- package/core/frontend/helpers/authors.js +1 -1
- package/core/frontend/helpers/link.js +3 -8
- package/core/frontend/helpers/tags.js +1 -1
- package/core/frontend/meta/author-url.js +2 -2
- package/core/frontend/meta/url.js +4 -4
- package/core/frontend/services/proxy.js +1 -1
- package/core/frontend/services/routing/events.js +20 -6
- package/core/frontend/services/routing/permalink-adapter.js +0 -13
- package/core/frontend/services/routing/permalink-adapter.ts +0 -13
- package/core/frontend/services/routing/router-manager.js +15 -9
- package/core/frontend/services/sitemap/base-site-map-generator.js +1 -28
- package/core/frontend/services/sitemap/site-map-manager.js +22 -57
- package/core/server/adapters/route-settings/utils.js +10 -2
- package/core/server/adapters/route-settings/utils.ts +12 -2
- package/core/server/api/endpoints/email-post.js +1 -1
- package/core/server/api/endpoints/previews.js +1 -1
- package/core/server/api/endpoints/search-index-public.js +3 -3
- package/core/server/api/endpoints/search-index.js +4 -4
- package/core/server/api/endpoints/utils/serializers/input/authors.js +2 -2
- package/core/server/api/endpoints/utils/serializers/input/comments.js +1 -1
- package/core/server/api/endpoints/utils/serializers/input/pages.js +3 -3
- package/core/server/api/endpoints/utils/serializers/input/posts.js +3 -3
- package/core/server/api/endpoints/utils/serializers/input/tags.js +1 -1
- package/core/server/api/endpoints/utils/serializers/input/users.js +2 -2
- package/core/server/api/endpoints/utils/serializers/input/utils/url.js +31 -13
- package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +2 -2
- package/core/server/api/endpoints/utils/serializers/output/mappers/tags.js +2 -2
- package/core/server/api/endpoints/utils/serializers/output/mappers/users.js +8 -0
- package/core/server/api/endpoints/utils/serializers/output/utils/url.js +18 -11
- package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +74 -0
- package/core/server/data/migrations/versions/6.57/2026-08-04-18-54-14-reset-automation-email-analytics.js +25 -0
- package/core/server/data/schema/commands.js +21 -5
- package/core/server/data/schema/schema.js +24 -5
- package/core/server/lib/common/to-plain.ts +1 -1
- package/core/server/models/post.js +2 -2
- package/core/server/services/adapter-manager/adapter-manager.js +1 -45
- package/core/server/services/adapter-manager/adapter-manager.ts +2 -48
- package/core/server/services/adapter-manager/adapter-paths.js +24 -0
- package/core/server/services/adapter-manager/adapter-paths.ts +21 -0
- package/core/server/services/adapter-manager/base-classes.js +22 -0
- package/core/server/services/adapter-manager/base-classes.ts +22 -0
- package/core/server/services/adapter-manager/index.js +4 -27
- package/core/server/services/adapter-manager/index.ts +4 -31
- package/core/server/services/adapter-manager/utils.js +50 -0
- package/core/server/services/adapter-manager/utils.ts +57 -0
- package/core/server/services/audience-feedback/audience-feedback-controller.js +1 -1
- package/core/server/services/audience-feedback/audience-feedback-service.js +2 -2
- package/core/server/services/automations/service.js +2 -2
- package/core/server/services/automations/service.ts +2 -2
- package/core/server/services/comments/comments-service-emails.js +1 -1
- package/core/server/services/comments/comments-service.js +1 -1
- package/core/server/services/email-analytics/email-analytics-service-wrapper.js +11 -2
- package/core/server/services/email-analytics/email-analytics-service.js +1 -1
- package/core/server/services/email-service/email-service-wrapper.js +2 -1
- package/core/server/services/email-service/mailgun-email-provider.js +15 -0
- package/core/server/services/indexnow-ping/indexnow-ping-service.js +1 -1
- package/core/server/services/indexnow-ping/indexnow-ping-service.ts +2 -4
- package/core/server/services/lib/mailgun-client.js +3 -3
- package/core/server/services/media-inliner/external-media-inliner.js +12 -8
- package/core/server/services/member-attribution/url-translator.js +5 -7
- package/core/server/services/member-welcome-emails/service.js +72 -12
- package/core/server/services/members/api.js +1 -1
- package/core/server/services/members/import-export/import/completion-email.js +8 -4
- package/core/server/services/members/import-export/import/completion-email.ts +9 -4
- package/core/server/services/members/import-export/import/importer.js +25 -4
- package/core/server/services/members/import-export/import/importer.ts +26 -6
- package/core/server/services/members/import-export/import/row.ts +5 -1
- package/core/server/services/members/import-export/index.js +1 -5
- package/core/server/services/members/import-export/index.ts +2 -6
- package/core/server/services/members-custom-fields/queries.js +4 -13
- package/core/server/services/members-custom-fields/queries.ts +4 -13
- package/core/server/services/members-custom-fields/schema.js +18 -27
- package/core/server/services/members-custom-fields/schema.ts +17 -27
- package/core/server/services/members-custom-fields/storage.js +94 -27
- package/core/server/services/members-custom-fields/storage.ts +114 -39
- package/core/server/services/members-custom-fields/values-service.js +98 -219
- package/core/server/services/members-custom-fields/values-service.ts +110 -238
- package/core/server/services/mentions/resource-service.js +1 -1
- package/core/server/services/mentions/service.js +2 -2
- package/core/server/services/route-settings/dynamic-routing-service.js +9 -59
- package/core/server/services/slack-ping/slack-ping-service.js +2 -2
- package/core/server/services/slack-ping/slack-ping-service.ts +3 -5
- package/core/server/services/stats/content-stats-service.js +3 -3
- package/core/server/services/stats/posts-stats-service.js +2 -2
- package/core/server/services/url/config.js +90 -135
- package/core/server/services/url/index.js +6 -49
- package/core/server/services/url/lazy-find-resource.js +14 -11
- package/core/server/services/url/lazy-find-resource.ts +14 -11
- package/core/server/services/url/lazy-url-service.js +174 -94
- package/core/server/services/url/lazy-url-service.ts +233 -97
- package/core/server/services/url/routable-resources.js +62 -71
- package/core/server/services/url/router-filter.js +12 -4
- package/core/server/services/url/router-filter.ts +12 -4
- package/core/server/services/webhooks/index.js +1 -1
- package/core/server/services/webhooks/serialize.js +2 -1
- package/core/shared/config/env/config.testing-mysql.json +1 -2
- package/core/shared/config/env/config.testing.json +1 -2
- package/core/shared/events/index.js +1 -2
- package/core/shared/labs.js +3 -3
- package/package.json +5 -6
- package/pnpm-lock.yaml +6 -9
- package/pnpm-workspace.yaml +2 -2
- package/tsconfig.json +1 -0
- package/core/built/admin/assets/Combination-BCm9FL4o.js +0 -41
- package/core/built/admin/assets/activitypub/layout-CTgrezN7.js +0 -1
- package/core/built/admin/assets/automated-emails-6t2bIRWf.js +0 -7
- package/core/built/admin/assets/checkbox-BIl077a0.js +0 -1
- package/core/built/admin/assets/code-editor-view-C0aWNwdl.js +0 -1
- package/core/built/admin/assets/comments-qwhIjH3c.js +0 -1
- package/core/built/admin/assets/disabled-sources-indicator-DYAXW-6V.js +0 -1
- package/core/built/admin/assets/dist-BUKmD51u.js +0 -1
- package/core/built/admin/assets/dist-CNlMj7ML.js +0 -1
- package/core/built/admin/assets/dist-DDY6GRVz.js +0 -1
- package/core/built/admin/assets/dist-DG78yroX.js +0 -23
- package/core/built/admin/assets/editor-DDYSOXGN.js +0 -8
- package/core/built/admin/assets/esm-CFX93H5M.js +0 -2
- package/core/built/admin/assets/filters-CLehPo_D.js +0 -1
- package/core/built/admin/assets/index-08n05-rc.js +0 -67
- package/core/built/admin/assets/index-DgUzgFvS.css +0 -1
- package/core/built/admin/assets/label-picker-DVnpu_IF.js +0 -1
- package/core/built/admin/assets/layout-B0YLh7R9.js +0 -1
- package/core/built/admin/assets/member-custom-fields-C8NpZYza.js +0 -1
- package/core/built/admin/assets/member-detail-CIVAdKXf.js +0 -1
- package/core/built/admin/assets/members-oLPiSkXD.js +0 -10
- package/core/built/admin/assets/modals-CB7v-k3T.js +0 -31
- package/core/built/admin/assets/newsletter-rbeDX9Wk.js +0 -1
- package/core/built/admin/assets/overview--4V-kx9c.js +0 -1
- package/core/built/admin/assets/overview-DSLv30oz.js +0 -1
- package/core/built/admin/assets/post-analytics-header-pU2m9ZuV.js +0 -1
- package/core/built/admin/assets/preferences-Ru4CRPHC.js +0 -5
- package/core/built/admin/assets/profile-BNb1oj7D.js +0 -1
- package/core/built/admin/assets/select-B0HteHrp.js +0 -1
- package/core/built/admin/assets/settings-B1C9Q3wo.js +0 -4
- package/core/built/admin/assets/social-urls-DA2lXrhW.js +0 -8
- package/core/built/admin/assets/switch-CyQcwMAv.js +0 -1
- package/core/built/admin/assets/tabs-D4y1gqUd.js +0 -1
- package/core/built/admin/assets/tags-Dmft-Y4O.js +0 -1
- package/core/built/admin/assets/toggle-group-R2QMFvXW.js +0 -1
- package/core/built/admin/assets/use-filter-params-WrdhAISv.js +0 -1
- package/core/built/admin/assets/web-BqW0b0_C.js +0 -1
- package/core/built/admin/assets/web-Cy5GKMWM.js +0 -2
- package/core/server/services/url/local-file-cache.js +0 -75
- package/core/server/services/url/queue.js +0 -260
- package/core/server/services/url/resource.js +0 -102
- package/core/server/services/url/resources.js +0 -480
- package/core/server/services/url/url-generator.js +0 -276
- package/core/server/services/url/url-service-facade.js +0 -366
- package/core/server/services/url/url-service-facade.ts +0 -519
- package/core/server/services/url/url-service.js +0 -387
- package/core/server/services/url/urls.js +0 -140
- package/core/shared/events/url-resource-updated-event.js +0 -33
- /package/core/built/admin/assets/{dist-CqO2lWS0.js → dist-CqO2lWS02.js} +0 -0
|
@@ -1,14 +1,53 @@
|
|
|
1
1
|
const debug = require('@tryghost/debug')('services:url:lazy');
|
|
2
2
|
const errors = require('@tryghost/errors');
|
|
3
|
+
const logging = require('@tryghost/logging');
|
|
3
4
|
const localUtils = require('../../../shared/url-utils').default;
|
|
4
5
|
const {matchPermalink, toLookupParams} = require('./permalink-matcher');
|
|
5
6
|
const {buildFilter, filterMatches, routerTypeOf} = require('./router-filter');
|
|
7
|
+
const {fetchRoutableResources: defaultFetchRoutableResources} = require('./routable-resources');
|
|
6
8
|
const urlConfig = require('./config');
|
|
7
9
|
|
|
8
|
-
import type {Resource, UrlOptions, LazyUrlServiceBackend} from './url-service-facade';
|
|
9
10
|
import type {CompiledFilter} from './router-filter';
|
|
10
11
|
import type {PermalinkParams} from './permalink-matcher';
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Routing-level resource. `type` is one of the plural router keys
|
|
15
|
+
* ('posts', 'pages', 'tags', 'authors'). Concrete records carry additional
|
|
16
|
+
* fields (slug, published_at, primary_tag, ...) read by the permalink
|
|
17
|
+
* templates and router filters.
|
|
18
|
+
*/
|
|
19
|
+
export interface Resource {
|
|
20
|
+
type: string;
|
|
21
|
+
id: string;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UrlOptions {
|
|
26
|
+
absolute?: boolean;
|
|
27
|
+
withSubdirectory?: boolean;
|
|
28
|
+
// Diagnostic only: the producing api endpoint and the fetch shape its
|
|
29
|
+
// input serializer settled on, threaded from the output serializer so a
|
|
30
|
+
// thin-resource report names what under-fetched. Ignored by URL
|
|
31
|
+
// generation; read only into that report.
|
|
32
|
+
serializerContext?: {
|
|
33
|
+
apiType?: string;
|
|
34
|
+
docName?: string;
|
|
35
|
+
method?: string;
|
|
36
|
+
withRelated?: unknown;
|
|
37
|
+
columns?: unknown;
|
|
38
|
+
forcedUrlRelations?: unknown;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* On-demand enumeration of the routable rows of a type. Injectable so tests
|
|
44
|
+
* can drive `getRoutableResources` without a database.
|
|
45
|
+
*/
|
|
46
|
+
export type FetchRoutableResources = (
|
|
47
|
+
type: string,
|
|
48
|
+
options: {columns?: string[]; requiredFields?: string[]; requiredRelations?: string[]}
|
|
49
|
+
) => Promise<Array<Record<string, unknown>>>;
|
|
50
|
+
|
|
12
51
|
export type ResourceLookupParams = {id: string} | {slug: string};
|
|
13
52
|
|
|
14
53
|
export type FindResource = (
|
|
@@ -24,17 +63,15 @@ interface RouterConfig {
|
|
|
24
63
|
compiledFilter: CompiledFilter | null;
|
|
25
64
|
}
|
|
26
65
|
|
|
27
|
-
// Per-resource-type
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
// record columns each filter reads; a resource that reaches URL generation
|
|
32
|
-
// must carry them (a thin one is rejected — see _assertBaseFieldsPresent).
|
|
66
|
+
// Per-resource-type gate on which rows get a URL at all — the forward-lookup
|
|
67
|
+
// counterpart of the fetch filters in routable-resources.js. `fields` lists
|
|
68
|
+
// the record columns each filter reads; a resource that reaches URL generation
|
|
69
|
+
// must carry them (see _missingBaseFields).
|
|
33
70
|
//
|
|
34
71
|
// Authors are intentionally absent: users.visibility is schema-pinned to
|
|
35
|
-
// 'public' (isIn: [['public']]), so
|
|
36
|
-
//
|
|
37
|
-
//
|
|
72
|
+
// 'public' (isIn: [['public']]), so the visibility:public author filter never
|
|
73
|
+
// excludes anyone — every author is routable, and serialized authors drop
|
|
74
|
+
// visibility anyway (#10438).
|
|
38
75
|
const BASE_FILTERS: Record<string, {filter: string; fields: string[]}> = {
|
|
39
76
|
posts: {filter: 'status:published+type:post', fields: ['status', 'type']},
|
|
40
77
|
pages: {filter: 'status:published+type:page', fields: ['status', 'type']},
|
|
@@ -47,6 +84,16 @@ interface BaseFilter {
|
|
|
47
84
|
fields: string[];
|
|
48
85
|
}
|
|
49
86
|
|
|
87
|
+
// Why a resource could not be routed: the columns it was missing, and which
|
|
88
|
+
// filter needed them. Reported, then degraded to /404/.
|
|
89
|
+
interface ThinResource {
|
|
90
|
+
resourceType: string;
|
|
91
|
+
missing: string[];
|
|
92
|
+
baseFilter?: string;
|
|
93
|
+
routerIdentifier?: string;
|
|
94
|
+
filter?: string | null;
|
|
95
|
+
}
|
|
96
|
+
|
|
50
97
|
function buildBaseFilters(): Map<string, BaseFilter> {
|
|
51
98
|
const baseFilters = new Map<string, BaseFilter>();
|
|
52
99
|
for (const [type, {filter, fields}] of Object.entries(BASE_FILTERS)) {
|
|
@@ -58,19 +105,18 @@ function buildBaseFilters(): Map<string, BaseFilter> {
|
|
|
58
105
|
return baseFilters;
|
|
59
106
|
}
|
|
60
107
|
|
|
61
|
-
// Columns
|
|
62
|
-
// services/url/config.js)
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
// router filters (and neither requires nor force-loads
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// replaces it. The base filter is unaffected — it runs against the full record.
|
|
108
|
+
// Columns a router filter must not see (the `exclude` lists in
|
|
109
|
+
// services/url/config.js), keyed by resourceType. Ghost previously answered
|
|
110
|
+
// URLs from a precomputed cache that dropped these columns, so a filter
|
|
111
|
+
// referencing one matched it as absent — NQL reads absent as null. This
|
|
112
|
+
// service loads full records and would see the real value, so it strips them
|
|
113
|
+
// before evaluating router filters (and neither requires nor force-loads
|
|
114
|
+
// them), keeping routes.yaml files that rely on that behaviour working. The
|
|
115
|
+
// base filter above is unaffected — it runs against the full record.
|
|
70
116
|
function buildExcludedFilterFields(): Map<string, Set<string>> {
|
|
71
117
|
const excluded = new Map<string, Set<string>>();
|
|
72
118
|
for (const entry of urlConfig) {
|
|
73
|
-
const exclude = entry?.
|
|
119
|
+
const exclude = entry?.exclude;
|
|
74
120
|
if (Array.isArray(exclude)) {
|
|
75
121
|
excluded.set(entry.type, new Set(exclude));
|
|
76
122
|
}
|
|
@@ -80,10 +126,19 @@ function buildExcludedFilterFields(): Map<string, Set<string>> {
|
|
|
80
126
|
|
|
81
127
|
const EMPTY_FIELD_SET: ReadonlySet<string> = new Set();
|
|
82
128
|
|
|
129
|
+
// 1, 10, 100, ... — exact for integers, unlike a log10 comparison.
|
|
130
|
+
function isPowerOfTen(n: number): boolean {
|
|
131
|
+
let power = 1;
|
|
132
|
+
while (power < n) {
|
|
133
|
+
power *= 10;
|
|
134
|
+
}
|
|
135
|
+
return power === n;
|
|
136
|
+
}
|
|
137
|
+
|
|
83
138
|
// Relation roots are loaded via getRequiredRelations (as withRelated), not as
|
|
84
139
|
// scalar columns; `page`/`type` are the router-type discriminator, always set
|
|
85
140
|
// on the resource. Everything else a router filter references is a scalar
|
|
86
|
-
// own-column the resource must carry to be routed
|
|
141
|
+
// own-column the resource must carry to be routed.
|
|
87
142
|
const FILTER_NON_SCALAR_FIELDS = new Set([
|
|
88
143
|
'tag', 'tags', 'author', 'authors', 'primary_tag', 'primary_author', 'page', 'type'
|
|
89
144
|
]);
|
|
@@ -115,26 +170,38 @@ function filterScalarFields(filter: string | null): string[] {
|
|
|
115
170
|
interface LazyUrlServiceDeps {
|
|
116
171
|
urlUtils?: typeof localUtils;
|
|
117
172
|
findResource: FindResource;
|
|
173
|
+
fetchRoutableResources?: FetchRoutableResources;
|
|
118
174
|
}
|
|
119
175
|
|
|
120
176
|
const ROUTER_TYPE_TO_DB_TYPE: Record<string, string> = {posts: 'post', pages: 'page'};
|
|
121
177
|
|
|
122
178
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
179
|
+
* Ghost's URL service: computes URLs and ownership per call from the
|
|
180
|
+
* registered router configs. Forward lookups are pure; resolveUrl and
|
|
181
|
+
* getRoutableResources are the only DB-touching paths, and only through the
|
|
182
|
+
* injected findResource / fetchRoutableResources hooks.
|
|
127
183
|
*/
|
|
128
|
-
export class LazyUrlService
|
|
184
|
+
export class LazyUrlService {
|
|
129
185
|
private urlUtils: typeof localUtils;
|
|
130
186
|
private findResource: FindResource;
|
|
187
|
+
private fetchRoutableResources: FetchRoutableResources;
|
|
131
188
|
// Router configs in registration order, which is also their priority.
|
|
132
189
|
private routerConfigs: RouterConfig[];
|
|
133
190
|
private requiredRelations: string[] | null;
|
|
134
191
|
private baseFilters: Map<string, BaseFilter>;
|
|
135
192
|
private excludedFilterFields: Map<string, Set<string>>;
|
|
136
|
-
|
|
137
|
-
|
|
193
|
+
// True once routers have been registered. Cleared by reset() so the
|
|
194
|
+
// route-reload window re-gates the maintenance middleware.
|
|
195
|
+
private routersReady: boolean;
|
|
196
|
+
// How many times each thin-resource cause has been seen — see
|
|
197
|
+
// _degradeThinResource.
|
|
198
|
+
private reportedThinResources: Map<string, number>;
|
|
199
|
+
|
|
200
|
+
constructor({
|
|
201
|
+
urlUtils = localUtils,
|
|
202
|
+
findResource,
|
|
203
|
+
fetchRoutableResources = defaultFetchRoutableResources
|
|
204
|
+
}: LazyUrlServiceDeps) {
|
|
138
205
|
if (typeof findResource !== 'function') {
|
|
139
206
|
throw new errors.IncorrectUsageError({
|
|
140
207
|
message: 'LazyUrlService requires a findResource function'
|
|
@@ -142,10 +209,13 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
142
209
|
}
|
|
143
210
|
this.urlUtils = urlUtils;
|
|
144
211
|
this.findResource = findResource;
|
|
212
|
+
this.fetchRoutableResources = fetchRoutableResources;
|
|
145
213
|
this.routerConfigs = [];
|
|
146
214
|
this.requiredRelations = null;
|
|
147
215
|
this.baseFilters = buildBaseFilters();
|
|
148
216
|
this.excludedFilterFields = buildExcludedFilterFields();
|
|
217
|
+
this.routersReady = false;
|
|
218
|
+
this.reportedThinResources = new Map();
|
|
149
219
|
}
|
|
150
220
|
|
|
151
221
|
onRouterAddedType(identifier: string, filter: string | null, resourceType: string, permalink: string): void {
|
|
@@ -158,6 +228,7 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
158
228
|
compiledFilter: buildFilter(filter)
|
|
159
229
|
});
|
|
160
230
|
this.requiredRelations = null;
|
|
231
|
+
this.routersReady = true;
|
|
161
232
|
}
|
|
162
233
|
|
|
163
234
|
onRouterUpdated(): void {
|
|
@@ -169,6 +240,8 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
169
240
|
reset(): void {
|
|
170
241
|
this.routerConfigs = [];
|
|
171
242
|
this.requiredRelations = null;
|
|
243
|
+
this.routersReady = false;
|
|
244
|
+
this.reportedThinResources.clear();
|
|
172
245
|
}
|
|
173
246
|
|
|
174
247
|
getRequiredRelations(): string[] {
|
|
@@ -198,10 +271,10 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
198
271
|
return [...this.requiredRelations];
|
|
199
272
|
}
|
|
200
273
|
|
|
201
|
-
// Columns a resource of this type must carry for the
|
|
274
|
+
// Columns a resource of this type must carry for the service to build
|
|
202
275
|
// its URL: its base-filter columns plus the scalar columns its routers'
|
|
203
276
|
// permalinks substitute and filters read. Relations are covered separately
|
|
204
|
-
// by getRequiredRelations
|
|
277
|
+
// by getRequiredRelations.
|
|
205
278
|
getRequiredFields(routerType: string): string[] {
|
|
206
279
|
const fields = new Set<string>();
|
|
207
280
|
const excluded = this._excludedFilterFieldsFor(routerType);
|
|
@@ -233,9 +306,10 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
233
306
|
fields.add(computed);
|
|
234
307
|
}
|
|
235
308
|
}
|
|
236
|
-
// Skip columns
|
|
237
|
-
//
|
|
238
|
-
//
|
|
309
|
+
// Skip the columns a router filter must not see: their filters are
|
|
310
|
+
// evaluated against an absent value, so requiring them would
|
|
311
|
+
// force-load a column that can't affect the result (see
|
|
312
|
+
// buildExcludedFilterFields).
|
|
239
313
|
filterScalarFields(config.filter).forEach((field) => {
|
|
240
314
|
if (!excluded.has(field)) {
|
|
241
315
|
fields.add(field);
|
|
@@ -246,30 +320,50 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
246
320
|
}
|
|
247
321
|
|
|
248
322
|
hasFinished(): boolean {
|
|
249
|
-
return
|
|
323
|
+
return this.routersReady;
|
|
250
324
|
}
|
|
251
325
|
|
|
326
|
+
/**
|
|
327
|
+
* All routable rows of a type. The columns URL computation needs come
|
|
328
|
+
* from the active routing config, so the rows are never thin for it;
|
|
329
|
+
* callers name any extra columns they want back.
|
|
330
|
+
*/
|
|
331
|
+
async getRoutableResources(type: string, {columns = []}: {columns?: string[]} = {}): Promise<Array<Record<string, unknown>>> {
|
|
332
|
+
return this.fetchRoutableResources(type, {
|
|
333
|
+
columns,
|
|
334
|
+
requiredFields: this.getRequiredFields(type),
|
|
335
|
+
requiredRelations: this.getRequiredRelations()
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* A thin resource — one missing a column its base filter or a router
|
|
341
|
+
* filter reads — is a caller bug: we cannot tell whether it routes. It is
|
|
342
|
+
* reported and degraded to /404/ rather than thrown, because 500ing a page
|
|
343
|
+
* that does route is worse than a 404 on one that may not.
|
|
344
|
+
*/
|
|
252
345
|
getUrlForResource(resource: Resource, options: UrlOptions = {}): string {
|
|
253
346
|
const routerType = routerTypeOf(resource);
|
|
254
347
|
if (!routerType) {
|
|
255
|
-
return this.
|
|
348
|
+
return this.notFoundUrl(options);
|
|
256
349
|
}
|
|
257
350
|
|
|
258
351
|
const record = this._recordForFilter(resource);
|
|
259
|
-
// The router (collection) filter is evaluated against
|
|
260
|
-
// column set; the base filter keeps the
|
|
261
|
-
//
|
|
352
|
+
// The router (collection) filter is evaluated against the reduced
|
|
353
|
+
// column set (see buildExcludedFilterFields); the base filter keeps the
|
|
354
|
+
// full record, since it reads status / type / visibility.
|
|
262
355
|
const filterRecord = this._recordForRouterFilter(record, routerType);
|
|
263
356
|
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
// only when a router for the type exists, since otherwise the resource
|
|
268
|
-
// 404s regardless.
|
|
357
|
+
// Only resources that pass the per-type base filter (visibility:public
|
|
358
|
+
// tags, status:published posts) get a URL. Checked only when a router
|
|
359
|
+
// for the type exists, since otherwise the resource 404s regardless.
|
|
269
360
|
if (this._hasRouterForType(routerType)) {
|
|
270
|
-
this.
|
|
361
|
+
const missing = this._missingBaseFields(routerType, resource);
|
|
362
|
+
if (missing) {
|
|
363
|
+
return this._degradeThinResource(resource, missing, options);
|
|
364
|
+
}
|
|
271
365
|
if (!this._baseFilterMatches(routerType, record)) {
|
|
272
|
-
return this.
|
|
366
|
+
return this.notFoundUrl(options);
|
|
273
367
|
}
|
|
274
368
|
}
|
|
275
369
|
|
|
@@ -277,13 +371,16 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
277
371
|
if (config.resourceType !== routerType) {
|
|
278
372
|
continue;
|
|
279
373
|
}
|
|
280
|
-
this.
|
|
374
|
+
const missing = this._missingRouterFields(config, resource, routerType);
|
|
375
|
+
if (missing) {
|
|
376
|
+
return this._degradeThinResource(resource, missing, options);
|
|
377
|
+
}
|
|
281
378
|
if (filterMatches(config.compiledFilter, filterRecord)) {
|
|
282
379
|
const path = this.urlUtils.replacePermalink(config.permalink, resource);
|
|
283
380
|
return this._formatPath(path, options);
|
|
284
381
|
}
|
|
285
382
|
}
|
|
286
|
-
return this.
|
|
383
|
+
return this.notFoundUrl(options);
|
|
287
384
|
}
|
|
288
385
|
|
|
289
386
|
ownsResource(routerIdentifier: string, resource: Resource | null): boolean {
|
|
@@ -294,15 +391,15 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
294
391
|
if (!routerType) {
|
|
295
392
|
return false;
|
|
296
393
|
}
|
|
297
|
-
// A resource failing its base filter
|
|
298
|
-
//
|
|
394
|
+
// A resource failing its base filter has no URL, so no router owns it.
|
|
395
|
+
// Mirrors the base-filter gate in getUrlForResource.
|
|
299
396
|
const record = this._recordForFilter(resource);
|
|
300
397
|
if (!this._baseFilterMatches(routerType, record)) {
|
|
301
398
|
return false;
|
|
302
399
|
}
|
|
303
400
|
const filterRecord = this._recordForRouterFilter(record, routerType);
|
|
304
401
|
// Ownership is exclusive: only the first matching router of the type
|
|
305
|
-
// owns the resource
|
|
402
|
+
// owns the resource.
|
|
306
403
|
const owner = this.routerConfigs.find(
|
|
307
404
|
c => c.resourceType === routerType && filterMatches(c.compiledFilter, filterRecord)
|
|
308
405
|
);
|
|
@@ -352,11 +449,11 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
352
449
|
return null;
|
|
353
450
|
}
|
|
354
451
|
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
// captured params match it. Without this, derived/relation
|
|
358
|
-
// query can't filter on (year/month, primary_tag) would
|
|
359
|
-
// 200-ing a URL
|
|
452
|
+
// Only a URL that equals the resource's own generated (canonical) URL
|
|
453
|
+
// resolves, so we regenerate the record's URL for this permalink and
|
|
454
|
+
// confirm the captured params match it. Without this, derived/relation
|
|
455
|
+
// segments the query can't filter on (year/month, primary_tag) would
|
|
456
|
+
// resolve any slug, 200-ing a URL that has no page.
|
|
360
457
|
private _matchesCanonicalUrl(config: RouterConfig, params: PermalinkParams, resource: Record<string, unknown>): boolean {
|
|
361
458
|
const canonicalPath = this.urlUtils.replacePermalink(config.permalink, resource);
|
|
362
459
|
const canonicalParams = matchPermalink(config.permalink, canonicalPath);
|
|
@@ -368,6 +465,52 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
368
465
|
return Object.keys(captured).every(key => canonical[key] === captured[key]);
|
|
369
466
|
}
|
|
370
467
|
|
|
468
|
+
// Reports which columns were missing alongside what the caller handed over
|
|
469
|
+
// — the resource's shape, and what the active routing config needs — so
|
|
470
|
+
// the fetch that under-fetched can be found from the log line alone.
|
|
471
|
+
//
|
|
472
|
+
// Reported on the first occurrence of each distinct cause, then at each
|
|
473
|
+
// order of magnitude. A caller that under-fetches does so for every row it
|
|
474
|
+
// serializes, so a browse over a large collection would otherwise emit one
|
|
475
|
+
// error per row — but reporting only once would hide that the problem is
|
|
476
|
+
// still happening, and how widely. `occurrences` carries that instead.
|
|
477
|
+
// Cleared on reset(), since a new routing config can make a previously
|
|
478
|
+
// thin resource fine (or newly broken).
|
|
479
|
+
private _degradeThinResource(resource: Resource, thin: ThinResource, options: UrlOptions): string {
|
|
480
|
+
const producedBy = options.serializerContext;
|
|
481
|
+
// The producer is part of the key: two endpoints under-fetching the
|
|
482
|
+
// same way are two bugs, and the endpoint set is small enough that
|
|
483
|
+
// this cannot flood.
|
|
484
|
+
const key = [
|
|
485
|
+
thin.resourceType,
|
|
486
|
+
thin.routerIdentifier ?? '',
|
|
487
|
+
thin.missing.join(','),
|
|
488
|
+
producedBy ? `${producedBy.apiType}:${producedBy.docName}:${producedBy.method}` : ''
|
|
489
|
+
].join('|');
|
|
490
|
+
const occurrences = (this.reportedThinResources.get(key) ?? 0) + 1;
|
|
491
|
+
this.reportedThinResources.set(key, occurrences);
|
|
492
|
+
if (!isPowerOfTen(occurrences)) {
|
|
493
|
+
return this.notFoundUrl(options);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
logging.error(new errors.InternalServerError({
|
|
497
|
+
message: 'URL service could not build a URL, degraded to /404/',
|
|
498
|
+
code: 'LAZY_URL_RESOLUTION_ERROR',
|
|
499
|
+
errorDetails: {
|
|
500
|
+
method: 'getUrlForResource',
|
|
501
|
+
type: resource.type,
|
|
502
|
+
id: resource.id,
|
|
503
|
+
status: (resource as Record<string, unknown>).status,
|
|
504
|
+
resourceKeys: Object.keys(resource),
|
|
505
|
+
requiredRelations: this.getRequiredRelations(),
|
|
506
|
+
occurrences,
|
|
507
|
+
...(producedBy ? {serializer: producedBy} : {}),
|
|
508
|
+
...thin
|
|
509
|
+
}
|
|
510
|
+
}));
|
|
511
|
+
return this.notFoundUrl(options);
|
|
512
|
+
}
|
|
513
|
+
|
|
371
514
|
private _hasRouterForType(routerType: string): boolean {
|
|
372
515
|
return this.routerConfigs.some(config => config.resourceType === routerType);
|
|
373
516
|
}
|
|
@@ -382,33 +525,24 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
382
525
|
|
|
383
526
|
// A resource that reaches URL generation must carry the columns its base
|
|
384
527
|
// filter reads (status for posts/pages, visibility for tags) — production
|
|
385
|
-
// callers always do
|
|
386
|
-
// them the filter can't be evaluated
|
|
387
|
-
//
|
|
388
|
-
private
|
|
528
|
+
// callers always do, via full models or the serializers' force-load.
|
|
529
|
+
// Without them the filter can't be evaluated at all, so we cannot tell a
|
|
530
|
+
// genuine miss from a caller that under-fetched.
|
|
531
|
+
private _missingBaseFields(routerType: string, resource: Resource): ThinResource | null {
|
|
389
532
|
const base = this.baseFilters.get(routerType);
|
|
390
533
|
if (!base) {
|
|
391
|
-
return;
|
|
534
|
+
return null;
|
|
392
535
|
}
|
|
393
536
|
const r = resource as Record<string, unknown>;
|
|
394
537
|
const missing = base.fields.filter(field => r[field] === undefined);
|
|
395
538
|
if (missing.length === 0) {
|
|
396
|
-
return;
|
|
539
|
+
return null;
|
|
397
540
|
}
|
|
398
|
-
|
|
399
|
-
message: 'Thin resource passed to LazyUrlService.getUrlForResource',
|
|
400
|
-
code: 'LAZY_URL_THIN_RESOURCE',
|
|
401
|
-
errorDetails: {
|
|
402
|
-
resourceType: routerType,
|
|
403
|
-
resourceId: resource.id,
|
|
404
|
-
baseFilter: base.filter,
|
|
405
|
-
missing
|
|
406
|
-
}
|
|
407
|
-
});
|
|
541
|
+
return {resourceType: routerType, baseFilter: base.filter, missing};
|
|
408
542
|
}
|
|
409
543
|
|
|
410
544
|
// Normalizes the plural router type to the singular DB value for filter
|
|
411
|
-
// evaluation only, so page
|
|
545
|
+
// evaluation only, so `page:` filters in routes.yaml match.
|
|
412
546
|
private _recordForFilter(resource: Resource): Record<string, unknown> {
|
|
413
547
|
const record = resource as Record<string, unknown>;
|
|
414
548
|
const dbType = ROUTER_TYPE_TO_DB_TYPE[record.type as string];
|
|
@@ -419,8 +553,7 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
419
553
|
return this.excludedFilterFields.get(routerType) ?? EMPTY_FIELD_SET;
|
|
420
554
|
}
|
|
421
555
|
|
|
422
|
-
// Strips the columns
|
|
423
|
-
// filter is evaluated against the same reduced shape eager uses — an
|
|
556
|
+
// Strips the columns a router/collection filter must not see, so an
|
|
424
557
|
// excluded column reads as absent (→ null in NQL) rather than its real
|
|
425
558
|
// value. Only affects filters that reference an excluded column; every
|
|
426
559
|
// other filter sees an identical record.
|
|
@@ -448,8 +581,16 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
448
581
|
return path;
|
|
449
582
|
}
|
|
450
583
|
|
|
451
|
-
|
|
452
|
-
|
|
584
|
+
/**
|
|
585
|
+
* The /404/ a resource gets when nothing routes it, formatted for the given
|
|
586
|
+
* options. Deliberately omits the argument `_formatPath` passes.
|
|
587
|
+
*
|
|
588
|
+
* The subdirectory is not lost by that omission: `createUrl` takes it from
|
|
589
|
+
* its own base whenever the url is relative, so a subdirectory install gets
|
|
590
|
+
* `/blog/404/` from here either way. The third argument `_formatPath`
|
|
591
|
+
* passes is `trailingSlash`, and `/404/` already ends in one.
|
|
592
|
+
*/
|
|
593
|
+
private notFoundUrl(options: UrlOptions = {}): string {
|
|
453
594
|
if (options.absolute) {
|
|
454
595
|
return this.urlUtils.createUrl('/404/', options.absolute);
|
|
455
596
|
}
|
|
@@ -460,13 +601,13 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
460
601
|
}
|
|
461
602
|
|
|
462
603
|
// A filtered router that references a relation the resource doesn't carry
|
|
463
|
-
// can't be evaluated
|
|
464
|
-
//
|
|
465
|
-
// a thin one is
|
|
466
|
-
//
|
|
467
|
-
private
|
|
604
|
+
// can't be evaluated, so the resource would 404 for a reason that has
|
|
605
|
+
// nothing to do with the routing config. Callers must hand over
|
|
606
|
+
// fully-inflated resources; a thin one is reported rather than silently
|
|
607
|
+
// 404'd (see getUrlForResource).
|
|
608
|
+
private _missingRouterFields(config: RouterConfig, resource: Resource, routerType: string): ThinResource | null {
|
|
468
609
|
if (!config.filter) {
|
|
469
|
-
return;
|
|
610
|
+
return null;
|
|
470
611
|
}
|
|
471
612
|
const r = resource as Record<string, unknown>;
|
|
472
613
|
const missing: string[] = [];
|
|
@@ -482,8 +623,8 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
482
623
|
if (/\bprimary_author\b/.test(config.filter) && r.primary_author === undefined) {
|
|
483
624
|
missing.push('primary_author');
|
|
484
625
|
}
|
|
485
|
-
//
|
|
486
|
-
//
|
|
626
|
+
// Excluded columns are never required — their filters match them as
|
|
627
|
+
// absent, so a resource lacking one is not thin here.
|
|
487
628
|
const excluded = this._excludedFilterFieldsFor(routerType);
|
|
488
629
|
for (const field of filterScalarFields(config.filter)) {
|
|
489
630
|
if (!excluded.has(field) && r[field] === undefined) {
|
|
@@ -491,19 +632,14 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
491
632
|
}
|
|
492
633
|
}
|
|
493
634
|
if (missing.length === 0) {
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
routerIdentifier: config.identifier,
|
|
503
|
-
filter: config.filter,
|
|
504
|
-
missing
|
|
505
|
-
}
|
|
506
|
-
});
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
resourceType: routerType,
|
|
639
|
+
routerIdentifier: config.identifier,
|
|
640
|
+
filter: config.filter,
|
|
641
|
+
missing
|
|
642
|
+
};
|
|
507
643
|
}
|
|
508
644
|
}
|
|
509
645
|
|