ghost 6.55.0 → 6.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (483) hide show
  1. package/bin/validate-adapters.js +74 -0
  2. package/bin/validate-adapters.ts +87 -0
  3. package/bin/warm-compile-cache.sh +60 -0
  4. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  5. package/components/tryghost-kg-default-cards-10.3.3.tgz +0 -0
  6. package/components/tryghost-kg-default-nodes-2.1.4.tgz +0 -0
  7. package/components/tryghost-kg-default-transforms-1.3.3.tgz +0 -0
  8. package/components/tryghost-kg-html-to-lexical-1.3.3.tgz +0 -0
  9. package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
  10. package/components/tryghost-nql-string-0.0.0.tgz +0 -0
  11. package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
  12. package/core/app.js +1 -1
  13. package/core/boot.js +21 -25
  14. package/core/bridge.js +10 -12
  15. package/core/built/admin/assets/Combination-BDvCdg1t.js +41 -0
  16. package/core/built/admin/assets/{PolarAngleAxis-BGjf7sBu.js → PolarAngleAxis-C4qtHy1B.js} +1 -1
  17. package/core/built/admin/assets/{RadialBarChart-zeFf9mBz.js → RadialBarChart-Bh09Stsj.js} +1 -1
  18. package/core/built/admin/assets/about-C7O-AgRZ.js +1 -0
  19. package/core/built/admin/assets/{account-migration-sjHLx5dy.js → account-migration-Cv_ol1ko.js} +1 -1
  20. package/core/built/admin/assets/action-list-lGzZLHc8.js +1 -0
  21. package/core/built/admin/assets/activitypub/{account-migration-DoYXtnZ8.js → account-migration-DHFbePl6.js} +7 -7
  22. package/core/built/admin/assets/activitypub/activitypub.js +6 -6
  23. package/core/built/admin/assets/activitypub/{avatar-B3DEDB1l.js → avatar-CT4tS2_S.js} +2 -2
  24. package/core/built/admin/assets/activitypub/{avatar-flipboard-Cqrg0XMx.js → avatar-flipboard-ByYWKipo.js} +2 -2
  25. package/core/built/admin/assets/activitypub/{bluesky-sharing-Bxeh6MKW.js → bluesky-sharing-srVnSHDz.js} +16 -16
  26. package/core/built/admin/assets/activitypub/{content-formatters-BpQARh28.js → content-formatters-C2sos5Ht.js} +3 -3
  27. package/core/built/admin/assets/activitypub/{copy-D9UWIAan.js → copy-B1UBTOoZ.js} +2 -2
  28. package/core/built/admin/assets/activitypub/{dist-D8sFfdpC.js → dist-MPsb2WZ1.js} +12 -12
  29. package/core/built/admin/assets/activitypub/{domain-CWueFW6q.js → domain-DzcM9Dzo.js} +9 -9
  30. package/core/built/admin/assets/activitypub/{edit-profile-BGxPU7N5.js → edit-profile-CsQ_4xAk.js} +945 -945
  31. package/core/built/admin/assets/activitypub/{explore-DBK-u0Ch.js → explore-CJe0DZBK.js} +8 -8
  32. package/core/built/admin/assets/activitypub/{feed-Bcir5RQZ.js → feed-CKV3qWON.js} +33 -33
  33. package/core/built/admin/assets/activitypub/{field-BV-CIT3d.js → field-DckHfDJf.js} +3 -3
  34. package/core/built/admin/assets/activitypub/{hash-BC9Q0N4c.js → hash-Dlp8RM55.js} +2 -2
  35. package/core/built/admin/assets/activitypub/{inbox-DQ4ZnC1m.js → inbox-CsPy4R0M.js} +2 -2
  36. package/core/built/admin/assets/activitypub/{inbox-yC0nfvzl.js → inbox-DAsjzUUe.js} +85 -85
  37. package/core/built/admin/assets/activitypub/{label-DTRS9TDk.js → label-DWyo9E5T.js} +3 -3
  38. package/core/built/admin/assets/activitypub/layout-DM_3pqsU.js +1 -0
  39. package/core/built/admin/assets/activitypub/{moderation-Dwl9t0pb.js → moderation-B0Inc03M.js} +11 -11
  40. package/core/built/admin/assets/activitypub/{note-B-zKsZ7r.js → note-CSE54lER.js} +7 -7
  41. package/core/built/admin/assets/activitypub/{notifications-DjJZD54t.js → notifications-RSwbKqdh.js} +9 -9
  42. package/core/built/admin/assets/activitypub/{preferences-CoU8vs6d.js → preferences-CE231n2_.js} +20 -20
  43. package/core/built/admin/assets/activitypub/{profile-BAsaJekx.js → profile-aaQ1j1Eo.js} +237 -237
  44. package/core/built/admin/assets/activitypub/{reply-CpdMeAO5.js → reply-Cjb0Vs3m.js} +2 -2
  45. package/core/built/admin/assets/activitypub/{routes-CLH3OVTP.js → routes-Djb0mc9k.js} +1139 -1150
  46. package/core/built/admin/assets/activitypub/{separator-BI1FLsYM.js → separator-D8pROI5n.js} +3 -3
  47. package/core/built/admin/assets/activitypub/{settings-DA6pNJ-Q.js → settings-DMc32IQl.js} +13 -13
  48. package/core/built/admin/assets/activitypub/{site-CQD42Pw5.js → site-DVIcsohX.js} +81 -81
  49. package/core/built/admin/assets/activitypub/{step-1-DIDg4aHb.js → step-1-9Yey9usF.js} +5 -5
  50. package/core/built/admin/assets/activitypub/{step-2-BfGe8BYt.js → step-2-DdYmKRXV.js} +6 -6
  51. package/core/built/admin/assets/activitypub/{step-3-mE9spAqw.js → step-3-hdSkcjZU.js} +10 -10
  52. package/core/built/admin/assets/activitypub/{tabs-Cg0saow1.js → tabs-CUqseYzQ.js} +317 -315
  53. package/core/built/admin/assets/activitypub/{topic-filter-DLHBZT93.js → topic-filter-Coni6OXM.js} +3 -3
  54. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-VzbN99u_.js → use-activity-pub-queries-MeHTPMQv.js} +2 -2
  55. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-CAwXTmqH.js → use-navigate-with-base-path-B046AM38.js} +115 -96
  56. package/core/built/admin/assets/activitypub/{use-reply-chain-data-Br4aOWOh.js → use-reply-chain-data-1AO-Xob6.js} +5 -5
  57. package/core/built/admin/assets/activitypub/{x-C8tAnXly.js → x-BRfRYd16.js} +2 -2
  58. package/core/built/admin/assets/add-integration-modal-m9Dv6f77.js +1 -0
  59. package/core/built/admin/assets/add-newsletter-modal-CPSVZL2H.js +1 -0
  60. package/core/built/admin/assets/add-recommendation-modal-Ci9Z5yat.js +1 -0
  61. package/core/built/admin/assets/analytics-DqSCVV8U.js +1 -0
  62. package/core/built/admin/assets/announcement-bar-modal-DU3avV7d.js +1 -0
  63. package/core/built/admin/assets/api-keys-BK0LylyK.js +1 -0
  64. package/core/built/admin/assets/api-keys-_iCT4dxr.js +1 -0
  65. package/core/built/admin/assets/{audience-DjEWNhp3.js → audience-DTJtXXWY.js} +1 -1
  66. package/core/built/admin/assets/automated-emails-PayPCT8L.js +1 -0
  67. package/core/built/admin/assets/{automations-DhKeS_U5.js → automations-162q5HjL.js} +1 -1
  68. package/core/built/admin/assets/{automations-Bx3VcFIL.js → automations-dtQ_MRZ7.js} +1 -1
  69. package/core/built/admin/assets/{bluesky-sharing-ClMd0-Ft.js → bluesky-sharing-B8Es3AuV.js} +1 -1
  70. package/core/built/admin/assets/{box-BWbhXh21.js → box-By_EgGtm.js} +1 -1
  71. package/core/built/admin/assets/brand-icon-09YqFZjV.js +1 -0
  72. package/core/built/admin/assets/{breadcrumb-BD0U2ygg.js → breadcrumb-tg5zL--z.js} +1 -1
  73. package/core/built/admin/assets/build-CsiH26uS.js +1 -0
  74. package/core/built/admin/assets/{card-DGoDJzrn.js → card-aUHX0hyR.js} +1 -1
  75. package/core/built/admin/assets/{chart-BuRT2Y6p.js → chart-BnXyfH11.js} +1 -1
  76. package/core/built/admin/assets/{chart-helpers-fjQKRVyY.js → chart-helpers-CZElTGYL.js} +1 -1
  77. package/core/built/admin/assets/checkbox-Cyo-x7mC.js +1 -0
  78. package/core/built/admin/assets/chunk.524.cd5498dd4e195f679266.js +25 -0
  79. package/core/built/admin/assets/chunk.582.6f92ec1f03734bf2ea3f.js +27 -0
  80. package/core/built/admin/assets/{chunk.481.6c7395a49484bb4583aa.js → chunk.873.b8fc05a8ab877fb819ed.js} +62 -60
  81. package/core/built/admin/assets/code-editor-view-Dq1rQ4tS.js +1 -0
  82. package/core/built/admin/assets/color-picker-field-BonBgxhU.js +1 -0
  83. package/core/built/admin/assets/color-utils-BsrwXuDz.js +1 -0
  84. package/core/built/admin/assets/combobox-uX5r_paI.js +1 -0
  85. package/core/built/admin/assets/command-DxwBO2pF.js +1 -0
  86. package/core/built/admin/assets/comments-Dn-RO-nU.js +1 -0
  87. package/core/built/admin/assets/components-CwL6a6As-B0E0-NBJ.js +1 -0
  88. package/core/built/admin/assets/confirmation-modal-B6D6mk6R.js +1 -0
  89. package/core/built/admin/assets/{container-BYz2Ed39.js → container-LiZf3XdY.js} +1 -1
  90. package/core/built/admin/assets/content-api-modal-BxQ-7T6u.js +1 -0
  91. package/core/built/admin/assets/copy-field-BAxL4Zqn.js +1 -0
  92. package/core/built/admin/assets/currency-Dy4Tc2R2.js +1 -0
  93. package/core/built/admin/assets/custom-integration-modal---d_l7lc.js +1 -0
  94. package/core/built/admin/assets/{data-list-16rkJYYi.js → data-list-CupF7bjM.js} +1 -1
  95. package/core/built/admin/assets/{date-range-select-BFp58EMk.js → date-range-select-PB2n1HZf.js} +1 -1
  96. package/core/built/admin/assets/debounce-Dg2fW5Rg.js +3 -0
  97. package/core/built/admin/assets/design-and-theme-modal-DYRT207a.js +23 -0
  98. package/core/built/admin/assets/dirty-confirm-dialog-BnDJ39ao.js +1 -0
  99. package/core/built/admin/assets/disabled-sources-indicator-iMHrkyV3.js +1 -0
  100. package/core/built/admin/assets/dist-B2PvRwLn.js +1 -0
  101. package/core/built/admin/assets/{dist-DGzPJ_bO.js → dist-Cb5RSHSg.js} +1 -1
  102. package/core/built/admin/assets/{dist-P_ohUlTi.js → dist-CqO2lWS02.js} +1 -1
  103. package/core/built/admin/assets/{dist-pHiOxyxa.js → dist-D5vhMdXf.js} +1 -1
  104. package/core/built/admin/assets/dist-DlCyLrOC.js +1 -0
  105. package/core/built/admin/assets/dist-WeOIvSYM.js +23 -0
  106. package/core/built/admin/assets/{dist-DyViJwIy.js → dist-cFzKLG7y.js} +3 -3
  107. package/core/built/admin/assets/dist-j1xxxnY8.js +1 -0
  108. package/core/built/admin/assets/dist-myQaY0ka.js +1 -0
  109. package/core/built/admin/assets/domain-BR8FIdjD.js +1 -0
  110. package/core/built/admin/assets/{dropzone-DlK-K2DQ.js → dropzone-hNxmUvwD.js} +1 -1
  111. package/core/built/admin/assets/{edit-profile-BKT-REJw.js → edit-profile-CENrkY0l.js} +1 -1
  112. package/core/built/admin/assets/editor-B18D0lm8.js +8 -0
  113. package/core/built/admin/assets/embed-signup-form-modal-BsCYUMvQ.js +9 -0
  114. package/core/built/admin/assets/{empty-indicator-oX1u1Pkn.js → empty-indicator-WB1WgT9h.js} +1 -1
  115. package/core/built/admin/assets/{explore-ClUpJZOp.js → explore-BmrnljWw.js} +1 -1
  116. package/core/built/admin/assets/{feed-DF92dUMP.js → feed-Dy4li6i8.js} +1 -1
  117. package/core/built/admin/assets/{field-Do7lieBA.js → field-Dh_lwk13.js} +1 -1
  118. package/core/built/admin/assets/filter-sources-D2zDuJH5.js +1 -0
  119. package/core/built/admin/assets/filters-3JBQcNtJ.js +1 -0
  120. package/core/built/admin/assets/filters-CFt_BDe4.js +1 -0
  121. package/core/built/admin/assets/first-promoter-modal-D6Rjxugw.js +1 -0
  122. package/core/built/admin/assets/format-url-C-t8G6Qd.js +1 -0
  123. package/core/built/admin/assets/{ghost-a9c788f6f0b1ea6cf0f14ca4b13f4496.css → ghost-d38c30b866e59f2e06a357b5eb844fe5.css} +1 -1
  124. package/core/built/admin/assets/{ghost-c4057474943effc568c5659ccc4909d7.js → ghost-d683fbeb945b13155ea669caa5d8db41.js} +420 -398
  125. package/core/built/admin/assets/{ghost-dark-db36d795d21c61426837df40af270966.css → ghost-dark-1bffd5099ba7a749303133efacf8480b.css} +1 -1
  126. package/core/built/admin/assets/{gift-link-modal-f9VIMNDv.js → gift-link-modal-zMtlJhLQ.js} +1 -1
  127. package/core/built/admin/assets/{gift-links-CsUhefWG.js → gift-links-CVuqLhsZ.js} +1 -1
  128. package/core/built/admin/assets/global-data-provider-B949LDXJ.js +1 -0
  129. package/core/built/admin/assets/grid-CUciBfjf.js +1 -0
  130. package/core/built/admin/assets/{growth-CVw1My4S.js → growth-C38Xtp8D.js} +2 -2
  131. package/core/built/admin/assets/{growth-B3I6gNLm.js → growth-CFws67_i.js} +1 -1
  132. package/core/built/admin/assets/history-modal-ILAOkbxH.js +1 -0
  133. package/core/built/admin/assets/{hooks-CixO01lL.js → hooks-D6EE7BIt.js} +2 -2
  134. package/core/built/admin/assets/html-field-9a9Id_2q.js +1 -0
  135. package/core/built/admin/assets/iframe-buffering-e8vH4-Qn.js +1 -0
  136. package/core/built/admin/assets/images-CrQKVc9Q.js +1 -0
  137. package/core/built/admin/assets/{inbox-CFBSu4Ed.js → inbox-BrSRzyvz.js} +6 -6
  138. package/core/built/admin/assets/index-BHy2DZX-.js +67 -0
  139. package/core/built/admin/assets/index-Ds2iUoF6.css +1 -0
  140. package/core/built/admin/assets/index.es-B9tvf-t4.js +1 -0
  141. package/core/built/admin/assets/{inline-EMAm3okk.js → inline-Cw-gigDW.js} +1 -1
  142. package/core/built/admin/assets/input-group-6vEPvX9S.js +1 -0
  143. package/core/built/admin/assets/{input-surface-BpAjZXY5.js → input-surface-BdOe4KzO.js} +1 -1
  144. package/core/built/admin/assets/integration-header-B4DyXV06.js +1 -0
  145. package/core/built/admin/assets/integrations-BT6_iJvu.js +1 -0
  146. package/core/built/admin/assets/invite-user-modal-5xDCEw90.js +1 -0
  147. package/core/built/admin/assets/isEmail-BOfC647y.js +1 -0
  148. package/core/built/admin/assets/koenig-editor-base-BWLEB9Vf.js +1 -0
  149. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +9535 -9527
  150. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +124 -124
  151. package/core/built/admin/assets/{koenig-lexical-f8vHRYvz.js → koenig-lexical-Cj62r_y9.js} +86 -86
  152. package/core/built/admin/assets/{kpi-card-DSap9rAy.js → kpi-card-DOUnkZcN.js} +1 -1
  153. package/core/built/admin/assets/{kpi-card-DU0rn9Cj.js → kpi-card-IHDo8Q56.js} +1 -1
  154. package/core/built/admin/assets/{kpi-helpers-Dz33AV6h.js → kpi-helpers-CjUdECAK.js} +1 -1
  155. package/core/built/admin/assets/{kpi-tabs-BtTTrKvJ.js → kpi-tabs-X3DkOYbI.js} +1 -1
  156. package/core/built/admin/assets/{label-DS8Mi6G5.js → label-Dq6QaOuS.js} +1 -1
  157. package/core/built/admin/assets/label-picker-8YlhS2U3.js +1 -0
  158. package/core/built/admin/assets/labels-DAPjKPy2.js +1 -0
  159. package/core/built/admin/assets/layout-TMj4P-vv.js +1 -0
  160. package/core/built/admin/assets/limit-modal-BRPsit_B.js +1 -0
  161. package/core/built/admin/assets/link-helpers-Bzkk68GD.js +1 -0
  162. package/core/built/admin/assets/{list-page-B6ANtzkd.js → list-page-CdsnQR9N.js} +1 -1
  163. package/core/built/admin/assets/member-custom-fields-CmvsuuV_.js +1 -0
  164. package/core/built/admin/assets/member-detail-DCvRrkmJ.js +1 -0
  165. package/core/built/admin/assets/members-DxbSKngD.js +11 -0
  166. package/core/built/admin/assets/modal-page-BMTzFHVO.js +1 -0
  167. package/core/built/admin/assets/{moderation-DySbgyUS.js → moderation-Da5lhhWh.js} +1 -1
  168. package/core/built/admin/assets/multi-select-combobox-CYdkH7KQ.js +1 -0
  169. package/core/built/admin/assets/navigation-modal-BIoTwtMy.js +1 -0
  170. package/core/built/admin/assets/newsletter-BptlVoJm.js +1 -0
  171. package/core/built/admin/assets/newsletter-detail-modal-BIvxqE0N.js +1 -0
  172. package/core/built/admin/assets/newsletter-emails-Bf8IZLsP.js +1 -0
  173. package/core/built/admin/assets/newsletters-BgrIZk0V.js +1 -0
  174. package/core/built/admin/assets/{newsletters-BXJbJgA-.js → newsletters-Du0lzskS.js} +1 -1
  175. package/core/built/admin/assets/{note-C8kxrz6w.js → note-DYcLOmDb.js} +1 -1
  176. package/core/built/admin/assets/{notifications-CgtSfX7t.js → notifications-B2SPFIV9.js} +1 -1
  177. package/core/built/admin/assets/{offers-DEniVRD5.js → offers-Um6ab71O.js} +1 -1
  178. package/core/built/admin/assets/offers-container-modal-BZnZITMw.js +1 -0
  179. package/core/built/admin/assets/{onboarding-route-4qtziA9d.js → onboarding-route-DgdfxkQh.js} +1 -1
  180. package/core/built/admin/assets/overview-BYQJXSog.js +1 -0
  181. package/core/built/admin/assets/overview-Dt2_mR6U.js +1 -0
  182. package/core/built/admin/assets/{page-header-B3Wb9Jqv.js → page-header-CpmxJj5B.js} +1 -1
  183. package/core/built/admin/assets/{pages-B4goRPUn.js → pages-BVMnpI2O.js} +1 -1
  184. package/core/built/admin/assets/pintura-modal-B4MuBuuO.js +1 -0
  185. package/core/built/admin/assets/portal-frame-BF0NBIji.js +1 -0
  186. package/core/built/admin/assets/portal-modal-DMVbm0oy.js +1 -0
  187. package/core/built/admin/assets/{post-analytics-We-ko5Cb.js → post-analytics-CXWBOGyP.js} +1 -1
  188. package/core/built/admin/assets/post-analytics-header-ChzjXVPt.js +1 -0
  189. package/core/built/admin/assets/{post-analytics-provider-B6UynTGb.js → post-analytics-provider-Cgw4U_fw.js} +1 -1
  190. package/core/built/admin/assets/{post-share-modal-DFfO93_0.js → post-share-modal-B9Vmk_XM.js} +1 -1
  191. package/core/built/admin/assets/{posts-DhXE7b6r.js → posts-CxG7v0-M.js} +1 -1
  192. package/core/built/admin/assets/preferences-BfzJmnsd.js +5 -0
  193. package/core/built/admin/assets/preview-chrome-BOVO6_PE.js +1 -0
  194. package/core/built/admin/assets/preview-modal-Dw_HtD6S.js +1 -0
  195. package/core/built/admin/assets/profile-BT5vtv9b.js +1 -0
  196. package/core/built/admin/assets/radio-group-I7ou7PoJ.js +1 -0
  197. package/core/built/admin/assets/{referrers-pmJT4k3-.js → referrers-B9QDAfYm.js} +1 -1
  198. package/core/built/admin/assets/{router-provider-C1R4AZ2o.js → router-provider-DSQfprxH.js} +1 -1
  199. package/core/built/admin/assets/select-D9oU1IoM.js +1 -0
  200. package/core/built/admin/assets/settings-CfZFbyu_.js +4 -0
  201. package/core/built/admin/assets/{settings-WXNeWyU1.js → settings-CpvHWix7.js} +1 -1
  202. package/core/built/admin/assets/settings-breadcrumbs-K0USP2nw.js +1 -0
  203. package/core/built/admin/assets/{shade-app-B3heOAfz-CGXQwWeO.js → shade-app-B3heOAfz-BhQXxNkT.js} +1 -1
  204. package/core/built/admin/assets/{share-modal-DsgW3wbg.js → share-modal-ClIyPzc5.js} +1 -1
  205. package/core/built/admin/assets/slack-modal-1XXqOfiw.js +1 -0
  206. package/core/built/admin/assets/social-urls-CHQ9cTAK.js +1 -0
  207. package/core/built/admin/assets/{sort-button-Bb_FCl7e.js → sort-button-CdBsm40v.js} +1 -1
  208. package/core/built/admin/assets/{source-icon-BeaRxUn9.js → source-icon-CEYmVLXR.js} +1 -1
  209. package/core/built/admin/assets/{stack-xiN0eQ6Z.js → stack-CIfOAAVf.js} +1 -1
  210. package/core/built/admin/assets/{stats-Bjj_kmJ1.js → stats-D8nZCTy5.js} +1 -1
  211. package/core/built/admin/assets/{stats-view-BxPbW6K1.js → stats-view-CugFVf0g.js} +1 -1
  212. package/core/built/admin/assets/{step-1-B1e3-1F8.js → step-1-DOYjrORn.js} +1 -1
  213. package/core/built/admin/assets/{step-2-C5WaeBJo.js → step-2-vA6RsarH.js} +1 -1
  214. package/core/built/admin/assets/{step-3-B46iVPwu.js → step-3-BtIBxMId.js} +2 -2
  215. package/core/built/admin/assets/stripe-button-UFP4QRXd.js +1 -0
  216. package/core/built/admin/assets/stripe-connect-modal-C_L3uM6n.js +1 -0
  217. package/core/built/admin/assets/switch-CE2NuMn-.js +1 -0
  218. package/core/built/admin/assets/{table-CB-4_HBJ.js → table-TMCgCQK7.js} +1 -1
  219. package/core/built/admin/assets/tabs-Dd6oOKbv.js +1 -0
  220. package/core/built/admin/assets/tag-detail-D3l-UJPR.js +1 -0
  221. package/core/built/admin/assets/tags-BwCgmr5v.js +1 -0
  222. package/core/built/admin/assets/tags-WdKQGWR8.js +1 -0
  223. package/core/built/admin/assets/testimonials-modal-CqWp-xXJ.js +1 -0
  224. package/core/built/admin/assets/{text-DwtjwGkh.js → text-DU9Hv33_.js} +1 -1
  225. package/core/built/admin/assets/textarea-d9qEkMOP.js +1 -0
  226. package/core/built/admin/assets/tier-detail-modal-BkcR2u0A.js +1 -0
  227. package/core/built/admin/assets/{tiers-DYsa4DCo.js → tiers-Da6kxHQP.js} +1 -1
  228. package/core/built/admin/assets/toggle-group-Guorw93z.js +1 -0
  229. package/core/built/admin/assets/{topic-filter-DAFT87tQ.js → topic-filter-CbgoWIHn.js} +1 -1
  230. package/core/built/admin/assets/transistor-modal-DuNnnpal.js +1 -0
  231. package/core/built/admin/assets/unsplash-modal-y2bVwTHH.js +1 -0
  232. package/core/built/admin/assets/{use-analytics-data-S0FTd43s.js → use-analytics-data-B-b4wNSC.js} +1 -1
  233. package/core/built/admin/assets/use-check-theme-limit-error-m09J38Mq.js +2 -0
  234. package/core/built/admin/assets/use-feature-flag-ud0Qz8vt.js +1 -0
  235. package/core/built/admin/assets/{use-filter-params-Gpoq2E1L.js → use-filter-params-BnbBDc7T.js} +1 -1
  236. package/core/built/admin/assets/use-filterable-api-iMpDZBxD.js +1 -0
  237. package/core/built/admin/assets/use-form-vRjI0Kwk.js +1 -0
  238. package/core/built/admin/assets/{use-growth-stats-CIaiHOVb.js → use-growth-stats-DPU5rt3W.js} +1 -1
  239. package/core/built/admin/assets/use-hash-link-navigation-guard-B9j6HIrV.js +1 -0
  240. package/core/built/admin/assets/use-limiter-BkN3E4gw.js +2 -0
  241. package/core/built/admin/assets/use-pintura-config-C_z8HTG2.js +1 -0
  242. package/core/built/admin/assets/use-pintura-editor-DNgbkhGp.js +1 -0
  243. package/core/built/admin/assets/{use-post-referrers-Cq2vBPrA.js → use-post-referrers-C2mXyrp-.js} +1 -1
  244. package/core/built/admin/assets/use-query-params-DHhhu8wi.js +1 -0
  245. package/core/built/admin/assets/{use-reply-chain-data-DBDRtGu-.js → use-reply-chain-data-DaK8q2qQ.js} +1 -1
  246. package/core/built/admin/assets/use-setting-group-YlYcoNyP.js +1 -0
  247. package/core/built/admin/assets/use-settings-navigation-Bf1g1rEh.js +1 -0
  248. package/core/built/admin/assets/{use-simple-pagination-CjYNxdwe.js → use-simple-pagination-D9ckKo5-.js} +1 -1
  249. package/core/built/admin/assets/{use-tinybird-query-falHAtuK.js → use-tinybird-query-GSa32hVw.js} +1 -1
  250. package/core/built/admin/assets/{use-tinybird-token-D7-8ND1y.js → use-tinybird-token-CKLOZFsw.js} +1 -1
  251. package/core/built/admin/assets/use-url-input-z-p0Kewm.js +5 -0
  252. package/core/built/admin/assets/user-detail-modal-BMxOA4Sv.js +1 -0
  253. package/core/built/admin/assets/{users-Dkv8NQZe.js → users-B6hIds6n.js} +1 -1
  254. package/core/built/admin/assets/validator-Cr0sMKuW.js +7 -0
  255. package/core/built/admin/assets/{virtual-list-CvnC9zF2.js → virtual-list-RHeEL05L.js} +1 -1
  256. package/core/built/admin/assets/{web-jFYWCKDn.js → web-Bw2QMO5u.js} +2 -2
  257. package/core/built/admin/assets/web-xJtcwMy4.js +1 -0
  258. package/core/built/admin/assets/zapier-modal-BP2f4-vY.js +1 -0
  259. package/core/built/admin/index.html +20 -18
  260. package/core/frontend/helpers/authors.js +1 -1
  261. package/core/frontend/helpers/link.js +3 -8
  262. package/core/frontend/helpers/tags.js +1 -1
  263. package/core/frontend/meta/author-url.js +2 -2
  264. package/core/frontend/meta/url.js +4 -4
  265. package/core/frontend/services/data/fetch-data.js +10 -5
  266. package/core/frontend/services/proxy.js +1 -1
  267. package/core/frontend/services/routing/api-adapter.js +115 -0
  268. package/core/frontend/services/routing/api-adapter.ts +157 -0
  269. package/core/frontend/services/routing/collection-router.js +5 -3
  270. package/core/frontend/services/routing/controllers/index.js +1 -1
  271. package/core/frontend/services/routing/controllers/rss.js +26 -31
  272. package/core/frontend/services/routing/controllers/rss.ts +66 -0
  273. package/core/frontend/services/routing/controllers/static.js +8 -7
  274. package/core/frontend/services/routing/events.js +20 -6
  275. package/core/frontend/services/routing/parent-router.js +17 -4
  276. package/core/frontend/services/routing/permalink-adapter.js +13 -0
  277. package/core/frontend/services/routing/permalink-adapter.ts +10 -0
  278. package/core/frontend/services/routing/registry.js +59 -33
  279. package/core/frontend/services/routing/router-manager.js +24 -22
  280. package/core/frontend/services/routing/static-routes-router.js +4 -4
  281. package/core/frontend/services/routing/taxonomy-router.js +6 -2
  282. package/core/frontend/services/sitemap/base-site-map-generator.js +1 -28
  283. package/core/frontend/services/sitemap/site-map-manager.js +22 -57
  284. package/core/server/adapters/route-settings/utils.js +10 -2
  285. package/core/server/adapters/route-settings/utils.ts +12 -2
  286. package/core/server/api/endpoints/email-post.js +1 -1
  287. package/core/server/api/endpoints/previews.js +1 -1
  288. package/core/server/api/endpoints/search-index-public.js +3 -3
  289. package/core/server/api/endpoints/search-index.js +4 -4
  290. package/core/server/api/endpoints/utils/serializers/input/authors.js +2 -2
  291. package/core/server/api/endpoints/utils/serializers/input/comments.js +1 -1
  292. package/core/server/api/endpoints/utils/serializers/input/pages.js +3 -3
  293. package/core/server/api/endpoints/utils/serializers/input/posts.js +3 -3
  294. package/core/server/api/endpoints/utils/serializers/input/tags.js +1 -1
  295. package/core/server/api/endpoints/utils/serializers/input/users.js +2 -2
  296. package/core/server/api/endpoints/utils/serializers/input/utils/url.js +31 -13
  297. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +2 -2
  298. package/core/server/api/endpoints/utils/serializers/output/mappers/tags.js +2 -2
  299. package/core/server/api/endpoints/utils/serializers/output/mappers/users.js +8 -0
  300. package/core/server/api/endpoints/utils/serializers/output/utils/url.js +18 -11
  301. package/core/server/data/migrations/versions/6.56/2026-08-03-19-27-51-add-automation-run-step-id-to-automated-email-recipients.js +8 -0
  302. package/core/server/data/migrations/versions/6.56/2026-08-04-14-42-39-restrict-notification-add-permission-to-administrators.js +12 -0
  303. package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +74 -0
  304. package/core/server/data/migrations/versions/6.57/2026-08-04-18-54-14-reset-automation-email-analytics.js +25 -0
  305. package/core/server/data/schema/commands.js +21 -5
  306. package/core/server/data/schema/fixtures/fixtures.json +2 -2
  307. package/core/server/data/schema/schema.js +25 -5
  308. package/core/server/lib/common/to-plain.ts +1 -1
  309. package/core/server/models/post.js +12 -3
  310. package/core/server/services/adapter-manager/adapter-manager.js +1 -45
  311. package/core/server/services/adapter-manager/adapter-manager.ts +2 -48
  312. package/core/server/services/adapter-manager/adapter-paths.js +24 -0
  313. package/core/server/services/adapter-manager/adapter-paths.ts +21 -0
  314. package/core/server/services/adapter-manager/base-classes.js +22 -0
  315. package/core/server/services/adapter-manager/base-classes.ts +22 -0
  316. package/core/server/services/adapter-manager/index.js +4 -27
  317. package/core/server/services/adapter-manager/index.ts +4 -31
  318. package/core/server/services/adapter-manager/utils.js +50 -0
  319. package/core/server/services/adapter-manager/utils.ts +57 -0
  320. package/core/server/services/audience-feedback/audience-feedback-controller.js +1 -1
  321. package/core/server/services/audience-feedback/audience-feedback-service.js +2 -2
  322. package/core/server/services/automations/automations-repository.ts +3 -1
  323. package/core/server/services/automations/database-automations-repository.js +33 -14
  324. package/core/server/services/automations/database-automations-repository.ts +41 -15
  325. package/core/server/services/automations/get-scheduler-idempotency-key.js +13 -0
  326. package/core/server/services/automations/get-scheduler-idempotency-key.ts +8 -0
  327. package/core/server/services/automations/poll.js +3 -1
  328. package/core/server/services/automations/poll.ts +4 -1
  329. package/core/server/services/automations/service.js +7 -3
  330. package/core/server/services/automations/service.ts +7 -3
  331. package/core/server/services/comments/comments-service-emails.js +1 -1
  332. package/core/server/services/comments/comments-service.js +1 -1
  333. package/core/server/services/email-analytics/email-analytics-service-wrapper.js +11 -2
  334. package/core/server/services/email-analytics/email-analytics-service.js +1 -1
  335. package/core/server/services/email-service/email-service-wrapper.js +2 -1
  336. package/core/server/services/email-service/mailgun-email-provider.js +15 -0
  337. package/core/server/services/gift-links/CONTEXT.md +9 -0
  338. package/core/server/services/gifts/CONTEXT.md +55 -0
  339. package/core/server/services/gifts/README.md +28 -0
  340. package/core/server/services/gifts/gift-bookshelf-repository.js +65 -45
  341. package/core/server/services/gifts/gift-bookshelf-repository.ts +203 -74
  342. package/core/server/services/gifts/gift-checkout-adapter.js +59 -0
  343. package/core/server/services/gifts/gift-checkout-offer.js +74 -0
  344. package/core/server/services/gifts/gift-checkout-offer.ts +127 -0
  345. package/core/server/services/gifts/gift-codec.js +63 -0
  346. package/core/server/services/gifts/gift-codec.ts +62 -0
  347. package/core/server/services/gifts/gift-controller.js +6 -31
  348. package/core/server/services/gifts/gift-controller.ts +6 -69
  349. package/core/server/services/gifts/gift-email-service.ts +3 -2
  350. package/core/server/services/gifts/gift-schema.js +34 -0
  351. package/core/server/services/gifts/gift-schema.ts +39 -0
  352. package/core/server/services/gifts/gift-service-wrapper.js +13 -11
  353. package/core/server/services/gifts/gift-service.js +265 -52
  354. package/core/server/services/gifts/gift-service.ts +431 -97
  355. package/core/server/services/gifts/gift.ts +16 -39
  356. package/core/server/services/indexnow-ping/indexnow-ping-service.js +1 -1
  357. package/core/server/services/indexnow-ping/indexnow-ping-service.ts +2 -4
  358. package/core/server/services/lib/mailgun-client.js +3 -3
  359. package/core/server/services/link-tracking/link-click-repository.js +6 -1
  360. package/core/server/services/link-tracking/link-click-tracking-service.js +12 -3
  361. package/core/server/services/media-inliner/external-media-inliner.js +12 -8
  362. package/core/server/services/member-attribution/url-translator.js +5 -7
  363. package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +4 -3
  364. package/core/server/services/member-welcome-emails/service.js +79 -15
  365. package/core/server/services/members/api.js +2 -3
  366. package/core/server/services/members/import-export/import/completion-email.js +8 -4
  367. package/core/server/services/members/import-export/import/completion-email.ts +9 -4
  368. package/core/server/services/members/import-export/import/importer.js +25 -4
  369. package/core/server/services/members/import-export/import/importer.ts +27 -6
  370. package/core/server/services/members/import-export/import/row.ts +5 -1
  371. package/core/server/services/members/import-export/index.js +6 -2
  372. package/core/server/services/members/import-export/index.ts +10 -4
  373. package/core/server/services/members/members-api/controllers/router-controller.js +38 -36
  374. package/core/server/services/members/members-api/members-api.js +13 -7
  375. package/core/server/services/members/members-api/repositories/event-repository.js +4 -94
  376. package/core/server/services/members/members-api/services/member-bread-service.js +3 -3
  377. package/core/server/services/members/members-api/services/payments-service.js +6 -68
  378. package/core/server/services/members-custom-fields/queries.js +4 -13
  379. package/core/server/services/members-custom-fields/queries.ts +4 -13
  380. package/core/server/services/members-custom-fields/schema.js +19 -21
  381. package/core/server/services/members-custom-fields/schema.ts +18 -20
  382. package/core/server/services/members-custom-fields/storage.js +94 -27
  383. package/core/server/services/members-custom-fields/storage.ts +114 -39
  384. package/core/server/services/members-custom-fields/values-service.js +98 -103
  385. package/core/server/services/members-custom-fields/values-service.ts +116 -114
  386. package/core/server/services/mentions/resource-service.js +1 -1
  387. package/core/server/services/mentions/service.js +2 -2
  388. package/core/server/services/oembed/oembed-service.js +44 -3
  389. package/core/server/services/posts/posts-service.js +11 -0
  390. package/core/server/services/route-settings/dynamic-routing-service.js +10 -62
  391. package/core/server/services/route-settings/route-settings-parser.js +25 -25
  392. package/core/server/services/route-settings/route-settings-parser.ts +27 -27
  393. package/core/server/services/route-settings/validation-errors.js +68 -4
  394. package/core/server/services/route-settings/validation-errors.ts +79 -4
  395. package/core/server/services/slack-ping/slack-ping-service.js +2 -2
  396. package/core/server/services/slack-ping/slack-ping-service.ts +3 -5
  397. package/core/server/services/stats/content-stats-service.js +3 -3
  398. package/core/server/services/stats/posts-stats-service.js +29 -25
  399. package/core/server/services/stripe/services/webhook/charge-refunded-event-service.js +1 -1
  400. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +16 -4
  401. package/core/server/services/stripe/stripe-api.js +10 -0
  402. package/core/server/services/url/config.js +90 -135
  403. package/core/server/services/url/index.js +6 -49
  404. package/core/server/services/url/lazy-find-resource.js +14 -11
  405. package/core/server/services/url/lazy-find-resource.ts +14 -11
  406. package/core/server/services/url/lazy-url-service.js +174 -94
  407. package/core/server/services/url/lazy-url-service.ts +233 -97
  408. package/core/server/services/url/routable-resources.js +62 -71
  409. package/core/server/services/url/router-filter.js +12 -4
  410. package/core/server/services/url/router-filter.ts +12 -4
  411. package/core/server/services/webhooks/index.js +1 -1
  412. package/core/server/services/webhooks/serialize.js +2 -1
  413. package/core/server/web/gift-preview/controller.js +10 -25
  414. package/core/shared/config/env/config.testing-mysql.json +1 -2
  415. package/core/shared/config/env/config.testing.json +1 -2
  416. package/core/shared/events/index.js +1 -2
  417. package/core/shared/labs.js +4 -4
  418. package/package.json +10 -10
  419. package/pnpm-lock.yaml +27 -20
  420. package/pnpm-workspace.yaml +3 -2
  421. package/tsconfig.json +1 -0
  422. package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
  423. package/core/built/admin/assets/Combination-CJG7Flp-.js +0 -41
  424. package/core/built/admin/assets/activitypub/layout-qx-bUL1k.js +0 -1
  425. package/core/built/admin/assets/automated-emails-DRy7qY-J.js +0 -7
  426. package/core/built/admin/assets/checkbox-CWa28lgO.js +0 -1
  427. package/core/built/admin/assets/chunk.524.cffd399783a519e404a9.js +0 -25
  428. package/core/built/admin/assets/chunk.582.eebf0d0b2d202bb746a5.js +0 -27
  429. package/core/built/admin/assets/code-editor-view-CCr0rBWS.js +0 -1
  430. package/core/built/admin/assets/comments-DyUSMUdj.js +0 -1
  431. package/core/built/admin/assets/disabled-sources-indicator-CUxMfiPa.js +0 -1
  432. package/core/built/admin/assets/dist-2Tnz2Yfq.js +0 -1
  433. package/core/built/admin/assets/dist-CjY-f5Z1.js +0 -1
  434. package/core/built/admin/assets/dist-DDY6GRVz.js +0 -1
  435. package/core/built/admin/assets/dist-DG78yroX.js +0 -23
  436. package/core/built/admin/assets/domain-C0bV2UV8.js +0 -1
  437. package/core/built/admin/assets/editor-Dp865O-4.js +0 -8
  438. package/core/built/admin/assets/esm-CFX93H5M.js +0 -2
  439. package/core/built/admin/assets/filter-sources-BV2rnpfA.js +0 -1
  440. package/core/built/admin/assets/filters-CUXJ2Nm9.js +0 -1
  441. package/core/built/admin/assets/filters-DfbIWc_l.js +0 -1
  442. package/core/built/admin/assets/index-9Cw8z3rA.css +0 -1
  443. package/core/built/admin/assets/index-BGG7NckO.js +0 -67
  444. package/core/built/admin/assets/input-group-BSgG_lVw.js +0 -1
  445. package/core/built/admin/assets/label-picker-1DvSuLA1.js +0 -1
  446. package/core/built/admin/assets/labels-CDSUt4J_.js +0 -1
  447. package/core/built/admin/assets/layout-yQEi2qDM.js +0 -1
  448. package/core/built/admin/assets/link-helpers-7EYqvvHs.js +0 -1
  449. package/core/built/admin/assets/member-custom-fields-yOMHyAg9.js +0 -1
  450. package/core/built/admin/assets/member-detail-BAq7gDaa.js +0 -1
  451. package/core/built/admin/assets/members-CQpVFJu4.js +0 -10
  452. package/core/built/admin/assets/modals-ReORvWnu.js +0 -31
  453. package/core/built/admin/assets/newsletter-CJQvtHqN.js +0 -1
  454. package/core/built/admin/assets/newsletters-B3QQXfGT.js +0 -1
  455. package/core/built/admin/assets/nql-BsgFMd93.js +0 -1
  456. package/core/built/admin/assets/overview-Bf2sbznq.js +0 -1
  457. package/core/built/admin/assets/overview-CBfePxcr.js +0 -1
  458. package/core/built/admin/assets/post-analytics-header-Bmc9su6q.js +0 -1
  459. package/core/built/admin/assets/preferences-DJPfHEGu.js +0 -5
  460. package/core/built/admin/assets/profile-DqBQp-gT.js +0 -1
  461. package/core/built/admin/assets/select-C5_xEt5b.js +0 -1
  462. package/core/built/admin/assets/settings-BwiVIXau.js +0 -4
  463. package/core/built/admin/assets/social-urls-BQPFEk1_.js +0 -8
  464. package/core/built/admin/assets/switch-z5Y6Uzt6.js +0 -1
  465. package/core/built/admin/assets/tabs-CCEgKYzq.js +0 -1
  466. package/core/built/admin/assets/tags-Cg2VJGmG.js +0 -1
  467. package/core/built/admin/assets/textarea-Bjccvh_E.js +0 -1
  468. package/core/built/admin/assets/toggle-group-b3IlxvhV.js +0 -1
  469. package/core/built/admin/assets/web-B_5GZRvH.js +0 -1
  470. package/core/server/services/gifts/gift-repository.js +0 -2
  471. package/core/server/services/gifts/gift-repository.ts +0 -29
  472. package/core/server/services/route-settings/activation-bridge.js +0 -174
  473. package/core/server/services/route-settings/activation-bridge.ts +0 -250
  474. package/core/server/services/url/local-file-cache.js +0 -75
  475. package/core/server/services/url/queue.js +0 -260
  476. package/core/server/services/url/resource.js +0 -102
  477. package/core/server/services/url/resources.js +0 -480
  478. package/core/server/services/url/url-generator.js +0 -276
  479. package/core/server/services/url/url-service-facade.js +0 -366
  480. package/core/server/services/url/url-service-facade.ts +0 -519
  481. package/core/server/services/url/url-service.js +0 -387
  482. package/core/server/services/url/urls.js +0 -140
  483. package/core/shared/events/url-resource-updated-event.js +0 -33
@@ -3,21 +3,21 @@ 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 logging = require('@tryghost/logging');
6
7
  const localUtils = require('../../../shared/url-utils').default;
7
8
  const { matchPermalink, toLookupParams } = require('./permalink-matcher');
8
9
  const { buildFilter, filterMatches, routerTypeOf } = require('./router-filter');
10
+ const { fetchRoutableResources: defaultFetchRoutableResources } = require('./routable-resources');
9
11
  const urlConfig = require('./config');
10
- // Per-resource-type base filters, mirroring eager's resource-fetch filters
11
- // (`modelOptions.filter` in services/url/config.js). Deliberately duplicated
12
- // rather than imported: lazy is meant to replace eager, at which point eager's
13
- // config goes away and this becomes the single source. `fields` lists the
14
- // record columns each filter reads; a resource that reaches URL generation
15
- // must carry them (a thin one is rejected — see _assertBaseFieldsPresent).
12
+ // Per-resource-type gate on which rows get a URL at all — the forward-lookup
13
+ // counterpart of the fetch filters in routable-resources.js. `fields` lists
14
+ // the record columns each filter reads; a resource that reaches URL generation
15
+ // must carry them (see _missingBaseFields).
16
16
  //
17
17
  // Authors are intentionally absent: users.visibility is schema-pinned to
18
- // 'public' (isIn: [['public']]), so eager's visibility:public author filter
19
- // never excludes anyone — every author is routable, and serialized authors
20
- // drop visibility anyway (#10438).
18
+ // 'public' (isIn: [['public']]), so the visibility:public author filter never
19
+ // excludes anyone — every author is routable, and serialized authors drop
20
+ // visibility anyway (#10438).
21
21
  const BASE_FILTERS = {
22
22
  posts: { filter: 'status:published+type:post', fields: ['status', 'type'] },
23
23
  pages: { filter: 'status:published+type:page', fields: ['status', 'type'] },
@@ -33,19 +33,18 @@ function buildBaseFilters() {
33
33
  }
34
34
  return baseFilters;
35
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.
36
+ // Columns a router filter must not see (the `exclude` lists in
37
+ // services/url/config.js), keyed by resourceType. Ghost previously answered
38
+ // URLs from a precomputed cache that dropped these columns, so a filter
39
+ // referencing one matched it as absent NQL reads absent as null. This
40
+ // service loads full records and would see the real value, so it strips them
41
+ // before evaluating router filters (and neither requires nor force-loads
42
+ // them), keeping routes.yaml files that rely on that behaviour working. The
43
+ // base filter above is unaffected it runs against the full record.
45
44
  function buildExcludedFilterFields() {
46
45
  const excluded = new Map();
47
46
  for (const entry of urlConfig) {
48
- const exclude = entry?.modelOptions?.exclude;
47
+ const exclude = entry?.exclude;
49
48
  if (Array.isArray(exclude)) {
50
49
  excluded.set(entry.type, new Set(exclude));
51
50
  }
@@ -53,10 +52,18 @@ function buildExcludedFilterFields() {
53
52
  return excluded;
54
53
  }
55
54
  const EMPTY_FIELD_SET = new Set();
55
+ // 1, 10, 100, ... — exact for integers, unlike a log10 comparison.
56
+ function isPowerOfTen(n) {
57
+ let power = 1;
58
+ while (power < n) {
59
+ power *= 10;
60
+ }
61
+ return power === n;
62
+ }
56
63
  // Relation roots are loaded via getRequiredRelations (as withRelated), not as
57
64
  // scalar columns; `page`/`type` are the router-type discriminator, always set
58
65
  // on the resource. Everything else a router filter references is a scalar
59
- // own-column the resource must carry to be routed like eager.
66
+ // own-column the resource must carry to be routed.
60
67
  const FILTER_NON_SCALAR_FIELDS = new Set([
61
68
  'tag', 'tags', 'author', 'authors', 'primary_tag', 'primary_author', 'page', 'type'
62
69
  ]);
@@ -85,20 +92,27 @@ function filterScalarFields(filter) {
85
92
  }
86
93
  const ROUTER_TYPE_TO_DB_TYPE = { posts: 'post', pages: 'page' };
87
94
  /**
88
- * On-demand replacement for the eager UrlService: computes URLs and ownership
89
- * per call from the registered router configs instead of precomputing a full
90
- * map at boot. Forward lookups are pure; resolveUrl is the only DB-touching
91
- * path, and only through the injected findResource hook.
95
+ * Ghost's URL service: computes URLs and ownership per call from the
96
+ * registered router configs. Forward lookups are pure; resolveUrl and
97
+ * getRoutableResources are the only DB-touching paths, and only through the
98
+ * injected findResource / fetchRoutableResources hooks.
92
99
  */
93
100
  class LazyUrlService {
94
101
  urlUtils;
95
102
  findResource;
103
+ fetchRoutableResources;
96
104
  // Router configs in registration order, which is also their priority.
97
105
  routerConfigs;
98
106
  requiredRelations;
99
107
  baseFilters;
100
108
  excludedFilterFields;
101
- constructor({ urlUtils = localUtils, findResource }) {
109
+ // True once routers have been registered. Cleared by reset() so the
110
+ // route-reload window re-gates the maintenance middleware.
111
+ routersReady;
112
+ // How many times each thin-resource cause has been seen — see
113
+ // _degradeThinResource.
114
+ reportedThinResources;
115
+ constructor({ urlUtils = localUtils, findResource, fetchRoutableResources = defaultFetchRoutableResources }) {
102
116
  if (typeof findResource !== 'function') {
103
117
  throw new errors.IncorrectUsageError({
104
118
  message: 'LazyUrlService requires a findResource function'
@@ -106,10 +120,13 @@ class LazyUrlService {
106
120
  }
107
121
  this.urlUtils = urlUtils;
108
122
  this.findResource = findResource;
123
+ this.fetchRoutableResources = fetchRoutableResources;
109
124
  this.routerConfigs = [];
110
125
  this.requiredRelations = null;
111
126
  this.baseFilters = buildBaseFilters();
112
127
  this.excludedFilterFields = buildExcludedFilterFields();
128
+ this.routersReady = false;
129
+ this.reportedThinResources = new Map();
113
130
  }
114
131
  onRouterAddedType(identifier, filter, resourceType, permalink) {
115
132
  debug('onRouterAddedType', identifier, resourceType, permalink, filter);
@@ -121,6 +138,7 @@ class LazyUrlService {
121
138
  compiledFilter: buildFilter(filter)
122
139
  });
123
140
  this.requiredRelations = null;
141
+ this.routersReady = true;
124
142
  }
125
143
  onRouterUpdated() {
126
144
  // Defensive: a router update could change a filter the cache derived
@@ -130,6 +148,8 @@ class LazyUrlService {
130
148
  reset() {
131
149
  this.routerConfigs = [];
132
150
  this.requiredRelations = null;
151
+ this.routersReady = false;
152
+ this.reportedThinResources.clear();
133
153
  }
134
154
  getRequiredRelations() {
135
155
  if (this.requiredRelations !== null) {
@@ -157,10 +177,10 @@ class LazyUrlService {
157
177
  this.requiredRelations = [...required];
158
178
  return [...this.requiredRelations];
159
179
  }
160
- // Columns a resource of this type must carry for the lazy service to build
180
+ // Columns a resource of this type must carry for the service to build
161
181
  // its URL: its base-filter columns plus the scalar columns its routers'
162
182
  // permalinks substitute and filters read. Relations are covered separately
163
- // by getRequiredRelations; eager needs none of this (it looks URLs up by id).
183
+ // by getRequiredRelations.
164
184
  getRequiredFields(routerType) {
165
185
  const fields = new Set();
166
186
  const excluded = this._excludedFilterFieldsFor(routerType);
@@ -192,9 +212,10 @@ class LazyUrlService {
192
212
  fields.add(computed);
193
213
  }
194
214
  }
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).
215
+ // Skip the columns a router filter must not see: their filters are
216
+ // evaluated against an absent value, so requiring them would
217
+ // force-load a column that can't affect the result (see
218
+ // buildExcludedFilterFields).
198
219
  filterScalarFields(config.filter).forEach((field) => {
199
220
  if (!excluded.has(field)) {
200
221
  fields.add(field);
@@ -204,40 +225,62 @@ class LazyUrlService {
204
225
  return [...fields];
205
226
  }
206
227
  hasFinished() {
207
- return true;
228
+ return this.routersReady;
229
+ }
230
+ /**
231
+ * All routable rows of a type. The columns URL computation needs come
232
+ * from the active routing config, so the rows are never thin for it;
233
+ * callers name any extra columns they want back.
234
+ */
235
+ async getRoutableResources(type, { columns = [] } = {}) {
236
+ return this.fetchRoutableResources(type, {
237
+ columns,
238
+ requiredFields: this.getRequiredFields(type),
239
+ requiredRelations: this.getRequiredRelations()
240
+ });
208
241
  }
242
+ /**
243
+ * A thin resource — one missing a column its base filter or a router
244
+ * filter reads — is a caller bug: we cannot tell whether it routes. It is
245
+ * reported and degraded to /404/ rather than thrown, because 500ing a page
246
+ * that does route is worse than a 404 on one that may not.
247
+ */
209
248
  getUrlForResource(resource, options = {}) {
210
249
  const routerType = routerTypeOf(resource);
211
250
  if (!routerType) {
212
- return this._formatNotFound(options);
251
+ return this.notFoundUrl(options);
213
252
  }
214
253
  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).
254
+ // The router (collection) filter is evaluated against the reduced
255
+ // column set (see buildExcludedFilterFields); the base filter keeps the
256
+ // full record, since it reads status / type / visibility.
218
257
  const filterRecord = this._recordForRouterFilter(record, routerType);
219
- // Eager only builds URLs for resources that pass the per-type base
220
- // filter (e.g. visibility:public tags, status:published posts), so a
221
- // resource failing it has no URL there and must 404 here too. Checked
222
- // only when a router for the type exists, since otherwise the resource
223
- // 404s regardless.
258
+ // Only resources that pass the per-type base filter (visibility:public
259
+ // tags, status:published posts) get a URL. Checked only when a router
260
+ // for the type exists, since otherwise the resource 404s regardless.
224
261
  if (this._hasRouterForType(routerType)) {
225
- this._assertBaseFieldsPresent(routerType, resource);
262
+ const missing = this._missingBaseFields(routerType, resource);
263
+ if (missing) {
264
+ return this._degradeThinResource(resource, missing, options);
265
+ }
226
266
  if (!this._baseFilterMatches(routerType, record)) {
227
- return this._formatNotFound(options);
267
+ return this.notFoundUrl(options);
228
268
  }
229
269
  }
230
270
  for (const config of this.routerConfigs) {
231
271
  if (config.resourceType !== routerType) {
232
272
  continue;
233
273
  }
234
- this._assertNotThin(config, resource, routerType);
274
+ const missing = this._missingRouterFields(config, resource, routerType);
275
+ if (missing) {
276
+ return this._degradeThinResource(resource, missing, options);
277
+ }
235
278
  if (filterMatches(config.compiledFilter, filterRecord)) {
236
279
  const path = this.urlUtils.replacePermalink(config.permalink, resource);
237
280
  return this._formatPath(path, options);
238
281
  }
239
282
  }
240
- return this._formatNotFound(options);
283
+ return this.notFoundUrl(options);
241
284
  }
242
285
  ownsResource(routerIdentifier, resource) {
243
286
  if (!resource) {
@@ -247,15 +290,15 @@ class LazyUrlService {
247
290
  if (!routerType) {
248
291
  return false;
249
292
  }
250
- // A resource failing its base filter is not in eager's map, so no
251
- // router owns it. Mirrors the base-filter gate in getUrlForResource.
293
+ // A resource failing its base filter has no URL, so no router owns it.
294
+ // Mirrors the base-filter gate in getUrlForResource.
252
295
  const record = this._recordForFilter(resource);
253
296
  if (!this._baseFilterMatches(routerType, record)) {
254
297
  return false;
255
298
  }
256
299
  const filterRecord = this._recordForRouterFilter(record, routerType);
257
300
  // Ownership is exclusive: only the first matching router of the type
258
- // owns the resource, matching eager's reservation.
301
+ // owns the resource.
259
302
  const owner = this.routerConfigs.find(c => c.resourceType === routerType && filterMatches(c.compiledFilter, filterRecord));
260
303
  return !!owner && owner.identifier === routerIdentifier;
261
304
  }
@@ -299,11 +342,11 @@ class LazyUrlService {
299
342
  }
300
343
  return null;
301
344
  }
302
- // Eager only resolves a URL that equals a resource's generated (canonical)
303
- // URL, so we regenerate the record's URL for this permalink and confirm the
304
- // captured params match it. Without this, derived/relation segments the
305
- // query can't filter on (year/month, primary_tag) would resolve any slug,
306
- // 200-ing a URL the eager service 404s.
345
+ // Only a URL that equals the resource's own generated (canonical) URL
346
+ // resolves, so we regenerate the record's URL for this permalink and
347
+ // confirm the captured params match it. Without this, derived/relation
348
+ // segments the query can't filter on (year/month, primary_tag) would
349
+ // resolve any slug, 200-ing a URL that has no page.
307
350
  _matchesCanonicalUrl(config, params, resource) {
308
351
  const canonicalPath = this.urlUtils.replacePermalink(config.permalink, resource);
309
352
  const canonicalParams = matchPermalink(config.permalink, canonicalPath);
@@ -314,6 +357,50 @@ class LazyUrlService {
314
357
  const canonical = canonicalParams;
315
358
  return Object.keys(captured).every(key => canonical[key] === captured[key]);
316
359
  }
360
+ // Reports which columns were missing alongside what the caller handed over
361
+ // — the resource's shape, and what the active routing config needs — so
362
+ // the fetch that under-fetched can be found from the log line alone.
363
+ //
364
+ // Reported on the first occurrence of each distinct cause, then at each
365
+ // order of magnitude. A caller that under-fetches does so for every row it
366
+ // serializes, so a browse over a large collection would otherwise emit one
367
+ // error per row — but reporting only once would hide that the problem is
368
+ // still happening, and how widely. `occurrences` carries that instead.
369
+ // Cleared on reset(), since a new routing config can make a previously
370
+ // thin resource fine (or newly broken).
371
+ _degradeThinResource(resource, thin, options) {
372
+ const producedBy = options.serializerContext;
373
+ // The producer is part of the key: two endpoints under-fetching the
374
+ // same way are two bugs, and the endpoint set is small enough that
375
+ // this cannot flood.
376
+ const key = [
377
+ thin.resourceType,
378
+ thin.routerIdentifier ?? '',
379
+ thin.missing.join(','),
380
+ producedBy ? `${producedBy.apiType}:${producedBy.docName}:${producedBy.method}` : ''
381
+ ].join('|');
382
+ const occurrences = (this.reportedThinResources.get(key) ?? 0) + 1;
383
+ this.reportedThinResources.set(key, occurrences);
384
+ if (!isPowerOfTen(occurrences)) {
385
+ return this.notFoundUrl(options);
386
+ }
387
+ logging.error(new errors.InternalServerError({
388
+ message: 'URL service could not build a URL, degraded to /404/',
389
+ code: 'LAZY_URL_RESOLUTION_ERROR',
390
+ errorDetails: {
391
+ method: 'getUrlForResource',
392
+ type: resource.type,
393
+ id: resource.id,
394
+ status: resource.status,
395
+ resourceKeys: Object.keys(resource),
396
+ requiredRelations: this.getRequiredRelations(),
397
+ occurrences,
398
+ ...(producedBy ? { serializer: producedBy } : {}),
399
+ ...thin
400
+ }
401
+ }));
402
+ return this.notFoundUrl(options);
403
+ }
317
404
  _hasRouterForType(routerType) {
318
405
  return this.routerConfigs.some(config => config.resourceType === routerType);
319
406
  }
@@ -326,32 +413,23 @@ class LazyUrlService {
326
413
  }
327
414
  // A resource that reaches URL generation must carry the columns its base
328
415
  // filter reads (status for posts/pages, visibility for tags) — production
329
- // callers always do (full models, or forced by the serializers). Without
330
- // them the filter can't be evaluated and we'd silently 404 a URL eager would
331
- // have produced, so refuse loudly instead of guessing.
332
- _assertBaseFieldsPresent(routerType, resource) {
416
+ // callers always do, via full models or the serializers' force-load.
417
+ // Without them the filter can't be evaluated at all, so we cannot tell a
418
+ // genuine miss from a caller that under-fetched.
419
+ _missingBaseFields(routerType, resource) {
333
420
  const base = this.baseFilters.get(routerType);
334
421
  if (!base) {
335
- return;
422
+ return null;
336
423
  }
337
424
  const r = resource;
338
425
  const missing = base.fields.filter(field => r[field] === undefined);
339
426
  if (missing.length === 0) {
340
- return;
427
+ return null;
341
428
  }
342
- throw new errors.InternalServerError({
343
- message: 'Thin resource passed to LazyUrlService.getUrlForResource',
344
- code: 'LAZY_URL_THIN_RESOURCE',
345
- errorDetails: {
346
- resourceType: routerType,
347
- resourceId: resource.id,
348
- baseFilter: base.filter,
349
- missing
350
- }
351
- });
429
+ return { resourceType: routerType, baseFilter: base.filter, missing };
352
430
  }
353
431
  // Normalizes the plural router type to the singular DB value for filter
354
- // evaluation only, so page: filters match as in the eager generator.
432
+ // evaluation only, so `page:` filters in routes.yaml match.
355
433
  _recordForFilter(resource) {
356
434
  const record = resource;
357
435
  const dbType = ROUTER_TYPE_TO_DB_TYPE[record.type];
@@ -360,8 +438,7 @@ class LazyUrlService {
360
438
  _excludedFilterFieldsFor(routerType) {
361
439
  return this.excludedFilterFields.get(routerType) ?? EMPTY_FIELD_SET;
362
440
  }
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
441
+ // Strips the columns a router/collection filter must not see, so an
365
442
  // excluded column reads as absent (→ null in NQL) rather than its real
366
443
  // value. Only affects filters that reference an excluded column; every
367
444
  // other filter sees an identical record.
@@ -387,8 +464,16 @@ class LazyUrlService {
387
464
  }
388
465
  return path;
389
466
  }
390
- // Mirrors the eager miss path: the /404/ fallback carries no subdirectory.
391
- _formatNotFound(options) {
467
+ /**
468
+ * The /404/ a resource gets when nothing routes it, formatted for the given
469
+ * options. Deliberately omits the argument `_formatPath` passes.
470
+ *
471
+ * The subdirectory is not lost by that omission: `createUrl` takes it from
472
+ * its own base whenever the url is relative, so a subdirectory install gets
473
+ * `/blog/404/` from here either way. The third argument `_formatPath`
474
+ * passes is `trailingSlash`, and `/404/` already ends in one.
475
+ */
476
+ notFoundUrl(options = {}) {
392
477
  if (options.absolute) {
393
478
  return this.urlUtils.createUrl('/404/', options.absolute);
394
479
  }
@@ -398,13 +483,13 @@ class LazyUrlService {
398
483
  return '/404/';
399
484
  }
400
485
  // A filtered router that references a relation the resource doesn't carry
401
- // can't be evaluated: lazy would 404 here while eager (full data in memory)
402
- // returns a URL. Callers must hand the service fully-inflated resources, so
403
- // a thin one is a programmer error we refuse loudly rather than mask as a
404
- // silent /404/.
405
- _assertNotThin(config, resource, routerType) {
486
+ // can't be evaluated, so the resource would 404 for a reason that has
487
+ // nothing to do with the routing config. Callers must hand over
488
+ // fully-inflated resources; a thin one is reported rather than silently
489
+ // 404'd (see getUrlForResource).
490
+ _missingRouterFields(config, resource, routerType) {
406
491
  if (!config.filter) {
407
- return;
492
+ return null;
408
493
  }
409
494
  const r = resource;
410
495
  const missing = [];
@@ -420,8 +505,8 @@ class LazyUrlService {
420
505
  if (/\bprimary_author\b/.test(config.filter) && r.primary_author === undefined) {
421
506
  missing.push('primary_author');
422
507
  }
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.
508
+ // Excluded columns are never required — their filters match them as
509
+ // absent, so a resource lacking one is not thin here.
425
510
  const excluded = this._excludedFilterFieldsFor(routerType);
426
511
  for (const field of filterScalarFields(config.filter)) {
427
512
  if (!excluded.has(field) && r[field] === undefined) {
@@ -429,19 +514,14 @@ class LazyUrlService {
429
514
  }
430
515
  }
431
516
  if (missing.length === 0) {
432
- return;
433
- }
434
- throw new errors.InternalServerError({
435
- message: 'Thin resource passed to LazyUrlService.getUrlForResource',
436
- code: 'LAZY_URL_THIN_RESOURCE',
437
- errorDetails: {
438
- resourceType: routerType,
439
- resourceId: resource.id,
440
- routerIdentifier: config.identifier,
441
- filter: config.filter,
442
- missing
443
- }
444
- });
517
+ return null;
518
+ }
519
+ return {
520
+ resourceType: routerType,
521
+ routerIdentifier: config.identifier,
522
+ filter: config.filter,
523
+ missing
524
+ };
445
525
  }
446
526
  }
447
527
  exports.LazyUrlService = LazyUrlService;