ghost 6.57.1 → 6.58.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 (747) hide show
  1. package/LICENSE +22 -0
  2. package/bin/validate-adapters.js +1 -5
  3. package/components/ghost-storage-base-3.0.0.tgz +0 -0
  4. package/components/tryghost-adapter-base-cache-0.2.0.tgz +0 -0
  5. package/components/tryghost-adapter-base-jobs-0.0.0.tgz +0 -0
  6. package/components/tryghost-adapter-base-redirects-0.0.0.tgz +0 -0
  7. package/components/tryghost-adapter-base-route-settings-0.0.0.tgz +0 -0
  8. package/components/tryghost-adapter-base-scheduling-0.2.2.tgz +0 -0
  9. package/components/tryghost-adapter-base-sso-0.1.2.tgz +0 -0
  10. package/components/tryghost-admin-api-schema-0.0.0.tgz +0 -0
  11. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  12. package/components/tryghost-i18n-0.0.0.tgz +0 -0
  13. package/components/tryghost-kg-card-factory-5.2.4.tgz +0 -0
  14. package/components/tryghost-kg-clean-basic-html-4.3.4.tgz +0 -0
  15. package/components/tryghost-kg-converters-1.2.5.tgz +0 -0
  16. package/components/tryghost-kg-default-cards-10.3.5.tgz +0 -0
  17. package/components/tryghost-kg-default-nodes-2.2.0.tgz +0 -0
  18. package/components/tryghost-kg-default-transforms-1.3.4.tgz +0 -0
  19. package/components/tryghost-kg-html-to-lexical-1.4.0.tgz +0 -0
  20. package/components/tryghost-kg-lexical-html-renderer-1.5.0.tgz +0 -0
  21. package/components/tryghost-kg-markdown-html-renderer-7.2.4.tgz +0 -0
  22. package/components/tryghost-kg-utils-1.1.4.tgz +0 -0
  23. package/components/tryghost-nql-string-0.0.0.tgz +0 -0
  24. package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
  25. package/core/boot.js +35 -3
  26. package/core/built/admin/assets/{about-BNojRaxf.js → about-B_BvSyLF.js} +1 -1
  27. package/core/built/admin/assets/{account-migration-DMS8JsM-.js → account-migration-oJUGxnzT.js} +1 -1
  28. package/core/built/admin/assets/activitypub/{account-migration-DHFbePl6.js → account-migration-BpDeIF3G.js} +8 -8
  29. package/core/built/admin/assets/activitypub/activitypub.js +4 -4
  30. package/core/built/admin/assets/activitypub/{avatar-CT4tS2_S.js → avatar-B0SpBPVL.js} +3 -3
  31. package/core/built/admin/assets/activitypub/{avatar-flipboard-ByYWKipo.js → avatar-flipboard-DKt1LGcE.js} +3 -3
  32. package/core/built/admin/assets/activitypub/{bluesky-sharing-srVnSHDz.js → bluesky-sharing-aPMtrS3L.js} +10 -10
  33. package/core/built/admin/assets/activitypub/{chunk-KS7C4IRE-D4HEcEwi.js → chunk-KS7C4IRE-CnTrsgmM.js} +26 -15
  34. package/core/built/admin/assets/activitypub/{content-formatters-C2sos5Ht.js → content-formatters-BORfuA9G.js} +318 -284
  35. package/core/built/admin/assets/activitypub/{copy-B1UBTOoZ.js → copy-D-YvNi2E.js} +2 -2
  36. package/core/built/admin/assets/activitypub/{dist-MPsb2WZ1.js → dist-Dqggkccr.js} +5 -5
  37. package/core/built/admin/assets/activitypub/{domain-DzcM9Dzo.js → domain-CPdiB27_.js} +10 -10
  38. package/core/built/admin/assets/activitypub/{edit-profile-CsQ_4xAk.js → edit-profile-BY2PawIe.js} +7 -7
  39. package/core/built/admin/assets/activitypub/{explore-CJe0DZBK.js → explore-Sp9bgAwq.js} +11 -11
  40. package/core/built/admin/assets/activitypub/{feed-CKV3qWON.js → feed-BjtF4ohL.js} +10 -10
  41. package/core/built/admin/assets/activitypub/{field-DckHfDJf.js → field-BDOYVTg6.js} +4 -4
  42. package/core/built/admin/assets/activitypub/{hash-Dlp8RM55.js → hash-DqXYHgzG.js} +2 -2
  43. package/core/built/admin/assets/activitypub/{inbox-CsPy4R0M.js → inbox-CZ7IOAMn.js} +2 -2
  44. package/core/built/admin/assets/activitypub/{inbox-DAsjzUUe.js → inbox-z1o2j1Tb.js} +11 -11
  45. package/core/built/admin/assets/activitypub/{label-DWyo9E5T.js → label-1jhN-qhD.js} +4 -4
  46. package/core/built/admin/assets/activitypub/layout-BRMezuty.js +1 -0
  47. package/core/built/admin/assets/activitypub/{moderation-B0Inc03M.js → moderation-CZudJi8N.js} +9 -9
  48. package/core/built/admin/assets/activitypub/{note-CSE54lER.js → note-nr_m_5yb.js} +8 -8
  49. package/core/built/admin/assets/activitypub/{notifications-RSwbKqdh.js → notifications-BdcyivbT.js} +10 -10
  50. package/core/built/admin/assets/activitypub/{onboarding-BFDkzeR2.js → onboarding-B4kwav9u.js} +2 -2
  51. package/core/built/admin/assets/activitypub/{onboarding-D6rcBdPe.js → onboarding-DZNElJYP.js} +3 -3
  52. package/core/built/admin/assets/activitypub/{preferences-CE231n2_.js → preferences-CNqy1x9C.js} +13 -13
  53. package/core/built/admin/assets/activitypub/{profile-aaQ1j1Eo.js → profile-DlY4rhbO.js} +15 -15
  54. package/core/built/admin/assets/activitypub/{reply-Cjb0Vs3m.js → reply-DASIN0Ec.js} +2 -2
  55. package/core/built/admin/assets/activitypub/{routes-Djb0mc9k.js → routes-CEHdEZzS.js} +31 -31
  56. package/core/built/admin/assets/activitypub/{separator-D8pROI5n.js → separator-B3k35sUV.js} +4 -4
  57. package/core/built/admin/assets/activitypub/{settings-DMc32IQl.js → settings-CrSV1cux.js} +6 -6
  58. package/core/built/admin/assets/activitypub/{site-DVIcsohX.js → site-CtBUCPBz.js} +6 -6
  59. package/core/built/admin/assets/activitypub/{step-1-9Yey9usF.js → step-1-D9jGWyAy.js} +7 -7
  60. package/core/built/admin/assets/activitypub/{step-2-DdYmKRXV.js → step-2-Jz_enuo9.js} +7 -7
  61. package/core/built/admin/assets/activitypub/{step-3-hdSkcjZU.js → step-3-KpUqpGWi.js} +12 -12
  62. package/core/built/admin/assets/activitypub/{tabs-CUqseYzQ.js → tabs-DvlsxTCI.js} +7 -7
  63. package/core/built/admin/assets/activitypub/{topic-filter-Coni6OXM.js → topic-filter-CIN1E2jg.js} +4 -4
  64. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-MeHTPMQv.js → use-activity-pub-queries-CATFd6YV.js} +4 -4
  65. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-B046AM38.js → use-navigate-with-base-path-iCkcbMGk.js} +2 -2
  66. package/core/built/admin/assets/activitypub/{use-reply-chain-data-1AO-Xob6.js → use-reply-chain-data-DJ0vhA8V.js} +6 -6
  67. package/core/built/admin/assets/activitypub/{users-BLeiOTHc.js → users-sgJ6GNZX.js} +12 -11
  68. package/core/built/admin/assets/activitypub/{x-BRfRYd16.js → x-Cy2jUiYM.js} +2 -2
  69. package/core/built/admin/assets/add-integration-modal-BW23PDYs.js +1 -0
  70. package/core/built/admin/assets/add-newsletter-modal-M0ls8BKO.js +1 -0
  71. package/core/built/admin/assets/{add-recommendation-modal-D-3LOrdd.js → add-recommendation-modal-B0NeBMzl.js} +1 -1
  72. package/core/built/admin/assets/{analytics-DTIMRhWw.js → analytics-BC2oR8ay.js} +1 -1
  73. package/core/built/admin/assets/announcement-bar-modal-pXBqjQE_.js +1 -0
  74. package/core/built/admin/assets/{api-keys-_iCT4dxr.js → api-keys-BgKr003d.js} +1 -1
  75. package/core/built/admin/assets/{api-keys-Dx4NeNpK.js → api-keys-Df6uv1dT.js} +1 -1
  76. package/core/built/admin/assets/{audience-DkknNqhZ.js → audience-BY8od8If.js} +1 -1
  77. package/core/built/admin/assets/{automated-emails-PayPCT8L.js → automated-emails-28c2Lbb3.js} +1 -1
  78. package/core/built/admin/assets/{automations-DvSU7QhW.js → automations-DCs6Wady.js} +1 -1
  79. package/core/built/admin/assets/{automations-162q5HjL.js → automations-Dzp_QnON.js} +1 -1
  80. package/core/built/admin/assets/{bluesky-sharing-yZ9A_jL8.js → bluesky-sharing-D0P42Cws.js} +1 -1
  81. package/core/built/admin/assets/{breadcrumb-CjOH7LCU.js → breadcrumb-MTj_cKLb.js} +1 -1
  82. package/core/built/admin/assets/card-CiiE7QfM.js +1 -0
  83. package/core/built/admin/assets/{chart-helpers-DGxvWnBT.js → chart-helpers-DcO3k_vA.js} +1 -1
  84. package/core/built/admin/assets/{checkbox-BSUcnNq_.js → checkbox-LmClhqcS.js} +1 -1
  85. package/core/built/admin/assets/{chunk.424.4a27628efdade15c2dab.js → chunk.408.1a258af360eb8d905508.js} +40 -37
  86. package/core/built/admin/assets/{chunk.571.40f093ed6d883993714e.js → chunk.492.a9da46ca956b10116774.js} +1342 -1342
  87. package/core/built/admin/assets/{chunk.571.40f093ed6d883993714e.js.LICENSE.txt → chunk.492.a9da46ca956b10116774.js.LICENSE.txt} +1 -1
  88. package/core/built/admin/assets/chunk.524.4c693c3a00ac91df34fa.js +25 -0
  89. package/core/built/admin/assets/chunk.582.20b600e329e58d12d7c2.js +27 -0
  90. package/core/built/admin/assets/code-editor-view-DqTOUnbM.js +1 -0
  91. package/core/built/admin/assets/code-editor-view-bA0lfxUL.js +1 -0
  92. package/core/built/admin/assets/{debounce-Cx5B_9OI.js → color-picker-CBlJMv3y.js} +2 -2
  93. package/core/built/admin/assets/color-picker-field-Cv9GOJQZ.js +1 -0
  94. package/core/built/admin/assets/{combobox-5rdgDrDV.js → combobox-BbPJzjCp.js} +1 -1
  95. package/core/built/admin/assets/command-BrhXIlDr.js +1 -0
  96. package/core/built/admin/assets/comments-Cu4rD1aJ.js +1 -0
  97. package/core/built/admin/assets/confirmation-provider-BEdrnHns.js +1 -0
  98. package/core/built/admin/assets/{content-api-modal-BtW4Z1-1.js → content-api-modal-BOFWRUkC.js} +1 -1
  99. package/core/built/admin/assets/{copy-field-CEp_XieW.js → copy-field-8biQvNMD.js} +1 -1
  100. package/core/built/admin/assets/custom-field-type-option-CNhAgHbJ.js +1 -0
  101. package/core/built/admin/assets/custom-integration-modal-CrGOI8IB.js +1 -0
  102. package/core/built/admin/assets/{date-range-select-B3Ij1Rqg.js → date-range-select-Bsh4gIek.js} +1 -1
  103. package/core/built/admin/assets/debounce-pscB46sW.js +1 -0
  104. package/core/built/admin/assets/design-and-theme-modal-DWZguwWX.js +23 -0
  105. package/core/built/admin/assets/{dirty-confirm-dialog-YE9L21gY.js → dirty-confirm-dialog-DmdMpgyN.js} +1 -1
  106. package/core/built/admin/assets/{disabled-sources-indicator-uIzbWL7K.js → disabled-sources-indicator-BLnmyWgt.js} +1 -1
  107. package/core/built/admin/assets/dist-4m6pHdc0.js +23 -0
  108. package/core/built/admin/assets/{dist-DCk0UkGx.js → dist-B6bJuxL3.js} +1 -1
  109. package/core/built/admin/assets/{dist-CbHbGtQ-.js → dist-BtaTA7Ii.js} +1 -1
  110. package/core/built/admin/assets/{dist-Q1p9iXwH.js → dist-CZgIBXQN.js} +1 -1
  111. package/core/built/admin/assets/dist-CiHx7vA5.js +6 -0
  112. package/core/built/admin/assets/{dist-ARN6XTcR.js → dist-ECAUrdGc.js} +1 -1
  113. package/core/built/admin/assets/dist-Z97pCNqS.js +11 -0
  114. package/core/built/admin/assets/{dist-t6AXpf_4.js → dist-zwwGt8Yf.js} +1 -1
  115. package/core/built/admin/assets/{domain-DzrOAPY8.js → domain-Dp9TRrFp.js} +1 -1
  116. package/core/built/admin/assets/{dropzone-CzhP33wa.js → dropzone-B21Entqz.js} +1 -1
  117. package/core/built/admin/assets/{edit-profile-oufs4R4n.js → edit-profile-DG6lpMsr.js} +1 -1
  118. package/core/built/admin/assets/editor-CPQOrhrn.js +8 -0
  119. package/core/built/admin/assets/{editor-DtxX27Jz.css → editor-DLioOiRN.css} +1 -1
  120. package/core/built/admin/assets/{embed-signup-form-modal-Ch3s6lqA.js → embed-signup-form-modal-CgWniDha.js} +2 -2
  121. package/core/built/admin/assets/esm-CXo5SEPy.js +2 -0
  122. package/core/built/admin/assets/{explore-CA-R01Br.js → explore-C3rzONJv.js} +1 -1
  123. package/core/built/admin/assets/{feed-ClC8_Gld.js → feed-DpUwS1cU.js} +1 -1
  124. package/core/built/admin/assets/{field-B3OPenbP.js → field-BQqLTgHM.js} +1 -1
  125. package/core/built/admin/assets/{filter-sources-CiPLnG9A.js → filter-sources-FgqoUIL7.js} +1 -1
  126. package/core/built/admin/assets/{filters-yFvybRMe.js → filters-D0uIMD8n.js} +1 -1
  127. package/core/built/admin/assets/filters-DZJT29GW.js +1 -0
  128. package/core/built/admin/assets/{first-promoter-modal-BC0UpYCD.js → first-promoter-modal-DJhQQheG.js} +1 -1
  129. package/core/built/admin/assets/framework-provider-CFLwK5uS.js +4 -0
  130. package/core/built/admin/assets/{ghost-d38c30b866e59f2e06a357b5eb844fe5.css → ghost-68f447a1796b93a1f128b649bac8506d.css} +1 -1
  131. package/core/built/admin/assets/{ghost-d683fbeb945b13155ea669caa5d8db41.js → ghost-ba408063a5922b458b4a8f216cf23251.js} +124 -342
  132. package/core/built/admin/assets/{ghost-dark-1bffd5099ba7a749303133efacf8480b.css → ghost-dark-28ae4899c92022857f3584694bcb50ef.css} +1 -1
  133. package/core/built/admin/assets/{gift-link-modal-BKRuqo-k.js → gift-link-modal-sdFLjND9.js} +1 -1
  134. package/core/built/admin/assets/{gift-links-CVuqLhsZ.js → gift-links-D7BJ9qfe.js} +1 -1
  135. package/core/built/admin/assets/{global-data-provider-RzW6Bnlf.js → global-data-provider-CixmTX00.js} +1 -1
  136. package/core/built/admin/assets/{growth-ClNAldfL.js → growth-0YYkBEeS.js} +1 -1
  137. package/core/built/admin/assets/{growth-Dz8OSG06.js → growth-DpjAPeQT.js} +2 -2
  138. package/core/built/admin/assets/{history-modal-DIVuQ5Ct.js → history-modal-BWJ4-cJ0.js} +1 -1
  139. package/core/built/admin/assets/{hooks-D6EE7BIt.js → hooks-CJepWnbB.js} +1 -1
  140. package/core/built/admin/assets/{html-field-CVZ2sMxw.js → html-field-C-oa5DEl.js} +1 -1
  141. package/core/built/admin/assets/{iframe-buffering-e8vH4-Qn.js → iframe-buffering-Ba9sDpi2.js} +1 -1
  142. package/core/built/admin/assets/{images-Ok8FFJ3A.js → images-3cQgwfTD.js} +1 -1
  143. package/core/built/admin/assets/{inbox-CMUVI6qb.js → inbox-Dz_JblyJ.js} +6 -6
  144. package/core/built/admin/assets/index-BOJzlYiz.js +67 -0
  145. package/core/built/admin/assets/index-Bid6LAyc.css +1 -0
  146. package/core/built/admin/assets/{input-group-2XU-iuh_.js → input-group-DpogNUIt.js} +1 -1
  147. package/core/built/admin/assets/{integrations-BT6_iJvu.js → integrations-B9yYgci5.js} +1 -1
  148. package/core/built/admin/assets/{invite-user-modal-DrZ7xxrZ.js → invite-user-modal-DCiQSiuD.js} +1 -1
  149. package/core/built/admin/assets/{koenig-editor-base-BPj4S760.js → koenig-editor-base-BApy8Q7C.js} +1 -1
  150. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +8328 -8294
  151. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +84 -84
  152. package/core/built/admin/assets/{koenig-lexical-Cdjv-m3_.js → koenig-lexical-2KuqRwZI.js} +33 -33
  153. package/core/built/admin/assets/{kpi-card-J8Mj1sDX.js → kpi-card-Ck3tT2Wg.js} +1 -1
  154. package/core/built/admin/assets/{kpi-card-awos0BMH.js → kpi-card-DDIxptaS.js} +1 -1
  155. package/core/built/admin/assets/{kpi-helpers-SEhvH-8E.js → kpi-helpers-ssbuy-hD.js} +1 -1
  156. package/core/built/admin/assets/{kpi-tabs-R6BjsKhd.js → kpi-tabs-BqoIM6bI.js} +1 -1
  157. package/core/built/admin/assets/{label-BMC3dzLI.js → label-DodDsMzn.js} +1 -1
  158. package/core/built/admin/assets/label-picker-Cvvbmzb5.js +1 -0
  159. package/core/built/admin/assets/{labels-DAPjKPy2.js → labels-DqvSR6JF.js} +1 -1
  160. package/core/built/admin/assets/layout-DkkGyev2.js +1 -0
  161. package/core/built/admin/assets/limit-service-BhiEvqcP.js +20 -0
  162. package/core/built/admin/assets/{link-helpers-Bzkk68GD.js → link-helpers-kpbU8TWh.js} +1 -1
  163. package/core/built/admin/assets/luxon-D1RVjjpq.js +1 -0
  164. package/core/built/admin/assets/member-custom-fields-CvGcpaiW.js +1 -0
  165. package/core/built/admin/assets/member-detail-CMCMZ47_.js +1 -0
  166. package/core/built/admin/assets/members-sRl6fNfq.js +11 -0
  167. package/core/built/admin/assets/{moderation-mPjYHgZW.js → moderation-KhKwGqI1.js} +1 -1
  168. package/core/built/admin/assets/{multi-select-combobox-CsoyHAiX.js → multi-select-combobox-nhCff0_K.js} +1 -1
  169. package/core/built/admin/assets/{navigation-modal-D_c0badj.js → navigation-modal-MDJExWDJ.js} +1 -1
  170. package/core/built/admin/assets/newsletter-BIBHumfV.js +1 -0
  171. package/core/built/admin/assets/newsletter-detail-modal-CfjBMH3v.js +1 -0
  172. package/core/built/admin/assets/{newsletter-emails-B-OLfrvr.js → newsletter-emails-ClDL-7dA.js} +1 -1
  173. package/core/built/admin/assets/newsletters-CBe-IPzK.js +1 -0
  174. package/core/built/admin/assets/{newsletters-Du0lzskS.js → newsletters-ILsexy-5.js} +1 -1
  175. package/core/built/admin/assets/{note-CSVsUKBm.js → note-HnNUOt4d.js} +1 -1
  176. package/core/built/admin/assets/{notifications-DV9FRtDw.js → notifications-Cj5e-258.js} +1 -1
  177. package/core/built/admin/assets/nql-lang-CaWrLesS.js +19 -0
  178. package/core/built/admin/assets/{offers-Um6ab71O.js → offers-DMxebaoK.js} +1 -1
  179. package/core/built/admin/assets/offers-container-modal-BHwHtixC.js +1 -0
  180. package/core/built/admin/assets/{onboarding-route--jL5KLPD.js → onboarding-route-D_4jp7Nq.js} +1 -1
  181. package/core/built/admin/assets/overview-CezDdg-F.js +1 -0
  182. package/core/built/admin/assets/overview-D5mM51Lj.js +1 -0
  183. package/core/built/admin/assets/{page-header-DAvDYKO9.js → page-header-VLPJfj_d.js} +1 -1
  184. package/core/built/admin/assets/{pages-BVMnpI2O.js → pages-Gchxp5c7.js} +1 -1
  185. package/core/built/admin/assets/{pintura-modal-jiUCxX0Q.js → pintura-modal-BXXJ8Iaa.js} +1 -1
  186. package/core/built/admin/assets/{portal-frame-D4D4nEO6.js → portal-frame-BLCvF8f-.js} +1 -1
  187. package/core/built/admin/assets/portal-modal-DN80tAcy.js +1 -0
  188. package/core/built/admin/assets/{post-analytics-BhAtiFJp.js → post-analytics-Gv81eN_g.js} +1 -1
  189. package/core/built/admin/assets/post-analytics-header-_ydSY8qO.js +1 -0
  190. package/core/built/admin/assets/{post-analytics-provider-DGg0FUqs.js → post-analytics-provider-O2YjZMhN.js} +1 -1
  191. package/core/built/admin/assets/{post-share-modal-DtFAC132.js → post-share-modal-CzYak_a5.js} +1 -1
  192. package/core/built/admin/assets/{posts-CxG7v0-M.js → posts-DgcUi2rc.js} +1 -1
  193. package/core/built/admin/assets/preferences-gneLb6LV.js +5 -0
  194. package/core/built/admin/assets/{preview-modal-DctYx20m.js → preview-modal-Bkq-tgVm.js} +1 -1
  195. package/core/built/admin/assets/profile-CzXmCNTC.js +1 -0
  196. package/core/built/admin/assets/{radio-group-CI5_Kf_d.js → radio-group-Dsq5ZEbV.js} +1 -1
  197. package/core/built/admin/assets/{referrers-B9QDAfYm.js → referrers-C3a2K8qB.js} +1 -1
  198. package/core/built/admin/assets/{router-provider-DSQfprxH.js → router-provider-CkB_ArLF.js} +1 -1
  199. package/core/built/admin/assets/{select-CkXqNJDJ.js → select-xuZAuDyS.js} +1 -1
  200. package/core/built/admin/assets/{settings-ZCj-W56o.js → settings-Ct8pabhr.js} +1 -1
  201. package/core/built/admin/assets/settings-KSWWQanQ.js +4 -0
  202. package/core/built/admin/assets/{settings-breadcrumbs-DIq31FwA.js → settings-breadcrumbs-DbPGu-Tu.js} +1 -1
  203. package/core/built/admin/assets/{share-modal-BNj14g79.js → share-modal-CdiUx9WM.js} +1 -1
  204. package/core/built/admin/assets/{slack-modal-C6SpMQ-q.js → slack-modal-BP--Z3jE.js} +1 -1
  205. package/core/built/admin/assets/{sort-button-B-k54ow2.js → sort-button-CjfKdlsL.js} +1 -1
  206. package/core/built/admin/assets/{source-icon-Cej3sZM6.js → source-icon-DHj1h6Ts.js} +1 -1
  207. package/core/built/admin/assets/{stats-B2KW8E2y.js → stats-C615kulJ.js} +1 -1
  208. package/core/built/admin/assets/{stats-view-CVzU2-p5.js → stats-view-BSMRdYiN.js} +1 -1
  209. package/core/built/admin/assets/{step-1-Dbn1iIMU.js → step-1-pSdGYtIF.js} +1 -1
  210. package/core/built/admin/assets/{step-2-BahnSuN6.js → step-2-T9Uy4eBD.js} +1 -1
  211. package/core/built/admin/assets/{step-3-CW04e2ZJ.js → step-3-D4uXe_XG.js} +2 -2
  212. package/core/built/admin/assets/{stripe-button-BkBZqaGG.js → stripe-button-Bh4p6Wud.js} +1 -1
  213. package/core/built/admin/assets/stripe-connect-modal-C2vTvepi.js +1 -0
  214. package/core/built/admin/assets/{table-BtLcEi1M.js → table-DghJxZMH.js} +1 -1
  215. package/core/built/admin/assets/{tabs-BaWRfg8j.js → tabs-l9c7FjzI.js} +1 -1
  216. package/core/built/admin/assets/tag-detail-DBw_M2-p.js +2 -0
  217. package/core/built/admin/assets/{tags-BbygUvNB.js → tags-DoWsAG6l.js} +1 -1
  218. package/core/built/admin/assets/{tags-BwCgmr5v.js → tags-EmEvJN75.js} +1 -1
  219. package/core/built/admin/assets/{testimonials-modal--bdzppyh.js → testimonials-modal-BZKePE2T.js} +1 -1
  220. package/core/built/admin/assets/tier-detail-modal-D65pVWsN.js +1 -0
  221. package/core/built/admin/assets/{tiers-Da6kxHQP.js → tiers-8iTFjJWg.js} +1 -1
  222. package/core/built/admin/assets/{toggle-group-CVl-Xigr.js → toggle-group-uJnl42w3.js} +1 -1
  223. package/core/built/admin/assets/{topic-filter-BoRHRbL1.js → topic-filter-BM7hz0kW.js} +1 -1
  224. package/core/built/admin/assets/transistor-modal-VZOGN3od.js +1 -0
  225. package/core/built/admin/assets/{unsplash-modal-CixoRAO2.js → unsplash-modal-CKGfACxF.js} +1 -1
  226. package/core/built/admin/assets/{use-analytics-data-BiWvcf1u.js → use-analytics-data-IxJcSjc9.js} +1 -1
  227. package/core/built/admin/assets/use-check-theme-limit-error-CGp3CGT5.js +1 -0
  228. package/core/built/admin/assets/use-feature-flag-CSq2Lwzt.js +1 -0
  229. package/core/built/admin/assets/use-filter-params-0hcJJuEJ.js +1 -0
  230. package/core/built/admin/assets/{use-filterable-api-iMpDZBxD.js → use-filterable-api-CQBtVG-l.js} +1 -1
  231. package/core/built/admin/assets/{use-growth-stats-Dom8xVzo.js → use-growth-stats-1NW-8eEZ.js} +1 -1
  232. package/core/built/admin/assets/use-limiter-ffwpsZPp.js +2 -0
  233. package/core/built/admin/assets/{use-pintura-config-Bj4QmiVk.js → use-pintura-config-BjFChXV1.js} +1 -1
  234. package/core/built/admin/assets/{use-pintura-editor-DvrslmPE.js → use-pintura-editor-Cb41oJyL.js} +1 -1
  235. package/core/built/admin/assets/{use-post-referrers-pxIHTXFs.js → use-post-referrers-Dp1BnTAK.js} +1 -1
  236. package/core/built/admin/assets/{use-reply-chain-data-CmFe0N6F.js → use-reply-chain-data-C0ZUTkfk.js} +1 -1
  237. package/core/built/admin/assets/{use-setting-group-Cmp_sy-d.js → use-setting-group-Bx8M6FL2.js} +1 -1
  238. package/core/built/admin/assets/{use-settings-navigation-BmROPrls.js → use-settings-navigation-BOZcat_V.js} +1 -1
  239. package/core/built/admin/assets/{use-simple-pagination-D5rL3YwO.js → use-simple-pagination-DsEGSJhe.js} +1 -1
  240. package/core/built/admin/assets/{use-tinybird-query-DP2dMaq7.js → use-tinybird-query-Ci_DiPkX.js} +1 -1
  241. package/core/built/admin/assets/{use-tinybird-token-DrK_skPQ.js → use-tinybird-token-CsUwc722.js} +1 -1
  242. package/core/built/admin/assets/use-upgrade-route-B3_DUiuT.js +1 -0
  243. package/core/built/admin/assets/{use-url-input-ChrUZBFf.js → use-url-input-BEZJpVgN.js} +2 -2
  244. package/core/built/admin/assets/user-detail-modal-595z1N5r.js +1 -0
  245. package/core/built/admin/assets/{users-B6hIds6n.js → users-DmGMPfw7.js} +1 -1
  246. package/core/built/admin/assets/virtual-list-D5ugXyZY.js +1 -0
  247. package/core/built/admin/assets/{web-D71mjB2r.js → web-CAta3lRE.js} +1 -1
  248. package/core/built/admin/assets/web-Ds9_wWJK.js +2 -0
  249. package/core/built/admin/assets/zapier-modal-CcfDZY53.js +1 -0
  250. package/core/built/admin/index.html +12 -12
  251. package/core/frontend/apps/private-blogging/lib/middleware.js +5 -58
  252. package/core/frontend/helpers/ghost_head.js +52 -0
  253. package/core/frontend/meta/asset-url.js +20 -9
  254. package/core/frontend/public/cards.manifest.json +1 -0
  255. package/core/frontend/public/ghost-stats.min.js +1 -1
  256. package/core/frontend/services/assets-minification/card-assets.js +121 -46
  257. package/core/frontend/services/assets-minification/index.js +13 -4
  258. package/core/frontend/services/data/fetch-data.js +7 -14
  259. package/core/frontend/services/llms/service.js +27 -6
  260. package/core/frontend/services/proxy.js +1 -0
  261. package/core/frontend/services/routing/api-adapter.js +11 -5
  262. package/core/frontend/services/routing/collection-router.js +1 -2
  263. package/core/frontend/services/routing/controllers/entry/markdown.js +63 -5
  264. package/core/frontend/services/routing/controllers/entry.js +1 -1
  265. package/core/frontend/services/routing/controllers/previews.js +0 -3
  266. package/core/frontend/services/routing/events.js +2 -1
  267. package/core/frontend/services/routing/parent-router.js +0 -22
  268. package/core/frontend/services/routing/router-manager.js +2 -2
  269. package/core/frontend/services/routing/static-pages-router.js +0 -1
  270. package/core/frontend/services/sitemap/handler.js +3 -4
  271. package/core/frontend/services/sitemap/site-map-manager.js +1 -2
  272. package/core/frontend/services/theme-engine/i18n/i18n.js +0 -33
  273. package/core/frontend/web/middleware/llms-discovery.js +2 -2
  274. package/core/frontend/web/routers/serve-public-file.js +33 -3
  275. package/core/frontend/web/site.js +5 -4
  276. package/core/server/adapters/jobs/InMemoryJobsBackend.js +113 -0
  277. package/core/server/adapters/redirects/S3RedirectsStore.js +33 -13
  278. package/core/server/adapters/route-settings/S3RouteSettingsStore.js +33 -13
  279. package/core/server/adapters/scheduling/scheduling-default.js +6 -7
  280. package/core/server/adapters/storage/LocalStorageBase.js +1 -3
  281. package/core/server/api/endpoints/index.js +0 -4
  282. package/core/server/api/endpoints/member-custom-fields.js +15 -4
  283. package/core/server/api/endpoints/pages.js +3 -11
  284. package/core/server/api/endpoints/posts.js +3 -11
  285. package/core/server/api/endpoints/utils/api-filter-utils.js +15 -1
  286. package/core/server/api/endpoints/utils/serializers/input/settings.js +4 -0
  287. package/core/server/api/endpoints/utils/serializers/output/index.js +0 -4
  288. package/core/server/api/endpoints/utils/serializers/output/mappers/email-batches.js +1 -1
  289. package/core/server/api/endpoints/utils/serializers/output/mappers/snippets.js +0 -1
  290. package/core/server/api/endpoints/utils/serializers/output/member-custom-fields.js +1 -0
  291. package/core/server/data/exporter/table-lists.js +4 -1
  292. package/core/server/data/migrations/utils/schema.js +5 -3
  293. package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +19 -7
  294. package/core/server/data/migrations/versions/6.58/2026-08-10-08-20-50-add-machine-payments-settings.js +41 -0
  295. package/core/server/data/migrations/versions/6.58/2026-08-10-08-21-00-add-machine-payment-events-table.js +18 -0
  296. package/core/server/data/migrations/versions/6.58/2026-08-10-15-24-09-reset-custom-field-definitions-for-underscored-keys.js +66 -0
  297. package/core/server/data/migrations/versions/6.58/2026-08-11-10-00-00-swap-custom-field-values-to-key-fk.js +65 -0
  298. package/core/server/data/migrations/versions/6.58/2026-08-11-10-34-25-remove-legacy-explore-integration.js +131 -0
  299. package/core/server/data/migrations/versions/6.58/2026-08-11-16-00-00-add-sort-order-to-members-custom-fields.js +13 -0
  300. package/core/server/data/migrations/versions/6.58/2026-08-11-21-22-48-rename-email-batches-provider-id.js +8 -0
  301. package/core/server/data/migrations/versions/6.58/2026-08-13-16-07-33-add-automation-runs-automation-id-created-at-index.js +28 -0
  302. package/core/server/data/schema/README.md +87 -0
  303. package/core/server/data/schema/commands.js +5 -2
  304. package/core/server/data/schema/default-settings/default-settings.json +27 -0
  305. package/core/server/data/schema/fixtures/fixtures.json +0 -21
  306. package/core/server/data/schema/schema.js +37 -9
  307. package/core/server/data/seeders/data-generator.js +3 -1
  308. package/core/server/data/seeders/importers/automation-action-edges-importer.js +38 -0
  309. package/core/server/data/seeders/importers/automation-action-revisions-importer.js +139 -0
  310. package/core/server/data/seeders/importers/automation-actions-importer.js +84 -0
  311. package/core/server/data/seeders/importers/automation-run-steps-importer.js +134 -0
  312. package/core/server/data/seeders/importers/automation-runs-importer.js +97 -0
  313. package/core/server/data/seeders/importers/automations-importer.js +78 -0
  314. package/core/server/data/seeders/importers/benefits-importer.js +1 -1
  315. package/core/server/data/seeders/importers/comment-reports-importer.js +5 -5
  316. package/core/server/data/seeders/importers/comments-importer.js +6 -6
  317. package/core/server/data/seeders/importers/email-batches-importer.js +9 -8
  318. package/core/server/data/seeders/importers/email-recipient-failures-importer.js +1 -1
  319. package/core/server/data/seeders/importers/email-recipients-importer.js +12 -12
  320. package/core/server/data/seeders/importers/emails-importer.js +7 -7
  321. package/core/server/data/seeders/importers/index.js +6 -0
  322. package/core/server/data/seeders/importers/labels-importer.js +4 -4
  323. package/core/server/data/seeders/importers/members-click-events-importer.js +4 -4
  324. package/core/server/data/seeders/importers/members-created-events-importer.js +5 -5
  325. package/core/server/data/seeders/importers/members-feedback-importer.js +7 -7
  326. package/core/server/data/seeders/importers/members-importer.js +5 -5
  327. package/core/server/data/seeders/importers/members-labels-importer.js +1 -1
  328. package/core/server/data/seeders/importers/members-login-events-importer.js +4 -4
  329. package/core/server/data/seeders/importers/members-newsletters-importer.js +1 -1
  330. package/core/server/data/seeders/importers/members-paid-subscription-events-importer.js +1 -1
  331. package/core/server/data/seeders/importers/members-products-importer.js +3 -3
  332. package/core/server/data/seeders/importers/members-status-events-importer.js +4 -4
  333. package/core/server/data/seeders/importers/members-stripe-customers-importer.js +3 -3
  334. package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +8 -8
  335. package/core/server/data/seeders/importers/members-subscribe-events-importer.js +3 -3
  336. package/core/server/data/seeders/importers/members-subscription-created-events-importer.js +4 -4
  337. package/core/server/data/seeders/importers/newsletters-importer.js +1 -1
  338. package/core/server/data/seeders/importers/offer-redemptions-importer.js +6 -6
  339. package/core/server/data/seeders/importers/offers-importer.js +4 -4
  340. package/core/server/data/seeders/importers/posts-authors-importer.js +1 -1
  341. package/core/server/data/seeders/importers/posts-importer.js +5 -5
  342. package/core/server/data/seeders/importers/posts-products-importer.js +1 -1
  343. package/core/server/data/seeders/importers/posts-tags-importer.js +1 -1
  344. package/core/server/data/seeders/importers/products-benefits-importer.js +1 -1
  345. package/core/server/data/seeders/importers/products-importer.js +1 -1
  346. package/core/server/data/seeders/importers/recommendation-click-events-importer.js +1 -1
  347. package/core/server/data/seeders/importers/recommendation-subscribe-events-importer.js +1 -1
  348. package/core/server/data/seeders/importers/recommendations-importer.js +1 -1
  349. package/core/server/data/seeders/importers/redirects-importer.js +1 -1
  350. package/core/server/data/seeders/importers/roles-users-importer.js +1 -1
  351. package/core/server/data/seeders/importers/stripe-prices-importer.js +1 -1
  352. package/core/server/data/seeders/importers/stripe-products-importer.js +1 -1
  353. package/core/server/data/seeders/importers/table-importer.js +92 -91
  354. package/core/server/data/seeders/importers/tags-importer.js +3 -3
  355. package/core/server/data/seeders/importers/users-importer.js +3 -3
  356. package/core/server/data/seeders/importers/web-mentions-importer.js +3 -3
  357. package/core/server/data/seeders/utils/database-date.js +14 -18
  358. package/core/server/data/seeders/utils/json-importer.js +1 -1
  359. package/core/server/data/seeders/utils/random.js +8 -7
  360. package/core/server/data/seeders/utils/topological-sort.js +5 -3
  361. package/core/server/data/tinybird/ARCHITECTURE.md +1 -2
  362. package/core/server/models/base/plugins/crud.js +0 -5
  363. package/core/server/models/index.js +2 -0
  364. package/core/server/models/machine-payment-event.js +18 -0
  365. package/core/server/models/member.js +24 -0
  366. package/core/server/models/settings.js +32 -3
  367. package/core/server/models/user.js +1 -1
  368. package/core/server/services/README.md +30 -0
  369. package/core/server/services/activitypub/activity-pub-service.js +2 -3
  370. package/core/server/services/adapter-manager/base-classes.js +3 -1
  371. package/core/server/services/auth/passwordreset.js +47 -51
  372. package/core/server/services/automations/database-automations-repository.js +19 -3
  373. package/core/server/services/automations/poll.js +2 -8
  374. package/core/server/services/comments/comments-service.js +4 -16
  375. package/core/server/services/comments/index.js +1 -3
  376. package/core/server/services/email-analytics/email-analytics-service-wrapper.js +68 -119
  377. package/core/server/services/email-analytics/email-analytics-service.js +73 -229
  378. package/core/server/services/email-analytics/event-processing-result.js +0 -10
  379. package/core/server/services/email-analytics/fetch-mailgun-events.js +25 -0
  380. package/core/server/services/email-analytics/index.js +35 -67
  381. package/core/server/services/email-analytics/lib/queries.js +88 -51
  382. package/core/server/services/email-analytics/newsletter-email-analytics-batch-processor.js +4 -1
  383. package/core/server/services/email-service/batch-sending-service.js +1 -1
  384. package/core/server/services/email-service/email-event-processor.js +12 -12
  385. package/core/server/services/email-service/events/email-bounced-event.js +8 -46
  386. package/core/server/services/email-service/events/email-delivered-event.js +8 -36
  387. package/core/server/services/email-service/events/email-opened-event.js +8 -36
  388. package/core/server/services/email-service/events/email-temporary-bounced-event.js +8 -46
  389. package/core/server/services/email-service/events/email-unsubscribed-event.js +8 -31
  390. package/core/server/services/email-service/events/spam-complaint-event.js +8 -31
  391. package/core/server/services/email-service/newsletter-email-event-storage.js +8 -7
  392. package/core/server/services/email-service/sending-service.js +1 -1
  393. package/core/server/services/email-suppression-list/mailgun-email-suppression-list.js +2 -2
  394. package/core/server/services/email-suppression-list/service.js +1 -3
  395. package/core/server/services/explore-ping/explore-ping-service.js +1 -6
  396. package/core/server/services/explore-ping/index.js +0 -2
  397. package/core/server/services/gifts/index.js +109 -3
  398. package/core/server/services/gifts/jobs/clean-gifts-job.js +1 -1
  399. package/core/server/services/gifts/jobs/send-gift-reminders-job.js +1 -1
  400. package/core/server/services/jobs-service/index.js +36 -0
  401. package/core/server/services/jobs-service/job.js +6 -0
  402. package/core/server/services/jobs-service/jobs-service.js +91 -0
  403. package/core/server/services/jobs-service/register-job-handlers.js +6 -0
  404. package/core/server/services/lib/mailgun-client.js +1 -1
  405. package/core/server/services/machine-payments/README.md +23 -0
  406. package/core/server/services/machine-payments/adapters/mpp-adapter.js +195 -0
  407. package/core/server/services/machine-payments/content-loader.js +89 -0
  408. package/core/server/services/machine-payments/eligibility.js +6 -0
  409. package/core/server/services/machine-payments/events/machine-payment-event-repository.js +58 -0
  410. package/core/server/services/machine-payments/events/machine-payment-event.js +46 -0
  411. package/core/server/services/machine-payments/index.js +87 -0
  412. package/core/server/services/machine-payments/pricing.js +54 -0
  413. package/core/server/services/machine-payments/service.js +275 -0
  414. package/core/server/services/machine-payments/stripe/deposit-address-store.js +157 -0
  415. package/core/server/services/machine-payments/stripe/payment-recorder.js +85 -0
  416. package/core/server/services/machine-payments/stripe/stripe-client-options.js +25 -0
  417. package/core/server/services/machine-payments/types.js +2 -0
  418. package/core/server/services/members/import-export/csv/parse.js +9 -1
  419. package/core/server/services/members/import-export/import/completion-email.js +61 -27
  420. package/core/server/services/members/import-export/import/email-template.js +28 -15
  421. package/core/server/services/members/import-export/import/importer.js +99 -51
  422. package/core/server/services/members/import-export/import/spool.js +1 -1
  423. package/core/server/services/members/import-export/index.js +24 -1
  424. package/core/server/services/members/members-api/services/member-bread-service.js +2 -2
  425. package/core/server/services/members/members-config-provider.js +12 -2
  426. package/core/server/services/members-custom-fields/actions.js +2 -1
  427. package/core/server/services/members-custom-fields/definitions-service.js +109 -27
  428. package/core/server/services/members-custom-fields/filter.js +199 -0
  429. package/core/server/services/members-custom-fields/key.js +37 -0
  430. package/core/server/services/members-custom-fields/queries.js +15 -0
  431. package/core/server/services/members-custom-fields/schema.js +1 -1
  432. package/core/server/services/members-custom-fields/values-service.js +18 -14
  433. package/core/server/services/members-events/last-seen-at-updater.js +1 -1
  434. package/core/server/services/oembed/oembed-service.js +8 -2
  435. package/core/server/services/permissions/can-this.js +4 -1
  436. package/core/server/services/posts/posts-service.js +18 -16
  437. package/core/server/services/public-config/config.js +1 -1
  438. package/core/server/services/route-settings/dynamic-routing-service.js +2 -0
  439. package/core/server/services/settings-helpers/settings-helpers.js +0 -1
  440. package/core/server/services/stats/posts-stats-service.js +0 -2
  441. package/core/server/services/stripe/stripe-api.js +2 -9
  442. package/core/server/web/api/endpoints/admin/middleware.js +0 -1
  443. package/core/server/web/api/endpoints/admin/routes.js +2 -3
  444. package/core/server/web/comments/routes.js +31 -8
  445. package/core/shared/config/defaults.json +17 -2
  446. package/core/shared/config/loader.js +11 -2
  447. package/core/shared/config/overrides.json +2 -0
  448. package/core/shared/config/secrets.js +71 -0
  449. package/core/shared/labs.js +4 -13
  450. package/core/shared/machine-payments.js +30 -0
  451. package/core/shared/private-site-access/index.js +68 -0
  452. package/package.json +48 -43
  453. package/pnpm-lock.yaml +1412 -1583
  454. package/pnpm-workspace.yaml +85 -48
  455. package/bin/validate-adapters.ts +0 -87
  456. package/components/tryghost-adapter-base-scheduling-0.2.1.tgz +0 -0
  457. package/components/tryghost-adapter-base-sso-0.1.1.tgz +0 -0
  458. package/components/tryghost-kg-card-factory-5.2.3.tgz +0 -0
  459. package/components/tryghost-kg-clean-basic-html-4.3.3.tgz +0 -0
  460. package/components/tryghost-kg-converters-1.2.4.tgz +0 -0
  461. package/components/tryghost-kg-default-cards-10.3.4.tgz +0 -0
  462. package/components/tryghost-kg-default-nodes-2.1.5.tgz +0 -0
  463. package/components/tryghost-kg-default-transforms-1.3.3.tgz +0 -0
  464. package/components/tryghost-kg-html-to-lexical-1.3.3.tgz +0 -0
  465. package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
  466. package/components/tryghost-kg-markdown-html-renderer-7.2.3.tgz +0 -0
  467. package/components/tryghost-kg-utils-1.1.3.tgz +0 -0
  468. package/content/themes/casper/assets/built/casper.js.map +0 -1
  469. package/content/themes/casper/assets/built/global.css.map +0 -1
  470. package/content/themes/casper/assets/built/screen.css.map +0 -1
  471. package/content/themes/source/assets/built/screen.css.map +0 -1
  472. package/content/themes/source/assets/built/source.js.map +0 -1
  473. package/core/built/admin/assets/activitypub/layout-DM_3pqsU.js +0 -1
  474. package/core/built/admin/assets/add-integration-modal-DU9ghGLS.js +0 -1
  475. package/core/built/admin/assets/add-newsletter-modal-DFxuQrYu.js +0 -1
  476. package/core/built/admin/assets/announcement-bar-modal-DGCcezB6.js +0 -1
  477. package/core/built/admin/assets/card-aUHX0hyR.js +0 -1
  478. package/core/built/admin/assets/chunk.524.dce1c51e0bc30fb2718e.js +0 -25
  479. package/core/built/admin/assets/chunk.582.3a430d5c6a744fd475b6.js +0 -27
  480. package/core/built/admin/assets/code-editor-view-BcyKABrG.js +0 -1
  481. package/core/built/admin/assets/color-picker-field-8NoSnhNI.js +0 -1
  482. package/core/built/admin/assets/command-BOYMbZa_.js +0 -1
  483. package/core/built/admin/assets/comments-BhNM5CYl.js +0 -1
  484. package/core/built/admin/assets/confirmation-modal-CYD7x3UQ.js +0 -1
  485. package/core/built/admin/assets/custom-integration-modal-bk3lNEuQ.js +0 -1
  486. package/core/built/admin/assets/design-and-theme-modal-B2xYKS1I.js +0 -23
  487. package/core/built/admin/assets/dist-Bg8bSvLA.js +0 -6
  488. package/core/built/admin/assets/dist-BnFdlebk.js +0 -11
  489. package/core/built/admin/assets/dist-CsvXCJSe.js +0 -23
  490. package/core/built/admin/assets/editor-BBKrunMK.js +0 -8
  491. package/core/built/admin/assets/filters-Bz0HsRie.js +0 -1
  492. package/core/built/admin/assets/framework-provider-CmtnPXIT.js +0 -4
  493. package/core/built/admin/assets/index-DUXM0NlS.js +0 -67
  494. package/core/built/admin/assets/index-Ds2iUoF6.css +0 -1
  495. package/core/built/admin/assets/label-picker-Bnlru5tO.js +0 -1
  496. package/core/built/admin/assets/layout-yR9rlvEw.js +0 -1
  497. package/core/built/admin/assets/limit-modal-COmLevuY.js +0 -1
  498. package/core/built/admin/assets/limit-service-9fwD33lU.js +0 -20
  499. package/core/built/admin/assets/member-custom-fields-BvFKEpxJ.js +0 -1
  500. package/core/built/admin/assets/member-detail-CWdPAe3p.js +0 -1
  501. package/core/built/admin/assets/members-Bnt0ih2R.js +0 -11
  502. package/core/built/admin/assets/newsletter-DR49NyGS.js +0 -1
  503. package/core/built/admin/assets/newsletter-detail-modal-B45pL1pb.js +0 -1
  504. package/core/built/admin/assets/newsletters-DGs5BaKH.js +0 -1
  505. package/core/built/admin/assets/nql-lang-B7faUf8D.js +0 -19
  506. package/core/built/admin/assets/offers-container-modal-B0hFS8nk.js +0 -1
  507. package/core/built/admin/assets/overview-B-ZPNqMo.js +0 -1
  508. package/core/built/admin/assets/overview-D96iwoJi.js +0 -1
  509. package/core/built/admin/assets/portal-modal-BLFdYeDH.js +0 -1
  510. package/core/built/admin/assets/post-analytics-header-DK27VjJm.js +0 -1
  511. package/core/built/admin/assets/preferences-CuWLISHa.js +0 -5
  512. package/core/built/admin/assets/profile-BsKYYtuL.js +0 -1
  513. package/core/built/admin/assets/settings-B4vM-rgV.js +0 -4
  514. package/core/built/admin/assets/stripe-connect-modal-CYDDrSJm.js +0 -1
  515. package/core/built/admin/assets/tag-detail-CmqiC32L.js +0 -1
  516. package/core/built/admin/assets/tier-detail-modal-BZmAsIy4.js +0 -1
  517. package/core/built/admin/assets/transistor-modal-CBPDqEGy.js +0 -1
  518. package/core/built/admin/assets/use-check-theme-limit-error-B14lZukR.js +0 -2
  519. package/core/built/admin/assets/use-feature-flag-BmJoc5WN.js +0 -1
  520. package/core/built/admin/assets/use-filter-params-ChoV72Hw.js +0 -1
  521. package/core/built/admin/assets/use-limiter-Dwg8ivgQ.js +0 -2
  522. package/core/built/admin/assets/user-detail-modal-XODpOqNC.js +0 -1
  523. package/core/built/admin/assets/virtual-list-Dnoafqbk.js +0 -1
  524. package/core/built/admin/assets/web-z71lA1Y3.js +0 -2
  525. package/core/built/admin/assets/zapier-modal-Bp507Um7.js +0 -1
  526. package/core/frontend/meta/og-type.ts +0 -13
  527. package/core/frontend/services/routing/api-adapter.ts +0 -157
  528. package/core/frontend/services/routing/config.ts +0 -59
  529. package/core/frontend/services/routing/controllers/entry/canonical-url.ts +0 -14
  530. package/core/frontend/services/routing/controllers/entry/gift-links.ts +0 -56
  531. package/core/frontend/services/routing/controllers/entry/markdown.ts +0 -72
  532. package/core/frontend/services/routing/controllers/entry.ts +0 -120
  533. package/core/frontend/services/routing/controllers/rss.ts +0 -66
  534. package/core/frontend/services/routing/permalink-adapter.ts +0 -10
  535. package/core/frontend/src/utils/privacy.ts +0 -66
  536. package/core/server/adapters/redirects/FileStore.ts +0 -113
  537. package/core/server/adapters/redirects/S3RedirectsStore.ts +0 -185
  538. package/core/server/adapters/route-settings/FileStore.ts +0 -144
  539. package/core/server/adapters/route-settings/S3RouteSettingsStore.ts +0 -199
  540. package/core/server/adapters/route-settings/utils.ts +0 -17
  541. package/core/server/adapters/scheduling/deps.d.ts +0 -1
  542. package/core/server/adapters/scheduling/error-capture.ts +0 -91
  543. package/core/server/adapters/scheduling/scheduling-default.ts +0 -248
  544. package/core/server/adapters/scheduling/utils.ts +0 -36
  545. package/core/server/adapters/sso/DefaultSSOAdapter.ts +0 -15
  546. package/core/server/adapters/storage/LocalFilesStorage.ts +0 -24
  547. package/core/server/adapters/storage/LocalImagesStorage.ts +0 -24
  548. package/core/server/adapters/storage/LocalMediaStorage.ts +0 -24
  549. package/core/server/adapters/storage/LocalStorageBase.ts +0 -342
  550. package/core/server/adapters/storage/S3Storage.ts +0 -506
  551. package/core/server/adapters/storage/utils.ts +0 -60
  552. package/core/server/api/endpoints/automation-action-links.ts +0 -46
  553. package/core/server/api/endpoints/explore.js +0 -18
  554. package/core/server/api/endpoints/gift-links.ts +0 -87
  555. package/core/server/api/endpoints/libraries.d.ts +0 -2
  556. package/core/server/api/endpoints/member-commenting.ts +0 -92
  557. package/core/server/api/endpoints/member-custom-fields.ts +0 -106
  558. package/core/server/api/endpoints/utils/api-filter-utils.ts +0 -22
  559. package/core/server/api/endpoints/utils/gift-link-access.ts +0 -82
  560. package/core/server/api/endpoints/utils/public-cache-keys.ts +0 -47
  561. package/core/server/api/endpoints/utils/serializers/input/member-commenting.ts +0 -29
  562. package/core/server/api/endpoints/utils/serializers/output/explore.js +0 -11
  563. package/core/server/api/endpoints/utils/serializers/output/gift-links.ts +0 -21
  564. package/core/server/api/endpoints/utils/serializers/output/member-commenting.ts +0 -8
  565. package/core/server/api/endpoints/utils/serializers/output/member-custom-fields.ts +0 -25
  566. package/core/server/api/endpoints/utils/serializers/output/members.d.ts +0 -20
  567. package/core/server/api/endpoints/utils/serializers/output/posts-csv-transform.ts +0 -46
  568. package/core/server/api/endpoints/utils/serializers/output/stream-csv-response.ts +0 -47
  569. package/core/server/api/endpoints/utils/validate-email-sender-fields.ts +0 -42
  570. package/core/server/data/importer/email-template.ts +0 -213
  571. package/core/server/data/seeders/utils/topological-sort.ts +0 -48
  572. package/core/server/lib/auto-filling-map.ts +0 -23
  573. package/core/server/lib/bootstrap-socket.ts +0 -114
  574. package/core/server/lib/case-keys.ts +0 -30
  575. package/core/server/lib/common/to-plain.ts +0 -25
  576. package/core/server/lib/db-date.ts +0 -8
  577. package/core/server/lib/get-inbox-links.ts +0 -276
  578. package/core/server/lib/post-revisions.ts +0 -152
  579. package/core/server/lib/soonest-timer.ts +0 -57
  580. package/core/server/models/base/plugins/bulk-filters.ts +0 -35
  581. package/core/server/models/role-utils.ts +0 -56
  582. package/core/server/services/activitypub/activity-pub-service.ts +0 -216
  583. package/core/server/services/adapter-manager/adapter-manager.ts +0 -253
  584. package/core/server/services/adapter-manager/adapter-paths.ts +0 -26
  585. package/core/server/services/adapter-manager/base-classes.ts +0 -22
  586. package/core/server/services/adapter-manager/index.ts +0 -16
  587. package/core/server/services/adapter-manager/types.ts +0 -45
  588. package/core/server/services/adapter-manager/utils.ts +0 -308
  589. package/core/server/services/auth/reset-authentication.ts +0 -72
  590. package/core/server/services/automations/automations-api.ts +0 -396
  591. package/core/server/services/automations/automations-repository.ts +0 -218
  592. package/core/server/services/automations/constants.ts +0 -4
  593. package/core/server/services/automations/database-automations-repository.ts +0 -1513
  594. package/core/server/services/automations/database-date.ts +0 -26
  595. package/core/server/services/automations/fake-wait-hours-multiplier.ts +0 -12
  596. package/core/server/services/automations/get-scheduler-idempotency-key.ts +0 -8
  597. package/core/server/services/automations/mailgun-message-id.ts +0 -27
  598. package/core/server/services/automations/poll.ts +0 -325
  599. package/core/server/services/automations/scheduler-poll-time.ts +0 -38
  600. package/core/server/services/automations/service.ts +0 -131
  601. package/core/server/services/automations/stale-lock-cutoff.ts +0 -5
  602. package/core/server/services/comments/email-templates/new-comment-reply.txt.ts +0 -25
  603. package/core/server/services/comments/email-templates/new-comment.txt.ts +0 -25
  604. package/core/server/services/comments/email-templates/report.txt.ts +0 -34
  605. package/core/server/services/custom-redirects/redirect-config-parser.ts +0 -148
  606. package/core/server/services/custom-redirects/redirects-service.ts +0 -181
  607. package/core/server/services/custom-redirects/utils.ts +0 -7
  608. package/core/server/services/donations/donation-bookshelf-repository.ts +0 -63
  609. package/core/server/services/donations/donation-payment-event.ts +0 -51
  610. package/core/server/services/email-address/email-address-parser.d.ts +0 -12
  611. package/core/server/services/email-address/email-address-service.ts +0 -241
  612. package/core/server/services/email-analytics/automation-email-analytics-batch-processor.ts +0 -130
  613. package/core/server/services/email-analytics/batch-event-processor.ts +0 -18
  614. package/core/server/services/email-analytics/email-analytics-provider-mailgun.js +0 -57
  615. package/core/server/services/email-analytics/event-processing-result.ts +0 -66
  616. package/core/server/services/email-analytics/events/start-automation-email-analytics-job-event.ts +0 -14
  617. package/core/server/services/email-analytics/events/start-email-analytics-job-event.ts +0 -15
  618. package/core/server/services/email-analytics/index.ts +0 -109
  619. package/core/server/services/email-analytics/jobs/email-analytics-job-scheduler.ts +0 -126
  620. package/core/server/services/email-analytics/lib/queries.ts +0 -341
  621. package/core/server/services/email-service/domain-warming-service.ts +0 -91
  622. package/core/server/services/explore/explore-service.js +0 -59
  623. package/core/server/services/explore/index.js +0 -18
  624. package/core/server/services/explore-ping/explore-ping-service.ts +0 -198
  625. package/core/server/services/explore-ping/index.ts +0 -42
  626. package/core/server/services/files/file-type-utils.ts +0 -56
  627. package/core/server/services/gift-links/actions.ts +0 -51
  628. package/core/server/services/gift-links/codec.ts +0 -16
  629. package/core/server/services/gift-links/index.ts +0 -20
  630. package/core/server/services/gift-links/models.ts +0 -22
  631. package/core/server/services/gift-links/schema.ts +0 -33
  632. package/core/server/services/gift-links/serializers.ts +0 -20
  633. package/core/server/services/gift-links/service.ts +0 -95
  634. package/core/server/services/gifts/constants.ts +0 -3
  635. package/core/server/services/gifts/email-templates/gift-purchase-confirmation.ts +0 -43
  636. package/core/server/services/gifts/email-templates/gift-reminder.ts +0 -50
  637. package/core/server/services/gifts/gift-bookshelf-repository.ts +0 -366
  638. package/core/server/services/gifts/gift-checkout-offer.ts +0 -127
  639. package/core/server/services/gifts/gift-codec.ts +0 -62
  640. package/core/server/services/gifts/gift-controller.ts +0 -51
  641. package/core/server/services/gifts/gift-email-renderer.ts +0 -97
  642. package/core/server/services/gifts/gift-email-service.ts +0 -158
  643. package/core/server/services/gifts/gift-reminder-scheduler.ts +0 -107
  644. package/core/server/services/gifts/gift-schema.ts +0 -39
  645. package/core/server/services/gifts/gift-service-wrapper.js +0 -142
  646. package/core/server/services/gifts/gift-service.ts +0 -1034
  647. package/core/server/services/gifts/gift.ts +0 -234
  648. package/core/server/services/identity-tokens/identity-token-service.ts +0 -36
  649. package/core/server/services/indexnow-ping/constants.ts +0 -32
  650. package/core/server/services/indexnow-ping/index.ts +0 -35
  651. package/core/server/services/indexnow-ping/indexnow-ping-service.ts +0 -311
  652. package/core/server/services/internal-keys/index.ts +0 -53
  653. package/core/server/services/lib/dynamic-redirect-manager.d.ts +0 -20
  654. package/core/server/services/lib/in-memory-repository.ts +0 -78
  655. package/core/server/services/lib/member-signup-contexts.ts +0 -22
  656. package/core/server/services/link-redirection/link-redirect.ts +0 -24
  657. package/core/server/services/member-welcome-emails/constants.ts +0 -35
  658. package/core/server/services/members/commenting/index.ts +0 -2
  659. package/core/server/services/members/commenting/member-commenting-codec.ts +0 -73
  660. package/core/server/services/members/commenting/member-commenting.ts +0 -69
  661. package/core/server/services/members/create-paid-member-shim.ts +0 -40
  662. package/core/server/services/members/import-export/config.ts +0 -34
  663. package/core/server/services/members/import-export/csv/formula.ts +0 -14
  664. package/core/server/services/members/import-export/csv/index.ts +0 -3
  665. package/core/server/services/members/import-export/csv/parse.ts +0 -69
  666. package/core/server/services/members/import-export/csv/serialize.ts +0 -18
  667. package/core/server/services/members/import-export/export/exporter.ts +0 -325
  668. package/core/server/services/members/import-export/import/completion-email.ts +0 -146
  669. package/core/server/services/members/import-export/import/importer.ts +0 -489
  670. package/core/server/services/members/import-export/import/reader.ts +0 -33
  671. package/core/server/services/members/import-export/import/row.ts +0 -56
  672. package/core/server/services/members/import-export/import/spool.ts +0 -38
  673. package/core/server/services/members/import-export/index.ts +0 -143
  674. package/core/server/services/members/index.d.ts +0 -32
  675. package/core/server/services/members-custom-fields/actions.ts +0 -62
  676. package/core/server/services/members-custom-fields/codec.ts +0 -13
  677. package/core/server/services/members-custom-fields/config.ts +0 -38
  678. package/core/server/services/members-custom-fields/definitions-service.ts +0 -413
  679. package/core/server/services/members-custom-fields/index.ts +0 -50
  680. package/core/server/services/members-custom-fields/models.ts +0 -15
  681. package/core/server/services/members-custom-fields/queries.ts +0 -13
  682. package/core/server/services/members-custom-fields/schema.ts +0 -71
  683. package/core/server/services/members-custom-fields/serializers.ts +0 -22
  684. package/core/server/services/members-custom-fields/storage.ts +0 -122
  685. package/core/server/services/members-custom-fields/values-service.ts +0 -269
  686. package/core/server/services/notifications/notification-email.ts +0 -66
  687. package/core/server/services/notifications/sanitize-email-html.ts +0 -35
  688. package/core/server/services/post-scheduling/index.ts +0 -13
  689. package/core/server/services/post-scheduling/post-scheduling.ts +0 -133
  690. package/core/server/services/recommendations/service/bookshelf-click-event-repository.ts +0 -49
  691. package/core/server/services/recommendations/service/bookshelf-recommendation-repository.ts +0 -117
  692. package/core/server/services/recommendations/service/bookshelf-repository.ts +0 -192
  693. package/core/server/services/recommendations/service/bookshelf-subscribe-event-repository.ts +0 -49
  694. package/core/server/services/recommendations/service/click-event.ts +0 -32
  695. package/core/server/services/recommendations/service/in-memory-recommendation-repository.ts +0 -20
  696. package/core/server/services/recommendations/service/incoming-recommendation-controller.ts +0 -51
  697. package/core/server/services/recommendations/service/incoming-recommendation-email-renderer.ts +0 -37
  698. package/core/server/services/recommendations/service/incoming-recommendation-service.ts +0 -159
  699. package/core/server/services/recommendations/service/index.ts +0 -15
  700. package/core/server/services/recommendations/service/libraries.d.ts +0 -2
  701. package/core/server/services/recommendations/service/recommendation-controller.ts +0 -257
  702. package/core/server/services/recommendations/service/recommendation-metadata-service.ts +0 -132
  703. package/core/server/services/recommendations/service/recommendation-repository.ts +0 -13
  704. package/core/server/services/recommendations/service/recommendation-service.ts +0 -285
  705. package/core/server/services/recommendations/service/recommendation.ts +0 -200
  706. package/core/server/services/recommendations/service/subscribe-event.ts +0 -32
  707. package/core/server/services/recommendations/service/unsafe-data.ts +0 -217
  708. package/core/server/services/recommendations/service/well-known-service.ts +0 -47
  709. package/core/server/services/remote-flags/index.ts +0 -101
  710. package/core/server/services/remote-flags/remote-flags-service.ts +0 -236
  711. package/core/server/services/remote-flags/resolve.ts +0 -108
  712. package/core/server/services/route-settings/route-settings-parser.ts +0 -455
  713. package/core/server/services/route-settings/validation-errors.ts +0 -271
  714. package/core/server/services/route-settings/yaml-parser.d.ts +0 -4
  715. package/core/server/services/slack-ping/constants.ts +0 -15
  716. package/core/server/services/slack-ping/index.ts +0 -35
  717. package/core/server/services/slack-ping/slack-ping-service.ts +0 -295
  718. package/core/server/services/staff/email-templates/donation.txt.ts +0 -26
  719. package/core/server/services/staff/email-templates/gift.txt.ts +0 -27
  720. package/core/server/services/staff/email-templates/new-free-signup.txt.ts +0 -15
  721. package/core/server/services/staff/email-templates/new-gift-subscription.txt.ts +0 -26
  722. package/core/server/services/staff/email-templates/new-milestone-received.txt.ts +0 -20
  723. package/core/server/services/staff/email-templates/new-paid-cancellation.txt.ts +0 -15
  724. package/core/server/services/staff/email-templates/new-paid-started.txt.ts +0 -15
  725. package/core/server/services/staff/email-templates/recommendation-received.txt.ts +0 -23
  726. package/core/server/services/staff/email-templates/types.ts +0 -13
  727. package/core/server/services/url/lazy-find-resource.ts +0 -104
  728. package/core/server/services/url/lazy-url-service.ts +0 -647
  729. package/core/server/services/url/permalink-matcher.ts +0 -119
  730. package/core/server/services/url/router-filter.ts +0 -80
  731. package/core/server/services/verification/verification-webhook-service.ts +0 -137
  732. package/core/shared/config/helpers.ts +0 -133
  733. package/core/shared/config/index.ts +0 -12
  734. package/core/shared/config/loader.ts +0 -87
  735. package/core/shared/config/utils.ts +0 -104
  736. package/core/shared/errify.ts +0 -31
  737. package/core/shared/events-ts/index.ts +0 -8
  738. package/core/shared/events-ts/post-deleted-event.ts +0 -15
  739. package/core/shared/events-ts/posts-bulk-add-tags-event.ts +0 -13
  740. package/core/shared/events-ts/posts-bulk-destroyed-event.ts +0 -13
  741. package/core/shared/events-ts/posts-bulk-featured-event.ts +0 -13
  742. package/core/shared/events-ts/posts-bulk-unfeatured-event.ts +0 -13
  743. package/core/shared/events-ts/posts-bulk-unpublished-event.ts +0 -13
  744. package/core/shared/events-ts/posts-bulk-unscheduled-event.ts +0 -13
  745. package/core/shared/labs-flag-overrides.ts +0 -35
  746. package/core/shared/one-at-a-time.ts +0 -47
  747. package/core/shared/url-utils.ts +0 -20
@@ -1,1513 +0,0 @@
1
- import errors from '@tryghost/errors';
2
- import tpl from '@tryghost/tpl';
3
- import crypto from 'node:crypto';
4
- import ObjectId from 'bson-objectid';
5
- import {dequal} from 'dequal';
6
- import {type Knex} from 'knex';
7
- // @ts-expect-error This module currently lacks type definitions.
8
- import lexicalLib from '../../lib/lexical';
9
- import urlUtils from '../../../shared/url-utils';
10
- import {DEFAULT_EMAIL_DESIGN_SETTING_SLUG, MEMBER_WELCOME_EMAIL_SLUGS} from '../member-welcome-emails/constants';
11
- import type {
12
- AutomatedEmailEvents,
13
- Automation,
14
- AutomationAction,
15
- AutomationEdge,
16
- AutomationEmailStats,
17
- AutomationSummary,
18
- AutomationStepTerminalStatus,
19
- AutomationStepToRun,
20
- AutomationsRepository,
21
- EditAutomationData,
22
- Page
23
- } from './automations-repository';
24
- import {fromDatabaseDate, toDatabaseDate, type DatabaseDate} from './database-date';
25
- import {getStaleLockCutoff} from './stale-lock-cutoff';
26
- import type {ExclusifyUnion, ReadonlyDeep} from 'type-fest';
27
-
28
- const HOUR_MS = 60 * 60 * 1000;
29
- const DEFAULT_WELCOME_EMAIL_AUTOMATIONS = [{
30
- name: 'Free member welcome flow',
31
- slug: MEMBER_WELCOME_EMAIL_SLUGS.free
32
- }, {
33
- name: 'Paid member welcome flow',
34
- slug: MEMBER_WELCOME_EMAIL_SLUGS.paid
35
- }];
36
-
37
- const messages = {
38
- invalidAutomationActionRevision: 'Automation action "{actionId}" of type "{actionType}" is missing required revision field "{field}".',
39
- conflictingAutomationActionId: 'Automation action "{actionId}" already exists and cannot be inserted.',
40
- conflictingAutomationActionType: 'Automation action "{actionId}" already exists with a different type.',
41
- defaultEmailDesignSettingNotFound: 'Default automated email design setting not found.'
42
- };
43
-
44
- const DEFAULT_EMAIL_DESIGN_SETTING_REFERENCE = DEFAULT_EMAIL_DESIGN_SETTING_SLUG;
45
-
46
- interface AutomationRow {
47
- id: string;
48
- slug: string;
49
- name: string;
50
- status: string;
51
- created_at: DatabaseDate;
52
- updated_at: DatabaseDate;
53
- }
54
-
55
-
56
- interface ActionRow {
57
- id: string;
58
- type: 'wait' | 'send_email';
59
- wait_hours: number | null;
60
- email_subject: string | null;
61
- email_lexical: string | null;
62
- email_design_setting_id: string | null;
63
- }
64
-
65
- type ActionStatsRow = {
66
- action_id: string;
67
- email_clicked_count: number | null;
68
- email_sent_count: number | null;
69
- email_opened_count: number | null;
70
- };
71
-
72
- type ActionLinkRow = {
73
- url: string;
74
- clicked_count: string | number;
75
- };
76
-
77
- type ActionRevisionRow = {
78
- action_id: string;
79
- created_at: DatabaseDate;
80
- wait_hours: number | null;
81
- email_subject: string | null;
82
- email_lexical: string | null;
83
- email_design_setting_id: string | null;
84
- };
85
-
86
- interface EdgeRow {
87
- source_action_id: string;
88
- target_action_id: string;
89
- }
90
-
91
- type NextActionRevisionRow = {
92
- automation_id: string;
93
- action_id: string;
94
- automation_action_revision_id: string;
95
- type: 'wait' | 'send_email';
96
- wait_hours: number | null;
97
- };
98
-
99
- type StepToRunRow = {
100
- id: string;
101
- locked_by: string;
102
- automation_run_id: string;
103
- automation_id: string;
104
- automation_slug: string;
105
- automation_status: 'inactive' | 'active';
106
- member_id: string | null;
107
- member_email: string;
108
- action_id: string;
109
- automation_action_revision_id: string;
110
- type: string;
111
- ready_at: DatabaseDate;
112
- step_attempts: number;
113
- wait_hours: number | null;
114
- email_subject: string | null;
115
- email_lexical: string | null;
116
- email_design_setting_id: string | null;
117
- };
118
-
119
- type WaitActionData = Extract<AutomationAction, {type: 'wait'}>['data'];
120
- type SendEmailActionData = Extract<AutomationAction, {type: 'send_email'}>['data'];
121
- type RevisionDataFor<ActionDataT> = {
122
- [FieldT in keyof ActionDataT]: ActionRevisionRow[FieldT & keyof ActionRevisionRow];
123
- };
124
- type WaitRevisionData = RevisionDataFor<WaitActionData>;
125
- type SendEmailRevisionData = RevisionDataFor<SendEmailActionData>;
126
-
127
- type ActionToInsert = {
128
- id: string;
129
- created_at: string;
130
- updated_at: string;
131
- automation_id: string;
132
- type: AutomationAction['type'];
133
- };
134
- type ActionRevisionToInsert = {
135
- actionId: string;
136
- action: AutomationAction;
137
- createdAt: string;
138
- };
139
-
140
- export function createDatabaseAutomationsRepository({
141
- knex,
142
- fakeWaitHoursMultiplier
143
- }: {
144
- knex: Knex;
145
- fakeWaitHoursMultiplier: number | null;
146
- }): AutomationsRepository {
147
- return {
148
- async browse(): Promise<Page<AutomationSummary>> {
149
- return await knex.transaction(async (trx) => {
150
- await ensureDefaultAutomations(trx);
151
- const rows = await loadAutomations(trx);
152
- return {
153
- data: rows.map(row => buildAutomationSummary(row)),
154
- meta: {
155
- pagination: buildPagination(rows.length)
156
- }
157
- };
158
- });
159
- },
160
-
161
- async getById(id: string): Promise<Automation | null> {
162
- return await knex.transaction(async (trx) => {
163
- const automation = await loadAutomation(trx, id);
164
-
165
- if (!automation) {
166
- return null;
167
- }
168
-
169
- return await buildAutomation(trx, automation);
170
- });
171
- },
172
-
173
- async getAutomationActionLinks(automationId, actionId) {
174
- const action = await knex('automation_actions')
175
- .select('id')
176
- .where({
177
- id: actionId,
178
- automation_id: automationId
179
- })
180
- .whereNull('deleted_at')
181
- .first();
182
-
183
- if (!action) {
184
- return null;
185
- }
186
-
187
- const rows = await knex('redirects as redirects')
188
- .countDistinct({clicked_count: 'members_click_events.member_id'})
189
- .select<ActionLinkRow[]>(knex.raw('MIN(??) as ??', ['redirects.to', 'url']))
190
- .innerJoin('automation_action_revisions as revisions', 'revisions.id', 'redirects.automation_action_revision_id')
191
- .leftJoin('members_click_events', 'members_click_events.redirect_id', 'redirects.id')
192
- .where('revisions.action_id', actionId)
193
- .whereNotNull('redirects.to_hash')
194
- .groupBy('redirects.to_hash')
195
- .orderBy('clicked_count', 'desc')
196
- .orderBy('url', 'asc');
197
-
198
- return rows.map(row => ({
199
- url: urlUtils.transformReadyToAbsolute(row.url),
200
- clicked_count: Number(row.clicked_count)
201
- }));
202
- },
203
-
204
- async edit(id: string, data: EditAutomationData): Promise<Automation | null> {
205
- return await knex.transaction(async (trx) => {
206
- const automation = await loadAutomation(trx, id);
207
-
208
- if (!automation) {
209
- return null;
210
- }
211
-
212
- const now = new Date();
213
-
214
- const updatedAutomation = await updateAutomation(trx, {
215
- ...automation,
216
- status: data.status,
217
- updated_at: toDatabaseDate(now)
218
- });
219
-
220
- await replaceAutomationGraph(trx, updatedAutomation.id, data.actions, data.edges);
221
-
222
- if (updatedAutomation.status === 'inactive') {
223
- await cancelCancelablePendingStepsForAutomation(trx, updatedAutomation.id, now);
224
- }
225
-
226
- return await buildAutomation(trx, updatedAutomation);
227
- });
228
- },
229
-
230
- async trigger(options: {
231
- memberEmail: string;
232
- memberId: string;
233
- memberStatus: 'free' | 'paid';
234
- }): Promise<void> {
235
- return await knex.transaction(trx => trigger(trx, {
236
- ...options,
237
- fakeWaitHoursMultiplier
238
- }));
239
- },
240
-
241
- async fetchAndLockSteps(limit: number): Promise<{
242
- steps: AutomationStepToRun[],
243
- nextStepReadyAt: null | Date;
244
- }> {
245
- return await knex.transaction(trx => fetchAndLockSteps(trx, limit));
246
- },
247
-
248
- async finishStepAndEnqueueNext(step: AutomationStepToRun): Promise<Date | null> {
249
- return await knex.transaction(trx => finishStepAndEnqueueNext(trx, {
250
- step,
251
- fakeWaitHoursMultiplier
252
- }));
253
- },
254
-
255
- async markStepTerminal(step: AutomationStepToRun, status: AutomationStepTerminalStatus): Promise<boolean> {
256
- return await knex.transaction(trx => markStepTerminal(trx, step, status));
257
- },
258
-
259
- async retryStep(step: AutomationStepToRun, retryAt: Date): Promise<boolean> {
260
- return await knex.transaction(trx => retryStep(trx, step, retryAt));
261
- },
262
-
263
- async recordEmailSent(options): Promise<void> {
264
- await knex.transaction(async (trx) => {
265
- await trx('automation_action_revisions')
266
- .where('id', options.automationActionRevisionId)
267
- .update({
268
- email_sent_count: trx.raw('COALESCE(??, 0) + ?', ['email_sent_count', 1])
269
- });
270
-
271
- const now = toDatabaseDate(new Date());
272
- await trx('automated_email_recipients').insert({
273
- id: ObjectId().toHexString(),
274
- member_id: options.memberId,
275
- member_uuid: options.memberUuid,
276
- member_email: options.memberEmail,
277
- member_name: options.memberName,
278
- automation_action_revision_id: options.automationActionRevisionId,
279
- automation_run_step_id: options.automationRunStepId,
280
- ...(options.mailgunMessageId ? {mailgun_message_id: options.mailgunMessageId} : {}),
281
- track_clicks: options.trackClicks,
282
- track_opens: options.trackOpens,
283
- created_at: now,
284
- updated_at: now
285
- });
286
- });
287
- },
288
-
289
- async getAutomatedEmailRecipientsByMailgunIds(mailgunMessageIds) {
290
- if (mailgunMessageIds.length === 0) {
291
- return [];
292
- }
293
- return await knex('automated_email_recipients')
294
- .select('id', 'mailgun_message_id', 'automation_action_revision_id')
295
- .whereNotNull('automation_action_revision_id')
296
- .whereIn('mailgun_message_id', mailgunMessageIds);
297
- },
298
-
299
- async trackEmailDeliveredAndOpened(eventsByAutomatedEmailRecipientId) {
300
- if (eventsByAutomatedEmailRecipientId.size === 0) {
301
- return;
302
- }
303
-
304
- await knex.transaction(async (trx) => {
305
- const revisionIds = new Set<string>();
306
- for (const {openedAt, automationActionRevisionId} of eventsByAutomatedEmailRecipientId.values()) {
307
- if (openedAt) {
308
- revisionIds.add(automationActionRevisionId);
309
- }
310
- }
311
-
312
- const orderedRevisionIds = await lockActionRevisions(trx, revisionIds);
313
-
314
- const notYetOpened = await lockNotYetOpened(trx, eventsByAutomatedEmailRecipientId);
315
- const newOpensPerRevision = new Map<string, number>();
316
-
317
- for (const [id, {deliveredAt, openedAt, automationActionRevisionId}] of eventsByAutomatedEmailRecipientId) {
318
- const updates: Record<string, Knex.Raw> = {};
319
- if (deliveredAt) {
320
- updates.delivered_at = trx.raw('CASE WHEN delivered_at IS NULL OR delivered_at > ? THEN ? ELSE delivered_at END', [deliveredAt, deliveredAt]);
321
- }
322
- if (openedAt) {
323
- updates.opened_at = trx.raw('CASE WHEN opened_at IS NULL OR opened_at > ? THEN ? ELSE opened_at END', [openedAt, openedAt]);
324
- }
325
- if (Object.keys(updates).length === 0) {
326
- continue;
327
- }
328
- await trx('automated_email_recipients')
329
- .where({id})
330
- .update(updates);
331
-
332
- if (openedAt && notYetOpened.has(id)) {
333
- newOpensPerRevision.set(
334
- automationActionRevisionId,
335
- (newOpensPerRevision.get(automationActionRevisionId) ?? 0) + 1
336
- );
337
- }
338
- }
339
-
340
- for (const id of orderedRevisionIds) {
341
- const opens = newOpensPerRevision.get(id);
342
- if (!opens) {
343
- continue;
344
- }
345
- await trx('automation_action_revisions')
346
- .where({id})
347
- .update({
348
- email_opened_count: trx.raw('COALESCE(email_opened_count, 0) + ?', [opens])
349
- });
350
- }
351
- });
352
- },
353
-
354
- async trackEmailClicked({automationActionRevisionId, automationRunStepId, memberId, clickedAt}, {transacting} = {}) {
355
- const trackClick = async (trx: Knex.Transaction) => {
356
- await lockActionRevisions(trx, [automationActionRevisionId]);
357
-
358
- const recipient = await trx('automated_email_recipients')
359
- .select('id', 'clicked_at')
360
- .where({
361
- automation_action_revision_id: automationActionRevisionId,
362
- automation_run_step_id: automationRunStepId,
363
- member_id: memberId,
364
- track_clicks: true
365
- })
366
- .forUpdate()
367
- .first();
368
-
369
- if (!recipient || recipient.clicked_at !== null) {
370
- return;
371
- }
372
-
373
- const updated = await trx('automated_email_recipients')
374
- .where({id: recipient.id})
375
- .whereNull('clicked_at')
376
- .update({clicked_at: clickedAt});
377
-
378
- if (updated === 0) {
379
- return;
380
- }
381
-
382
- await trx('automation_action_revisions')
383
- .where({id: automationActionRevisionId})
384
- .update({
385
- email_clicked_count: trx.raw('COALESCE(email_clicked_count, 0) + 1')
386
- });
387
-
388
- };
389
-
390
- if (transacting) {
391
- await trackClick(transacting);
392
- return;
393
- }
394
-
395
- await knex.transaction(trackClick);
396
- }
397
- };
398
- }
399
-
400
- /**
401
- * Lock revisions before recipients because inserting a recipient takes a shared
402
- * foreign-key lock on its revision. Updating that revision later can deadlock
403
- * with another transaction that has already locked the revision and is waiting
404
- * for the recipient. Keep multi-revision lock acquisition deterministic.
405
- */
406
- async function lockActionRevisions(
407
- trx: Knex.Transaction,
408
- revisionIds: Iterable<string>
409
- ): Promise<string[]> {
410
- const sortedRevisionIds = [...new Set(revisionIds)]
411
- .sort((left, right) => left.localeCompare(right));
412
-
413
- if (sortedRevisionIds.length > 0) {
414
- await trx('automation_action_revisions')
415
- .select('id')
416
- .whereIn('id', sortedRevisionIds)
417
- .forUpdate();
418
- }
419
-
420
- return sortedRevisionIds;
421
- }
422
-
423
- /**
424
- * Which of these recipients have yet to open, and so should count towards their
425
- * revision's open count. Locks them for the transaction, so a worker racing on
426
- * the same open reads them as opened and doesn't count them a second time.
427
- */
428
- async function lockNotYetOpened(
429
- trx: Knex.Transaction,
430
- eventsByAutomatedEmailRecipientId: ReadonlyDeep<Map<string, AutomatedEmailEvents>>
431
- ): Promise<Set<string>> {
432
- const ids: string[] = [];
433
- for (const [id, {openedAt}] of eventsByAutomatedEmailRecipientId) {
434
- if (openedAt) {
435
- ids.push(id);
436
- }
437
- }
438
-
439
- if (ids.length === 0) {
440
- return new Set();
441
- }
442
-
443
- const rows = await trx('automated_email_recipients')
444
- .select('id')
445
- .whereIn('id', ids)
446
- .whereNull('opened_at')
447
- .forUpdate();
448
-
449
- return new Set(rows.map(row => row.id));
450
- }
451
-
452
- async function ensureDefaultAutomations(trx: Knex.Transaction): Promise<void> {
453
- for (const defaults of DEFAULT_WELCOME_EMAIL_AUTOMATIONS) {
454
- const automation = await ensureAutomation(trx, defaults);
455
- await ensureWelcomeEmailAction(trx, automation.id);
456
- }
457
- }
458
-
459
- async function ensureAutomation(
460
- trx: Knex.Transaction,
461
- defaults: Readonly<{name: string; slug: string}>
462
- ): Promise<AutomationRow> {
463
- const now = toDatabaseDate(new Date());
464
- const id = ObjectId().toHexString();
465
-
466
- await trx('automations')
467
- .insert({
468
- id,
469
- status: 'inactive',
470
- name: defaults.name,
471
- slug: defaults.slug,
472
- created_at: now,
473
- updated_at: now
474
- })
475
- .onConflict('slug')
476
- .ignore();
477
-
478
- return requireAutomation(await loadAutomationBySlug(trx, defaults.slug), defaults.slug);
479
- }
480
-
481
- async function ensureWelcomeEmailAction(trx: Knex.Transaction, automationId: string): Promise<void> {
482
- const hasActions = await trx('automation_actions')
483
- .where('automation_id', automationId)
484
- .whereNull('deleted_at')
485
- .first('id');
486
- if (hasActions) {
487
- return;
488
- }
489
-
490
- await trx('automations')
491
- .select('id')
492
- .where('id', automationId)
493
- .forUpdate()
494
- .first();
495
-
496
- const email = await trx('welcome_email_automated_emails')
497
- .select('subject', 'lexical', 'email_design_setting_id')
498
- .where('welcome_email_automation_id', automationId)
499
- .orderBy([
500
- 'created_at',
501
- 'id'
502
- ])
503
- .first();
504
-
505
- if (!email) {
506
- return;
507
- }
508
-
509
- const now = toDatabaseDate(new Date());
510
- const actionId = ObjectId().toHexString();
511
-
512
- await insertActions(trx, [{
513
- id: actionId,
514
- created_at: now,
515
- updated_at: now,
516
- automation_id: automationId,
517
- type: 'send_email'
518
- }]);
519
- await insertActionRevisions(trx, [{
520
- actionId,
521
- action: {
522
- id: actionId,
523
- type: 'send_email',
524
- data: {
525
- email_subject: email.subject,
526
- email_lexical: email.lexical ?? '',
527
- email_design_setting_id: email.email_design_setting_id
528
- }
529
- },
530
- createdAt: getNextRevisionCreatedAt(null, now)
531
- }]);
532
- }
533
-
534
- async function trigger(trx: Knex.Transaction, options: Readonly<{
535
- memberEmail: string;
536
- memberId: string;
537
- memberStatus: 'free' | 'paid';
538
- fakeWaitHoursMultiplier: number | null;
539
- }>): Promise<void> {
540
- const {
541
- memberEmail,
542
- memberId,
543
- memberStatus,
544
- fakeWaitHoursMultiplier
545
- } = options;
546
-
547
- const firstAction = await findFirstActionRevision(trx, memberStatus);
548
- if (!firstAction) {
549
- return;
550
- }
551
-
552
- const now = new Date();
553
- const nowString = toDatabaseDate(now);
554
-
555
- const readyAt = getReadyAtForAction(firstAction, now, fakeWaitHoursMultiplier);
556
-
557
- const run = {
558
- id: ObjectId().toHexString(),
559
- created_at: nowString,
560
- updated_at: nowString,
561
- automation_id: firstAction.automation_id,
562
- member_id: memberId,
563
- member_email: memberEmail
564
- };
565
-
566
- await trx('automation_runs').insert(run);
567
- await insertRunStep(trx, {
568
- automationRunId: run.id,
569
- automationActionRevisionId: firstAction.automation_action_revision_id,
570
- now,
571
- readyAt
572
- });
573
- }
574
-
575
- async function insertRunStep(trx: Knex.Transaction, {
576
- automationRunId,
577
- automationActionRevisionId,
578
- now,
579
- readyAt
580
- }: ReadonlyDeep<{
581
- automationRunId: string;
582
- automationActionRevisionId: string;
583
- now: Date;
584
- readyAt: Date;
585
- }>): Promise<void> {
586
- const nowString = toDatabaseDate(now);
587
-
588
- await trx('automation_run_steps').insert({
589
- id: ObjectId().toHexString(),
590
- created_at: nowString,
591
- updated_at: nowString,
592
- automation_run_id: automationRunId,
593
- automation_action_revision_id: automationActionRevisionId,
594
- ready_at: toDatabaseDate(readyAt)
595
- });
596
- }
597
-
598
- async function fetchAndLockSteps(trx: Knex.Transaction, limit: number): Promise<{
599
- steps: AutomationStepToRun[],
600
- nextStepReadyAt: null | Date;
601
- }> {
602
- // Two things make this tricky:
603
- //
604
- // - We want to do row-level locking, so multiple calls don't step on each other.
605
- // - We can't `UPDATE` a fixed number of rows.
606
- //
607
- // To get around these problems, here's what we do:
608
- //
609
- // 1. Select up to `limit` candidate rows.
610
- // 2. Try to lock those rows.
611
- // 3. Select any rows we successfully locked.
612
-
613
- const now = new Date();
614
- const nowString = toDatabaseDate(now);
615
- const staleLockCutoff = getStaleLockCutoff(now);
616
- const staleLockCutoffString = toDatabaseDate(staleLockCutoff);
617
- const lockId = crypto.randomUUID();
618
-
619
- // 1. Select up to `limit` candidate rows.
620
- const candidates: ReadonlyArray<{id: string}> = await trx('automation_run_steps')
621
- .select('id')
622
- .where('status', 'pending')
623
- .where('ready_at', '<=', nowString)
624
- .where((builder) => {
625
- builder
626
- .whereNull('locked_by')
627
- .orWhere('locked_at', '<', staleLockCutoffString);
628
- })
629
- .orderBy([
630
- 'ready_at',
631
- 'created_at',
632
- 'id'
633
- ])
634
- .limit(limit);
635
- if (candidates.length === 0) {
636
- return {
637
- steps: [],
638
- nextStepReadyAt: await findNextPendingReadyAt(trx, staleLockCutoff)
639
- };
640
- }
641
-
642
- const candidateIds = candidates.map(candidate => candidate.id);
643
-
644
- // 2. Try to lock those rows.
645
- await trx('automation_run_steps')
646
- .update({
647
- locked_by: lockId,
648
- locked_at: nowString,
649
- started_at: nowString,
650
- updated_at: nowString
651
- })
652
- .increment('step_attempts', 1)
653
- .whereIn('id', candidateIds)
654
- .where('status', 'pending')
655
- .where('ready_at', '<=', nowString)
656
- .where((builder) => {
657
- builder
658
- .whereNull('locked_by')
659
- .orWhere('locked_at', '<', staleLockCutoffString);
660
- });
661
-
662
- // 3. Select any rows we successfully locked.
663
- const rows: StepToRunRow[] = await trx('automation_run_steps as step')
664
- .select(
665
- 'step.id as id',
666
- 'step.locked_by as locked_by',
667
- 'step.automation_run_id as automation_run_id',
668
- 'run.automation_id as automation_id',
669
- 'automation.slug as automation_slug',
670
- 'automation.status as automation_status',
671
- 'run.member_id as member_id',
672
- 'run.member_email as member_email',
673
- 'action.id as action_id',
674
- 'revision.id as automation_action_revision_id',
675
- 'action.type as type',
676
- 'step.ready_at as ready_at',
677
- 'step.step_attempts as step_attempts',
678
- 'revision.wait_hours as wait_hours',
679
- 'revision.email_subject as email_subject',
680
- 'revision.email_lexical as email_lexical',
681
- 'revision.email_design_setting_id as email_design_setting_id'
682
- )
683
- .innerJoin('automation_runs as run', 'run.id', 'step.automation_run_id')
684
- .innerJoin('automations as automation', 'automation.id', 'run.automation_id')
685
- .innerJoin('automation_action_revisions as revision', 'revision.id', 'step.automation_action_revision_id')
686
- .innerJoin('automation_actions as action', 'action.id', 'revision.action_id')
687
- .whereIn('step.id', candidateIds)
688
- .where('step.locked_by', lockId)
689
- .orderBy([
690
- 'step.ready_at',
691
- 'step.created_at',
692
- 'step.id'
693
- ]);
694
-
695
- return {
696
- steps: rows.map(row => buildStepToRun(row)),
697
- nextStepReadyAt: await findNextPendingReadyAt(trx, staleLockCutoff)
698
- };
699
- }
700
-
701
- async function findNextPendingReadyAt(trx: Knex.Transaction, staleLockCutoff: Readonly<Date>): Promise<Date | null> {
702
- const row = await trx('automation_run_steps')
703
- .select({next_ready_at: 'ready_at'})
704
- .where('status', 'pending')
705
- .where((builder) => {
706
- builder
707
- .whereNull('locked_by')
708
- .orWhere('locked_at', '<', toDatabaseDate(staleLockCutoff));
709
- })
710
- .orderBy('ready_at')
711
- .first();
712
- return row?.next_ready_at ? fromDatabaseDate(row.next_ready_at) : null;
713
- }
714
-
715
- function buildStepToRun(row: ReadonlyDeep<StepToRunRow>): AutomationStepToRun {
716
- const base = {
717
- id: row.id,
718
- step_attempts: row.step_attempts,
719
- ready_at: fromDatabaseDate(row.ready_at),
720
- locked_by: row.locked_by,
721
- automation_run_id: row.automation_run_id,
722
- automation_id: row.automation_id,
723
- automation_slug: row.automation_slug,
724
- automation_status: row.automation_status,
725
- member_id: row.member_id,
726
- member_email: row.member_email,
727
- action_id: row.action_id,
728
- automation_action_revision_id: row.automation_action_revision_id
729
- };
730
-
731
- switch (row.type) {
732
- case 'wait':
733
- return {
734
- ...base,
735
- type: 'wait',
736
- wait_hours: requireValue(row, 'wait_hours')
737
- };
738
- case 'send_email':
739
- return {
740
- ...base,
741
- type: 'send_email',
742
- email_subject: requireValue(row, 'email_subject'),
743
- email_lexical: requireValue(row, 'email_lexical'),
744
- email_design_setting_id: row.email_design_setting_id
745
- };
746
- default:
747
- throw new errors.InternalServerError({
748
- message: `Unexpected action type from database: ${row.type}`
749
- });
750
- }
751
- }
752
-
753
- async function findFirstActionRevision(trx: Knex.Transaction, memberStatus: 'free' | 'paid'): Promise<NextActionRevisionRow | null> {
754
- const automationSlug: NonNullable<string> = MEMBER_WELCOME_EMAIL_SLUGS[memberStatus];
755
-
756
- const row = await trx('automations as automation')
757
- .select(
758
- 'automation.id as automation_id',
759
- 'actions.id as action_id',
760
- 'revisions.id as automation_action_revision_id',
761
- 'actions.type as type',
762
- 'revisions.wait_hours as wait_hours'
763
- )
764
- .innerJoin('automation_actions as actions', 'actions.automation_id', 'automation.id')
765
- .innerJoin('automation_action_revisions as revisions', 'revisions.action_id', 'actions.id')
766
- .where('automation.slug', automationSlug)
767
- .where('automation.status', 'active')
768
- .whereNull('actions.deleted_at')
769
- .whereNotExists(trx('automation_action_edges as edge')
770
- .select('edge.target_action_id')
771
- .innerJoin('automation_actions as source_actions', 'source_actions.id', 'edge.source_action_id')
772
- .whereNull('source_actions.deleted_at')
773
- .where('edge.target_action_id', trx.ref('actions.id')))
774
- .where('revisions.created_at', trx('automation_action_revisions')
775
- .max('created_at')
776
- .where('action_id', trx.ref('actions.id')))
777
- .orderBy([
778
- 'actions.created_at',
779
- 'actions.id'
780
- ])
781
- .first();
782
-
783
- return row ?? null;
784
- }
785
-
786
- async function finishStepAndEnqueueNext(
787
- trx: Knex.Transaction,
788
- options: Readonly<{
789
- step: Pick<AutomationStepToRun, 'id' | 'locked_by' | 'action_id' | 'automation_run_id'>;
790
- fakeWaitHoursMultiplier: number | null;
791
- }>
792
- ): Promise<Date | null> {
793
- const {
794
- step,
795
- fakeWaitHoursMultiplier
796
- } = options;
797
-
798
- const didFinish = await markStepTerminal(trx, step, 'finished');
799
- if (!didFinish) {
800
- return null;
801
- }
802
-
803
- if (!await isRunAutomationActive(trx, step.automation_run_id)) {
804
- return null;
805
- }
806
-
807
- const next = await findNextActionRevision(trx, step.action_id);
808
-
809
- if (!next) {
810
- return null;
811
- }
812
-
813
- const now = new Date();
814
- const nextReadyAt = getReadyAtForAction(next, now, fakeWaitHoursMultiplier);
815
-
816
- await insertRunStep(trx, {
817
- automationRunId: step.automation_run_id,
818
- automationActionRevisionId: next.automation_action_revision_id,
819
- now,
820
- readyAt: nextReadyAt
821
- });
822
-
823
- return nextReadyAt;
824
- }
825
-
826
- async function findNextActionRevision(trx: Knex.Transaction, sourceActionId: string): Promise<NextActionRevisionRow | null> {
827
- const row = await trx('automation_action_edges as edge')
828
- .select(
829
- 'action.id as action_id',
830
- 'revision.id as automation_action_revision_id',
831
- 'action.type as type',
832
- 'revision.wait_hours as wait_hours'
833
- )
834
- .innerJoin('automation_actions as action', 'action.id', 'edge.target_action_id')
835
- .innerJoin('automation_action_revisions as revision', 'revision.action_id', 'action.id')
836
- .where('edge.source_action_id', sourceActionId)
837
- .whereNull('action.deleted_at')
838
- .where('revision.created_at', trx('automation_action_revisions')
839
- .max('created_at')
840
- .where('action_id', trx.ref('action.id')))
841
- .orderBy('revision.created_at', 'desc')
842
- .orderBy('revision.id', 'desc')
843
- .first();
844
-
845
- return row ?? null;
846
- }
847
-
848
- async function markStepTerminal(
849
- trx: Knex.Transaction,
850
- step: Pick<AutomationStepToRun, 'id' | 'locked_by'>,
851
- status: AutomationStepTerminalStatus
852
- ): Promise<boolean> {
853
- const nowString = toDatabaseDate(new Date());
854
- return await updateStep(trx, step, {
855
- status,
856
- finished_at: nowString,
857
- updated_at: nowString
858
- });
859
- }
860
-
861
- async function retryStep(
862
- trx: Knex.Transaction,
863
- step: Pick<AutomationStepToRun, 'id' | 'locked_by'>,
864
- retryAt: Readonly<Date>
865
- ): Promise<boolean> {
866
- if (!await isStepRunAutomationActive(trx, step.id)) {
867
- await markStepTerminal(trx, step, 'automation disabled');
868
- return false;
869
- }
870
-
871
- const nowString = toDatabaseDate(new Date());
872
- return await updateStep(trx, step, {
873
- status: 'pending',
874
- started_at: null,
875
- finished_at: null,
876
- ready_at: toDatabaseDate(retryAt),
877
- updated_at: nowString
878
- });
879
- }
880
-
881
- function getReadyAtForAction(
882
- action: ReadonlyDeep<Pick<NextActionRevisionRow, 'action_id' | 'type' | 'wait_hours'>>,
883
- now: Readonly<Date>,
884
- fakeWaitHoursMultiplier: number | null
885
- ): Date {
886
- switch (action.type) {
887
- case 'wait': {
888
- const waitHours = requireValue({
889
- ...action,
890
- id: action.action_id
891
- }, 'wait_hours');
892
- const waitMs = waitHours * (fakeWaitHoursMultiplier || HOUR_MS);
893
- return new Date(now.getTime() + waitMs);
894
- }
895
- case 'send_email':
896
- return now;
897
- default: {
898
- const _exhaustive: never = action.type;
899
- throw new errors.IncorrectUsageError({
900
- message: `Unexpected action type ${_exhaustive}`
901
- });
902
- }
903
- }
904
- }
905
-
906
- async function cancelCancelablePendingStepsForAutomation(
907
- trx: Knex.Transaction,
908
- automationId: string,
909
- now: Readonly<Date>
910
- ): Promise<void> {
911
- const nowString = toDatabaseDate(now);
912
- const staleLockCutoff = toDatabaseDate(getStaleLockCutoff(now));
913
- await trx('automation_run_steps')
914
- .update({
915
- status: 'automation disabled',
916
- finished_at: nowString,
917
- updated_at: nowString,
918
- locked_by: null,
919
- locked_at: null
920
- })
921
- .where('status', 'pending')
922
- .whereIn('automation_run_id', trx('automation_runs')
923
- .select('id')
924
- .where('automation_id', automationId))
925
- .where((builder) => {
926
- builder
927
- .whereNull('locked_by')
928
- .orWhere('locked_at', '<', staleLockCutoff);
929
- });
930
- }
931
-
932
- async function isStepRunAutomationActive(trx: Knex.Transaction, stepId: string): Promise<boolean> {
933
- const query = trx('automation_run_steps as step')
934
- .select(trx.raw('1'))
935
- .innerJoin('automation_runs as run', 'run.id', 'step.automation_run_id')
936
- .innerJoin('automations as automation', 'automation.id', 'run.automation_id')
937
- .where('step.id', stepId)
938
- .where('automation.status', 'active');
939
- return await selectExists(trx, query);
940
- }
941
-
942
- async function isRunAutomationActive(trx: Knex.Transaction, automationRunId: string): Promise<boolean> {
943
- const query = trx('automation_runs as run')
944
- .select(trx.raw('1'))
945
- .innerJoin('automations as automation', 'automation.id', 'run.automation_id')
946
- .where('run.id', automationRunId)
947
- .where('automation.status', 'active');
948
- return await selectExists(trx, query);
949
- }
950
-
951
- async function selectExists(trx: Knex.Transaction, query: Knex.QueryBuilder): Promise<boolean> {
952
- const row = await trx
953
- .select<{exists: boolean | number | string}>(trx.raw('exists ? as `exists`', [query]))
954
- .first();
955
- return Boolean(Number(row?.exists));
956
- }
957
-
958
- /**
959
- * Update a step. Returns whether the update succeeded.
960
- *
961
- * Should only update locked steps to avoid race conditions. Imagine the following scenario:
962
- *
963
- * 1. A step is locked by Worker A.
964
- * 2. The lock expires.
965
- * 3. The step is locked by Worker B.
966
- * 4. Worker A finishes its work.
967
- *
968
- * Worker A has lost its lock, so it shouldn't be updating the step any more.
969
- */
970
- async function updateStep(
971
- trx: Knex.Transaction,
972
- step: Pick<AutomationStepToRun, 'id' | 'locked_by'>,
973
- attrs: {
974
- status: string;
975
- started_at?: string | null;
976
- finished_at?: string | null;
977
- ready_at?: string;
978
- updated_at: string;
979
- }
980
- ): Promise<boolean> {
981
- /* eslint-disable camelcase */
982
- const {started_at, finished_at, ready_at} = attrs;
983
- const changes = await trx('automation_run_steps')
984
- .update({
985
- status: attrs.status,
986
- updated_at: attrs.updated_at,
987
- locked_by: null,
988
- locked_at: null,
989
- ...(started_at === undefined ? {} : {started_at}),
990
- ...(finished_at === undefined ? {} : {finished_at}),
991
- ...(ready_at === undefined ? {} : {ready_at})
992
- })
993
- .where('id', step.id)
994
- .where('status', 'pending')
995
- .where('locked_by', step.locked_by);
996
- /* eslint-enable camelcase */
997
- return changes >= 1;
998
- }
999
-
1000
- async function loadAutomation(trx: Knex.Transaction, automationId: string): Promise<AutomationRow | null> {
1001
- const row = await trx('automations')
1002
- .select('id', 'slug', 'name', 'status', 'created_at', 'updated_at')
1003
- .where('id', automationId)
1004
- .first();
1005
- return row ?? null;
1006
- }
1007
-
1008
- async function loadAutomationBySlug(trx: Knex.Transaction, slug: string): Promise<AutomationRow | null> {
1009
- const row = await trx('automations')
1010
- .select('id', 'slug', 'name', 'status', 'created_at', 'updated_at')
1011
- .where('slug', slug)
1012
- .first();
1013
- return row ?? null;
1014
- }
1015
-
1016
- async function loadAutomations(trx: Knex.Transaction): Promise<AutomationRow[]> {
1017
- return await trx('automations')
1018
- .select('id', 'slug', 'name', 'status', 'created_at', 'updated_at')
1019
- .orderBy('name');
1020
- }
1021
-
1022
- async function updateAutomation(trx: Knex.Transaction, automation: AutomationRow): Promise<AutomationRow> {
1023
- await trx('automations')
1024
- .update({
1025
- status: automation.status,
1026
- updated_at: automation.updated_at
1027
- })
1028
- .where('id', automation.id);
1029
-
1030
- return requireAutomation(await loadAutomation(trx, automation.id), automation.id);
1031
- }
1032
-
1033
- async function replaceAutomationGraph(trx: Knex.Transaction, automationId: string, submittedActions: AutomationAction[], edges: AutomationEdge[]): Promise<void> {
1034
- const existingActions = await loadAutomationActionRows(trx, automationId);
1035
- const existingActionById = new Map(existingActions.map(action => [action.id, action]));
1036
- const actions = (await resolveEmailDesignSettingIds(trx, submittedActions)).map(formatActionOnWrite);
1037
- const submittedActionIds = new Set(actions.map(action => action.id));
1038
- const actionIdsWithOwners = await loadActionIdsWithOwners(trx, [...submittedActionIds]);
1039
- const latestRevisionByActionId = new Map((await loadLatestActionRevisions(trx, [...submittedActionIds])).map(revision => [revision.action_id, revision]));
1040
- const now = toDatabaseDate(new Date());
1041
- const actionsToInsert: ActionToInsert[] = [];
1042
- const revisionsToInsert: ActionRevisionToInsert[] = [];
1043
-
1044
- for (const action of actions) {
1045
- const existingAction = existingActionById.get(action.id);
1046
- if (existingAction) {
1047
- if (existingAction.type !== action.type) {
1048
- throw new errors.ValidationError({
1049
- message: tpl(messages.conflictingAutomationActionType, {
1050
- actionId: action.id
1051
- }),
1052
- property: 'actions.type'
1053
- });
1054
- }
1055
- } else {
1056
- if (actionIdsWithOwners.has(action.id)) {
1057
- throw new errors.ValidationError({
1058
- message: tpl(messages.conflictingAutomationActionId, {
1059
- actionId: action.id
1060
- }),
1061
- property: 'actions.id'
1062
- });
1063
- }
1064
-
1065
- actionsToInsert.push({
1066
- id: action.id,
1067
- created_at: now,
1068
- updated_at: now,
1069
- automation_id: automationId,
1070
- type: action.type
1071
- });
1072
- }
1073
-
1074
- const latestRevision = latestRevisionByActionId.get(action.id);
1075
- if (shouldInsertActionRevision(action, latestRevision)) {
1076
- revisionsToInsert.push({
1077
- actionId: action.id,
1078
- action,
1079
- createdAt: getNextRevisionCreatedAt(latestRevision?.created_at ?? null, now)
1080
- });
1081
- }
1082
- }
1083
-
1084
- await insertActions(trx, actionsToInsert);
1085
- await insertActionRevisions(trx, revisionsToInsert);
1086
-
1087
- const actionIdsToSoftDelete = existingActions
1088
- .filter(existingAction => !submittedActionIds.has(existingAction.id))
1089
- .map(existingAction => existingAction.id);
1090
- await softDeleteActions(trx, actionIdsToSoftDelete, now);
1091
-
1092
- await deleteAutomationEdges(trx, automationId);
1093
-
1094
- await insertActionEdges(trx, edges);
1095
- }
1096
-
1097
- function formatActionOnWrite(action: AutomationAction): AutomationAction {
1098
- if (action.type !== 'send_email') {
1099
- return action;
1100
- }
1101
-
1102
- return {
1103
- ...action,
1104
- data: {
1105
- ...action.data,
1106
- email_lexical: urlUtils.lexicalToTransformReady(action.data.email_lexical, {
1107
- nodes: lexicalLib.nodes,
1108
- transformMap: lexicalLib.urlTransformMap
1109
- })
1110
- }
1111
- };
1112
- }
1113
-
1114
- async function resolveEmailDesignSettingIds(trx: Knex.Transaction, actions: ReadonlyArray<AutomationAction>): Promise<AutomationAction[]> {
1115
- if (!actions.some(action => action.type === 'send_email' && action.data.email_design_setting_id === DEFAULT_EMAIL_DESIGN_SETTING_REFERENCE)) {
1116
- return [...actions];
1117
- }
1118
-
1119
- const defaultEmailDesignSettingId = await loadDefaultEmailDesignSettingId(trx);
1120
-
1121
- return actions.map((action) => {
1122
- if (action.type !== 'send_email' || action.data.email_design_setting_id !== DEFAULT_EMAIL_DESIGN_SETTING_REFERENCE) {
1123
- return action;
1124
- }
1125
-
1126
- return {
1127
- ...action,
1128
- data: {
1129
- ...action.data,
1130
- email_design_setting_id: defaultEmailDesignSettingId
1131
- }
1132
- };
1133
- });
1134
- }
1135
-
1136
- async function loadDefaultEmailDesignSettingId(trx: Knex.Transaction): Promise<string> {
1137
- const row = await trx('email_design_settings')
1138
- .select('id')
1139
- .where('slug', DEFAULT_EMAIL_DESIGN_SETTING_SLUG)
1140
- .first();
1141
-
1142
- if (!row?.id) {
1143
- throw new errors.InternalServerError({
1144
- message: tpl(messages.defaultEmailDesignSettingNotFound)
1145
- });
1146
- }
1147
-
1148
- return row.id;
1149
- }
1150
-
1151
- async function loadAutomationActionRows(trx: Knex.Transaction, automationId: string): Promise<Array<Pick<ActionRow, 'id' | 'type'>>> {
1152
- return await trx('automation_actions')
1153
- .select('id', 'type')
1154
- .where('automation_id', automationId)
1155
- .whereNull('deleted_at');
1156
- }
1157
-
1158
- async function loadActionIdsWithOwners(trx: Knex.Transaction, actionIds: ReadonlyArray<string>): Promise<Set<string>> {
1159
- if (actionIds.length === 0) {
1160
- return new Set();
1161
- }
1162
- const rows = await trx('automation_actions')
1163
- .select('id')
1164
- .whereIn('id', actionIds);
1165
- return new Set(rows.map(row => row.id));
1166
- }
1167
-
1168
- async function insertActions(trx: Knex.Transaction, actions: ReadonlyArray<ActionToInsert>): Promise<void> {
1169
- if (actions.length === 0) {
1170
- return;
1171
- }
1172
- await trx('automation_actions').insert(actions);
1173
- }
1174
-
1175
- function shouldInsertActionRevision(action: AutomationAction, latestRevision: ActionRevisionRow | undefined): boolean {
1176
- if (!latestRevision) {
1177
- return true;
1178
- }
1179
-
1180
- return !dequal(buildRevisionActionData(action, latestRevision), action.data);
1181
- }
1182
-
1183
- function buildRevisionActionData(action: AutomationAction, revision: ActionRevisionRow): ExclusifyUnion<WaitRevisionData | SendEmailRevisionData> {
1184
- switch (action.type) {
1185
- case 'wait':
1186
- return {
1187
- wait_hours: revision.wait_hours
1188
- };
1189
- case 'send_email':
1190
- return {
1191
- email_subject: revision.email_subject,
1192
- email_lexical: revision.email_lexical,
1193
- email_design_setting_id: revision.email_design_setting_id
1194
- };
1195
- default: {
1196
- const _exhaustive: never = action;
1197
- throw new errors.InternalServerError({
1198
- message: `Unhandled action type: ${_exhaustive}`
1199
- });
1200
- }
1201
- }
1202
- }
1203
-
1204
- async function loadLatestActionRevisions(
1205
- trx: Knex.Transaction,
1206
- actionIds: ReadonlyArray<string>
1207
- ): Promise<ActionRevisionRow[]> {
1208
- if (actionIds.length === 0) {
1209
- return [];
1210
- }
1211
-
1212
- const latestRevisionDates = trx('automation_action_revisions')
1213
- .select('action_id')
1214
- .max({created_at: 'created_at'})
1215
- .whereIn('action_id', actionIds)
1216
- .groupBy('action_id')
1217
- .as('latest_revision_dates');
1218
-
1219
- return await trx('automation_action_revisions')
1220
- .select(
1221
- 'automation_action_revisions.action_id',
1222
- 'automation_action_revisions.created_at',
1223
- 'automation_action_revisions.wait_hours',
1224
- 'automation_action_revisions.email_subject',
1225
- 'automation_action_revisions.email_lexical',
1226
- 'automation_action_revisions.email_design_setting_id'
1227
- )
1228
- .innerJoin(latestRevisionDates, function () {
1229
- this.on('automation_action_revisions.action_id', 'latest_revision_dates.action_id')
1230
- .andOn('automation_action_revisions.created_at', 'latest_revision_dates.created_at');
1231
- });
1232
- }
1233
-
1234
- async function softDeleteActions(
1235
- trx: Knex.Transaction,
1236
- actionIds: ReadonlyArray<string>,
1237
- deletedAt: string
1238
- ): Promise<void> {
1239
- if (actionIds.length === 0) {
1240
- return;
1241
- }
1242
- await trx('automation_actions')
1243
- .update({
1244
- deleted_at: deletedAt,
1245
- updated_at: deletedAt
1246
- })
1247
- .whereIn('id', actionIds);
1248
- }
1249
-
1250
- async function insertActionRevisions(
1251
- trx: Knex.Transaction,
1252
- revisions: ReadonlyArray<ActionRevisionToInsert>
1253
- ): Promise<void> {
1254
- if (revisions.length === 0) {
1255
- return;
1256
- }
1257
- await trx('automation_action_revisions').insert(
1258
- revisions.map(({actionId, action, createdAt}) => buildActionRevision(actionId, action, createdAt))
1259
- );
1260
- }
1261
-
1262
- function getNextRevisionCreatedAt(latestCreatedAt: DatabaseDate | null, requestedCreatedAt: string) {
1263
- if (!latestCreatedAt) {
1264
- return toDatabaseDate(requestedCreatedAt);
1265
- }
1266
-
1267
- const requestedTime = fromDatabaseDate(requestedCreatedAt).getTime();
1268
- const latestTime = fromDatabaseDate(latestCreatedAt).getTime();
1269
-
1270
- if (requestedTime > latestTime) {
1271
- return toDatabaseDate(requestedCreatedAt);
1272
- }
1273
-
1274
- return toDatabaseDate(new Date(latestTime + 1000));
1275
- }
1276
-
1277
- function buildActionRevision(actionId: string, action: AutomationAction, createdAt: string) {
1278
- switch (action.type) {
1279
- case 'wait':
1280
- return {
1281
- id: ObjectId().toString(),
1282
- created_at: createdAt,
1283
- action_id: actionId,
1284
- wait_hours: action.data.wait_hours,
1285
- email_subject: null,
1286
- email_lexical: null,
1287
- email_design_setting_id: null
1288
- };
1289
- case 'send_email':
1290
- return {
1291
- id: ObjectId().toString(),
1292
- created_at: createdAt,
1293
- action_id: actionId,
1294
- wait_hours: null,
1295
- email_subject: action.data.email_subject,
1296
- email_lexical: action.data.email_lexical,
1297
- email_design_setting_id: action.data.email_design_setting_id
1298
- };
1299
- default: {
1300
- const _exhaustive: never = action;
1301
- throw new errors.InternalServerError({
1302
- message: `Unexpected action type ${_exhaustive}`
1303
- });
1304
- }
1305
- }
1306
- }
1307
-
1308
- async function deleteAutomationEdges(trx: Knex.Transaction, automationId: string): Promise<void> {
1309
- await trx('automation_action_edges')
1310
- .delete()
1311
- .whereIn('source_action_id', trx('automation_actions')
1312
- .select('id')
1313
- .where('automation_id', automationId));
1314
- }
1315
-
1316
- async function insertActionEdges(trx: Knex.Transaction, edges: ReadonlyArray<AutomationEdge>): Promise<void> {
1317
- if (edges.length === 0) {
1318
- return;
1319
- }
1320
- await trx('automation_action_edges').insert(edges.map(edge => ({
1321
- source_action_id: edge.source_action_id,
1322
- target_action_id: edge.target_action_id
1323
- })));
1324
- }
1325
-
1326
- function requireAutomation(automation: AutomationRow | null, id: string): AutomationRow {
1327
- if (!automation) {
1328
- throw new errors.InternalServerError({
1329
- message: `Updated automation "${id}" could not be loaded.`
1330
- });
1331
- }
1332
-
1333
- return automation;
1334
- }
1335
-
1336
- async function buildAutomation(trx: Knex.Transaction, automation: AutomationRow): Promise<Automation> {
1337
- const actionRows = await loadActionRows(trx, automation.id);
1338
- const actionStats = await loadActionStats(trx, actionRows.map(row => row.id));
1339
- const edgeRows = await loadEdgeRows(trx, automation.id);
1340
- return {
1341
- ...buildAutomationSummary(automation),
1342
- actions: actionRows.map(row => buildActionPayload(row, actionStats.get(row.id) ?? null)),
1343
- edges: edgeRows.map(row => buildEdgePayload(row))
1344
- };
1345
- }
1346
-
1347
- function buildAutomationSummary(automation: AutomationRow): AutomationSummary {
1348
- return {
1349
- id: automation.id,
1350
- slug: automation.slug,
1351
- name: automation.name,
1352
- status: automation.status,
1353
- created_at: serializeDate(automation.created_at),
1354
- updated_at: serializeDate(automation.updated_at)
1355
- };
1356
- }
1357
-
1358
- function serializeDate(date: DatabaseDate) {
1359
- const normalizedDate = fromDatabaseDate(date);
1360
- normalizedDate.setMilliseconds(0);
1361
- return normalizedDate.toISOString();
1362
- }
1363
-
1364
- async function loadActionRows(trx: Knex.Transaction, automationId: string): Promise<ActionRow[]> {
1365
- return await trx('automation_actions as a')
1366
- .select(
1367
- 'a.id as id',
1368
- 'a.type as type',
1369
- 'r.wait_hours as wait_hours',
1370
- 'r.email_subject as email_subject',
1371
- 'r.email_lexical as email_lexical',
1372
- 'r.email_design_setting_id as email_design_setting_id'
1373
- )
1374
- .innerJoin('automation_action_revisions as r', 'r.action_id', 'a.id')
1375
- .where('a.automation_id', automationId)
1376
- .whereNull('a.deleted_at')
1377
- .where('r.created_at', trx('automation_action_revisions')
1378
- .max('created_at')
1379
- .where('action_id', trx.ref('a.id')))
1380
- .orderBy([
1381
- 'a.created_at',
1382
- 'a.id'
1383
- ]);
1384
- }
1385
-
1386
- async function loadActionStats(
1387
- trx: Knex.Transaction,
1388
- actionIds: ReadonlyArray<string>
1389
- ): Promise<Map<string, AutomationEmailStats>> {
1390
- if (actionIds.length === 0) {
1391
- return new Map();
1392
- }
1393
-
1394
- const rows: ActionStatsRow[] = await trx('automation_action_revisions')
1395
- .select('action_id')
1396
- .sum({
1397
- email_clicked_count: 'email_clicked_count',
1398
- email_sent_count: 'email_sent_count',
1399
- email_opened_count: 'email_opened_count'
1400
- })
1401
- .whereIn('action_id', actionIds)
1402
- .groupBy('action_id');
1403
-
1404
- return new Map(rows.map(row => [row.action_id, buildEmailStats(row)]));
1405
- }
1406
-
1407
- async function loadEdgeRows(trx: Knex.Transaction, automationId: string): Promise<EdgeRow[]> {
1408
- return await trx('automation_action_edges as e')
1409
- .select('e.source_action_id', 'e.target_action_id')
1410
- .innerJoin('automation_actions as source_action', (join) => {
1411
- join
1412
- .on('source_action.id', 'e.source_action_id')
1413
- .onNull('source_action.deleted_at');
1414
- })
1415
- .innerJoin('automation_actions as target_action', (join) => {
1416
- join
1417
- .on('target_action.id', 'e.target_action_id')
1418
- .onNull('target_action.deleted_at')
1419
- .on('target_action.automation_id', 'source_action.automation_id');
1420
- })
1421
- .where('source_action.automation_id', automationId)
1422
- .orderBy([
1423
- 'e.source_action_id',
1424
- 'e.target_action_id'
1425
- ]);
1426
- }
1427
-
1428
- function buildActionPayload(row: ActionRow, stats: AutomationEmailStats | null): AutomationAction {
1429
- switch (row.type) {
1430
- case 'wait':
1431
- return {
1432
- id: row.id,
1433
- type: 'wait',
1434
- data: {
1435
- wait_hours: requireValue(row, 'wait_hours')
1436
- }
1437
- };
1438
- case 'send_email':
1439
- return {
1440
- id: row.id,
1441
- type: 'send_email',
1442
- data: {
1443
- email_subject: requireValue(row, 'email_subject'),
1444
- email_lexical: urlUtils.transformReadyToAbsolute(requireValue(row, 'email_lexical')),
1445
- email_design_setting_id: requireValue(row, 'email_design_setting_id')
1446
- },
1447
- stats: stats ?? EMPTY_EMAIL_STATS
1448
- };
1449
- }
1450
- }
1451
-
1452
- const EMPTY_EMAIL_STATS: AutomationEmailStats = {
1453
- email_clicked_count: 0,
1454
- email_sent_count: 0,
1455
- email_opened_count: 0,
1456
- opened_rate: null,
1457
- clicked_rate: null
1458
- };
1459
-
1460
- function buildEmailStats(row: ActionStatsRow): AutomationEmailStats {
1461
- const emailClickedCount = row.email_clicked_count ?? 0;
1462
- const emailSentCount = row.email_sent_count ?? 0;
1463
- const emailOpenedCount = row.email_opened_count ?? 0;
1464
- return {
1465
- email_clicked_count: emailClickedCount,
1466
- email_sent_count: emailSentCount,
1467
- email_opened_count: emailOpenedCount,
1468
- opened_rate: emailSentCount
1469
- ? Math.round(emailOpenedCount / emailSentCount * 100)
1470
- : null,
1471
- clicked_rate: emailSentCount
1472
- ? Math.round(emailClickedCount / emailSentCount * 100)
1473
- : null
1474
- };
1475
- }
1476
-
1477
- function requireValue<
1478
- RowT extends {id: string, type: string},
1479
- FieldT extends keyof RowT
1480
- >(
1481
- row: RowT,
1482
- field: FieldT
1483
- ): NonNullable<RowT[FieldT]> {
1484
- const value = row[field];
1485
- if ((value === null) || (value === undefined)) {
1486
- throw new errors.InternalServerError({
1487
- message: tpl(messages.invalidAutomationActionRevision, {
1488
- actionId: row.id,
1489
- actionType: row.type,
1490
- field
1491
- })
1492
- });
1493
- }
1494
- return value;
1495
- }
1496
-
1497
- function buildEdgePayload(edge: EdgeRow): AutomationEdge {
1498
- return {
1499
- source_action_id: edge.source_action_id,
1500
- target_action_id: edge.target_action_id
1501
- };
1502
- }
1503
-
1504
- function buildPagination(total: number) {
1505
- return {
1506
- page: 1,
1507
- pages: 1,
1508
- limit: 'all' as const,
1509
- total,
1510
- prev: null,
1511
- next: null
1512
- };
1513
- }