@beechcms/api 0.6.0-preview.2 → 0.6.0-preview.4

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 (388) 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-BwlCt-IA.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-BcVN-DNy.js +642 -0
  11. package/assets/dashboard/assets/index-Cf1yRCqG.css +1 -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-BMWpjFzf.js +1 -0
  20. package/assets/dashboard/assets/timeseries-chart-recharts-8_GwAFR0.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 +45 -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} +19 -24
  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} +3 -16
  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 +7 -0
  98. package/dist/index.js +9732 -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/queue.middleware.d.ts +11 -0
  103. package/dist/middleware/rate-limit.middleware.d.ts +9 -0
  104. package/dist/middleware/repository.middleware.d.ts +34 -0
  105. package/dist/middleware/seed-registry.middleware.d.ts +10 -0
  106. package/dist/middleware/storage.middleware.d.ts +7 -0
  107. package/dist/middleware.d.ts +11 -0
  108. package/dist/public/access-policy.d.ts +12 -0
  109. package/dist/public/api-key-middleware.d.ts +6 -0
  110. package/dist/public/cache-utils.d.ts +10 -0
  111. package/dist/public/entry-projection.d.ts +2 -0
  112. package/dist/public/idempotency.d.ts +9 -0
  113. package/dist/public/index.d.ts +11 -0
  114. package/dist/public/problem-details.d.ts +31 -0
  115. package/dist/public/public-add.d.ts +3 -0
  116. package/dist/public/public-edit.d.ts +5 -0
  117. package/dist/public/public-errors.d.ts +13 -0
  118. package/dist/public/public-read.d.ts +3 -0
  119. package/dist/public/public-routes.d.ts +3 -0
  120. package/dist/public/query-builder.d.ts +28 -0
  121. package/dist/public/rate-limit-middleware.d.ts +3 -0
  122. package/dist/public/read-list.d.ts +26 -0
  123. package/dist/public/read-single.d.ts +22 -0
  124. package/dist/public/response-builder.d.ts +22 -0
  125. package/dist/public/sanitize.d.ts +23 -0
  126. package/{src/public/slug-utils.ts → dist/public/slug-utils.d.ts} +3 -10
  127. package/dist/rate-limit/cloudflare-rate-limiter.d.ts +6 -0
  128. package/dist/rate-limit/in-memory-rate-limiter.d.ts +7 -0
  129. package/dist/rate-limit/no-op-rate-limiter.d.ts +4 -0
  130. package/dist/search-utils.d.ts +54 -0
  131. package/dist/search.d.ts +6 -0
  132. package/dist/shared/apply-policies.d.ts +10 -0
  133. package/dist/shared/automations.repository.d1.d.ts +12 -0
  134. package/dist/shared/background-notification-service.d.ts +18 -0
  135. package/dist/shared/base.repository.d1.d.ts +18 -0
  136. package/dist/shared/cloudflare-queue-service.d.ts +11 -0
  137. package/dist/shared/content-utils.d.ts +21 -0
  138. package/dist/shared/content.repository.d1.d.ts +68 -0
  139. package/dist/shared/d1-activity-log.repository.d.ts +13 -0
  140. package/dist/shared/d1-activity-logger.d.ts +20 -0
  141. package/dist/shared/d1-analytics.repository.d.ts +20 -0
  142. package/dist/shared/d1-content-scan.repository.d.ts +7 -0
  143. package/dist/shared/d1-notification.repository.d.ts +17 -0
  144. package/dist/shared/d1-password-reset-token.repository.d.ts +10 -0
  145. package/dist/shared/d1-search.repository.d.ts +14 -0
  146. package/dist/shared/d1-session.repository.d.ts +12 -0
  147. package/dist/shared/d1-setup-checklist.repository.d.ts +12 -0
  148. package/dist/shared/d1-user.repository.d.ts +18 -0
  149. package/dist/shared/d1-widget.repository.d.ts +31 -0
  150. package/dist/shared/dashboard-layout.repository.d1.d.ts +9 -0
  151. package/dist/shared/demo-data-sql.d.ts +1 -0
  152. package/dist/shared/demo-data.repository.d1.d.ts +6 -0
  153. package/dist/shared/execution-context-scheduler.d.ts +6 -0
  154. package/dist/shared/fixed-clock.d.ts +13 -0
  155. package/dist/shared/fts-sync.d.ts +1 -0
  156. package/dist/shared/idempotency.repository.d1.d.ts +16 -0
  157. package/dist/shared/in-memory-activity-logger.d.ts +11 -0
  158. package/dist/shared/in-memory-notification-service.d.ts +11 -0
  159. package/dist/shared/in-memory-queue-service.d.ts +16 -0
  160. package/dist/shared/in-memory-seed.repository.d.ts +18 -0
  161. package/dist/shared/media.repository.d1.d.ts +35 -0
  162. package/dist/shared/qstash-notification-service.d.ts +18 -0
  163. package/dist/shared/query-utils.d.ts +36 -0
  164. package/dist/shared/queue-consumer.d.ts +9 -0
  165. package/dist/shared/request-utils.d.ts +13 -0
  166. package/dist/shared/schema-mutator.d1.d.ts +12 -0
  167. package/dist/shared/seed-layout.repository.d1.d.ts +9 -0
  168. package/dist/shared/seed-registry-cache.d.ts +12 -0
  169. package/dist/shared/seed.repository.d1.d.ts +13 -0
  170. package/dist/shared/sequential-id-generator.d.ts +13 -0
  171. package/dist/shared/site-settings.repository.d1.d.ts +7 -0
  172. package/dist/shared/storage/factory.d.ts +8 -0
  173. package/dist/shared/storage/s3-bucket.d.ts +43 -0
  174. package/dist/shared/storage-utils.d.ts +7 -0
  175. package/dist/shared/system-stats.repository.d1.d.ts +25 -0
  176. package/dist/types.d.ts +87 -0
  177. package/dist/upload.d.ts +16 -0
  178. package/dist/widget.d.ts +7 -0
  179. package/migrations/0000_v040_base.sql +265 -265
  180. package/migrations/0029_automations.sql +14 -14
  181. package/migrations/0030_test_seeds.sql +125 -125
  182. package/package.json +28 -18
  183. package/assets/dashboard/assets/chart-BrJFdsGZ.js +0 -39
  184. package/assets/dashboard/assets/index-CIaoMxuB.css +0 -1
  185. package/assets/dashboard/assets/index-wTKgygsg.js +0 -636
  186. package/assets/dashboard/assets/pie-chart-recharts-BEaE1FOE.js +0 -1
  187. package/assets/dashboard/assets/timeseries-chart-recharts-DmWM6imF.js +0 -1
  188. package/src/auth/__fixtures__/in-memory-hash-provider.ts +0 -17
  189. package/src/auth/__fixtures__/static-token-service.ts +0 -22
  190. package/src/auth/bcrypt-hash-provider.ts +0 -24
  191. package/src/auth/constants.ts +0 -14
  192. package/src/auth/generate-refresh-token.test.ts +0 -23
  193. package/src/auth/hash-provider.test.ts +0 -55
  194. package/src/auth/jose-token-service.ts +0 -62
  195. package/src/auth/jwt-claims-passthrough.test.ts +0 -87
  196. package/src/auth/login.test.ts +0 -100
  197. package/src/auth/login.ts +0 -78
  198. package/src/auth/refresh.ts +0 -11
  199. package/src/auth/token-service.test.ts +0 -90
  200. package/src/factory.ts +0 -370
  201. package/src/features/automations/__tests__/action-executors.test.ts +0 -255
  202. package/src/features/automations/__tests__/automation-runner.test.ts +0 -196
  203. package/src/features/automations/__tests__/automation-runner.utils.test.ts +0 -60
  204. package/src/features/automations/__tests__/automations.handler.test.ts +0 -264
  205. package/src/features/automations/__tests__/automations.repository.test.ts +0 -163
  206. package/src/features/automations/__tests__/automations.schema.test.ts +0 -224
  207. package/src/features/automations/__tests__/context-resolver.test.ts +0 -126
  208. package/src/features/automations/__tests__/cron-runner.test.ts +0 -267
  209. package/src/features/automations/__tests__/cron-runner.utils.test.ts +0 -144
  210. package/src/features/automations/__tests__/set-variable.executor.test.ts +0 -383
  211. package/src/features/automations/__tests__/template-grammar.test.ts +0 -308
  212. package/src/features/automations/__tests__/webhook.executor.test.ts +0 -142
  213. package/src/features/automations/__tests__/when-evaluator.test.ts +0 -274
  214. package/src/features/automations/__tests__/when-pushdown.test.ts +0 -281
  215. package/src/features/automations/action-executors/create-entry.executor.ts +0 -27
  216. package/src/features/automations/action-executors/edit-field.executor.ts +0 -26
  217. package/src/features/automations/action-executors/index.ts +0 -37
  218. package/src/features/automations/action-executors/send-mail.executor.ts +0 -53
  219. package/src/features/automations/action-executors/set-variable.executor.ts +0 -159
  220. package/src/features/automations/action-executors/webhook.executor.ts +0 -67
  221. package/src/features/automations/automation-runner.ts +0 -85
  222. package/src/features/automations/automation-runner.utils.ts +0 -47
  223. package/src/features/automations/automations.handler.ts +0 -197
  224. package/src/features/automations/automations.schema.ts +0 -177
  225. package/src/features/automations/context-resolver.ts +0 -152
  226. package/src/features/automations/cron-runner.ts +0 -140
  227. package/src/features/automations/cron-runner.utils.ts +0 -44
  228. package/src/features/automations/filter-translation.ts +0 -46
  229. package/src/features/automations/index.ts +0 -16
  230. package/src/features/automations/template-grammar.ts +0 -245
  231. package/src/features/automations/var-access-resolver.ts +0 -140
  232. package/src/features/automations/when-evaluator.ts +0 -87
  233. package/src/features/automations/when-pushdown.ts +0 -57
  234. package/src/features/backrefs/__tests__/backrefs.handler.test.ts +0 -359
  235. package/src/features/backrefs/backrefs.handler.ts +0 -168
  236. package/src/features/backrefs/d1-backref.repository.ts +0 -84
  237. package/src/features/backrefs/index.ts +0 -5
  238. package/src/features/content/constants.ts +0 -16
  239. package/src/features/content/handlers/bulk.handler.ts +0 -185
  240. package/src/features/content/handlers/create.ts +0 -129
  241. package/src/features/content/handlers/delete.ts +0 -62
  242. package/src/features/content/handlers/facets.ts +0 -49
  243. package/src/features/content/handlers/get.ts +0 -120
  244. package/src/features/content/handlers/helpers.test.ts +0 -180
  245. package/src/features/content/handlers/helpers.ts +0 -93
  246. package/src/features/content/handlers/list.ts +0 -174
  247. package/src/features/content/handlers/update.ts +0 -174
  248. package/src/features/content/index.ts +0 -26
  249. package/src/features/dashboard-layout/__tests__/dashboard-layout.handler.test.ts +0 -388
  250. package/src/features/dashboard-layout/dashboard-layout.handler.ts +0 -206
  251. package/src/features/dashboard-layout/index.ts +0 -5
  252. package/src/features/draft/draft.handler.ts +0 -195
  253. package/src/features/draft/draft.middleware.ts +0 -66
  254. package/src/features/draft/index.ts +0 -5
  255. package/src/features/email/email.service.ts +0 -98
  256. package/src/features/email/email.types.ts +0 -119
  257. package/src/features/email/providers/resend.ts +0 -67
  258. package/src/features/email/providers/smtp.ts +0 -55
  259. package/src/features/email/templates/automation-mail.ts +0 -19
  260. package/src/features/email/templates/password-changed.ts +0 -63
  261. package/src/features/email/templates/password-reset.ts +0 -68
  262. package/src/features/email/templates/shell.ts +0 -96
  263. package/src/features/notifications/index.ts +0 -5
  264. package/src/features/notifications/notifications.handler.ts +0 -105
  265. package/src/features/password-reset/index.ts +0 -19
  266. package/src/features/password-reset/request.ts +0 -92
  267. package/src/features/password-reset/reset.ts +0 -102
  268. package/src/features/rotate-field/index.ts +0 -5
  269. package/src/features/rotate-field/rotate-field.handler.ts +0 -132
  270. package/src/features/rotate-field/rotate-field.schema.ts +0 -17
  271. package/src/features/schema/schema.handler.ts +0 -131
  272. package/src/features/seeds/index.ts +0 -5
  273. package/src/features/seeds/seeds.handler.test.ts +0 -1199
  274. package/src/features/seeds/seeds.handler.ts +0 -558
  275. package/src/features/settings/__tests__/settings.handler.test.ts +0 -555
  276. package/src/features/settings/settings.handler.ts +0 -392
  277. package/src/features/setup/index.ts +0 -288
  278. package/src/features/stats/index.ts +0 -5
  279. package/src/features/stats/stats.handler.ts +0 -420
  280. package/src/features/webhooks/index.ts +0 -63
  281. package/src/index.ts +0 -65
  282. package/src/media-utils.ts +0 -82
  283. package/src/middleware/auth-providers.middleware.test.ts +0 -85
  284. package/src/middleware/auth-providers.middleware.ts +0 -45
  285. package/src/middleware/observability.middleware.test.ts +0 -93
  286. package/src/middleware/observability.middleware.ts +0 -70
  287. package/src/middleware/rate-limit.middleware.ts +0 -45
  288. package/src/middleware/repository.middleware.ts +0 -117
  289. package/src/middleware/seed-registry.middleware.test.ts +0 -97
  290. package/src/middleware/seed-registry.middleware.ts +0 -21
  291. package/src/middleware/storage.middleware.ts +0 -25
  292. package/src/middleware.ts +0 -45
  293. package/src/public/access-policy.ts +0 -27
  294. package/src/public/api-key-middleware.ts +0 -57
  295. package/src/public/cache-utils.ts +0 -38
  296. package/src/public/entry-projection.ts +0 -46
  297. package/src/public/idempotency.ts +0 -23
  298. package/src/public/index.ts +0 -16
  299. package/src/public/problem-details.ts +0 -124
  300. package/src/public/public-add.ts +0 -114
  301. package/src/public/public-edit.ts +0 -163
  302. package/src/public/public-errors.ts +0 -19
  303. package/src/public/public-read.ts +0 -63
  304. package/src/public/public-routes.ts +0 -88
  305. package/src/public/query-builder.test.ts +0 -224
  306. package/src/public/query-builder.ts +0 -156
  307. package/src/public/rate-limit-middleware.ts +0 -34
  308. package/src/public/read-list.ts +0 -54
  309. package/src/public/read-single.ts +0 -48
  310. package/src/public/response-builder.ts +0 -30
  311. package/src/public/sanitize.ts +0 -69
  312. package/src/rate-limit/cloudflare-rate-limiter.test.ts +0 -30
  313. package/src/rate-limit/cloudflare-rate-limiter.ts +0 -15
  314. package/src/rate-limit/in-memory-rate-limiter.test.ts +0 -37
  315. package/src/rate-limit/in-memory-rate-limiter.ts +0 -17
  316. package/src/rate-limit/no-op-rate-limiter.test.ts +0 -22
  317. package/src/rate-limit/no-op-rate-limiter.ts +0 -11
  318. package/src/search-utils.test.ts +0 -211
  319. package/src/search-utils.ts +0 -213
  320. package/src/search.ts +0 -65
  321. package/src/shared/apply-policies.test.ts +0 -81
  322. package/src/shared/apply-policies.ts +0 -67
  323. package/src/shared/automations.repository.d1.ts +0 -150
  324. package/src/shared/background-notification-service.test.ts +0 -62
  325. package/src/shared/background-notification-service.ts +0 -52
  326. package/src/shared/base.repository.d1.ts +0 -32
  327. package/src/shared/content-utils.test.ts +0 -165
  328. package/src/shared/content-utils.ts +0 -86
  329. package/src/shared/content.repository.d1.test.ts +0 -507
  330. package/src/shared/content.repository.d1.ts +0 -851
  331. package/src/shared/d1-activity-log.repository.test.ts +0 -140
  332. package/src/shared/d1-activity-log.repository.ts +0 -105
  333. package/src/shared/d1-activity-logger.test.ts +0 -86
  334. package/src/shared/d1-activity-logger.ts +0 -67
  335. package/src/shared/d1-analytics.repository.test.ts +0 -78
  336. package/src/shared/d1-analytics.repository.ts +0 -85
  337. package/src/shared/d1-content-scan.repository.test.ts +0 -80
  338. package/src/shared/d1-content-scan.repository.ts +0 -37
  339. package/src/shared/d1-notification.repository.test.ts +0 -128
  340. package/src/shared/d1-notification.repository.ts +0 -118
  341. package/src/shared/d1-password-reset-token.repository.test.ts +0 -81
  342. package/src/shared/d1-password-reset-token.repository.ts +0 -56
  343. package/src/shared/d1-search.repository.test.ts +0 -87
  344. package/src/shared/d1-search.repository.ts +0 -88
  345. package/src/shared/d1-session.repository.test.ts +0 -125
  346. package/src/shared/d1-session.repository.ts +0 -102
  347. package/src/shared/d1-setup-checklist.repository.ts +0 -36
  348. package/src/shared/d1-user.repository.test.ts +0 -151
  349. package/src/shared/d1-user.repository.ts +0 -119
  350. package/src/shared/d1-widget.repository.test.ts +0 -267
  351. package/src/shared/d1-widget.repository.ts +0 -385
  352. package/src/shared/dashboard-layout.repository.d1.test.ts +0 -135
  353. package/src/shared/dashboard-layout.repository.d1.ts +0 -58
  354. package/src/shared/demo-data-sql.ts +0 -216
  355. package/src/shared/demo-data.repository.d1.ts +0 -20
  356. package/src/shared/execution-context-scheduler.ts +0 -13
  357. package/src/shared/fixed-clock.ts +0 -25
  358. package/src/shared/fts-sync.ts +0 -8
  359. package/src/shared/idempotency.repository.d1.test.ts +0 -83
  360. package/src/shared/idempotency.repository.d1.ts +0 -60
  361. package/src/shared/in-memory-activity-logger.ts +0 -19
  362. package/src/shared/in-memory-notification-service.ts +0 -19
  363. package/src/shared/in-memory-seed.repository.ts +0 -58
  364. package/src/shared/media.repository.d1.test.ts +0 -107
  365. package/src/shared/media.repository.d1.ts +0 -68
  366. package/src/shared/qstash-notification-service.test.ts +0 -67
  367. package/src/shared/qstash-notification-service.ts +0 -55
  368. package/src/shared/query-utils.ts +0 -141
  369. package/src/shared/request-utils.ts +0 -26
  370. package/src/shared/schema-mutator.d1.test.ts +0 -142
  371. package/src/shared/schema-mutator.d1.ts +0 -64
  372. package/src/shared/seed-layout.repository.d1.test.ts +0 -114
  373. package/src/shared/seed-layout.repository.d1.ts +0 -62
  374. package/src/shared/seed-registry-cache.test.ts +0 -68
  375. package/src/shared/seed-registry-cache.ts +0 -49
  376. package/src/shared/seed.repository.d1.test.ts +0 -143
  377. package/src/shared/seed.repository.d1.ts +0 -98
  378. package/src/shared/sequential-id-generator.ts +0 -33
  379. package/src/shared/site-settings.repository.d1.ts +0 -53
  380. package/src/shared/storage/factory.ts +0 -41
  381. package/src/shared/storage/s3-bucket.ts +0 -195
  382. package/src/shared/storage-utils.ts +0 -40
  383. package/src/shared/system-stats.repository.d1.test.ts +0 -58
  384. package/src/shared/system-stats.repository.d1.ts +0 -48
  385. package/src/types.ts +0 -90
  386. package/src/upload.ts +0 -198
  387. package/src/widget.test.ts +0 -85
  388. package/src/widget.ts +0 -251
@@ -1,44 +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
- export function cronMatches(expression: string | null, scheduledTime: number): boolean {
6
- if (!expression) return false
7
- const parts = expression.trim().split(/\s+/)
8
- if (parts.length !== 5) return false
9
-
10
- const d = new Date(scheduledTime)
11
- const actual = [
12
- d.getUTCMinutes(),
13
- d.getUTCHours(),
14
- d.getUTCDate(),
15
- d.getUTCMonth() + 1,
16
- d.getUTCDay(),
17
- ] as const
18
-
19
- return parts.every((field, i) => matchField(field, actual[i]))
20
- }
21
-
22
- function matchField(field: string, value: number): boolean {
23
- if (field === '*') return true
24
- if (field.includes(',')) {
25
- return field.split(',').some((part) => matchField(part, value))
26
- }
27
- if (field.includes('/')) {
28
- const [rangePart, stepPart] = field.split('/')
29
- const step = Number(stepPart)
30
- if (!Number.isFinite(step) || step <= 0) return false
31
- if (rangePart === '*') return value % step === 0
32
- if (rangePart.includes('-')) {
33
- const [lo, hi] = rangePart.split('-').map(Number)
34
- return value >= lo && value <= hi && (value - lo) % step === 0
35
- }
36
- return false
37
- }
38
- if (field.includes('-')) {
39
- const [lo, hi] = field.split('-').map(Number)
40
- return Number.isFinite(lo) && Number.isFinite(hi) && value >= lo && value <= hi
41
- }
42
- const n = Number(field)
43
- return Number.isFinite(n) && n === value
44
- }
@@ -1,46 +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
- /**
6
- * Shared helper: converts a TriggerCondition into a FilterGroup accepted by
7
- * ContentRepository.findMany. Used by cron-runner and context-resolver.
8
- *
9
- * TODO Sprint 8 (Task 12): when-pushdown.ts will call extractPushdownFilters
10
- * to convert safe WhenNode predicates to FilterGroup[] for SQL pre-filtering.
11
- */
12
- import type { BranchType, FilterGroup, FilterOperator, FilterType, Seed, TriggerCondition } from '@beechcms/core'
13
-
14
- const SYSTEM_COLUMNS = new Set(['id', 'slug', 'status', 'created_at', 'updated_at'])
15
-
16
- function mapBranchTypeToFilterType(type: BranchType): FilterType {
17
- switch (type) {
18
- case 'number': return 'number'
19
- case 'boolean': return 'boolean'
20
- case 'date': return 'date'
21
- case 'tags': return 'tags'
22
- case 'json': return 'json'
23
- default: return 'text'
24
- }
25
- }
26
-
27
- function mapOp(op: TriggerCondition['op']): FilterOperator {
28
- switch (op) {
29
- case 'isempty': return 'is_empty'
30
- case 'isnotempty': return 'is_not_empty'
31
- default: return op
32
- }
33
- }
34
-
35
- export function conditionToFilterGroup(c: TriggerCondition, seed: Seed): FilterGroup {
36
- const branch = seed.branches.find((b) => b.alias === c.field)
37
- const type: FilterType = branch
38
- ? mapBranchTypeToFilterType(branch.type)
39
- : SYSTEM_COLUMNS.has(c.field) ? 'system' : 'text'
40
-
41
- return {
42
- column: c.field,
43
- type,
44
- conditions: [{ op: mapOp(c.op), value: c.value as string | number | boolean | null }],
45
- }
46
- }
@@ -1,16 +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
- export { AutomationRunner } from './automation-runner'
6
- export type { AutomationRunnerDeps } from './automation-runner'
7
- export { runCronAutomations } from './cron-runner'
8
- export type { CronRunnerDeps } from './cron-runner'
9
- export { automationsApp } from './automations.handler'
10
- export {
11
- createAutomationSchema,
12
- updateAutomationSchema,
13
- toggleAutomationSchema,
14
- automationActionSchema,
15
- } from './automations.schema'
16
- export type { CreateAutomationBody, UpdateAutomationBody } from './automations.schema'
@@ -1,245 +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
- /**
6
- * Pure template-key parser for the automation template engine.
7
- *
8
- * Grammar (Sprint 6):
9
- * simple: {{title}}, {{author.name}}
10
- * scoped: {{<scope>:<selector>:<field>}}
11
- *
12
- * Grammar (Sprint 7):
13
- * var_access: {{varname.firstone.campo}}, {{varname.count.(status=paid)}}
14
- * {{varname.array[id1,id2].count}}
15
- *
16
- * Scopes: this | batch | <seedSlug> | <contextKey>
17
- * Selectors: lastone | firstone | all | byid(<id>) | where(<alias>=<value>)
18
- * Fields: branch alias, system column, or aggregate (count/sum/avg/min/max/pluck)
19
- *
20
- * Sugar:
21
- * {{batch:count}} → batch:all:count
22
- * {{<scope>:<field>}} → <scope>:lastone:<field> (default selector)
23
- *
24
- * TODO Sprint 8: extend ParsedKey to support WhenOperand refs once the
25
- * recursive when-group evaluator (Tasks 10-16) is implemented.
26
- */
27
-
28
- export { AUTOMATION_RESERVED_WORDS } from '@beechcms/core'
29
-
30
- // ---------------------------------------------------------------------------
31
- // Sprint 7 types: var_access parsed key
32
- // ---------------------------------------------------------------------------
33
-
34
- export type VarStep =
35
- | { type: 'field'; name: string }
36
- | { type: 'nav'; nav: 'firstone' | 'lastone' }
37
- | { type: 'agg'; op: 'count' | 'sum' | 'avg' | 'min' | 'max' | 'pluck'; field?: string }
38
- | { type: 'array'; ids: string[] }
39
-
40
- export interface InlineCondition {
41
- column: string
42
- op: '=' | '!=' | '<' | '>' | '<=' | '>='
43
- value: string
44
- }
45
-
46
- // ---------------------------------------------------------------------------
47
-
48
- export type AutomationContextSelector =
49
- | { kind: 'lastone' }
50
- | { kind: 'firstone' }
51
- | { kind: 'all' }
52
- | { kind: 'byid'; id: string }
53
- | { kind: 'where'; alias: string; value: string }
54
-
55
- export type AggregateOp = 'count' | 'sum' | 'avg' | 'min' | 'max' | 'pluck'
56
-
57
- export type ParsedKey =
58
- | { kind: 'simple'; path: string }
59
- | {
60
- kind: 'scoped'
61
- scope: string
62
- selector: AutomationContextSelector
63
- op: 'field' | AggregateOp
64
- field: string | null
65
- }
66
- | {
67
- kind: 'var_access'
68
- name: string
69
- steps: VarStep[]
70
- conditions: InlineCondition[]
71
- }
72
-
73
- const SLUG_RE = /^[a-zA-Z0-9_-]+$/
74
- const AGGREGATE_OPS = new Set<string>(['count', 'sum', 'avg', 'min', 'max', 'pluck'])
75
- const NAV_OPS = new Set<string>(['firstone', 'lastone'])
76
- const INLINE_COND_RE = /^\(([\w]+)\s*(=|!=|<=|>=|<|>)\s*([^)]+)\)$/
77
-
78
- /**
79
- * Split `raw` on `:` but treat content inside `(...)` as atomic.
80
- */
81
- function splitKey(raw: string): string[] {
82
- const tokens: string[] = []
83
- let depth = 0
84
- let current = ''
85
-
86
- for (const ch of raw) {
87
- if (ch === '(') {
88
- depth++
89
- current += ch
90
- } else if (ch === ')') {
91
- depth--
92
- current += ch
93
- } else if (ch === ':' && depth === 0) {
94
- tokens.push(current)
95
- current = ''
96
- } else {
97
- current += ch
98
- }
99
- }
100
- if (current) tokens.push(current)
101
- return tokens
102
- }
103
-
104
- /**
105
- * Split `raw` on `.` treating `[...]` and `(...)` as atomic blocks.
106
- */
107
- function splitDot(raw: string): string[] {
108
- const tokens: string[] = []
109
- let depth = 0
110
- let current = ''
111
-
112
- for (const ch of raw) {
113
- if (ch === '[' || ch === '(') {
114
- depth++
115
- current += ch
116
- } else if (ch === ']' || ch === ')') {
117
- depth--
118
- current += ch
119
- } else if (ch === '.' && depth === 0) {
120
- tokens.push(current)
121
- current = ''
122
- } else {
123
- current += ch
124
- }
125
- }
126
- if (current) tokens.push(current)
127
- return tokens
128
- }
129
-
130
- function parseSelector(token: string): AutomationContextSelector | null {
131
- if (token === 'lastone') return { kind: 'lastone' }
132
- if (token === 'firstone') return { kind: 'firstone' }
133
- if (token === 'all') return { kind: 'all' }
134
-
135
- const byidMatch = token.match(/^byid\((.+)\)$/)
136
- if (byidMatch) return { kind: 'byid', id: byidMatch[1] }
137
-
138
- const whereMatch = token.match(/^where\(([^=]+)=(.+)\)$/)
139
- if (whereMatch) return { kind: 'where', alias: whereMatch[1], value: whereMatch[2] }
140
-
141
- return null
142
- }
143
-
144
- function isKnownScope(token: string): boolean {
145
- return token === 'this' || token === 'batch' || SLUG_RE.test(token)
146
- }
147
-
148
- export function parseTemplateKey(raw: string): ParsedKey | null {
149
- if (!raw) return null
150
-
151
- const trimmed = raw.trim()
152
-
153
- // Step 1: if contains `:` at depth 0 → existing scoped / sugar logic
154
- const colonTokens = splitKey(trimmed)
155
- if (colonTokens.length > 1) {
156
- const [scopeToken, ...rest] = colonTokens
157
- if (!isKnownScope(scopeToken)) return null
158
-
159
- if (rest.length === 1) {
160
- const fieldOrAggregate = rest[0]
161
- if (AGGREGATE_OPS.has(fieldOrAggregate)) {
162
- return { kind: 'scoped', scope: scopeToken, selector: { kind: 'all' }, op: fieldOrAggregate as AggregateOp, field: null }
163
- }
164
- return { kind: 'scoped', scope: scopeToken, selector: { kind: 'lastone' }, op: 'field', field: fieldOrAggregate }
165
- }
166
-
167
- const selectorToken = rest[0]
168
- const selector = parseSelector(selectorToken)
169
- if (!selector) return null
170
-
171
- const fieldTokens = rest.slice(1)
172
- if (fieldTokens.length === 0) return null
173
-
174
- const opToken = fieldTokens[0]
175
- if (AGGREGATE_OPS.has(opToken)) {
176
- if (selector.kind !== 'all') return null
177
- const subField = fieldTokens[1] ?? null
178
- return { kind: 'scoped', scope: scopeToken, selector, op: opToken as AggregateOp, field: subField }
179
- }
180
-
181
- return { kind: 'scoped', scope: scopeToken, selector, op: 'field', field: opToken }
182
- }
183
-
184
- // Step 2: tokenise on `.` at depth 0
185
- const dotTokens = splitDot(trimmed)
186
-
187
- // Step 3: if no token contains `[` or `(`, return simple (fast path)
188
- if (!dotTokens.some((t) => t.includes('[') || t.includes('('))) {
189
- return { kind: 'simple', path: trimmed }
190
- }
191
-
192
- // Step 4: build var_access
193
- const [nameToken, ...stepTokens] = dotTokens
194
- const steps: VarStep[] = []
195
- const conditions: InlineCondition[] = []
196
-
197
- let i = 0
198
- while (i < stepTokens.length) {
199
- const token = stepTokens[i]
200
-
201
- // Inline condition: (col op val)
202
- const condMatch = token.match(INLINE_COND_RE)
203
- if (condMatch) {
204
- conditions.push({ column: condMatch[1], op: condMatch[2] as InlineCondition['op'], value: condMatch[3].trim() })
205
- i++
206
- continue
207
- }
208
-
209
- // Array selector: array[id1,id2,...]
210
- const arrayMatch = token.match(/^array\[([^\]]*)\]$/)
211
- if (arrayMatch) {
212
- const ids = arrayMatch[1].split(',').map((s) => s.trim()).filter(Boolean)
213
- steps.push({ type: 'array', ids })
214
- i++
215
- continue
216
- }
217
-
218
- // Nav: firstone / lastone
219
- if (NAV_OPS.has(token)) {
220
- steps.push({ type: 'nav', nav: token as 'firstone' | 'lastone' })
221
- i++
222
- continue
223
- }
224
-
225
- // Aggregate: count | sum | avg | min | max | pluck
226
- if (AGGREGATE_OPS.has(token)) {
227
- const nextToken = stepTokens[i + 1]
228
- // Consume the next token as field if it's a plain identifier (no special chars)
229
- if (nextToken && !nextToken.includes('[') && !nextToken.includes('(') && !AGGREGATE_OPS.has(nextToken) && !NAV_OPS.has(nextToken)) {
230
- steps.push({ type: 'agg', op: token as VarStep & { type: 'agg' } extends { op: infer O } ? O : never, field: nextToken })
231
- i += 2
232
- } else {
233
- steps.push({ type: 'agg', op: token as any })
234
- i++
235
- }
236
- continue
237
- }
238
-
239
- // Plain field
240
- steps.push({ type: 'field', name: token })
241
- i++
242
- }
243
-
244
- return { kind: 'var_access', name: nameToken, steps, conditions }
245
- }
@@ -1,140 +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 type { Seed } from '@beechcms/core'
6
- import type { ParsedKey, InlineCondition } from './template-grammar'
7
- import { materializeCollection } from './action-executors/set-variable.executor'
8
-
9
- type VarAccessParsed = Extract<ParsedKey, { kind: 'var_access' }>
10
-
11
- function toNumeric(v: unknown): number {
12
- const n = Number(v)
13
- if (Number.isFinite(n)) return n
14
- if (typeof v === 'string') {
15
- const d = Date.parse(v)
16
- if (!Number.isNaN(d)) return d / 1000
17
- }
18
- return NaN
19
- }
20
-
21
- function numericCoerce(a: unknown, b: unknown): { left: unknown; right: unknown } {
22
- const na = toNumeric(a)
23
- const nb = toNumeric(b)
24
- const numeric = Number.isFinite(na) || Number.isFinite(nb)
25
- return { left: numeric ? na : a, right: numeric ? nb : b }
26
- }
27
-
28
- function applyCondition(record: Record<string, unknown>, cond: InlineCondition): boolean {
29
- const raw = record[cond.column]
30
- const { left, right } = numericCoerce(raw, cond.value)
31
- switch (cond.op) {
32
- case '=': return left === right
33
- case '!=': return left !== right
34
- case '<': return (left as number) < (right as number)
35
- case '>': return (left as number) > (right as number)
36
- case '<=': return (left as number) <= (right as number)
37
- case '>=': return (left as number) >= (right as number)
38
- }
39
- }
40
-
41
- function applyConditions(
42
- record: Record<string, unknown>,
43
- conditions: InlineCondition[],
44
- ): boolean {
45
- return conditions.every((c) => applyCondition(record, c))
46
- }
47
-
48
- function filterItems(
49
- items: Array<Record<string, unknown>>,
50
- conditions: InlineCondition[],
51
- ): Array<Record<string, unknown>> {
52
- if (conditions.length === 0) return items
53
- return items.filter((r) => applyConditions(r, conditions))
54
- }
55
-
56
- function getItems(value: unknown): Array<Record<string, unknown>> | null {
57
- if (!value || typeof value !== 'object') return null
58
- const items = (value as any)['_items']
59
- return Array.isArray(items) ? items : null
60
- }
61
-
62
- const EMPTY_SEED = { branches: [] } as unknown as Seed
63
-
64
- export function resolveVarAccess(
65
- parsed: VarAccessParsed,
66
- variables: Record<string, unknown>,
67
- onMissing?: (field: string) => void,
68
- ): unknown {
69
- if (!(parsed.name in variables)) {
70
- onMissing?.(parsed.name)
71
- return undefined
72
- }
73
-
74
- let current: unknown = variables[parsed.name]
75
- const { steps, conditions } = parsed
76
-
77
- for (let i = 0; i < steps.length; i++) {
78
- const step = steps[i]
79
-
80
- // ── array selector ─────────────────────────────────────────────────────
81
- if (step.type === 'array') {
82
- const items = getItems(current) ?? (Array.isArray(current) ? current as Array<Record<string, unknown>> : null)
83
- if (!items) return undefined
84
- const idSet = new Set(step.ids)
85
- const subset = items.filter((r) => idSet.has(String(r['id'] ?? '')))
86
- // Apply conditions to the subset before building the collection
87
- const filtered = filterItems(subset, conditions)
88
- current = materializeCollection(EMPTY_SEED, filtered, null)
89
- // Conditions consumed — no post-filter needed
90
- continue
91
- }
92
-
93
- // ── nav: firstone / lastone ────────────────────────────────────────────
94
- if (step.type === 'nav') {
95
- // Nav moves us from a collection to a single record.
96
- // Conditions are a guard on that record, NOT a pre-filter on _items.
97
- const record = (current as any)?.[step.nav] as Record<string, unknown> | null | undefined
98
- if (!record) { current = null; continue }
99
- // Apply conditions to the single record (post-filter)
100
- if (conditions.length > 0 && !applyConditions(record, conditions)) return undefined
101
- current = record
102
- continue
103
- }
104
-
105
- // ── aggregate ──────────────────────────────────────────────────────────
106
- if (step.type === 'agg') {
107
- // Apply conditions to _items before aggregating
108
- const items = getItems(current)
109
- if (items !== null) {
110
- const filtered = filterItems(items, conditions)
111
- current = materializeCollection(EMPTY_SEED, filtered, null)
112
- }
113
- if (step.op === 'count') {
114
- current = (current as any)?.['count']
115
- } else if (step.field) {
116
- const sub = (current as any)?.[step.op]
117
- current = typeof sub === 'object' && sub !== null ? sub[step.field] : sub
118
- } else {
119
- current = (current as any)?.[step.op]
120
- }
121
- // Conditions consumed by filtering _items
122
- return current
123
- }
124
-
125
- // ── field access ───────────────────────────────────────────────────────
126
- if (step.type === 'field') {
127
- current = (current as any)?.[step.name]
128
- }
129
- }
130
-
131
- // Post-filter on single record (conditions not consumed by agg or array)
132
- if (conditions.length > 0 && current !== null && current !== undefined) {
133
- if (typeof current === 'object' && !Array.isArray(current)) {
134
- if (!applyConditions(current as Record<string, unknown>, conditions)) return undefined
135
- }
136
- // For scalar values: conditions cannot be checked — pass through
137
- }
138
-
139
- return current
140
- }
@@ -1,87 +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 type { WhenNode, WhenPredicate, WhenGroup, WhenOperand } from '@beechcms/core'
6
- import type { ResolvedContext } from './context-resolver'
7
- import { parseTemplateKey } from './template-grammar'
8
-
9
- export function evaluateWhen(
10
- node: WhenNode | null,
11
- context: ResolvedContext,
12
- ): boolean {
13
- if (!node) return true
14
- if (node.kind === 'predicate') return evalPredicate(node, context)
15
- return evalGroup(node, context)
16
- }
17
-
18
- function evalGroup(group: WhenGroup, context: ResolvedContext): boolean {
19
- if (group.children.length === 0) {
20
- const empty = group.op === 'AND'
21
- return group.negate ? !empty : empty
22
- }
23
-
24
- for (const child of group.children) {
25
- const r = evaluateWhen(child, context)
26
- if (group.op === 'AND' && !r) return group.negate ? true : false
27
- if (group.op === 'OR' && r) return group.negate ? false : true
28
- }
29
-
30
- const combined = group.op === 'AND'
31
- return group.negate ? !combined : combined
32
- }
33
-
34
- function resolveOperand(operand: WhenOperand, context: ResolvedContext): unknown {
35
- if (operand.kind === 'literal') return operand.value
36
-
37
- // this.field (dot notation from UI) → this:field (colon, parsed as scoped key)
38
- const key = operand.key.startsWith('this.') ? 'this:' + operand.key.slice(5) : operand.key
39
- const parsed = parseTemplateKey(key)
40
- if (!parsed) return null
41
-
42
- const val = context.lookup(parsed)
43
- return val ?? null
44
- }
45
-
46
- function evalPredicate(pred: WhenPredicate, context: ResolvedContext): boolean {
47
- const left = resolveOperand(pred.left, context)
48
- const right = pred.right !== undefined ? resolveOperand(pred.right, context) : undefined
49
- return compare(pred.op, left, right)
50
- }
51
-
52
- function compare(op: string, left: unknown, right: unknown): boolean {
53
- switch (op) {
54
- case 'isempty': return left == null || left === ''
55
- case 'isnotempty': return left != null && left !== ''
56
-
57
- case 'eq': return coerceEqual(left, right)
58
- case 'neq': return !coerceEqual(left, right)
59
-
60
- case 'gt': return Number(left) > Number(right)
61
- case 'gte': return Number(left) >= Number(right)
62
- case 'lt': return Number(left) < Number(right)
63
- case 'lte': return Number(left) <= Number(right)
64
-
65
- case 'contains': return typeof left === 'string' && typeof right === 'string' && left.includes(right)
66
- case 'startswith': return typeof left === 'string' && typeof right === 'string' && left.startsWith(right)
67
- case 'endswith': return typeof left === 'string' && typeof right === 'string' && left.endsWith(right)
68
-
69
- case 'in': return Array.isArray(right) && right.some((r) => coerceEqual(left, r))
70
- case 'notin': return !Array.isArray(right) || !right.some((r) => coerceEqual(left, r))
71
-
72
- case 'matches': {
73
- if (typeof left !== 'string' || typeof right !== 'string') return false
74
- try { return new RegExp(right).test(left) } catch { return false }
75
- }
76
-
77
- default: return false
78
- }
79
- }
80
-
81
- function coerceEqual(a: unknown, b: unknown): boolean {
82
- if (a === null && b === null) return true
83
- if (a === null || b === null) return false
84
- if (typeof a === 'number' && typeof b === 'string' && !Number.isNaN(Number(b))) return a === Number(b)
85
- if (typeof b === 'number' && typeof a === 'string' && !Number.isNaN(Number(a))) return Number(a) === b
86
- return a === b
87
- }
@@ -1,57 +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 type { WhenNode, WhenPredicate, TriggerCondition, FilterGroup, Seed } from '@beechcms/core'
6
- import { conditionToFilterGroup } from './filter-translation'
7
-
8
- const PUSHDOWN_OPS = new Set<string>(['eq', 'neq', 'contains', 'gt', 'lt', 'isempty', 'isnotempty'])
9
-
10
- export function extractPushdownFilters(
11
- node: WhenNode | null,
12
- seed: Seed,
13
- ): FilterGroup[] {
14
- if (!node) return []
15
-
16
- // Single predicate at root
17
- if (node.kind === 'predicate') {
18
- const f = tryPushdown(node, seed)
19
- return f ? [f] : []
20
- }
21
-
22
- // Only extract direct predicate children of the outermost AND group (non-negated)
23
- if (node.kind === 'group' && node.op === 'AND' && !node.negate) {
24
- return node.children.flatMap((child) => {
25
- if (child.kind !== 'predicate') return []
26
- const f = tryPushdown(child, seed)
27
- return f ? [f] : []
28
- })
29
- }
30
-
31
- return []
32
- }
33
-
34
- function tryPushdown(pred: WhenPredicate, seed: Seed): FilterGroup | null {
35
- if (pred.left.kind !== 'ref') return null
36
-
37
- const key = pred.left.key
38
- if (!key.startsWith('this.')) return null
39
- const field = key.slice(5)
40
- if (!field) return null
41
-
42
- if (!PUSHDOWN_OPS.has(pred.op)) return null
43
-
44
- if (pred.op === 'isempty' || pred.op === 'isnotempty') {
45
- return conditionToFilterGroup(
46
- { field, op: pred.op as TriggerCondition['op'], value: null },
47
- seed,
48
- )
49
- }
50
-
51
- if (!pred.right || pred.right.kind !== 'literal') return null
52
-
53
- return conditionToFilterGroup(
54
- { field, op: pred.op as TriggerCondition['op'], value: pred.right.value },
55
- seed,
56
- )
57
- }