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,21 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.DbCustomFieldLeaf = exports.DbCustomFieldValue = exports.DbCustomField = exports.FieldStatusSchema = exports.FIELD_STATUS = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const custom_field_types_1 = require("@tryghost/custom-field-types");
|
|
6
6
|
const db_date_1 = require("../../lib/db-date");
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// schema.js's `isIn` constraint on the column — which is static config and can't
|
|
11
|
-
// import this, so that one stays literal with a pointer back here.
|
|
7
|
+
// `archived` is soft: the field drops out of the values path but stays in the definition
|
|
8
|
+
// list so it can be renamed, restored or deleted. Mirrors schema.js's `isIn` on the
|
|
9
|
+
// column, which is static config and cannot import this.
|
|
12
10
|
exports.FIELD_STATUS = { active: 'active', archived: 'archived' };
|
|
13
11
|
exports.FieldStatusSchema = zod_1.z.enum([exports.FIELD_STATUS.active, exports.FIELD_STATUS.archived]);
|
|
14
|
-
// The
|
|
15
|
-
//
|
|
16
|
-
// only stores registered types), so the row already carries the narrow type and
|
|
17
|
-
// the definition codec needs no cast. `status` travels with the row: it's part of
|
|
18
|
-
// the read projection so the definition list can group active vs archived.
|
|
12
|
+
// The single source for the read projection and the knex table type below. `type` parses
|
|
13
|
+
// as the field-type enum, so the row carries the narrow type and no codec needs a cast.
|
|
19
14
|
exports.DbCustomField = zod_1.z.object({
|
|
20
15
|
id: zod_1.z.string(),
|
|
21
16
|
key: zod_1.z.string(),
|
|
@@ -25,32 +20,28 @@ exports.DbCustomField = zod_1.z.object({
|
|
|
25
20
|
created_at: db_date_1.DbDate,
|
|
26
21
|
updated_at: db_date_1.DbDate.nullable()
|
|
27
22
|
});
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// codec's business (see storage.ts), so they're plain nullable strings here.
|
|
23
|
+
// One part of a member's value. What a `path` means is storage.ts's business, so the row
|
|
24
|
+
// carries it as a plain string.
|
|
31
25
|
exports.DbCustomFieldValue = zod_1.z.object({
|
|
32
26
|
id: zod_1.z.string(),
|
|
33
27
|
custom_field_id: zod_1.z.string(),
|
|
34
28
|
member_id: zod_1.z.string(),
|
|
29
|
+
path: zod_1.z.string(),
|
|
30
|
+
// Nullable like the column, though nothing here writes a null: a part with no value
|
|
31
|
+
// has no row.
|
|
35
32
|
value_text: zod_1.z.string().nullable(),
|
|
36
|
-
value_json: zod_1.z.string().nullable(),
|
|
37
33
|
created_at: db_date_1.DbDate,
|
|
38
34
|
updated_at: db_date_1.DbDate.nullable()
|
|
39
35
|
});
|
|
40
|
-
//
|
|
41
|
-
// sub-fields a composite has is its field type's business, not the row's.
|
|
36
|
+
// The field's key travels with the row so a value assembles without a second lookup.
|
|
42
37
|
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
exports.
|
|
47
|
-
// The value join a read needs: the field's identity and type travel with the
|
|
48
|
-
// stored columns, so a row can be decoded without a second lookup. `type` is
|
|
49
|
-
// parsed as the field-type enum, which narrows it with no cast.
|
|
50
|
-
exports.DbCustomFieldValueWithField = zod_1.z.object({
|
|
38
|
+
// `type` takes no part in the assembly and is here as a gate: a value whose type has left
|
|
39
|
+
// the catalog is one the definitions list no longer returns either, so failing to parse
|
|
40
|
+
// is what drops it.
|
|
41
|
+
exports.DbCustomFieldLeaf = zod_1.z.object({
|
|
51
42
|
member_id: zod_1.z.string(),
|
|
52
43
|
key: zod_1.z.string(),
|
|
53
44
|
type: custom_field_types_1.FieldTypeSchema,
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
path: zod_1.z.string(),
|
|
46
|
+
value_text: zod_1.z.string()
|
|
56
47
|
});
|
|
@@ -3,20 +3,15 @@ import type {Knex} from 'knex';
|
|
|
3
3
|
import {FieldTypeSchema} from '@tryghost/custom-field-types';
|
|
4
4
|
import {DbDate} from '../../lib/db-date';
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// schema.js's `isIn` constraint on the column — which is static config and can't
|
|
10
|
-
// import this, so that one stays literal with a pointer back here.
|
|
6
|
+
// `archived` is soft: the field drops out of the values path but stays in the definition
|
|
7
|
+
// list so it can be renamed, restored or deleted. Mirrors schema.js's `isIn` on the
|
|
8
|
+
// column, which is static config and cannot import this.
|
|
11
9
|
export const FIELD_STATUS = {active: 'active', archived: 'archived'} as const;
|
|
12
10
|
export type FieldStatus = typeof FIELD_STATUS[keyof typeof FIELD_STATUS];
|
|
13
11
|
export const FieldStatusSchema = z.enum([FIELD_STATUS.active, FIELD_STATUS.archived]);
|
|
14
12
|
|
|
15
|
-
// The
|
|
16
|
-
//
|
|
17
|
-
// only stores registered types), so the row already carries the narrow type and
|
|
18
|
-
// the definition codec needs no cast. `status` travels with the row: it's part of
|
|
19
|
-
// the read projection so the definition list can group active vs archived.
|
|
13
|
+
// The single source for the read projection and the knex table type below. `type` parses
|
|
14
|
+
// as the field-type enum, so the row carries the narrow type and no codec needs a cast.
|
|
20
15
|
export const DbCustomField = z.object({
|
|
21
16
|
id: z.string(),
|
|
22
17
|
key: z.string(),
|
|
@@ -29,38 +24,33 @@ export const DbCustomField = z.object({
|
|
|
29
24
|
|
|
30
25
|
type CustomFieldRow = z.infer<typeof DbCustomField>;
|
|
31
26
|
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
// codec's business (see storage.ts), so they're plain nullable strings here.
|
|
27
|
+
// One part of a member's value. What a `path` means is storage.ts's business, so the row
|
|
28
|
+
// carries it as a plain string.
|
|
35
29
|
export const DbCustomFieldValue = z.object({
|
|
36
30
|
id: z.string(),
|
|
37
31
|
custom_field_id: z.string(),
|
|
38
32
|
member_id: z.string(),
|
|
33
|
+
path: z.string(),
|
|
34
|
+
// Nullable like the column, though nothing here writes a null: a part with no value
|
|
35
|
+
// has no row.
|
|
39
36
|
value_text: z.string().nullable(),
|
|
40
|
-
value_json: z.string().nullable(),
|
|
41
37
|
created_at: DbDate,
|
|
42
38
|
updated_at: DbDate.nullable()
|
|
43
39
|
});
|
|
44
40
|
|
|
45
41
|
type CustomFieldValueRow = z.infer<typeof DbCustomFieldValue>;
|
|
46
42
|
|
|
47
|
-
//
|
|
48
|
-
// sub-fields a composite has is its field type's business, not the row's.
|
|
43
|
+
// The field's key travels with the row so a value assembles without a second lookup.
|
|
49
44
|
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
export const
|
|
54
|
-
|
|
55
|
-
// The value join a read needs: the field's identity and type travel with the
|
|
56
|
-
// stored columns, so a row can be decoded without a second lookup. `type` is
|
|
57
|
-
// parsed as the field-type enum, which narrows it with no cast.
|
|
58
|
-
export const DbCustomFieldValueWithField = z.object({
|
|
45
|
+
// `type` takes no part in the assembly and is here as a gate: a value whose type has left
|
|
46
|
+
// the catalog is one the definitions list no longer returns either, so failing to parse
|
|
47
|
+
// is what drops it.
|
|
48
|
+
export const DbCustomFieldLeaf = z.object({
|
|
59
49
|
member_id: z.string(),
|
|
60
50
|
key: z.string(),
|
|
61
51
|
type: FieldTypeSchema,
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
path: z.string(),
|
|
53
|
+
value_text: z.string()
|
|
64
54
|
});
|
|
65
55
|
|
|
66
56
|
declare module 'knex/types/tables' {
|
|
@@ -1,35 +1,102 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
exports.storageColumnsFor = storageColumnsFor;
|
|
5
|
-
const custom_field_types_1 = require("@tryghost/custom-field-types");
|
|
6
|
-
const STORAGE_CODECS = {
|
|
7
|
-
// The value is already a string by the time it gets here — the field type's
|
|
8
|
-
// schema validated it — so encoding is identity.
|
|
9
|
-
text: {
|
|
10
|
-
column: 'value_text',
|
|
11
|
-
encode: value => String(value),
|
|
12
|
-
decode: stored => stored
|
|
13
|
-
},
|
|
14
|
-
json: {
|
|
15
|
-
column: 'value_json',
|
|
16
|
-
encode: value => JSON.stringify(value),
|
|
17
|
-
decode: stored => JSON.parse(stored)
|
|
18
|
-
}
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
4
|
};
|
|
20
|
-
|
|
21
|
-
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ROOT_PATH = void 0;
|
|
7
|
+
exports.leavesFor = leavesFor;
|
|
8
|
+
exports.leavesToWrite = leavesToWrite;
|
|
9
|
+
exports.valueFromLeaves = valueFromLeaves;
|
|
10
|
+
exports.valuesFromLeaves = valuesFromLeaves;
|
|
11
|
+
const errors_1 = __importDefault(require("@tryghost/errors"));
|
|
12
|
+
// A value is a tree; the database stores one row per leaf, under the path addressing it.
|
|
13
|
+
// A value with no parts is a single leaf at the root, a part is a leaf under its key, and
|
|
14
|
+
// a nested part is addressed with a dotted path.
|
|
15
|
+
//
|
|
16
|
+
// Nothing here may consult a field type. Values arrive already parsed against their own,
|
|
17
|
+
// and rows are rebuilt as what they were written as rather than as what the type says
|
|
18
|
+
// today, so a type that changes shape does not rewrite what members already have.
|
|
19
|
+
/** Also the notation a segment filter addresses a part by, so the two cannot drift. */
|
|
20
|
+
const SEPARATOR = '.';
|
|
21
|
+
/** Empty rather than null: a unique index does not constrain nulls, which would let the same leaf be stored twice. */
|
|
22
|
+
exports.ROOT_PATH = '';
|
|
23
|
+
function isRecord(value) {
|
|
24
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
+
* Values are rebuilt into plain objects, so a segment naming something every object
|
|
28
|
+
* already has would reach the prototype instead of the value — and a write through
|
|
29
|
+
* `__proto__` reaches every object in the process. Paths come out of the database, so
|
|
30
|
+
* this holds here rather than trusting whatever put them there.
|
|
27
31
|
*/
|
|
28
|
-
function
|
|
29
|
-
|
|
32
|
+
function isSafeSegment(segment) {
|
|
33
|
+
return !(segment in Object.prototype);
|
|
34
|
+
}
|
|
35
|
+
/** Every leaf a value names, at any depth, including the ones it names as empty. */
|
|
36
|
+
function leavesFor(value, path = exports.ROOT_PATH) {
|
|
37
|
+
if (typeof value === 'string') {
|
|
38
|
+
return [{ path, value_text: value }];
|
|
39
|
+
}
|
|
40
|
+
// Thrown rather than stored: the read path rejects a non-string row, so writing one
|
|
41
|
+
// loses the value with nothing to say it ever arrived.
|
|
42
|
+
if (!isRecord(value)) {
|
|
43
|
+
throw new errors_1.default.IncorrectUsageError({
|
|
44
|
+
message: `A custom field value must be a string or a record of them, not ${value === null ? 'null' : typeof value}.`
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// Undefined means the value does not name the part, which is not the same as naming
|
|
48
|
+
// it empty: the first leaves what is stored alone, the second clears it.
|
|
49
|
+
return Object.entries(value)
|
|
50
|
+
.filter(([, part]) => part !== undefined)
|
|
51
|
+
.flatMap(([key, part]) => leavesFor(part, path === exports.ROOT_PATH ? key : `${path}${SEPARATOR}${key}`));
|
|
52
|
+
}
|
|
53
|
+
/** A value split into the leaves it sets and the paths it clears. */
|
|
54
|
+
function leavesToWrite(value) {
|
|
55
|
+
const named = leavesFor(value);
|
|
30
56
|
return {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
[codec.column]: codec.encode(value)
|
|
57
|
+
set: named.filter(leaf => leaf.value_text !== ''),
|
|
58
|
+
cleared: named.filter(leaf => leaf.value_text === '').map(leaf => leaf.path)
|
|
34
59
|
};
|
|
35
60
|
}
|
|
61
|
+
/** The value a set of leaves adds up to, rebuilt to whatever depth their paths describe. */
|
|
62
|
+
function valueFromLeaves(leaves) {
|
|
63
|
+
const root = leaves.find(leaf => leaf.path === exports.ROOT_PATH);
|
|
64
|
+
if (root) {
|
|
65
|
+
return root.value_text;
|
|
66
|
+
}
|
|
67
|
+
const value = {};
|
|
68
|
+
for (const leaf of leaves) {
|
|
69
|
+
const segments = leaf.path.split(SEPARATOR);
|
|
70
|
+
if (!segments.every(isSafeSegment)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const last = segments.pop();
|
|
74
|
+
// A segment already holding a string is replaced rather than descended into: this
|
|
75
|
+
// runs over every member of a list response, so a contradictory pair of paths
|
|
76
|
+
// should cost one odd value rather than the whole response.
|
|
77
|
+
const parent = segments.reduce((target, segment) => {
|
|
78
|
+
if (!isRecord(target[segment])) {
|
|
79
|
+
target[segment] = {};
|
|
80
|
+
}
|
|
81
|
+
return target[segment];
|
|
82
|
+
}, value);
|
|
83
|
+
parent[last] = leaf.value_text;
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
/** Every member's values, keyed by member and then field key; a member with no rows is absent. */
|
|
88
|
+
function valuesFromLeaves(leaves) {
|
|
89
|
+
const byMemberAndField = new Map();
|
|
90
|
+
for (const { member_id: memberId, key, path, value_text: valueText } of leaves) {
|
|
91
|
+
const fields = byMemberAndField.get(memberId) ?? new Map();
|
|
92
|
+
const forField = fields.get(key) ?? [];
|
|
93
|
+
forField.push({ path, value_text: valueText });
|
|
94
|
+
fields.set(key, forField);
|
|
95
|
+
byMemberAndField.set(memberId, fields);
|
|
96
|
+
}
|
|
97
|
+
const byMember = new Map();
|
|
98
|
+
for (const [memberId, fields] of byMemberAndField) {
|
|
99
|
+
byMember.set(memberId, Object.fromEntries([...fields].map(([key, forField]) => [key, valueFromLeaves(forField)])));
|
|
100
|
+
}
|
|
101
|
+
return byMember;
|
|
102
|
+
}
|
|
@@ -1,47 +1,122 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
encode: value => JSON.stringify(value),
|
|
27
|
-
decode: stored => JSON.parse(stored)
|
|
28
|
-
}
|
|
29
|
-
} as const satisfies Record<StorageType, StorageCodec>;
|
|
1
|
+
import errors from '@tryghost/errors';
|
|
2
|
+
|
|
3
|
+
// A value is a tree; the database stores one row per leaf, under the path addressing it.
|
|
4
|
+
// A value with no parts is a single leaf at the root, a part is a leaf under its key, and
|
|
5
|
+
// a nested part is addressed with a dotted path.
|
|
6
|
+
//
|
|
7
|
+
// Nothing here may consult a field type. Values arrive already parsed against their own,
|
|
8
|
+
// and rows are rebuilt as what they were written as rather than as what the type says
|
|
9
|
+
// today, so a type that changes shape does not rewrite what members already have.
|
|
10
|
+
|
|
11
|
+
/** Also the notation a segment filter addresses a part by, so the two cannot drift. */
|
|
12
|
+
const SEPARATOR = '.';
|
|
13
|
+
|
|
14
|
+
/** Empty rather than null: a unique index does not constrain nulls, which would let the same leaf be stored twice. */
|
|
15
|
+
export const ROOT_PATH = '';
|
|
16
|
+
|
|
17
|
+
export interface Leaf {
|
|
18
|
+
path: string;
|
|
19
|
+
value_text: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface StoredLeaf extends Leaf {
|
|
23
|
+
member_id: string;
|
|
24
|
+
key: string;
|
|
25
|
+
}
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
return
|
|
27
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
28
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
33
29
|
}
|
|
34
30
|
|
|
35
31
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
32
|
+
* Values are rebuilt into plain objects, so a segment naming something every object
|
|
33
|
+
* already has would reach the prototype instead of the value — and a write through
|
|
34
|
+
* `__proto__` reaches every object in the process. Paths come out of the database, so
|
|
35
|
+
* this holds here rather than trusting whatever put them there.
|
|
39
36
|
*/
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
function isSafeSegment(segment: string): boolean {
|
|
38
|
+
return !(segment in Object.prototype);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Every leaf a value names, at any depth, including the ones it names as empty. */
|
|
42
|
+
export function leavesFor(value: unknown, path: string = ROOT_PATH): Leaf[] {
|
|
43
|
+
if (typeof value === 'string') {
|
|
44
|
+
return [{path, value_text: value}];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Thrown rather than stored: the read path rejects a non-string row, so writing one
|
|
48
|
+
// loses the value with nothing to say it ever arrived.
|
|
49
|
+
if (!isRecord(value)) {
|
|
50
|
+
throw new errors.IncorrectUsageError({
|
|
51
|
+
message: `A custom field value must be a string or a record of them, not ${value === null ? 'null' : typeof value}.`
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Undefined means the value does not name the part, which is not the same as naming
|
|
56
|
+
// it empty: the first leaves what is stored alone, the second clears it.
|
|
57
|
+
return Object.entries(value)
|
|
58
|
+
.filter(([, part]) => part !== undefined)
|
|
59
|
+
.flatMap(([key, part]) => leavesFor(part, path === ROOT_PATH ? key : `${path}${SEPARATOR}${key}`));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A value split into the leaves it sets and the paths it clears. */
|
|
63
|
+
export function leavesToWrite(value: unknown): {set: Leaf[], cleared: string[]} {
|
|
64
|
+
const named = leavesFor(value);
|
|
65
|
+
|
|
42
66
|
return {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
[codec.column]: codec.encode(value)
|
|
67
|
+
set: named.filter(leaf => leaf.value_text !== ''),
|
|
68
|
+
cleared: named.filter(leaf => leaf.value_text === '').map(leaf => leaf.path)
|
|
46
69
|
};
|
|
47
70
|
}
|
|
71
|
+
|
|
72
|
+
/** The value a set of leaves adds up to, rebuilt to whatever depth their paths describe. */
|
|
73
|
+
export function valueFromLeaves(leaves: readonly Leaf[]): unknown {
|
|
74
|
+
const root = leaves.find(leaf => leaf.path === ROOT_PATH);
|
|
75
|
+
if (root) {
|
|
76
|
+
return root.value_text;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const value: Record<string, unknown> = {};
|
|
80
|
+
for (const leaf of leaves) {
|
|
81
|
+
const segments = leaf.path.split(SEPARATOR);
|
|
82
|
+
if (!segments.every(isSafeSegment)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const last = segments.pop() as string;
|
|
87
|
+
// A segment already holding a string is replaced rather than descended into: this
|
|
88
|
+
// runs over every member of a list response, so a contradictory pair of paths
|
|
89
|
+
// should cost one odd value rather than the whole response.
|
|
90
|
+
const parent = segments.reduce<Record<string, unknown>>((target, segment) => {
|
|
91
|
+
if (!isRecord(target[segment])) {
|
|
92
|
+
target[segment] = {};
|
|
93
|
+
}
|
|
94
|
+
return target[segment] as Record<string, unknown>;
|
|
95
|
+
}, value);
|
|
96
|
+
parent[last] = leaf.value_text;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Every member's values, keyed by member and then field key; a member with no rows is absent. */
|
|
103
|
+
export function valuesFromLeaves(leaves: readonly StoredLeaf[]): Map<string, Record<string, unknown>> {
|
|
104
|
+
const byMemberAndField = new Map<string, Map<string, Leaf[]>>();
|
|
105
|
+
|
|
106
|
+
for (const {member_id: memberId, key, path, value_text: valueText} of leaves) {
|
|
107
|
+
const fields = byMemberAndField.get(memberId) ?? new Map<string, Leaf[]>();
|
|
108
|
+
const forField = fields.get(key) ?? [];
|
|
109
|
+
forField.push({path, value_text: valueText});
|
|
110
|
+
fields.set(key, forField);
|
|
111
|
+
byMemberAndField.set(memberId, fields);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const byMember = new Map<string, Record<string, unknown>>();
|
|
115
|
+
for (const [memberId, fields] of byMemberAndField) {
|
|
116
|
+
byMember.set(memberId, Object.fromEntries(
|
|
117
|
+
[...fields].map(([key, forField]) => [key, valueFromLeaves(forField)])
|
|
118
|
+
));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return byMember;
|
|
122
|
+
}
|