@antelopejs/dms-mailing 0.2.2

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 (550) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +162 -0
  3. package/dist/antelope-modules.d.ts +32 -0
  4. package/dist/antelope-modules.js +36 -0
  5. package/dist/antelope-modules.js.map +1 -0
  6. package/dist/automation/email-event-trigger.d.ts +16 -0
  7. package/dist/automation/email-event-trigger.js +70 -0
  8. package/dist/automation/email-event-trigger.js.map +1 -0
  9. package/dist/automation/index.d.ts +4 -0
  10. package/dist/automation/index.js +39 -0
  11. package/dist/automation/index.js.map +1 -0
  12. package/dist/automation/send-template-action.d.ts +13 -0
  13. package/dist/automation/send-template-action.js +71 -0
  14. package/dist/automation/send-template-action.js.map +1 -0
  15. package/dist/config.d.ts +5 -0
  16. package/dist/config.js +13 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/constants.d.ts +28 -0
  19. package/dist/constants.js +32 -0
  20. package/dist/constants.js.map +1 -0
  21. package/dist/crons/index.d.ts +1 -0
  22. package/dist/crons/index.js +18 -0
  23. package/dist/crons/index.js.map +1 -0
  24. package/dist/crons/retention.d.ts +18 -0
  25. package/dist/crons/retention.js +107 -0
  26. package/dist/crons/retention.js.map +1 -0
  27. package/dist/data/body.d.ts +1 -0
  28. package/dist/data/body.js +18 -0
  29. package/dist/data/body.js.map +1 -0
  30. package/dist/data/index.d.ts +3 -0
  31. package/dist/data/index.js +20 -0
  32. package/dist/data/index.js.map +1 -0
  33. package/dist/data/sends-table.d.ts +28 -0
  34. package/dist/data/sends-table.js +161 -0
  35. package/dist/data/sends-table.js.map +1 -0
  36. package/dist/data/templates-table.d.ts +31 -0
  37. package/dist/data/templates-table.js +173 -0
  38. package/dist/data/templates-table.js.map +1 -0
  39. package/dist/data-types/categories-type.d.ts +35 -0
  40. package/dist/data-types/categories-type.js +37 -0
  41. package/dist/data-types/categories-type.js.map +1 -0
  42. package/dist/data-types/category-type.d.ts +8 -0
  43. package/dist/data-types/category-type.js +43 -0
  44. package/dist/data-types/category-type.js.map +1 -0
  45. package/dist/data-types/index.d.ts +2 -0
  46. package/dist/data-types/index.js +19 -0
  47. package/dist/data-types/index.js.map +1 -0
  48. package/dist/db/index.d.ts +2 -0
  49. package/dist/db/index.js +19 -0
  50. package/dist/db/index.js.map +1 -0
  51. package/dist/db/models/index.d.ts +4 -0
  52. package/dist/db/models/index.js +21 -0
  53. package/dist/db/models/index.js.map +1 -0
  54. package/dist/db/models/send-events.model.d.ts +27 -0
  55. package/dist/db/models/send-events.model.js +35 -0
  56. package/dist/db/models/send-events.model.js.map +1 -0
  57. package/dist/db/models/sends.model.d.ts +32 -0
  58. package/dist/db/models/sends.model.js +115 -0
  59. package/dist/db/models/sends.model.js.map +1 -0
  60. package/dist/db/models/settings.model.d.ts +23 -0
  61. package/dist/db/models/settings.model.js +13 -0
  62. package/dist/db/models/settings.model.js.map +1 -0
  63. package/dist/db/models/templates.model.d.ts +23 -0
  64. package/dist/db/models/templates.model.js +13 -0
  65. package/dist/db/models/templates.model.js.map +1 -0
  66. package/dist/db/tables/index.d.ts +4 -0
  67. package/dist/db/tables/index.js +21 -0
  68. package/dist/db/tables/index.js.map +1 -0
  69. package/dist/db/tables/send-events.table.d.ts +10 -0
  70. package/dist/db/tables/send-events.table.js +44 -0
  71. package/dist/db/tables/send-events.table.js.map +1 -0
  72. package/dist/db/tables/sends.table.d.ts +26 -0
  73. package/dist/db/tables/sends.table.js +113 -0
  74. package/dist/db/tables/sends.table.js.map +1 -0
  75. package/dist/db/tables/settings.table.d.ts +15 -0
  76. package/dist/db/tables/settings.table.js +68 -0
  77. package/dist/db/tables/settings.table.js.map +1 -0
  78. package/dist/db/tables/templates.table.d.ts +18 -0
  79. package/dist/db/tables/templates.table.js +81 -0
  80. package/dist/db/tables/templates.table.js.map +1 -0
  81. package/dist/engine/conditions.d.ts +2 -0
  82. package/dist/engine/conditions.js +20 -0
  83. package/dist/engine/conditions.js.map +1 -0
  84. package/dist/engine/coverage.d.ts +4 -0
  85. package/dist/engine/coverage.js +45 -0
  86. package/dist/engine/coverage.js.map +1 -0
  87. package/dist/engine/fallback-html.d.ts +4 -0
  88. package/dist/engine/fallback-html.js +84 -0
  89. package/dist/engine/fallback-html.js.map +1 -0
  90. package/dist/engine/index.d.ts +7 -0
  91. package/dist/engine/index.js +24 -0
  92. package/dist/engine/index.js.map +1 -0
  93. package/dist/engine/paths.d.ts +3 -0
  94. package/dist/engine/paths.js +32 -0
  95. package/dist/engine/paths.js.map +1 -0
  96. package/dist/engine/resolve.d.ts +4 -0
  97. package/dist/engine/resolve.js +139 -0
  98. package/dist/engine/resolve.js.map +1 -0
  99. package/dist/engine/tokens.d.ts +7 -0
  100. package/dist/engine/tokens.js +29 -0
  101. package/dist/engine/tokens.js.map +1 -0
  102. package/dist/engine/walk.d.ts +3 -0
  103. package/dist/engine/walk.js +14 -0
  104. package/dist/engine/walk.js.map +1 -0
  105. package/dist/hooks/index.d.ts +1 -0
  106. package/dist/hooks/index.js +18 -0
  107. package/dist/hooks/index.js.map +1 -0
  108. package/dist/hooks/tenant-export.d.ts +14 -0
  109. package/dist/hooks/tenant-export.js +84 -0
  110. package/dist/hooks/tenant-export.js.map +1 -0
  111. package/dist/implementations/dms-mailing/index.d.ts +2 -0
  112. package/dist/implementations/dms-mailing/index.js +8 -0
  113. package/dist/implementations/dms-mailing/index.js.map +1 -0
  114. package/dist/index.d.ts +13 -0
  115. package/dist/index.js +95 -0
  116. package/dist/index.js.map +1 -0
  117. package/dist/pages/editor.d.ts +9 -0
  118. package/dist/pages/editor.js +30 -0
  119. package/dist/pages/editor.js.map +1 -0
  120. package/dist/pages/index.d.ts +7 -0
  121. package/dist/pages/index.js +24 -0
  122. package/dist/pages/index.js.map +1 -0
  123. package/dist/pages/module.d.ts +1 -0
  124. package/dist/pages/module.js +12 -0
  125. package/dist/pages/module.js.map +1 -0
  126. package/dist/pages/overview.d.ts +14 -0
  127. package/dist/pages/overview.js +92 -0
  128. package/dist/pages/overview.js.map +1 -0
  129. package/dist/pages/sends.d.ts +13 -0
  130. package/dist/pages/sends.js +125 -0
  131. package/dist/pages/sends.js.map +1 -0
  132. package/dist/pages/settings/form.d.ts +1 -0
  133. package/dist/pages/settings/form.js +68 -0
  134. package/dist/pages/settings/form.js.map +1 -0
  135. package/dist/pages/settings/index.d.ts +2 -0
  136. package/dist/pages/settings/index.js +19 -0
  137. package/dist/pages/settings/index.js.map +1 -0
  138. package/dist/pages/settings/page.d.ts +10 -0
  139. package/dist/pages/settings/page.js +35 -0
  140. package/dist/pages/settings/page.js.map +1 -0
  141. package/dist/pages/templates.d.ts +10 -0
  142. package/dist/pages/templates.js +140 -0
  143. package/dist/pages/templates.js.map +1 -0
  144. package/dist/permissions.d.ts +1 -0
  145. package/dist/permissions.js +37 -0
  146. package/dist/permissions.js.map +1 -0
  147. package/dist/quick-actions.d.ts +2 -0
  148. package/dist/quick-actions.js +18 -0
  149. package/dist/quick-actions.js.map +1 -0
  150. package/dist/realtime.d.ts +4 -0
  151. package/dist/realtime.js +44 -0
  152. package/dist/realtime.js.map +1 -0
  153. package/dist/routes/events.d.ts +9 -0
  154. package/dist/routes/events.js +63 -0
  155. package/dist/routes/events.js.map +1 -0
  156. package/dist/routes/index.d.ts +7 -0
  157. package/dist/routes/index.js +24 -0
  158. package/dist/routes/index.js.map +1 -0
  159. package/dist/routes/metrics.d.ts +54 -0
  160. package/dist/routes/metrics.js +235 -0
  161. package/dist/routes/metrics.js.map +1 -0
  162. package/dist/routes/period.d.ts +5 -0
  163. package/dist/routes/period.js +36 -0
  164. package/dist/routes/period.js.map +1 -0
  165. package/dist/routes/provider.d.ts +13 -0
  166. package/dist/routes/provider.js +41 -0
  167. package/dist/routes/provider.js.map +1 -0
  168. package/dist/routes/sends.d.ts +31 -0
  169. package/dist/routes/sends.js +178 -0
  170. package/dist/routes/sends.js.map +1 -0
  171. package/dist/routes/settings.d.ts +14 -0
  172. package/dist/routes/settings.js +72 -0
  173. package/dist/routes/settings.js.map +1 -0
  174. package/dist/routes/templates.d.ts +61 -0
  175. package/dist/routes/templates.js +230 -0
  176. package/dist/routes/templates.js.map +1 -0
  177. package/dist/services/attention.d.ts +23 -0
  178. package/dist/services/attention.js +104 -0
  179. package/dist/services/attention.js.map +1 -0
  180. package/dist/services/categories.d.ts +34 -0
  181. package/dist/services/categories.js +34 -0
  182. package/dist/services/categories.js.map +1 -0
  183. package/dist/services/events.d.ts +10 -0
  184. package/dist/services/events.js +89 -0
  185. package/dist/services/events.js.map +1 -0
  186. package/dist/services/metrics.d.ts +34 -0
  187. package/dist/services/metrics.js +103 -0
  188. package/dist/services/metrics.js.map +1 -0
  189. package/dist/services/notify.d.ts +20 -0
  190. package/dist/services/notify.js +63 -0
  191. package/dist/services/notify.js.map +1 -0
  192. package/dist/services/permissions.d.ts +2 -0
  193. package/dist/services/permissions.js +15 -0
  194. package/dist/services/permissions.js.map +1 -0
  195. package/dist/services/provider.d.ts +6 -0
  196. package/dist/services/provider.js +25 -0
  197. package/dist/services/provider.js.map +1 -0
  198. package/dist/services/render.d.ts +6 -0
  199. package/dist/services/render.js +21 -0
  200. package/dist/services/render.js.map +1 -0
  201. package/dist/services/send.d.ts +49 -0
  202. package/dist/services/send.js +259 -0
  203. package/dist/services/send.js.map +1 -0
  204. package/dist/services/settings.d.ts +4 -0
  205. package/dist/services/settings.js +61 -0
  206. package/dist/services/settings.js.map +1 -0
  207. package/dist/services/status.d.ts +3 -0
  208. package/dist/services/status.js +16 -0
  209. package/dist/services/status.js.map +1 -0
  210. package/dist/services/templates.d.ts +17 -0
  211. package/dist/services/templates.js +92 -0
  212. package/dist/services/templates.js.map +1 -0
  213. package/dist/test/antelope.test.d.ts +2 -0
  214. package/dist/test/antelope.test.js +179 -0
  215. package/dist/test/antelope.test.js.map +1 -0
  216. package/dist/test/helpers/http.d.ts +4 -0
  217. package/dist/test/helpers/http.js +28 -0
  218. package/dist/test/helpers/http.js.map +1 -0
  219. package/dist/test/helpers/owner.d.ts +5 -0
  220. package/dist/test/helpers/owner.js +30 -0
  221. package/dist/test/helpers/owner.js.map +1 -0
  222. package/dist/test/integration/events.test.d.ts +1 -0
  223. package/dist/test/integration/events.test.js +132 -0
  224. package/dist/test/integration/events.test.js.map +1 -0
  225. package/dist/test/integration/retention.test.d.ts +1 -0
  226. package/dist/test/integration/retention.test.js +291 -0
  227. package/dist/test/integration/retention.test.js.map +1 -0
  228. package/dist/test/integration/sends.test.d.ts +1 -0
  229. package/dist/test/integration/sends.test.js +157 -0
  230. package/dist/test/integration/sends.test.js.map +1 -0
  231. package/dist/test/integration/settings.test.d.ts +1 -0
  232. package/dist/test/integration/settings.test.js +45 -0
  233. package/dist/test/integration/settings.test.js.map +1 -0
  234. package/dist/test/integration/setup.test.d.ts +1 -0
  235. package/dist/test/integration/setup.test.js +32 -0
  236. package/dist/test/integration/setup.test.js.map +1 -0
  237. package/dist/test/integration/templates.test.d.ts +1 -0
  238. package/dist/test/integration/templates.test.js +167 -0
  239. package/dist/test/integration/templates.test.js.map +1 -0
  240. package/dist/test/integration/tenant-export.test.d.ts +1 -0
  241. package/dist/test/integration/tenant-export.test.js +85 -0
  242. package/dist/test/integration/tenant-export.test.js.map +1 -0
  243. package/dist/test/unit/attention.test.d.ts +1 -0
  244. package/dist/test/unit/attention.test.js +109 -0
  245. package/dist/test/unit/attention.test.js.map +1 -0
  246. package/dist/test/unit/automation.test.d.ts +1 -0
  247. package/dist/test/unit/automation.test.js +104 -0
  248. package/dist/test/unit/automation.test.js.map +1 -0
  249. package/dist/test/unit/categories.test.d.ts +1 -0
  250. package/dist/test/unit/categories.test.js +38 -0
  251. package/dist/test/unit/categories.test.js.map +1 -0
  252. package/dist/test/unit/engine/conditions.test.d.ts +1 -0
  253. package/dist/test/unit/engine/conditions.test.js +23 -0
  254. package/dist/test/unit/engine/conditions.test.js.map +1 -0
  255. package/dist/test/unit/engine/coverage.test.d.ts +1 -0
  256. package/dist/test/unit/engine/coverage.test.js +106 -0
  257. package/dist/test/unit/engine/coverage.test.js.map +1 -0
  258. package/dist/test/unit/engine/fallback-html.test.d.ts +1 -0
  259. package/dist/test/unit/engine/fallback-html.test.js +78 -0
  260. package/dist/test/unit/engine/fallback-html.test.js.map +1 -0
  261. package/dist/test/unit/engine/paths.test.d.ts +1 -0
  262. package/dist/test/unit/engine/paths.test.js +33 -0
  263. package/dist/test/unit/engine/paths.test.js.map +1 -0
  264. package/dist/test/unit/engine/resolve.test.d.ts +1 -0
  265. package/dist/test/unit/engine/resolve.test.js +157 -0
  266. package/dist/test/unit/engine/resolve.test.js.map +1 -0
  267. package/dist/test/unit/engine/tokens.test.d.ts +1 -0
  268. package/dist/test/unit/engine/tokens.test.js +26 -0
  269. package/dist/test/unit/engine/tokens.test.js.map +1 -0
  270. package/dist/test/unit/events.test.d.ts +1 -0
  271. package/dist/test/unit/events.test.js +13 -0
  272. package/dist/test/unit/events.test.js.map +1 -0
  273. package/dist/test/unit/metrics.test.d.ts +1 -0
  274. package/dist/test/unit/metrics.test.js +108 -0
  275. package/dist/test/unit/metrics.test.js.map +1 -0
  276. package/dist/test/unit/notify.test.d.ts +1 -0
  277. package/dist/test/unit/notify.test.js +49 -0
  278. package/dist/test/unit/notify.test.js.map +1 -0
  279. package/dist/test/unit/period.test.d.ts +1 -0
  280. package/dist/test/unit/period.test.js +44 -0
  281. package/dist/test/unit/period.test.js.map +1 -0
  282. package/dist/test/unit/provider.test.d.ts +1 -0
  283. package/dist/test/unit/provider.test.js +47 -0
  284. package/dist/test/unit/provider.test.js.map +1 -0
  285. package/dist/test/unit/realtime.test.d.ts +1 -0
  286. package/dist/test/unit/realtime.test.js +13 -0
  287. package/dist/test/unit/realtime.test.js.map +1 -0
  288. package/dist/test/unit/render.test.d.ts +1 -0
  289. package/dist/test/unit/render.test.js +23 -0
  290. package/dist/test/unit/render.test.js.map +1 -0
  291. package/dist/test/unit/retention.test.d.ts +1 -0
  292. package/dist/test/unit/retention.test.js +66 -0
  293. package/dist/test/unit/retention.test.js.map +1 -0
  294. package/dist/test/unit/send-batch.test.d.ts +1 -0
  295. package/dist/test/unit/send-batch.test.js +46 -0
  296. package/dist/test/unit/send-batch.test.js.map +1 -0
  297. package/dist/test/unit/send.test.d.ts +1 -0
  298. package/dist/test/unit/send.test.js +120 -0
  299. package/dist/test/unit/send.test.js.map +1 -0
  300. package/dist/test/unit/smoke.test.d.ts +1 -0
  301. package/dist/test/unit/smoke.test.js +10 -0
  302. package/dist/test/unit/smoke.test.js.map +1 -0
  303. package/dist/test/unit/status.test.d.ts +1 -0
  304. package/dist/test/unit/status.test.js +16 -0
  305. package/dist/test/unit/status.test.js.map +1 -0
  306. package/dist/test/unit/templates.test.d.ts +1 -0
  307. package/dist/test/unit/templates.test.js +48 -0
  308. package/dist/test/unit/templates.test.js.map +1 -0
  309. package/dist/test/unit/tenant-export.test.d.ts +1 -0
  310. package/dist/test/unit/tenant-export.test.js +59 -0
  311. package/dist/test/unit/tenant-export.test.js.map +1 -0
  312. package/dist/test/unit/webhook-secret.test.d.ts +1 -0
  313. package/dist/test/unit/webhook-secret.test.js +42 -0
  314. package/dist/test/unit/webhook-secret.test.js.map +1 -0
  315. package/dist/types/blocks.d.ts +2 -0
  316. package/dist/types/blocks.js +3 -0
  317. package/dist/types/blocks.js.map +1 -0
  318. package/dist/types/index.d.ts +4 -0
  319. package/dist/types/index.js +21 -0
  320. package/dist/types/index.js.map +1 -0
  321. package/dist/types/resolved.d.ts +68 -0
  322. package/dist/types/resolved.js +3 -0
  323. package/dist/types/resolved.js.map +1 -0
  324. package/dist/types/sends.d.ts +14 -0
  325. package/dist/types/sends.js +19 -0
  326. package/dist/types/sends.js.map +1 -0
  327. package/dist/types/settings.d.ts +16 -0
  328. package/dist/types/settings.js +11 -0
  329. package/dist/types/settings.js.map +1 -0
  330. package/dist/validation/settings.schema.d.ts +63 -0
  331. package/dist/validation/settings.schema.js +29 -0
  332. package/dist/validation/settings.schema.js.map +1 -0
  333. package/dist/validation/templates.schema.d.ts +248 -0
  334. package/dist/validation/templates.schema.js +126 -0
  335. package/dist/validation/templates.schema.js.map +1 -0
  336. package/frontend-vue/.editorconfig +12 -0
  337. package/frontend-vue/.prettierrc +11 -0
  338. package/frontend-vue/app/components/AttentionCard.vue +78 -0
  339. package/frontend-vue/app/components/CategoriesInput.vue +126 -0
  340. package/frontend-vue/app/components/CategoryInput.vue +67 -0
  341. package/frontend-vue/app/components/Editor.vue +63 -0
  342. package/frontend-vue/app/components/FunnelCard.vue +66 -0
  343. package/frontend-vue/app/components/HtmlPreviewModal.vue +20 -0
  344. package/frontend-vue/app/components/NewTemplateModal.vue +158 -0
  345. package/frontend-vue/app/components/ProviderChip.vue +39 -0
  346. package/frontend-vue/app/components/SendDrawer.vue +298 -0
  347. package/frontend-vue/app/components/TemplateCard.vue +127 -0
  348. package/frontend-vue/app/components/TemplateDrawer.vue +394 -0
  349. package/frontend-vue/app/components/TemplateGallery.vue +112 -0
  350. package/frontend-vue/app/components/TemplatePreviewFrame.vue +103 -0
  351. package/frontend-vue/app/components/TestSendModal.vue +162 -0
  352. package/frontend-vue/app/components/TokenText.vue +26 -0
  353. package/frontend-vue/app/components/blocks/Button.vue +19 -0
  354. package/frontend-vue/app/components/blocks/Code.vue +17 -0
  355. package/frontend-vue/app/components/blocks/Divider.vue +3 -0
  356. package/frontend-vue/app/components/blocks/Footer.vue +22 -0
  357. package/frontend-vue/app/components/blocks/Heading.vue +18 -0
  358. package/frontend-vue/app/components/blocks/Hero.vue +32 -0
  359. package/frontend-vue/app/components/blocks/List.vue +33 -0
  360. package/frontend-vue/app/components/blocks/Paragraph.vue +18 -0
  361. package/frontend-vue/app/components/blocks/Total.vue +18 -0
  362. package/frontend-vue/app/components/editor/EditorBlock.vue +121 -0
  363. package/frontend-vue/app/components/editor/EditorBlockList.vue +44 -0
  364. package/frontend-vue/app/components/editor/EditorBlockSettings.vue +186 -0
  365. package/frontend-vue/app/components/editor/EditorCanvas.vue +91 -0
  366. package/frontend-vue/app/components/editor/EditorConditionFields.vue +88 -0
  367. package/frontend-vue/app/components/editor/EditorDataPanel.vue +128 -0
  368. package/frontend-vue/app/components/editor/EditorDraftBar.vue +35 -0
  369. package/frontend-vue/app/components/editor/EditorIfBlock.vue +120 -0
  370. package/frontend-vue/app/components/editor/EditorMissingLocale.vue +62 -0
  371. package/frontend-vue/app/components/editor/EditorPalette.vue +62 -0
  372. package/frontend-vue/app/components/editor/EditorRail.vue +58 -0
  373. package/frontend-vue/app/components/editor/EditorSubjectSettings.vue +37 -0
  374. package/frontend-vue/app/components/editor/EditorTemplateSettings.vue +87 -0
  375. package/frontend-vue/app/components/editor/EditorTestSendModal.vue +66 -0
  376. package/frontend-vue/app/components/editor/EditorToolbar.vue +155 -0
  377. package/frontend-vue/app/components/editor/EditorVariables.vue +163 -0
  378. package/frontend-vue/app/components/editor/EditorWorkspace.vue +189 -0
  379. package/frontend-vue/app/composables/useEditorContext.ts +44 -0
  380. package/frontend-vue/app/composables/useMailingApi.ts +197 -0
  381. package/frontend-vue/app/composables/useMailingPeriodData.ts +39 -0
  382. package/frontend-vue/app/composables/useTemplateDrawer.ts +90 -0
  383. package/frontend-vue/app/composables/useTemplateEditor.ts +228 -0
  384. package/frontend-vue/app/emails/EmailMailingTemplate.vue +24 -0
  385. package/frontend-vue/app/emails/components/MailingBlocks.vue +117 -0
  386. package/frontend-vue/app/plugins/template-gallery-display.client.ts +14 -0
  387. package/frontend-vue/app/types/component.ts +29 -0
  388. package/frontend-vue/app/types/mailing.ts +365 -0
  389. package/frontend-vue/app/utils/blocks/components.ts +14 -0
  390. package/frontend-vue/app/utils/blocks/dnd.ts +15 -0
  391. package/frontend-vue/app/utils/blocks/factory.ts +100 -0
  392. package/frontend-vue/app/utils/blocks/fields.ts +58 -0
  393. package/frontend-vue/app/utils/blocks/index.ts +5 -0
  394. package/frontend-vue/app/utils/blocks/tree.ts +74 -0
  395. package/frontend-vue/app/utils/categories.ts +54 -0
  396. package/frontend-vue/app/utils/clone.ts +3 -0
  397. package/frontend-vue/app/utils/conditions.ts +50 -0
  398. package/frontend-vue/app/utils/editor-route.ts +18 -0
  399. package/frontend-vue/app/utils/gallery.ts +41 -0
  400. package/frontend-vue/app/utils/locales.ts +41 -0
  401. package/frontend-vue/app/utils/paths.ts +84 -0
  402. package/frontend-vue/app/utils/period.ts +23 -0
  403. package/frontend-vue/app/utils/testData.ts +10 -0
  404. package/frontend-vue/app/utils/timeline.ts +68 -0
  405. package/frontend-vue/app/utils/tokens.ts +24 -0
  406. package/frontend-vue/app/utils/variables.ts +48 -0
  407. package/frontend-vue/dms.email.ts +9 -0
  408. package/frontend-vue/dms.frontend.ts +29 -0
  409. package/frontend-vue/eslint.config.mjs +13 -0
  410. package/frontend-vue/i18n/locales/mailing-en-GB.json +397 -0
  411. package/frontend-vue/i18n/locales/mailing-fr-FR.json +397 -0
  412. package/frontend-vue/package.json +32 -0
  413. package/frontend-vue/pnpm-lock.yaml +6446 -0
  414. package/frontend-vue/pnpm-workspace.yaml +2 -0
  415. package/frontend-vue/tests/block-components.test.ts +14 -0
  416. package/frontend-vue/tests/block-fields.test.ts +23 -0
  417. package/frontend-vue/tests/blocks-factory.test.ts +28 -0
  418. package/frontend-vue/tests/blocks-tree.test.ts +49 -0
  419. package/frontend-vue/tests/categories.test.ts +64 -0
  420. package/frontend-vue/tests/conditions.test.ts +36 -0
  421. package/frontend-vue/tests/dnd.test.ts +11 -0
  422. package/frontend-vue/tests/editor-block-list.test.ts +54 -0
  423. package/frontend-vue/tests/editor-route.test.ts +23 -0
  424. package/frontend-vue/tests/email-render-cases.json +77 -0
  425. package/frontend-vue/tests/gallery.test.ts +39 -0
  426. package/frontend-vue/tests/i18n-editor.test.ts +21 -0
  427. package/frontend-vue/tests/i18n.test.ts +83 -0
  428. package/frontend-vue/tests/locales.test.ts +38 -0
  429. package/frontend-vue/tests/mailing-api.test.ts +10 -0
  430. package/frontend-vue/tests/paths.test.ts +128 -0
  431. package/frontend-vue/tests/period.test.ts +13 -0
  432. package/frontend-vue/tests/smoke.test.ts +8 -0
  433. package/frontend-vue/tests/template-editor.test.ts +65 -0
  434. package/frontend-vue/tests/test-data.test.ts +15 -0
  435. package/frontend-vue/tests/timeline.test.ts +29 -0
  436. package/frontend-vue/tests/tokens.test.ts +12 -0
  437. package/frontend-vue/tests/variables.test.ts +46 -0
  438. package/frontend-vue/vitest.config.ts +10 -0
  439. package/package.json +94 -0
  440. package/src/antelope-modules.ts +48 -0
  441. package/src/automation/email-event-trigger.ts +112 -0
  442. package/src/automation/index.ts +33 -0
  443. package/src/automation/send-template-action.ts +100 -0
  444. package/src/config.ts +15 -0
  445. package/src/constants.ts +42 -0
  446. package/src/crons/index.ts +1 -0
  447. package/src/crons/retention.ts +126 -0
  448. package/src/data/body.ts +14 -0
  449. package/src/data/index.ts +3 -0
  450. package/src/data/sends-table.ts +154 -0
  451. package/src/data/templates-table.ts +202 -0
  452. package/src/data-types/categories-type.ts +29 -0
  453. package/src/data-types/category-type.ts +44 -0
  454. package/src/data-types/index.ts +2 -0
  455. package/src/db/index.ts +2 -0
  456. package/src/db/models/index.ts +4 -0
  457. package/src/db/models/send-events.model.ts +46 -0
  458. package/src/db/models/sends.model.ts +147 -0
  459. package/src/db/models/settings.model.ts +12 -0
  460. package/src/db/models/templates.model.ts +12 -0
  461. package/src/db/tables/index.ts +4 -0
  462. package/src/db/tables/send-events.table.ts +31 -0
  463. package/src/db/tables/sends.table.ts +85 -0
  464. package/src/db/tables/settings.table.ts +49 -0
  465. package/src/db/tables/templates.table.ts +70 -0
  466. package/src/engine/conditions.ts +30 -0
  467. package/src/engine/coverage.ts +67 -0
  468. package/src/engine/fallback-html.ts +116 -0
  469. package/src/engine/index.ts +7 -0
  470. package/src/engine/paths.ts +33 -0
  471. package/src/engine/resolve.ts +178 -0
  472. package/src/engine/tokens.ts +35 -0
  473. package/src/engine/walk.ts +18 -0
  474. package/src/hooks/index.ts +1 -0
  475. package/src/hooks/tenant-export.ts +127 -0
  476. package/src/implementations/dms-mailing/index.ts +2 -0
  477. package/src/index.ts +62 -0
  478. package/src/pages/editor.ts +23 -0
  479. package/src/pages/index.ts +11 -0
  480. package/src/pages/module.ts +9 -0
  481. package/src/pages/overview.ts +175 -0
  482. package/src/pages/sends.ts +174 -0
  483. package/src/pages/settings/form.ts +67 -0
  484. package/src/pages/settings/index.ts +2 -0
  485. package/src/pages/settings/page.ts +34 -0
  486. package/src/pages/templates.ts +141 -0
  487. package/src/permissions.ts +44 -0
  488. package/src/quick-actions.ts +20 -0
  489. package/src/realtime.ts +53 -0
  490. package/src/routes/events.ts +57 -0
  491. package/src/routes/index.ts +7 -0
  492. package/src/routes/metrics.ts +243 -0
  493. package/src/routes/period.ts +46 -0
  494. package/src/routes/provider.ts +31 -0
  495. package/src/routes/sends.ts +215 -0
  496. package/src/routes/settings.ts +66 -0
  497. package/src/routes/templates.ts +256 -0
  498. package/src/services/attention.ts +151 -0
  499. package/src/services/categories.ts +43 -0
  500. package/src/services/events.ts +121 -0
  501. package/src/services/metrics.ts +175 -0
  502. package/src/services/notify.ts +90 -0
  503. package/src/services/permissions.ts +42 -0
  504. package/src/services/provider.ts +46 -0
  505. package/src/services/render.ts +33 -0
  506. package/src/services/send.ts +409 -0
  507. package/src/services/settings.ts +74 -0
  508. package/src/services/status.ts +16 -0
  509. package/src/services/templates.ts +157 -0
  510. package/src/test/antelope.test.ts +188 -0
  511. package/src/test/helpers/http.ts +23 -0
  512. package/src/test/helpers/owner.ts +37 -0
  513. package/src/test/integration/events.test.ts +136 -0
  514. package/src/test/integration/retention.test.ts +363 -0
  515. package/src/test/integration/sends.test.ts +200 -0
  516. package/src/test/integration/settings.test.ts +50 -0
  517. package/src/test/integration/setup.test.ts +35 -0
  518. package/src/test/integration/templates.test.ts +229 -0
  519. package/src/test/integration/tenant-export.test.ts +125 -0
  520. package/src/test/unit/attention.test.ts +129 -0
  521. package/src/test/unit/automation.test.ts +130 -0
  522. package/src/test/unit/categories.test.ts +51 -0
  523. package/src/test/unit/engine/conditions.test.ts +35 -0
  524. package/src/test/unit/engine/coverage.test.ts +118 -0
  525. package/src/test/unit/engine/fallback-html.test.ts +86 -0
  526. package/src/test/unit/engine/paths.test.ts +39 -0
  527. package/src/test/unit/engine/resolve.test.ts +176 -0
  528. package/src/test/unit/engine/tokens.test.ts +26 -0
  529. package/src/test/unit/events.test.ts +19 -0
  530. package/src/test/unit/metrics.test.ts +170 -0
  531. package/src/test/unit/notify.test.ts +71 -0
  532. package/src/test/unit/period.test.ts +64 -0
  533. package/src/test/unit/provider.test.ts +57 -0
  534. package/src/test/unit/realtime.test.ts +11 -0
  535. package/src/test/unit/render.test.ts +23 -0
  536. package/src/test/unit/retention.test.ts +91 -0
  537. package/src/test/unit/send-batch.test.ts +72 -0
  538. package/src/test/unit/send.test.ts +161 -0
  539. package/src/test/unit/smoke.test.ts +8 -0
  540. package/src/test/unit/status.test.ts +14 -0
  541. package/src/test/unit/templates.test.ts +52 -0
  542. package/src/test/unit/tenant-export.test.ts +88 -0
  543. package/src/test/unit/webhook-secret.test.ts +50 -0
  544. package/src/types/blocks.ts +31 -0
  545. package/src/types/index.ts +4 -0
  546. package/src/types/resolved.ts +89 -0
  547. package/src/types/sends.ts +44 -0
  548. package/src/types/settings.ts +24 -0
  549. package/src/validation/settings.schema.ts +33 -0
  550. package/src/validation/templates.schema.ts +145 -0
@@ -0,0 +1,44 @@
1
+ import { CustomComponent } from "@antelopejs/interface-dms/base/custom";
2
+ import {
3
+ DataType,
4
+ RegisterDataType,
5
+ } from "@antelopejs/interface-dms/base/data-types";
6
+ import { DefaultDataCompareTypes } from "@antelopejs/interface-dms/base/data-types/compare-types";
7
+ import { z } from "zod";
8
+ import { TEMPLATE_CATEGORY_TYPE_ID } from "../constants";
9
+
10
+ /**
11
+ * A template's category. Deliberately **not** a `SelectType`: that builds its
12
+ * validation from a `z.enum` over build-time items, so every category the user
13
+ * adds in Settings afterwards would be rejected on save. The authority is the
14
+ * tenant's settings, so the value is validated as a plain optional string and
15
+ * the input fetches the current list.
16
+ *
17
+ * `IsEmpty` / `IsNotEmpty` are what the table filter offers as
18
+ * "uncategorised".
19
+ */
20
+ @RegisterDataType(TEMPLATE_CATEGORY_TYPE_ID)
21
+ export class TemplateCategoryType extends DataType {
22
+ constructor(public readonly options: Record<string, unknown> = {}) {
23
+ super(
24
+ [
25
+ DefaultDataCompareTypes.Is,
26
+ DefaultDataCompareTypes.IsNot,
27
+ DefaultDataCompareTypes.IsEmpty,
28
+ DefaultDataCompareTypes.IsNotEmpty,
29
+ ],
30
+ DefaultDataCompareTypes.Is,
31
+ options,
32
+ );
33
+ }
34
+
35
+ protected defaultInputComponent() {
36
+ return CustomComponent("DmsMailingCategoryInput")
37
+ .options(this.options)
38
+ .serializeSync();
39
+ }
40
+
41
+ getValidation() {
42
+ return z.string().optional();
43
+ }
44
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./categories-type";
2
+ export * from "./category-type";
@@ -0,0 +1,2 @@
1
+ export * from "./models";
2
+ export * from "./tables";
@@ -0,0 +1,4 @@
1
+ export * from "./send-events.model";
2
+ export * from "./sends.model";
3
+ export * from "./settings.model";
4
+ export * from "./templates.model";
@@ -0,0 +1,46 @@
1
+ import { BasicDataModel } from "@antelopejs/interface-database-decorators";
2
+ import { MAILING_SEND_EVENTS_TABLE_NAME, MailingSendEvent } from "../tables";
3
+ import { SendModel } from "./sends.model";
4
+
5
+ type NewSendEvent = Omit<MailingSendEvent, "_id">;
6
+
7
+ const FIRST_ROW_OFFSET = 0;
8
+
9
+ export class SendEventModel extends BasicDataModel(
10
+ MailingSendEvent,
11
+ MAILING_SEND_EVENTS_TABLE_NAME,
12
+ ) {
13
+ async getBySend(sendId: string): Promise<MailingSendEvent[]> {
14
+ return this.getBy("sendId", sendId);
15
+ }
16
+
17
+ /** Removes a late event if retention finished while its writer was paused. */
18
+ async insertForActiveSend(event: NewSendEvent): Promise<boolean> {
19
+ const [id] = await this.insert(event);
20
+ const send = await new SendModel(this.database).get(event.sendId);
21
+ if (send && !send.isRetiring) return true;
22
+ await this.delete(id);
23
+ return false;
24
+ }
25
+
26
+ /** Keyset pagination remains stable when overlapping sweeps delete earlier rows. */
27
+ listAfter(after: string, size: number): Promise<MailingSendEvent[]> {
28
+ return this.table
29
+ .filter((row) => row.key("_id").gt(after))
30
+ .orderBy("_id", "asc")
31
+ .slice(FIRST_ROW_OFFSET, size)
32
+ .run();
33
+ }
34
+
35
+ /**
36
+ * Drops a send's whole history in one statement. Retention purges up to
37
+ * 20 000 sends a night; deleting their events one row at a time turned that
38
+ * into six figures of round-trips.
39
+ */
40
+ deleteBySend(sendId: string): Promise<number> {
41
+ return this.table
42
+ .filter((row) => row.key("sendId").eq(sendId))
43
+ .delete()
44
+ .run();
45
+ }
46
+ }
@@ -0,0 +1,147 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { BasicDataModel } from "@antelopejs/interface-database-decorators";
3
+ import type {
4
+ AtomicMutationOutcome,
5
+ ValueProxy,
6
+ } from "@antelopejs/interface-database";
7
+ import { MAILING_SENDS_TABLE_NAME, MailingSend } from "../tables";
8
+
9
+ const FIRST_ROW_OFFSET = 0;
10
+ const SINGLE_ROW = 1;
11
+
12
+ type NewSend = Omit<
13
+ MailingSend,
14
+ "_id" | "createdAt" | "revision" | "isRetiring" | "lastActivityAt"
15
+ >;
16
+
17
+ type SendActivity = Pick<
18
+ MailingSend,
19
+ "status" | "opens" | "clicks" | "lastEventAt" | "provider"
20
+ >;
21
+
22
+ function canRetire(row: ValueProxy<MailingSend>, limit: Date) {
23
+ return row
24
+ .key("createdAt")
25
+ .lt(limit)
26
+ .and(row.key("status").ne("queued"))
27
+ .and(row.key("lastEventAt").default(row.key("createdAt")).lt(limit))
28
+ .and(row.key("lastActivityAt").default(row.key("createdAt")).lt(limit));
29
+ }
30
+
31
+ function isInactive(send: MailingSend, limit: Date): boolean {
32
+ return (
33
+ send.status !== "queued" &&
34
+ send.createdAt < limit &&
35
+ (send.lastEventAt ?? send.createdAt) < limit &&
36
+ (send.lastActivityAt ?? send.createdAt) < limit
37
+ );
38
+ }
39
+
40
+ function wasApplied(outcome: AtomicMutationOutcome): boolean {
41
+ if (outcome === "unknown")
42
+ throw new Error("Mailing mutation outcome is unknown; not retrying");
43
+ return outcome === "applied";
44
+ }
45
+
46
+ export class SendModel extends BasicDataModel(
47
+ MailingSend,
48
+ MAILING_SENDS_TABLE_NAME,
49
+ ) {
50
+ /** Creates a new immutable send identity and initializes its revision. */
51
+ insertSend(send: NewSend): Promise<string[]> {
52
+ return this.table
53
+ .insert({
54
+ ...send,
55
+ _id: randomUUID(),
56
+ createdAt: new Date(),
57
+ revision: randomUUID(),
58
+ })
59
+ .run();
60
+ }
61
+
62
+ /** Reads the stored row, including the revision the mutation contract compares. */
63
+ getSnapshot(sendId: string): Promise<MailingSend | undefined> {
64
+ return this.table.get(sendId).run();
65
+ }
66
+
67
+ async getByProviderMessageId(
68
+ messageId: string,
69
+ ): Promise<MailingSend | undefined> {
70
+ const rows = await this.table
71
+ .getAll(messageId, "providerMessageId")
72
+ .slice(FIRST_ROW_OFFSET, SINGLE_ROW)
73
+ .run();
74
+ return rows[0];
75
+ }
76
+
77
+ listBetween(from: Date, to: Date): Promise<MailingSend[]> {
78
+ return this.table
79
+ .filter((row) => row.key("createdAt").ge(from))
80
+ .filter((row) => row.key("createdAt").lt(to))
81
+ .run();
82
+ }
83
+
84
+ listOlderThan(limit: Date, pageSize: number): Promise<MailingSend[]> {
85
+ return this.table
86
+ .filter((row) => row.key("isRetiring").eq(true).or(canRetire(row, limit)))
87
+ .slice(FIRST_ROW_OFFSET, pageSize)
88
+ .run();
89
+ }
90
+
91
+ /** Atomically closes an inactive send to new events; retirement is irreversible. */
92
+ async retire(sendId: string, limit: Date): Promise<boolean> {
93
+ const send = await this.getSnapshot(sendId);
94
+ if (!send) return false;
95
+ if (send.isRetiring) return true;
96
+ if (!isInactive(send, limit)) return false;
97
+ const outcome = await this.table
98
+ .atomicMutation(sendId, {
99
+ type: "update",
100
+ revisionField: "revision",
101
+ expectedRevision: send.revision,
102
+ nextRevision: randomUUID(),
103
+ patch: { isRetiring: true },
104
+ })
105
+ .run();
106
+ if (wasApplied(outcome)) return true;
107
+ return (await this.get(sendId))?.isRetiring === true;
108
+ }
109
+
110
+ /** Returns the number of retired sends actually deleted, including overlapping sweeps. */
111
+ async deleteRetired(sendId: string): Promise<number> {
112
+ const send = await this.getSnapshot(sendId);
113
+ if (!send?.isRetiring) return 0;
114
+ const outcome = await this.table
115
+ .atomicMutation(sendId, {
116
+ type: "delete",
117
+ revisionField: "revision",
118
+ expectedRevision: send.revision,
119
+ })
120
+ .run();
121
+ return Number(wasApplied(outcome));
122
+ }
123
+
124
+ /** Accepts activity only before retirement, using ingestion time rather than provider time. */
125
+ async recordActivity(
126
+ send: MailingSend,
127
+ changes: SendActivity,
128
+ ): Promise<boolean> {
129
+ if (send.isRetiring) return false;
130
+ const patch = Object.fromEntries(
131
+ Object.entries({
132
+ ...changes,
133
+ lastActivityAt: new Date(),
134
+ }).filter(([, value]) => value !== undefined),
135
+ );
136
+ const outcome = await this.table
137
+ .atomicMutation(send._id, {
138
+ type: "update",
139
+ revisionField: "revision",
140
+ expectedRevision: send.revision,
141
+ nextRevision: randomUUID(),
142
+ patch,
143
+ })
144
+ .run();
145
+ return wasApplied(outcome);
146
+ }
147
+ }
@@ -0,0 +1,12 @@
1
+ import { BasicDataModel } from "@antelopejs/interface-database-decorators";
2
+ import { MAILING_SETTINGS_TABLE_NAME, MailingSettings } from "../tables";
3
+
4
+ export class SettingsModel extends BasicDataModel(
5
+ MailingSettings,
6
+ MAILING_SETTINGS_TABLE_NAME,
7
+ ) {
8
+ async getSingleton(): Promise<MailingSettings | undefined> {
9
+ const rows = await this.getAll();
10
+ return rows[0];
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ import { BasicDataModel } from "@antelopejs/interface-database-decorators";
2
+ import { MAILING_TEMPLATES_TABLE_NAME, MailingTemplate } from "../tables";
3
+
4
+ export class TemplateModel extends BasicDataModel(
5
+ MailingTemplate,
6
+ MAILING_TEMPLATES_TABLE_NAME,
7
+ ) {
8
+ async getBySlug(slug: string): Promise<MailingTemplate | undefined> {
9
+ const rows = await this.getBy("slug", slug);
10
+ return rows[0];
11
+ }
12
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./send-events.table";
2
+ export * from "./sends.table";
3
+ export * from "./settings.table";
4
+ export * from "./templates.table";
@@ -0,0 +1,31 @@
1
+ import {
2
+ Field,
3
+ Index,
4
+ RegisterTable,
5
+ Table,
6
+ } from "@antelopejs/interface-database-decorators";
7
+ import { TENANT_SCHEMA_NAME } from "@antelopejs/interface-dms/constants";
8
+ import type { SendEventType } from "../../types";
9
+
10
+ export const MAILING_SEND_EVENTS_TABLE_NAME = "mailing_send_events";
11
+
12
+ @RegisterTable(MAILING_SEND_EVENTS_TABLE_NAME, TENANT_SCHEMA_NAME)
13
+ export class MailingSendEvent extends Table {
14
+ @Field("string")
15
+ declare _id: string;
16
+
17
+ @Index()
18
+ @Field("string")
19
+ declare sendId: string;
20
+
21
+ @Field("string")
22
+ declare type: SendEventType;
23
+
24
+ @Index()
25
+ @Field("date")
26
+ declare at: Date;
27
+
28
+ /** JSON-serialized SendEventDetails reported by the provider. */
29
+ @Field("string")
30
+ declare json_details: string;
31
+ }
@@ -0,0 +1,85 @@
1
+ import {
2
+ CreationTime,
3
+ Field,
4
+ Index,
5
+ RegisterTable,
6
+ Table,
7
+ } from "@antelopejs/interface-database-decorators";
8
+ import { TENANT_SCHEMA_NAME } from "@antelopejs/interface-dms/constants";
9
+ import type { SendStatus } from "../../types";
10
+
11
+ export const MAILING_SENDS_TABLE_NAME = "mailing_sends";
12
+
13
+ @RegisterTable(MAILING_SENDS_TABLE_NAME, TENANT_SCHEMA_NAME)
14
+ export class MailingSend extends Table {
15
+ @Field("string")
16
+ declare _id: string;
17
+
18
+ @Index()
19
+ @CreationTime()
20
+ @Field("date")
21
+ declare createdAt: Date;
22
+
23
+ @Index()
24
+ @Field("string")
25
+ declare templateId: string;
26
+
27
+ @Index()
28
+ @Field("string")
29
+ declare templateSlug: string;
30
+
31
+ @Field("string")
32
+ declare locale: string;
33
+
34
+ @Index()
35
+ @Field("string")
36
+ declare recipientEmail: string;
37
+
38
+ @Field("string")
39
+ declare recipientName?: string;
40
+
41
+ @Index()
42
+ @Field("string")
43
+ declare status: SendStatus;
44
+
45
+ @Field("string")
46
+ declare provider?: string;
47
+
48
+ @Index()
49
+ @Field("string")
50
+ declare providerMessageId?: string;
51
+
52
+ @Field("number")
53
+ declare latencyMs: number;
54
+
55
+ @Field("string")
56
+ declare error?: string;
57
+
58
+ @Field("string")
59
+ declare source?: string;
60
+
61
+ /** JSON-serialized variables the template was rendered with. */
62
+ @Field("string")
63
+ declare json_variables: string;
64
+
65
+ @Field("number")
66
+ declare opens: number;
67
+
68
+ @Field("number")
69
+ declare clicks: number;
70
+
71
+ @Field("date")
72
+ declare lastEventAt?: Date;
73
+
74
+ @Field("date")
75
+ declare lastActivityAt?: Date;
76
+
77
+ @Field("string")
78
+ declare revision: string;
79
+
80
+ @Field("boolean")
81
+ declare isRetiring?: boolean;
82
+
83
+ @Field("boolean")
84
+ declare isTest: boolean;
85
+ }
@@ -0,0 +1,49 @@
1
+ import {
2
+ CreationTime,
3
+ Field,
4
+ RegisterTable,
5
+ Table,
6
+ UpdateTime,
7
+ } from "@antelopejs/interface-database-decorators";
8
+ import { TENANT_SCHEMA_NAME } from "@antelopejs/interface-dms/constants";
9
+
10
+ export const MAILING_SETTINGS_TABLE_NAME = "mailing_settings";
11
+
12
+ @RegisterTable(MAILING_SETTINGS_TABLE_NAME, TENANT_SCHEMA_NAME)
13
+ export class MailingSettings extends Table {
14
+ @Field("string")
15
+ declare _id: string;
16
+
17
+ @CreationTime()
18
+ @Field("date")
19
+ declare createdAt: Date;
20
+
21
+ @UpdateTime()
22
+ @Field("date")
23
+ declare updatedAt: Date;
24
+
25
+ @Field("string")
26
+ declare fallbackLocale: string;
27
+
28
+ @Field("number")
29
+ declare logRetentionDays: number;
30
+
31
+ @Field("boolean")
32
+ declare blockOnMissingVariables: boolean;
33
+
34
+ @Field("string")
35
+ declare senderName: string;
36
+
37
+ @Field("string")
38
+ declare senderEmail: string;
39
+
40
+ @Field("string")
41
+ declare replyTo: string;
42
+
43
+ /** JSON-serialized TemplateCategory[] offered when classifying a template. */
44
+ @Field("string")
45
+ declare json_categories: string;
46
+
47
+ @Field("string")
48
+ declare webhookSecret: string;
49
+ }
@@ -0,0 +1,70 @@
1
+ import {
2
+ CreationTime,
3
+ Field,
4
+ Index,
5
+ RegisterTable,
6
+ Table,
7
+ UpdateTime,
8
+ } from "@antelopejs/interface-database-decorators";
9
+ import { TENANT_SCHEMA_NAME } from "@antelopejs/interface-dms/constants";
10
+ import type { TemplateStatus } from "../../types";
11
+
12
+ export const MAILING_TEMPLATES_TABLE_NAME = "mailing_templates";
13
+
14
+ @RegisterTable(MAILING_TEMPLATES_TABLE_NAME, TENANT_SCHEMA_NAME)
15
+ export class MailingTemplate extends Table {
16
+ @Field("string")
17
+ declare _id: string;
18
+
19
+ @Index()
20
+ @CreationTime()
21
+ @Field("date")
22
+ declare createdAt: Date;
23
+
24
+ @Index()
25
+ @UpdateTime()
26
+ @Field("date")
27
+ declare updatedAt: Date;
28
+
29
+ @Index()
30
+ @Field("string")
31
+ declare slug: string;
32
+
33
+ @Field("string")
34
+ declare name: string;
35
+
36
+ @Index()
37
+ @Field("string")
38
+ declare category: string;
39
+
40
+ @Index()
41
+ @Field("string")
42
+ declare status: TemplateStatus;
43
+
44
+ /** JSON-serialized TemplateContent, replaced in place on every save. */
45
+ @Field("string")
46
+ declare json_content: string;
47
+
48
+ /** JSON-serialized VariableDefinition[] declared for this template. */
49
+ @Field("string")
50
+ declare json_variables: string;
51
+
52
+ /** JSON-serialized data set used by the preview and the test send. */
53
+ @Field("string")
54
+ declare json_test_data: string;
55
+
56
+ /**
57
+ * Locale codes present in the current version's content, comma-separated.
58
+ * Denormalised so a template row can show its coverage without loading the
59
+ * version. Blank on rows written before this field existed: unknown, not
60
+ * "none".
61
+ */
62
+ @Field("string")
63
+ declare locales: string;
64
+
65
+ @Field("string")
66
+ declare updatedBy: string;
67
+
68
+ @Field("date")
69
+ declare publishedAt?: Date;
70
+ }
@@ -0,0 +1,30 @@
1
+ import type { Condition, ConditionOperator } from "../types";
2
+ import { getPath } from "./paths";
3
+
4
+ type Evaluator = (actual: unknown, expected: string) => boolean;
5
+
6
+ const asNumber = (value: unknown): number => Number(value);
7
+
8
+ const EVALUATORS: Record<ConditionOperator, Evaluator> = {
9
+ truthy: (actual) => Boolean(actual),
10
+ falsy: (actual) => !actual,
11
+ eq: (actual, expected) => String(actual) === expected,
12
+ ne: (actual, expected) => String(actual) !== expected,
13
+ gt: (actual, expected) => asNumber(actual) > asNumber(expected),
14
+ lt: (actual, expected) => asNumber(actual) < asNumber(expected),
15
+ };
16
+
17
+ /**
18
+ * An unknown operator hides the block rather than throwing. Every entry point
19
+ * validates the operator against a `z.enum`, so this only guards content stored
20
+ * before an operator existed: a missing block is inspectable in the preview,
21
+ * a TypeError mid-send is a mail nobody receives.
22
+ */
23
+ export function evaluateCondition(
24
+ condition: Condition,
25
+ data: Record<string, unknown>,
26
+ ): boolean {
27
+ const evaluate = EVALUATORS[condition.operator];
28
+ if (!evaluate) return false;
29
+ return evaluate(getPath(data, condition.path), condition.value);
30
+ }
@@ -0,0 +1,67 @@
1
+ import { SYSTEM_VARIABLE_PATHS } from "../constants";
2
+ import type {
3
+ Block,
4
+ LocaleContent,
5
+ TemplateContent,
6
+ VariableDefinition,
7
+ } from "../types";
8
+ import { getPath, isPresent, normalizePath } from "./paths";
9
+ import { extractTokens } from "./tokens";
10
+ import { walkBlocks } from "./walk";
11
+
12
+ type PathCollector = (block: Block) => string[];
13
+
14
+ const textTokens = (...texts: string[]): string[] =>
15
+ texts.flatMap(extractTokens);
16
+
17
+ const COLLECTORS: Record<Block["type"], PathCollector> = {
18
+ hero: (block) =>
19
+ block.type === "hero" ? textTokens(block.imageUrl, block.alt) : [],
20
+ heading: (block) => (block.type === "heading" ? textTokens(block.text) : []),
21
+ paragraph: (block) =>
22
+ block.type === "paragraph" ? textTokens(block.text) : [],
23
+ code: (block) => (block.type === "code" ? textTokens(block.text) : []),
24
+ list: (block) => (block.type === "list" ? [normalizePath(block.source)] : []),
25
+ total: (block) =>
26
+ block.type === "total" ? textTokens(block.label, block.value) : [],
27
+ button: (block) =>
28
+ block.type === "button" ? textTokens(block.text, block.href) : [],
29
+ if: (block) =>
30
+ block.type === "if" ? [normalizePath(block.condition.path)] : [],
31
+ divider: () => [],
32
+ footer: (block) => (block.type === "footer" ? textTokens(block.text) : []),
33
+ };
34
+
35
+ const isUserPath = (path: string): boolean =>
36
+ !SYSTEM_VARIABLE_PATHS.includes(path);
37
+
38
+ export function collectVariablePaths(content: LocaleContent): string[] {
39
+ const paths = new Set<string>(textTokens(content.subject, content.preheader));
40
+ walkBlocks(content.blocks, (block) => {
41
+ for (const path of COLLECTORS[block.type](block)) {
42
+ paths.add(normalizePath(path));
43
+ }
44
+ if (block.visibleIf) paths.add(normalizePath(block.visibleIf.path));
45
+ });
46
+ return [...paths].filter(isUserPath).sort();
47
+ }
48
+
49
+ /** Every variable path the content references, across all of its locales. */
50
+ export function collectContentVariablePaths(
51
+ content: TemplateContent,
52
+ ): string[] {
53
+ const paths = Object.values(content.locales).flatMap((locale) =>
54
+ collectVariablePaths(locale),
55
+ );
56
+ return [...new Set(paths)].sort();
57
+ }
58
+
59
+ export function missingRequiredVariables(
60
+ declared: VariableDefinition[],
61
+ data: Record<string, unknown>,
62
+ ): string[] {
63
+ return declared
64
+ .filter((variable) => variable.required)
65
+ .map((variable) => normalizePath(variable.path))
66
+ .filter((path) => !isPresent(getPath(data, path)));
67
+ }