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
@@ -1,21 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DbCustomFieldValueWithField = exports.StoredCompositeValue = exports.DbCustomFieldValue = exports.DbCustomField = exports.FieldStatusSchema = exports.FIELD_STATUS = void 0;
3
+ exports.DbCustomFieldLeaf = exports.DbCustomFieldValue = exports.DbCustomField = exports.FieldStatusSchema = exports.FIELD_STATUS = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const custom_field_types_1 = require("@tryghost/custom-field-types");
6
6
  const db_date_1 = require("../../lib/db-date");
7
- // A field's lifecycle state. `archived` is a soft state: the field drops out of
8
- // the values path but stays visible in the definition list (with its status) so
9
- // admins can find, rename, restore, or permanently delete it. The values mirror
10
- // schema.js's `isIn` constraint on the column — which is static config and can't
11
- // import this, so that one stays literal with a pointer back here.
7
+ // `archived` is soft: the field drops out of the values path but stays in the definition
8
+ // list so it can be renamed, restored or deleted. Mirrors schema.js's `isIn` on the
9
+ // column, which is static config and cannot import this.
12
10
  exports.FIELD_STATUS = { active: 'active', archived: 'archived' };
13
11
  exports.FieldStatusSchema = zod_1.z.enum([exports.FIELD_STATUS.active, exports.FIELD_STATUS.archived]);
14
- // The members_custom_fields row: the single source for the read projection and the
15
- // knex table type below. `type` is validated as the field-type enum here (the DB
16
- // only stores registered types), so the row already carries the narrow type and
17
- // the definition codec needs no cast. `status` travels with the row: it's part of
18
- // the read projection so the definition list can group active vs archived.
12
+ // The single source for the read projection and the knex table type below. `type` parses
13
+ // as the field-type enum, so the row carries the narrow type and no codec needs a cast.
19
14
  exports.DbCustomField = zod_1.z.object({
20
15
  id: zod_1.z.string(),
21
16
  key: zod_1.z.string(),
@@ -25,32 +20,28 @@ exports.DbCustomField = zod_1.z.object({
25
20
  created_at: db_date_1.DbDate,
26
21
  updated_at: db_date_1.DbDate.nullable()
27
22
  });
28
- // A member's stored value for one field. `value_text`/`value_json` are the raw
29
- // columns — which one carries the value, and how it decodes, is the storage
30
- // codec's business (see storage.ts), so they're plain nullable strings here.
23
+ // One part of a member's value. What a `path` means is storage.ts's business, so the row
24
+ // carries it as a plain string.
31
25
  exports.DbCustomFieldValue = zod_1.z.object({
32
26
  id: zod_1.z.string(),
33
27
  custom_field_id: zod_1.z.string(),
34
28
  member_id: zod_1.z.string(),
29
+ path: zod_1.z.string(),
30
+ // Nullable like the column, though nothing here writes a null: a part with no value
31
+ // has no row.
35
32
  value_text: zod_1.z.string().nullable(),
36
- value_json: zod_1.z.string().nullable(),
37
33
  created_at: db_date_1.DbDate,
38
34
  updated_at: db_date_1.DbDate.nullable()
39
35
  });
40
- // A composite value once decoded: sub-field keys to values, and no narrower. Which
41
- // sub-fields a composite has is its field type's business, not the row's.
36
+ // The field's key travels with the row so a value assembles without a second lookup.
42
37
  //
43
- // It doubles as the guard on a stored blob. A JSON column can just as easily hold an
44
- // array, a null or a bare number, and a record rejects all three so parsing against
45
- // this is what narrows the type, with no assertion needed.
46
- exports.StoredCompositeValue = zod_1.z.record(zod_1.z.string(), zod_1.z.unknown());
47
- // The value join a read needs: the field's identity and type travel with the
48
- // stored columns, so a row can be decoded without a second lookup. `type` is
49
- // parsed as the field-type enum, which narrows it with no cast.
50
- exports.DbCustomFieldValueWithField = zod_1.z.object({
38
+ // `type` takes no part in the assembly and is here as a gate: a value whose type has left
39
+ // the catalog is one the definitions list no longer returns either, so failing to parse
40
+ // is what drops it.
41
+ exports.DbCustomFieldLeaf = zod_1.z.object({
51
42
  member_id: zod_1.z.string(),
52
43
  key: zod_1.z.string(),
53
44
  type: custom_field_types_1.FieldTypeSchema,
54
- value_text: zod_1.z.string().nullable(),
55
- value_json: zod_1.z.string().nullable()
45
+ path: zod_1.z.string(),
46
+ value_text: zod_1.z.string()
56
47
  });
@@ -3,20 +3,15 @@ import type {Knex} from 'knex';
3
3
  import {FieldTypeSchema} from '@tryghost/custom-field-types';
4
4
  import {DbDate} from '../../lib/db-date';
5
5
 
6
- // A field's lifecycle state. `archived` is a soft state: the field drops out of
7
- // the values path but stays visible in the definition list (with its status) so
8
- // admins can find, rename, restore, or permanently delete it. The values mirror
9
- // schema.js's `isIn` constraint on the column — which is static config and can't
10
- // import this, so that one stays literal with a pointer back here.
6
+ // `archived` is soft: the field drops out of the values path but stays in the definition
7
+ // list so it can be renamed, restored or deleted. Mirrors schema.js's `isIn` on the
8
+ // column, which is static config and cannot import this.
11
9
  export const FIELD_STATUS = {active: 'active', archived: 'archived'} as const;
12
10
  export type FieldStatus = typeof FIELD_STATUS[keyof typeof FIELD_STATUS];
13
11
  export const FieldStatusSchema = z.enum([FIELD_STATUS.active, FIELD_STATUS.archived]);
14
12
 
15
- // The members_custom_fields row: the single source for the read projection and the
16
- // knex table type below. `type` is validated as the field-type enum here (the DB
17
- // only stores registered types), so the row already carries the narrow type and
18
- // the definition codec needs no cast. `status` travels with the row: it's part of
19
- // the read projection so the definition list can group active vs archived.
13
+ // The single source for the read projection and the knex table type below. `type` parses
14
+ // as the field-type enum, so the row carries the narrow type and no codec needs a cast.
20
15
  export const DbCustomField = z.object({
21
16
  id: z.string(),
22
17
  key: z.string(),
@@ -29,38 +24,33 @@ export const DbCustomField = z.object({
29
24
 
30
25
  type CustomFieldRow = z.infer<typeof DbCustomField>;
31
26
 
32
- // A member's stored value for one field. `value_text`/`value_json` are the raw
33
- // columns — which one carries the value, and how it decodes, is the storage
34
- // codec's business (see storage.ts), so they're plain nullable strings here.
27
+ // One part of a member's value. What a `path` means is storage.ts's business, so the row
28
+ // carries it as a plain string.
35
29
  export const DbCustomFieldValue = z.object({
36
30
  id: z.string(),
37
31
  custom_field_id: z.string(),
38
32
  member_id: z.string(),
33
+ path: z.string(),
34
+ // Nullable like the column, though nothing here writes a null: a part with no value
35
+ // has no row.
39
36
  value_text: z.string().nullable(),
40
- value_json: z.string().nullable(),
41
37
  created_at: DbDate,
42
38
  updated_at: DbDate.nullable()
43
39
  });
44
40
 
45
41
  type CustomFieldValueRow = z.infer<typeof DbCustomFieldValue>;
46
42
 
47
- // A composite value once decoded: sub-field keys to values, and no narrower. Which
48
- // sub-fields a composite has is its field type's business, not the row's.
43
+ // The field's key travels with the row so a value assembles without a second lookup.
49
44
  //
50
- // It doubles as the guard on a stored blob. A JSON column can just as easily hold an
51
- // array, a null or a bare number, and a record rejects all three so parsing against
52
- // this is what narrows the type, with no assertion needed.
53
- export const StoredCompositeValue = z.record(z.string(), z.unknown());
54
-
55
- // The value join a read needs: the field's identity and type travel with the
56
- // stored columns, so a row can be decoded without a second lookup. `type` is
57
- // parsed as the field-type enum, which narrows it with no cast.
58
- export const DbCustomFieldValueWithField = z.object({
45
+ // `type` takes no part in the assembly and is here as a gate: a value whose type has left
46
+ // the catalog is one the definitions list no longer returns either, so failing to parse
47
+ // is what drops it.
48
+ export const DbCustomFieldLeaf = z.object({
59
49
  member_id: z.string(),
60
50
  key: z.string(),
61
51
  type: FieldTypeSchema,
62
- value_text: z.string().nullable(),
63
- value_json: z.string().nullable()
52
+ path: z.string(),
53
+ value_text: z.string()
64
54
  });
65
55
 
66
56
  declare module 'knex/types/tables' {
@@ -1,35 +1,102 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.storageCodecFor = storageCodecFor;
4
- exports.storageColumnsFor = storageColumnsFor;
5
- const custom_field_types_1 = require("@tryghost/custom-field-types");
6
- const STORAGE_CODECS = {
7
- // The value is already a string by the time it gets here — the field type's
8
- // schema validated it — so encoding is identity.
9
- text: {
10
- column: 'value_text',
11
- encode: value => String(value),
12
- decode: stored => stored
13
- },
14
- json: {
15
- column: 'value_json',
16
- encode: value => JSON.stringify(value),
17
- decode: stored => JSON.parse(stored)
18
- }
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
19
4
  };
20
- function storageCodecFor(type) {
21
- return STORAGE_CODECS[custom_field_types_1.FIELD_TYPES[type].storageType];
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ROOT_PATH = void 0;
7
+ exports.leavesFor = leavesFor;
8
+ exports.leavesToWrite = leavesToWrite;
9
+ exports.valueFromLeaves = valueFromLeaves;
10
+ exports.valuesFromLeaves = valuesFromLeaves;
11
+ const errors_1 = __importDefault(require("@tryghost/errors"));
12
+ // A value is a tree; the database stores one row per leaf, under the path addressing it.
13
+ // A value with no parts is a single leaf at the root, a part is a leaf under its key, and
14
+ // a nested part is addressed with a dotted path.
15
+ //
16
+ // Nothing here may consult a field type. Values arrive already parsed against their own,
17
+ // and rows are rebuilt as what they were written as rather than as what the type says
18
+ // today, so a type that changes shape does not rewrite what members already have.
19
+ /** Also the notation a segment filter addresses a part by, so the two cannot drift. */
20
+ const SEPARATOR = '.';
21
+ /** Empty rather than null: a unique index does not constrain nulls, which would let the same leaf be stored twice. */
22
+ exports.ROOT_PATH = '';
23
+ function isRecord(value) {
24
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
22
25
  }
23
26
  /**
24
- * The value columns for a row, with the one this field type doesn't use
25
- * explicitly nulled the "exactly one column populated" invariant is written
26
- * out on every insert and update rather than left to convention.
27
+ * Values are rebuilt into plain objects, so a segment naming something every object
28
+ * already has would reach the prototype instead of the value and a write through
29
+ * `__proto__` reaches every object in the process. Paths come out of the database, so
30
+ * this holds here rather than trusting whatever put them there.
27
31
  */
28
- function storageColumnsFor(type, value) {
29
- const codec = storageCodecFor(type);
32
+ function isSafeSegment(segment) {
33
+ return !(segment in Object.prototype);
34
+ }
35
+ /** Every leaf a value names, at any depth, including the ones it names as empty. */
36
+ function leavesFor(value, path = exports.ROOT_PATH) {
37
+ if (typeof value === 'string') {
38
+ return [{ path, value_text: value }];
39
+ }
40
+ // Thrown rather than stored: the read path rejects a non-string row, so writing one
41
+ // loses the value with nothing to say it ever arrived.
42
+ if (!isRecord(value)) {
43
+ throw new errors_1.default.IncorrectUsageError({
44
+ message: `A custom field value must be a string or a record of them, not ${value === null ? 'null' : typeof value}.`
45
+ });
46
+ }
47
+ // Undefined means the value does not name the part, which is not the same as naming
48
+ // it empty: the first leaves what is stored alone, the second clears it.
49
+ return Object.entries(value)
50
+ .filter(([, part]) => part !== undefined)
51
+ .flatMap(([key, part]) => leavesFor(part, path === exports.ROOT_PATH ? key : `${path}${SEPARATOR}${key}`));
52
+ }
53
+ /** A value split into the leaves it sets and the paths it clears. */
54
+ function leavesToWrite(value) {
55
+ const named = leavesFor(value);
30
56
  return {
31
- value_text: null,
32
- value_json: null,
33
- [codec.column]: codec.encode(value)
57
+ set: named.filter(leaf => leaf.value_text !== ''),
58
+ cleared: named.filter(leaf => leaf.value_text === '').map(leaf => leaf.path)
34
59
  };
35
60
  }
61
+ /** The value a set of leaves adds up to, rebuilt to whatever depth their paths describe. */
62
+ function valueFromLeaves(leaves) {
63
+ const root = leaves.find(leaf => leaf.path === exports.ROOT_PATH);
64
+ if (root) {
65
+ return root.value_text;
66
+ }
67
+ const value = {};
68
+ for (const leaf of leaves) {
69
+ const segments = leaf.path.split(SEPARATOR);
70
+ if (!segments.every(isSafeSegment)) {
71
+ continue;
72
+ }
73
+ const last = segments.pop();
74
+ // A segment already holding a string is replaced rather than descended into: this
75
+ // runs over every member of a list response, so a contradictory pair of paths
76
+ // should cost one odd value rather than the whole response.
77
+ const parent = segments.reduce((target, segment) => {
78
+ if (!isRecord(target[segment])) {
79
+ target[segment] = {};
80
+ }
81
+ return target[segment];
82
+ }, value);
83
+ parent[last] = leaf.value_text;
84
+ }
85
+ return value;
86
+ }
87
+ /** Every member's values, keyed by member and then field key; a member with no rows is absent. */
88
+ function valuesFromLeaves(leaves) {
89
+ const byMemberAndField = new Map();
90
+ for (const { member_id: memberId, key, path, value_text: valueText } of leaves) {
91
+ const fields = byMemberAndField.get(memberId) ?? new Map();
92
+ const forField = fields.get(key) ?? [];
93
+ forField.push({ path, value_text: valueText });
94
+ fields.set(key, forField);
95
+ byMemberAndField.set(memberId, fields);
96
+ }
97
+ const byMember = new Map();
98
+ for (const [memberId, fields] of byMemberAndField) {
99
+ byMember.set(memberId, Object.fromEntries([...fields].map(([key, forField]) => [key, valueFromLeaves(forField)])));
100
+ }
101
+ return byMember;
102
+ }
@@ -1,47 +1,122 @@
1
- import {FIELD_TYPES, type FieldType, type StorageType} from '@tryghost/custom-field-types';
2
-
3
- // The backend half of the storage-type contract: the catalog says which storage
4
- // type a field type routes to, and this says what that means in the database —
5
- // the column, and how a value crosses the boundary. Adding a field type that
6
- // reuses an existing storage type needs nothing here.
7
-
8
- export type ValueColumn = 'value_text' | 'value_json';
9
-
10
- interface StorageCodec {
11
- column: ValueColumn;
12
- encode(value: unknown): string;
13
- decode(stored: string): unknown;
14
- }
15
-
16
- const STORAGE_CODECS = {
17
- // The value is already a string by the time it gets here — the field type's
18
- // schema validated it — so encoding is identity.
19
- text: {
20
- column: 'value_text',
21
- encode: value => String(value),
22
- decode: stored => stored
23
- },
24
- json: {
25
- column: 'value_json',
26
- encode: value => JSON.stringify(value),
27
- decode: stored => JSON.parse(stored)
28
- }
29
- } as const satisfies Record<StorageType, StorageCodec>;
1
+ import errors from '@tryghost/errors';
2
+
3
+ // A value is a tree; the database stores one row per leaf, under the path addressing it.
4
+ // A value with no parts is a single leaf at the root, a part is a leaf under its key, and
5
+ // a nested part is addressed with a dotted path.
6
+ //
7
+ // Nothing here may consult a field type. Values arrive already parsed against their own,
8
+ // and rows are rebuilt as what they were written as rather than as what the type says
9
+ // today, so a type that changes shape does not rewrite what members already have.
10
+
11
+ /** Also the notation a segment filter addresses a part by, so the two cannot drift. */
12
+ const SEPARATOR = '.';
13
+
14
+ /** Empty rather than null: a unique index does not constrain nulls, which would let the same leaf be stored twice. */
15
+ export const ROOT_PATH = '';
16
+
17
+ export interface Leaf {
18
+ path: string;
19
+ value_text: string;
20
+ }
21
+
22
+ export interface StoredLeaf extends Leaf {
23
+ member_id: string;
24
+ key: string;
25
+ }
30
26
 
31
- export function storageCodecFor(type: FieldType): StorageCodec {
32
- return STORAGE_CODECS[FIELD_TYPES[type].storageType];
27
+ function isRecord(value: unknown): value is Record<string, unknown> {
28
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
33
29
  }
34
30
 
35
31
  /**
36
- * The value columns for a row, with the one this field type doesn't use
37
- * explicitly nulled the "exactly one column populated" invariant is written
38
- * out on every insert and update rather than left to convention.
32
+ * Values are rebuilt into plain objects, so a segment naming something every object
33
+ * already has would reach the prototype instead of the value and a write through
34
+ * `__proto__` reaches every object in the process. Paths come out of the database, so
35
+ * this holds here rather than trusting whatever put them there.
39
36
  */
40
- export function storageColumnsFor(type: FieldType, value: unknown): Record<ValueColumn, string | null> {
41
- const codec = storageCodecFor(type);
37
+ function isSafeSegment(segment: string): boolean {
38
+ return !(segment in Object.prototype);
39
+ }
40
+
41
+ /** Every leaf a value names, at any depth, including the ones it names as empty. */
42
+ export function leavesFor(value: unknown, path: string = ROOT_PATH): Leaf[] {
43
+ if (typeof value === 'string') {
44
+ return [{path, value_text: value}];
45
+ }
46
+
47
+ // Thrown rather than stored: the read path rejects a non-string row, so writing one
48
+ // loses the value with nothing to say it ever arrived.
49
+ if (!isRecord(value)) {
50
+ throw new errors.IncorrectUsageError({
51
+ message: `A custom field value must be a string or a record of them, not ${value === null ? 'null' : typeof value}.`
52
+ });
53
+ }
54
+
55
+ // Undefined means the value does not name the part, which is not the same as naming
56
+ // it empty: the first leaves what is stored alone, the second clears it.
57
+ return Object.entries(value)
58
+ .filter(([, part]) => part !== undefined)
59
+ .flatMap(([key, part]) => leavesFor(part, path === ROOT_PATH ? key : `${path}${SEPARATOR}${key}`));
60
+ }
61
+
62
+ /** A value split into the leaves it sets and the paths it clears. */
63
+ export function leavesToWrite(value: unknown): {set: Leaf[], cleared: string[]} {
64
+ const named = leavesFor(value);
65
+
42
66
  return {
43
- value_text: null,
44
- value_json: null,
45
- [codec.column]: codec.encode(value)
67
+ set: named.filter(leaf => leaf.value_text !== ''),
68
+ cleared: named.filter(leaf => leaf.value_text === '').map(leaf => leaf.path)
46
69
  };
47
70
  }
71
+
72
+ /** The value a set of leaves adds up to, rebuilt to whatever depth their paths describe. */
73
+ export function valueFromLeaves(leaves: readonly Leaf[]): unknown {
74
+ const root = leaves.find(leaf => leaf.path === ROOT_PATH);
75
+ if (root) {
76
+ return root.value_text;
77
+ }
78
+
79
+ const value: Record<string, unknown> = {};
80
+ for (const leaf of leaves) {
81
+ const segments = leaf.path.split(SEPARATOR);
82
+ if (!segments.every(isSafeSegment)) {
83
+ continue;
84
+ }
85
+
86
+ const last = segments.pop() as string;
87
+ // A segment already holding a string is replaced rather than descended into: this
88
+ // runs over every member of a list response, so a contradictory pair of paths
89
+ // should cost one odd value rather than the whole response.
90
+ const parent = segments.reduce<Record<string, unknown>>((target, segment) => {
91
+ if (!isRecord(target[segment])) {
92
+ target[segment] = {};
93
+ }
94
+ return target[segment] as Record<string, unknown>;
95
+ }, value);
96
+ parent[last] = leaf.value_text;
97
+ }
98
+
99
+ return value;
100
+ }
101
+
102
+ /** Every member's values, keyed by member and then field key; a member with no rows is absent. */
103
+ export function valuesFromLeaves(leaves: readonly StoredLeaf[]): Map<string, Record<string, unknown>> {
104
+ const byMemberAndField = new Map<string, Map<string, Leaf[]>>();
105
+
106
+ for (const {member_id: memberId, key, path, value_text: valueText} of leaves) {
107
+ const fields = byMemberAndField.get(memberId) ?? new Map<string, Leaf[]>();
108
+ const forField = fields.get(key) ?? [];
109
+ forField.push({path, value_text: valueText});
110
+ fields.set(key, forField);
111
+ byMemberAndField.set(memberId, fields);
112
+ }
113
+
114
+ const byMember = new Map<string, Record<string, unknown>>();
115
+ for (const [memberId, fields] of byMemberAndField) {
116
+ byMember.set(memberId, Object.fromEntries(
117
+ [...fields].map(([key, forField]) => [key, valueFromLeaves(forField)])
118
+ ));
119
+ }
120
+
121
+ return byMember;
122
+ }