ghost 6.56.0 → 6.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (385) 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-html-to-lexical-1.3.3.tgz +0 -0
  6. package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
  7. package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
  8. package/core/app.js +1 -1
  9. package/core/boot.js +21 -25
  10. package/core/bridge.js +10 -12
  11. package/core/built/admin/assets/Combination-BDvCdg1t.js +41 -0
  12. package/core/built/admin/assets/about-C7O-AgRZ.js +1 -0
  13. package/core/built/admin/assets/{account-migration-D3O_yVck.js → account-migration-Cv_ol1ko.js} +1 -1
  14. package/core/built/admin/assets/action-list-lGzZLHc8.js +1 -0
  15. package/core/built/admin/assets/activitypub/{account-migration-D8FbP-Z-.js → account-migration-DHFbePl6.js} +7 -7
  16. package/core/built/admin/assets/activitypub/activitypub.js +3 -3
  17. package/core/built/admin/assets/activitypub/{avatar-11EA1VSs.js → avatar-CT4tS2_S.js} +2 -2
  18. package/core/built/admin/assets/activitypub/{avatar-flipboard-jfbLG-xe.js → avatar-flipboard-ByYWKipo.js} +2 -2
  19. package/core/built/admin/assets/activitypub/{bluesky-sharing-f1jTQe2P.js → bluesky-sharing-srVnSHDz.js} +8 -8
  20. package/core/built/admin/assets/activitypub/{content-formatters-nmTOPY9M.js → content-formatters-C2sos5Ht.js} +3 -3
  21. package/core/built/admin/assets/activitypub/{copy-BCwo9ep3.js → copy-B1UBTOoZ.js} +2 -2
  22. package/core/built/admin/assets/activitypub/{dist-BEfX-0aW.js → dist-MPsb2WZ1.js} +4 -4
  23. package/core/built/admin/assets/activitypub/{domain-AaDlZlrA.js → domain-DzcM9Dzo.js} +9 -9
  24. package/core/built/admin/assets/activitypub/{edit-profile-DPi8Tl5M.js → edit-profile-CsQ_4xAk.js} +5 -5
  25. package/core/built/admin/assets/activitypub/{explore-CxTzjILQ.js → explore-CJe0DZBK.js} +8 -8
  26. package/core/built/admin/assets/activitypub/{feed-DPv5x2MX.js → feed-CKV3qWON.js} +9 -9
  27. package/core/built/admin/assets/activitypub/{field-DbgA8lat.js → field-DckHfDJf.js} +3 -3
  28. package/core/built/admin/assets/activitypub/{hash-BWuZoYkA.js → hash-Dlp8RM55.js} +2 -2
  29. package/core/built/admin/assets/activitypub/{inbox-35VDQ2K2.js → inbox-CsPy4R0M.js} +2 -2
  30. package/core/built/admin/assets/activitypub/{inbox-DSrF_i1O.js → inbox-DAsjzUUe.js} +10 -10
  31. package/core/built/admin/assets/activitypub/{label-CPjFBbo7.js → label-DWyo9E5T.js} +3 -3
  32. package/core/built/admin/assets/activitypub/layout-DM_3pqsU.js +1 -0
  33. package/core/built/admin/assets/activitypub/{moderation-BkDC1XVZ.js → moderation-B0Inc03M.js} +7 -7
  34. package/core/built/admin/assets/activitypub/{note-xEyH4xT-.js → note-CSE54lER.js} +7 -7
  35. package/core/built/admin/assets/activitypub/{notifications-DClaL3ui.js → notifications-RSwbKqdh.js} +9 -9
  36. package/core/built/admin/assets/activitypub/{preferences-DpnOa-4Z.js → preferences-CE231n2_.js} +11 -11
  37. package/core/built/admin/assets/activitypub/{profile-Dvi9mz-6.js → profile-aaQ1j1Eo.js} +13 -13
  38. package/core/built/admin/assets/activitypub/{reply-Cs9ySd6Z.js → reply-Cjb0Vs3m.js} +2 -2
  39. package/core/built/admin/assets/activitypub/{routes-DFBeTnfn.js → routes-Djb0mc9k.js} +26 -26
  40. package/core/built/admin/assets/activitypub/{separator-C5u_VjzY.js → separator-D8pROI5n.js} +3 -3
  41. package/core/built/admin/assets/activitypub/{settings-CV2fFu6D.js → settings-DMc32IQl.js} +5 -5
  42. package/core/built/admin/assets/activitypub/{site-_TgDd_EM.js → site-DVIcsohX.js} +4 -4
  43. package/core/built/admin/assets/activitypub/{step-1-BfnNL2oF.js → step-1-9Yey9usF.js} +5 -5
  44. package/core/built/admin/assets/activitypub/{step-2-DLndZkO-.js → step-2-DdYmKRXV.js} +6 -6
  45. package/core/built/admin/assets/activitypub/{step-3-A-PZTtRE.js → step-3-hdSkcjZU.js} +10 -10
  46. package/core/built/admin/assets/activitypub/{tabs-BUbz7tea.js → tabs-CUqseYzQ.js} +6 -6
  47. package/core/built/admin/assets/activitypub/{topic-filter-BdX_xYYT.js → topic-filter-Coni6OXM.js} +3 -3
  48. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-EwuRemHh.js → use-activity-pub-queries-MeHTPMQv.js} +2 -2
  49. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-cy4ACBc4.js → use-navigate-with-base-path-B046AM38.js} +24 -7
  50. package/core/built/admin/assets/activitypub/{use-reply-chain-data-CyFcGfan.js → use-reply-chain-data-1AO-Xob6.js} +5 -5
  51. package/core/built/admin/assets/activitypub/{x-7zk8mV2p.js → x-BRfRYd16.js} +2 -2
  52. package/core/built/admin/assets/add-integration-modal-m9Dv6f77.js +1 -0
  53. package/core/built/admin/assets/add-newsletter-modal-CPSVZL2H.js +1 -0
  54. package/core/built/admin/assets/add-recommendation-modal-Ci9Z5yat.js +1 -0
  55. package/core/built/admin/assets/analytics-DqSCVV8U.js +1 -0
  56. package/core/built/admin/assets/announcement-bar-modal-DU3avV7d.js +1 -0
  57. package/core/built/admin/assets/api-keys-BK0LylyK.js +1 -0
  58. package/core/built/admin/assets/api-keys-_iCT4dxr.js +1 -0
  59. package/core/built/admin/assets/{audience-CkBb2LlO.js → audience-DTJtXXWY.js} +1 -1
  60. package/core/built/admin/assets/automated-emails-PayPCT8L.js +1 -0
  61. package/core/built/admin/assets/{automations-DhKeS_U5.js → automations-162q5HjL.js} +1 -1
  62. package/core/built/admin/assets/{automations-cMllU545.js → automations-dtQ_MRZ7.js} +1 -1
  63. package/core/built/admin/assets/{bluesky-sharing-DPGpyD-G.js → bluesky-sharing-B8Es3AuV.js} +1 -1
  64. package/core/built/admin/assets/brand-icon-09YqFZjV.js +1 -0
  65. package/core/built/admin/assets/{breadcrumb-DIfcF5ow.js → breadcrumb-tg5zL--z.js} +1 -1
  66. package/core/built/admin/assets/{chart-helpers-DV0FkdcD.js → chart-helpers-CZElTGYL.js} +1 -1
  67. package/core/built/admin/assets/checkbox-Cyo-x7mC.js +1 -0
  68. package/core/built/admin/assets/{chunk.524.87b52f4fada529df2c4c.js → chunk.524.cd5498dd4e195f679266.js} +4 -4
  69. package/core/built/admin/assets/{chunk.582.340bcca873e806cbd4bb.js → chunk.582.6f92ec1f03734bf2ea3f.js} +6 -6
  70. package/core/built/admin/assets/{chunk.332.05991a101d67f732dec3.js → chunk.873.b8fc05a8ab877fb819ed.js} +28 -27
  71. package/core/built/admin/assets/code-editor-view-Dq1rQ4tS.js +1 -0
  72. package/core/built/admin/assets/color-picker-field-BonBgxhU.js +1 -0
  73. package/core/built/admin/assets/color-utils-BsrwXuDz.js +1 -0
  74. package/core/built/admin/assets/combobox-uX5r_paI.js +1 -0
  75. package/core/built/admin/assets/command-DxwBO2pF.js +1 -0
  76. package/core/built/admin/assets/comments-Dn-RO-nU.js +1 -0
  77. package/core/built/admin/assets/components-CwL6a6As-B0E0-NBJ.js +1 -0
  78. package/core/built/admin/assets/confirmation-modal-B6D6mk6R.js +1 -0
  79. package/core/built/admin/assets/content-api-modal-BxQ-7T6u.js +1 -0
  80. package/core/built/admin/assets/copy-field-BAxL4Zqn.js +1 -0
  81. package/core/built/admin/assets/currency-Dy4Tc2R2.js +1 -0
  82. package/core/built/admin/assets/custom-integration-modal---d_l7lc.js +1 -0
  83. package/core/built/admin/assets/{date-range-select-dXc-GsVe.js → date-range-select-PB2n1HZf.js} +1 -1
  84. package/core/built/admin/assets/debounce-Dg2fW5Rg.js +3 -0
  85. package/core/built/admin/assets/design-and-theme-modal-DYRT207a.js +23 -0
  86. package/core/built/admin/assets/dirty-confirm-dialog-BnDJ39ao.js +1 -0
  87. package/core/built/admin/assets/disabled-sources-indicator-iMHrkyV3.js +1 -0
  88. package/core/built/admin/assets/dist-B2PvRwLn.js +1 -0
  89. package/core/built/admin/assets/{dist-pHiOxyxa.js → dist-D5vhMdXf.js} +1 -1
  90. package/core/built/admin/assets/dist-DlCyLrOC.js +1 -0
  91. package/core/built/admin/assets/dist-WeOIvSYM.js +23 -0
  92. package/core/built/admin/assets/{dist-DyViJwIy.js → dist-cFzKLG7y.js} +3 -3
  93. package/core/built/admin/assets/dist-j1xxxnY8.js +1 -0
  94. package/core/built/admin/assets/dist-myQaY0ka.js +1 -0
  95. package/core/built/admin/assets/{domain-B5ogXUBa.js → domain-BR8FIdjD.js} +1 -1
  96. package/core/built/admin/assets/{dropzone-BnFG7dDP.js → dropzone-hNxmUvwD.js} +1 -1
  97. package/core/built/admin/assets/{edit-profile-B07jCZy9.js → edit-profile-CENrkY0l.js} +1 -1
  98. package/core/built/admin/assets/editor-B18D0lm8.js +8 -0
  99. package/core/built/admin/assets/embed-signup-form-modal-BsCYUMvQ.js +9 -0
  100. package/core/built/admin/assets/{explore-Ca1y8HW_.js → explore-BmrnljWw.js} +1 -1
  101. package/core/built/admin/assets/{feed-x4iFMBP4.js → feed-Dy4li6i8.js} +1 -1
  102. package/core/built/admin/assets/{field-ChCAabZg.js → field-Dh_lwk13.js} +1 -1
  103. package/core/built/admin/assets/{filter-sources-LdtH1e-T.js → filter-sources-D2zDuJH5.js} +1 -1
  104. package/core/built/admin/assets/filters-3JBQcNtJ.js +1 -0
  105. package/core/built/admin/assets/{filters-CjVKRP8U.js → filters-CFt_BDe4.js} +1 -1
  106. package/core/built/admin/assets/first-promoter-modal-D6Rjxugw.js +1 -0
  107. package/core/built/admin/assets/format-url-C-t8G6Qd.js +1 -0
  108. package/core/built/admin/assets/{ghost-7f01131115186d1af84ead76e79646dd.js → ghost-d683fbeb945b13155ea669caa5d8db41.js} +300 -298
  109. package/core/built/admin/assets/{gift-link-modal-Buqh2vXh.js → gift-link-modal-zMtlJhLQ.js} +1 -1
  110. package/core/built/admin/assets/{gift-links-CsUhefWG.js → gift-links-CVuqLhsZ.js} +1 -1
  111. package/core/built/admin/assets/global-data-provider-B949LDXJ.js +1 -0
  112. package/core/built/admin/assets/grid-CUciBfjf.js +1 -0
  113. package/core/built/admin/assets/{growth-Dy2UOSay.js → growth-C38Xtp8D.js} +2 -2
  114. package/core/built/admin/assets/{growth-DajOBdN0.js → growth-CFws67_i.js} +1 -1
  115. package/core/built/admin/assets/history-modal-ILAOkbxH.js +1 -0
  116. package/core/built/admin/assets/{hooks-CixO01lL.js → hooks-D6EE7BIt.js} +2 -2
  117. package/core/built/admin/assets/html-field-9a9Id_2q.js +1 -0
  118. package/core/built/admin/assets/iframe-buffering-e8vH4-Qn.js +1 -0
  119. package/core/built/admin/assets/images-CrQKVc9Q.js +1 -0
  120. package/core/built/admin/assets/{inbox-BUOLDoji.js → inbox-BrSRzyvz.js} +13 -13
  121. package/core/built/admin/assets/index-BHy2DZX-.js +67 -0
  122. package/core/built/admin/assets/index-Ds2iUoF6.css +1 -0
  123. package/core/built/admin/assets/index.es-B9tvf-t4.js +1 -0
  124. package/core/built/admin/assets/{input-group-uSK3XT7b.js → input-group-6vEPvX9S.js} +1 -1
  125. package/core/built/admin/assets/integration-header-B4DyXV06.js +1 -0
  126. package/core/built/admin/assets/integrations-BT6_iJvu.js +1 -0
  127. package/core/built/admin/assets/invite-user-modal-5xDCEw90.js +1 -0
  128. package/core/built/admin/assets/isEmail-BOfC647y.js +1 -0
  129. package/core/built/admin/assets/koenig-editor-base-BWLEB9Vf.js +1 -0
  130. package/core/built/admin/assets/{koenig-lexical-BFc4yFed.js → koenig-lexical-Cj62r_y9.js} +1 -1
  131. package/core/built/admin/assets/{kpi-card-BN-E6WYX.js → kpi-card-DOUnkZcN.js} +1 -1
  132. package/core/built/admin/assets/{kpi-card-CCt-qeWD.js → kpi-card-IHDo8Q56.js} +1 -1
  133. package/core/built/admin/assets/{kpi-helpers-DlxTbTs4.js → kpi-helpers-CjUdECAK.js} +1 -1
  134. package/core/built/admin/assets/{kpi-tabs-BlcUttpn.js → kpi-tabs-X3DkOYbI.js} +1 -1
  135. package/core/built/admin/assets/{label-AVrxwmsz.js → label-Dq6QaOuS.js} +1 -1
  136. package/core/built/admin/assets/label-picker-8YlhS2U3.js +1 -0
  137. package/core/built/admin/assets/{labels-DAIqKed7.js → labels-DAPjKPy2.js} +1 -1
  138. package/core/built/admin/assets/layout-TMj4P-vv.js +1 -0
  139. package/core/built/admin/assets/limit-modal-BRPsit_B.js +1 -0
  140. package/core/built/admin/assets/{link-helpers-D9LsDOkG.js → link-helpers-Bzkk68GD.js} +1 -1
  141. package/core/built/admin/assets/member-custom-fields-CmvsuuV_.js +1 -0
  142. package/core/built/admin/assets/member-detail-DCvRrkmJ.js +1 -0
  143. package/core/built/admin/assets/members-DxbSKngD.js +11 -0
  144. package/core/built/admin/assets/modal-page-BMTzFHVO.js +1 -0
  145. package/core/built/admin/assets/{moderation-CQ3urTFE.js → moderation-Da5lhhWh.js} +1 -1
  146. package/core/built/admin/assets/multi-select-combobox-CYdkH7KQ.js +1 -0
  147. package/core/built/admin/assets/navigation-modal-BIoTwtMy.js +1 -0
  148. package/core/built/admin/assets/newsletter-BptlVoJm.js +1 -0
  149. package/core/built/admin/assets/newsletter-detail-modal-BIvxqE0N.js +1 -0
  150. package/core/built/admin/assets/newsletter-emails-Bf8IZLsP.js +1 -0
  151. package/core/built/admin/assets/{newsletters-vt3Ni1Ov.js → newsletters-BgrIZk0V.js} +1 -1
  152. package/core/built/admin/assets/{newsletters-BXJbJgA-.js → newsletters-Du0lzskS.js} +1 -1
  153. package/core/built/admin/assets/{note-Dk877BTv.js → note-DYcLOmDb.js} +1 -1
  154. package/core/built/admin/assets/{notifications-CPlLjPxO.js → notifications-B2SPFIV9.js} +1 -1
  155. package/core/built/admin/assets/{offers-DEniVRD5.js → offers-Um6ab71O.js} +1 -1
  156. package/core/built/admin/assets/offers-container-modal-BZnZITMw.js +1 -0
  157. package/core/built/admin/assets/{onboarding-route-DdStOtjy.js → onboarding-route-DgdfxkQh.js} +1 -1
  158. package/core/built/admin/assets/overview-BYQJXSog.js +1 -0
  159. package/core/built/admin/assets/overview-Dt2_mR6U.js +1 -0
  160. package/core/built/admin/assets/{page-header-pp4dWmOS.js → page-header-CpmxJj5B.js} +1 -1
  161. package/core/built/admin/assets/{pages-B4goRPUn.js → pages-BVMnpI2O.js} +1 -1
  162. package/core/built/admin/assets/pintura-modal-B4MuBuuO.js +1 -0
  163. package/core/built/admin/assets/portal-frame-BF0NBIji.js +1 -0
  164. package/core/built/admin/assets/portal-modal-DMVbm0oy.js +1 -0
  165. package/core/built/admin/assets/{post-analytics-BMJrI81R.js → post-analytics-CXWBOGyP.js} +1 -1
  166. package/core/built/admin/assets/post-analytics-header-ChzjXVPt.js +1 -0
  167. package/core/built/admin/assets/{post-analytics-provider-Btml5vJI.js → post-analytics-provider-Cgw4U_fw.js} +1 -1
  168. package/core/built/admin/assets/{post-share-modal-AcUZkbfL.js → post-share-modal-B9Vmk_XM.js} +1 -1
  169. package/core/built/admin/assets/{posts-DhXE7b6r.js → posts-CxG7v0-M.js} +1 -1
  170. package/core/built/admin/assets/preferences-BfzJmnsd.js +5 -0
  171. package/core/built/admin/assets/preview-chrome-BOVO6_PE.js +1 -0
  172. package/core/built/admin/assets/preview-modal-Dw_HtD6S.js +1 -0
  173. package/core/built/admin/assets/profile-BT5vtv9b.js +1 -0
  174. package/core/built/admin/assets/radio-group-I7ou7PoJ.js +1 -0
  175. package/core/built/admin/assets/{referrers-pmJT4k3-.js → referrers-B9QDAfYm.js} +1 -1
  176. package/core/built/admin/assets/{router-provider-BmEZf6c6.js → router-provider-DSQfprxH.js} +1 -1
  177. package/core/built/admin/assets/select-D9oU1IoM.js +1 -0
  178. package/core/built/admin/assets/settings-CfZFbyu_.js +4 -0
  179. package/core/built/admin/assets/{settings-BGgk7oCI.js → settings-CpvHWix7.js} +1 -1
  180. package/core/built/admin/assets/settings-breadcrumbs-K0USP2nw.js +1 -0
  181. package/core/built/admin/assets/{share-modal-BeuydaBf.js → share-modal-ClIyPzc5.js} +1 -1
  182. package/core/built/admin/assets/slack-modal-1XXqOfiw.js +1 -0
  183. package/core/built/admin/assets/social-urls-CHQ9cTAK.js +1 -0
  184. package/core/built/admin/assets/{sort-button-Df4t2AL8.js → sort-button-CdBsm40v.js} +1 -1
  185. package/core/built/admin/assets/{source-icon-ajoaKb41.js → source-icon-CEYmVLXR.js} +1 -1
  186. package/core/built/admin/assets/{stats-C_lfxZAw.js → stats-D8nZCTy5.js} +1 -1
  187. package/core/built/admin/assets/{stats-view-Cxot1iD8.js → stats-view-CugFVf0g.js} +1 -1
  188. package/core/built/admin/assets/{step-1-ogAA4lDr.js → step-1-DOYjrORn.js} +1 -1
  189. package/core/built/admin/assets/{step-2-C5Xini-7.js → step-2-vA6RsarH.js} +1 -1
  190. package/core/built/admin/assets/{step-3-CZv1FV81.js → step-3-BtIBxMId.js} +2 -2
  191. package/core/built/admin/assets/stripe-button-UFP4QRXd.js +1 -0
  192. package/core/built/admin/assets/stripe-connect-modal-C_L3uM6n.js +1 -0
  193. package/core/built/admin/assets/switch-CE2NuMn-.js +1 -0
  194. package/core/built/admin/assets/{table-DFj1wiYv.js → table-TMCgCQK7.js} +1 -1
  195. package/core/built/admin/assets/tabs-Dd6oOKbv.js +1 -0
  196. package/core/built/admin/assets/tag-detail-D3l-UJPR.js +1 -0
  197. package/core/built/admin/assets/tags-BwCgmr5v.js +1 -0
  198. package/core/built/admin/assets/tags-WdKQGWR8.js +1 -0
  199. package/core/built/admin/assets/testimonials-modal-CqWp-xXJ.js +1 -0
  200. package/core/built/admin/assets/tier-detail-modal-BkcR2u0A.js +1 -0
  201. package/core/built/admin/assets/{tiers-DYsa4DCo.js → tiers-Da6kxHQP.js} +1 -1
  202. package/core/built/admin/assets/toggle-group-Guorw93z.js +1 -0
  203. package/core/built/admin/assets/{topic-filter-DGDxDalo.js → topic-filter-CbgoWIHn.js} +1 -1
  204. package/core/built/admin/assets/transistor-modal-DuNnnpal.js +1 -0
  205. package/core/built/admin/assets/unsplash-modal-y2bVwTHH.js +1 -0
  206. package/core/built/admin/assets/{use-analytics-data-C76v4LIp.js → use-analytics-data-B-b4wNSC.js} +1 -1
  207. package/core/built/admin/assets/use-check-theme-limit-error-m09J38Mq.js +2 -0
  208. package/core/built/admin/assets/use-feature-flag-ud0Qz8vt.js +1 -0
  209. package/core/built/admin/assets/use-filter-params-BnbBDc7T.js +1 -0
  210. package/core/built/admin/assets/use-filterable-api-iMpDZBxD.js +1 -0
  211. package/core/built/admin/assets/use-form-vRjI0Kwk.js +1 -0
  212. package/core/built/admin/assets/{use-growth-stats-aNXJOmQr.js → use-growth-stats-DPU5rt3W.js} +1 -1
  213. package/core/built/admin/assets/use-hash-link-navigation-guard-B9j6HIrV.js +1 -0
  214. package/core/built/admin/assets/use-limiter-BkN3E4gw.js +2 -0
  215. package/core/built/admin/assets/use-pintura-config-C_z8HTG2.js +1 -0
  216. package/core/built/admin/assets/use-pintura-editor-DNgbkhGp.js +1 -0
  217. package/core/built/admin/assets/{use-post-referrers-CG0SZioj.js → use-post-referrers-C2mXyrp-.js} +1 -1
  218. package/core/built/admin/assets/use-query-params-DHhhu8wi.js +1 -0
  219. package/core/built/admin/assets/{use-reply-chain-data-RLdPKRmS.js → use-reply-chain-data-DaK8q2qQ.js} +1 -1
  220. package/core/built/admin/assets/use-setting-group-YlYcoNyP.js +1 -0
  221. package/core/built/admin/assets/use-settings-navigation-Bf1g1rEh.js +1 -0
  222. package/core/built/admin/assets/{use-simple-pagination-DgcF69KI.js → use-simple-pagination-D9ckKo5-.js} +1 -1
  223. package/core/built/admin/assets/{use-tinybird-query-C1R_Kbmy.js → use-tinybird-query-GSa32hVw.js} +1 -1
  224. package/core/built/admin/assets/{use-tinybird-token-C9fPjVM_.js → use-tinybird-token-CKLOZFsw.js} +1 -1
  225. package/core/built/admin/assets/use-url-input-z-p0Kewm.js +5 -0
  226. package/core/built/admin/assets/user-detail-modal-BMxOA4Sv.js +1 -0
  227. package/core/built/admin/assets/{users-Dkv8NQZe.js → users-B6hIds6n.js} +1 -1
  228. package/core/built/admin/assets/validator-Cr0sMKuW.js +7 -0
  229. package/core/built/admin/assets/{virtual-list-BmTUzeoF.js → virtual-list-RHeEL05L.js} +1 -1
  230. package/core/built/admin/assets/web-Bw2QMO5u.js +2 -0
  231. package/core/built/admin/assets/web-xJtcwMy4.js +1 -0
  232. package/core/built/admin/assets/zapier-modal-BP2f4-vY.js +1 -0
  233. package/core/built/admin/index.html +15 -13
  234. package/core/frontend/helpers/authors.js +1 -1
  235. package/core/frontend/helpers/link.js +3 -8
  236. package/core/frontend/helpers/tags.js +1 -1
  237. package/core/frontend/meta/author-url.js +2 -2
  238. package/core/frontend/meta/url.js +4 -4
  239. package/core/frontend/services/proxy.js +1 -1
  240. package/core/frontend/services/routing/events.js +20 -6
  241. package/core/frontend/services/routing/permalink-adapter.js +0 -13
  242. package/core/frontend/services/routing/permalink-adapter.ts +0 -13
  243. package/core/frontend/services/routing/router-manager.js +15 -9
  244. package/core/frontend/services/sitemap/base-site-map-generator.js +1 -28
  245. package/core/frontend/services/sitemap/site-map-manager.js +22 -57
  246. package/core/server/adapters/route-settings/utils.js +10 -2
  247. package/core/server/adapters/route-settings/utils.ts +12 -2
  248. package/core/server/api/endpoints/email-post.js +1 -1
  249. package/core/server/api/endpoints/previews.js +1 -1
  250. package/core/server/api/endpoints/search-index-public.js +3 -3
  251. package/core/server/api/endpoints/search-index.js +4 -4
  252. package/core/server/api/endpoints/utils/serializers/input/authors.js +2 -2
  253. package/core/server/api/endpoints/utils/serializers/input/comments.js +1 -1
  254. package/core/server/api/endpoints/utils/serializers/input/pages.js +3 -3
  255. package/core/server/api/endpoints/utils/serializers/input/posts.js +3 -3
  256. package/core/server/api/endpoints/utils/serializers/input/tags.js +1 -1
  257. package/core/server/api/endpoints/utils/serializers/input/users.js +2 -2
  258. package/core/server/api/endpoints/utils/serializers/input/utils/url.js +31 -13
  259. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +2 -2
  260. package/core/server/api/endpoints/utils/serializers/output/mappers/tags.js +2 -2
  261. package/core/server/api/endpoints/utils/serializers/output/mappers/users.js +8 -0
  262. package/core/server/api/endpoints/utils/serializers/output/utils/url.js +18 -11
  263. package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +74 -0
  264. package/core/server/data/migrations/versions/6.57/2026-08-04-18-54-14-reset-automation-email-analytics.js +25 -0
  265. package/core/server/data/schema/commands.js +21 -5
  266. package/core/server/data/schema/schema.js +24 -5
  267. package/core/server/lib/common/to-plain.ts +1 -1
  268. package/core/server/models/post.js +2 -2
  269. package/core/server/services/adapter-manager/adapter-manager.js +1 -45
  270. package/core/server/services/adapter-manager/adapter-manager.ts +2 -48
  271. package/core/server/services/adapter-manager/adapter-paths.js +24 -0
  272. package/core/server/services/adapter-manager/adapter-paths.ts +21 -0
  273. package/core/server/services/adapter-manager/base-classes.js +22 -0
  274. package/core/server/services/adapter-manager/base-classes.ts +22 -0
  275. package/core/server/services/adapter-manager/index.js +4 -27
  276. package/core/server/services/adapter-manager/index.ts +4 -31
  277. package/core/server/services/adapter-manager/utils.js +50 -0
  278. package/core/server/services/adapter-manager/utils.ts +57 -0
  279. package/core/server/services/audience-feedback/audience-feedback-controller.js +1 -1
  280. package/core/server/services/audience-feedback/audience-feedback-service.js +2 -2
  281. package/core/server/services/automations/service.js +2 -2
  282. package/core/server/services/automations/service.ts +2 -2
  283. package/core/server/services/comments/comments-service-emails.js +1 -1
  284. package/core/server/services/comments/comments-service.js +1 -1
  285. package/core/server/services/email-analytics/email-analytics-service-wrapper.js +11 -2
  286. package/core/server/services/email-analytics/email-analytics-service.js +1 -1
  287. package/core/server/services/email-service/email-service-wrapper.js +2 -1
  288. package/core/server/services/email-service/mailgun-email-provider.js +15 -0
  289. package/core/server/services/indexnow-ping/indexnow-ping-service.js +1 -1
  290. package/core/server/services/indexnow-ping/indexnow-ping-service.ts +2 -4
  291. package/core/server/services/lib/mailgun-client.js +3 -3
  292. package/core/server/services/media-inliner/external-media-inliner.js +12 -8
  293. package/core/server/services/member-attribution/url-translator.js +5 -7
  294. package/core/server/services/member-welcome-emails/service.js +72 -12
  295. package/core/server/services/members/api.js +1 -1
  296. package/core/server/services/members/import-export/import/completion-email.js +8 -4
  297. package/core/server/services/members/import-export/import/completion-email.ts +9 -4
  298. package/core/server/services/members/import-export/import/importer.js +25 -4
  299. package/core/server/services/members/import-export/import/importer.ts +26 -6
  300. package/core/server/services/members/import-export/import/row.ts +5 -1
  301. package/core/server/services/members/import-export/index.js +1 -5
  302. package/core/server/services/members/import-export/index.ts +2 -6
  303. package/core/server/services/members-custom-fields/queries.js +4 -13
  304. package/core/server/services/members-custom-fields/queries.ts +4 -13
  305. package/core/server/services/members-custom-fields/schema.js +18 -27
  306. package/core/server/services/members-custom-fields/schema.ts +17 -27
  307. package/core/server/services/members-custom-fields/storage.js +94 -27
  308. package/core/server/services/members-custom-fields/storage.ts +114 -39
  309. package/core/server/services/members-custom-fields/values-service.js +98 -219
  310. package/core/server/services/members-custom-fields/values-service.ts +110 -238
  311. package/core/server/services/mentions/resource-service.js +1 -1
  312. package/core/server/services/mentions/service.js +2 -2
  313. package/core/server/services/route-settings/dynamic-routing-service.js +9 -59
  314. package/core/server/services/slack-ping/slack-ping-service.js +2 -2
  315. package/core/server/services/slack-ping/slack-ping-service.ts +3 -5
  316. package/core/server/services/stats/content-stats-service.js +3 -3
  317. package/core/server/services/stats/posts-stats-service.js +2 -2
  318. package/core/server/services/url/config.js +90 -135
  319. package/core/server/services/url/index.js +6 -49
  320. package/core/server/services/url/lazy-find-resource.js +14 -11
  321. package/core/server/services/url/lazy-find-resource.ts +14 -11
  322. package/core/server/services/url/lazy-url-service.js +174 -94
  323. package/core/server/services/url/lazy-url-service.ts +233 -97
  324. package/core/server/services/url/routable-resources.js +62 -71
  325. package/core/server/services/url/router-filter.js +12 -4
  326. package/core/server/services/url/router-filter.ts +12 -4
  327. package/core/server/services/webhooks/index.js +1 -1
  328. package/core/server/services/webhooks/serialize.js +2 -1
  329. package/core/shared/config/env/config.testing-mysql.json +1 -2
  330. package/core/shared/config/env/config.testing.json +1 -2
  331. package/core/shared/events/index.js +1 -2
  332. package/core/shared/labs.js +3 -3
  333. package/package.json +5 -6
  334. package/pnpm-lock.yaml +6 -9
  335. package/pnpm-workspace.yaml +2 -2
  336. package/tsconfig.json +1 -0
  337. package/core/built/admin/assets/Combination-BCm9FL4o.js +0 -41
  338. package/core/built/admin/assets/activitypub/layout-CTgrezN7.js +0 -1
  339. package/core/built/admin/assets/automated-emails-6t2bIRWf.js +0 -7
  340. package/core/built/admin/assets/checkbox-BIl077a0.js +0 -1
  341. package/core/built/admin/assets/code-editor-view-C0aWNwdl.js +0 -1
  342. package/core/built/admin/assets/comments-qwhIjH3c.js +0 -1
  343. package/core/built/admin/assets/disabled-sources-indicator-DYAXW-6V.js +0 -1
  344. package/core/built/admin/assets/dist-BUKmD51u.js +0 -1
  345. package/core/built/admin/assets/dist-CNlMj7ML.js +0 -1
  346. package/core/built/admin/assets/dist-DDY6GRVz.js +0 -1
  347. package/core/built/admin/assets/dist-DG78yroX.js +0 -23
  348. package/core/built/admin/assets/editor-DDYSOXGN.js +0 -8
  349. package/core/built/admin/assets/esm-CFX93H5M.js +0 -2
  350. package/core/built/admin/assets/filters-CLehPo_D.js +0 -1
  351. package/core/built/admin/assets/index-08n05-rc.js +0 -67
  352. package/core/built/admin/assets/index-DgUzgFvS.css +0 -1
  353. package/core/built/admin/assets/label-picker-DVnpu_IF.js +0 -1
  354. package/core/built/admin/assets/layout-B0YLh7R9.js +0 -1
  355. package/core/built/admin/assets/member-custom-fields-C8NpZYza.js +0 -1
  356. package/core/built/admin/assets/member-detail-CIVAdKXf.js +0 -1
  357. package/core/built/admin/assets/members-oLPiSkXD.js +0 -10
  358. package/core/built/admin/assets/modals-CB7v-k3T.js +0 -31
  359. package/core/built/admin/assets/newsletter-rbeDX9Wk.js +0 -1
  360. package/core/built/admin/assets/overview--4V-kx9c.js +0 -1
  361. package/core/built/admin/assets/overview-DSLv30oz.js +0 -1
  362. package/core/built/admin/assets/post-analytics-header-pU2m9ZuV.js +0 -1
  363. package/core/built/admin/assets/preferences-Ru4CRPHC.js +0 -5
  364. package/core/built/admin/assets/profile-BNb1oj7D.js +0 -1
  365. package/core/built/admin/assets/select-B0HteHrp.js +0 -1
  366. package/core/built/admin/assets/settings-B1C9Q3wo.js +0 -4
  367. package/core/built/admin/assets/social-urls-DA2lXrhW.js +0 -8
  368. package/core/built/admin/assets/switch-CyQcwMAv.js +0 -1
  369. package/core/built/admin/assets/tabs-D4y1gqUd.js +0 -1
  370. package/core/built/admin/assets/tags-Dmft-Y4O.js +0 -1
  371. package/core/built/admin/assets/toggle-group-R2QMFvXW.js +0 -1
  372. package/core/built/admin/assets/use-filter-params-WrdhAISv.js +0 -1
  373. package/core/built/admin/assets/web-BqW0b0_C.js +0 -1
  374. package/core/built/admin/assets/web-Cy5GKMWM.js +0 -2
  375. package/core/server/services/url/local-file-cache.js +0 -75
  376. package/core/server/services/url/queue.js +0 -260
  377. package/core/server/services/url/resource.js +0 -102
  378. package/core/server/services/url/resources.js +0 -480
  379. package/core/server/services/url/url-generator.js +0 -276
  380. package/core/server/services/url/url-service-facade.js +0 -366
  381. package/core/server/services/url/url-service-facade.ts +0 -519
  382. package/core/server/services/url/url-service.js +0 -387
  383. package/core/server/services/url/urls.js +0 -140
  384. package/core/shared/events/url-resource-updated-event.js +0 -33
  385. /package/core/built/admin/assets/{dist-CqO2lWS0.js → dist-CqO2lWS02.js} +0 -0
@@ -36,7 +36,7 @@ const controller = {
36
36
  }
37
37
  },
38
38
  async query(frame) {
39
- urlSerializerUtils.forceUrlRelationsWhenLazy(frame, 'posts');
39
+ urlSerializerUtils.forceUrlRelations(frame, 'posts');
40
40
 
41
41
  const model = await models.Post.findOne(Object.assign(frame.data, {status: 'sent'}), frame.options);
42
42
 
@@ -82,7 +82,7 @@ const controller = {
82
82
  await _addMemberContextToFrame(frame);
83
83
 
84
84
  // previews has no input serializer, so the URL force-load happens here
85
- urlSerializerUtils.forceUrlRelationsWhenLazy(frame, 'posts');
85
+ urlSerializerUtils.forceUrlRelations(frame, 'posts');
86
86
 
87
87
  const model = await models.Post.findOne(Object.assign({status: 'all'}, frame.data), frame.options);
88
88
  if (!model) {
@@ -4,8 +4,8 @@ const {requiredUrlColumns} = require('./utils/serializers/input/utils/url');
4
4
  const getPostServiceInstance = require('../../services/posts/posts-service-instance');
5
5
  const postsService = getPostServiceInstance();
6
6
 
7
- const urlRelationsWhenLazyRouting = () => {
8
- const withRelated = urlService.facade.getRequiredRelations();
7
+ const urlRelationsForRouting = () => {
8
+ const withRelated = urlService.getRequiredRelations();
9
9
  return withRelated.length ? {withRelated} : {};
10
10
  };
11
11
 
@@ -23,7 +23,7 @@ const controller = {
23
23
  limit: '10000',
24
24
  order: 'updated_at DESC',
25
25
  columns: requiredUrlColumns('posts', ['id', 'slug', 'title', 'excerpt', 'url', 'updated_at', 'visibility']),
26
- ...urlRelationsWhenLazyRouting()
26
+ ...urlRelationsForRouting()
27
27
  };
28
28
 
29
29
  return postsService.browsePosts(options);
@@ -4,8 +4,8 @@ const {requiredUrlColumns} = require('./utils/serializers/input/utils/url');
4
4
  const getPostServiceInstance = require('../../services/posts/posts-service-instance');
5
5
  const postsService = getPostServiceInstance();
6
6
 
7
- const urlRelationsWhenLazyRouting = () => {
8
- const withRelated = urlService.facade.getRequiredRelations();
7
+ const urlRelationsForRouting = () => {
8
+ const withRelated = urlService.getRequiredRelations();
9
9
  return withRelated.length ? {withRelated} : {};
10
10
  };
11
11
 
@@ -26,7 +26,7 @@ const controller = {
26
26
  limit: '10000',
27
27
  order: 'updated_at DESC',
28
28
  columns: requiredUrlColumns('posts', ['id', 'uuid', 'url', 'title', 'slug', 'status', 'published_at', 'visibility']),
29
- ...urlRelationsWhenLazyRouting()
29
+ ...urlRelationsForRouting()
30
30
  };
31
31
 
32
32
  return postsService.browsePosts(options);
@@ -46,7 +46,7 @@ const controller = {
46
46
  limit: '10000',
47
47
  order: 'updated_at DESC',
48
48
  columns: requiredUrlColumns('pages', ['id', 'uuid', 'url', 'title', 'slug', 'status', 'published_at', 'visibility']),
49
- ...urlRelationsWhenLazyRouting()
49
+ ...urlRelationsForRouting()
50
50
  };
51
51
 
52
52
  return postsService.browsePosts(options);
@@ -17,7 +17,7 @@ module.exports = {
17
17
  browse(apiConfig, frame) {
18
18
  debug('browse');
19
19
 
20
- url.forceUrlColumnsWhenLazy(frame, 'authors');
20
+ url.forceUrlColumns(frame, 'authors');
21
21
 
22
22
  if (utils.isContentAPI(frame)) {
23
23
  setDefaultOrder(frame);
@@ -27,7 +27,7 @@ module.exports = {
27
27
  read(apiConfig, frame) {
28
28
  debug('read');
29
29
 
30
- url.forceUrlColumnsWhenLazy(frame, 'authors');
30
+ url.forceUrlColumns(frame, 'authors');
31
31
 
32
32
  if (utils.isContentAPI(frame)) {
33
33
  setDefaultOrder(frame);
@@ -24,7 +24,7 @@ module.exports = {
24
24
  });
25
25
 
26
26
  if (frame.options.withRelated.includes('post')) {
27
- for (const relation of urlService.facade.getRequiredRelations()) {
27
+ for (const relation of urlService.getRequiredRelations()) {
28
28
  const prefixed = `post.${relation}`;
29
29
  if (!frame.options.withRelated.includes(prefixed)) {
30
30
  frame.options.withRelated.push(prefixed);
@@ -56,7 +56,7 @@ function defaultRelations(frame) {
56
56
  frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'tiers', 'count.signups', 'count.paid_conversions'];
57
57
  }
58
58
 
59
- url.forceUrlRelationsWhenLazy(frame, 'pages');
59
+ url.forceUrlRelations(frame, 'pages');
60
60
  }
61
61
 
62
62
  function setDefaultOrder(frame) {
@@ -126,7 +126,7 @@ module.exports = {
126
126
 
127
127
  setDefaultOrder(frame);
128
128
  forceVisibilityColumn(frame);
129
- url.forceUrlRelationsWhenLazy(frame, 'pages');
129
+ url.forceUrlRelations(frame, 'pages');
130
130
  }
131
131
 
132
132
  if (!localUtils.isContentAPI(frame)) {
@@ -145,7 +145,7 @@ module.exports = {
145
145
  removeSourceFormats(frame);
146
146
  setDefaultOrder(frame);
147
147
  forceVisibilityColumn(frame);
148
- url.forceUrlRelationsWhenLazy(frame, 'pages');
148
+ url.forceUrlRelations(frame, 'pages');
149
149
  }
150
150
 
151
151
  if (!localUtils.isContentAPI(frame)) {
@@ -82,7 +82,7 @@ function defaultRelations(frame) {
82
82
  frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter', 'count.clicks'];
83
83
  }
84
84
 
85
- url.forceUrlRelationsWhenLazy(frame, 'posts');
85
+ url.forceUrlRelations(frame, 'posts');
86
86
  }
87
87
 
88
88
  function setDefaultOrder(frame) {
@@ -161,7 +161,7 @@ module.exports = {
161
161
  setDefaultOrder(frame);
162
162
  forceVisibilityColumn(frame);
163
163
  mapWithRelated(frame);
164
- url.forceUrlRelationsWhenLazy(frame, 'posts');
164
+ url.forceUrlRelations(frame, 'posts');
165
165
  }
166
166
 
167
167
  if (!localUtils.isContentAPI(frame)) {
@@ -188,7 +188,7 @@ module.exports = {
188
188
 
189
189
  setDefaultOrder(frame);
190
190
  forceVisibilityColumn(frame);
191
- url.forceUrlRelationsWhenLazy(frame, 'posts');
191
+ url.forceUrlRelations(frame, 'posts');
192
192
  }
193
193
 
194
194
  if (!localUtils.isContentAPI(frame)) {
@@ -19,7 +19,7 @@ module.exports = {
19
19
  browse(apiConfig, frame) {
20
20
  debug('browse');
21
21
 
22
- url.forceUrlColumnsWhenLazy(frame, 'tags');
22
+ url.forceUrlColumns(frame, 'tags');
23
23
 
24
24
  if (utils.isContentAPI(frame)) {
25
25
  setDefaultOrder(frame);
@@ -6,13 +6,13 @@ module.exports = {
6
6
  debug('browse');
7
7
 
8
8
  // Staff users route through the authors router types.
9
- url.forceUrlColumnsWhenLazy(frame, 'authors');
9
+ url.forceUrlColumns(frame, 'authors');
10
10
  },
11
11
 
12
12
  read(apiConfig, frame) {
13
13
  debug('read');
14
14
 
15
- url.forceUrlColumnsWhenLazy(frame, 'authors');
15
+ url.forceUrlColumns(frame, 'authors');
16
16
 
17
17
  if (frame.data.id === 'me' && frame.options.context && frame.options.context.user) {
18
18
  frame.data.id = frame.options.context.user;
@@ -71,28 +71,41 @@ const forSetting = (attrs) => {
71
71
  // build a URL (e.g. status/visibility/slug), so the service would reject the
72
72
  // resource as thin. Force them back into the fetch and record them on the
73
73
  // frame — the output mapper strips them from the response after the URL is
74
- // built. No-op under the eager service (getRequiredFields []).
75
- const forceUrlColumnsWhenLazy = (frame, routerType) => {
74
+ // built. No-op when the routing config needs no extra columns.
75
+ const forceUrlColumns = (frame, routerType, extraColumns = []) => {
76
76
  if (!Array.isArray(frame.options.columns) || !frame.options.columns.includes('url')) {
77
77
  return;
78
78
  }
79
- const forced = urlService.facade.getRequiredFields(routerType).filter(field => !frame.options.columns.includes(field));
79
+ const required = new Set([...urlService.getRequiredFields(routerType), ...extraColumns]);
80
+ const missing = [...required].filter(field => !frame.options.columns.includes(field));
81
+ if (!missing.length) {
82
+ return;
83
+ }
84
+ // A read already carries the fields it was looked up by: `findOne` forges
85
+ // the model with them before the fetch, so they survive a narrowed
86
+ // `?fields=` list and the caller is served them today. Stripping those back
87
+ // out would take a field away — `posts/slug/:slug/?fields=url` still
88
+ // answers with its slug. They are still selected, because the lookup
89
+ // matches case-insensitively: the forged value can differ in case from the
90
+ // stored one, and the URL is built from whichever the model carries.
91
+ const carried = Object.keys(frame.data || {});
92
+ const forced = missing.filter(field => !carried.includes(field));
80
93
  if (forced.length) {
81
- frame.forcedUrlColumns = forced;
82
- frame.options.columns.push(...forced);
94
+ frame.forcedUrlColumns = {routerType, columns: forced};
83
95
  }
96
+ frame.options.columns.push(...missing);
84
97
  };
85
98
 
86
99
  // `url` is serialized for every post/page unless `?fields` narrows it away,
87
100
  // so the relations the lazy URL service reads (e.g. tags for a tag-filtered
88
101
  // collection) must be loaded whenever the URL will be built — not only for
89
102
  // the `?fields=url` case. Forced relations are recorded on the frame for the
90
- // output mapper to strip. No-op under the eager service.
91
- const forceUrlRelationsWhenLazy = (frame, routerType) => {
103
+ // output mapper to strip. No-op when the routing config reads no relations.
104
+ const forceUrlRelations = (frame, routerType) => {
92
105
  if (!localUtils.willSerializeUrl(frame)) {
93
106
  return;
94
107
  }
95
- const relations = urlService.facade.getRequiredRelations();
108
+ const relations = urlService.getRequiredRelations();
96
109
  if (relations.length) {
97
110
  const requested = frame.options.withRelated || [];
98
111
  // a nested include covers its parent: `authors.roles` loads authors
@@ -105,13 +118,18 @@ const forceUrlRelationsWhenLazy = (frame, routerType) => {
105
118
  frame.options.withRelated = [...requested, ...forced];
106
119
  }
107
120
  }
108
- forceUrlColumnsWhenLazy(frame, routerType);
121
+ // The URL is looked up by `model.id`, and Bookshelf matches eager-loaded
122
+ // rows back to their parent by the same key — so a `?fields=` list that
123
+ // omits it serializes /404/ and leaves the relations above unattached,
124
+ // silently and without a key on the model at all. A read looked up by id
125
+ // is covered by the carried-fields rule above.
126
+ forceUrlColumns(frame, routerType, ['id']);
109
127
  };
110
128
 
111
- // Options-object variant of forceUrlColumnsWhenLazy for endpoints that build
129
+ // Options-object variant of forceUrlColumns for endpoints that build
112
130
  // their query options directly (search index).
113
131
  const requiredUrlColumns = (routerType, columns) => {
114
- const required = urlService.facade.getRequiredFields(routerType).filter(field => !columns.includes(field));
132
+ const required = urlService.getRequiredFields(routerType).filter(field => !columns.includes(field));
115
133
  return required.length ? [...columns, ...required] : columns;
116
134
  };
117
135
 
@@ -120,5 +138,5 @@ module.exports.requiredUrlColumns = requiredUrlColumns;
120
138
  module.exports.forUser = forUser;
121
139
  module.exports.forTag = forTag;
122
140
  module.exports.forSetting = forSetting;
123
- module.exports.forceUrlColumnsWhenLazy = forceUrlColumnsWhenLazy;
124
- module.exports.forceUrlRelationsWhenLazy = forceUrlRelationsWhenLazy;
141
+ module.exports.forceUrlColumns = forceUrlColumns;
142
+ module.exports.forceUrlRelations = forceUrlRelations;
@@ -136,8 +136,8 @@ module.exports = async (model, frame, options = {}) => {
136
136
  clean.post(jsonModel, frame);
137
137
 
138
138
  // Columns force-loaded for the URL computation, not requested by the caller.
139
- if (frame.forcedUrlColumns) {
140
- frame.forcedUrlColumns.forEach((column) => {
139
+ if (frame.forcedUrlColumns && frame.forcedUrlColumns.routerType === routerType) {
140
+ frame.forcedUrlColumns.columns.forEach((column) => {
141
141
  delete jsonModel[column];
142
142
  });
143
143
  }
@@ -8,8 +8,8 @@ module.exports = (model, frame) => {
8
8
  clean.tag(jsonModel, frame);
9
9
 
10
10
  // Columns force-loaded for the URL computation, not requested by the caller.
11
- if (frame.forcedUrlColumns) {
12
- frame.forcedUrlColumns.forEach((column) => {
11
+ if (frame.forcedUrlColumns && frame.forcedUrlColumns.routerType === 'tags') {
12
+ frame.forcedUrlColumns.columns.forEach((column) => {
13
13
  delete jsonModel[column];
14
14
  });
15
15
  }
@@ -6,6 +6,14 @@ module.exports = (model, frame) => {
6
6
 
7
7
  url.forUser(model.id, jsonModel, frame.options);
8
8
 
9
+ // Force-loaded for the URL computation, not requested by the caller (the
10
+ // author permalink substitutes :slug, so `?fields=url` pulls it in).
11
+ if (frame.forcedUrlColumns && frame.forcedUrlColumns.routerType === 'authors') {
12
+ frame.forcedUrlColumns.columns.forEach((column) => {
13
+ delete jsonModel[column];
14
+ });
15
+ }
16
+
9
17
  clean.author(jsonModel, frame);
10
18
 
11
19
  return jsonModel;
@@ -10,8 +10,9 @@ const forPost = (id, attrs, frame, type = 'posts') => {
10
10
  // type from `attrs` would silently fall back to 'posts' for pages.
11
11
  // The mapper that owns the resource always knows which it is.
12
12
  //
13
- // `id` is passed separately for the same reason: without it, the eager
14
- // facade's id-based fallback hits /404/ for every record.
13
+ // `id` is passed separately for the same reason: `?fields=url` strips it
14
+ // from `attrs`, and the URL service needs it for `:id` permalinks and to
15
+ // name the resource when it reports a problem.
15
16
  //
16
17
  // When `url` was not requested (`?fields=id,title`), don't compute it at
17
18
  // all — attrs is stripped to the requested columns, so the resource would
@@ -24,17 +25,23 @@ const forPost = (id, attrs, frame, type = 'posts') => {
24
25
  return attrs;
25
26
  }
26
27
 
27
- // Diagnostic only: hand the compare-mode URL service the producing api
28
- // endpoint. The parity/thin-resource logs capture a caller stack, but it
29
- // truncates at the async api-framework boundary and never names the
30
- // endpoint; the api-framework Frame carries it, so pass it through. Omitted
31
- // for non-api callers (mentions, email) whose frame has no endpoint fields.
28
+ // Names the fetch that produced this resource. It matters on the degrade
29
+ // path: a thin resource is answered with a silent /404/, and the report is
30
+ // the only way back to the endpoint that under-fetched it. Ignored by URL
31
+ // generation itself; read only into that report.
32
32
  const options = {absolute: true};
33
33
  if (frame && (frame.docName || frame.method)) {
34
- options.serializerContext = {apiType: frame.apiType, docName: frame.docName, method: frame.method};
34
+ options.serializerContext = {
35
+ apiType: frame.apiType,
36
+ docName: frame.docName,
37
+ method: frame.method,
38
+ withRelated: frame.options && frame.options.withRelated,
39
+ columns: frame.options && frame.options.columns,
40
+ forcedUrlRelations: frame.forcedUrlRelations
41
+ };
35
42
  }
36
43
 
37
- attrs.url = urlService.facade.getUrlForResource({...attrs, id, type}, options);
44
+ attrs.url = urlService.getUrlForResource({...attrs, id, type}, options);
38
45
 
39
46
  /**
40
47
  * CASE: admin api should serve preview urls
@@ -71,7 +78,7 @@ const forPost = (id, attrs, frame, type = 'posts') => {
71
78
 
72
79
  const forUser = (id, attrs, options) => {
73
80
  if (!options.columns || (options.columns && options.columns.includes('url'))) {
74
- attrs.url = urlService.facade.getUrlForResource({...attrs, id, type: 'authors'}, {absolute: true});
81
+ attrs.url = urlService.getUrlForResource({...attrs, id, type: 'authors'}, {absolute: true});
75
82
  }
76
83
 
77
84
  return attrs;
@@ -79,7 +86,7 @@ const forUser = (id, attrs, options) => {
79
86
 
80
87
  const forTag = (id, attrs, options) => {
81
88
  if (!options.columns || (options.columns && options.columns.includes('url'))) {
82
- attrs.url = urlService.facade.getUrlForResource({...attrs, id, type: 'tags'}, {absolute: true});
89
+ attrs.url = urlService.getUrlForResource({...attrs, id, type: 'tags'}, {absolute: true});
83
90
  }
84
91
 
85
92
  return attrs;
@@ -0,0 +1,74 @@
1
+ const logging = require('@tryghost/logging');
2
+ const {createNonTransactionalMigration} = require('../../utils');
3
+ const {addColumn, dropColumn, addUnique, dropUnique, addIndex, dropIndex} = require('../../../schema/commands');
4
+
5
+ const TABLE = 'members_custom_field_values';
6
+
7
+ // Named explicitly; the derived name would overrun MySQL's identifier limit, which
8
+ // schema.test.js checks for every table.
9
+ const LEAF_UNIQUE = 'members_custom_field_values_leaf_unique';
10
+
11
+ // Values are discarded rather than converted: custom fields sit behind a private flag and
12
+ // have never been released, so only a site deliberately opted in can hold one.
13
+ async function discardStoredValues(knex) {
14
+ const discarded = await knex(TABLE).del();
15
+ if (discarded > 0) {
16
+ logging.warn(`Discarded ${discarded} custom field value(s): the storage format changed before the feature was released`);
17
+ }
18
+ }
19
+
20
+ // Column changes ask for `auto` so MySQL picks an in-place algorithm where it can; the
21
+ // helpers otherwise default to a full table copy, which blocks writes.
22
+ //
23
+ // Two constraints shape the order of everything below.
24
+ //
25
+ // MySQL refuses to drop the last index serving a foreign key, counting any index whose
26
+ // leftmost column is the referencing one, so each replacement is added before the thing
27
+ // it replaces is dropped.
28
+ //
29
+ // This migration is not transactional, so a crash leaves the table half-changed with
30
+ // nothing recorded as done and knex-migrator runs it again on the next boot. Every step
31
+ // therefore tolerates having already happened: the constraint and index helpers swallow
32
+ // that themselves, the column steps ask first.
33
+ module.exports = createNonTransactionalMigration(
34
+ async function up(knex) {
35
+ // First, so nothing can collide with the new constraint.
36
+ await discardStoredValues(knex);
37
+
38
+ if (!await knex.schema.hasColumn(TABLE, 'path')) {
39
+ await addColumn(TABLE, 'path', knex, undefined, {algorithm: 'auto'});
40
+ }
41
+
42
+ // `member_id` stays leftmost, covering that foreign key the moment this exists.
43
+ await addUnique(TABLE, ['member_id', 'custom_field_id', 'path'], knex, LEAF_UNIQUE);
44
+ await dropUnique(TABLE, ['member_id', 'custom_field_id'], knex);
45
+
46
+ if (await knex.schema.hasColumn(TABLE, 'value_json')) {
47
+ await dropColumn(TABLE, 'value_json', knex, {}, {algorithm: 'auto'});
48
+ }
49
+ await addIndex(TABLE, ['custom_field_id', 'path'], knex);
50
+
51
+ // Only present after a rollback, where `down` adds it to keep a foreign key
52
+ // covered. Leaving it would be drift against a fresh install.
53
+ await dropIndex(TABLE, ['custom_field_id'], knex);
54
+ },
55
+ async function down(knex) {
56
+ // Rebuilding a composite would need the field-type catalog, which a migration must
57
+ // not read: it has to keep working when the catalog moves on.
58
+ await discardStoredValues(knex);
59
+
60
+ await addUnique(TABLE, ['member_id', 'custom_field_id'], knex);
61
+ await dropUnique(TABLE, ['member_id', 'custom_field_id', 'path'], knex, LEAF_UNIQUE);
62
+
63
+ // Back before the composite one goes, or MySQL has nothing covering that key.
64
+ await addIndex(TABLE, ['custom_field_id'], knex);
65
+ await dropIndex(TABLE, ['custom_field_id', 'path'], knex);
66
+
67
+ if (!await knex.schema.hasColumn(TABLE, 'value_json')) {
68
+ await addColumn(TABLE, 'value_json', knex, {type: 'text', maxlength: 65535, nullable: true}, {algorithm: 'auto'});
69
+ }
70
+ if (await knex.schema.hasColumn(TABLE, 'path')) {
71
+ await dropColumn(TABLE, 'path', knex, {}, {algorithm: 'auto'});
72
+ }
73
+ }
74
+ );
@@ -0,0 +1,25 @@
1
+ const logging = require('@tryghost/logging');
2
+ const {createTransactionalMigration} = require('../../utils');
3
+
4
+ module.exports = createTransactionalMigration(
5
+ async function up(knex) {
6
+ logging.info('Resetting automation email analytics counts');
7
+
8
+ const updatedRevisions = await knex('automation_action_revisions').update({
9
+ email_sent_count: null,
10
+ email_opened_count: null,
11
+ email_clicked_count: null
12
+ });
13
+
14
+ logging.info(`Reset automation email analytics counts for ${updatedRevisions} action revisions`);
15
+
16
+ const deletedRecipients = await knex('automated_email_recipients')
17
+ .whereNotNull('automation_action_revision_id')
18
+ .del();
19
+
20
+ logging.info(`Deleted ${deletedRecipients} automation email recipient rows`);
21
+ },
22
+ async function down() {
23
+ logging.info('Skipping rollback for automation email analytics reset');
24
+ }
25
+ );
@@ -276,13 +276,18 @@ async function dropIndex(tableName, columns, transaction = db.knex) {
276
276
  * @param {string} tableName - name of the table to add unique constraint to
277
277
  * @param {string|string[]} columns - column(s) to form unique constraint with
278
278
  * @param {import('knex').Knex} [transaction] - connection object containing knex reference
279
+ * @param {string} [indexName] - name for the constraint; knex derives one from the table
280
+ * and every column when omitted, which can overrun MySQL's 64-character limit
279
281
  */
280
- async function addUnique(tableName, columns, transaction = db.knex) {
282
+ async function addUnique(tableName, columns, transaction = db.knex, indexName) {
281
283
  try {
282
284
  logging.info(`Adding unique constraint for '${columns}' in table '${tableName}'`);
283
285
 
284
286
  return await transaction.schema.table(tableName, function (table) {
285
- table.unique(columns);
287
+ // Knex derives a name from the table and every column when it is not given
288
+ // one, and that can overrun MySQL's 64-character identifier limit on a wide
289
+ // table or a constraint over several columns.
290
+ table.unique(columns, indexName ? {indexName} : undefined);
286
291
  });
287
292
  } catch (err) {
288
293
  if (err.code === 'SQLITE_ERROR') {
@@ -304,12 +309,14 @@ async function addUnique(tableName, columns, transaction = db.knex) {
304
309
  * @param {string|string[]} columns - column(s) unique constraint was formed
305
310
  * @param {import('knex').Knex} transaction - connection object containing knex reference
306
311
  */
307
- async function dropUnique(tableName, columns, transaction = db.knex) {
312
+ async function dropUnique(tableName, columns, transaction = db.knex, indexName) {
308
313
  try {
309
314
  logging.info(`Dropping unique constraint for '${columns}' in table '${tableName}'`);
310
315
 
311
316
  return await transaction.schema.table(tableName, function (table) {
312
- table.dropUnique(columns);
317
+ // Named constraints have to be dropped by that name: the one knex would
318
+ // derive from the columns is not what is on the table.
319
+ table.dropUnique(columns, indexName);
313
320
  });
314
321
  } catch (err) {
315
322
  if (err.code === 'SQLITE_ERROR') {
@@ -542,7 +549,16 @@ function createTable(table, transaction = db.knex, tableSpec = schema[table]) {
542
549
  });
543
550
  }
544
551
  if (tableSpec['@@UNIQUE_CONSTRAINTS@@']) {
545
- tableSpec['@@UNIQUE_CONSTRAINTS@@'].forEach(unique => t.unique(unique));
552
+ tableSpec['@@UNIQUE_CONSTRAINTS@@'].forEach((unique) => {
553
+ // An entry is normally the columns alone, and knex names the constraint
554
+ // after the table and every one of them. The object form is for when
555
+ // that derived name would overrun MySQL's 64-character limit.
556
+ if (unique && typeof unique === 'object' && !Array.isArray(unique)) {
557
+ t.unique(unique.columns, {indexName: unique.indexName});
558
+ } else {
559
+ t.unique(unique);
560
+ }
561
+ });
546
562
  }
547
563
  if (tableSpec['@@PRIMARY_KEY@@']) {
548
564
  t.primary(tableSpec['@@PRIMARY_KEY@@']);
@@ -682,15 +682,34 @@ module.exports = {
682
682
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
683
683
  custom_field_id: {type: 'string', maxlength: 24, nullable: false, references: 'members_custom_fields.id', cascadeDelete: true},
684
684
  member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
685
- // Exactly one value column is populated per row, chosen by the field
686
- // type's storage type in @tryghost/custom-field-types, whose byte bound
687
- // on long_text matches this column exactly.
685
+ // Which part of the field's value this row carries. A scalar has one part and
686
+ // stores it under the empty path; a composite stores one row per sub-field it
687
+ // fills, under that sub-field's key. Not nullable, because MySQL counts NULLs
688
+ // as distinct in a unique index and the constraint below would stop holding.
689
+ path: {type: 'string', maxlength: 191, nullable: false, defaultTo: ''},
690
+ // One row, one value: a part a member has not filled in has no row at all. The
691
+ // column stays nullable even so, because making it NOT NULL is only reachable
692
+ // through knex's dropNullable, which rewrites the column on MySQL and widens
693
+ // TEXT to MEDIUMTEXT — a migrated site would then accept sixteen megabytes in a
694
+ // column a fresh install bounds at 65,535 bytes. The bound matching long_text's
695
+ // exactly is worth more than the schema restating what the write path enforces.
688
696
  value_text: {type: 'text', maxlength: 65535, nullable: true},
689
- value_json: {type: 'text', maxlength: 65535, nullable: true},
690
697
  created_at: {type: 'dateTime', nullable: false},
691
698
  updated_at: {type: 'dateTime', nullable: true},
699
+ // Named, because the name knex derives from the table and all three columns is
700
+ // 65 characters and MySQL stops at 64. The migration that first created this
701
+ // table already shortened a column for the same reason; a third column spends
702
+ // what headroom that bought.
692
703
  '@@UNIQUE_CONSTRAINTS@@': [
693
- ['member_id', 'custom_field_id']
704
+ {columns: ['member_id', 'custom_field_id', 'path'], indexName: 'members_custom_field_values_leaf_unique'}
705
+ ],
706
+ // What a segment filter looks up: every member holding a given value for a
707
+ // given part of a given field. The value itself is not in the index — it is
708
+ // TEXT, so MySQL would need a prefix length, and the schema's index builder
709
+ // applies one length to every column in a composite index rather than to a
710
+ // single chosen one.
711
+ '@@INDEXES@@': [
712
+ ['custom_field_id', 'path']
694
713
  ]
695
714
  },
696
715
  members_stripe_customers: {
@@ -7,7 +7,7 @@
7
7
  * callers accept either shape uniformly:
8
8
  *
9
9
  * const data = toPlain(post);
10
- * urlService.facade.getUrlForResource({...data, type: 'posts'}, ...)
10
+ * urlService.getUrlForResource({...data, type: 'posts'}, ...)
11
11
  */
12
12
  type JsonSerializable<T> = {
13
13
  toJSON(): T;
@@ -1006,8 +1006,8 @@ Post = ghostBookshelf.Model.extend({
1006
1006
  * - `slug`: /:slug/
1007
1007
  * - `published_at`: /:year/:slug
1008
1008
  * - `author_id`: /:author/:slug, /:primary_author/:slug
1009
- * - now, the UrlService pre-generates urls based on the resources
1010
- * - you can ask `urlService.getUrlByResourceId(post.id)`
1009
+ * - now, the UrlService computes urls from the routing config
1010
+ * - you can ask `urlService.getUrlForResource(post)`
1011
1011
  *
1012
1012
  * ### events
1013
1013
  * - you call `findAll` with `columns: id`