ghost 6.62.0 → 6.64.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/lib/duplicate-deps.js +204 -0
- package/bin/validate-adapters.js +50 -6
- package/components/tryghost-adapter-base-jobs-0.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-scheduling-0.2.3.tgz +0 -0
- package/components/tryghost-adapter-base-sso-0.1.3.tgz +0 -0
- package/components/tryghost-admin-api-schema-0.0.0.tgz +0 -0
- package/components/tryghost-api-framework-0.0.0.tgz +0 -0
- package/components/tryghost-checkout-0.0.0.tgz +0 -0
- package/components/tryghost-i18n-0.0.0.tgz +0 -0
- package/components/tryghost-kg-card-factory-5.2.4.tgz +0 -0
- package/components/tryghost-kg-clean-basic-html-4.3.4.tgz +0 -0
- package/components/tryghost-kg-converters-1.2.5.tgz +0 -0
- package/components/tryghost-kg-default-cards-10.3.6.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.2.1.tgz +0 -0
- package/components/tryghost-kg-default-transforms-1.3.4.tgz +0 -0
- package/components/tryghost-kg-html-to-lexical-1.4.0.tgz +0 -0
- package/components/tryghost-kg-lexical-html-renderer-1.5.1.tgz +0 -0
- package/components/tryghost-kg-markdown-html-renderer-7.2.4.tgz +0 -0
- package/components/tryghost-kg-utils-1.1.4.tgz +0 -0
- package/components/tryghost-limit-service-0.0.0.tgz +0 -0
- package/components/tryghost-metafield-types-0.0.0.tgz +0 -0
- package/content/themes/source/assets/built/screen.css +1 -1
- package/content/themes/source/assets/css/screen.css +1 -1
- package/content/themes/source/package.json +7 -7
- package/content/themes/source/partials/post-card.hbs +1 -1
- package/core/boot.js +17 -9
- package/core/built/admin/assets/Combination-DWfJPcz9.js +41 -0
- package/core/built/admin/assets/{PolarAngleAxis-pkMyqz8g.js → PolarAngleAxis-a81ieQUO.js} +2 -2
- package/core/built/admin/assets/RadialBarChart-CHRBhiBN.js +1 -0
- package/core/built/admin/assets/_baseAssignValue-DcPokYW8.js +1 -0
- package/core/built/admin/assets/about-39CAfroa.js +1 -0
- package/core/built/admin/assets/accordion-CJmRaqK7.js +1 -0
- package/core/built/admin/assets/account-migration-DYf7skbb.js +1 -0
- package/core/built/admin/assets/action-list-Crnh-_UI.js +1 -0
- package/core/built/admin/assets/activitypub/{account-migration-By69CgCp.js → account-migration-KA6Jjf9L.js} +7 -7
- package/core/built/admin/assets/activitypub/activitypub.js +3 -3
- package/core/built/admin/assets/activitypub/{avatar-DLPL3Abs.js → avatar-DVJF5bxP.js} +2 -2
- package/core/built/admin/assets/activitypub/{avatar-flipboard-C9iDY-QV.js → avatar-flipboard-csQaNeYK.js} +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-BJcPMrDd.js → bluesky-sharing-C7nTDMvw.js} +9 -9
- package/core/built/admin/assets/activitypub/{content-formatters-3rTHg3mp.js → content-formatters-CLcjaBwV.js} +4 -4
- package/core/built/admin/assets/activitypub/{copy-DIAOQoCZ.js → copy-DcOh8kXw.js} +2 -2
- package/core/built/admin/assets/activitypub/{domain-DUR4xSkV.js → domain-fZ-X8i4-.js} +9 -9
- package/core/built/admin/assets/activitypub/{edit-profile-BqtfzxCp.js → edit-profile-BqMLQV88.js} +6 -6
- package/core/built/admin/assets/activitypub/{explore-BxuJd-J4.js → explore-B4yPoP8E.js} +10 -10
- package/core/built/admin/assets/activitypub/{feed-BM1TcksA.js → feed-BRC_mvAe.js} +9 -9
- package/core/built/admin/assets/activitypub/{field-Dn7HHx2-.js → field-COacNgOt.js} +3 -3
- package/core/built/admin/assets/activitypub/{hash-Bm3WxYN4.js → hash-C7u-irQQ.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-CCjkuow2.js → inbox-BW-yvUoN.js} +10 -10
- package/core/built/admin/assets/activitypub/{inbox-d8oJRTf4.js → inbox-CbTTdDQ3.js} +2 -2
- package/core/built/admin/assets/activitypub/{label-1AA_s9Fa.js → label-kPMmYI7l.js} +3 -3
- package/core/built/admin/assets/activitypub/layout-BL4_jtuV.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-CnmxEB8w.js → moderation-CuidJzc0.js} +10 -10
- package/core/built/admin/assets/activitypub/{note-ZKc_Bnba.js → note-CAMRRSqD.js} +7 -7
- package/core/built/admin/assets/activitypub/{notifications-CB2tJZau.js → notifications-BmNlXGgp.js} +9 -9
- package/core/built/admin/assets/activitypub/{onboarding-BsJ9E5qf.js → onboarding-Bqg7fh_L.js} +2 -2
- package/core/built/admin/assets/activitypub/{onboarding-CSUJJIlh.js → onboarding-Dmun9jr_.js} +2 -2
- package/core/built/admin/assets/activitypub/{preferences-0-JZnUkI.js → preferences--oH19wf9.js} +11 -11
- package/core/built/admin/assets/activitypub/{profile-C35cFIhM.js → profile-DH_tQx2D.js} +14 -14
- package/core/built/admin/assets/activitypub/{reply-DYK5vVqb.js → reply-Cap9PxSH.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-BD4vscyc.js → routes-DdyY9uFF.js} +30 -30
- package/core/built/admin/assets/activitypub/{separator-BbQqTm-p.js → separator-CAxxu8VV.js} +3 -3
- package/core/built/admin/assets/activitypub/{settings-BsMROdqh.js → settings-B6EEcVkW.js} +6 -6
- package/core/built/admin/assets/activitypub/{site-O0RL9YCN.js → site-BYps63oh.js} +2 -2
- package/core/built/admin/assets/activitypub/{step-1-BMSx7c_X.js → step-1-CKjR2KAm.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-2-DtPQzlTH.js → step-2-B6PKfOUQ.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-3-CLlOZTyt.js → step-3-C-qBbcUo.js} +11 -11
- package/core/built/admin/assets/activitypub/{tabs-Dxk-b9M-.js → tabs-DLhZtVde.js} +6 -6
- package/core/built/admin/assets/activitypub/{topic-filter-5yZH6tId.js → topic-filter-DsUOuu0k.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-CV50qe03.js → use-activity-pub-queries-CX9snsLs.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-Bmk5h4lW.js → use-navigate-with-base-path-DsJTGDSS.js} +988 -959
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-Cbvzgjzd.js → use-reply-chain-data-AJi9zkzm.js} +5 -5
- package/core/built/admin/assets/activitypub/{users-BjcoP_HC.js → users-inweyKL5.js} +186 -160
- package/core/built/admin/assets/activitypub/{x-DbQZOLiI.js → x-DR8AT3-4.js} +2 -2
- package/core/built/admin/assets/add-integration-modal-B12oRifD.js +1 -0
- package/core/built/admin/assets/add-newsletter-modal-CTvXiIqY.js +1 -0
- package/core/built/admin/assets/add-recommendation-modal-CJf92edY.js +1 -0
- package/core/built/admin/assets/analytics-PHR1Fggi.js +1 -0
- package/core/built/admin/assets/announcement-bar-modal-Bql0CXdp.js +1 -0
- package/core/built/admin/assets/api-keys-CubH-7_H.js +1 -0
- package/core/built/admin/assets/{api-keys-3-0vJC3Y.js → api-keys-omLA6iYn.js} +1 -1
- package/core/built/admin/assets/{audience-CtbA54Sn.js → audience-6GXevic-.js} +1 -1
- package/core/built/admin/assets/automated-emails-DJQ4uNZo.js +1 -0
- package/core/built/admin/assets/automations-DcZSLFDU.js +1 -0
- package/core/built/admin/assets/automations-hDVcRPjh.js +1 -0
- package/core/built/admin/assets/avatar-flipboard-DiAGgshK.js +1 -0
- package/core/built/admin/assets/badge-qP06OT7C.js +1 -0
- package/core/built/admin/assets/bluesky-sharing-CJw4d4Mf.js +1 -0
- package/core/built/admin/assets/box-CZ_LSVSy.js +1 -0
- package/core/built/admin/assets/brand-icon-D3ffDYaC.js +1 -0
- package/core/built/admin/assets/breadcrumb-Chn1Ip_q.js +1 -0
- package/core/built/admin/assets/button-DvIh2Bq-.js +1 -0
- package/core/built/admin/assets/calendar-1QGoStFM.js +1 -0
- package/core/built/admin/assets/card-FHQ4D10L.js +1 -0
- package/core/built/admin/assets/chart-helpers-CNpA7re_.js +1 -0
- package/core/built/admin/assets/{chart-BrhA41Zi.js → chart-jVz9Nf9t.js} +9 -9
- package/core/built/admin/assets/checkbox-DKht9xMl.js +1 -0
- package/core/built/admin/assets/{chunk-KS7C4IRE-DCsL4i4D.js → chunk-KS7C4IRE-DhOcHQOU.js} +1 -1
- package/core/built/admin/assets/chunk.368.22ce1b732c0b40abd3f1.js +479 -0
- package/core/built/admin/assets/chunk.524.cc9be98e44890cedc8c8.js +25 -0
- package/core/built/admin/assets/{chunk.582.5ed5d124cfca9c259b9c.js → chunk.582.e6fe742b3217207b11a6.js} +23 -30
- package/core/built/admin/assets/{chunk.492.f24d855d7e3f3aad261c.js → chunk.972.4b8c97f93d66db15e751.js} +13582 -11017
- package/core/built/admin/assets/clean-tracked-url-dkN05vOf.js +1 -0
- package/core/built/admin/assets/code-editor-view-BLQIV85-.js +1 -0
- package/core/built/admin/assets/code-editor-view-Cg5eJxcr.js +1 -0
- package/core/built/admin/assets/color-picker-DQoghc0t.js +3 -0
- package/core/built/admin/assets/color-picker-field-BbeK5RRs.js +1 -0
- package/core/built/admin/assets/combobox-weO4FBbv.js +1 -0
- package/core/built/admin/assets/command-DlLrCm9V.js +1 -0
- package/core/built/admin/assets/comments-BjTitLmP.js +1 -0
- package/core/built/admin/assets/components-CwL6a6As-CCRaoCeP.js +1 -0
- package/core/built/admin/assets/{config-DOMQDZGZ.js → config-BPljH6wF.js} +1 -1
- package/core/built/admin/assets/confirmation-context-IyqoXVk-.js +1 -0
- package/core/built/admin/assets/container-DAimjOF_.js +1 -0
- package/core/built/admin/assets/content-api-modal-0ZjuLcQ8.js +1 -0
- package/core/built/admin/assets/context-menu-BtioYFrg.js +1 -0
- package/core/built/admin/assets/copy-field-C6ujqJlP.js +1 -0
- package/core/built/admin/assets/create-ghost-browse-value-source-DAZMWZNq.js +1 -0
- package/core/built/admin/assets/custom-integration-modal-Dm8QI_J4.js +1 -0
- package/core/built/admin/assets/data-list-C0CKkeWW.js +1 -0
- package/core/built/admin/assets/date-range-select-DrjHQdgz.js +1 -0
- package/core/built/admin/assets/design-and-theme-modal-CXVOzBWu.js +2 -0
- package/core/built/admin/assets/detail-page-QpLIL-EO.js +1 -0
- package/core/built/admin/assets/dirty-confirm-dialog-DWwxREGh.js +1 -0
- package/core/built/admin/assets/disabled-sources-indicator-CLZhMv8J.js +1 -0
- package/core/built/admin/assets/dist-BRQkNYN1.js +11 -0
- package/core/built/admin/assets/{dist-B3keLvjj.js → dist-BXHrDV19.js} +2 -2
- package/core/built/admin/assets/dist-Bf-HQtVL.js +1 -0
- package/core/built/admin/assets/dist-Bm-PB47a.js +6 -0
- package/core/built/admin/assets/{dist-B6bJuxL3.js → dist-C2lYC76d.js} +1 -1
- package/core/built/admin/assets/dist-CUwte9v_.js +1 -0
- package/core/built/admin/assets/dist-Cm2qbZKL.js +1 -0
- package/core/built/admin/assets/{dist-BtaTA7Ii.js → dist-D7yMk931.js} +1 -1
- package/core/built/admin/assets/{dist-zwwGt8Yf.js → dist-DNAdeRyr.js} +1 -1
- package/core/built/admin/assets/dist-DS6zpxid.js +1 -0
- package/core/built/admin/assets/dist-DVVKsyPh.js +23 -0
- package/core/built/admin/assets/dist-DxfQlAmn.js +1 -0
- package/core/built/admin/assets/dist-DziNzWI2.js +1 -0
- package/core/built/admin/assets/dist-FEQQAwTe.js +1 -0
- package/core/built/admin/assets/dist-nmRzULcD.js +1 -0
- package/core/built/admin/assets/domain-DBrBic6q.js +1 -0
- package/core/built/admin/assets/dropzone-D61QVcEY.js +5 -0
- package/core/built/admin/assets/edit-profile-jCqJBxqw.js +1 -0
- package/core/built/admin/assets/editor-8Fm4UBEd.js +8 -0
- package/core/built/admin/assets/editor-screen-BSX_uC2B.js +27 -0
- package/core/built/admin/assets/email-previews-Bs-JDhIw.js +1 -0
- package/core/built/admin/assets/email-sending-status-context-C76KYL1V.js +1 -0
- package/core/built/admin/assets/email-sending-status-provider-DnNhlIvt.js +1 -0
- package/core/built/admin/assets/emails-CgvIIz3q.js +1 -0
- package/core/built/admin/assets/{embed-signup-form-modal-Ds6mPEi5.js → embed-signup-form-modal-DqtBs1WE.js} +1 -1
- package/core/built/admin/assets/empty-indicator-L4Ql5Db7.js +1 -0
- package/core/built/admin/assets/en-Cx75dJv5.js +1 -0
- package/core/built/admin/assets/esm--rhhUGIG.js +2 -0
- package/core/built/admin/assets/explore-DXhDCtKw.js +1 -0
- package/core/built/admin/assets/feature-flag-overrides-context-CrcQKm9Z.js +1 -0
- package/core/built/admin/assets/feature-image-placeholder-e2q6TcH6.js +1 -0
- package/core/built/admin/assets/feed-mmt4noNL.js +1 -0
- package/core/built/admin/assets/feedback-BTPBQLT5.js +1 -0
- package/core/built/admin/assets/field-p7KnP98B.js +1 -0
- package/core/built/admin/assets/filter-bar-CiIKBg3D.js +1 -0
- package/core/built/admin/assets/filter-sources-I-dRaQxl.js +1 -0
- package/core/built/admin/assets/filters-1injvyxJ.js +1 -0
- package/core/built/admin/assets/filters-DnpO6GWX.js +1 -0
- package/core/built/admin/assets/first-promoter-modal-wr73Dldc.js +1 -0
- package/core/built/admin/assets/focus-koenig-editor-on-bottom-click-CkFA8EkI.js +1 -0
- package/core/built/admin/assets/framework-provider-DB-svAju.js +4 -0
- package/core/built/admin/assets/{ghost-8eabfc1ece81b03cf7b90780d31487e8.css → ghost-8fd5bb799356bbefb254210ba7e1236d.css} +1 -1
- package/core/built/admin/assets/{ghost-c2848508292937415f20f5a830eea59e.js → ghost-a1e6e172c6917e8ef8fa6b0007756d9b.js} +71 -46
- package/core/built/admin/assets/{ghost-dark-a7bebe2c561f55369dcf29f4c820f7ff.css → ghost-dark-cac0cb2d5bb78e9bd14d8d1c2768ed45.css} +1 -1
- package/core/built/admin/assets/gift-link-modal-C3H7YFXO.js +1 -0
- package/core/built/admin/assets/{gift-links-DLDS7ljO.js → gift-links-D8gl_yJS.js} +1 -1
- package/core/built/admin/assets/global-data-context-3MItMry2.js +1 -0
- package/core/built/admin/assets/grid-5vplBYri.js +1 -0
- package/core/built/admin/assets/growth-B1og1kQp.js +1 -0
- package/core/built/admin/assets/growth-Bf6OHZlK.js +3 -0
- package/core/built/admin/assets/heading-BB21QAbb.js +1 -0
- package/core/built/admin/assets/history-modal-t_qEnxPx.js +1 -0
- package/core/built/admin/assets/hooks-DYfTEQLk.js +2 -0
- package/core/built/admin/assets/html-field-COLQbV4r.js +1 -0
- package/core/built/admin/assets/iframe-buffering-Bq1el-gw.js +1 -0
- package/core/built/admin/assets/images-4mvoc3lB.js +1 -0
- package/core/built/admin/assets/{inbox-Mcp4DXZN.js → inbox-DgSZ6w2p.js} +34 -34
- package/core/built/admin/assets/index-B3HZU2i8.css +2 -0
- package/core/built/admin/assets/index-DDd-zhNL.js +4 -0
- package/core/built/admin/assets/index.es-Ct5XQNSj.js +1 -0
- package/core/built/admin/assets/inline-B8H6NzIw.js +1 -0
- package/core/built/admin/assets/input-group-CLIN2ykI.js +1 -0
- package/core/built/admin/assets/{input-surface-BsrIZeSF.js → input-surface-wSO5CRy5.js} +1 -1
- package/core/built/admin/assets/integration-header-CKGrXNVP.js +1 -0
- package/core/built/admin/assets/{integrations-Bjbu86Z0.js → integrations-DnJrZnHv.js} +1 -1
- package/core/built/admin/assets/invite-user-modal-BaN2mdhI.js +1 -0
- package/core/built/admin/assets/jsx-runtime-BlNNh7jY.js +1 -0
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +4 -4
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +2 -2
- package/core/built/admin/assets/{koenig-lexical-DBSqAuiI.js → koenig-lexical-CXVgQFUk.js} +2 -2
- package/core/built/admin/assets/koenig-loader-B1XPPY5C.js +2 -0
- package/core/built/admin/assets/kpi-card-CRLPVlFW.js +1 -0
- package/core/built/admin/assets/kpi-card-CRPbW8NZ.js +1 -0
- package/core/built/admin/assets/kpi-helpers-D7zT96bp.js +1 -0
- package/core/built/admin/assets/kpi-tabs-DE_yh9Go.js +1 -0
- package/core/built/admin/assets/label-CgeBxy4o.js +1 -0
- package/core/built/admin/assets/label-picker-DGDp4Vwr.js +1 -0
- package/core/built/admin/assets/labels-KMkFoRG6.js +1 -0
- package/core/built/admin/assets/layout-DWnOMly4.js +1 -0
- package/core/built/admin/assets/lib-_QYaUU89.js +2 -0
- package/core/built/admin/assets/link-helpers-8J7ixADV.js +1 -0
- package/core/built/admin/assets/links-FwNCVyVe.js +1 -0
- package/core/built/admin/assets/list-page-DLMP0PxC.js +1 -0
- package/core/built/admin/assets/luxon-BXCsmep3.js +1 -0
- package/core/built/admin/assets/member-activity-ClMWFyvN.js +1 -0
- package/core/built/admin/assets/member-activity-feed-BQY5Rwaj.js +1 -0
- package/core/built/admin/assets/member-custom-fields-Dd_ie1zG.js +1 -0
- package/core/built/admin/assets/member-detail-DjwkGVne.js +1 -0
- package/core/built/admin/assets/members--N9lhk1N.js +4 -0
- package/core/built/admin/assets/modal-page-Dz-9TP4E.js +1 -0
- package/core/built/admin/assets/moderation-DfYBKq9s.js +1 -0
- package/core/built/admin/assets/multi-select-combobox-BcBEzUW_.js +1 -0
- package/core/built/admin/assets/navigation-modal-Cd-scywg.js +1 -0
- package/core/built/admin/assets/newsletter-detail-modal-CFRY6vg_.js +1 -0
- package/core/built/admin/assets/newsletter-emails-4ntck1AC.js +1 -0
- package/core/built/admin/assets/newsletter-o6eCJtab.js +1 -0
- package/core/built/admin/assets/newsletters-Bmlv0edG.js +1 -0
- package/core/built/admin/assets/newsletters-Dz77m__5.js +1 -0
- package/core/built/admin/assets/note-CWs3LF-0.js +1 -0
- package/core/built/admin/assets/notifications-BjSvTJqh.js +1 -0
- package/core/built/admin/assets/{nql-BLpCn7qX.js → nql-DnDc7UuU.js} +1 -1
- package/core/built/admin/assets/nql-lang-CeTB_Bhv.js +19 -0
- package/core/built/admin/assets/{offers-9J1CSiLy.js → offers-ByWSK0rR.js} +1 -1
- package/core/built/admin/assets/offers-container-modal-Bc9A196w.js +1 -0
- package/core/built/admin/assets/onboarding-B4hfBi9a.js +1 -0
- package/core/built/admin/assets/onboarding-DV_XbHts.js +1 -0
- package/core/built/admin/assets/onboarding-route-Di1LJYEE.js +1 -0
- package/core/built/admin/assets/overview-DE7NHZQN.js +1 -0
- package/core/built/admin/assets/overview-DEF-ZY6x.js +1 -0
- package/core/built/admin/assets/page-header-B-G_g42C.js +1 -0
- package/core/built/admin/assets/pagemenu-CnkTDO71.js +1 -0
- package/core/built/admin/assets/pages-DZ_YGB1D.js +1 -0
- package/core/built/admin/assets/pages-route-VT7r7YhG.js +1 -0
- package/core/built/admin/assets/papaparse.min-BlhnCUXn.js +10 -0
- package/core/built/admin/assets/pintura-modal-BljM7hf8.js +1 -0
- package/core/built/admin/assets/portal-frame-D9V8iq5x.js +1 -0
- package/core/built/admin/assets/portal-modal-B5OOR9ZD.js +1 -0
- package/core/built/admin/assets/post-analytics-context-bt2YTBAC.js +1 -0
- package/core/built/admin/assets/post-analytics-header-J0WciLx6.js +1 -0
- package/core/built/admin/assets/post-analytics-provider-CgreZDjS.js +1 -0
- package/core/built/admin/assets/post-analytics-rGb-PgD-.js +1 -0
- package/core/built/admin/assets/post-helpers-C88JBa4D.js +1 -0
- package/core/built/admin/assets/post-share-modal-D0BbTo4W.js +1 -0
- package/core/built/admin/assets/post-utils-DKF4mfuo.js +1 -0
- package/core/built/admin/assets/posts-list-screen-CO5jAWoU.js +2 -0
- package/core/built/admin/assets/posts-route-DV-CXWK9.js +1 -0
- package/core/built/admin/assets/posts-tcoO3iRG.js +1 -0
- package/core/built/admin/assets/preferences-Tac29oG6.js +5 -0
- package/core/built/admin/assets/preview-chrome-C2lQgZBZ.js +1 -0
- package/core/built/admin/assets/preview-modal-CjRbBr8U.js +1 -0
- package/core/built/admin/assets/profile-yiM1f_Og.js +1 -0
- package/core/built/admin/assets/prop-types-6lTeqvom.js +1 -0
- package/core/built/admin/assets/radio-group-CKROWsw4.js +1 -0
- package/core/built/admin/assets/react-BXwhMTuc.js +1 -0
- package/core/built/admin/assets/{jsx-runtime-DX3WBWVO.js → react-dom-CMOtJ5MH.js} +2 -2
- package/core/built/admin/assets/{referrers-CnhCMWCs.js → referrers-1R8bPpoV.js} +1 -1
- package/core/built/admin/assets/router-provider-CU87qxXw.js +1 -0
- package/core/built/admin/assets/schemas-BoYdB6YU.js +64 -0
- package/core/built/admin/assets/select-CKaC4ggu.js +1 -0
- package/core/built/admin/assets/settings-81kxUa28.js +1 -0
- package/core/built/admin/assets/settings-BVXFqBVK.js +4 -0
- package/core/built/admin/assets/settings-breadcrumbs-czGRx-iu.js +1 -0
- package/core/built/admin/assets/{shade-app-CcS3f8MZ-vwURazk8.js → shade-app-CcS3f8MZ-D5BnfHck.js} +1 -1
- package/core/built/admin/assets/share-modal-BL-16PiB.js +1 -0
- package/core/built/admin/assets/skeleton-DwCva0-b.js +1 -0
- package/core/built/admin/assets/slack-modal-D7W_fNON.js +1 -0
- package/core/built/admin/assets/sort-button-Dquc83jY.js +1 -0
- package/core/built/admin/assets/source-icon-CQboPZQ3.js +1 -0
- package/core/built/admin/assets/stack-BURz77VS.js +1 -0
- package/core/built/admin/assets/stats-Cvp8Ep5m.js +1 -0
- package/core/built/admin/assets/stats-view-NDja_g96.js +1 -0
- package/core/built/admin/assets/step-1-BKhjy0PV.js +1 -0
- package/core/built/admin/assets/step-2-PxA1OM7R.js +1 -0
- package/core/built/admin/assets/step-3-BJbAiq16.js +2 -0
- package/core/built/admin/assets/stripe-button-C3cfCAou.js +1 -0
- package/core/built/admin/assets/stripe-connect-modal-FyE3Ey2c.js +1 -0
- package/core/built/admin/assets/style-Wd9nQubI.css +1 -0
- package/core/built/admin/assets/switch-zfUUy9Jh.js +1 -0
- package/core/built/admin/assets/table-dE4GTIRT.js +1 -0
- package/core/built/admin/assets/tabs-B3XK93sR.js +1 -0
- package/core/built/admin/assets/tag-detail-D4P-tZdM.js +2 -0
- package/core/built/admin/assets/tag-picker-BNSZ6Qxw.js +1 -0
- package/core/built/admin/assets/tags-BsZcaJ6h.js +1 -0
- package/core/built/admin/assets/{tags-zTFiKRpb.js → tags-NwZ7BPK6.js} +1 -1
- package/core/built/admin/assets/testimonials-modal-Dkt6ODLW.js +1 -0
- package/core/built/admin/assets/text-DC-ot6u2.js +1 -0
- package/core/built/admin/assets/textarea-B4FffFmE.js +1 -0
- package/core/built/admin/assets/themes-DtcEFQWm.js +1 -0
- package/core/built/admin/assets/tier-detail-modal-a77Z2VMH.js +1 -0
- package/core/built/admin/assets/tiers-6mPzCXlM.js +1 -0
- package/core/built/admin/assets/toggle-group-D2p4okmJ.js +1 -0
- package/core/built/admin/assets/topic-filter-DVOe3D-b.js +1 -0
- package/core/built/admin/assets/transistor-modal-C8RMkhOD.js +1 -0
- package/core/built/admin/assets/trend-badge-B9v7kMF8.js +1 -0
- package/core/built/admin/assets/unsplash-modal-DXt7Brum.js +1 -0
- package/core/built/admin/assets/use-analytics-data-ChHqiZXv.js +1 -0
- package/core/built/admin/assets/use-availability-hPdeG76y.js +1 -0
- package/core/built/admin/assets/{use-check-theme-limit-error-Cw3WQHy8.js → use-check-theme-limit-error-JD-J5aaZ.js} +2 -2
- package/core/built/admin/assets/use-email-sending-status-DNMtL5B9.js +1 -0
- package/core/built/admin/assets/use-filter-params-BSFigDIn.js +1 -0
- package/core/built/admin/assets/use-filterable-api-CEMSAYPg.js +1 -0
- package/core/built/admin/assets/use-form-DJKZJhQh.js +1 -0
- package/core/built/admin/assets/use-growth-stats-CqqdnESN.js +1 -0
- package/core/built/admin/assets/use-host-limits-mpTkZIq8.js +1 -0
- package/core/built/admin/assets/use-limiter-DBGjYqu7.js +19 -0
- package/core/built/admin/assets/use-navigate-with-base-path-88EQpw2k.js +1 -0
- package/core/built/admin/assets/use-pintura-config-BmU45Ruy.js +1 -0
- package/core/built/admin/assets/use-pintura-editor-Bz2bZ4iT.js +1 -0
- package/core/built/admin/assets/use-post-referrers-DyPjyBSN.js +1 -0
- package/core/built/admin/assets/use-query-params-DlOHCo-R.js +1 -0
- package/core/built/admin/assets/use-reply-chain-data-DmD2yX5D.js +1 -0
- package/core/built/admin/assets/use-router-history-entry-CKx9LhqD.js +1 -0
- package/core/built/admin/assets/use-sending-eta-CQeG6OP_.js +1 -0
- package/core/built/admin/assets/use-setting-group-Dza6bQ7W.js +1 -0
- package/core/built/admin/assets/use-settings-navigation-BIjI5mL3.js +1 -0
- package/core/built/admin/assets/use-simple-pagination-DwVxe-Lo.js +1 -0
- package/core/built/admin/assets/use-staff-users-i_Mq9OGR.js +1 -0
- package/core/built/admin/assets/{use-tinybird-query-CSnGXzZh.js → use-tinybird-query-DXoF74NM.js} +1 -1
- package/core/built/admin/assets/use-tinybird-token-Lk1RSn0t.js +1 -0
- package/core/built/admin/assets/use-unsaved-changes-guard-CKVPWz_F.js +1 -0
- package/core/built/admin/assets/use-upgrade-route-CVjDlNSs.js +1 -0
- package/core/built/admin/assets/{use-url-input-DFr8lCbS.js → use-url-input-DpmbDppC.js} +1 -1
- package/core/built/admin/assets/user-detail-modal-BU4QCHE7.js +1 -0
- package/core/built/admin/assets/{users-Cg-OeGzC.js → users-C60_hpn6.js} +1 -1
- package/core/built/admin/assets/{validator-Cr0sMKuW.js → validator-DlFQP70j.js} +1 -1
- package/core/built/admin/assets/{vendor-5753232c7d6ffb3c44e1eb0a02133953.js → vendor-2dd263291938d407802568d3357ec69e.js} +19 -17
- package/core/built/admin/assets/virtual-list-DScLNuU8.js +1 -0
- package/core/built/admin/assets/web-BZS-t4H0.js +1 -0
- package/core/built/admin/assets/web-DxAUxnK3.js +2 -0
- package/core/built/admin/assets/zapier-modal-BafLUO4R.js +1 -0
- package/core/built/admin/index.html +38 -34
- package/core/cli/command.js +17 -2
- package/core/frontend/meta/asset-url.js +1 -16
- package/core/frontend/services/asset-hash/index.js +32 -2
- package/core/frontend/services/routing/controllers/unsubscribe.js +2 -1
- package/core/frontend/services/rss/generate-feed.js +1 -1
- package/core/frontend/services/theme-engine/active.js +2 -4
- package/core/frontend/services/theme-engine/i18n/i18n.js +11 -1
- package/core/frontend/services/theme-engine/i18next/theme-i18n.js +1 -1
- package/core/frontend/web/middleware/llms-discovery.js +34 -43
- package/core/server/adapters/jobs/InMemoryJobsBackend.js +74 -29
- package/core/server/adapters/route-settings/S3RouteSettingsStore.js +1 -1
- package/core/server/adapters/storage/LocalFilesStorage.js +12 -2
- package/core/server/adapters/storage/LocalStorageBase.js +13 -0
- package/core/server/api/endpoints/actions.js +2 -1
- package/core/server/api/endpoints/authentication.js +1 -2
- package/core/server/api/endpoints/authors-public.js +15 -5
- package/core/server/api/endpoints/automated-emails.js +3 -2
- package/core/server/api/endpoints/automations.js +77 -49
- package/core/server/api/endpoints/emails.js +21 -3
- package/core/server/api/endpoints/index.js +9 -1
- package/core/server/api/endpoints/invites.js +2 -1
- package/core/server/api/endpoints/labels.js +6 -2
- package/core/server/api/endpoints/member-metafields-members.js +23 -0
- package/core/server/api/endpoints/member-metafields.js +34 -21
- package/core/server/api/endpoints/members-account.js +42 -0
- package/core/server/api/endpoints/members.js +22 -6
- package/core/server/api/endpoints/newsletters-public.js +10 -1
- package/core/server/api/endpoints/pages-public.js +15 -5
- package/core/server/api/endpoints/pages.js +2 -6
- package/core/server/api/endpoints/posts-public.js +15 -5
- package/core/server/api/endpoints/posts.js +3 -11
- package/core/server/api/endpoints/snippets.js +2 -1
- package/core/server/api/endpoints/tags-public.js +22 -3
- package/core/server/api/endpoints/tags.js +6 -2
- package/core/server/api/endpoints/tiers-checkout-config.js +2 -2
- package/core/server/api/endpoints/tiers-public.js +38 -1
- package/core/server/api/endpoints/users.js +4 -8
- package/core/server/api/endpoints/utils/api-filter-utils.js +232 -6
- package/core/server/api/endpoints/utils/public-cache-keys.js +3 -0
- package/core/server/api/endpoints/utils/serializers/input/authors.js +2 -0
- package/core/server/api/endpoints/utils/serializers/input/members.js +16 -2
- package/core/server/api/endpoints/utils/serializers/input/pages.js +2 -0
- package/core/server/api/endpoints/utils/serializers/input/posts.js +2 -0
- package/core/server/api/endpoints/utils/serializers/input/tags.js +2 -0
- package/core/server/api/endpoints/utils/serializers/output/emails.js +7 -0
- package/core/server/api/endpoints/utils/serializers/output/index.js +4 -0
- package/core/server/api/endpoints/utils/serializers/output/member-metafields.js +3 -3
- package/core/server/api/endpoints/utils/serializers/output/members-account.js +19 -0
- package/core/server/api/endpoints/utils/serializers/output/members-csv-transform.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/tiers.js +10 -2
- package/core/server/api/endpoints/utils/serializers/output/users.js +0 -10
- package/core/server/api/endpoints/utils/serializers/output/utils/clean.js +2 -0
- package/core/server/api/endpoints/utils/serializers/output/utils/extra-attrs.js +3 -0
- package/core/server/api/endpoints/utils/validators/input/settings.js +15 -1
- package/core/server/data/exporter/exporter.js +2 -1
- package/core/server/data/exporter/table-lists.js +6 -8
- package/core/server/data/importer/importers/data/settings-importer.js +8 -2
- package/core/server/data/migrations/versions/6.53/2026-07-15-20-22-53-add-members-custom-field-values.js +1 -1
- package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +10 -1
- package/core/server/data/migrations/versions/6.63/2026-09-03-10-07-03-rename-custom-field-tables-to-metafields.js +169 -0
- package/core/server/data/migrations/versions/6.63/2026-09-03-11-03-31-remove-member-custom-field-read-permissions.js +30 -0
- package/core/server/data/migrations/versions/6.64/2026-09-03-15-40-25-add-member-access-to-members-metafields.js +12 -0
- package/core/server/data/migrations/versions/6.64/2026-09-08-11-01-12-add-posts-auto-excerpt-and-reading-time-columns.js +17 -0
- package/core/server/data/migrations/versions/6.64/2026-09-09-13-56-18-add-automation-run-updated-at-indexes.js +6 -0
- package/core/server/data/migrations/versions/6.64/2026-09-09-13-56-18-add-tinybird-syncs-table.js +10 -0
- package/core/server/data/schema/fixtures/fixtures.json +0 -11
- package/core/server/data/schema/lib/default-index-name.js +8 -11
- package/core/server/data/schema/schema.js +46 -20
- package/core/server/data/seeders/README.md +48 -0
- package/core/server/data/seeders/importers/automations-importer.js +0 -1
- package/core/server/data/seeders/importers/comments-importer.js +1 -1
- package/core/server/data/seeders/importers/email-recipients-importer.js +1 -1
- package/core/server/data/seeders/importers/emails-importer.js +1 -1
- package/core/server/data/seeders/importers/members-importer.js +1 -1
- package/core/server/data/seeders/importers/members-login-events-importer.js +1 -1
- package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +1 -1
- package/core/server/data/seeders/utils/blog-info.js +4 -3
- package/core/server/data/seeders/utils/event-generator.js +72 -46
- package/core/server/data/tinybird/datasources/_mv_automation_run_steps.datasource +10 -0
- package/core/server/data/tinybird/datasources/_mv_automation_runs.datasource +10 -0
- package/core/server/data/tinybird/datasources/_mv_pending_automation_runs.datasource +10 -0
- package/core/server/data/tinybird/datasources/automation_run_events.datasource +12 -0
- package/core/server/data/tinybird/datasources/automation_run_step_events.datasource +12 -0
- package/core/server/data/tinybird/endpoints/api_automation_browse_stats.pipe +43 -0
- package/core/server/data/tinybird/fixtures/automation_run_events.ndjson +7 -0
- package/core/server/data/tinybird/fixtures/automation_run_step_events.ndjson +8 -0
- package/core/server/data/tinybird/pipes/mv_automation_run_steps.pipe +13 -0
- package/core/server/data/tinybird/pipes/mv_automation_runs.pipe +15 -0
- package/core/server/data/tinybird/pipes/mv_hits.pipe +86 -50
- package/core/server/data/tinybird/pipes/mv_pending_automation_runs.pipe +7 -0
- package/core/server/data/tinybird/tests/api_automation_browse_stats.yaml +18 -0
- package/core/server/ghost-server.js +3 -3
- package/core/server/lib/db-types/count.js +16 -0
- package/core/server/lib/generate-password.js +54 -0
- package/core/server/lib/public-jwk.js +77 -0
- package/core/server/lib/validate-password.js +81 -120
- package/core/server/models/action.js +4 -4
- package/core/server/models/base/plugins/filtered-collection.js +16 -0
- package/core/server/models/member.js +6 -6
- package/core/server/models/user.js +12 -10
- package/core/server/services/adapter-manager/base-classes.js +21 -3
- package/core/server/services/automations/automations-api.js +46 -1
- package/core/server/services/automations/database-automations-repository.js +11 -4
- package/core/server/services/automations/tinybird-automation-stats.js +56 -0
- package/core/server/services/content-import/import/media.js +1 -1
- package/core/server/services/email-analytics/email-analytics-service-wrapper.js +15 -29
- package/core/server/services/email-analytics/email-analytics-service.js +19 -3
- package/core/server/services/email-analytics/index.js +9 -4
- package/core/server/services/email-rendering/finalize.js +7 -2
- package/core/server/services/email-service/CONTEXT.md +37 -0
- package/core/server/services/email-service/README.md +45 -0
- package/core/server/services/email-service/batch-sending-service.js +2 -0
- package/core/server/services/email-service/email-controller.js +18 -2
- package/core/server/services/email-service/email-renderer.js +7 -2
- package/core/server/services/email-service/email-service-wrapper.js +4 -1
- package/core/server/services/email-service/email-service.js +43 -34
- package/core/server/services/email-service/sending-status-schema.js +21 -0
- package/core/server/services/email-service/sending-status-serializers.js +33 -0
- package/core/server/services/email-service/sending-status-service.js +47 -0
- package/core/server/services/email-service/sending-status.js +117 -0
- package/core/server/services/email-suppression-list/email-suppression-list.js +0 -33
- package/core/server/services/email-suppression-list/mailgun-email-suppression-list.js +6 -7
- package/core/server/services/gifts/README.md +2 -2
- package/core/server/services/gifts/constants.js +1 -1
- package/core/server/services/gifts/gift-email-service.js +10 -3
- package/core/server/services/gifts/gift-service.js +19 -57
- package/core/server/services/gifts/index.js +1 -2
- package/core/server/services/i18n.js +1 -1
- package/core/server/services/identity-tokens/identity-token-service-wrapper.js +3 -4
- package/core/server/services/jobs-service/index.js +8 -0
- package/core/server/services/jobs-service/jobs-service.js +52 -3
- package/core/server/services/jobs-service/register-job-handlers.js +13 -2
- package/core/server/services/limits.js +17 -1
- package/core/server/services/member-welcome-emails/service.js +1 -1
- package/core/server/services/members/account-service.js +98 -0
- package/core/server/services/members/api.js +4 -4
- package/core/server/services/members/import-export/export/exporter.js +20 -20
- package/core/server/services/members/import-export/import/completion-email.js +6 -6
- package/core/server/services/members/import-export/import/importer.js +13 -13
- package/core/server/services/members/import-export/index.js +10 -9
- package/core/server/services/members/members-api/controllers/router-controller.js +7 -2
- package/core/server/services/members/members-api/controllers/well-known-controller.js +2 -1
- package/core/server/services/members/members-api/members-api.js +26 -6
- package/core/server/services/members/members-api/repositories/event-repository.js +4 -0
- package/core/server/services/members/members-api/repositories/member-repository.js +4 -4
- package/core/server/services/members/members-api/services/member-bread-service.js +57 -48
- package/core/server/services/members/members-api/services/token-service.js +11 -12
- package/core/server/services/members/members-ssr.js +18 -0
- package/core/server/services/members/middleware.js +104 -83
- package/core/server/services/members/request-integrity-token-provider.js +2 -1
- package/core/server/services/members/service.js +70 -42
- package/core/server/services/members/utils.js +9 -0
- package/core/server/services/members-metafields/access.js +39 -0
- package/core/server/services/{members-custom-fields → members-metafields}/actions.js +6 -5
- package/core/server/services/members-metafields/bindings-service.js +147 -0
- package/core/server/services/members-metafields/codec.js +21 -0
- package/core/server/services/{members-custom-fields → members-metafields}/config.js +1 -1
- package/core/server/services/{members-custom-fields → members-metafields}/definitions-service.js +71 -67
- package/core/server/services/{members-custom-fields → members-metafields}/filter.js +9 -9
- package/core/server/services/{members-custom-fields → members-metafields}/index.js +16 -7
- package/core/server/services/{members-custom-fields → members-metafields}/key.js +1 -1
- package/core/server/services/{members-custom-fields → members-metafields}/models.js +6 -4
- package/core/server/services/members-metafields/namespaces.js +34 -0
- package/core/server/services/members-metafields/queries.js +60 -0
- package/core/server/services/{members-custom-fields → members-metafields}/schema.js +18 -12
- package/core/server/services/{members-custom-fields → members-metafields}/serializers.js +7 -6
- package/core/server/services/{members-custom-fields → members-metafields}/values-service.js +46 -27
- package/core/server/services/mentions/mention-discovery-service.js +1 -1
- package/core/server/services/mentions/mention-sending-service.js +27 -10
- package/core/server/services/mentions/mention.js +1 -1
- package/core/server/services/mentions/send-webmentions-job.js +18 -0
- package/core/server/services/mentions/service.js +1 -31
- package/core/server/services/oembed/oembed-service.js +36 -11
- package/core/server/services/posts/post-scheduling.js +20 -0
- package/core/server/services/staff/staff-service-emails.js +5 -1
- package/core/server/services/stats/stats-service.js +12 -3
- package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +2 -2
- package/core/server/services/stripe/stripe-service.js +4 -4
- package/core/server/services/tier-checkout-config/codec.js +2 -2
- package/core/server/services/tier-checkout-config/index.js +2 -1
- package/core/server/services/tier-checkout-config/models.js +2 -2
- package/core/server/services/tier-checkout-config/queries.js +8 -8
- package/core/server/services/tier-checkout-config/serializers.js +57 -0
- package/core/server/services/tier-checkout-config/service.js +18 -2
- package/core/server/services/tiers/service.js +3 -3
- package/core/server/services/tinybird/tinybird-service.js +1 -0
- package/core/server/services/tinybird-sync/get-ingest-config.js +39 -0
- package/core/server/services/tinybird-sync/index.js +28 -0
- package/core/server/services/tinybird-sync/sync-table-to-tinybird.js +149 -0
- package/core/server/services/tinybird-sync/tinybird-sync-service.js +62 -0
- package/core/server/services/url/router-filter.js +8 -10
- package/core/server/services/users.js +1 -19
- package/core/server/web/api/endpoints/admin/routes.js +34 -34
- package/core/server/web/members/account/index.js +2 -0
- package/core/server/web/members/account/routes.js +46 -0
- package/core/server/web/members/app.js +4 -18
- package/core/server/web/well-known.js +12 -16
- package/core/shared/config/defaults.json +2 -3
- package/core/shared/{flush-logs.js → flush.js} +7 -3
- package/core/shared/labs.js +6 -2
- package/core/shared/timing-safe-string-equal.js +26 -0
- package/package.json +46 -46
- package/patches/@elastic__elasticsearch@8.19.2.patch +67 -0
- package/patches/juice@11.1.1.patch +46 -0
- package/pnpm-lock.yaml +750 -1742
- package/pnpm-workspace.yaml +37 -24
- package/scripts/prune.mts +70 -3
- package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/components/tryghost-kg-default-cards-10.3.5.tgz +0 -0
- package/components/tryghost-kg-lexical-html-renderer-1.5.0.tgz +0 -0
- package/core/built/admin/assets/Combination-B0l4MKVh.js +0 -41
- package/core/built/admin/assets/RadialBarChart-JAKoS4KT.js +0 -1
- package/core/built/admin/assets/_baseAssignValue-C_vooEQc.js +0 -1
- package/core/built/admin/assets/about-Dyae_UT6.js +0 -1
- package/core/built/admin/assets/account-migration-CESa0mnX.js +0 -1
- package/core/built/admin/assets/action-list-C4v0-du8.js +0 -1
- package/core/built/admin/assets/activitypub/layout-2-0bw6HN.js +0 -1
- package/core/built/admin/assets/add-integration-modal-BlF6rs_f.js +0 -1
- package/core/built/admin/assets/add-newsletter-modal-CglVQqah.js +0 -1
- package/core/built/admin/assets/add-recommendation-modal-wz7BsuaG.js +0 -1
- package/core/built/admin/assets/analytics-C4HE1gBg.js +0 -1
- package/core/built/admin/assets/announcement-bar-modal-Ci4zz8jO.js +0 -1
- package/core/built/admin/assets/api-keys-Ck-Yteiv.js +0 -1
- package/core/built/admin/assets/automated-emails-D5VVMND9.js +0 -1
- package/core/built/admin/assets/automations-CzKt5fb8.js +0 -1
- package/core/built/admin/assets/automations-sAcJ1334.js +0 -1
- package/core/built/admin/assets/avatar-flipboard-C6Kj3Awu.js +0 -1
- package/core/built/admin/assets/bluesky-sharing-BEtJ_gL_.js +0 -1
- package/core/built/admin/assets/box-CdfQAP_F.js +0 -1
- package/core/built/admin/assets/brand-icon-CrIGOA3Z.js +0 -1
- package/core/built/admin/assets/breadcrumb-zUrZrBtW.js +0 -1
- package/core/built/admin/assets/build-CsiH26uS.js +0 -1
- package/core/built/admin/assets/button-eNMKQWXr.js +0 -1
- package/core/built/admin/assets/card--VNbfgo3.js +0 -1
- package/core/built/admin/assets/chart-helpers-CfBKt0vT.js +0 -1
- package/core/built/admin/assets/checkbox-CgOn_tKW.js +0 -1
- package/core/built/admin/assets/chunk.524.a697dee88db67fdf6021.js +0 -32
- package/core/built/admin/assets/chunk.598.7e056fabb47ce7dbd73f.js +0 -366
- package/core/built/admin/assets/code-editor-view-BYNoAoNV.js +0 -1
- package/core/built/admin/assets/code-editor-view-K2wI-I5f.js +0 -1
- package/core/built/admin/assets/color-picker-Ddw80xX6.js +0 -3
- package/core/built/admin/assets/color-picker-field-D9nUhvA7.js +0 -1
- package/core/built/admin/assets/combobox-w9j8PPQT.js +0 -1
- package/core/built/admin/assets/command-BfpHORBv.js +0 -1
- package/core/built/admin/assets/comments-BLmCmyCs.js +0 -1
- package/core/built/admin/assets/components-CwL6a6As-B0E0-NBJ.js +0 -1
- package/core/built/admin/assets/confirmation-context-CBM8G0Vj.js +0 -1
- package/core/built/admin/assets/container-BuQG1wuP.js +0 -1
- package/core/built/admin/assets/content-api-modal-TXRz3U7S.js +0 -1
- package/core/built/admin/assets/context-menu-_QMdd3kp.js +0 -1
- package/core/built/admin/assets/copy-field-6N01S07E.js +0 -1
- package/core/built/admin/assets/create-ghost-browse-value-source-qIBymvvf.js +0 -1
- package/core/built/admin/assets/custom-field-type-option-BddFlJFa.js +0 -1
- package/core/built/admin/assets/custom-integration-modal-DmNquxUV.js +0 -1
- package/core/built/admin/assets/data-list-CA2jYGPq.js +0 -1
- package/core/built/admin/assets/date-range-select-uvz_DtPh.js +0 -1
- package/core/built/admin/assets/design-and-theme-modal-BSmo12Op.js +0 -2
- package/core/built/admin/assets/detail-page-Dnv-CdR1.js +0 -1
- package/core/built/admin/assets/dirty-confirm-dialog-BZ1iytEd.js +0 -1
- package/core/built/admin/assets/disabled-sources-indicator-DMRrxsiJ.js +0 -1
- package/core/built/admin/assets/dist-4m6pHdc0.js +0 -23
- package/core/built/admin/assets/dist-8YJUlf3B.js +0 -1
- package/core/built/admin/assets/dist-CZgIBXQN.js +0 -1
- package/core/built/admin/assets/dist-CiHx7vA5.js +0 -6
- package/core/built/admin/assets/dist-DIC8yz5C.js +0 -1
- package/core/built/admin/assets/dist-Dcsslyo72.js +0 -1
- package/core/built/admin/assets/dist-Dgt2cr6a.js +0 -1
- package/core/built/admin/assets/dist-ECAUrdGc.js +0 -1
- package/core/built/admin/assets/dist-JLOy8o0Q.js +0 -1
- package/core/built/admin/assets/dist-Ndy-PuGN.js +0 -1
- package/core/built/admin/assets/dist-Z97pCNqS.js +0 -11
- package/core/built/admin/assets/domain-BNYrt0aD.js +0 -1
- package/core/built/admin/assets/dropzone-D7nFdAe5.js +0 -5
- package/core/built/admin/assets/edit-profile-C5Asm4LA.js +0 -1
- package/core/built/admin/assets/editor-REokrzci.js +0 -8
- package/core/built/admin/assets/empty-indicator-D9QdpuNX.js +0 -1
- package/core/built/admin/assets/en-B1jX3WMs.js +0 -1
- package/core/built/admin/assets/esm-CXo5SEPy.js +0 -2
- package/core/built/admin/assets/explore-UY10Egsp.js +0 -1
- package/core/built/admin/assets/feature-image-placeholder-D1iF_8bc.js +0 -1
- package/core/built/admin/assets/feed-81C8mJWR.js +0 -1
- package/core/built/admin/assets/field-Dihp5p18.js +0 -1
- package/core/built/admin/assets/filter-bar-Da1qxybu.js +0 -1
- package/core/built/admin/assets/filter-sources-BO9xUPj7.js +0 -1
- package/core/built/admin/assets/filters-CRUWecfY.js +0 -1
- package/core/built/admin/assets/filters-DirqNXjH.js +0 -1
- package/core/built/admin/assets/first-promoter-modal-CT6MxYZb.js +0 -1
- package/core/built/admin/assets/framework-provider-DqI25epG.js +0 -4
- package/core/built/admin/assets/gift-link-modal-CQVAye17.js +0 -1
- package/core/built/admin/assets/global-data-context-gAMDH9u1.js +0 -1
- package/core/built/admin/assets/grid-Dj29PY05.js +0 -1
- package/core/built/admin/assets/growth-BWIu-6xj.js +0 -3
- package/core/built/admin/assets/growth-DU29oxeA.js +0 -1
- package/core/built/admin/assets/heading-Dh5SFVjL.js +0 -1
- package/core/built/admin/assets/history-modal-CFd1Hvx6.js +0 -1
- package/core/built/admin/assets/hooks-CuEZV2-q.js +0 -2
- package/core/built/admin/assets/html-field-CJBCLTvv.js +0 -1
- package/core/built/admin/assets/iframe-buffering-B2OKJzbz.js +0 -1
- package/core/built/admin/assets/images-scfkWZiK.js +0 -1
- package/core/built/admin/assets/index-6MkPdCtr.js +0 -4
- package/core/built/admin/assets/index-Bn7QhcZu.css +0 -2
- package/core/built/admin/assets/index.es-CNnuNH0d.js +0 -1
- package/core/built/admin/assets/inline-BWSs_VzR.js +0 -1
- package/core/built/admin/assets/input-group-CMhe2DvY.js +0 -1
- package/core/built/admin/assets/integration-header-Bm6IDb1A.js +0 -1
- package/core/built/admin/assets/invite-user-modal-DfheMXs0.js +0 -1
- package/core/built/admin/assets/koenig-loader-aFQNlJlD.js +0 -2
- package/core/built/admin/assets/kpi-card-DBRMfGOW.js +0 -1
- package/core/built/admin/assets/kpi-card-DFafD1qq.js +0 -1
- package/core/built/admin/assets/kpi-helpers-Cv8i8hns.js +0 -1
- package/core/built/admin/assets/kpi-tabs-BJItvLYm.js +0 -1
- package/core/built/admin/assets/label-BnSCvffu.js +0 -1
- package/core/built/admin/assets/label-picker-KVoo91sh.js +0 -1
- package/core/built/admin/assets/labels-CH3Kn8RV.js +0 -1
- package/core/built/admin/assets/layout-Bjv2Ll-5.js +0 -1
- package/core/built/admin/assets/limit-service-BhiEvqcP.js +0 -20
- package/core/built/admin/assets/link-helpers-MVDdnFCY.js +0 -1
- package/core/built/admin/assets/list-page-BucXJ0hi.js +0 -1
- package/core/built/admin/assets/luxon-D1RVjjpq.js +0 -1
- package/core/built/admin/assets/member-custom-fields-DDQV6QeG.js +0 -1
- package/core/built/admin/assets/member-detail-CdJF6Agw.js +0 -1
- package/core/built/admin/assets/members-DjQbq9Z2.js +0 -4
- package/core/built/admin/assets/modal-page-xCivTMvI.js +0 -1
- package/core/built/admin/assets/moderation-AgCIEso4.js +0 -1
- package/core/built/admin/assets/multi-select-combobox-BcITkUVr.js +0 -1
- package/core/built/admin/assets/navigation-modal-BkT7b1M2.js +0 -1
- package/core/built/admin/assets/newsletter-BMMAuvgq.js +0 -1
- package/core/built/admin/assets/newsletter-detail-modal-0SCJ7Zpm.js +0 -1
- package/core/built/admin/assets/newsletter-emails-DKzwafuh.js +0 -1
- package/core/built/admin/assets/newsletters-BRBb_uQC.js +0 -1
- package/core/built/admin/assets/newsletters-DhNigJUh.js +0 -1
- package/core/built/admin/assets/note-DyGHtS9a.js +0 -1
- package/core/built/admin/assets/notifications-ZgWniKna.js +0 -1
- package/core/built/admin/assets/nql-lang-CaWrLesS.js +0 -19
- package/core/built/admin/assets/offers-container-modal-B-E8TIWr.js +0 -1
- package/core/built/admin/assets/onboarding-CzIsre-d.js +0 -1
- package/core/built/admin/assets/onboarding-qg8Akovm.js +0 -1
- package/core/built/admin/assets/onboarding-route-CaavZulg.js +0 -1
- package/core/built/admin/assets/overview-BW8GLeYi.js +0 -1
- package/core/built/admin/assets/overview-BkXZNzaZ.js +0 -1
- package/core/built/admin/assets/page-header-De0VzKBi.js +0 -1
- package/core/built/admin/assets/pagemenu-B5KKCpbA.js +0 -1
- package/core/built/admin/assets/pages-jgaFm69d.js +0 -1
- package/core/built/admin/assets/pages-route-CIUAQxtY.js +0 -1
- package/core/built/admin/assets/papaparse.min-cQwnxGCG.js +0 -10
- package/core/built/admin/assets/pintura-modal-BZ2J9w3n.js +0 -1
- package/core/built/admin/assets/portal-frame-B6Hr9wHc.js +0 -1
- package/core/built/admin/assets/portal-modal-D4ceei3t.js +0 -1
- package/core/built/admin/assets/post-analytics-context-D0Mw-Pmv.js +0 -1
- package/core/built/admin/assets/post-analytics-header-BYCsT0GP.js +0 -1
- package/core/built/admin/assets/post-analytics-ot7f6pih.js +0 -1
- package/core/built/admin/assets/post-analytics-provider-Bo1gmYaP.js +0 -1
- package/core/built/admin/assets/post-helpers-C5OQMBoi.js +0 -1
- package/core/built/admin/assets/post-share-modal-C-PaYZ_e.js +0 -1
- package/core/built/admin/assets/posts-DuGazgTb.js +0 -1
- package/core/built/admin/assets/posts-list-screen-CBGGgjjj.js +0 -2
- package/core/built/admin/assets/posts-route-D6xmS7Vf.js +0 -1
- package/core/built/admin/assets/preferences-C5YJLp74.js +0 -5
- package/core/built/admin/assets/preview-chrome-BljUBlOk.js +0 -1
- package/core/built/admin/assets/preview-modal-BfeFqi5y.js +0 -1
- package/core/built/admin/assets/profile-B8mx2yAu.js +0 -1
- package/core/built/admin/assets/prop-types-DeUPn3oD.js +0 -1
- package/core/built/admin/assets/radio-group-DczlLKOn.js +0 -1
- package/core/built/admin/assets/react-Bb3IsNbi.js +0 -1
- package/core/built/admin/assets/rolldown-runtime-DAXXjFlN.js +0 -1
- package/core/built/admin/assets/router-provider-B0gZyiGJ.js +0 -1
- package/core/built/admin/assets/schemas-DppqmpeJ.js +0 -64
- package/core/built/admin/assets/select-cb0t_TAJ.js +0 -1
- package/core/built/admin/assets/settings-DZqxR0Da.js +0 -1
- package/core/built/admin/assets/settings-DvfTNdHQ.js +0 -4
- package/core/built/admin/assets/settings-breadcrumbs-DLzbRRSt.js +0 -1
- package/core/built/admin/assets/share-modal-D2-Bz1_I.js +0 -1
- package/core/built/admin/assets/skeleton-Ce9GgUpf.js +0 -1
- package/core/built/admin/assets/slack-modal-DYDTUria.js +0 -1
- package/core/built/admin/assets/sort-button-VVeQjjbu.js +0 -1
- package/core/built/admin/assets/source-icon-Ck5_5Kgq.js +0 -1
- package/core/built/admin/assets/stack-B2eszIiq.js +0 -1
- package/core/built/admin/assets/stats-Rjmk5owg.js +0 -1
- package/core/built/admin/assets/stats-view-BGGmkmgv.js +0 -1
- package/core/built/admin/assets/step-1-CsaBjeN4.js +0 -1
- package/core/built/admin/assets/step-2-D2vnxOOM.js +0 -1
- package/core/built/admin/assets/step-3-wBoLdGEb.js +0 -2
- package/core/built/admin/assets/stripe-button-CDgLz0S0.js +0 -1
- package/core/built/admin/assets/stripe-connect-modal-CSjMJetP.js +0 -1
- package/core/built/admin/assets/switch-CI4gLWqb.js +0 -1
- package/core/built/admin/assets/table-Do6iQ83Q.js +0 -1
- package/core/built/admin/assets/tabs-D2yWLMp_.js +0 -1
- package/core/built/admin/assets/tag-detail-BhXlOsCa.js +0 -2
- package/core/built/admin/assets/tags-ldukRApe.js +0 -1
- package/core/built/admin/assets/testimonials-modal-CtnZBJKl.js +0 -1
- package/core/built/admin/assets/text-BQDSDsKs.js +0 -1
- package/core/built/admin/assets/textarea-DJ1Q5V39.js +0 -1
- package/core/built/admin/assets/tier-detail-modal-BOs8e_G8.js +0 -1
- package/core/built/admin/assets/tiers-Nd_z4BZY.js +0 -1
- package/core/built/admin/assets/toggle-group-ByUJsxwD.js +0 -1
- package/core/built/admin/assets/topic-filter-BgC3g5_v.js +0 -1
- package/core/built/admin/assets/transistor-modal-Drr0SNfo.js +0 -1
- package/core/built/admin/assets/trend-badge-6ye93YAW.js +0 -1
- package/core/built/admin/assets/unsplash-modal-p6AoQoWp.js +0 -1
- package/core/built/admin/assets/use-analytics-data-CWCMAESo.js +0 -1
- package/core/built/admin/assets/use-filter-params-BIni9taJ.js +0 -1
- package/core/built/admin/assets/use-filterable-api-CzXcpUh8.js +0 -1
- package/core/built/admin/assets/use-form-vRjI0Kwk.js +0 -1
- package/core/built/admin/assets/use-growth-stats-DCVG3s9w.js +0 -1
- package/core/built/admin/assets/use-host-limits-DZtR08oa.js +0 -1
- package/core/built/admin/assets/use-limiter-D2WRRx0H.js +0 -2
- package/core/built/admin/assets/use-navigate-with-base-path-BHPA228f.js +0 -1
- package/core/built/admin/assets/use-pintura-config-h-J0Lvta.js +0 -1
- package/core/built/admin/assets/use-pintura-editor-BfN1UwzH.js +0 -1
- package/core/built/admin/assets/use-post-referrers-Czg47m8s.js +0 -1
- package/core/built/admin/assets/use-query-params-BfzuqbcD.js +0 -1
- package/core/built/admin/assets/use-reply-chain-data-ns0caK8k.js +0 -1
- package/core/built/admin/assets/use-router-history-entry-uldn7zJO.js +0 -1
- package/core/built/admin/assets/use-setting-group-DpcuCUDg.js +0 -1
- package/core/built/admin/assets/use-settings-navigation-CZjGrhsp.js +0 -1
- package/core/built/admin/assets/use-simple-pagination-DzJWobPR.js +0 -1
- package/core/built/admin/assets/use-staff-users-Csipb-O0.js +0 -1
- package/core/built/admin/assets/use-tinybird-token-DTOKRR7e.js +0 -1
- package/core/built/admin/assets/use-unsaved-changes-guard-CBzs-KPi.js +0 -1
- package/core/built/admin/assets/use-upgrade-route-DhRSo7at.js +0 -1
- package/core/built/admin/assets/user-detail-modal-B_iUClI6.js +0 -1
- package/core/built/admin/assets/virtual-list-BW-padMe.js +0 -1
- package/core/built/admin/assets/web-C1v33ehA.js +0 -2
- package/core/built/admin/assets/web-C4BKzn2T.js +0 -1
- package/core/built/admin/assets/zapier-modal-BBFfI6Ml.js +0 -1
- package/core/server/services/email-suppression-list/in-memory-email-suppression-list.js +0 -39
- package/core/server/services/members-custom-fields/bindings-service.js +0 -120
- package/core/server/services/members-custom-fields/codec.js +0 -14
- package/core/server/services/members-custom-fields/queries.js +0 -32
- /package/core/built/admin/assets/{chunk.492.f24d855d7e3f3aad261c.js.LICENSE.txt → chunk.972.4b8c97f93d66db15e751.js.LICENSE.txt} +0 -0
- /package/core/server/services/{members-custom-fields → members-metafields}/storage.js +0 -0
|
@@ -75,7 +75,7 @@ class EmailAnalyticsServiceWrapper {
|
|
|
75
75
|
}
|
|
76
76
|
return result;
|
|
77
77
|
}
|
|
78
|
-
_logJobCompletion(jobType, fetchResult, totalDurationMs) {
|
|
78
|
+
_logJobCompletion(jobType, fetchResult, totalDurationMs, lagSeconds = null) {
|
|
79
79
|
const config = this.#getConfig();
|
|
80
80
|
const { eventCount, apiPollingTimeMs, processingTimeMs, aggregationTimeMs, emailAggregationTimeMs, memberAggregationTimeMs, result, } = fetchResult;
|
|
81
81
|
if (eventCount === 0) {
|
|
@@ -89,6 +89,7 @@ class EmailAnalyticsServiceWrapper {
|
|
|
89
89
|
const logMessage = [
|
|
90
90
|
`[Background Job] ${this.#backgroundJobName} processed ${jobType} | ${this.#logPrefix}`,
|
|
91
91
|
`${eventCount} events in ${(totalDurationMs / 1000).toFixed(1)}s (${throughput.toFixed(2)} events/s)`,
|
|
92
|
+
...(lagSeconds === null ? [] : [`Lag: ${(lagSeconds / 60).toFixed(1)}m`]),
|
|
92
93
|
`Mode: ${batchMode}`,
|
|
93
94
|
`Timings: API ${(apiPollingTimeMs / 1000).toFixed(1)}s (${apiPercent}%) / Processing ${(processingTimeMs / 1000).toFixed(1)}s (${processingPercent}%) / Aggregation ${(aggregationTimeMs / 1000).toFixed(1)}s (${aggregationPercent}%) [Email ${(emailAggregationTimeMs / 1000).toFixed(1)}s / Member ${(memberAggregationTimeMs / 1000).toFixed(1)}s]`,
|
|
94
95
|
`Events: opened=${result.opened} delivered=${result.delivered} failed=${result.permanentFailed + result.temporaryFailed} unprocessable=${result.unprocessable}`,
|
|
@@ -100,6 +101,7 @@ class EmailAnalyticsServiceWrapper {
|
|
|
100
101
|
task: jobType,
|
|
101
102
|
event_count: eventCount,
|
|
102
103
|
duration_ms: totalDurationMs,
|
|
104
|
+
...(lagSeconds === null ? {} : { lag_seconds: lagSeconds }),
|
|
103
105
|
},
|
|
104
106
|
}, logMessage);
|
|
105
107
|
// We're only concerned with open throughput as this is displayed to users and is most sensitive to being up to date
|
|
@@ -124,46 +126,30 @@ class EmailAnalyticsServiceWrapper {
|
|
|
124
126
|
}
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
|
-
async
|
|
128
|
-
const config = this.#getConfig();
|
|
129
|
-
const beginTimestamp = await this.service.getLastOpenedEventTimestamp();
|
|
130
|
-
const lagMinutes = (Date.now() - beginTimestamp.getTime()) / 60000;
|
|
131
|
-
const lagThreshold = config.get('emailAnalytics:openedJobLagWarningMinutes');
|
|
132
|
-
// NOTE: We only update the begin timestamp when we process events, so there's cases where we can have a false positive
|
|
133
|
-
// - Ghost or Mailgun outages
|
|
134
|
-
// - Lack of actual email activity
|
|
135
|
-
if (lagThreshold && lagMinutes > lagThreshold) {
|
|
136
|
-
logging_1.default.warn(`${this.#logPrefix} Opened events processing is ${lagMinutes.toFixed(1)} minutes behind (threshold: ${lagThreshold})`);
|
|
137
|
-
}
|
|
129
|
+
async #fetchAndLog(jobType, fetch, lagPipeline) {
|
|
138
130
|
const fetchStartedAt = Date.now();
|
|
139
|
-
const fetchResult = await
|
|
131
|
+
const fetchResult = await fetch();
|
|
140
132
|
const totalDuration = Date.now() - fetchStartedAt;
|
|
141
|
-
|
|
133
|
+
// Lag is read after the fetch so a clean run counts as caught up
|
|
134
|
+
const lagSeconds = lagPipeline ? this.service.getStatus()[lagPipeline].lagSeconds : null;
|
|
135
|
+
this._logJobCompletion(jobType, fetchResult, totalDuration, lagSeconds);
|
|
142
136
|
return fetchResult.eventCount;
|
|
143
137
|
}
|
|
138
|
+
async fetchLatestOpenedEvents({ maxEvents = Infinity, } = {}) {
|
|
139
|
+
return this.#fetchAndLog('latest-opened', () => this.service.fetchLatestOpenedEvents({ maxEvents }), 'latestOpened');
|
|
140
|
+
}
|
|
144
141
|
async fetchLatestNonOpenedEvents({ maxEvents = Infinity, } = {}) {
|
|
145
|
-
|
|
146
|
-
const fetchResult = await this.service.fetchLatestNonOpenedEvents({ maxEvents });
|
|
147
|
-
const totalDuration = Date.now() - fetchStartedAt;
|
|
148
|
-
this._logJobCompletion('latest', fetchResult, totalDuration);
|
|
149
|
-
return fetchResult.eventCount;
|
|
142
|
+
return this.#fetchAndLog('latest', () => this.service.fetchLatestNonOpenedEvents({ maxEvents }), 'latest');
|
|
150
143
|
}
|
|
151
144
|
async fetchMissing({ maxEvents = Infinity } = {}) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const totalDuration = Date.now() - fetchStartedAt;
|
|
155
|
-
this._logJobCompletion('missing', fetchResult, totalDuration);
|
|
156
|
-
return fetchResult.eventCount;
|
|
145
|
+
// The missing-events sweep trails the delivery pipeline by design, so it has no lag of its own
|
|
146
|
+
return this.#fetchAndLog('missing', () => this.service.fetchMissing({ maxEvents }));
|
|
157
147
|
}
|
|
158
148
|
async fetchScheduled({ maxEvents }) {
|
|
159
149
|
if (maxEvents < 300) {
|
|
160
150
|
return 0;
|
|
161
151
|
}
|
|
162
|
-
|
|
163
|
-
const fetchResult = await this.service.fetchScheduled({ maxEvents });
|
|
164
|
-
const totalDuration = Date.now() - fetchStartedAt;
|
|
165
|
-
this._logJobCompletion('scheduled', fetchResult, totalDuration);
|
|
166
|
-
return fetchResult.eventCount;
|
|
152
|
+
return this.#fetchAndLog('scheduled', () => this.service.fetchScheduled({ maxEvents }));
|
|
167
153
|
}
|
|
168
154
|
async startFetch() {
|
|
169
155
|
const startedAt = Date.now();
|
|
@@ -65,11 +65,18 @@ class EmailAnalyticsService {
|
|
|
65
65
|
this.queries.setJobMetadata(this.#jobNames.scheduled, null);
|
|
66
66
|
}
|
|
67
67
|
getStatus() {
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
const withLag = (data) => Object.assign(data, {
|
|
70
|
+
fetchedThrough: data.fetchedThrough ?? null,
|
|
71
|
+
lagSeconds: data.fetchedThrough
|
|
72
|
+
? Math.max(0, Math.floor((now - data.fetchedThrough.getTime()) / 1000))
|
|
73
|
+
: null,
|
|
74
|
+
});
|
|
68
75
|
return {
|
|
69
|
-
latest: this.#fetchLatestNonOpenedData,
|
|
70
|
-
missing: this.#fetchMissingData,
|
|
76
|
+
latest: withLag(this.#fetchLatestNonOpenedData),
|
|
77
|
+
missing: withLag(this.#fetchMissingData),
|
|
71
78
|
scheduled: this.#fetchScheduledData,
|
|
72
|
-
latestOpened: this.#fetchLatestOpenedData,
|
|
79
|
+
latestOpened: withLag(this.#fetchLatestOpenedData),
|
|
73
80
|
};
|
|
74
81
|
}
|
|
75
82
|
/**
|
|
@@ -363,6 +370,7 @@ class EmailAnalyticsService {
|
|
|
363
370
|
});
|
|
364
371
|
}
|
|
365
372
|
};
|
|
373
|
+
let fetchedThrough;
|
|
366
374
|
try {
|
|
367
375
|
const fetchResult = await this.#fetchEvents({
|
|
368
376
|
batchHandler: processBatch,
|
|
@@ -371,6 +379,11 @@ class EmailAnalyticsService {
|
|
|
371
379
|
maxEvents,
|
|
372
380
|
events: eventTypes,
|
|
373
381
|
});
|
|
382
|
+
// A void result means fetching was skipped (for example, Mailgun is not configured).
|
|
383
|
+
// Empty successful windows still establish progress through their requested end.
|
|
384
|
+
if (fetchResult) {
|
|
385
|
+
fetchedThrough = fetchResult.safeCursor ?? end;
|
|
386
|
+
}
|
|
374
387
|
if (fetchResult?.safeCursor &&
|
|
375
388
|
(!fetchData.lastEventTimestamp || fetchResult.safeCursor < fetchData.lastEventTimestamp)) {
|
|
376
389
|
fetchData.lastEventTimestamp = fetchResult.safeCursor;
|
|
@@ -425,6 +438,9 @@ class EmailAnalyticsService {
|
|
|
425
438
|
if (error) {
|
|
426
439
|
throw error;
|
|
427
440
|
}
|
|
441
|
+
if (fetchedThrough) {
|
|
442
|
+
fetchData.fetchedThrough = fetchedThrough;
|
|
443
|
+
}
|
|
428
444
|
return {
|
|
429
445
|
eventCount,
|
|
430
446
|
apiPollingTimeMs,
|
|
@@ -50,8 +50,13 @@ const init = ({ automationsApi, config, db, domainEvents, emailSuppressionList,
|
|
|
50
50
|
prometheusClient,
|
|
51
51
|
});
|
|
52
52
|
const newsletterMailgunTags = ['bulk-email'];
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const automationMailgunTags = [constants_1.AUTOMATION_EMAIL_TAG];
|
|
54
|
+
const giftMailgunTags = [constants_2.GIFT_DELIVERY_EMAIL_TAG];
|
|
55
|
+
const mailgunTagFromConfig = config.get('bulkEmail:mailgun:tag');
|
|
56
|
+
if (mailgunTagFromConfig) {
|
|
57
|
+
newsletterMailgunTags.push(mailgunTagFromConfig);
|
|
58
|
+
automationMailgunTags.push(mailgunTagFromConfig);
|
|
59
|
+
giftMailgunTags.push(mailgunTagFromConfig);
|
|
55
60
|
}
|
|
56
61
|
prometheusClient?.registerCounter({
|
|
57
62
|
name: newsletter_email_analytics_batch_processor_1.AGGREGATE_MEMBER_STATS_METRIC_NAME,
|
|
@@ -91,7 +96,7 @@ const init = ({ automationsApi, config, db, domainEvents, emailSuppressionList,
|
|
|
91
96
|
domainEvents,
|
|
92
97
|
event: start_automation_email_analytics_job_event_1.StartAutomationEmailAnalyticsJobEvent,
|
|
93
98
|
queries,
|
|
94
|
-
mailgunTags:
|
|
99
|
+
mailgunTags: automationMailgunTags,
|
|
95
100
|
jobNames: {
|
|
96
101
|
latestNonOpened: 'email-analytics-automation-latest-others',
|
|
97
102
|
missing: 'email-analytics-automation-missing',
|
|
@@ -116,7 +121,7 @@ const init = ({ automationsApi, config, db, domainEvents, emailSuppressionList,
|
|
|
116
121
|
domainEvents,
|
|
117
122
|
event: start_gift_email_analytics_job_event_1.StartGiftEmailAnalyticsJobEvent,
|
|
118
123
|
queries,
|
|
119
|
-
mailgunTags:
|
|
124
|
+
mailgunTags: giftMailgunTags,
|
|
120
125
|
jobNames: {
|
|
121
126
|
latestNonOpened: 'email-analytics-gifts-latest-others',
|
|
122
127
|
missing: 'email-analytics-gifts-missing',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const cheerio = require('cheerio');
|
|
1
|
+
const cheerio = require('cheerio/slim');
|
|
2
2
|
const juice = require('juice');
|
|
3
3
|
const htmlToPlaintext = require('@tryghost/html-to-plaintext');
|
|
4
4
|
|
|
@@ -12,7 +12,12 @@ const finalizeHtml = (html) => {
|
|
|
12
12
|
$('figcaption').addClass('kg-card-figcaption');
|
|
13
13
|
html = $.html();
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// resolveCSSVariables crashes on nameless declarations in user-authored style attributes
|
|
16
|
+
const juicedHtml = juice(html, {
|
|
17
|
+
inlinePseudoElements: true,
|
|
18
|
+
removeStyleTags: true,
|
|
19
|
+
resolveCSSVariables: false,
|
|
20
|
+
});
|
|
16
21
|
|
|
17
22
|
// Many email clients, like Outlook and Yahoo, [lack support for <figure>
|
|
18
23
|
// and <figcaption>][0]. To work around this, change the tags to <div>s.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Newsletter Email Sending
|
|
2
|
+
|
|
3
|
+
Newsletter Email Sending covers the preparation and submission of newsletter emails to the configured email provider.
|
|
4
|
+
|
|
5
|
+
## Language
|
|
6
|
+
|
|
7
|
+
**Sending status**:
|
|
8
|
+
The state of a newsletter email's send: preparing, submitting, submitted, or failed. It is separate from any later provider-reported outcome.
|
|
9
|
+
_Avoid_: Delivery status, provider outcome
|
|
10
|
+
|
|
11
|
+
**Sending phase**:
|
|
12
|
+
The part of a send, before it is submitted or fails, that progress and failures are reported against: preparing or submitting.
|
|
13
|
+
_Avoid_: Step, stage
|
|
14
|
+
|
|
15
|
+
**Preparing**:
|
|
16
|
+
The sending phase while none of a newsletter email's recipient batches has started submitting, including before any sending work has begun.
|
|
17
|
+
_Avoid_: Pending, queued
|
|
18
|
+
|
|
19
|
+
**Submitting**:
|
|
20
|
+
The sending phase once at least one recipient batch has started submitting to the configured email provider and the send has not yet been submitted or failed.
|
|
21
|
+
_Avoid_: Sending, delivering
|
|
22
|
+
|
|
23
|
+
**Sending progress**:
|
|
24
|
+
The completed and total recipient counts for the current sending phase.
|
|
25
|
+
_Avoid_: Delivery progress, expected count
|
|
26
|
+
|
|
27
|
+
**Submitted**:
|
|
28
|
+
Every batch of a newsletter email has been accepted by the configured email provider. Submission does not mean that recipients have received the email.
|
|
29
|
+
_Avoid_: Sent, delivered
|
|
30
|
+
|
|
31
|
+
**Failed**:
|
|
32
|
+
Sending stopped with an error and will not continue without a retry.
|
|
33
|
+
_Avoid_: Bounced, rejected
|
|
34
|
+
|
|
35
|
+
**Failed during**:
|
|
36
|
+
The sending phase a failed newsletter email was in when sending stopped.
|
|
37
|
+
_Avoid_: Failure step
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Email service
|
|
2
|
+
|
|
3
|
+
Renders newsletter emails, splits them into recipient batches, and submits the
|
|
4
|
+
batches to the configured email provider. Domain terms live in
|
|
5
|
+
[CONTEXT.md](CONTEXT.md).
|
|
6
|
+
|
|
7
|
+
## Sending status
|
|
8
|
+
|
|
9
|
+
The sending status served by the Admin API's `emails/:id/status` endpoint is
|
|
10
|
+
derived on read. `sending-status.ts` owns the derivation from an email and its
|
|
11
|
+
batches with their recipient counts; `SendingStatusService` reads those rows
|
|
12
|
+
and `sending-status-serializers.ts` shapes the response. Submitted is terminal and the batch aggregation only
|
|
13
|
+
describes a send that is still in progress or has failed, so submitted emails answer with the email's stored
|
|
14
|
+
`email_count` as both completed and total; batch creation reconciles that
|
|
15
|
+
column to the recipient rows it built. That also keeps reads of long-finished
|
|
16
|
+
emails cheap, with no query over the batches or recipients. The endpoint is always available; Admin decides
|
|
17
|
+
whether to poll it while a send is active.
|
|
18
|
+
|
|
19
|
+
The phase is read from the batches: an email is submitting once any batch has
|
|
20
|
+
left `pending`, and preparing otherwise. Batch statuses persist across
|
|
21
|
+
attempts, so a retried email reports submitting with frozen progress while it
|
|
22
|
+
waits for its job, and `failed_during` is the same derivation applied to a
|
|
23
|
+
failed email.
|
|
24
|
+
|
|
25
|
+
The rough ETA measures recent recipient throughput, including work completed by
|
|
26
|
+
concurrent workers. It stays `null` until enough timing samples are available in
|
|
27
|
+
the current phase and attempt, so short sends may finish without showing an ETA.
|
|
28
|
+
The rate uses the last minute of completions, including the interval crossing
|
|
29
|
+
the window boundary. Early estimates use the available history. Sparse sends
|
|
30
|
+
retain at least two measured intervals.
|
|
31
|
+
Progress counts update independently of the estimate.
|
|
32
|
+
|
|
33
|
+
The ETA is always `null` for failed emails and `0` once no work remains in the
|
|
34
|
+
current phase. Batches that failed during the current attempt are not remaining
|
|
35
|
+
work until the email is retried, so the ETA can reach `0` while completed is
|
|
36
|
+
still below total. Consumers should key completion on the status, never the ETA.
|
|
37
|
+
|
|
38
|
+
Ghost does not record when a sending attempt or phase started, so the ETA uses
|
|
39
|
+
the email's `updated_at` as a proxy for the start of the current attempt: the
|
|
40
|
+
sending job saves the email when it takes its status lock and, when the
|
|
41
|
+
recipient count changed, again after creating batches; a retry or boot resume
|
|
42
|
+
saves it when re-queuing the email. Batches completed before that timestamp
|
|
43
|
+
belong to an earlier attempt and are left out of the rate window. The proxy
|
|
44
|
+
only holds while nothing saves the Email model, or sets `emails.updated_at`
|
|
45
|
+
through a raw update, while batches are being submitted.
|
|
@@ -213,6 +213,7 @@ class BatchSendingService {
|
|
|
213
213
|
|
|
214
214
|
// Check if email is 'pending' only + change status to submitting in one transaction.
|
|
215
215
|
// This allows us to have a lock around the email job that makes sure an email can only have one active job.
|
|
216
|
+
// Also stamps updated_at, which SendingStatusService reads as the attempt start; do not save the Email once batches submit.
|
|
216
217
|
let email;
|
|
217
218
|
try {
|
|
218
219
|
email = await this.retryDb(
|
|
@@ -704,6 +705,7 @@ class BatchSendingService {
|
|
|
704
705
|
const deliveryTimes = this.calculateDeliveryTimes(email, batches.length);
|
|
705
706
|
|
|
706
707
|
// Loop batches and send them via the EmailProvider
|
|
708
|
+
// SendingStatusService treats a batch that fails in this run as finished work; never re-queue it within the run.
|
|
707
709
|
let succeededCount = 0;
|
|
708
710
|
const queue = batches.slice();
|
|
709
711
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const errors = require('@tryghost/errors');
|
|
2
2
|
const tpl = require('@tryghost/tpl');
|
|
3
3
|
|
|
4
|
+
/** @typedef {import('./sending-status-service').SendingStatusService} SendingStatusService */
|
|
5
|
+
|
|
4
6
|
const messages = {
|
|
5
7
|
postNotFound: 'Post not found.',
|
|
6
8
|
noEmailsProvided: 'No emails provided.',
|
|
@@ -23,16 +25,18 @@ class EmailController {
|
|
|
23
25
|
service;
|
|
24
26
|
models;
|
|
25
27
|
getRequiredUrlRelations;
|
|
28
|
+
sendingStatusService;
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
31
|
*
|
|
29
32
|
* @param {EmailService} service
|
|
30
|
-
* @param {{models: {Post: any, Newsletter: any, Email: any}, getRequiredUrlRelations?: () => string[]}} dependencies
|
|
33
|
+
* @param {{models: {Post: any, Newsletter: any, Email: any}, getRequiredUrlRelations?: () => string[], sendingStatusService: SendingStatusService}} dependencies
|
|
31
34
|
*/
|
|
32
|
-
constructor(service, { models, getRequiredUrlRelations = () => [] }) {
|
|
35
|
+
constructor(service, { models, getRequiredUrlRelations = () => [], sendingStatusService }) {
|
|
33
36
|
this.service = service;
|
|
34
37
|
this.models = models;
|
|
35
38
|
this.getRequiredUrlRelations = getRequiredUrlRelations;
|
|
39
|
+
this.sendingStatusService = sendingStatusService;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
async _getFrameData(frame) {
|
|
@@ -143,6 +147,18 @@ class EmailController {
|
|
|
143
147
|
|
|
144
148
|
return await this.service.retryEmail(email);
|
|
145
149
|
}
|
|
150
|
+
|
|
151
|
+
async getSendingStatus(frame) {
|
|
152
|
+
const status = await this.sendingStatusService.statusFor(frame.data.id);
|
|
153
|
+
|
|
154
|
+
if (!status) {
|
|
155
|
+
throw new errors.NotFoundError({
|
|
156
|
+
message: tpl(messages.emailNotFound),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return status;
|
|
161
|
+
}
|
|
146
162
|
}
|
|
147
163
|
|
|
148
164
|
module.exports = EmailController;
|
|
@@ -192,7 +192,7 @@ function escapeRegExp(string) {
|
|
|
192
192
|
* @return {ReturnType<typeof cheerio.load>}
|
|
193
193
|
*/
|
|
194
194
|
function cheerioLoad(html) {
|
|
195
|
-
const cheerio = require('cheerio');
|
|
195
|
+
const cheerio = require('cheerio/slim');
|
|
196
196
|
return cheerio.load(html);
|
|
197
197
|
}
|
|
198
198
|
|
|
@@ -709,7 +709,12 @@ class EmailRenderer {
|
|
|
709
709
|
|
|
710
710
|
// Juice HTML (inline CSS)
|
|
711
711
|
const juice = require('juice');
|
|
712
|
-
|
|
712
|
+
// resolveCSSVariables crashes on nameless declarations in user-authored style attributes
|
|
713
|
+
html = juice(html, {
|
|
714
|
+
inlinePseudoElements: true,
|
|
715
|
+
removeStyleTags: true,
|
|
716
|
+
resolveCSSVariables: false,
|
|
717
|
+
});
|
|
713
718
|
|
|
714
719
|
// happens after inlining of CSS so we can change element types without worrying about styling
|
|
715
720
|
$ = cheerioLoad(html);
|
|
@@ -24,6 +24,7 @@ class EmailServiceWrapper {
|
|
|
24
24
|
const EmailRenderer = require('./email-renderer');
|
|
25
25
|
const SendingService = require('./sending-service');
|
|
26
26
|
const BatchSendingService = require('./batch-sending-service');
|
|
27
|
+
const { SendingStatusService } = require('./sending-status-service');
|
|
27
28
|
const EmailSegmenter = require('./email-segmenter');
|
|
28
29
|
const MailgunEmailProvider = require('./mailgun-email-provider');
|
|
29
30
|
const { DomainWarmingService } = require('./domain-warming-service');
|
|
@@ -43,7 +44,7 @@ class EmailServiceWrapper {
|
|
|
43
44
|
const limitService = require('../limits');
|
|
44
45
|
const labs = require('../../../shared/labs');
|
|
45
46
|
const emailAddressService = require('../email-address');
|
|
46
|
-
const i18nLib = require('@tryghost/i18n');
|
|
47
|
+
const i18nLib = require('@tryghost/i18n').default;
|
|
47
48
|
const lexicalLib = require('../../lib/lexical');
|
|
48
49
|
const urlUtils = require('../../../shared/url-utils').default;
|
|
49
50
|
const memberAttribution = require('../member-attribution');
|
|
@@ -127,6 +128,7 @@ class EmailServiceWrapper {
|
|
|
127
128
|
sentry,
|
|
128
129
|
getRequiredUrlRelations,
|
|
129
130
|
});
|
|
131
|
+
const sendingStatusService = new SendingStatusService({ knex: db.knex });
|
|
130
132
|
|
|
131
133
|
if (ghostServer) {
|
|
132
134
|
// Two phases: stop claiming batches immediately, drain in-flight ones later.
|
|
@@ -169,6 +171,7 @@ class EmailServiceWrapper {
|
|
|
169
171
|
Email,
|
|
170
172
|
},
|
|
171
173
|
getRequiredUrlRelations,
|
|
174
|
+
sendingStatusService,
|
|
172
175
|
});
|
|
173
176
|
}
|
|
174
177
|
}
|
|
@@ -29,12 +29,15 @@ const messages = {
|
|
|
29
29
|
retryEmailNotFailed: 'Only failed emails can be retried',
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
// Resume scanner won't pick up `submitting` rows older than this. Rows beyond
|
|
33
|
-
// are flipped to `failed` on first boot so they surface in admin UI for operator
|
|
34
|
-
// rather than being silently resumed (and sending stale newsletters to current members).
|
|
32
|
+
// Resume scanner won't pick up `pending` or `submitting` rows older than this. Rows beyond
|
|
33
|
+
// the cutoff are flipped to `failed` on first boot so they surface in admin UI for operator
|
|
34
|
+
// review rather than being silently resumed (and sending stale newsletters to current members).
|
|
35
35
|
// Override via `bulkEmail:resumeMaxAgeMs` in config.
|
|
36
36
|
const DEFAULT_RESUME_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
37
37
|
|
|
38
|
+
// Non-terminal email statuses, both of which the boot scanner recovers.
|
|
39
|
+
const RESUMABLE_EMAIL_STATUSES = ['pending', 'submitting'];
|
|
40
|
+
|
|
38
41
|
class EmailService {
|
|
39
42
|
#batchSendingService;
|
|
40
43
|
#sendingService;
|
|
@@ -223,28 +226,31 @@ class EmailService {
|
|
|
223
226
|
}
|
|
224
227
|
|
|
225
228
|
/**
|
|
226
|
-
* Boot-time scanner:
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
229
|
+
* Boot-time scanner: recovers newsletter emails a previous container left without work.
|
|
230
|
+
* Two shapes, both resumed through the normal emailJob path:
|
|
231
|
+
* - `submitting` — the send was cut off mid-flight.
|
|
232
|
+
* - `pending` — dispatch was dropped from the in-memory job queue before the job ever
|
|
233
|
+
* started, so no batch work exists and nothing else will ever pick the row up.
|
|
234
|
+
* Iterates sequentially; one failure does not skip others. Rows older than the configured
|
|
235
|
+
* max-age are flipped to `failed` (not resumed) so stale content does not get sent to
|
|
236
|
+
* current members.
|
|
230
237
|
*/
|
|
231
238
|
async resumeInterruptedSends() {
|
|
232
239
|
const maxAgeMs = this.#config?.get?.('bulkEmail:resumeMaxAgeMs') ?? DEFAULT_RESUME_MAX_AGE_MS;
|
|
233
240
|
const cutoffIso = new Date(Date.now() - maxAgeMs).toISOString();
|
|
234
241
|
|
|
235
242
|
// Stale rows: too old to safely resume. Flip to `failed` so they surface in admin UI
|
|
236
|
-
// for operator review instead of being silently
|
|
243
|
+
// for operator review instead of being silently stuck in their current status forever.
|
|
237
244
|
const stale = await this.#models.Email.findAll({
|
|
238
|
-
filter: `status:
|
|
245
|
+
filter: `status:[${RESUMABLE_EMAIL_STATUSES.join(',')}]+created_at:<'${cutoffIso}'`,
|
|
239
246
|
});
|
|
240
|
-
const
|
|
241
|
-
for (const email of staleList) {
|
|
247
|
+
for (const email of stale.models) {
|
|
242
248
|
try {
|
|
243
249
|
const locked = await this.#batchSendingService.updateStatusLock(
|
|
244
250
|
this.#models.Email,
|
|
245
251
|
email.id,
|
|
246
252
|
'failed',
|
|
247
|
-
['
|
|
253
|
+
[email.get('status')],
|
|
248
254
|
);
|
|
249
255
|
if (locked) {
|
|
250
256
|
logging.warn(
|
|
@@ -257,20 +263,19 @@ class EmailService {
|
|
|
257
263
|
}
|
|
258
264
|
|
|
259
265
|
// Fresh rows: within the cutoff. Resume through the normal emailJob path.
|
|
260
|
-
const
|
|
261
|
-
filter: `status:
|
|
266
|
+
const fresh = await this.#models.Email.findAll({
|
|
267
|
+
filter: `status:[${RESUMABLE_EMAIL_STATUSES.join(',')}]+created_at:>'${cutoffIso}'`,
|
|
262
268
|
});
|
|
263
|
-
|
|
264
|
-
if (staleList.length === 0 && list.length === 0) {
|
|
269
|
+
if (stale.models.length === 0 && fresh.models.length === 0) {
|
|
265
270
|
return;
|
|
266
271
|
}
|
|
267
|
-
if (
|
|
272
|
+
if (fresh.models.length > 0) {
|
|
268
273
|
logging.info(
|
|
269
|
-
`Email resume: found ${
|
|
274
|
+
`Email resume: found ${fresh.models.length} pending or submitting email(s) within max age (${maxAgeMs}ms)`,
|
|
270
275
|
);
|
|
271
276
|
}
|
|
272
277
|
|
|
273
|
-
for (const email of
|
|
278
|
+
for (const email of fresh.models) {
|
|
274
279
|
try {
|
|
275
280
|
await this.#resumeOneEmail(email);
|
|
276
281
|
} catch (e) {
|
|
@@ -279,23 +284,24 @@ class EmailService {
|
|
|
279
284
|
}
|
|
280
285
|
|
|
281
286
|
logging.info(
|
|
282
|
-
`Email resume scan complete: ${
|
|
287
|
+
`Email resume scan complete: ${stale.models.length} stale email(s) flipped to failed, ${fresh.models.length} fresh email(s) rescheduled`,
|
|
283
288
|
);
|
|
284
289
|
}
|
|
285
290
|
|
|
286
291
|
async #resumeOneEmail(email) {
|
|
292
|
+
const status = email.get('status');
|
|
287
293
|
const post = await email.getLazyRelation('post');
|
|
288
294
|
const postStatus = post ? post.get('status') : null;
|
|
289
295
|
const sendable = postStatus === 'published' || postStatus === 'sent';
|
|
290
296
|
|
|
291
297
|
if (!sendable) {
|
|
292
298
|
// Parent post was unpublished or deleted while the email was in flight.
|
|
293
|
-
// Can't resume — mark the email as failed so it stops showing as
|
|
299
|
+
// Can't resume — mark the email as failed so it stops showing as unsent.
|
|
294
300
|
const locked = await this.#batchSendingService.updateStatusLock(
|
|
295
301
|
this.#models.Email,
|
|
296
302
|
email.id,
|
|
297
303
|
'failed',
|
|
298
|
-
[
|
|
304
|
+
[status],
|
|
299
305
|
);
|
|
300
306
|
if (locked) {
|
|
301
307
|
logging.warn(
|
|
@@ -305,19 +311,22 @@ class EmailService {
|
|
|
305
311
|
return;
|
|
306
312
|
}
|
|
307
313
|
|
|
308
|
-
//
|
|
309
|
-
// If updateStatusLock returns undefined, another path
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
logging.info(
|
|
318
|
-
`Email resume: ${email.id} status changed before lock could be taken — skipping`,
|
|
314
|
+
// `pending` is already what emailJob's status lock (['pending', 'failed']) admits, so
|
|
315
|
+
// only `submitting` needs flipping. If updateStatusLock returns undefined, another path
|
|
316
|
+
// raced us — leave it to whoever won.
|
|
317
|
+
if (status === 'submitting') {
|
|
318
|
+
const locked = await this.#batchSendingService.updateStatusLock(
|
|
319
|
+
this.#models.Email,
|
|
320
|
+
email.id,
|
|
321
|
+
'pending',
|
|
322
|
+
['submitting'],
|
|
319
323
|
);
|
|
320
|
-
|
|
324
|
+
if (!locked) {
|
|
325
|
+
logging.info(
|
|
326
|
+
`Email resume: ${email.id} status changed before lock could be taken — skipping`,
|
|
327
|
+
);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
321
330
|
}
|
|
322
331
|
|
|
323
332
|
// Structured breadcrumb so post-incident timing/batch state is recoverable from logs.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DbBatchSendingRow = exports.DbEmailSendingRow = exports.StoredSendingStatus = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const count_1 = require("../../lib/db-types/count");
|
|
6
|
+
const date_1 = require("../../lib/db-types/date");
|
|
7
|
+
exports.StoredSendingStatus = zod_1.z.enum(['pending', 'submitting', 'submitted', 'failed']);
|
|
8
|
+
// Projections of tables the Bookshelf models own, not full rows, so there is no knex table
|
|
9
|
+
// augmentation here: typing `emails` by these columns alone would mistype every other read.
|
|
10
|
+
exports.DbEmailSendingRow = zod_1.z.object({
|
|
11
|
+
id: zod_1.z.string(),
|
|
12
|
+
status: exports.StoredSendingStatus,
|
|
13
|
+
email_count: count_1.DbCount,
|
|
14
|
+
updated_at: date_1.DbDate.nullable(),
|
|
15
|
+
});
|
|
16
|
+
exports.DbBatchSendingRow = zod_1.z.object({
|
|
17
|
+
status: exports.StoredSendingStatus,
|
|
18
|
+
created_at: date_1.DbDate,
|
|
19
|
+
updated_at: date_1.DbDate,
|
|
20
|
+
recipient_count: count_1.DbCount,
|
|
21
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toEmailStatusesResponse = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const case_keys_1 = require("../../lib/case-keys");
|
|
6
|
+
const sending_status_1 = require("./sending-status");
|
|
7
|
+
const SendingPhaseResource = zod_1.z.enum(['preparing', 'submitting']);
|
|
8
|
+
const SendingResource = zod_1.z.object({
|
|
9
|
+
status: zod_1.z.enum(['preparing', 'submitting', 'submitted', 'failed']),
|
|
10
|
+
progress: zod_1.z.object({
|
|
11
|
+
completed: zod_1.z.number(),
|
|
12
|
+
total: zod_1.z.number(),
|
|
13
|
+
estimated_seconds_remaining: zod_1.z.number().nullable(),
|
|
14
|
+
}),
|
|
15
|
+
failed_during: SendingPhaseResource.optional(),
|
|
16
|
+
});
|
|
17
|
+
const EmailStatusResource = zod_1.z.object({
|
|
18
|
+
id: zod_1.z.string(),
|
|
19
|
+
sending: SendingResource,
|
|
20
|
+
});
|
|
21
|
+
const EmailStatusesResponse = zod_1.z.object({ email_statuses: zod_1.z.array(EmailStatusResource) });
|
|
22
|
+
exports.toEmailStatusesResponse = sending_status_1.EmailSendingStatus.transform(({ id, sending }) => ({
|
|
23
|
+
email_statuses: [
|
|
24
|
+
{
|
|
25
|
+
id,
|
|
26
|
+
sending: {
|
|
27
|
+
status: sending.status,
|
|
28
|
+
progress: (0, case_keys_1.snakeKeys)(sending.progress),
|
|
29
|
+
...(sending.status === 'failed' ? { failed_during: sending.failedDuring } : {}),
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
})).pipe(EmailStatusesResponse);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SendingStatusService = void 0;
|
|
4
|
+
const case_keys_1 = require("../../lib/case-keys");
|
|
5
|
+
const sending_status_schema_1 = require("./sending-status-schema");
|
|
6
|
+
const sending_status_1 = require("./sending-status");
|
|
7
|
+
class SendingStatusService {
|
|
8
|
+
#knex;
|
|
9
|
+
constructor({ knex }) {
|
|
10
|
+
this.#knex = knex;
|
|
11
|
+
}
|
|
12
|
+
async statusFor(emailId) {
|
|
13
|
+
const row = await this.#knex('emails')
|
|
14
|
+
.select('id', 'status', 'email_count', 'updated_at')
|
|
15
|
+
.where('id', emailId)
|
|
16
|
+
.first();
|
|
17
|
+
if (!row) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const email = sending_status_schema_1.DbEmailSendingRow.parse(row);
|
|
21
|
+
// A submitted email answers from its own count, and batch creation reconciles email_count
|
|
22
|
+
// to the recipient rows it built, so the batch query is skipped rather than run and ignored.
|
|
23
|
+
const batches = email.status === 'submitted' ? [] : await this.#batchesFor(emailId);
|
|
24
|
+
return {
|
|
25
|
+
id: email.id,
|
|
26
|
+
sending: (0, sending_status_1.buildSendingStatus)({
|
|
27
|
+
status: email.status,
|
|
28
|
+
recipientCount: email.email_count,
|
|
29
|
+
// The sending job saves the email when it takes its status lock, so updated_at
|
|
30
|
+
// stands in for the attempt start that Ghost does not record.
|
|
31
|
+
attemptStartedAt: email.updated_at,
|
|
32
|
+
}, batches),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async #batchesFor(emailId) {
|
|
36
|
+
// Correlated per-batch count stays on the batch_id index; grouping recipients by email_id scans every recipient row.
|
|
37
|
+
const recipientCount = this.#knex('email_recipients as recipient')
|
|
38
|
+
.count('*')
|
|
39
|
+
.whereRaw('recipient.batch_id = batch.id');
|
|
40
|
+
const rows = await this.#knex('email_batches as batch')
|
|
41
|
+
.select('batch.status', 'batch.created_at', 'batch.updated_at')
|
|
42
|
+
.select(recipientCount.as('recipient_count'))
|
|
43
|
+
.where('batch.email_id', emailId);
|
|
44
|
+
return rows.map((batchRow) => (0, case_keys_1.camelKeys)(sending_status_schema_1.DbBatchSendingRow.parse(batchRow)));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.SendingStatusService = SendingStatusService;
|