ghost 6.53.0 → 6.54.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/components/ghost-storage-base-3.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-cache-0.2.0.tgz +0 -0
- package/components/tryghost-adapter-base-scheduling-0.1.1.tgz +0 -0
- package/components/tryghost-adapter-base-sso-0.1.1.tgz +0 -0
- package/components/tryghost-admin-api-schema-0.0.0.tgz +0 -0
- package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/components/tryghost-i18n-0.0.0.tgz +0 -0
- package/components/tryghost-kg-converters-1.2.3.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
- package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
- package/core/boot.js +9 -6
- package/core/built/admin/assets/Combination-CJG7Flp-.js +41 -0
- package/core/built/admin/assets/Growth-CSaHoSVD.js +3 -0
- package/core/built/admin/assets/Newsletters-C5cs6FGo.js +1 -0
- package/core/built/admin/assets/Overview-j4NaeUNm.js +1 -0
- package/core/built/admin/assets/{PolarAngleAxis-pciQ0MOy.js → PolarAngleAxis-BGjf7sBu.js} +1 -1
- package/core/built/admin/assets/{RadialBarChart-DWmw2PcX.js → RadialBarChart-zeFf9mBz.js} +1 -1
- package/core/built/admin/assets/Web-_fD935OX.js +2 -0
- package/core/built/admin/assets/account-migration-DLvVA5yq.js +1 -0
- package/core/built/admin/assets/activitypub/{account-migration-BPZG37DJ.js → account-migration-DHbZcxGM.js} +13 -13
- package/core/built/admin/assets/activitypub/activitypub.js +7 -7
- package/core/built/admin/assets/activitypub/{avatar-ZYco0ITb.js → avatar-B3DEDB1l.js} +10 -10
- package/core/built/admin/assets/activitypub/{avatar-flipboard-D541lc4F.js → avatar-flipboard-Cqrg0XMx.js} +3 -3
- package/core/built/admin/assets/activitypub/{bluesky-sharing-NOHUtHjl.js → bluesky-sharing-zfxUey_L.js} +38 -37
- package/core/built/admin/assets/activitypub/{chunk-KS7C4IRE-B-GOFDPZ.js → chunk-KS7C4IRE-D4HEcEwi.js} +716 -710
- package/core/built/admin/assets/activitypub/{content-formatters-bwrp4iEO.js → content-formatters-BpQARh28.js} +229 -181
- package/core/built/admin/assets/activitypub/{copy-C7m7ovHw.js → copy-D9UWIAan.js} +2 -2
- package/core/built/admin/assets/activitypub/{dist-B5ira32t.js → dist-Ba_QxHHl.js} +5 -5
- package/core/built/admin/assets/activitypub/{domain-iqE4YXwD.js → domain-CmwSZLHK.js} +33 -33
- package/core/built/admin/assets/activitypub/{edit-profile-B8MFYh28.js → edit-profile-CXdwNmVG.js} +46 -45
- package/core/built/admin/assets/activitypub/{explore-BbdKodp3.js → explore-DfbjzGM1.js} +20 -20
- package/core/built/admin/assets/activitypub/{feed-Cl3A5owG.js → feed-DSEBJOqf.js} +15 -15
- package/core/built/admin/assets/activitypub/{field-DL6WyR-c.js → field-CY7XVcx6.js} +13 -13
- package/core/built/admin/assets/activitypub/{hash-6G3DzwL9.js → hash-BC9Q0N4c.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-YmhuBice.js → inbox-DQ4ZnC1m.js} +2 -2
- package/core/built/admin/assets/activitypub/inbox-DtQ0kytT.js +1221 -0
- package/core/built/admin/assets/activitypub/{label-DGbLjDvE.js → label-Dw96GQ7u.js} +6 -6
- package/core/built/admin/assets/activitypub/layout-IdVq3lRy.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-qnBtk1zS.js → moderation-Dg_-I-8C.js} +32 -31
- package/core/built/admin/assets/activitypub/{note-C7ZRoSJv.js → note-Cy1-uKMY.js} +21 -21
- package/core/built/admin/assets/activitypub/{notifications-DiEA4b1o.js → notifications-B_fvoLIM.js} +17 -17
- package/core/built/admin/assets/activitypub/{onboarding-_JVgEwgV.js → onboarding-BFDkzeR2.js} +2 -2
- package/core/built/admin/assets/activitypub/{onboarding-DG4zu3qM.js → onboarding-D6rcBdPe.js} +5 -5
- package/core/built/admin/assets/activitypub/{preferences-B3FtocnP.js → preferences-ByJseZp6.js} +1607 -1606
- package/core/built/admin/assets/activitypub/{profile-D8ZJfYXB.js → profile-BFtbnIjz.js} +264 -263
- package/core/built/admin/assets/activitypub/{reply-C1vggenw.js → reply-CpdMeAO5.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-BOii-V9n.js → routes-Dh3xXfce.js} +1311 -1295
- package/core/built/admin/assets/activitypub/{separator-1gNokN5S.js → separator-BI1FLsYM.js} +4 -4
- package/core/built/admin/assets/activitypub/{settings--9Sb0nbl.js → settings-DTgqJPxM.js} +14 -14
- package/core/built/admin/assets/activitypub/{site-fkMmqv0V.js → site-CdL9ZLqm.js} +10 -10
- package/core/built/admin/assets/activitypub/{step-1-QhivSMw2.js → step-1-DIDg4aHb.js} +15 -15
- package/core/built/admin/assets/activitypub/{step-2-pMk9YwqD.js → step-2-BfGe8BYt.js} +7 -7
- package/core/built/admin/assets/activitypub/{step-3-DvmVC1fo.js → step-3-mE9spAqw.js} +12 -12
- package/core/built/admin/assets/activitypub/{tabs-Dc0FEEYD.js → tabs-ogqwoBlb.js} +241 -237
- package/core/built/admin/assets/activitypub/{topic-filter-CuDyM5bD.js → topic-filter-DLHBZT93.js} +4 -4
- package/core/built/admin/assets/activitypub/use-activity-pub-queries-VzbN99u_.js +1957 -0
- package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-Drx1kdAK.js → use-navigate-with-base-path-CAwXTmqH.js} +165 -175
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-BHkCHcW5.js → use-reply-chain-data-CxonteFD.js} +8 -8
- package/core/built/admin/assets/activitypub/users-BLeiOTHc.js +1464 -0
- package/core/built/admin/assets/activitypub/{x-BHCEe-c2.js → x-C8tAnXly.js} +2 -2
- package/core/built/admin/assets/{audience-adiFehTD.js → audience-BKRdsFmy.js} +1 -1
- package/core/built/admin/assets/{automated-emails-UWM02CMr.js → automated-emails-Cn87L5Md.js} +1 -1
- package/core/built/admin/assets/automations-B7-y3dXt.js +1 -0
- package/core/built/admin/assets/automations-BP_aHamJ.js +1 -0
- package/core/built/admin/assets/{avatar-flipboard-Lz3F9fNT.js → avatar-flipboard-C6Kj3Awu.js} +1 -1
- package/core/built/admin/assets/bluesky-sharing-Bh36XPh4.js +1 -0
- package/core/built/admin/assets/box-BWbhXh21.js +1 -0
- package/core/built/admin/assets/breadcrumb-D34YWnaw.js +1 -0
- package/core/built/admin/assets/card-DGoDJzrn.js +1 -0
- package/core/built/admin/assets/{chart-ChpW-NzQ.js → chart-BuRT2Y6p.js} +14 -14
- package/core/built/admin/assets/chart-helpers-DbklaJw9.js +1 -0
- package/core/built/admin/assets/checkbox-DA4-1UiI.js +1 -0
- package/core/built/admin/assets/chunk.524.45e0875f41b71e46e849.js +25 -0
- package/core/built/admin/assets/chunk.582.51defd4b009f0de8d4d5.js +27 -0
- package/core/built/admin/assets/{chunk.831.0703a12e49f51cec3f3a.js → chunk.589.096f4d0bb775e5229aa9.js} +78 -85
- package/core/built/admin/assets/{chunk.432.c2caa0b20f03e71d1a35.js → chunk.962.b73442b07be930bb5d85.js} +2216 -3188
- package/core/built/admin/assets/{chunk.432.c2caa0b20f03e71d1a35.js.LICENSE.txt → chunk.962.b73442b07be930bb5d85.js.LICENSE.txt} +0 -63
- package/core/built/admin/assets/code-editor-view-BKBrkffZ.js +1 -0
- package/core/built/admin/assets/comments-CzVNIT8b.js +1 -0
- package/core/built/admin/assets/container-BYz2Ed39.js +1 -0
- package/core/built/admin/assets/data-list-16rkJYYi.js +1 -0
- package/core/built/admin/assets/date-range-select-B5vSsrpx.js +1 -0
- package/core/built/admin/assets/disabled-sources-indicator-NkOIoMue.js +1 -0
- package/core/built/admin/assets/{dist-CsekE4FL.js → dist-CQJY6SVY.js} +1 -1
- package/core/built/admin/assets/dist-DGzPJ_bO.js +4 -0
- package/core/built/admin/assets/{dist-BbGrW05X.js → dist-DyViJwIy.js} +1 -1
- package/core/built/admin/assets/dist-Ndy-PuGN.js +1 -0
- package/core/built/admin/assets/dist-P_ohUlTi.js +1 -0
- package/core/built/admin/assets/domain-B7_n5nfx.js +1 -0
- package/core/built/admin/assets/dropzone-DcuJEEUf.js +5 -0
- package/core/built/admin/assets/edit-profile-rvDGrS8J.js +1 -0
- package/core/built/admin/assets/editor-BCiIyFjm.js +8 -0
- package/core/built/admin/assets/empty-indicator-oX1u1Pkn.js +1 -0
- package/core/built/admin/assets/esm-CFX93H5M.js +2 -0
- package/core/built/admin/assets/explore-BZAZOg5X.js +1 -0
- package/core/built/admin/assets/feed-CFV8xDw9.js +1 -0
- package/core/built/admin/assets/field-BW-M2Zwp.js +1 -0
- package/core/built/admin/assets/{filter-sources-v39ihhhV.js → filter-sources-Fx8wiYYw.js} +1 -1
- package/core/built/admin/assets/filters-CGX_pf2N.js +1 -0
- package/core/built/admin/assets/filters-DtcXMZDU.js +1 -0
- package/core/built/admin/assets/framework-provider-CmtnPXIT.js +4 -0
- package/core/built/admin/assets/{ghost-b57f077997b739a8ae9a70772dc016cb.js → ghost-b1dce91ad3232cc259ee5fbbf9526243.js} +294 -290
- package/core/built/admin/assets/ghost-dark-c8d0ab7f515b2010c173e23e9b32ea8a.css +1 -0
- package/core/built/admin/assets/gift-link-modal-Bk8qpnMw.js +1 -0
- package/core/built/admin/assets/{gift-links-CY3GwiVi.js → gift-links-nBxJjZiQ.js} +1 -1
- package/core/built/admin/assets/growth-Ck2s3JjR.js +1 -0
- package/core/built/admin/assets/hooks-BApwwBE0.js +2 -0
- package/core/built/admin/assets/{inbox-TEISAmth.js → inbox-BRp1_aaC.js} +35 -35
- package/core/built/admin/assets/index-BBf502Wg.js +69 -0
- package/core/built/admin/assets/index-BCCZKxP2.css +1 -0
- package/core/built/admin/assets/inline-EMAm3okk.js +1 -0
- package/core/built/admin/assets/input-group-DjuYnNCd.js +1 -0
- package/core/built/admin/assets/{react-dom-C6aoWSfl.js → jsx-runtime-DX3WBWVO.js} +1 -1
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +5 -2
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +2 -2
- package/core/built/admin/assets/{koenig-lexical-Di7b48ED.js → koenig-lexical-CM7UFNdL.js} +2 -2
- package/core/built/admin/assets/kpi-card-BrGH6FsT.js +1 -0
- package/core/built/admin/assets/kpi-card-CIGyijjx.js +1 -0
- package/core/built/admin/assets/kpi-helpers-_HRRsklE.js +1 -0
- package/core/built/admin/assets/kpi-tabs-BSvh9eoq.js +1 -0
- package/core/built/admin/assets/label-BOR9XdKl.js +1 -0
- package/core/built/admin/assets/label-picker-BdNAWuUX.js +1 -0
- package/core/built/admin/assets/{labels-GbxlXWtf.js → labels-Cyayfd-e.js} +1 -1
- package/core/built/admin/assets/layout-Bw4GnxXp.js +1 -0
- package/core/built/admin/assets/limit-service-9fwD33lU.js +20 -0
- package/core/built/admin/assets/link-helpers-tOlxmuH5.js +1 -0
- package/core/built/admin/assets/list-page-B6ANtzkd.js +1 -0
- package/core/built/admin/assets/mailchimp-Ds_NQ04w.svg +9 -0
- package/core/built/admin/assets/member-custom-fields-DvFyamLu.js +1 -0
- package/core/built/admin/assets/member-detail-Cg2vTqvJ.js +1 -0
- package/core/built/admin/assets/members-DsPxYAVm.js +10 -0
- package/core/built/admin/assets/modals-Bf_fyvHZ.js +31 -0
- package/core/built/admin/assets/moderation-Dlp7M5sa.js +1 -0
- package/core/built/admin/assets/multi-select-combobox-CH1o8XXQ.js +1 -0
- package/core/built/admin/assets/newsletter-CQ5CAYUI.js +1 -0
- package/core/built/admin/assets/{newsletters-DZm3yLj8.js → newsletters-D1jbtV-h.js} +1 -1
- package/core/built/admin/assets/note-BrAZ7Z0o.js +1 -0
- package/core/built/admin/assets/notifications-BnbHLOPC.js +1 -0
- package/core/built/admin/assets/nql-lang-B7faUf8D.js +19 -0
- package/core/built/admin/assets/{offers-D11QtPXX.js → offers-CrY6nSRR.js} +1 -1
- package/core/built/admin/assets/onboarding-route-eU146kuA.js +1 -0
- package/core/built/admin/assets/overview-BpbaK-xu.js +1 -0
- package/core/built/admin/assets/page-header-C0jVxtS8.js +1 -0
- package/core/built/admin/assets/{pages-BnObVRrf.js → pages-BbNWFcGj.js} +1 -1
- package/core/built/admin/assets/pintura-CXOmq-5H.svg +15 -0
- package/core/built/admin/assets/post-analytics-Iw7dk1mT.js +1 -0
- package/core/built/admin/assets/post-analytics-header-D9pJ4iNm.js +1 -0
- package/core/built/admin/assets/{post-analytics-provider-DSfBCtnj.js → post-analytics-provider-BDqcfBor.js} +1 -1
- package/core/built/admin/assets/post-share-modal-DBfKl2GZ.js +1 -0
- package/core/built/admin/assets/{posts-CwH8VJvB.js → posts-EWpZIjkr.js} +1 -1
- package/core/built/admin/assets/preferences-D4EANGoc.js +5 -0
- package/core/built/admin/assets/profile-dJ44Bn_W.js +1 -0
- package/core/built/admin/assets/{referrers-nxR52jPI.js → referrers-BCfFYSHc.js} +1 -1
- package/core/built/admin/assets/router-provider-C1R4AZ2o.js +1 -0
- package/core/built/admin/assets/select--2uVN9_-.js +1 -0
- package/core/built/admin/assets/settings-Bz3RZRmE.js +1 -0
- package/core/built/admin/assets/settings-SD9bxIpt.js +4 -0
- package/core/built/admin/assets/share-modal-CkdTTMJd.js +1 -0
- package/core/built/admin/assets/social-urls-BvFZwYcE.js +8 -0
- package/core/built/admin/assets/sort-button-DZp9Y2r1.js +1 -0
- package/core/built/admin/assets/source-icon-CHr9qJ2Y.js +1 -0
- package/core/built/admin/assets/stack-xiN0eQ6Z.js +1 -0
- package/core/built/admin/assets/stats-DGoXZxWE.js +1 -0
- package/core/built/admin/assets/stats-view-D2-1L0ms.js +1 -0
- package/core/built/admin/assets/step-1-RdahbwyG.js +1 -0
- package/core/built/admin/assets/step-2-D83PeRwd.js +1 -0
- package/core/built/admin/assets/step-3-p_Op4wv4.js +2 -0
- package/core/built/admin/assets/switch-qp518gFQ.js +1 -0
- package/core/built/admin/assets/table-B8LgTQJZ.js +1 -0
- package/core/built/admin/assets/tabs-D3ZOYkM7.js +1 -0
- package/core/built/admin/assets/tags-B4l891z2.js +1 -0
- package/core/built/admin/assets/text-DwtjwGkh.js +1 -0
- package/core/built/admin/assets/textarea-CTlb3Uu6.js +1 -0
- package/core/built/admin/assets/{tiers-EghDzCKc.js → tiers-BgYLWRJv.js} +1 -1
- package/core/built/admin/assets/toggle-group-DNXDkAwf.js +1 -0
- package/core/built/admin/assets/topic-filter-DZGjEcz3.js +1 -0
- package/core/built/admin/assets/{use-analytics-data-0l_OHtnR.js → use-analytics-data-CaT8RirX.js} +1 -1
- package/core/built/admin/assets/use-filter-params-DeQ8sX4N.js +1 -0
- package/core/built/admin/assets/use-growth-stats-9Db2CuJe.js +1 -0
- package/core/built/admin/assets/{use-post-referrers-DwI31Ril.js → use-post-referrers-BV54YcQS.js} +1 -1
- package/core/built/admin/assets/use-reply-chain-data-DuYZGDan.js +1 -0
- package/core/built/admin/assets/use-simple-pagination-8fTua9Nt.js +1 -0
- package/core/built/admin/assets/{use-tinybird-query-DvZBBMG7.js → use-tinybird-query-Dz156HvZ.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-CQtebkvU.js → use-tinybird-token-C9RUdL_n.js} +1 -1
- package/core/built/admin/assets/{users-Pc8IRjk9.js → users-CcSCQFcQ.js} +1 -1
- package/core/built/admin/assets/virtual-list-BCvP0YF-.js +1 -0
- package/core/built/admin/assets/web-DTZnnUGr.js +1 -0
- package/core/built/admin/index.html +18 -22
- package/core/frontend/apps/private-blogging/index.js +1 -1
- package/core/frontend/apps/private-blogging/lib/middleware.js +1 -1
- package/core/frontend/helpers/split.js +2 -1
- package/core/frontend/meta/asset-url.js +1 -1
- package/core/frontend/meta/author-image.js +1 -1
- package/core/frontend/meta/blog-logo.js +1 -1
- package/core/frontend/meta/canonical-url.js +1 -1
- package/core/frontend/meta/cover-image.js +1 -1
- package/core/frontend/meta/get-meta.js +1 -1
- package/core/frontend/meta/og-image.js +1 -1
- package/core/frontend/meta/paginated-url.js +1 -1
- package/core/frontend/meta/twitter-image.js +1 -1
- package/core/frontend/meta/url.js +1 -1
- package/core/frontend/services/assets-minification/admin-auth-assets.js +1 -1
- package/core/frontend/services/llms/handler.js +1 -1
- package/core/frontend/services/proxy.js +31 -2
- package/core/frontend/services/routing/collection-router.js +1 -1
- package/core/frontend/services/routing/controllers/email-post.js +1 -1
- package/core/frontend/services/routing/controllers/entry/markdown.js +1 -1
- package/core/frontend/services/routing/controllers/entry/markdown.ts +1 -1
- package/core/frontend/services/routing/controllers/entry.js +1 -1
- package/core/frontend/services/routing/controllers/entry.ts +1 -1
- package/core/frontend/services/routing/controllers/previews.js +6 -7
- package/core/frontend/services/routing/controllers/unsubscribe.js +7 -3
- package/core/frontend/services/routing/email-router.js +1 -1
- package/core/frontend/services/routing/events.js +12 -0
- package/core/frontend/services/routing/middleware/page-param.js +1 -1
- package/core/frontend/services/routing/parent-router.js +1 -1
- package/core/frontend/services/routing/preview-router.js +1 -1
- package/core/frontend/services/routing/router-manager.js +4 -8
- package/core/frontend/services/routing/rss-router.js +1 -1
- package/core/frontend/services/routing/static-pages-router.js +1 -1
- package/core/frontend/services/routing/static-routes-router.js +1 -1
- package/core/frontend/services/routing/taxonomy-router.js +1 -1
- package/core/frontend/services/rss/generate-feed.js +1 -1
- package/core/frontend/services/sitemap/base-site-map-generator.js +23 -4
- package/core/frontend/services/sitemap/site-map-index-generator.js +1 -1
- package/core/frontend/services/sitemap/site-map-manager.js +14 -8
- package/core/frontend/services/sitemap/utils.js +1 -1
- package/core/frontend/services/theme-engine/middleware/update-global-template-options.js +1 -1
- package/core/frontend/services/theme-engine/middleware/update-local-template-options.js +1 -1
- package/core/frontend/utils/images.js +1 -1
- package/core/frontend/web/middleware/admin-toolbar.js +1 -1
- package/core/frontend/web/middleware/handle-image-sizes.js +2 -2
- package/core/frontend/web/middleware/redirect-ghost-to-admin.js +1 -1
- package/core/frontend/web/routers/serve-favicon.js +1 -1
- package/core/frontend/web/routers/serve-public-file.js +1 -1
- package/core/frontend/web/site.js +5 -5
- package/core/server/adapters/cache/AdapterCacheMemoryTTL.js +2 -2
- package/core/server/adapters/cache/MemoryCache.js +2 -2
- package/core/server/adapters/cache/Redis.js +1 -1
- package/core/server/adapters/cache/index.js +1 -1
- package/core/server/adapters/lib/redis/AdapterCacheRedis.js +2 -2
- package/core/server/adapters/route-settings/FileStore.js +2 -8
- package/core/server/adapters/route-settings/FileStore.ts +1 -6
- package/core/server/adapters/route-settings/S3RouteSettingsStore.js +206 -0
- package/core/server/adapters/route-settings/S3RouteSettingsStore.ts +199 -0
- package/core/server/adapters/route-settings/utils.js +13 -0
- package/core/server/adapters/route-settings/utils.ts +7 -0
- package/core/server/adapters/scheduling/deps.d.ts +0 -2
- package/core/server/adapters/scheduling/error-capture.js +76 -0
- package/core/server/adapters/scheduling/error-capture.ts +91 -0
- package/core/server/adapters/scheduling/utils.js +18 -34
- package/core/server/adapters/scheduling/utils.ts +36 -0
- package/core/server/adapters/storage/LocalFilesStorage.js +1 -1
- package/core/server/adapters/storage/LocalImagesStorage.js +1 -1
- package/core/server/adapters/storage/LocalMediaStorage.js +1 -1
- package/core/server/adapters/storage/LocalStorageBase.js +3 -3
- package/core/server/adapters/storage/S3Storage.js +2 -2
- package/core/server/adapters/storage/S3Storage.ts +1 -1
- package/core/server/adapters/storage/utils.js +1 -1
- package/core/server/api/endpoints/authentication.js +1 -1
- package/core/server/api/endpoints/featurebase.js +1 -1
- package/core/server/api/endpoints/files.js +2 -2
- package/core/server/api/endpoints/images.js +2 -2
- package/core/server/api/endpoints/libraries.d.ts +0 -1
- package/core/server/api/endpoints/media.js +8 -5
- package/core/server/api/endpoints/member-custom-fields.js +4 -1
- package/core/server/api/endpoints/member-custom-fields.ts +4 -1
- package/core/server/api/endpoints/posts.js +17 -1
- package/core/server/api/endpoints/search-index-public.js +4 -3
- package/core/server/api/endpoints/search-index.js +5 -4
- package/core/server/api/endpoints/settings-public.js +1 -1
- package/core/server/api/endpoints/settings.js +3 -1
- package/core/server/api/endpoints/utils/serializers/input/utils/url.js +9 -1
- package/core/server/api/endpoints/utils/serializers/output/default.js +7 -0
- package/core/server/api/endpoints/utils/serializers/output/files.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/media.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/member-custom-fields.js +7 -4
- package/core/server/api/endpoints/utils/serializers/output/member-custom-fields.ts +8 -4
- package/core/server/api/endpoints/utils/serializers/output/members.js +16 -26
- package/core/server/api/endpoints/utils/serializers/output/pages.js +7 -0
- package/core/server/api/endpoints/utils/serializers/output/posts.js +13 -0
- package/core/server/api/endpoints/utils/serializers/output/utils/url.js +1 -1
- package/core/server/api/endpoints/utils/validators/input/members.js +2 -52
- package/core/server/data/db/backup.js +1 -1
- package/core/server/data/importer/handlers/image.js +3 -3
- package/core/server/data/importer/import-manager.js +6 -4
- package/core/server/data/importer/importers/content-file-importer.js +2 -2
- package/core/server/data/migrations/utils/schema.js +4 -2
- package/core/server/data/migrations/versions/6.54/2026-07-19-16-59-00-add-content-import-permission.js +10 -0
- package/core/server/data/migrations/versions/6.54/2026-07-20-13-59-21-drop-email-tracked-sent-count-from-automation-action-revisions.js +7 -0
- package/core/server/data/migrations/versions/6.54/2026-07-21-01-58-20-add-automation-run-steps-poller-index.js +3 -0
- package/core/server/data/migrations/versions/6.54/2026-07-21-21-08-15-add-automated-email-recipients-mailgun-message-id-index.js +3 -0
- package/core/server/data/migrations/versions/6.54/2026-07-22-10-41-08-add-click-tracking-to-automated-email-recipients.js +13 -0
- package/core/server/data/migrations/versions/6.54/2026-07-22-15-10-30-add-email-clicked-count-to-automation-action-revisions.js +7 -0
- package/core/server/data/migrations/versions/6.54/2026-07-23-10-24-18-add-automation-action-revision-to-redirects.js +9 -0
- package/core/server/data/schema/commands.js +37 -3
- package/core/server/data/schema/fixtures/fixtures.json +7 -0
- package/core/server/data/schema/lib/default-index-name.js +20 -0
- package/core/server/data/schema/schema.js +40 -17
- package/core/server/data/seeders/data-generator.js +4 -2
- package/core/server/lib/image/image-size.js +5 -7
- package/core/server/lib/image/image-utils.js +2 -2
- package/core/server/lib/image/index.js +4 -4
- package/core/server/lib/lexical.js +3 -3
- package/core/server/lib/mobiledoc.js +2 -2
- package/core/server/lib/validate-password.js +1 -1
- package/core/server/models/action.js +9 -0
- package/core/server/models/base/plugins/generate-slug.js +1 -1
- package/core/server/models/collection.js +1 -1
- package/core/server/models/custom-theme-setting.js +1 -1
- package/core/server/models/member.js +73 -0
- package/core/server/models/newsletter.js +1 -1
- package/core/server/models/post.js +19 -5
- package/core/server/models/posts-meta.js +1 -1
- package/core/server/models/redirect.js +1 -1
- package/core/server/models/settings.js +1 -1
- package/core/server/models/snippet.js +1 -1
- package/core/server/models/tag.js +1 -1
- package/core/server/models/user.js +1 -1
- package/core/server/models/welcome-email-automated-email.js +1 -1
- package/core/server/services/activitypub/activity-pub-service-wrapper.js +1 -1
- package/core/server/services/adapter-manager/adapter-manager.js +5 -1
- package/core/server/services/adapter-manager/adapter-manager.ts +7 -2
- package/core/server/services/adapter-manager/index.js +18 -15
- package/core/server/services/adapter-manager/index.ts +23 -17
- package/core/server/services/adapter-manager/utils.js +124 -14
- package/core/server/services/adapter-manager/utils.ts +130 -14
- package/core/server/services/api-version-compatibility/mw-version-rewrites.js +1 -1
- package/core/server/services/audience-feedback/index.js +1 -1
- package/core/server/services/auth/passwordreset.js +1 -1
- package/core/server/services/auth/session/express-session.js +1 -1
- package/core/server/services/auth/session/index.js +2 -2
- package/core/server/services/auth/session/session-service.js +1 -1
- package/core/server/services/automations/automations-api.js +2 -19
- package/core/server/services/automations/automations-api.ts +2 -31
- package/core/server/services/automations/automations-repository.ts +15 -0
- package/core/server/services/automations/database-automations-repository.js +30 -2
- package/core/server/services/automations/database-automations-repository.ts +33 -2
- package/core/server/services/automations/poll.js +16 -12
- package/core/server/services/automations/poll.ts +17 -15
- package/core/server/services/automations/service.js +1 -1
- package/core/server/services/automations/service.ts +3 -13
- package/core/server/services/automations/welcome-email-automation-poll.js +2 -1
- package/core/server/services/comments/index.js +1 -1
- package/core/server/services/custom-redirects/index.js +2 -2
- package/core/server/services/email-analytics/email-analytics-service-wrapper.js +14 -13
- package/core/server/services/email-analytics/email-analytics-service.js +1 -1
- package/core/server/services/email-analytics/events/start-email-analytics-job-event.js +11 -15
- package/core/server/services/email-analytics/events/start-email-analytics-job-event.ts +15 -0
- package/core/server/services/email-analytics/index.js +2 -3
- package/core/server/services/email-analytics/index.ts +1 -2
- package/core/server/services/email-analytics/jobs/email-analytics-job-scheduler.js +1 -6
- package/core/server/services/email-analytics/jobs/email-analytics-job-scheduler.ts +0 -9
- package/core/server/services/email-analytics/jobs/fetch-latest/index.js +1 -1
- package/core/server/services/email-analytics/jobs/index.js +0 -2
- package/core/server/services/email-service/email-service-wrapper.js +1 -1
- package/core/server/services/email-service/email-service.js +20 -4
- package/core/server/services/explore-ping/explore-ping-service.js +24 -39
- package/core/server/services/explore-ping/explore-ping-service.ts +198 -0
- package/core/server/services/explore-ping/index.js +11 -11
- package/core/server/services/explore-ping/index.ts +42 -0
- package/core/server/services/gifts/gift-reminder-scheduler.js +1 -1
- package/core/server/services/gifts/gift-reminder-scheduler.ts +1 -1
- package/core/server/services/gifts/gift-service-wrapper.js +1 -1
- package/core/server/services/identity-tokens/identity-token-service-wrapper.js +1 -1
- package/core/server/services/indexnow-ping/constants.js +30 -0
- package/core/server/services/indexnow-ping/constants.ts +32 -0
- package/core/server/services/indexnow-ping/index.js +29 -253
- package/core/server/services/indexnow-ping/index.ts +37 -0
- package/core/server/services/indexnow-ping/indexnow-ping-service.js +229 -0
- package/core/server/services/indexnow-ping/indexnow-ping-service.ts +319 -0
- package/core/server/services/invites/index.js +1 -1
- package/core/server/services/lib/mailgun-client.js +3 -3
- package/core/server/services/link-redirection/index.js +2 -2
- package/core/server/services/link-redirection/link-redirect-repository.js +1 -1
- package/core/server/services/link-redirection/link-redirect.js +12 -13
- package/core/server/services/link-redirection/link-redirect.ts +22 -0
- package/core/server/services/link-redirection/link-redirects-service.js +1 -1
- package/core/server/services/link-tracking/full-post-link.js +2 -2
- package/core/server/services/link-tracking/index.js +1 -1
- package/core/server/services/mail/ghost-mailer.js +1 -1
- package/core/server/services/mail/index.js +1 -1
- package/core/server/services/media-inliner/external-media-inliner.js +2 -2
- package/core/server/services/media-inliner/service.js +4 -3
- package/core/server/services/member-attribution/index.js +1 -1
- package/core/server/services/member-welcome-emails/service.js +1 -1
- package/core/server/services/members/api.js +8 -2
- package/core/server/services/members/csv/index.js +10 -0
- package/core/server/services/members/csv/index.ts +3 -0
- package/core/server/services/members/csv/parse.js +94 -0
- package/core/server/services/members/csv/parse.ts +111 -0
- package/core/server/services/members/csv/types.js +2 -0
- package/core/server/services/members/csv/types.ts +39 -0
- package/core/server/services/members/csv/unparse.js +69 -0
- package/core/server/services/members/csv/unparse.ts +79 -0
- package/core/server/services/members/exporter/query.js +35 -8
- package/core/server/services/members/importer/config.js +35 -0
- package/core/server/services/members/importer/config.ts +39 -0
- package/core/server/services/members/importer/members-csv-importer.js +31 -13
- package/core/server/services/members/members-api/members-api.js +4 -2
- package/core/server/services/members/members-api/repositories/event-repository.js +13 -2
- package/core/server/services/members/members-api/services/member-bread-service.js +42 -19
- package/core/server/services/members/middleware.js +1 -1
- package/core/server/services/members/service.js +15 -2
- package/core/server/services/members/stripe-connect.js +1 -1
- package/core/server/services/members-custom-fields/actions.js +3 -0
- package/core/server/services/members-custom-fields/actions.ts +6 -2
- package/core/server/services/members-custom-fields/config.js +34 -0
- package/core/server/services/members-custom-fields/config.ts +38 -0
- package/core/server/services/members-custom-fields/definitions-service.js +156 -21
- package/core/server/services/members-custom-fields/definitions-service.ts +170 -25
- package/core/server/services/members-custom-fields/index.js +16 -3
- package/core/server/services/members-custom-fields/index.ts +17 -3
- package/core/server/services/members-custom-fields/values-service.js +56 -10
- package/core/server/services/members-custom-fields/values-service.ts +62 -10
- package/core/server/services/mentions/mention-sending-service.js +22 -2
- package/core/server/services/mentions/service.js +21 -5
- package/core/server/services/newsletters/index.js +1 -1
- package/core/server/services/oembed/oembed-service.js +122 -33
- package/core/server/services/oembed/service.js +4 -2
- package/core/server/services/offers/service.js +1 -1
- package/core/server/services/post-scheduling/index.js +4 -3
- package/core/server/services/post-scheduling/index.ts +4 -3
- package/core/server/services/post-scheduling/post-scheduling.js +1 -1
- package/core/server/services/post-scheduling/post-scheduling.ts +1 -1
- package/core/server/services/posts/post-email-handler.js +13 -6
- package/core/server/services/posts/post-scheduling.js +1 -1
- package/core/server/services/posts/posts-service-instance.js +1 -1
- package/core/server/services/posts/posts-service.js +2 -2
- package/core/server/services/posts-public/service.js +1 -1
- package/core/server/services/public-config/site.js +1 -1
- package/core/server/services/recommendations/recommendation-service-wrapper.js +1 -1
- package/core/server/services/recommendations/service/libraries.d.ts +0 -2
- package/core/server/services/route-settings/dynamic-routing-service.js +2 -3
- package/core/server/services/route-settings/index.js +1 -1
- package/core/server/services/route-settings/route-settings-parser.js +134 -124
- package/core/server/services/route-settings/route-settings-parser.ts +160 -125
- package/core/server/services/route-settings/validation-errors.js +178 -0
- package/core/server/services/route-settings/validation-errors.ts +196 -0
- package/core/server/services/settings/settings-service.js +2 -2
- package/core/server/services/settings-helpers/index.js +1 -1
- package/core/server/services/slack-notifications/service.js +1 -1
- package/core/server/services/slack-ping/constants.js +16 -0
- package/core/server/services/slack-ping/constants.ts +15 -0
- package/core/server/services/slack-ping/index.js +26 -204
- package/core/server/services/slack-ping/index.ts +35 -0
- package/core/server/services/slack-ping/slack-ping-service.js +214 -0
- package/core/server/services/slack-ping/slack-ping-service.ts +296 -0
- package/core/server/services/staff/index.js +1 -1
- package/core/server/services/stats/posts-stats-service.js +1 -1
- package/core/server/services/stats/service.js +1 -1
- package/core/server/services/stripe/service.js +1 -1
- package/core/server/services/tags-public/service.js +1 -1
- package/core/server/services/themes/validate.js +1 -1
- package/core/server/services/update-check/index.js +1 -1
- package/core/server/services/url/lazy-url-service.js +66 -6
- package/core/server/services/url/lazy-url-service.ts +73 -6
- package/core/server/services/url/url-generator.js +1 -1
- package/core/server/services/url/url-service-facade.js +68 -3
- package/core/server/services/url/url-service-facade.ts +79 -4
- package/core/server/services/url/url-service.js +1 -1
- package/core/server/services/url/urls.js +1 -1
- package/core/server/services/webhooks/index.js +19 -2
- package/core/server/services/webhooks/listen.js +6 -11
- package/core/server/services/webhooks/payload.js +1 -3
- package/core/server/services/webhooks/serialize.js +16 -1
- package/core/server/web/admin/app.js +1 -1
- package/core/server/web/admin/middleware/redirect-admin-urls.js +1 -1
- package/core/server/web/api/endpoints/admin/routes.js +1 -0
- package/core/server/web/api/middleware/cors.js +1 -1
- package/core/server/web/api/middleware/upload.js +4 -0
- package/core/server/web/gift-preview/controller.js +1 -1
- package/core/server/web/shared/middleware/redirect-amp-urls.js +1 -1
- package/core/server/web/shared/middleware/uncapitalise.js +2 -2
- package/core/server/web/shared/middleware/url-redirects.js +1 -1
- package/core/shared/config/defaults.json +8 -1
- package/core/shared/config/overrides.json +4 -0
- package/core/shared/url-utils.js +19 -15
- package/core/shared/url-utils.ts +20 -0
- package/ghost.js +0 -1
- package/package.json +26 -31
- package/pnpm-lock.yaml +681 -2789
- package/pnpm-workspace.yaml +31 -17
- package/components/tryghost-adapter-base-scheduling-0.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-sso-0.0.0.tgz +0 -0
- package/core/built/admin/assets/Combination--5EZGaDs.js +0 -43
- package/core/built/admin/assets/Growth-C0NoKcHQ.js +0 -3
- package/core/built/admin/assets/Newsletters-Ed8kDjdy.js +0 -1
- package/core/built/admin/assets/Overview-CArPgRZt.js +0 -1
- package/core/built/admin/assets/Web-CjjYoVM3.js +0 -2
- package/core/built/admin/assets/account-migration-mfa5mNI9.js +0 -1
- package/core/built/admin/assets/activitypub/inbox-B3rjKMTv.js +0 -1221
- package/core/built/admin/assets/activitypub/layout-jcurg3rU.js +0 -1
- package/core/built/admin/assets/activitypub/use-activity-pub-queries-yqGRcnLn.js +0 -2569
- package/core/built/admin/assets/activitypub/users-CfyHIsTG.js +0 -1316
- package/core/built/admin/assets/automations-DkUnxNdv.js +0 -1
- package/core/built/admin/assets/automations-vjXofmV_.js +0 -1
- package/core/built/admin/assets/bluesky-sharing-T-pO3RcP.js +0 -1
- package/core/built/admin/assets/breadcrumb-CekPYTCn.js +0 -1
- package/core/built/admin/assets/button--i2uMDDn.js +0 -4
- package/core/built/admin/assets/card-CSWz_BXq.js +0 -1
- package/core/built/admin/assets/chart-helpers-C_4Ka_vT.js +0 -1
- package/core/built/admin/assets/chunk.524.8ce2c741473e2f0929dc.js +0 -25
- package/core/built/admin/assets/chunk.582.453459d6228d2955408b.js +0 -27
- package/core/built/admin/assets/code-editor-view-BfkwQto2.js +0 -1
- package/core/built/admin/assets/command-K75Jzs0G.js +0 -1
- package/core/built/admin/assets/comments-BEIi1DH-.js +0 -1
- package/core/built/admin/assets/container-BNXvlcjA.js +0 -1
- package/core/built/admin/assets/data-list-BmK5QMab.js +0 -1
- package/core/built/admin/assets/date-range-select-5sKzwbt7.js +0 -1
- package/core/built/admin/assets/disabled-sources-indicator-L_ssJTNr.js +0 -1
- package/core/built/admin/assets/dist-BBIJuyPg.js +0 -1
- package/core/built/admin/assets/dist-BHE0dp6o.js +0 -1
- package/core/built/admin/assets/dist-DJ6c4_De.js +0 -1
- package/core/built/admin/assets/dist-DW0QXQO3.js +0 -1
- package/core/built/admin/assets/dist-DxFLceO5.js +0 -1
- package/core/built/admin/assets/dist-QcACAOAD.js +0 -1
- package/core/built/admin/assets/dist-duxRDcQu.js +0 -177
- package/core/built/admin/assets/domain-CLaPZ9Rc.js +0 -1
- package/core/built/admin/assets/dropzone-E0bSX4_a.js +0 -5
- package/core/built/admin/assets/edit-profile-DqNPKaA4.js +0 -1
- package/core/built/admin/assets/editor-CkLXQDRp.js +0 -8
- package/core/built/admin/assets/empty-indicator-BTtOlgIo.js +0 -1
- package/core/built/admin/assets/esm-TSfGW7nz.js +0 -2
- package/core/built/admin/assets/explore-BEstLxKr.js +0 -1
- package/core/built/admin/assets/feed-C58p2voG.js +0 -1
- package/core/built/admin/assets/field-BSbDUHPv.js +0 -1
- package/core/built/admin/assets/filters-BgYv8Gt7.js +0 -1
- package/core/built/admin/assets/filters-DvenFkmK.js +0 -1
- package/core/built/admin/assets/framework-provider-jQktxm9P.js +0 -4
- package/core/built/admin/assets/ghost-dark-8590dea6cd695ff90f27d262c4464538.css +0 -1
- package/core/built/admin/assets/gift-link-modal-BPjXkEFt.js +0 -1
- package/core/built/admin/assets/growth-D4JNiVTe.js +0 -1
- package/core/built/admin/assets/hooks-Cjxexi_I.js +0 -2
- package/core/built/admin/assets/index-BacwlcOy.js +0 -67
- package/core/built/admin/assets/index-DxRNHH0o.css +0 -1
- package/core/built/admin/assets/inline-C22rnPro.js +0 -1
- package/core/built/admin/assets/input-group-CKfrILLP.js +0 -1
- package/core/built/admin/assets/jsx-runtime-CtMZ-PAS.js +0 -1
- package/core/built/admin/assets/kpi-card-DZxQKAIb.js +0 -1
- package/core/built/admin/assets/kpi-card-FidQ5zg3.js +0 -1
- package/core/built/admin/assets/kpi-helpers-C5T8Vaf1.js +0 -1
- package/core/built/admin/assets/kpi-tabs-_VRK6hGV.js +0 -1
- package/core/built/admin/assets/label-Cza7hPgn.js +0 -1
- package/core/built/admin/assets/label-picker-DgC_zLaw.js +0 -1
- package/core/built/admin/assets/layout-BxsXnKFX.js +0 -1
- package/core/built/admin/assets/limit-service-CTurluJQ.js +0 -20
- package/core/built/admin/assets/link-helpers-C3Gqy2cz.js +0 -1
- package/core/built/admin/assets/list-page-BtqCnow_.js +0 -1
- package/core/built/admin/assets/member-custom-fields-9uXFBqSt.js +0 -1
- package/core/built/admin/assets/member-detail-Dq6ZDIxo.js +0 -1
- package/core/built/admin/assets/members-CUWD9jal.js +0 -10
- package/core/built/admin/assets/modals-CIdwHVga.js +0 -31
- package/core/built/admin/assets/moderation-DTt04UDA.js +0 -1
- package/core/built/admin/assets/newsletter-cC8vcc9h.js +0 -1
- package/core/built/admin/assets/note-DZU0IY9e.js +0 -1
- package/core/built/admin/assets/notifications-NoHpBHJ4.js +0 -1
- package/core/built/admin/assets/nql-lang-CvCSOQG3.js +0 -19
- package/core/built/admin/assets/onboarding-route-Deg2hNm8.js +0 -1
- package/core/built/admin/assets/overview-BQPt_9w3.js +0 -1
- package/core/built/admin/assets/page-header-D6bEqPo2.js +0 -1
- package/core/built/admin/assets/post-analytics-header-BPw_B2_D.js +0 -1
- package/core/built/admin/assets/post-analytics-kp2_--XO.js +0 -1
- package/core/built/admin/assets/post-share-modal-Cave3Z4W.js +0 -1
- package/core/built/admin/assets/preferences-C7h3j_uy.js +0 -5
- package/core/built/admin/assets/profile-B2x4q2oB.js +0 -1
- package/core/built/admin/assets/router-provider-5fsX1CYn.js +0 -1
- package/core/built/admin/assets/select-PhPjIBBT.js +0 -1
- package/core/built/admin/assets/settings-D5P9Gwgo.js +0 -6
- package/core/built/admin/assets/settings-zUOzQV5I.js +0 -1
- package/core/built/admin/assets/share-modal-EfpfbuWF.js +0 -1
- package/core/built/admin/assets/social-urls-DgAF08Xc.js +0 -18
- package/core/built/admin/assets/sort-button-WOQYJm2H.js +0 -1
- package/core/built/admin/assets/source-icon-hm8Cj-Gx.js +0 -1
- package/core/built/admin/assets/stats-CnQDb62m.js +0 -1
- package/core/built/admin/assets/stats-view-CCfTJgSe.js +0 -1
- package/core/built/admin/assets/step-1-BGlhY4DS.js +0 -1
- package/core/built/admin/assets/step-2-D3sDEyFH.js +0 -1
- package/core/built/admin/assets/step-3-CvZoUdsc.js +0 -2
- package/core/built/admin/assets/switch-CNjkOmwz.js +0 -1
- package/core/built/admin/assets/table-C5nZYPnW.js +0 -1
- package/core/built/admin/assets/tabs-dtOcExye.js +0 -1
- package/core/built/admin/assets/tags-Dhm0FB_g.js +0 -1
- package/core/built/admin/assets/textarea-CeKw3wmV.js +0 -1
- package/core/built/admin/assets/toggle-group-DfhZIMTW.js +0 -1
- package/core/built/admin/assets/topic-filter-Cxe1Il41.js +0 -1
- package/core/built/admin/assets/typeof-Ca-duI-z.js +0 -1
- package/core/built/admin/assets/use-filter-params-Bub6BqNy.js +0 -1
- package/core/built/admin/assets/use-growth-stats-BJORomv0.js +0 -1
- package/core/built/admin/assets/use-reply-chain-data-B89YjVoo.js +0 -1
- package/core/built/admin/assets/use-simple-pagination-BL2SYXKi.js +0 -1
- package/core/built/admin/assets/virtual-list-DNJOSK9I.js +0 -1
- package/core/built/admin/assets/web-BHUE3YQc.js +0 -1
- package/core/server/adapters/scheduling/scheduling-base.js +0 -5
- package/core/server/adapters/sso/SSOBase.js +0 -5
- package/core/server/adapters/storage/index.js +0 -17
|
@@ -3,9 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.LazyUrlService = void 0;
|
|
4
4
|
const debug = require('@tryghost/debug')('services:url:lazy');
|
|
5
5
|
const errors = require('@tryghost/errors');
|
|
6
|
-
const localUtils = require('../../../shared/url-utils');
|
|
6
|
+
const localUtils = require('../../../shared/url-utils').default;
|
|
7
7
|
const { matchPermalink, toLookupParams } = require('./permalink-matcher');
|
|
8
8
|
const { buildFilter, filterMatches, routerTypeOf } = require('./router-filter');
|
|
9
|
+
const urlConfig = require('./config');
|
|
9
10
|
// Per-resource-type base filters, mirroring eager's resource-fetch filters
|
|
10
11
|
// (`modelOptions.filter` in services/url/config.js). Deliberately duplicated
|
|
11
12
|
// rather than imported: lazy is meant to replace eager, at which point eager's
|
|
@@ -32,6 +33,26 @@ function buildBaseFilters() {
|
|
|
32
33
|
}
|
|
33
34
|
return baseFilters;
|
|
34
35
|
}
|
|
36
|
+
// Columns eager drops from its in-memory URL cache (the `exclude` lists in
|
|
37
|
+
// services/url/config.js). Eager evaluates a router's collection filter against
|
|
38
|
+
// that reduced cached record, so an excluded column reads as absent — NQL then
|
|
39
|
+
// treats it as null. Lazy loads full records and would see the real value, so
|
|
40
|
+
// to preserve eager's behaviour it strips these columns before evaluating
|
|
41
|
+
// router filters (and neither requires nor force-loads them). Keyed by
|
|
42
|
+
// resourceType ('posts'/'pages'/'tags'). Read from eager's config directly so
|
|
43
|
+
// the two can't drift; when eager is retired this derives from whatever
|
|
44
|
+
// replaces it. The base filter is unaffected — it runs against the full record.
|
|
45
|
+
function buildExcludedFilterFields() {
|
|
46
|
+
const excluded = new Map();
|
|
47
|
+
for (const entry of urlConfig) {
|
|
48
|
+
const exclude = entry?.modelOptions?.exclude;
|
|
49
|
+
if (Array.isArray(exclude)) {
|
|
50
|
+
excluded.set(entry.type, new Set(exclude));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return excluded;
|
|
54
|
+
}
|
|
55
|
+
const EMPTY_FIELD_SET = new Set();
|
|
35
56
|
// Relation roots are loaded via getRequiredRelations (as withRelated), not as
|
|
36
57
|
// scalar columns; `page`/`type` are the router-type discriminator, always set
|
|
37
58
|
// on the resource. Everything else a router filter references is a scalar
|
|
@@ -76,6 +97,7 @@ class LazyUrlService {
|
|
|
76
97
|
routerConfigs;
|
|
77
98
|
requiredRelations;
|
|
78
99
|
baseFilters;
|
|
100
|
+
excludedFilterFields;
|
|
79
101
|
constructor({ urlUtils = localUtils, findResource }) {
|
|
80
102
|
if (typeof findResource !== 'function') {
|
|
81
103
|
throw new errors.IncorrectUsageError({
|
|
@@ -87,6 +109,7 @@ class LazyUrlService {
|
|
|
87
109
|
this.routerConfigs = [];
|
|
88
110
|
this.requiredRelations = null;
|
|
89
111
|
this.baseFilters = buildBaseFilters();
|
|
112
|
+
this.excludedFilterFields = buildExcludedFilterFields();
|
|
90
113
|
}
|
|
91
114
|
onRouterAddedType(identifier, filter, resourceType, permalink) {
|
|
92
115
|
debug('onRouterAddedType', identifier, resourceType, permalink, filter);
|
|
@@ -140,6 +163,7 @@ class LazyUrlService {
|
|
|
140
163
|
// by getRequiredRelations; eager needs none of this (it looks URLs up by id).
|
|
141
164
|
getRequiredFields(routerType) {
|
|
142
165
|
const fields = new Set();
|
|
166
|
+
const excluded = this._excludedFilterFieldsFor(routerType);
|
|
143
167
|
const base = this.baseFilters.get(routerType);
|
|
144
168
|
if (base) {
|
|
145
169
|
base.fields.forEach(field => fields.add(field));
|
|
@@ -168,7 +192,14 @@ class LazyUrlService {
|
|
|
168
192
|
fields.add(computed);
|
|
169
193
|
}
|
|
170
194
|
}
|
|
171
|
-
|
|
195
|
+
// Skip columns eager drops from its cache: it never force-loads them
|
|
196
|
+
// and evaluates their filters against an absent value, so lazy must
|
|
197
|
+
// not require them either (see buildExcludedFilterFields).
|
|
198
|
+
filterScalarFields(config.filter).forEach((field) => {
|
|
199
|
+
if (!excluded.has(field)) {
|
|
200
|
+
fields.add(field);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
172
203
|
}
|
|
173
204
|
return [...fields];
|
|
174
205
|
}
|
|
@@ -181,6 +212,10 @@ class LazyUrlService {
|
|
|
181
212
|
return this._formatNotFound(options);
|
|
182
213
|
}
|
|
183
214
|
const record = this._recordForFilter(resource);
|
|
215
|
+
// The router (collection) filter is evaluated against eager's reduced
|
|
216
|
+
// column set; the base filter keeps the full record (it reads status /
|
|
217
|
+
// type / visibility, which eager applies at query time, not in cache).
|
|
218
|
+
const filterRecord = this._recordForRouterFilter(record, routerType);
|
|
184
219
|
// Eager only builds URLs for resources that pass the per-type base
|
|
185
220
|
// filter (e.g. visibility:public tags, status:published posts), so a
|
|
186
221
|
// resource failing it has no URL there and must 404 here too. Checked
|
|
@@ -197,7 +232,7 @@ class LazyUrlService {
|
|
|
197
232
|
continue;
|
|
198
233
|
}
|
|
199
234
|
this._assertNotThin(config, resource, routerType);
|
|
200
|
-
if (filterMatches(config.compiledFilter,
|
|
235
|
+
if (filterMatches(config.compiledFilter, filterRecord)) {
|
|
201
236
|
const path = this.urlUtils.replacePermalink(config.permalink, resource);
|
|
202
237
|
return this._formatPath(path, options);
|
|
203
238
|
}
|
|
@@ -218,9 +253,10 @@ class LazyUrlService {
|
|
|
218
253
|
if (!this._baseFilterMatches(routerType, record)) {
|
|
219
254
|
return false;
|
|
220
255
|
}
|
|
256
|
+
const filterRecord = this._recordForRouterFilter(record, routerType);
|
|
221
257
|
// Ownership is exclusive: only the first matching router of the type
|
|
222
258
|
// owns the resource, matching eager's reservation.
|
|
223
|
-
const owner = this.routerConfigs.find(c => c.resourceType === routerType && filterMatches(c.compiledFilter,
|
|
259
|
+
const owner = this.routerConfigs.find(c => c.resourceType === routerType && filterMatches(c.compiledFilter, filterRecord));
|
|
224
260
|
return !!owner && owner.identifier === routerIdentifier;
|
|
225
261
|
}
|
|
226
262
|
async resolveUrl(urlPath) {
|
|
@@ -252,7 +288,7 @@ class LazyUrlService {
|
|
|
252
288
|
// The base filter is enforced upstream by findResource's query
|
|
253
289
|
// scoping (visibility:public / status:published), so only the
|
|
254
290
|
// router filter needs re-checking here.
|
|
255
|
-
const record = this._recordForFilter(resource);
|
|
291
|
+
const record = this._recordForRouterFilter(this._recordForFilter(resource), config.resourceType);
|
|
256
292
|
if (!filterMatches(config.compiledFilter, record)) {
|
|
257
293
|
continue;
|
|
258
294
|
}
|
|
@@ -321,6 +357,27 @@ class LazyUrlService {
|
|
|
321
357
|
const dbType = ROUTER_TYPE_TO_DB_TYPE[record.type];
|
|
322
358
|
return dbType ? { ...record, type: dbType } : record;
|
|
323
359
|
}
|
|
360
|
+
_excludedFilterFieldsFor(routerType) {
|
|
361
|
+
return this.excludedFilterFields.get(routerType) ?? EMPTY_FIELD_SET;
|
|
362
|
+
}
|
|
363
|
+
// Strips the columns eager drops from its cache so a router/collection
|
|
364
|
+
// filter is evaluated against the same reduced shape eager uses — an
|
|
365
|
+
// excluded column reads as absent (→ null in NQL) rather than its real
|
|
366
|
+
// value. Only affects filters that reference an excluded column; every
|
|
367
|
+
// other filter sees an identical record.
|
|
368
|
+
_recordForRouterFilter(record, routerType) {
|
|
369
|
+
const excluded = this._excludedFilterFieldsFor(routerType);
|
|
370
|
+
if (excluded.size === 0) {
|
|
371
|
+
return record;
|
|
372
|
+
}
|
|
373
|
+
const stripped = {};
|
|
374
|
+
for (const key of Object.keys(record)) {
|
|
375
|
+
if (!excluded.has(key)) {
|
|
376
|
+
stripped[key] = record[key];
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return stripped;
|
|
380
|
+
}
|
|
324
381
|
_formatPath(path, options) {
|
|
325
382
|
if (options.absolute) {
|
|
326
383
|
return this.urlUtils.createUrl(path, options.absolute);
|
|
@@ -363,8 +420,11 @@ class LazyUrlService {
|
|
|
363
420
|
if (/\bprimary_author\b/.test(config.filter) && r.primary_author === undefined) {
|
|
364
421
|
missing.push('primary_author');
|
|
365
422
|
}
|
|
423
|
+
// Columns eager drops from its cache are never required — its filters
|
|
424
|
+
// match them as absent, so a resource lacking one is not thin here.
|
|
425
|
+
const excluded = this._excludedFilterFieldsFor(routerType);
|
|
366
426
|
for (const field of filterScalarFields(config.filter)) {
|
|
367
|
-
if (r[field] === undefined) {
|
|
427
|
+
if (!excluded.has(field) && r[field] === undefined) {
|
|
368
428
|
missing.push(field);
|
|
369
429
|
}
|
|
370
430
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
const debug = require('@tryghost/debug')('services:url:lazy');
|
|
2
2
|
const errors = require('@tryghost/errors');
|
|
3
|
-
const localUtils = require('../../../shared/url-utils');
|
|
3
|
+
const localUtils = require('../../../shared/url-utils').default;
|
|
4
4
|
const {matchPermalink, toLookupParams} = require('./permalink-matcher');
|
|
5
5
|
const {buildFilter, filterMatches, routerTypeOf} = require('./router-filter');
|
|
6
|
+
const urlConfig = require('./config');
|
|
6
7
|
|
|
7
8
|
import type {Resource, UrlOptions, LazyUrlServiceBackend} from './url-service-facade';
|
|
8
9
|
import type {CompiledFilter} from './router-filter';
|
|
@@ -57,6 +58,28 @@ function buildBaseFilters(): Map<string, BaseFilter> {
|
|
|
57
58
|
return baseFilters;
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
// Columns eager drops from its in-memory URL cache (the `exclude` lists in
|
|
62
|
+
// services/url/config.js). Eager evaluates a router's collection filter against
|
|
63
|
+
// that reduced cached record, so an excluded column reads as absent — NQL then
|
|
64
|
+
// treats it as null. Lazy loads full records and would see the real value, so
|
|
65
|
+
// to preserve eager's behaviour it strips these columns before evaluating
|
|
66
|
+
// router filters (and neither requires nor force-loads them). Keyed by
|
|
67
|
+
// resourceType ('posts'/'pages'/'tags'). Read from eager's config directly so
|
|
68
|
+
// the two can't drift; when eager is retired this derives from whatever
|
|
69
|
+
// replaces it. The base filter is unaffected — it runs against the full record.
|
|
70
|
+
function buildExcludedFilterFields(): Map<string, Set<string>> {
|
|
71
|
+
const excluded = new Map<string, Set<string>>();
|
|
72
|
+
for (const entry of urlConfig) {
|
|
73
|
+
const exclude = entry?.modelOptions?.exclude;
|
|
74
|
+
if (Array.isArray(exclude)) {
|
|
75
|
+
excluded.set(entry.type, new Set(exclude));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return excluded;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const EMPTY_FIELD_SET: ReadonlySet<string> = new Set();
|
|
82
|
+
|
|
60
83
|
// Relation roots are loaded via getRequiredRelations (as withRelated), not as
|
|
61
84
|
// scalar columns; `page`/`type` are the router-type discriminator, always set
|
|
62
85
|
// on the resource. Everything else a router filter references is a scalar
|
|
@@ -109,6 +132,7 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
109
132
|
private routerConfigs: RouterConfig[];
|
|
110
133
|
private requiredRelations: string[] | null;
|
|
111
134
|
private baseFilters: Map<string, BaseFilter>;
|
|
135
|
+
private excludedFilterFields: Map<string, Set<string>>;
|
|
112
136
|
|
|
113
137
|
constructor({urlUtils = localUtils, findResource}: LazyUrlServiceDeps) {
|
|
114
138
|
if (typeof findResource !== 'function') {
|
|
@@ -121,6 +145,7 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
121
145
|
this.routerConfigs = [];
|
|
122
146
|
this.requiredRelations = null;
|
|
123
147
|
this.baseFilters = buildBaseFilters();
|
|
148
|
+
this.excludedFilterFields = buildExcludedFilterFields();
|
|
124
149
|
}
|
|
125
150
|
|
|
126
151
|
onRouterAddedType(identifier: string, filter: string | null, resourceType: string, permalink: string): void {
|
|
@@ -179,6 +204,7 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
179
204
|
// by getRequiredRelations; eager needs none of this (it looks URLs up by id).
|
|
180
205
|
getRequiredFields(routerType: string): string[] {
|
|
181
206
|
const fields = new Set<string>();
|
|
207
|
+
const excluded = this._excludedFilterFieldsFor(routerType);
|
|
182
208
|
const base = this.baseFilters.get(routerType);
|
|
183
209
|
if (base) {
|
|
184
210
|
base.fields.forEach(field => fields.add(field));
|
|
@@ -207,7 +233,14 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
207
233
|
fields.add(computed);
|
|
208
234
|
}
|
|
209
235
|
}
|
|
210
|
-
|
|
236
|
+
// Skip columns eager drops from its cache: it never force-loads them
|
|
237
|
+
// and evaluates their filters against an absent value, so lazy must
|
|
238
|
+
// not require them either (see buildExcludedFilterFields).
|
|
239
|
+
filterScalarFields(config.filter).forEach((field) => {
|
|
240
|
+
if (!excluded.has(field)) {
|
|
241
|
+
fields.add(field);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
211
244
|
}
|
|
212
245
|
return [...fields];
|
|
213
246
|
}
|
|
@@ -223,6 +256,10 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
223
256
|
}
|
|
224
257
|
|
|
225
258
|
const record = this._recordForFilter(resource);
|
|
259
|
+
// The router (collection) filter is evaluated against eager's reduced
|
|
260
|
+
// column set; the base filter keeps the full record (it reads status /
|
|
261
|
+
// type / visibility, which eager applies at query time, not in cache).
|
|
262
|
+
const filterRecord = this._recordForRouterFilter(record, routerType);
|
|
226
263
|
|
|
227
264
|
// Eager only builds URLs for resources that pass the per-type base
|
|
228
265
|
// filter (e.g. visibility:public tags, status:published posts), so a
|
|
@@ -241,7 +278,7 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
241
278
|
continue;
|
|
242
279
|
}
|
|
243
280
|
this._assertNotThin(config, resource, routerType);
|
|
244
|
-
if (filterMatches(config.compiledFilter,
|
|
281
|
+
if (filterMatches(config.compiledFilter, filterRecord)) {
|
|
245
282
|
const path = this.urlUtils.replacePermalink(config.permalink, resource);
|
|
246
283
|
return this._formatPath(path, options);
|
|
247
284
|
}
|
|
@@ -263,10 +300,11 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
263
300
|
if (!this._baseFilterMatches(routerType, record)) {
|
|
264
301
|
return false;
|
|
265
302
|
}
|
|
303
|
+
const filterRecord = this._recordForRouterFilter(record, routerType);
|
|
266
304
|
// Ownership is exclusive: only the first matching router of the type
|
|
267
305
|
// owns the resource, matching eager's reservation.
|
|
268
306
|
const owner = this.routerConfigs.find(
|
|
269
|
-
c => c.resourceType === routerType && filterMatches(c.compiledFilter,
|
|
307
|
+
c => c.resourceType === routerType && filterMatches(c.compiledFilter, filterRecord)
|
|
270
308
|
);
|
|
271
309
|
return !!owner && owner.identifier === routerIdentifier;
|
|
272
310
|
}
|
|
@@ -299,7 +337,10 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
299
337
|
// The base filter is enforced upstream by findResource's query
|
|
300
338
|
// scoping (visibility:public / status:published), so only the
|
|
301
339
|
// router filter needs re-checking here.
|
|
302
|
-
const record = this.
|
|
340
|
+
const record = this._recordForRouterFilter(
|
|
341
|
+
this._recordForFilter(resource as Resource),
|
|
342
|
+
config.resourceType
|
|
343
|
+
);
|
|
303
344
|
if (!filterMatches(config.compiledFilter, record)) {
|
|
304
345
|
continue;
|
|
305
346
|
}
|
|
@@ -374,6 +415,29 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
374
415
|
return dbType ? {...record, type: dbType} : record;
|
|
375
416
|
}
|
|
376
417
|
|
|
418
|
+
private _excludedFilterFieldsFor(routerType: string): ReadonlySet<string> {
|
|
419
|
+
return this.excludedFilterFields.get(routerType) ?? EMPTY_FIELD_SET;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Strips the columns eager drops from its cache so a router/collection
|
|
423
|
+
// filter is evaluated against the same reduced shape eager uses — an
|
|
424
|
+
// excluded column reads as absent (→ null in NQL) rather than its real
|
|
425
|
+
// value. Only affects filters that reference an excluded column; every
|
|
426
|
+
// other filter sees an identical record.
|
|
427
|
+
private _recordForRouterFilter(record: Record<string, unknown>, routerType: string): Record<string, unknown> {
|
|
428
|
+
const excluded = this._excludedFilterFieldsFor(routerType);
|
|
429
|
+
if (excluded.size === 0) {
|
|
430
|
+
return record;
|
|
431
|
+
}
|
|
432
|
+
const stripped: Record<string, unknown> = {};
|
|
433
|
+
for (const key of Object.keys(record)) {
|
|
434
|
+
if (!excluded.has(key)) {
|
|
435
|
+
stripped[key] = record[key];
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return stripped;
|
|
439
|
+
}
|
|
440
|
+
|
|
377
441
|
private _formatPath(path: string, options: UrlOptions): string {
|
|
378
442
|
if (options.absolute) {
|
|
379
443
|
return this.urlUtils.createUrl(path, options.absolute);
|
|
@@ -418,8 +482,11 @@ export class LazyUrlService implements LazyUrlServiceBackend {
|
|
|
418
482
|
if (/\bprimary_author\b/.test(config.filter) && r.primary_author === undefined) {
|
|
419
483
|
missing.push('primary_author');
|
|
420
484
|
}
|
|
485
|
+
// Columns eager drops from its cache are never required — its filters
|
|
486
|
+
// match them as absent, so a resource lacking one is not thin here.
|
|
487
|
+
const excluded = this._excludedFilterFieldsFor(routerType);
|
|
421
488
|
for (const field of filterScalarFields(config.filter)) {
|
|
422
|
-
if (r[field] === undefined) {
|
|
489
|
+
if (!excluded.has(field) && r[field] === undefined) {
|
|
423
490
|
missing.push(field);
|
|
424
491
|
}
|
|
425
492
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const nql = require('@tryghost/nql');
|
|
2
2
|
const debug = require('@tryghost/debug')('services:url:generator');
|
|
3
|
-
const localUtils = require('../../../shared/url-utils');
|
|
3
|
+
const localUtils = require('../../../shared/url-utils').default;
|
|
4
4
|
|
|
5
5
|
// @TODO: merge with filter plugin
|
|
6
6
|
const EXPANSIONS = [{
|
|
@@ -50,7 +50,10 @@ class UrlServiceFacade {
|
|
|
50
50
|
const url = this.urlService.getUrlByResourceId(resource.id, options);
|
|
51
51
|
if (this.isComparing() && !options?.skipComparison) {
|
|
52
52
|
const context = this._compareContext(resource);
|
|
53
|
-
|
|
53
|
+
// Snapshot: callers mutate the resource's nested objects in place
|
|
54
|
+
// after this returns, but the comparison runs later via setImmediate.
|
|
55
|
+
const snapshot = _.cloneDeep(resource);
|
|
56
|
+
setImmediate(() => this._compare('getUrlForResource', url, () => this.lazyUrlService.getUrlForResource(snapshot, options), context));
|
|
54
57
|
}
|
|
55
58
|
return url;
|
|
56
59
|
}
|
|
@@ -117,7 +120,9 @@ class UrlServiceFacade {
|
|
|
117
120
|
const owns = this.urlService.owns(routerIdentifier, resource.id);
|
|
118
121
|
if (this.isComparing()) {
|
|
119
122
|
const context = this._compareContext(resource, { routerIdentifier });
|
|
120
|
-
|
|
123
|
+
// Snapshot, as in getUrlForResource.
|
|
124
|
+
const snapshot = _.cloneDeep(resource);
|
|
125
|
+
setImmediate(() => this._compare('ownsResource', owns, () => this.lazyUrlService.ownsResource(routerIdentifier, snapshot), context));
|
|
121
126
|
}
|
|
122
127
|
return owns;
|
|
123
128
|
}
|
|
@@ -133,6 +138,7 @@ class UrlServiceFacade {
|
|
|
133
138
|
return {
|
|
134
139
|
type: resource.type,
|
|
135
140
|
id: resource.id,
|
|
141
|
+
status: resource.status,
|
|
136
142
|
resourceKeys: Object.keys(resource),
|
|
137
143
|
caller: caller.stack,
|
|
138
144
|
...extra
|
|
@@ -157,7 +163,9 @@ class UrlServiceFacade {
|
|
|
157
163
|
if (this.isComparing()) {
|
|
158
164
|
// Fire-and-forget: don't await lazy so the reverse lookup adds no
|
|
159
165
|
// latency for its callers; the lazy DB read runs in the background.
|
|
160
|
-
|
|
166
|
+
// Snapshot the eager result, as in getUrlForResource.
|
|
167
|
+
const eagerSnapshot = _.cloneDeep(eagerResult);
|
|
168
|
+
void this._compareAsync('resolveUrl', eagerSnapshot, () => this.lazyUrlService.resolveUrl(urlPath), { path: urlPath }, (a, b) => this._resolvesToSameResource(a, b));
|
|
161
169
|
}
|
|
162
170
|
return eagerResult;
|
|
163
171
|
}
|
|
@@ -250,8 +258,65 @@ class UrlServiceFacade {
|
|
|
250
258
|
}
|
|
251
259
|
this._reportMismatch(method, eagerValue, lazyValue, context, isEqual);
|
|
252
260
|
}
|
|
261
|
+
_isNotFound(value) {
|
|
262
|
+
return typeof value === 'string' && value.endsWith('/404/');
|
|
263
|
+
}
|
|
264
|
+
// resolveUrl's contract is which resource a path maps to, not the exact
|
|
265
|
+
// serialized record. Eager (raw-knex) and lazy (model.toJSON) shape the same
|
|
266
|
+
// DB row differently — lazy carries a `parent` key eager omits, eager keeps
|
|
267
|
+
// __GHOST_URL__ placeholders lazy expands, timestamps differ in precision —
|
|
268
|
+
// so comparing whole records reports the same resolved resource as a
|
|
269
|
+
// mismatch. Compare resolved identity instead.
|
|
270
|
+
_resolvesToSameResource(a, b) {
|
|
271
|
+
if (a === null || b === null) {
|
|
272
|
+
return a === b;
|
|
273
|
+
}
|
|
274
|
+
const ra = a;
|
|
275
|
+
const rb = b;
|
|
276
|
+
return ra.id === rb.id && ra.type === rb.type;
|
|
277
|
+
}
|
|
278
|
+
// Divergences that are not lazy regressions, so logging them only buries
|
|
279
|
+
// the ones that are. Each branch is a class confirmed from production
|
|
280
|
+
// compare data.
|
|
281
|
+
_isExpectedDivergence(method, eagerValue, lazyValue, context) {
|
|
282
|
+
// Eager leaves tags/authors with no published posts out of its URL map
|
|
283
|
+
// (the shouldHavePosts gate) so they resolve to /404/, while lazy has no
|
|
284
|
+
// cheap way to run that check and returns the real URL. Eager cache
|
|
285
|
+
// staleness (a tag gaining its first post after boot) looks the same.
|
|
286
|
+
// Whether to keep lazy's behaviour is still open, but either way it is
|
|
287
|
+
// not a lazy bug, so exclude it to surface the divergences that are.
|
|
288
|
+
if (method === 'getUrlForResource'
|
|
289
|
+
&& (context.type === 'tags' || context.type === 'authors')
|
|
290
|
+
&& this._isNotFound(eagerValue) && !this._isNotFound(lazyValue)) {
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
// A site's owner starts with the default `ghost-user` slug and is
|
|
294
|
+
// renamed during setup. The rename emits no event the eager cache
|
|
295
|
+
// consumes, so eager serves /author/ghost-user/ until the next boot
|
|
296
|
+
// while lazy has the real slug from the database.
|
|
297
|
+
if (method === 'getUrlForResource'
|
|
298
|
+
&& context.type === 'authors'
|
|
299
|
+
&& typeof eagerValue === 'string' && eagerValue.endsWith('/author/ghost-user/')
|
|
300
|
+
&& !this._isNotFound(lazyValue)) {
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
// lazy returns /404/ where eager serves a real URL: eager cached a
|
|
304
|
+
// resource that is no longer routable (unpublished or deleted since,
|
|
305
|
+
// with no event to evict it). Suppress only when the resource is
|
|
306
|
+
// provably not published — a published resource lazy refuses to route
|
|
307
|
+
// is a real lazy bug, so that keeps logging.
|
|
308
|
+
if (method === 'getUrlForResource'
|
|
309
|
+
&& this._isNotFound(lazyValue) && !this._isNotFound(eagerValue)
|
|
310
|
+
&& typeof context.status === 'string' && context.status !== 'published') {
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
253
315
|
_reportMismatch(method, eagerValue, lazyValue, context, isEqual) {
|
|
254
316
|
if (!isEqual(eagerValue, lazyValue)) {
|
|
317
|
+
if (this._isExpectedDivergence(method, eagerValue, lazyValue, context)) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
255
320
|
const { caller, ...details } = context;
|
|
256
321
|
const report = new errors.InternalServerError({
|
|
257
322
|
message: 'URL service parity mismatch',
|
|
@@ -139,8 +139,11 @@ export class UrlServiceFacade {
|
|
|
139
139
|
const url = this.urlService.getUrlByResourceId(resource.id, options);
|
|
140
140
|
if (this.isComparing() && !options?.skipComparison) {
|
|
141
141
|
const context = this._compareContext(resource);
|
|
142
|
+
// Snapshot: callers mutate the resource's nested objects in place
|
|
143
|
+
// after this returns, but the comparison runs later via setImmediate.
|
|
144
|
+
const snapshot = _.cloneDeep(resource);
|
|
142
145
|
setImmediate(() => this._compare('getUrlForResource', url,
|
|
143
|
-
() => this.lazyUrlService!.getUrlForResource(
|
|
146
|
+
() => this.lazyUrlService!.getUrlForResource(snapshot, options),
|
|
144
147
|
context));
|
|
145
148
|
}
|
|
146
149
|
return url;
|
|
@@ -218,8 +221,10 @@ export class UrlServiceFacade {
|
|
|
218
221
|
const owns = this.urlService.owns(routerIdentifier, resource.id);
|
|
219
222
|
if (this.isComparing()) {
|
|
220
223
|
const context = this._compareContext(resource, {routerIdentifier});
|
|
224
|
+
// Snapshot, as in getUrlForResource.
|
|
225
|
+
const snapshot = _.cloneDeep(resource);
|
|
221
226
|
setImmediate(() => this._compare('ownsResource', owns,
|
|
222
|
-
() => this.lazyUrlService!.ownsResource(routerIdentifier,
|
|
227
|
+
() => this.lazyUrlService!.ownsResource(routerIdentifier, snapshot),
|
|
223
228
|
context));
|
|
224
229
|
}
|
|
225
230
|
return owns;
|
|
@@ -237,6 +242,7 @@ export class UrlServiceFacade {
|
|
|
237
242
|
return {
|
|
238
243
|
type: resource.type,
|
|
239
244
|
id: resource.id,
|
|
245
|
+
status: (resource as Record<string, unknown>).status,
|
|
240
246
|
resourceKeys: Object.keys(resource),
|
|
241
247
|
caller: caller.stack,
|
|
242
248
|
...extra
|
|
@@ -262,10 +268,12 @@ export class UrlServiceFacade {
|
|
|
262
268
|
if (this.isComparing()) {
|
|
263
269
|
// Fire-and-forget: don't await lazy so the reverse lookup adds no
|
|
264
270
|
// latency for its callers; the lazy DB read runs in the background.
|
|
265
|
-
|
|
271
|
+
// Snapshot the eager result, as in getUrlForResource.
|
|
272
|
+
const eagerSnapshot = _.cloneDeep(eagerResult);
|
|
273
|
+
void this._compareAsync('resolveUrl', eagerSnapshot,
|
|
266
274
|
() => this.lazyUrlService!.resolveUrl(urlPath),
|
|
267
275
|
{path: urlPath},
|
|
268
|
-
(a, b) =>
|
|
276
|
+
(a, b) => this._resolvesToSameResource(a, b));
|
|
269
277
|
}
|
|
270
278
|
return eagerResult;
|
|
271
279
|
}
|
|
@@ -377,6 +385,70 @@ export class UrlServiceFacade {
|
|
|
377
385
|
this._reportMismatch(method, eagerValue, lazyValue, context, isEqual);
|
|
378
386
|
}
|
|
379
387
|
|
|
388
|
+
private _isNotFound(value: unknown): boolean {
|
|
389
|
+
return typeof value === 'string' && value.endsWith('/404/');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// resolveUrl's contract is which resource a path maps to, not the exact
|
|
393
|
+
// serialized record. Eager (raw-knex) and lazy (model.toJSON) shape the same
|
|
394
|
+
// DB row differently — lazy carries a `parent` key eager omits, eager keeps
|
|
395
|
+
// __GHOST_URL__ placeholders lazy expands, timestamps differ in precision —
|
|
396
|
+
// so comparing whole records reports the same resolved resource as a
|
|
397
|
+
// mismatch. Compare resolved identity instead.
|
|
398
|
+
private _resolvesToSameResource(a: unknown, b: unknown): boolean {
|
|
399
|
+
if (a === null || b === null) {
|
|
400
|
+
return a === b;
|
|
401
|
+
}
|
|
402
|
+
const ra = a as Resource;
|
|
403
|
+
const rb = b as Resource;
|
|
404
|
+
return ra.id === rb.id && ra.type === rb.type;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Divergences that are not lazy regressions, so logging them only buries
|
|
408
|
+
// the ones that are. Each branch is a class confirmed from production
|
|
409
|
+
// compare data.
|
|
410
|
+
private _isExpectedDivergence(
|
|
411
|
+
method: string,
|
|
412
|
+
eagerValue: unknown,
|
|
413
|
+
lazyValue: unknown,
|
|
414
|
+
context: Record<string, unknown>
|
|
415
|
+
): boolean {
|
|
416
|
+
// Eager leaves tags/authors with no published posts out of its URL map
|
|
417
|
+
// (the shouldHavePosts gate) so they resolve to /404/, while lazy has no
|
|
418
|
+
// cheap way to run that check and returns the real URL. Eager cache
|
|
419
|
+
// staleness (a tag gaining its first post after boot) looks the same.
|
|
420
|
+
// Whether to keep lazy's behaviour is still open, but either way it is
|
|
421
|
+
// not a lazy bug, so exclude it to surface the divergences that are.
|
|
422
|
+
if (method === 'getUrlForResource'
|
|
423
|
+
&& (context.type === 'tags' || context.type === 'authors')
|
|
424
|
+
&& this._isNotFound(eagerValue) && !this._isNotFound(lazyValue)) {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// A site's owner starts with the default `ghost-user` slug and is
|
|
429
|
+
// renamed during setup. The rename emits no event the eager cache
|
|
430
|
+
// consumes, so eager serves /author/ghost-user/ until the next boot
|
|
431
|
+
// while lazy has the real slug from the database.
|
|
432
|
+
if (method === 'getUrlForResource'
|
|
433
|
+
&& context.type === 'authors'
|
|
434
|
+
&& typeof eagerValue === 'string' && eagerValue.endsWith('/author/ghost-user/')
|
|
435
|
+
&& !this._isNotFound(lazyValue)) {
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// lazy returns /404/ where eager serves a real URL: eager cached a
|
|
440
|
+
// resource that is no longer routable (unpublished or deleted since,
|
|
441
|
+
// with no event to evict it). Suppress only when the resource is
|
|
442
|
+
// provably not published — a published resource lazy refuses to route
|
|
443
|
+
// is a real lazy bug, so that keeps logging.
|
|
444
|
+
if (method === 'getUrlForResource'
|
|
445
|
+
&& this._isNotFound(lazyValue) && !this._isNotFound(eagerValue)
|
|
446
|
+
&& typeof context.status === 'string' && context.status !== 'published') {
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
|
|
380
452
|
private _reportMismatch(
|
|
381
453
|
method: string,
|
|
382
454
|
eagerValue: unknown,
|
|
@@ -385,6 +457,9 @@ export class UrlServiceFacade {
|
|
|
385
457
|
isEqual: (a: unknown, b: unknown) => boolean
|
|
386
458
|
): void {
|
|
387
459
|
if (!isEqual(eagerValue, lazyValue)) {
|
|
460
|
+
if (this._isExpectedDivergence(method, eagerValue, lazyValue, context)) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
388
463
|
const {caller, ...details} = context;
|
|
389
464
|
const report = new errors.InternalServerError({
|
|
390
465
|
message: 'URL service parity mismatch',
|
|
@@ -8,7 +8,7 @@ const UrlGenerator = require('./url-generator');
|
|
|
8
8
|
const Queue = require('./queue');
|
|
9
9
|
const Urls = require('./urls');
|
|
10
10
|
const Resources = require('./resources');
|
|
11
|
-
const urlUtils = require('../../../shared/url-utils');
|
|
11
|
+
const urlUtils = require('../../../shared/url-utils').default;
|
|
12
12
|
const resourcesConfig = require('./config');
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const debug = require('@tryghost/debug')('services:url:urls');
|
|
2
|
-
const urlUtils = require('../../../shared/url-utils');
|
|
2
|
+
const urlUtils = require('../../../shared/url-utils').default;
|
|
3
3
|
const logging = require('@tryghost/logging');
|
|
4
4
|
const errors = require('@tryghost/errors');
|
|
5
5
|
|
|
@@ -1,5 +1,22 @@
|
|
|
1
|
+
// Composition root for the webhook dispatch pipeline: builds the
|
|
2
|
+
// serialize → payload → trigger chain and registers the model-event
|
|
3
|
+
// listeners. Requires are deferred until listen() runs so the model layer
|
|
4
|
+
// isn't loaded before boot wires it.
|
|
1
5
|
module.exports = {
|
|
2
|
-
|
|
3
|
-
|
|
6
|
+
listen() {
|
|
7
|
+
const models = require('../../models');
|
|
8
|
+
const limitService = require('../../services/limits');
|
|
9
|
+
const events = require('../../lib/common/events');
|
|
10
|
+
const urlService = require('../url').facade;
|
|
11
|
+
const createSerialize = require('./serialize');
|
|
12
|
+
const createPayload = require('./payload');
|
|
13
|
+
const WebhookTrigger = require('./webhook-trigger');
|
|
14
|
+
const registerListeners = require('./listen');
|
|
15
|
+
|
|
16
|
+
const serialize = createSerialize({urlService});
|
|
17
|
+
const payload = createPayload({serialize});
|
|
18
|
+
const trigger = new WebhookTrigger({models, payload, limitService});
|
|
19
|
+
|
|
20
|
+
registerListeners({events, trigger});
|
|
4
21
|
}
|
|
5
22
|
};
|
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
-
const limitService = require('../../services/limits');
|
|
3
|
-
const WebhookTrigger = require('./webhook-trigger');
|
|
4
|
-
const models = require('../../models');
|
|
5
|
-
const payload = require('./payload');
|
|
6
|
-
|
|
7
|
-
// The webhook system is fundamentally built on top of our model event system
|
|
8
|
-
const events = require('../../lib/common/events');
|
|
9
2
|
|
|
10
3
|
const WEBHOOKS = [
|
|
11
4
|
'site.changed',
|
|
@@ -44,8 +37,10 @@ const WEBHOOKS = [
|
|
|
44
37
|
'page.tag.detached'
|
|
45
38
|
];
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
// Registers the webhook dispatch listeners on the model event system. The
|
|
41
|
+
// trigger is built by the composition root (index.js) and injected here so
|
|
42
|
+
// this module owns only the event-to-trigger wiring.
|
|
43
|
+
const registerListeners = ({events, trigger}) => {
|
|
49
44
|
_.each(WEBHOOKS, (event) => {
|
|
50
45
|
// @NOTE: The early exit makes sure the listeners are only registered once.
|
|
51
46
|
// During testing the "events" instance is kept around with all the
|
|
@@ -62,9 +57,9 @@ const listen = async () => {
|
|
|
62
57
|
return;
|
|
63
58
|
}
|
|
64
59
|
|
|
65
|
-
|
|
60
|
+
trigger.trigger(event, model);
|
|
66
61
|
});
|
|
67
62
|
});
|
|
68
63
|
};
|
|
69
64
|
|
|
70
|
-
module.exports =
|
|
65
|
+
module.exports = registerListeners;
|