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
|
@@ -14,56 +14,34 @@ const queries_1 = require("./queries");
|
|
|
14
14
|
const storage_1 = require("./storage");
|
|
15
15
|
const FIELDS_TABLE = 'members_custom_fields';
|
|
16
16
|
const VALUES_TABLE = 'members_custom_field_values';
|
|
17
|
-
|
|
18
|
-
// could actually have minted is ever refused by it.
|
|
17
|
+
/** Matches the `members_custom_fields.key` column, so no key a site could hold is refused. */
|
|
19
18
|
const MAX_KEY_LENGTH = 191;
|
|
20
|
-
// Values arrive keyed by field key. Each value stays `unknown` here: it is
|
|
21
|
-
// validated by its own field type's schema, which isn't known until the key is
|
|
22
|
-
// resolved to a definition.
|
|
23
|
-
const ValuesInput = zod_1.z.record(zod_1.z.string().max(MAX_KEY_LENGTH), zod_1.z.unknown());
|
|
24
|
-
// JSON.parse as a total function. Anything unreadable — unparseable text, or the null
|
|
25
|
-
// a nullable column can always return — reads as `undefined`, which no value schema
|
|
26
|
-
// accepts. That way a corrupt blob is turned away by the same check that turns away a
|
|
27
|
-
// well-formed one of the wrong shape, instead of needing a branch of its own.
|
|
28
|
-
function decodeJson(raw) {
|
|
29
|
-
if (raw === null) {
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
return JSON.parse(raw);
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
19
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
20
|
+
* Rows per insert statement, bounded by knex rather than by either database. SQLite takes
|
|
21
|
+
* a multi-row `VALUES` perfectly well; knex's SQLite dialect emits that form only for a
|
|
22
|
+
* single row and compiles anything longer into `INSERT ... SELECT ? UNION ALL SELECT ?`,
|
|
23
|
+
* which SQLite refuses past 500 terms.
|
|
44
24
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
25
|
+
* Open as knex#721 since 2015, with an approved but unmerged fix in knex#5780. This can
|
|
26
|
+
* go when that lands; until then the alternative is hand-written upsert SQL per engine.
|
|
27
|
+
*/
|
|
28
|
+
const UPSERT_CHUNK = 400;
|
|
29
|
+
// Values stay `unknown`: each is validated by its own field type, which is not known
|
|
30
|
+
// until the key is resolved to a definition.
|
|
31
|
+
const ValuesInput = zod_1.z.record(zod_1.z.string().max(MAX_KEY_LENGTH), zod_1.z.unknown());
|
|
32
|
+
/**
|
|
33
|
+
* What a member holds for each defined field. Separate from the definitions service
|
|
34
|
+
* because a value belongs to the member and a definition belongs to the site's settings,
|
|
35
|
+
* which are different aggregates rather than different layers.
|
|
49
36
|
*/
|
|
50
37
|
class CustomFieldValuesService {
|
|
51
38
|
knex;
|
|
52
|
-
/**
|
|
53
|
-
* @private
|
|
54
|
-
* A getter rather than a number: the ceiling is an operator setting that can
|
|
55
|
-
* change between requests.
|
|
56
|
-
*/
|
|
39
|
+
/** A getter, not a number: the ceiling is an operator setting that changes between requests. */
|
|
57
40
|
getMaxDefinitions;
|
|
58
41
|
constructor({ knex, getMaxDefinitions }) {
|
|
59
42
|
this.knex = knex;
|
|
60
43
|
this.getMaxDefinitions = getMaxDefinitions;
|
|
61
44
|
}
|
|
62
|
-
/**
|
|
63
|
-
* The active fields for a set of keys, keyed by key, for resolving a write.
|
|
64
|
-
* Scoped to the keys the caller is actually writing rather than every active
|
|
65
|
-
* field, since a publisher may have defined many and an edit touches few.
|
|
66
|
-
*/
|
|
67
45
|
async activeFieldsByKey(keys) {
|
|
68
46
|
if (keys.length === 0) {
|
|
69
47
|
return new Map();
|
|
@@ -74,60 +52,36 @@ class CustomFieldValuesService {
|
|
|
74
52
|
return new Map(fields.map(field => [field.key, field]));
|
|
75
53
|
}
|
|
76
54
|
/**
|
|
77
|
-
* Members' values
|
|
78
|
-
* key. A member with no values is absent from the outer map; a field a member
|
|
79
|
-
* has no value for is absent from the inner one — "not set" is the absence of
|
|
80
|
-
* a key, told apart from a value by not being there. Archived fields are
|
|
81
|
-
* excluded to match the definitions `browse`: their values stay in the
|
|
82
|
-
* database, they just stop being addressable.
|
|
83
|
-
*
|
|
84
|
-
* One query for the whole set, so a member list can't turn into an N+1.
|
|
55
|
+
* Members' values, keyed by member id then field key; anything absent is unset.
|
|
85
56
|
*
|
|
86
|
-
*
|
|
87
|
-
* the
|
|
88
|
-
*
|
|
89
|
-
* take down a member's payload.
|
|
57
|
+
* Archived fields are excluded to match the definitions browse: their values stay in
|
|
58
|
+
* the database and stop being addressable. A row that will not parse is dropped and
|
|
59
|
+
* logged rather than failing the read, so one stale row cannot take down a member.
|
|
90
60
|
*/
|
|
91
61
|
async getValuesForMembers(memberIds) {
|
|
92
|
-
const byMember = new Map();
|
|
93
62
|
if (memberIds.length === 0) {
|
|
94
|
-
return
|
|
63
|
+
return new Map();
|
|
95
64
|
}
|
|
96
65
|
const rows = await this.knex(VALUES_TABLE)
|
|
97
66
|
.join(FIELDS_TABLE, `${VALUES_TABLE}.custom_field_id`, `${FIELDS_TABLE}.id`)
|
|
98
67
|
.whereIn(`${VALUES_TABLE}.member_id`, memberIds)
|
|
99
|
-
// Read join, so the status filter is qualified and lives inline — it
|
|
100
|
-
// shares the invariant's vocabulary with queries.ts, not its builder.
|
|
101
68
|
.where(`${FIELDS_TABLE}.status`, schema_1.FIELD_STATUS.active)
|
|
102
69
|
.orderBy(`${FIELDS_TABLE}.created_at`, 'asc')
|
|
103
70
|
.orderBy(`${FIELDS_TABLE}.id`, 'asc')
|
|
104
|
-
.
|
|
71
|
+
.orderBy(`${VALUES_TABLE}.path`, 'asc')
|
|
72
|
+
.select(`${VALUES_TABLE}.member_id`, `${FIELDS_TABLE}.key`, `${FIELDS_TABLE}.type`, `${VALUES_TABLE}.path`, `${VALUES_TABLE}.value_text`);
|
|
73
|
+
const leaves = [];
|
|
105
74
|
for (const row of rows) {
|
|
106
75
|
try {
|
|
107
|
-
|
|
108
|
-
const codec = (0, storage_1.storageCodecFor)(type);
|
|
109
|
-
const raw = stored[codec.column];
|
|
110
|
-
// The column the type routes to is the only one that can hold this
|
|
111
|
-
// field's value; a null there means the row is not carrying one.
|
|
112
|
-
if (raw === null) {
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
const values = byMember.get(memberId) ?? {};
|
|
116
|
-
values[key] = codec.decode(raw);
|
|
117
|
-
byMember.set(memberId, values);
|
|
76
|
+
leaves.push(schema_1.DbCustomFieldLeaf.parse(row));
|
|
118
77
|
}
|
|
119
78
|
catch (err) {
|
|
120
|
-
logging_1.default.warn(`Skipping
|
|
79
|
+
logging_1.default.warn(`Skipping unreadable custom field value (field '${row.key}', path '${row.path}'): ${err instanceof Error ? err.message : String(err)}`);
|
|
121
80
|
}
|
|
122
81
|
}
|
|
123
|
-
return
|
|
82
|
+
return (0, storage_1.valuesFromLeaves)(leaves);
|
|
124
83
|
}
|
|
125
|
-
/**
|
|
126
|
-
* @private
|
|
127
|
-
* Input as the values object it claims to be, rejecting anything that isn't
|
|
128
|
-
* one. Shared by every caller so they cannot disagree on what a values object
|
|
129
|
-
* is, or on the error when it isn't one.
|
|
130
|
-
*/
|
|
84
|
+
/** Shared by every caller so they cannot disagree on what a values object is. */
|
|
131
85
|
parseValues(input) {
|
|
132
86
|
const parsed = ValuesInput.safeParse(input);
|
|
133
87
|
if (!parsed.success) {
|
|
@@ -136,12 +90,8 @@ class CustomFieldValuesService {
|
|
|
136
90
|
return parsed.data;
|
|
137
91
|
}
|
|
138
92
|
/**
|
|
139
|
-
* Whether input names any values.
|
|
140
|
-
*
|
|
141
|
-
* have raised.
|
|
142
|
-
*
|
|
143
|
-
* Answers the shape question alone, with no catalog lookup, so it can be asked
|
|
144
|
-
* before a write is known to be permitted.
|
|
93
|
+
* Whether input names any values. Asks the shape question alone, with no catalog
|
|
94
|
+
* lookup, so it can be asked before a write is known to be permitted.
|
|
145
95
|
*/
|
|
146
96
|
namesValues(input) {
|
|
147
97
|
if (input === undefined) {
|
|
@@ -150,19 +100,15 @@ class CustomFieldValuesService {
|
|
|
150
100
|
return Object.keys(this.parseValues(input)).length > 0;
|
|
151
101
|
}
|
|
152
102
|
/**
|
|
153
|
-
* Resolve input into the writes it implies,
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* up front, before opening a transaction), then apply the same plan without
|
|
157
|
-
* re-resolving or re-validating.
|
|
103
|
+
* Resolve input into the writes it implies, writing nothing. Returned so a caller can
|
|
104
|
+
* validate before opening a transaction it would otherwise have to unwind, then apply
|
|
105
|
+
* the same plan without re-resolving it.
|
|
158
106
|
*/
|
|
159
107
|
async planWrite(input) {
|
|
160
108
|
const values = this.parseValues(input);
|
|
161
109
|
const keys = Object.keys(values);
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
// database driver's bound-parameter limit, which one key per parameter would
|
|
165
|
-
// otherwise exceed.
|
|
110
|
+
// Bounded by the definitions ceiling, which also holds the lookup below inside
|
|
111
|
+
// the driver's bound-parameter limit.
|
|
166
112
|
const maxKeys = this.getMaxDefinitions();
|
|
167
113
|
if (keys.length > maxKeys) {
|
|
168
114
|
throw new errors_1.default.ValidationError({
|
|
@@ -175,27 +121,27 @@ class CustomFieldValuesService {
|
|
|
175
121
|
for (const [key, raw] of Object.entries(values)) {
|
|
176
122
|
const field = byKey.get(key);
|
|
177
123
|
if (!field) {
|
|
178
|
-
// Unknown
|
|
179
|
-
//
|
|
124
|
+
// Unknown or archived. Refused rather than ignored: a typo that silently
|
|
125
|
+
// drops what somebody typed is worse than a save that fails. The catalog
|
|
126
|
+
// applies the same rule to the parts of a composite value.
|
|
180
127
|
throw new errors_1.default.ValidationError({ message: `Unknown custom field: ${key}`, property: `custom_fields.${key}` });
|
|
181
128
|
}
|
|
182
|
-
// `null` clears any field
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
if (raw === null || (raw === '' && custom_field_types_1.FIELD_TYPES[field.type].storageType === 'text')) {
|
|
129
|
+
// `null` clears any field, and `''` clears one with no parts. For a value
|
|
130
|
+
// with parts `''` names nothing, so it is left to fail validation rather than
|
|
131
|
+
// being read as a silent delete.
|
|
132
|
+
if (raw === null || (raw === '' && (0, custom_field_types_1.subFieldsOf)(field.type) === null)) {
|
|
187
133
|
writes.push({ field });
|
|
188
134
|
continue;
|
|
189
135
|
}
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
136
|
+
// Message only, no `context`: the API error handler moves a message into
|
|
137
|
+
// `context` when `context` is empty and prepends it when it is not, so
|
|
138
|
+
// anything added here reaches the client glued to the front of the reason.
|
|
139
|
+
// Which field failed rides in `property`.
|
|
193
140
|
const value = custom_field_types_1.FIELD_TYPES[field.type].value.safeParse(raw);
|
|
194
141
|
if (!value.success) {
|
|
195
142
|
const issue = value.error.issues[0];
|
|
196
143
|
throw new errors_1.default.ValidationError({
|
|
197
|
-
message:
|
|
198
|
-
context: issue.message,
|
|
144
|
+
message: issue.message,
|
|
199
145
|
property: [`custom_fields.${key}`, ...issue.path].join('.')
|
|
200
146
|
});
|
|
201
147
|
}
|
|
@@ -203,139 +149,72 @@ class CustomFieldValuesService {
|
|
|
203
149
|
}
|
|
204
150
|
return writes;
|
|
205
151
|
}
|
|
206
|
-
/**
|
|
207
|
-
* @private
|
|
208
|
-
* The composite values this member already holds for the fields in a plan,
|
|
209
|
-
* keyed by field id. Composites only: a scalar has nothing to merge into.
|
|
210
|
-
*
|
|
211
|
-
* Read through the caller's executor, never the base connection. On a
|
|
212
|
-
* single-connection pool a read off `this.knex` inside an open transaction
|
|
213
|
-
* waits for the connection that transaction is holding, and hangs.
|
|
214
|
-
*
|
|
215
|
-
* A blob that won't decode is left out, so the write replaces it rather than
|
|
216
|
-
* merging into something unreadable.
|
|
217
|
-
*/
|
|
218
|
-
async storedComposites(trx, memberId, writes) {
|
|
219
|
-
const composites = new Map();
|
|
220
|
-
// Merging is only meaningful for a value with parts, which is a fact about the
|
|
221
|
-
// type's shape rather than the column it lands in. Asking the storage type
|
|
222
|
-
// instead would sweep in the first json-backed type that isn't a record — a
|
|
223
|
-
// multi-select held as a list, say — and log a decode failure for every row of
|
|
224
|
-
// an import, over a value that reads back perfectly well.
|
|
225
|
-
const fieldIds = writes
|
|
226
|
-
.filter(({ field, value }) => value !== undefined && (0, custom_field_types_1.subFieldsOf)(field.type) !== null)
|
|
227
|
-
.map(({ field }) => field.id);
|
|
228
|
-
if (fieldIds.length === 0) {
|
|
229
|
-
return composites;
|
|
230
|
-
}
|
|
231
|
-
const rows = await trx(VALUES_TABLE)
|
|
232
|
-
.where('member_id', memberId)
|
|
233
|
-
.whereIn('custom_field_id', fieldIds)
|
|
234
|
-
.whereNotNull('value_json')
|
|
235
|
-
.select('custom_field_id', 'value_json')
|
|
236
|
-
// Merging reads a value and writes it back, so the row has to stay still in
|
|
237
|
-
// between: without the lock a write committing after this read is overwritten
|
|
238
|
-
// by the sub-fields it just replaced. knex omits this on SQLite, which needs
|
|
239
|
-
// no lock — its pool is a single connection, so writes never overlap.
|
|
240
|
-
.forUpdate();
|
|
241
|
-
for (const row of rows) {
|
|
242
|
-
// The blob is the edge here: the columns come from a query written just
|
|
243
|
-
// above, but whatever an earlier write left in value_json is unknown until
|
|
244
|
-
// it is read back. Parsing it is what narrows the type, and it rejects the
|
|
245
|
-
// array, null or bare number the column could equally be holding.
|
|
246
|
-
const stored = schema_1.StoredCompositeValue.safeParse(decodeJson(row.value_json));
|
|
247
|
-
if (stored.success) {
|
|
248
|
-
composites.set(row.custom_field_id, stored.data);
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
logging_1.default.warn(`Replacing an unreadable custom field value rather than merging into it (member '${memberId}', field '${row.custom_field_id}').`);
|
|
252
|
-
}
|
|
253
|
-
return composites;
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* @private
|
|
257
|
-
* The stored sub-fields with the incoming ones written over them, re-checked
|
|
258
|
-
* against the field type before it is handed back.
|
|
259
|
-
*
|
|
260
|
-
* The re-check is what makes this safe to write without a second thought. Both
|
|
261
|
-
* halves were valid when they were written, but "valid" is a moving target: the
|
|
262
|
-
* stored half may predate a change to the type, so it can carry a sub-field the
|
|
263
|
-
* type has since dropped or a bound it has since tightened. Parsing the result
|
|
264
|
-
* strips what no longer belongs instead of faithfully persisting it on every
|
|
265
|
-
* import from here on.
|
|
266
|
-
*
|
|
267
|
-
* A merged value that won't parse falls back to the incoming value alone rather
|
|
268
|
-
* than failing. The row is trying to write something valid, and refusing it over
|
|
269
|
-
* data that was already in the database punishes the wrong write.
|
|
270
|
-
*/
|
|
271
|
-
mergeComposite(field, existing, incoming) {
|
|
272
|
-
const merged = custom_field_types_1.FIELD_TYPES[field.type].value.safeParse({ ...existing, ...incoming });
|
|
273
|
-
if (merged.success) {
|
|
274
|
-
return merged.data;
|
|
275
|
-
}
|
|
276
|
-
logging_1.default.warn(`Replacing a custom field value that no longer validates rather than merging into it (field '${field.key}').`);
|
|
277
|
-
return incoming;
|
|
278
|
-
}
|
|
279
152
|
/**
|
|
280
153
|
* Apply a plan from `planWrite`.
|
|
281
154
|
*
|
|
282
|
-
*
|
|
283
|
-
* that
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
* sub-fields, because a sub-field is a field: a row writes the sub-fields it
|
|
287
|
-
* fills and leaves the rest alone, exactly as a blank `name` column leaves a
|
|
288
|
-
* member's name alone. The CSV import needs it — without it a file naming one
|
|
289
|
-
* address column would replace the whole address, clearing five sub-fields it
|
|
290
|
-
* never mentioned. The API does not merge: a PUT sends a whole address, so
|
|
291
|
-
* what it sends is what should be stored.
|
|
155
|
+
* A write touches the paths it names and nothing else, at every level: naming a path
|
|
156
|
+
* with an empty value clears that part, naming the field with `null` clears all of
|
|
157
|
+
* them, and saying nothing about a path leaves it alone. There is no whole-value
|
|
158
|
+
* replace, so a caller that does not know about a field cannot erase it.
|
|
292
159
|
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* it reads the stored value and writes it back, and the row lock that keeps those
|
|
296
|
-
* two steps together only exists inside the transaction. `planWrite` runs before
|
|
297
|
-
* one is opened -- the importer plans a row, then opens a transaction per member --
|
|
298
|
-
* so a merge decided there would have to read outside the lock it depends on.
|
|
299
|
-
*
|
|
300
|
-
* Always transactional, so a mid-batch failure rolls the batch back. Passed an
|
|
301
|
-
* executor it joins that transaction -- the importer passes its per-member one, so a
|
|
302
|
-
* failed value write takes the member with it; passed nothing it opens its own.
|
|
160
|
+
* Always transactional. Given an executor it joins that transaction, so the importer's
|
|
161
|
+
* failed value write takes its member with it; given none it opens its own.
|
|
303
162
|
*/
|
|
304
|
-
async applyWrite(memberId, writes, executor = this.knex
|
|
163
|
+
async applyWrite(memberId, writes, { executor = this.knex } = {}) {
|
|
305
164
|
if (writes.length === 0) {
|
|
306
165
|
return;
|
|
307
166
|
}
|
|
308
167
|
const apply = async (trx) => {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
168
|
+
// Built first, then sent as whole statements: a handful per member rather
|
|
169
|
+
// than one per part, under one timestamp, because a write happened once
|
|
170
|
+
// however many rows record it.
|
|
171
|
+
const now = new Date();
|
|
172
|
+
const clearedFields = [];
|
|
173
|
+
const clearedPaths = [];
|
|
174
|
+
const rows = [];
|
|
312
175
|
for (const { field, value } of writes) {
|
|
313
|
-
const target = { member_id: memberId, custom_field_id: field.id };
|
|
314
176
|
if (value === undefined) {
|
|
315
|
-
|
|
177
|
+
clearedFields.push(field.id);
|
|
316
178
|
continue;
|
|
317
179
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
:
|
|
328
|
-
|
|
180
|
+
const { set, cleared } = (0, storage_1.leavesToWrite)(value);
|
|
181
|
+
if (cleared.length > 0) {
|
|
182
|
+
clearedPaths.push({ fieldId: field.id, paths: cleared });
|
|
183
|
+
}
|
|
184
|
+
rows.push(...set.map(leaf => ({
|
|
185
|
+
id: new bson_objectid_1.default().toHexString(),
|
|
186
|
+
member_id: memberId,
|
|
187
|
+
custom_field_id: field.id,
|
|
188
|
+
path: leaf.path,
|
|
189
|
+
value_text: leaf.value_text,
|
|
190
|
+
created_at: now,
|
|
191
|
+
updated_at: now
|
|
192
|
+
})));
|
|
193
|
+
}
|
|
194
|
+
if (clearedFields.length > 0) {
|
|
195
|
+
await trx(VALUES_TABLE).where('member_id', memberId).whereIn('custom_field_id', clearedFields).del();
|
|
196
|
+
}
|
|
197
|
+
if (clearedPaths.length > 0) {
|
|
198
|
+
// One statement with a group per field, rather than a statement per field.
|
|
199
|
+
await trx(VALUES_TABLE).where('member_id', memberId).where((builder) => {
|
|
200
|
+
for (const { fieldId, paths } of clearedPaths) {
|
|
201
|
+
builder.orWhere(pair => pair.where('custom_field_id', fieldId).whereIn('path', paths));
|
|
202
|
+
}
|
|
203
|
+
}).del();
|
|
204
|
+
}
|
|
205
|
+
for (let from = 0; from < rows.length; from += UPSERT_CHUNK) {
|
|
206
|
+
// Typed as the plain row because `merge` takes its columns as `keyof` the
|
|
207
|
+
// builder's record, which for a composite table registration is the scope
|
|
208
|
+
// names rather than the columns.
|
|
329
209
|
await trx(VALUES_TABLE)
|
|
330
|
-
.insert(
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
.onConflict(['member_id', 'custom_field_id'])
|
|
334
|
-
.merge(
|
|
210
|
+
.insert(rows.slice(from, from + UPSERT_CHUNK))
|
|
211
|
+
// Naming the columns rather than giving values takes each from the row
|
|
212
|
+
// that lost the conflict, so every part updates to its own value.
|
|
213
|
+
.onConflict(['member_id', 'custom_field_id', 'path'])
|
|
214
|
+
.merge(['value_text', 'updated_at']);
|
|
335
215
|
}
|
|
336
216
|
};
|
|
337
|
-
//
|
|
338
|
-
// rather than nesting a savepoint under it, otherwise open one.
|
|
217
|
+
// knex's marker for a transactor: join it rather than nesting a savepoint under it.
|
|
339
218
|
if (executor.isTransaction) {
|
|
340
219
|
await apply(executor);
|
|
341
220
|
}
|