@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,142 +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 { executeWebhook } from '../action-executors/webhook.executor'
7
- import type { ResolvedContext } from '../context-resolver'
8
-
9
- function makeContext(entry: Record<string, unknown> = {}, event = 'create'): ResolvedContext {
10
- return {
11
- triggerEntry: entry,
12
- lookup(parsed) {
13
- if (parsed.kind === 'simple') {
14
- const key = parsed.path.startsWith('this.') ? parsed.path.slice(5) : parsed.path
15
- if (key === 'trigger_event') return event
16
- return entry[key] ?? null
17
- }
18
- return null
19
- },
20
- }
21
- }
22
-
23
- const baseContext = makeContext({ id: 'entry-1', title: 'Hello' })
24
-
25
- const baseAction = {
26
- type: 'webhook' as const,
27
- url: 'https://example.com/hook',
28
- body_template: '{"static":true}',
29
- }
30
-
31
- function makeOkResponse(status = 200): Response {
32
- return { ok: true, status } as unknown as Response
33
- }
34
-
35
- function makeErrorResponse(status: number): Response {
36
- return { ok: false, status } as unknown as Response
37
- }
38
-
39
- beforeEach(() => {
40
- vi.restoreAllMocks()
41
- })
42
-
43
- describe('executeWebhook', () => {
44
- it('signs request when WEBHOOK_SECRET is set', async () => {
45
- const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(makeOkResponse())
46
-
47
- await executeWebhook(baseAction, baseContext, { WEBHOOK_SECRET: 'test-secret' })
48
-
49
- expect(fetchSpy).toHaveBeenCalledOnce()
50
- const [, init] = fetchSpy.mock.calls[0]
51
- const headers = init?.headers as Record<string, string>
52
- expect(headers['X-BeechCMS-Signature']).toMatch(/^sha256=[0-9a-f]{64}$/)
53
- })
54
-
55
- it('omits signature header and warns once when WEBHOOK_SECRET is absent', async () => {
56
- const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(makeOkResponse())
57
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
58
-
59
- await executeWebhook(baseAction, baseContext, {})
60
- await executeWebhook(baseAction, baseContext, {})
61
-
62
- const [, init] = fetchSpy.mock.calls[0]
63
- const headers = init?.headers as Record<string, string>
64
- expect(headers['X-BeechCMS-Signature']).toBeUndefined()
65
- expect(warnSpy).toHaveBeenCalledOnce()
66
- expect(warnSpy.mock.calls[0][0]).toContain('WEBHOOK_SECRET')
67
- })
68
-
69
- it('overwrites user-supplied X-BeechCMS-Signature header with computed value', async () => {
70
- const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(makeOkResponse())
71
-
72
- const action = {
73
- ...baseAction,
74
- headers: { 'X-BeechCMS-Signature': 'sha256=user-supplied-value' },
75
- }
76
-
77
- await executeWebhook(action, baseContext, { WEBHOOK_SECRET: 'test-secret' })
78
-
79
- const [, init] = fetchSpy.mock.calls[0]
80
- const headers = init?.headers as Record<string, string>
81
- expect(headers['X-BeechCMS-Signature']).toMatch(/^sha256=[0-9a-f]{64}$/)
82
- expect(headers['X-BeechCMS-Signature']).not.toBe('sha256=user-supplied-value')
83
- })
84
-
85
- it('throws with status code when endpoint responds with 500', async () => {
86
- vi.spyOn(globalThis, 'fetch').mockResolvedValue(makeErrorResponse(500))
87
-
88
- await expect(executeWebhook(baseAction, baseContext, {}))
89
- .rejects.toThrow('500')
90
- })
91
-
92
- it('interpolates body_template placeholders before sending', async () => {
93
- const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(makeOkResponse())
94
-
95
- const action = { ...baseAction, body_template: '{"event":"{{trigger_event}}","id":"{{id}}"}' }
96
- const ctx = makeContext({ id: 'abc-123' }, 'update')
97
-
98
- await executeWebhook(action, ctx, {})
99
-
100
- const [, init] = fetchSpy.mock.calls[0]
101
- const body = init?.body as string
102
- expect(body).toContain('abc-123')
103
- expect(body).toContain('update')
104
- })
105
-
106
- it('recalculates HMAC and matches X-BeechCMS-Signature header', async () => {
107
- const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(makeOkResponse())
108
- const secret = 'verify-secret'
109
-
110
- await executeWebhook(baseAction, baseContext, { WEBHOOK_SECRET: secret })
111
-
112
- const [, init] = fetchSpy.mock.calls[0]
113
- const headers = init?.headers as Record<string, string>
114
- const sentBody = init?.body as string
115
- const sentSig = headers['X-BeechCMS-Signature']
116
-
117
- const encoder = new TextEncoder()
118
- const key = await crypto.subtle.importKey(
119
- 'raw',
120
- encoder.encode(secret),
121
- { name: 'HMAC', hash: 'SHA-256' },
122
- false,
123
- ['sign'],
124
- )
125
- const sig = await crypto.subtle.sign('HMAC', key, encoder.encode(sentBody))
126
- const expected = 'sha256=' + Array.from(new Uint8Array(sig))
127
- .map((b) => b.toString(16).padStart(2, '0'))
128
- .join('')
129
-
130
- expect(sentSig).toBe(expected)
131
- })
132
-
133
- it('throws timeout error when fetch takes longer than 8 seconds', async () => {
134
- vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
135
- const err = new DOMException('The operation was aborted.', 'TimeoutError')
136
- return Promise.reject(err)
137
- })
138
-
139
- await expect(executeWebhook(baseAction, baseContext, {}))
140
- .rejects.toThrow('timed out after 8000ms')
141
- })
142
- })
@@ -1,274 +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 type { WhenNode } from '@beechcms/core'
7
- import { evaluateWhen } from '../when-evaluator'
8
- import type { ResolvedContext } from '../context-resolver'
9
- import { parseTemplateKey } from '../template-grammar'
10
-
11
- // ---------------------------------------------------------------------------
12
- // Minimal ResolvedContext builder for testing
13
- // ---------------------------------------------------------------------------
14
-
15
- function makeContext(entry: Record<string, unknown>): ResolvedContext {
16
- return {
17
- triggerEntry: entry,
18
- lookup(parsed, onMissing) {
19
- if (!parsed) return undefined
20
- if (parsed.kind === 'simple') {
21
- const parts = parsed.path.split('.')
22
- let cur: unknown = entry
23
- for (const p of parts) {
24
- cur = (cur as Record<string, unknown>)?.[p]
25
- }
26
- if (cur === undefined && onMissing) onMissing(parsed.path)
27
- return cur
28
- }
29
- if (parsed.kind === 'scoped' && parsed.scope === 'this' && parsed.op === 'field' && parsed.field) {
30
- return (entry as Record<string, unknown>)[parsed.field]
31
- }
32
- return undefined
33
- },
34
- }
35
- }
36
-
37
- const entry = { status: 'paid', total: 150, name: 'Alice', tag: 'gold' }
38
- const ctx = makeContext(entry)
39
-
40
- // ---------------------------------------------------------------------------
41
- // null / empty → true
42
- // ---------------------------------------------------------------------------
43
-
44
- describe('evaluateWhen — null/empty', () => {
45
- it('null node returns true', () => {
46
- expect(evaluateWhen(null, ctx)).toBe(true)
47
- })
48
-
49
- it('empty AND group returns true', () => {
50
- const node: WhenNode = { kind: 'group', op: 'AND', children: [] }
51
- expect(evaluateWhen(node, ctx)).toBe(true)
52
- })
53
-
54
- it('empty OR group returns false', () => {
55
- const node: WhenNode = { kind: 'group', op: 'OR', children: [] }
56
- expect(evaluateWhen(node, ctx)).toBe(false)
57
- })
58
- })
59
-
60
- // ---------------------------------------------------------------------------
61
- // Basic predicates
62
- // ---------------------------------------------------------------------------
63
-
64
- describe('evaluateWhen — predicates', () => {
65
- it('eq literal', () => {
66
- const node: WhenNode = {
67
- kind: 'predicate',
68
- left: { kind: 'ref', key: 'this.status' },
69
- op: 'eq',
70
- right: { kind: 'literal', value: 'paid' },
71
- }
72
- expect(evaluateWhen(node, ctx)).toBe(true)
73
- })
74
-
75
- it('eq literal fails', () => {
76
- const node: WhenNode = {
77
- kind: 'predicate',
78
- left: { kind: 'ref', key: 'this.status' },
79
- op: 'eq',
80
- right: { kind: 'literal', value: 'unpaid' },
81
- }
82
- expect(evaluateWhen(node, ctx)).toBe(false)
83
- })
84
-
85
- it('gt numeric', () => {
86
- const node: WhenNode = {
87
- kind: 'predicate',
88
- left: { kind: 'ref', key: 'this.total' },
89
- op: 'gt',
90
- right: { kind: 'literal', value: 100 },
91
- }
92
- expect(evaluateWhen(node, ctx)).toBe(true)
93
- })
94
-
95
- it('lte numeric', () => {
96
- const node: WhenNode = {
97
- kind: 'predicate',
98
- left: { kind: 'ref', key: 'this.total' },
99
- op: 'lte',
100
- right: { kind: 'literal', value: 150 },
101
- }
102
- expect(evaluateWhen(node, ctx)).toBe(true)
103
- })
104
-
105
- it('contains string', () => {
106
- const node: WhenNode = {
107
- kind: 'predicate',
108
- left: { kind: 'ref', key: 'this.name' },
109
- op: 'contains',
110
- right: { kind: 'literal', value: 'lic' },
111
- }
112
- expect(evaluateWhen(node, ctx)).toBe(true)
113
- })
114
-
115
- it('startswith', () => {
116
- const node: WhenNode = {
117
- kind: 'predicate',
118
- left: { kind: 'ref', key: 'this.name' },
119
- op: 'startswith',
120
- right: { kind: 'literal', value: 'Ali' },
121
- }
122
- expect(evaluateWhen(node, ctx)).toBe(true)
123
- })
124
-
125
- it('isempty — non-empty field', () => {
126
- const node: WhenNode = {
127
- kind: 'predicate',
128
- left: { kind: 'ref', key: 'this.status' },
129
- op: 'isempty',
130
- }
131
- expect(evaluateWhen(node, ctx)).toBe(false)
132
- })
133
-
134
- it('isnotempty — non-empty field', () => {
135
- const node: WhenNode = {
136
- kind: 'predicate',
137
- left: { kind: 'ref', key: 'this.status' },
138
- op: 'isnotempty',
139
- }
140
- expect(evaluateWhen(node, ctx)).toBe(true)
141
- })
142
-
143
- it('matches regex', () => {
144
- const node: WhenNode = {
145
- kind: 'predicate',
146
- left: { kind: 'ref', key: 'this.tag' },
147
- op: 'matches',
148
- right: { kind: 'literal', value: '^g' },
149
- }
150
- expect(evaluateWhen(node, ctx)).toBe(true)
151
- })
152
-
153
- it('literal-to-literal comparison', () => {
154
- const node: WhenNode = {
155
- kind: 'predicate',
156
- left: { kind: 'literal', value: 10 },
157
- op: 'lt',
158
- right: { kind: 'literal', value: 20 },
159
- }
160
- expect(evaluateWhen(node, ctx)).toBe(true)
161
- })
162
- })
163
-
164
- // ---------------------------------------------------------------------------
165
- // AND / OR groups with short-circuit
166
- // ---------------------------------------------------------------------------
167
-
168
- describe('evaluateWhen — groups', () => {
169
- it('AND group — all true', () => {
170
- const node: WhenNode = {
171
- kind: 'group',
172
- op: 'AND',
173
- children: [
174
- { kind: 'predicate', left: { kind: 'ref', key: 'this.status' }, op: 'eq', right: { kind: 'literal', value: 'paid' } },
175
- { kind: 'predicate', left: { kind: 'ref', key: 'this.total' }, op: 'gt', right: { kind: 'literal', value: 100 } },
176
- ],
177
- }
178
- expect(evaluateWhen(node, ctx)).toBe(true)
179
- })
180
-
181
- it('AND group — one false', () => {
182
- const node: WhenNode = {
183
- kind: 'group',
184
- op: 'AND',
185
- children: [
186
- { kind: 'predicate', left: { kind: 'ref', key: 'this.status' }, op: 'eq', right: { kind: 'literal', value: 'paid' } },
187
- { kind: 'predicate', left: { kind: 'ref', key: 'this.total' }, op: 'gt', right: { kind: 'literal', value: 200 } },
188
- ],
189
- }
190
- expect(evaluateWhen(node, ctx)).toBe(false)
191
- })
192
-
193
- it('OR group — first true short-circuits', () => {
194
- const node: WhenNode = {
195
- kind: 'group',
196
- op: 'OR',
197
- children: [
198
- { kind: 'predicate', left: { kind: 'ref', key: 'this.status' }, op: 'eq', right: { kind: 'literal', value: 'paid' } },
199
- { kind: 'predicate', left: { kind: 'ref', key: 'this.total' }, op: 'gt', right: { kind: 'literal', value: 200 } },
200
- ],
201
- }
202
- expect(evaluateWhen(node, ctx)).toBe(true)
203
- })
204
-
205
- it('negate wraps group result', () => {
206
- const node: WhenNode = {
207
- kind: 'group',
208
- op: 'AND',
209
- negate: true,
210
- children: [
211
- { kind: 'predicate', left: { kind: 'ref', key: 'this.status' }, op: 'eq', right: { kind: 'literal', value: 'paid' } },
212
- ],
213
- }
214
- expect(evaluateWhen(node, ctx)).toBe(false)
215
- })
216
-
217
- it('nested AND inside OR', () => {
218
- const node: WhenNode = {
219
- kind: 'group',
220
- op: 'AND',
221
- children: [
222
- { kind: 'predicate', left: { kind: 'ref', key: 'this.total' }, op: 'gt', right: { kind: 'literal', value: 100 } },
223
- {
224
- kind: 'group',
225
- op: 'OR',
226
- children: [
227
- { kind: 'predicate', left: { kind: 'ref', key: 'this.tag' }, op: 'eq', right: { kind: 'literal', value: 'gold' } },
228
- { kind: 'predicate', left: { kind: 'ref', key: 'this.total' }, op: 'gt', right: { kind: 'literal', value: 5000 } },
229
- ],
230
- },
231
- ],
232
- }
233
- expect(evaluateWhen(node, ctx)).toBe(true)
234
- })
235
- })
236
-
237
- // ---------------------------------------------------------------------------
238
- // null handling
239
- // ---------------------------------------------------------------------------
240
-
241
- describe('evaluateWhen — null operands', () => {
242
- const emptyCtx = makeContext({})
243
-
244
- it('null == null → eq true', () => {
245
- const node: WhenNode = {
246
- kind: 'predicate',
247
- left: { kind: 'ref', key: 'this.missing' },
248
- op: 'eq',
249
- right: { kind: 'literal', value: null },
250
- }
251
- expect(evaluateWhen(node, emptyCtx)).toBe(true)
252
- })
253
-
254
- it('null isempty → true', () => {
255
- const node: WhenNode = {
256
- kind: 'predicate',
257
- left: { kind: 'ref', key: 'this.missing' },
258
- op: 'isempty',
259
- }
260
- expect(evaluateWhen(node, emptyCtx)).toBe(true)
261
- })
262
-
263
- it('null gt 0 → false (no exception)', () => {
264
- const node: WhenNode = {
265
- kind: 'predicate',
266
- left: { kind: 'ref', key: 'this.missing' },
267
- op: 'gt',
268
- right: { kind: 'literal', value: 0 },
269
- }
270
- expect(() => evaluateWhen(node, emptyCtx)).not.toThrow()
271
- expect(evaluateWhen(node, emptyCtx)).toBe(false)
272
- })
273
- })
274
-
@@ -1,281 +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 type { WhenNode, Seed } from '@beechcms/core'
7
- import { extractPushdownFilters } from '../when-pushdown'
8
-
9
- // ---------------------------------------------------------------------------
10
- // Minimal Seed
11
- // ---------------------------------------------------------------------------
12
-
13
- function makeSeed(aliases: string[] = []): Seed {
14
- return {
15
- slug: 'orders',
16
- label: 'Orders',
17
- displayNameAlias: aliases[0] ?? 'id',
18
- branches: aliases.map((alias, i) => ({ id: `br_0${i + 1}`, alias, label: alias, type: 'text' as const })),
19
- }
20
- }
21
-
22
- const seed = makeSeed(['status', 'total', 'name', 'tag'])
23
-
24
- // ---------------------------------------------------------------------------
25
- // null / empty
26
- // ---------------------------------------------------------------------------
27
-
28
- describe('extractPushdownFilters — null/empty', () => {
29
- it('null returns []', () => {
30
- expect(extractPushdownFilters(null, seed)).toEqual([])
31
- })
32
-
33
- it('empty AND group returns []', () => {
34
- const node: WhenNode = { kind: 'group', op: 'AND', children: [] }
35
- expect(extractPushdownFilters(node, seed)).toEqual([])
36
- })
37
-
38
- it('empty OR group returns []', () => {
39
- const node: WhenNode = { kind: 'group', op: 'OR', children: [] }
40
- expect(extractPushdownFilters(node, seed)).toEqual([])
41
- })
42
- })
43
-
44
- // ---------------------------------------------------------------------------
45
- // Safe single-predicate push-down
46
- // ---------------------------------------------------------------------------
47
-
48
- describe('extractPushdownFilters — safe predicates', () => {
49
- it('root eq predicate is pushed', () => {
50
- const node: WhenNode = {
51
- kind: 'predicate',
52
- left: { kind: 'ref', key: 'this.status' },
53
- op: 'eq',
54
- right: { kind: 'literal', value: 'paid' },
55
- }
56
- const result = extractPushdownFilters(node, seed)
57
- expect(result).toHaveLength(1)
58
- expect(result[0].column).toBe('status')
59
- expect(result[0].conditions[0].op).toBe('eq')
60
- expect(result[0].conditions[0].value).toBe('paid')
61
- })
62
-
63
- it('root neq predicate is pushed', () => {
64
- const node: WhenNode = {
65
- kind: 'predicate',
66
- left: { kind: 'ref', key: 'this.status' },
67
- op: 'neq',
68
- right: { kind: 'literal', value: 'cancelled' },
69
- }
70
- const result = extractPushdownFilters(node, seed)
71
- expect(result).toHaveLength(1)
72
- expect(result[0].conditions[0].op).toBe('neq')
73
- })
74
-
75
- it('root gt predicate is pushed', () => {
76
- const node: WhenNode = {
77
- kind: 'predicate',
78
- left: { kind: 'ref', key: 'this.total' },
79
- op: 'gt',
80
- right: { kind: 'literal', value: 100 },
81
- }
82
- const result = extractPushdownFilters(node, seed)
83
- expect(result).toHaveLength(1)
84
- expect(result[0].conditions[0].op).toBe('gt')
85
- })
86
-
87
- it('root lt predicate is pushed', () => {
88
- const node: WhenNode = {
89
- kind: 'predicate',
90
- left: { kind: 'ref', key: 'this.total' },
91
- op: 'lt',
92
- right: { kind: 'literal', value: 500 },
93
- }
94
- const result = extractPushdownFilters(node, seed)
95
- expect(result).toHaveLength(1)
96
- expect(result[0].conditions[0].op).toBe('lt')
97
- })
98
-
99
- it('root contains predicate is pushed', () => {
100
- const node: WhenNode = {
101
- kind: 'predicate',
102
- left: { kind: 'ref', key: 'this.name' },
103
- op: 'contains',
104
- right: { kind: 'literal', value: 'Alice' },
105
- }
106
- const result = extractPushdownFilters(node, seed)
107
- expect(result).toHaveLength(1)
108
- expect(result[0].conditions[0].op).toBe('contains')
109
- })
110
-
111
- it('root isempty predicate is pushed (no right operand)', () => {
112
- const node: WhenNode = {
113
- kind: 'predicate',
114
- left: { kind: 'ref', key: 'this.name' },
115
- op: 'isempty',
116
- }
117
- const result = extractPushdownFilters(node, seed)
118
- expect(result).toHaveLength(1)
119
- expect(result[0].conditions[0].op).toBe('is_empty')
120
- })
121
-
122
- it('root isnotempty predicate is pushed (no right operand)', () => {
123
- const node: WhenNode = {
124
- kind: 'predicate',
125
- left: { kind: 'ref', key: 'this.name' },
126
- op: 'isnotempty',
127
- }
128
- const result = extractPushdownFilters(node, seed)
129
- expect(result).toHaveLength(1)
130
- expect(result[0].conditions[0].op).toBe('is_not_empty')
131
- })
132
- })
133
-
134
- // ---------------------------------------------------------------------------
135
- // AND group — only direct safe predicate children are pushed
136
- // ---------------------------------------------------------------------------
137
-
138
- describe('extractPushdownFilters — AND group', () => {
139
- it('all safe AND children are pushed', () => {
140
- const node: WhenNode = {
141
- kind: 'group',
142
- op: 'AND',
143
- children: [
144
- { kind: 'predicate', left: { kind: 'ref', key: 'this.status' }, op: 'eq', right: { kind: 'literal', value: 'paid' } },
145
- { kind: 'predicate', left: { kind: 'ref', key: 'this.total' }, op: 'gt', right: { kind: 'literal', value: 100 } },
146
- ],
147
- }
148
- const result = extractPushdownFilters(node, seed)
149
- expect(result).toHaveLength(2)
150
- expect(result.map((r) => r.column)).toContain('status')
151
- expect(result.map((r) => r.column)).toContain('total')
152
- })
153
-
154
- it('mixed AND: unsafe children skipped, safe ones pushed', () => {
155
- const node: WhenNode = {
156
- kind: 'group',
157
- op: 'AND',
158
- children: [
159
- { kind: 'predicate', left: { kind: 'ref', key: 'this.status' }, op: 'eq', right: { kind: 'literal', value: 'paid' } },
160
- // gte is not in PUSHDOWN_OPS → skipped
161
- { kind: 'predicate', left: { kind: 'ref', key: 'this.total' }, op: 'gte', right: { kind: 'literal', value: 100 } },
162
- ],
163
- }
164
- const result = extractPushdownFilters(node, seed)
165
- expect(result).toHaveLength(1)
166
- expect(result[0].column).toBe('status')
167
- })
168
- })
169
-
170
- // ---------------------------------------------------------------------------
171
- // Unsafe operators stay in-memory
172
- // ---------------------------------------------------------------------------
173
-
174
- describe('extractPushdownFilters — unsafe operators stay in-memory', () => {
175
- for (const op of ['gte', 'lte', 'startswith', 'endswith', 'matches', 'in', 'notin'] as const) {
176
- it(`${op} is NOT pushed`, () => {
177
- const node: WhenNode = {
178
- kind: 'predicate',
179
- left: { kind: 'ref', key: 'this.status' },
180
- op,
181
- right: { kind: 'literal', value: 'x' },
182
- }
183
- expect(extractPushdownFilters(node, seed)).toHaveLength(0)
184
- })
185
- }
186
- })
187
-
188
- // ---------------------------------------------------------------------------
189
- // OR branches stay in-memory
190
- // ---------------------------------------------------------------------------
191
-
192
- describe('extractPushdownFilters — OR branches stay in-memory', () => {
193
- it('OR root group returns []', () => {
194
- const node: WhenNode = {
195
- kind: 'group',
196
- op: 'OR',
197
- children: [
198
- { kind: 'predicate', left: { kind: 'ref', key: 'this.status' }, op: 'eq', right: { kind: 'literal', value: 'paid' } },
199
- { kind: 'predicate', left: { kind: 'ref', key: 'this.total' }, op: 'gt', right: { kind: 'literal', value: 100 } },
200
- ],
201
- }
202
- expect(extractPushdownFilters(node, seed)).toHaveLength(0)
203
- })
204
-
205
- it('AND group with nested OR child: only direct predicate siblings are pushed', () => {
206
- const node: WhenNode = {
207
- kind: 'group',
208
- op: 'AND',
209
- children: [
210
- { kind: 'predicate', left: { kind: 'ref', key: 'this.status' }, op: 'eq', right: { kind: 'literal', value: 'paid' } },
211
- {
212
- kind: 'group',
213
- op: 'OR',
214
- children: [
215
- { kind: 'predicate', left: { kind: 'ref', key: 'this.total' }, op: 'gt', right: { kind: 'literal', value: 100 } },
216
- { kind: 'predicate', left: { kind: 'ref', key: 'this.tag' }, op: 'eq', right: { kind: 'literal', value: 'gold' } },
217
- ],
218
- },
219
- ],
220
- }
221
- const result = extractPushdownFilters(node, seed)
222
- // Only the direct 'status' predicate is pushed; the nested OR group is skipped
223
- expect(result).toHaveLength(1)
224
- expect(result[0].column).toBe('status')
225
- })
226
- })
227
-
228
- // ---------------------------------------------------------------------------
229
- // Negated group stays in-memory
230
- // ---------------------------------------------------------------------------
231
-
232
- describe('extractPushdownFilters — negated AND group stays in-memory', () => {
233
- it('negated AND root returns []', () => {
234
- const node: WhenNode = {
235
- kind: 'group',
236
- op: 'AND',
237
- negate: true,
238
- children: [
239
- { kind: 'predicate', left: { kind: 'ref', key: 'this.status' }, op: 'eq', right: { kind: 'literal', value: 'paid' } },
240
- ],
241
- }
242
- expect(extractPushdownFilters(node, seed)).toHaveLength(0)
243
- })
244
- })
245
-
246
- // ---------------------------------------------------------------------------
247
- // Cross-seed refs stay in-memory
248
- // ---------------------------------------------------------------------------
249
-
250
- describe('extractPushdownFilters — cross-seed refs stay in-memory', () => {
251
- it('ref not starting with this. is NOT pushed', () => {
252
- const node: WhenNode = {
253
- kind: 'predicate',
254
- left: { kind: 'ref', key: 'customers:lastone:status' },
255
- op: 'eq',
256
- right: { kind: 'literal', value: 'active' },
257
- }
258
- expect(extractPushdownFilters(node, seed)).toHaveLength(0)
259
- })
260
-
261
- it('right operand is a ref (not literal) → NOT pushed', () => {
262
- const node: WhenNode = {
263
- kind: 'predicate',
264
- left: { kind: 'ref', key: 'this.status' },
265
- op: 'eq',
266
- right: { kind: 'ref', key: 'customers:lastone:status' },
267
- }
268
- expect(extractPushdownFilters(node, seed)).toHaveLength(0)
269
- })
270
-
271
- it('literal left operand is NOT pushed', () => {
272
- const node: WhenNode = {
273
- kind: 'predicate',
274
- left: { kind: 'literal', value: 'paid' },
275
- op: 'eq',
276
- right: { kind: 'literal', value: 'paid' },
277
- }
278
- expect(extractPushdownFilters(node, seed)).toHaveLength(0)
279
- })
280
- })
281
-