@beechcms/api 0.6.0-preview.1 → 0.6.0-preview.3

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 (373) hide show
  1. package/README.md +4 -4
  2. package/assets/dashboard/BeechLogo.svg +18 -18
  3. package/assets/dashboard/BeechLogoLIght.svg +48 -48
  4. package/assets/dashboard/assets/chart-Cws4Yj6K.js +39 -0
  5. package/assets/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  6. package/assets/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  7. package/assets/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  8. package/assets/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  9. package/assets/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  10. package/assets/dashboard/assets/index-BjPpppHe.css +1 -0
  11. package/assets/dashboard/assets/index-CrW-zhkA.js +642 -0
  12. package/assets/dashboard/assets/lora-cyrillic-ext-wght-normal-YcVxMP-u.woff2 +0 -0
  13. package/assets/dashboard/assets/lora-cyrillic-wght-normal-UWPuZLjM.woff2 +0 -0
  14. package/assets/dashboard/assets/lora-latin-ext-wght-normal-C2Wlntb9.woff2 +0 -0
  15. package/assets/dashboard/assets/lora-latin-wght-normal-BiLcIKcI.woff2 +0 -0
  16. package/assets/dashboard/assets/lora-math-wght-normal-sOy7rEfW.woff2 +0 -0
  17. package/assets/dashboard/assets/lora-symbols-wght-normal-DQ5VrUkH.woff2 +0 -0
  18. package/assets/dashboard/assets/lora-vietnamese-wght-normal-CfJ7gtf3.woff2 +0 -0
  19. package/assets/dashboard/assets/pie-chart-recharts-CAxoDQp2.js +1 -0
  20. package/assets/dashboard/assets/timeseries-chart-recharts-CixDW-LM.js +1 -0
  21. package/assets/dashboard/beechLogoDark.svg +48 -48
  22. package/assets/dashboard/index.html +18 -18
  23. package/assets/dashboard/noResult.svg +42 -42
  24. package/assets/dashboard/sol.svg +3 -3
  25. package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
  26. package/dist/auth/__fixtures__/in-memory-hash-provider.d.ts +5 -0
  27. package/dist/auth/__fixtures__/static-token-service.d.ts +6 -0
  28. package/dist/auth/bcrypt-hash-provider.d.ts +8 -0
  29. package/dist/auth/constants.d.ts +10 -0
  30. package/dist/auth/jose-token-service.d.ts +14 -0
  31. package/dist/auth/login.d.ts +32 -0
  32. package/dist/auth/refresh.d.ts +1 -0
  33. package/dist/factory.d.ts +39 -0
  34. package/dist/features/automations/action-executors/create-entry.executor.d.ts +7 -0
  35. package/dist/features/automations/action-executors/edit-field.executor.d.ts +7 -0
  36. package/dist/features/automations/action-executors/index.d.ts +13 -0
  37. package/dist/features/automations/action-executors/send-mail.executor.d.ts +7 -0
  38. package/dist/features/automations/action-executors/set-variable.executor.d.ts +15 -0
  39. package/dist/features/automations/action-executors/webhook.executor.d.ts +7 -0
  40. package/dist/features/automations/automation-runner.d.ts +13 -0
  41. package/dist/features/automations/automation-runner.utils.d.ts +3 -0
  42. package/dist/features/automations/automations.handler.d.ts +7 -0
  43. package/dist/features/automations/automations.schema.d.ts +191 -0
  44. package/dist/features/automations/context-resolver.d.ts +8 -0
  45. package/dist/features/automations/cron-runner.d.ts +9 -0
  46. package/dist/features/automations/cron-runner.utils.d.ts +1 -0
  47. package/dist/features/automations/filter-translation.d.ts +9 -0
  48. package/dist/features/automations/index.d.ts +7 -0
  49. package/dist/features/automations/template-grammar.d.ts +73 -0
  50. package/dist/features/automations/var-access-resolver.d.ts +6 -0
  51. package/dist/features/automations/when-evaluator.d.ts +3 -0
  52. package/dist/features/automations/when-pushdown.d.ts +2 -0
  53. package/dist/features/backrefs/backrefs.handler.d.ts +3 -0
  54. package/dist/features/backrefs/d1-backref.repository.d.ts +17 -0
  55. package/dist/features/backrefs/index.d.ts +1 -0
  56. package/dist/features/content/constants.d.ts +12 -0
  57. package/dist/features/content/handlers/bulk.handler.d.ts +3 -0
  58. package/dist/features/content/handlers/create.d.ts +3 -0
  59. package/dist/features/content/handlers/delete.d.ts +3 -0
  60. package/dist/features/content/handlers/facets.d.ts +3 -0
  61. package/dist/features/content/handlers/get.d.ts +4 -0
  62. package/dist/features/content/handlers/helpers.d.ts +12 -0
  63. package/dist/features/content/handlers/list.d.ts +3 -0
  64. package/dist/features/content/handlers/update.d.ts +3 -0
  65. package/dist/features/content/index.d.ts +4 -0
  66. package/dist/features/dashboard-layout/dashboard-layout.handler.d.ts +7 -0
  67. package/dist/features/dashboard-layout/index.d.ts +1 -0
  68. package/dist/features/draft/draft.handler.d.ts +4 -0
  69. package/dist/features/draft/draft.middleware.d.ts +9 -0
  70. package/dist/features/draft/index.d.ts +1 -0
  71. package/{src/features/email/email.provider.ts → dist/features/email/email.provider.d.ts} +37 -42
  72. package/dist/features/email/email.service.d.ts +13 -0
  73. package/dist/features/email/email.types.d.ts +94 -0
  74. package/{src/features/email/index.ts → dist/features/email/index.d.ts} +20 -33
  75. package/dist/features/email/providers/resend.d.ts +36 -0
  76. package/dist/features/email/providers/smtp.d.ts +11 -0
  77. package/dist/features/email/templates/automation-mail.d.ts +8 -0
  78. package/dist/features/email/templates/password-changed.d.ts +15 -0
  79. package/dist/features/email/templates/password-reset.d.ts +18 -0
  80. package/dist/features/email/templates/shell.d.ts +58 -0
  81. package/dist/features/notifications/index.d.ts +1 -0
  82. package/dist/features/notifications/notifications.handler.d.ts +15 -0
  83. package/dist/features/password-reset/index.d.ts +6 -0
  84. package/dist/features/password-reset/request.d.ts +10 -0
  85. package/dist/features/password-reset/reset.d.ts +9 -0
  86. package/dist/features/rotate-field/index.d.ts +1 -0
  87. package/dist/features/rotate-field/rotate-field.handler.d.ts +7 -0
  88. package/dist/features/rotate-field/rotate-field.schema.d.ts +11 -0
  89. package/dist/features/schema/schema.handler.d.ts +7 -0
  90. package/dist/features/seeds/index.d.ts +1 -0
  91. package/dist/features/seeds/seeds.handler.d.ts +6 -0
  92. package/dist/features/settings/settings.handler.d.ts +7 -0
  93. package/dist/features/setup/index.d.ts +7 -0
  94. package/dist/features/stats/index.d.ts +1 -0
  95. package/dist/features/stats/stats.handler.d.ts +7 -0
  96. package/dist/features/webhooks/index.d.ts +3 -0
  97. package/dist/index.d.ts +6 -0
  98. package/dist/index.js +9666 -0
  99. package/dist/media-utils.d.ts +25 -0
  100. package/dist/middleware/auth-providers.middleware.d.ts +8 -0
  101. package/dist/middleware/observability.middleware.d.ts +20 -0
  102. package/dist/middleware/rate-limit.middleware.d.ts +9 -0
  103. package/dist/middleware/repository.middleware.d.ts +34 -0
  104. package/dist/middleware/seed-registry.middleware.d.ts +10 -0
  105. package/dist/middleware/storage.middleware.d.ts +7 -0
  106. package/dist/middleware.d.ts +11 -0
  107. package/dist/public/access-policy.d.ts +12 -0
  108. package/dist/public/api-key-middleware.d.ts +6 -0
  109. package/dist/public/cache-utils.d.ts +10 -0
  110. package/dist/public/entry-projection.d.ts +2 -0
  111. package/dist/public/idempotency.d.ts +9 -0
  112. package/dist/public/index.d.ts +11 -0
  113. package/dist/public/problem-details.d.ts +31 -0
  114. package/dist/public/public-add.d.ts +3 -0
  115. package/dist/public/public-edit.d.ts +5 -0
  116. package/dist/public/public-errors.d.ts +13 -0
  117. package/dist/public/public-read.d.ts +3 -0
  118. package/dist/public/public-routes.d.ts +3 -0
  119. package/dist/public/query-builder.d.ts +28 -0
  120. package/dist/public/rate-limit-middleware.d.ts +3 -0
  121. package/dist/public/read-list.d.ts +26 -0
  122. package/dist/public/read-single.d.ts +22 -0
  123. package/dist/public/response-builder.d.ts +22 -0
  124. package/dist/public/sanitize.d.ts +23 -0
  125. package/dist/public/slug-utils.d.ts +11 -0
  126. package/dist/rate-limit/cloudflare-rate-limiter.d.ts +6 -0
  127. package/dist/rate-limit/in-memory-rate-limiter.d.ts +7 -0
  128. package/dist/rate-limit/no-op-rate-limiter.d.ts +4 -0
  129. package/dist/search-utils.d.ts +54 -0
  130. package/dist/search.d.ts +6 -0
  131. package/dist/shared/apply-policies.d.ts +10 -0
  132. package/dist/shared/automations.repository.d1.d.ts +12 -0
  133. package/dist/shared/background-notification-service.d.ts +18 -0
  134. package/dist/shared/base.repository.d1.d.ts +18 -0
  135. package/dist/shared/content-utils.d.ts +21 -0
  136. package/dist/shared/content.repository.d1.d.ts +68 -0
  137. package/dist/shared/d1-activity-log.repository.d.ts +13 -0
  138. package/dist/shared/d1-activity-logger.d.ts +20 -0
  139. package/dist/shared/d1-analytics.repository.d.ts +20 -0
  140. package/dist/shared/d1-content-scan.repository.d.ts +7 -0
  141. package/dist/shared/d1-notification.repository.d.ts +17 -0
  142. package/dist/shared/d1-password-reset-token.repository.d.ts +10 -0
  143. package/dist/shared/d1-search.repository.d.ts +14 -0
  144. package/dist/shared/d1-session.repository.d.ts +12 -0
  145. package/dist/shared/d1-setup-checklist.repository.d.ts +12 -0
  146. package/dist/shared/d1-user.repository.d.ts +18 -0
  147. package/dist/shared/d1-widget.repository.d.ts +31 -0
  148. package/dist/shared/dashboard-layout.repository.d1.d.ts +9 -0
  149. package/dist/shared/demo-data-sql.d.ts +1 -0
  150. package/dist/shared/demo-data.repository.d1.d.ts +6 -0
  151. package/dist/shared/execution-context-scheduler.d.ts +6 -0
  152. package/dist/shared/fixed-clock.d.ts +13 -0
  153. package/dist/shared/fts-sync.d.ts +1 -0
  154. package/dist/shared/idempotency.repository.d1.d.ts +16 -0
  155. package/dist/shared/in-memory-activity-logger.d.ts +11 -0
  156. package/dist/shared/in-memory-notification-service.d.ts +11 -0
  157. package/dist/shared/in-memory-seed.repository.d.ts +18 -0
  158. package/dist/shared/media.repository.d1.d.ts +35 -0
  159. package/dist/shared/qstash-notification-service.d.ts +18 -0
  160. package/dist/shared/query-utils.d.ts +36 -0
  161. package/dist/shared/request-utils.d.ts +13 -0
  162. package/dist/shared/schema-mutator.d1.d.ts +12 -0
  163. package/dist/shared/seed-layout.repository.d1.d.ts +9 -0
  164. package/dist/shared/seed-registry-cache.d.ts +12 -0
  165. package/dist/shared/seed.repository.d1.d.ts +13 -0
  166. package/dist/shared/sequential-id-generator.d.ts +13 -0
  167. package/dist/shared/site-settings.repository.d1.d.ts +7 -0
  168. package/dist/shared/storage/factory.d.ts +8 -0
  169. package/dist/shared/storage/s3-bucket.d.ts +43 -0
  170. package/dist/shared/storage-utils.d.ts +7 -0
  171. package/dist/shared/system-stats.repository.d1.d.ts +25 -0
  172. package/dist/types.d.ts +85 -0
  173. package/dist/upload.d.ts +16 -0
  174. package/dist/widget.d.ts +7 -0
  175. package/migrations/0000_v040_base.sql +265 -265
  176. package/migrations/0029_automations.sql +14 -14
  177. package/migrations/0030_test_seeds.sql +125 -125
  178. package/package.json +27 -18
  179. package/assets/dashboard/assets/index-BSjk0Sx9.js +0 -634
  180. package/assets/dashboard/assets/index-kRCYVN2B.css +0 -1
  181. package/src/auth/__fixtures__/in-memory-hash-provider.ts +0 -17
  182. package/src/auth/__fixtures__/static-token-service.ts +0 -22
  183. package/src/auth/bcrypt-hash-provider.ts +0 -24
  184. package/src/auth/constants.ts +0 -14
  185. package/src/auth/generate-refresh-token.test.ts +0 -23
  186. package/src/auth/hash-provider.test.ts +0 -55
  187. package/src/auth/jose-token-service.ts +0 -62
  188. package/src/auth/jwt-claims-passthrough.test.ts +0 -87
  189. package/src/auth/login.test.ts +0 -100
  190. package/src/auth/login.ts +0 -78
  191. package/src/auth/refresh.ts +0 -11
  192. package/src/auth/token-service.test.ts +0 -90
  193. package/src/factory.ts +0 -368
  194. package/src/features/automations/__tests__/action-executors.test.ts +0 -255
  195. package/src/features/automations/__tests__/automation-runner.test.ts +0 -196
  196. package/src/features/automations/__tests__/automation-runner.utils.test.ts +0 -60
  197. package/src/features/automations/__tests__/automations.handler.test.ts +0 -264
  198. package/src/features/automations/__tests__/automations.repository.test.ts +0 -163
  199. package/src/features/automations/__tests__/automations.schema.test.ts +0 -224
  200. package/src/features/automations/__tests__/context-resolver.test.ts +0 -126
  201. package/src/features/automations/__tests__/cron-runner.test.ts +0 -267
  202. package/src/features/automations/__tests__/cron-runner.utils.test.ts +0 -144
  203. package/src/features/automations/__tests__/set-variable.executor.test.ts +0 -383
  204. package/src/features/automations/__tests__/template-grammar.test.ts +0 -308
  205. package/src/features/automations/__tests__/webhook.executor.test.ts +0 -142
  206. package/src/features/automations/__tests__/when-evaluator.test.ts +0 -274
  207. package/src/features/automations/__tests__/when-pushdown.test.ts +0 -281
  208. package/src/features/automations/action-executors/create-entry.executor.ts +0 -27
  209. package/src/features/automations/action-executors/edit-field.executor.ts +0 -26
  210. package/src/features/automations/action-executors/index.ts +0 -37
  211. package/src/features/automations/action-executors/send-mail.executor.ts +0 -53
  212. package/src/features/automations/action-executors/set-variable.executor.ts +0 -159
  213. package/src/features/automations/action-executors/webhook.executor.ts +0 -67
  214. package/src/features/automations/automation-runner.ts +0 -85
  215. package/src/features/automations/automation-runner.utils.ts +0 -47
  216. package/src/features/automations/automations.handler.ts +0 -197
  217. package/src/features/automations/automations.schema.ts +0 -177
  218. package/src/features/automations/context-resolver.ts +0 -152
  219. package/src/features/automations/cron-runner.ts +0 -140
  220. package/src/features/automations/cron-runner.utils.ts +0 -44
  221. package/src/features/automations/filter-translation.ts +0 -46
  222. package/src/features/automations/index.ts +0 -16
  223. package/src/features/automations/template-grammar.ts +0 -245
  224. package/src/features/automations/var-access-resolver.ts +0 -140
  225. package/src/features/automations/when-evaluator.ts +0 -87
  226. package/src/features/automations/when-pushdown.ts +0 -57
  227. package/src/features/backrefs/__tests__/backrefs.handler.test.ts +0 -359
  228. package/src/features/backrefs/backrefs.handler.ts +0 -168
  229. package/src/features/backrefs/d1-backref.repository.ts +0 -84
  230. package/src/features/backrefs/index.ts +0 -5
  231. package/src/features/content/constants.ts +0 -16
  232. package/src/features/content/handlers/bulk.handler.ts +0 -185
  233. package/src/features/content/handlers/create.ts +0 -129
  234. package/src/features/content/handlers/delete.ts +0 -62
  235. package/src/features/content/handlers/facets.ts +0 -49
  236. package/src/features/content/handlers/get.ts +0 -120
  237. package/src/features/content/handlers/helpers.test.ts +0 -180
  238. package/src/features/content/handlers/helpers.ts +0 -93
  239. package/src/features/content/handlers/list.ts +0 -174
  240. package/src/features/content/handlers/update.ts +0 -174
  241. package/src/features/content/index.ts +0 -26
  242. package/src/features/draft/draft.handler.ts +0 -195
  243. package/src/features/draft/draft.middleware.ts +0 -66
  244. package/src/features/draft/index.ts +0 -5
  245. package/src/features/email/email.service.ts +0 -98
  246. package/src/features/email/email.types.ts +0 -119
  247. package/src/features/email/providers/resend.ts +0 -67
  248. package/src/features/email/providers/smtp.ts +0 -55
  249. package/src/features/email/templates/automation-mail.ts +0 -19
  250. package/src/features/email/templates/password-changed.ts +0 -63
  251. package/src/features/email/templates/password-reset.ts +0 -68
  252. package/src/features/email/templates/shell.ts +0 -96
  253. package/src/features/notifications/index.ts +0 -5
  254. package/src/features/notifications/notifications.handler.ts +0 -105
  255. package/src/features/password-reset/index.ts +0 -19
  256. package/src/features/password-reset/request.ts +0 -92
  257. package/src/features/password-reset/reset.ts +0 -102
  258. package/src/features/rotate-field/index.ts +0 -5
  259. package/src/features/rotate-field/rotate-field.handler.ts +0 -132
  260. package/src/features/rotate-field/rotate-field.schema.ts +0 -17
  261. package/src/features/schema/schema.handler.ts +0 -131
  262. package/src/features/seeds/index.ts +0 -5
  263. package/src/features/seeds/seeds.handler.test.ts +0 -632
  264. package/src/features/seeds/seeds.handler.ts +0 -693
  265. package/src/features/settings/__tests__/settings.handler.test.ts +0 -555
  266. package/src/features/settings/settings.handler.ts +0 -392
  267. package/src/features/setup/index.ts +0 -204
  268. package/src/features/stats/index.ts +0 -5
  269. package/src/features/stats/stats.handler.ts +0 -420
  270. package/src/features/webhooks/index.ts +0 -63
  271. package/src/index.ts +0 -65
  272. package/src/media-utils.ts +0 -82
  273. package/src/middleware/auth-providers.middleware.ts +0 -45
  274. package/src/middleware/observability.middleware.ts +0 -70
  275. package/src/middleware/rate-limit.middleware.ts +0 -45
  276. package/src/middleware/repository.middleware.ts +0 -114
  277. package/src/middleware/seed-registry.middleware.test.ts +0 -97
  278. package/src/middleware/seed-registry.middleware.ts +0 -21
  279. package/src/middleware/storage.middleware.ts +0 -25
  280. package/src/middleware.ts +0 -45
  281. package/src/public/access-policy.ts +0 -27
  282. package/src/public/api-key-middleware.ts +0 -57
  283. package/src/public/cache-utils.ts +0 -38
  284. package/src/public/entry-projection.ts +0 -46
  285. package/src/public/idempotency.ts +0 -23
  286. package/src/public/index.ts +0 -16
  287. package/src/public/problem-details.ts +0 -124
  288. package/src/public/public-add.ts +0 -114
  289. package/src/public/public-edit.ts +0 -163
  290. package/src/public/public-errors.ts +0 -19
  291. package/src/public/public-read.ts +0 -63
  292. package/src/public/public-routes.ts +0 -88
  293. package/src/public/query-builder.test.ts +0 -224
  294. package/src/public/query-builder.ts +0 -156
  295. package/src/public/rate-limit-middleware.ts +0 -34
  296. package/src/public/read-list.ts +0 -54
  297. package/src/public/read-single.ts +0 -48
  298. package/src/public/response-builder.ts +0 -30
  299. package/src/public/sanitize.ts +0 -69
  300. package/src/public/slug-utils.ts +0 -18
  301. package/src/rate-limit/cloudflare-rate-limiter.test.ts +0 -30
  302. package/src/rate-limit/cloudflare-rate-limiter.ts +0 -15
  303. package/src/rate-limit/in-memory-rate-limiter.test.ts +0 -37
  304. package/src/rate-limit/in-memory-rate-limiter.ts +0 -17
  305. package/src/rate-limit/no-op-rate-limiter.test.ts +0 -22
  306. package/src/rate-limit/no-op-rate-limiter.ts +0 -11
  307. package/src/search-utils.test.ts +0 -211
  308. package/src/search-utils.ts +0 -213
  309. package/src/search.ts +0 -65
  310. package/src/shared/apply-policies.test.ts +0 -81
  311. package/src/shared/apply-policies.ts +0 -67
  312. package/src/shared/automations.repository.d1.ts +0 -150
  313. package/src/shared/background-notification-service.test.ts +0 -62
  314. package/src/shared/background-notification-service.ts +0 -52
  315. package/src/shared/base.repository.d1.ts +0 -32
  316. package/src/shared/content-utils.test.ts +0 -165
  317. package/src/shared/content-utils.ts +0 -86
  318. package/src/shared/content.repository.d1.test.ts +0 -458
  319. package/src/shared/content.repository.d1.ts +0 -851
  320. package/src/shared/d1-activity-log.repository.test.ts +0 -140
  321. package/src/shared/d1-activity-log.repository.ts +0 -105
  322. package/src/shared/d1-activity-logger.test.ts +0 -86
  323. package/src/shared/d1-activity-logger.ts +0 -67
  324. package/src/shared/d1-analytics.repository.test.ts +0 -78
  325. package/src/shared/d1-analytics.repository.ts +0 -85
  326. package/src/shared/d1-content-scan.repository.test.ts +0 -80
  327. package/src/shared/d1-content-scan.repository.ts +0 -37
  328. package/src/shared/d1-notification.repository.test.ts +0 -128
  329. package/src/shared/d1-notification.repository.ts +0 -118
  330. package/src/shared/d1-password-reset-token.repository.test.ts +0 -81
  331. package/src/shared/d1-password-reset-token.repository.ts +0 -56
  332. package/src/shared/d1-search.repository.test.ts +0 -87
  333. package/src/shared/d1-search.repository.ts +0 -88
  334. package/src/shared/d1-session.repository.test.ts +0 -125
  335. package/src/shared/d1-session.repository.ts +0 -102
  336. package/src/shared/d1-setup-checklist.repository.ts +0 -36
  337. package/src/shared/d1-user.repository.test.ts +0 -151
  338. package/src/shared/d1-user.repository.ts +0 -119
  339. package/src/shared/d1-widget.repository.test.ts +0 -221
  340. package/src/shared/d1-widget.repository.ts +0 -341
  341. package/src/shared/demo-data-sql.ts +0 -54
  342. package/src/shared/demo-data.repository.d1.ts +0 -20
  343. package/src/shared/execution-context-scheduler.ts +0 -13
  344. package/src/shared/fixed-clock.ts +0 -25
  345. package/src/shared/fts-sync.ts +0 -8
  346. package/src/shared/idempotency.repository.d1.test.ts +0 -83
  347. package/src/shared/idempotency.repository.d1.ts +0 -60
  348. package/src/shared/in-memory-activity-logger.ts +0 -19
  349. package/src/shared/in-memory-notification-service.ts +0 -19
  350. package/src/shared/in-memory-seed.repository.ts +0 -51
  351. package/src/shared/media.repository.d1.test.ts +0 -107
  352. package/src/shared/media.repository.d1.ts +0 -68
  353. package/src/shared/qstash-notification-service.test.ts +0 -67
  354. package/src/shared/qstash-notification-service.ts +0 -55
  355. package/src/shared/query-utils.ts +0 -141
  356. package/src/shared/request-utils.ts +0 -26
  357. package/src/shared/schema-mutator.d1.ts +0 -64
  358. package/src/shared/seed-layout.repository.d1.test.ts +0 -114
  359. package/src/shared/seed-layout.repository.d1.ts +0 -62
  360. package/src/shared/seed-registry-cache.test.ts +0 -68
  361. package/src/shared/seed-registry-cache.ts +0 -49
  362. package/src/shared/seed.repository.d1.test.ts +0 -143
  363. package/src/shared/seed.repository.d1.ts +0 -98
  364. package/src/shared/sequential-id-generator.ts +0 -33
  365. package/src/shared/site-settings.repository.d1.ts +0 -53
  366. package/src/shared/storage/factory.ts +0 -41
  367. package/src/shared/storage/s3-bucket.ts +0 -195
  368. package/src/shared/storage-utils.ts +0 -40
  369. package/src/shared/system-stats.repository.d1.test.ts +0 -58
  370. package/src/shared/system-stats.repository.d1.ts +0 -48
  371. package/src/types.ts +0 -87
  372. package/src/upload.ts +0 -198
  373. package/src/widget.ts +0 -212
@@ -1,196 +0,0 @@
1
- // SPDX-License-Identifier: BUSL-1.1
2
- // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
- // See LICENSE in the repository root for license terms.
4
-
5
- import { describe, it, expect, vi, beforeEach } from 'vitest'
6
- import { AutomationRunner } from '../automation-runner'
7
- import type { AutomationRunnerDeps } from '../automation-runner'
8
- import type { IAutomationRepository, ContentRepository, Seed, IIdGenerator, Automation } from '@beechcms/core'
9
- import { resolveVarAccess } from '../var-access-resolver'
10
- import { parseTemplateKey } from '../template-grammar'
11
-
12
- function makeAutomation(overrides: Partial<Automation> = {}): Automation {
13
- return {
14
- id: 'auto-1',
15
- seed_slug: 'posts',
16
- name: 'Test Automation',
17
- enabled: true,
18
- triggers: [{ event: 'create' as const }],
19
- trigger_conditions: null,
20
- actions: [],
21
- created_at: 0,
22
- updated_at: 0,
23
- ...overrides,
24
- }
25
- }
26
-
27
- function makeDeps(overrides: Partial<AutomationRunnerDeps> = {}): AutomationRunnerDeps {
28
- return {
29
- automationRepository: {
30
- findActive: vi.fn().mockResolvedValue([]),
31
- } as unknown as IAutomationRepository,
32
- contentRepository: {} as ContentRepository,
33
- getSeed: vi.fn().mockReturnValue({ slug: 'posts', branches: [] } as unknown as Seed),
34
- idGenerator: { uuid: vi.fn().mockReturnValue('new-id') } as unknown as IIdGenerator,
35
- env: {},
36
- ...overrides,
37
- }
38
- }
39
-
40
- describe('AutomationRunner', () => {
41
- beforeEach(() => { vi.restoreAllMocks() })
42
-
43
- it('does nothing when no automations match', async () => {
44
- const deps = makeDeps()
45
- const runner = new AutomationRunner(deps)
46
- await runner.run({ seedSlug: 'posts', event: 'create', entry: { id: '1' } })
47
- expect(deps.automationRepository.findActive).toHaveBeenCalledWith('posts', 'create')
48
- })
49
-
50
- it('does nothing when getSeed returns null', async () => {
51
- const deps = makeDeps({ getSeed: vi.fn().mockReturnValue(null) })
52
- const runner = new AutomationRunner(deps)
53
- await runner.run({ seedSlug: 'unknown', event: 'create', entry: { id: '1' } })
54
- expect(deps.automationRepository.findActive).not.toHaveBeenCalled()
55
- })
56
-
57
- it('skips actions when trigger conditions are not met', async () => {
58
- const automation = makeAutomation({
59
- trigger_conditions: {
60
- kind: 'group',
61
- op: 'AND',
62
- children: [{ kind: 'predicate', left: { kind: 'ref', key: 'this.status' }, op: 'eq', right: { kind: 'literal', value: 'published' } }],
63
- },
64
- actions: [{ type: 'webhook', url: 'https://example.com' }],
65
- })
66
- const fetchMock = vi.fn().mockResolvedValue({ ok: true })
67
- vi.stubGlobal('fetch', fetchMock)
68
-
69
- const deps = makeDeps({
70
- automationRepository: {
71
- findActive: vi.fn().mockResolvedValue([automation]),
72
- } as unknown as IAutomationRepository,
73
- })
74
- const runner = new AutomationRunner(deps)
75
- await runner.run({ seedSlug: 'posts', event: 'create', entry: { id: '1', status: 'draft' } })
76
-
77
- expect(fetchMock).not.toHaveBeenCalled()
78
- })
79
-
80
- it('continues to next action when first action throws', async () => {
81
- const secondActionFetch = vi.fn().mockResolvedValue({ ok: true })
82
- vi.stubGlobal('fetch', vi.fn()
83
- .mockRejectedValueOnce(new Error('network error'))
84
- .mockResolvedValue({ ok: true }),
85
- )
86
-
87
- const automation = makeAutomation({
88
- actions: [
89
- { type: 'webhook', url: 'https://fail.example.com' },
90
- { type: 'webhook', url: 'https://success.example.com' },
91
- ],
92
- })
93
-
94
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
95
-
96
- const deps = makeDeps({
97
- automationRepository: {
98
- findActive: vi.fn().mockResolvedValue([automation]),
99
- } as unknown as IAutomationRepository,
100
- })
101
- const runner = new AutomationRunner(deps)
102
- await runner.run({ seedSlug: 'posts', event: 'create', entry: { id: '1', status: 'published' } })
103
-
104
- expect(consoleSpy).toHaveBeenCalledWith('[automations] action failed', expect.objectContaining({
105
- automationId: 'auto-1',
106
- actionType: 'webhook',
107
- }))
108
-
109
- // fetch was called twice (first failed, second succeeded)
110
- const fetchCalls = (global.fetch as ReturnType<typeof vi.fn>).mock.calls
111
- expect(fetchCalls).toHaveLength(2)
112
- expect(fetchCalls[1][0]).toBe('https://success.example.com')
113
- })
114
-
115
- it('runs actions when conditions pass', async () => {
116
- const fetchMock = vi.fn().mockResolvedValue({ ok: true })
117
- vi.stubGlobal('fetch', fetchMock)
118
-
119
- const automation = makeAutomation({
120
- trigger_conditions: {
121
- kind: 'group',
122
- op: 'AND',
123
- children: [{ kind: 'predicate', left: { kind: 'ref', key: 'this.status' }, op: 'eq', right: { kind: 'literal', value: 'published' } }],
124
- },
125
- actions: [{ type: 'webhook', url: 'https://example.com/hook' }],
126
- })
127
-
128
- const deps = makeDeps({
129
- automationRepository: {
130
- findActive: vi.fn().mockResolvedValue([automation]),
131
- } as unknown as IAutomationRepository,
132
- })
133
- const runner = new AutomationRunner(deps)
134
- await runner.run({ seedSlug: 'posts', event: 'create', entry: { id: '1', status: 'published' } })
135
-
136
- expect(fetchMock).toHaveBeenCalledOnce()
137
- expect(fetchMock.mock.calls[0][0]).toBe('https://example.com/hook')
138
- })
139
- })
140
-
141
- // ── Sprint 07: resolveVarAccess unit tests ─────────────────────────────────────
142
-
143
- describe('resolveVarAccess (Sprint 07)', () => {
144
- function makeCollectionVar(items: Array<Record<string, unknown>>) {
145
- const out: Record<string, unknown> = {
146
- count: items.length,
147
- firstone: items[0] ?? null,
148
- lastone: items[items.length - 1] ?? null,
149
- sum: {},
150
- avg: {},
151
- min: {},
152
- max: {},
153
- pluck: {},
154
- }
155
- Object.defineProperty(out, '_items', { value: items, enumerable: false })
156
- return out
157
- }
158
-
159
- const items = [
160
- { id: 'a', status: 'paid', amount: 100, created_at: 1 },
161
- { id: 'b', status: 'draft', amount: 50, created_at: 2 },
162
- { id: 'c', status: 'paid', amount: 200, created_at: 3 },
163
- ]
164
-
165
- it('array[id] selector filters _items and rebuilds collection', () => {
166
- const variables = { ordini: makeCollectionVar(items) }
167
- const parsed = parseTemplateKey('ordini.array[a,c].count')!
168
- expect(parsed.kind).toBe('var_access')
169
- const result = resolveVarAccess(parsed as any, variables)
170
- expect(result).toBe(2)
171
- })
172
-
173
- it('inline condition (status=paid) reduces count', () => {
174
- const variables = { ordini: makeCollectionVar(items) }
175
- const parsed = parseTemplateKey('ordini.count.(status=paid)')!
176
- const result = resolveVarAccess(parsed as any, variables)
177
- expect(result).toBe(2)
178
- })
179
-
180
- it('conditions on firstone.id return undefined when record fails predicate', () => {
181
- const variables = { ordini: makeCollectionVar(items) }
182
- // firstone is items[0] which has status=paid, so (status=draft) should fail
183
- const parsed = parseTemplateKey('ordini.firstone.status.(status=draft)')!
184
- const result = resolveVarAccess(parsed as any, variables)
185
- // firstone.status = 'paid', condition says status=draft → undefined
186
- expect(result).toBeUndefined()
187
- })
188
-
189
- it('returns undefined when variable is missing and calls onMissing', () => {
190
- const onMissing = vi.fn()
191
- const parsed = parseTemplateKey('missing.count.(status=paid)')!
192
- const result = resolveVarAccess(parsed as any, {}, onMissing)
193
- expect(result).toBeUndefined()
194
- expect(onMissing).toHaveBeenCalledWith('missing')
195
- })
196
- })
@@ -1,60 +0,0 @@
1
- // SPDX-License-Identifier: BUSL-1.1
2
- // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
- // See LICENSE in the repository root for license terms.
4
-
5
- import { describe, it, expect } from 'vitest'
6
- import { interpolate } from '../automation-runner.utils'
7
- import { resolveAutomationContext } from '../context-resolver'
8
-
9
- async function makeCtx(entry: Record<string, unknown>) {
10
- return resolveAutomationContext({} as any, entry, [entry])
11
- }
12
-
13
- describe('interpolate', () => {
14
- it('replaces a single placeholder', async () => {
15
- expect(interpolate('Hello {{name}}!', await makeCtx({ name: 'World' }))).toBe('Hello World!')
16
- })
17
-
18
- it('replaces multiple placeholders', async () => {
19
- expect(interpolate('{{name}} is {{status}}', await makeCtx({ name: 'Alice', status: 'active' }))).toBe('Alice is active')
20
- })
21
-
22
- it('missing field becomes empty string by default', async () => {
23
- expect(interpolate('Hello {{name}}!', await makeCtx({}))).toBe('Hello !')
24
- })
25
-
26
- it('no placeholders returns template unchanged', async () => {
27
- expect(interpolate('No placeholders here', await makeCtx({ name: 'World' }))).toBe('No placeholders here')
28
- })
29
-
30
- it('handles numeric values', async () => {
31
- expect(interpolate('Count: {{count}}', await makeCtx({ count: 42 }))).toBe('Count: 42')
32
- })
33
-
34
- it('supports spaces inside double braces', async () => {
35
- expect(interpolate('Hello {{ name }}!', await makeCtx({ name: 'DoubleSpace' }))).toBe('Hello DoubleSpace!')
36
- })
37
-
38
- it('uses custom default value when a field is missing', async () => {
39
- expect(interpolate('Hello {{name}}!', await makeCtx({}), '[missing]')).toBe('Hello [missing]!')
40
- })
41
-
42
- it('triggers onMissing callback when field is absent', async () => {
43
- const missing: string[] = []
44
- const res = interpolate('Hello {{name}}! From {{city}}', await makeCtx({}), '[mancante]', (f) => missing.push(f))
45
- expect(res).toBe('Hello [mancante]! From [mancante]')
46
- expect(missing).toEqual(['name', 'city'])
47
- })
48
-
49
- it('returns empty string when template is falsy', async () => {
50
- expect(interpolate('' as any, await makeCtx({}))).toBe('')
51
- })
52
-
53
- it('resolves {{this.field}} dot notation against the trigger entry', async () => {
54
- expect(interpolate('{{this.customer_id}}', await makeCtx({ customer_id: 'cust-42' }))).toBe('cust-42')
55
- })
56
-
57
- it('resolves {{this:field}} colon notation against the trigger entry', async () => {
58
- expect(interpolate('{{this:customer_id}}', await makeCtx({ customer_id: 'cust-42' }))).toBe('cust-42')
59
- })
60
- })
@@ -1,264 +0,0 @@
1
- // SPDX-License-Identifier: BUSL-1.1
2
- // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
- // See LICENSE in the repository root for license terms.
4
-
5
- import { describe, it, expect, vi, beforeEach } from 'vitest'
6
- import { Hono } from 'hono'
7
- import type { IAutomationRepository, Automation } from '@beechcms/core'
8
- import { automationsApp } from '../automations.handler'
9
-
10
- const baseAutomation: Automation = {
11
- id: 'test-id',
12
- seed_slug: 'posts',
13
- name: 'test',
14
- enabled: true,
15
- triggers: [{ event: 'create' }],
16
- trigger_conditions: null,
17
- actions: [{ type: 'webhook', url: 'https://example.com', body_template: '{}' }],
18
- created_at: 1000,
19
- updated_at: 1000,
20
- }
21
-
22
- function makeStub(overrides: Partial<IAutomationRepository> = {}): IAutomationRepository {
23
- return {
24
- list: vi.fn().mockResolvedValue([baseAutomation]),
25
- findById: vi.fn().mockResolvedValue(baseAutomation),
26
- findActive: vi.fn().mockResolvedValue([]),
27
- create: vi.fn().mockResolvedValue('new-id'),
28
- update: vi.fn().mockResolvedValue(undefined),
29
- toggle: vi.fn().mockResolvedValue(undefined),
30
- delete: vi.fn().mockResolvedValue(undefined),
31
- ...overrides,
32
- }
33
- }
34
-
35
- function buildApp(stub: IAutomationRepository) {
36
- const app = new Hono()
37
- app.use('*', async (c, next) => {
38
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
- ;(c as any).set('automationRepository', stub)
40
- await next()
41
- })
42
- app.route('/', automationsApp)
43
- return app
44
- }
45
-
46
- describe('GET /', () => {
47
- it('returns 400 missing-seed without ?seed=', async () => {
48
- const app = buildApp(makeStub())
49
- const res = await app.request('/')
50
- expect(res.status).toBe(400)
51
- const body = await res.json() as { type: string }
52
- expect(body.type).toContain('missing-seed')
53
- })
54
-
55
- it('returns 200 with automations list', async () => {
56
- const app = buildApp(makeStub())
57
- const res = await app.request('/?seed=posts')
58
- expect(res.status).toBe(200)
59
- const body = await res.json() as Automation[]
60
- expect(body).toHaveLength(1)
61
- expect(body[0].id).toBe('test-id')
62
- })
63
- })
64
-
65
- describe('POST /', () => {
66
- it('returns 400 invalid-json for malformed JSON body', async () => {
67
- const app = buildApp(makeStub())
68
- const res = await app.request('/', {
69
- method: 'POST',
70
- headers: { 'Content-Type': 'application/json' },
71
- body: 'malformed-not-json',
72
- })
73
- expect(res.status).toBe(400)
74
- const body = await res.json() as { type: string }
75
- expect(body.type).toContain('invalid-json')
76
- })
77
-
78
- it('returns 400 automation-validation-failed for schema non-compliant body', async () => {
79
- const app = buildApp(makeStub())
80
- const res = await app.request('/', {
81
- method: 'POST',
82
- headers: { 'Content-Type': 'application/json' },
83
- body: JSON.stringify({ seed_slug: 'posts' }), // missing name, triggers, actions
84
- })
85
- expect(res.status).toBe(400)
86
- const body = await res.json() as { type: string }
87
- expect(body.type).toContain('automation-validation-failed')
88
- })
89
-
90
- it('returns 201 with id for valid body including optional trigger fields', async () => {
91
- const app = buildApp(makeStub())
92
- const res = await app.request('/', {
93
- method: 'POST',
94
- headers: { 'Content-Type': 'application/json' },
95
- body: JSON.stringify({
96
- seed_slug: 'posts',
97
- name: 'test-cron-automation',
98
- triggers: [{ event: 'cron', cron: '0 0 * * *' }],
99
- trigger_conditions: {
100
- kind: 'group',
101
- op: 'AND',
102
- children: [
103
- { kind: 'predicate', left: { kind: 'ref', key: 'this.status' }, op: 'eq', right: { kind: 'literal', value: 'draft' } },
104
- ],
105
- },
106
- actions: [{ type: 'webhook', url: 'https://example.com', body_template: '{}' }],
107
- }),
108
- })
109
- expect(res.status).toBe(201)
110
- const body = await res.json() as { id: string }
111
- expect(body.id).toBe('new-id')
112
- })
113
-
114
- it('returns 201 with id for valid body omitting optional trigger fields', async () => {
115
- const app = buildApp(makeStub())
116
- const res = await app.request('/', {
117
- method: 'POST',
118
- headers: { 'Content-Type': 'application/json' },
119
- body: JSON.stringify({
120
- seed_slug: 'posts',
121
- name: 'test-create-automation',
122
- triggers: [{ event: 'create' }],
123
- actions: [{ type: 'webhook', url: 'https://example.com', body_template: '{}' }],
124
- }),
125
- })
126
- expect(res.status).toBe(201)
127
- const body = await res.json() as { id: string }
128
- expect(body.id).toBe('new-id')
129
- })
130
- })
131
-
132
- describe('GET /:id', () => {
133
- it('returns 404 when not found', async () => {
134
- const app = buildApp(makeStub({ findById: vi.fn().mockResolvedValue(null) }))
135
- const res = await app.request('/unknown-id')
136
- expect(res.status).toBe(404)
137
- })
138
-
139
- it('returns 200 with automation', async () => {
140
- const app = buildApp(makeStub())
141
- const res = await app.request('/test-id')
142
- expect(res.status).toBe(200)
143
- })
144
- })
145
-
146
- describe('PUT /:id', () => {
147
- it('returns 404 when not found', async () => {
148
- const app = buildApp(makeStub({ findById: vi.fn().mockResolvedValue(null) }))
149
- const res = await app.request('/unknown-id', {
150
- method: 'PUT',
151
- headers: { 'Content-Type': 'application/json' },
152
- body: JSON.stringify({ name: 'renamed' }),
153
- })
154
- expect(res.status).toBe(404)
155
- })
156
-
157
- it('returns 400 invalid-json for malformed JSON body', async () => {
158
- const app = buildApp(makeStub())
159
- const res = await app.request('/test-id', {
160
- method: 'PUT',
161
- headers: { 'Content-Type': 'application/json' },
162
- body: 'malformed-not-json',
163
- })
164
- expect(res.status).toBe(400)
165
- const body = await res.json() as { type: string }
166
- expect(body.type).toContain('invalid-json')
167
- })
168
-
169
- it('returns 400 automation-validation-failed for invalid update values', async () => {
170
- const app = buildApp(makeStub())
171
- const res = await app.request('/test-id', {
172
- method: 'PUT',
173
- headers: { 'Content-Type': 'application/json' },
174
- body: JSON.stringify({ name: 12345 }), // name must be string
175
- })
176
- expect(res.status).toBe(400)
177
- const body = await res.json() as { type: string }
178
- expect(body.type).toContain('automation-validation-failed')
179
- })
180
-
181
- it('returns 400 when cron trigger has no cron expression', async () => {
182
- const app = buildApp(makeStub())
183
- const res = await app.request('/test-id', {
184
- method: 'PUT',
185
- headers: { 'Content-Type': 'application/json' },
186
- body: JSON.stringify({ triggers: [{ event: 'cron' }] }),
187
- })
188
- expect(res.status).toBe(400)
189
- const body = await res.json() as { type: string }
190
- expect(body.type).toContain('automation-validation-failed')
191
- })
192
-
193
- it('returns 204 for valid update', async () => {
194
- const app = buildApp(makeStub())
195
- const res = await app.request('/test-id', {
196
- method: 'PUT',
197
- headers: { 'Content-Type': 'application/json' },
198
- body: JSON.stringify({ name: 'renamed' }),
199
- })
200
- expect(res.status).toBe(204)
201
- })
202
- })
203
-
204
- describe('PATCH /:id/toggle', () => {
205
- it('returns 404 when automation not found', async () => {
206
- const app = buildApp(makeStub({ findById: vi.fn().mockResolvedValue(null) }))
207
- const res = await app.request('/unknown-id/toggle', {
208
- method: 'PATCH',
209
- headers: { 'Content-Type': 'application/json' },
210
- body: JSON.stringify({ enabled: false }),
211
- })
212
- expect(res.status).toBe(404)
213
- })
214
-
215
- it('returns 400 invalid-json for malformed JSON body', async () => {
216
- const app = buildApp(makeStub())
217
- const res = await app.request('/test-id/toggle', {
218
- method: 'PATCH',
219
- headers: { 'Content-Type': 'application/json' },
220
- body: 'malformed-not-json',
221
- })
222
- expect(res.status).toBe(400)
223
- const body = await res.json() as { type: string }
224
- expect(body.type).toContain('invalid-json')
225
- })
226
-
227
- it('returns 400 automation-validation-failed for invalid payload', async () => {
228
- const app = buildApp(makeStub())
229
- const res = await app.request('/test-id/toggle', {
230
- method: 'PATCH',
231
- headers: { 'Content-Type': 'application/json' },
232
- body: JSON.stringify({ enabled: 'not-a-boolean' }),
233
- })
234
- expect(res.status).toBe(400)
235
- const body = await res.json() as { type: string }
236
- expect(body.type).toContain('automation-validation-failed')
237
- })
238
-
239
- it('returns 204 and calls toggle(id, false)', async () => {
240
- const stub = makeStub()
241
- const app = buildApp(stub)
242
- const res = await app.request('/test-id/toggle', {
243
- method: 'PATCH',
244
- headers: { 'Content-Type': 'application/json' },
245
- body: JSON.stringify({ enabled: false }),
246
- })
247
- expect(res.status).toBe(204)
248
- expect(stub.toggle).toHaveBeenCalledWith('test-id', false)
249
- })
250
- })
251
-
252
- describe('DELETE /:id', () => {
253
- it('returns 404 when not found', async () => {
254
- const app = buildApp(makeStub({ findById: vi.fn().mockResolvedValue(null) }))
255
- const res = await app.request('/unknown-id', { method: 'DELETE' })
256
- expect(res.status).toBe(404)
257
- })
258
-
259
- it('returns 204 on success', async () => {
260
- const app = buildApp(makeStub())
261
- const res = await app.request('/test-id', { method: 'DELETE' })
262
- expect(res.status).toBe(204)
263
- })
264
- })
@@ -1,163 +0,0 @@
1
- // SPDX-License-Identifier: BUSL-1.1
2
- // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
- // See LICENSE in the repository root for license terms.
4
-
5
- import { describe, it, expect, vi } from 'vitest'
6
- import { D1AutomationRepository } from '../../../shared/automations.repository.d1'
7
-
8
- function makeDb() {
9
- const calls: { sql: string; bindings: unknown[] }[] = []
10
- let nextAllResult: unknown[] = []
11
- let nextFirstResult: unknown = null
12
-
13
- const db = {
14
- _calls: calls,
15
- _setAllResult(rows: unknown[]) { nextAllResult = rows },
16
- _setFirstResult(row: unknown) { nextFirstResult = row },
17
- prepare(sql: string) {
18
- let boundValues: unknown[] = []
19
- const stmt = {
20
- bind(...args: unknown[]) { boundValues = args; return stmt },
21
- async run() {
22
- calls.push({ sql, bindings: boundValues })
23
- return { success: true }
24
- },
25
- async all() {
26
- calls.push({ sql, bindings: boundValues })
27
- return { results: nextAllResult }
28
- },
29
- async first() {
30
- calls.push({ sql, bindings: boundValues })
31
- return nextFirstResult
32
- },
33
- }
34
- return stmt
35
- },
36
- }
37
- return db as unknown as D1Database & typeof db
38
- }
39
-
40
- const fixtureRow = {
41
- id: 'abc',
42
- seed_slug: 'posts',
43
- name: 'test',
44
- enabled: 1,
45
- triggers: JSON.stringify([{ event: 'create' }]),
46
- trigger_conditions: null,
47
- actions: JSON.stringify([{ type: 'webhook', url: 'https://example.com' }]),
48
- created_at: 1000,
49
- updated_at: 1000,
50
- }
51
-
52
- describe('D1AutomationRepository.create', () => {
53
- it('serialises actions and trigger_conditions to JSON', async () => {
54
- const db = makeDb()
55
- const repo = new D1AutomationRepository(db)
56
- const whenNode = {
57
- kind: 'group' as const,
58
- op: 'AND' as const,
59
- children: [{ kind: 'predicate' as const, left: { kind: 'ref' as const, key: 'this.status' }, op: 'eq' as const, right: { kind: 'literal' as const, value: 'published' } }],
60
- }
61
- await repo.create({
62
- seed_slug: 'posts',
63
- name: 'test',
64
- triggers: [{ event: 'create' }],
65
- trigger_conditions: whenNode,
66
- actions: [{ type: 'webhook', url: 'https://example.com' }],
67
- })
68
- expect(db._calls).toHaveLength(1)
69
- const { bindings } = db._calls[0]
70
- // triggers is index 3, trigger_conditions is index 4, actions is index 5
71
- expect(typeof bindings[3]).toBe('string')
72
- expect(JSON.parse(bindings[3] as string)).toEqual([{ event: 'create' }])
73
- expect(typeof bindings[4]).toBe('string')
74
- expect(JSON.parse(bindings[4] as string)).toEqual(whenNode)
75
- expect(typeof bindings[5]).toBe('string')
76
- expect(JSON.parse(bindings[5] as string)).toEqual([{ type: 'webhook', url: 'https://example.com' }])
77
- })
78
-
79
- it('stores null for missing trigger_conditions', async () => {
80
- const db = makeDb()
81
- const repo = new D1AutomationRepository(db)
82
- await repo.create({
83
- seed_slug: 'posts',
84
- name: 'test',
85
- triggers: [{ event: 'create' }],
86
- trigger_conditions: null,
87
- actions: [{ type: 'webhook', url: 'https://example.com' }],
88
- })
89
- const { bindings } = db._calls[0]
90
- expect(bindings[4]).toBeNull()
91
- })
92
- })
93
-
94
- describe('D1AutomationRepository.update', () => {
95
- it('builds SET clause only from provided keys', async () => {
96
- const db = makeDb()
97
- const repo = new D1AutomationRepository(db)
98
- await repo.update('abc', { name: 'renamed' })
99
- expect(db._calls).toHaveLength(1)
100
- expect(db._calls[0].sql).toContain('name = ?')
101
- expect(db._calls[0].sql).not.toContain('seed_slug')
102
- })
103
-
104
- it('short-circuits when no fields provided', async () => {
105
- const db = makeDb()
106
- const repo = new D1AutomationRepository(db)
107
- await repo.update('abc', {})
108
- expect(db._calls).toHaveLength(0)
109
- })
110
- })
111
-
112
- describe('D1AutomationRepository.toggle', () => {
113
- it('only writes enabled and updated_at', async () => {
114
- const db = makeDb()
115
- const repo = new D1AutomationRepository(db)
116
- await repo.toggle('abc', false)
117
- expect(db._calls).toHaveLength(1)
118
- const { sql, bindings } = db._calls[0]
119
- expect(sql).toBe('UPDATE automations SET enabled = ?, updated_at = ? WHERE id = ?')
120
- expect(bindings[0]).toBe(0)
121
- expect(bindings[2]).toBe('abc')
122
- })
123
- })
124
-
125
- describe('rowToAutomation JSON round-trip', () => {
126
- it('deserialises trigger_conditions as null when column is null', async () => {
127
- const db = makeDb()
128
- db._setAllResult([fixtureRow])
129
- const repo = new D1AutomationRepository(db)
130
- const rows = await repo.list('posts')
131
- expect(rows[0].trigger_conditions).toBeNull()
132
- })
133
-
134
- it('deserialises trigger_conditions as WhenNode when column contains WhenNode JSON', async () => {
135
- const db = makeDb()
136
- const whenNode = {
137
- kind: 'group',
138
- op: 'AND',
139
- children: [{
140
- kind: 'predicate',
141
- left: { kind: 'ref', key: 'this.status' },
142
- op: 'eq',
143
- right: { kind: 'literal', value: 'published' },
144
- }],
145
- }
146
- const rowWithConditions = {
147
- ...fixtureRow,
148
- trigger_conditions: JSON.stringify(whenNode),
149
- }
150
- db._setAllResult([rowWithConditions])
151
- const repo = new D1AutomationRepository(db)
152
- const rows = await repo.list('posts')
153
- expect(rows[0].trigger_conditions).toEqual(whenNode)
154
- })
155
-
156
- it('deserialises actions as parsed array', async () => {
157
- const db = makeDb()
158
- db._setAllResult([fixtureRow])
159
- const repo = new D1AutomationRepository(db)
160
- const rows = await repo.list('posts')
161
- expect(rows[0].actions).toEqual([{ type: 'webhook', url: 'https://example.com' }])
162
- })
163
- })