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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (373) hide show
  1. package/README.md +4 -4
  2. package/assets/dashboard/BeechLogo.svg +18 -18
  3. package/assets/dashboard/BeechLogoLIght.svg +48 -48
  4. package/assets/dashboard/assets/chart-Cws4Yj6K.js +39 -0
  5. package/assets/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  6. package/assets/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  7. package/assets/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  8. package/assets/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  9. package/assets/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  10. package/assets/dashboard/assets/index-BjPpppHe.css +1 -0
  11. package/assets/dashboard/assets/index-CrW-zhkA.js +642 -0
  12. package/assets/dashboard/assets/lora-cyrillic-ext-wght-normal-YcVxMP-u.woff2 +0 -0
  13. package/assets/dashboard/assets/lora-cyrillic-wght-normal-UWPuZLjM.woff2 +0 -0
  14. package/assets/dashboard/assets/lora-latin-ext-wght-normal-C2Wlntb9.woff2 +0 -0
  15. package/assets/dashboard/assets/lora-latin-wght-normal-BiLcIKcI.woff2 +0 -0
  16. package/assets/dashboard/assets/lora-math-wght-normal-sOy7rEfW.woff2 +0 -0
  17. package/assets/dashboard/assets/lora-symbols-wght-normal-DQ5VrUkH.woff2 +0 -0
  18. package/assets/dashboard/assets/lora-vietnamese-wght-normal-CfJ7gtf3.woff2 +0 -0
  19. package/assets/dashboard/assets/pie-chart-recharts-CAxoDQp2.js +1 -0
  20. package/assets/dashboard/assets/timeseries-chart-recharts-CixDW-LM.js +1 -0
  21. package/assets/dashboard/beechLogoDark.svg +48 -48
  22. package/assets/dashboard/index.html +18 -18
  23. package/assets/dashboard/noResult.svg +42 -42
  24. package/assets/dashboard/sol.svg +3 -3
  25. package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
  26. package/dist/auth/__fixtures__/in-memory-hash-provider.d.ts +5 -0
  27. package/dist/auth/__fixtures__/static-token-service.d.ts +6 -0
  28. package/dist/auth/bcrypt-hash-provider.d.ts +8 -0
  29. package/dist/auth/constants.d.ts +10 -0
  30. package/dist/auth/jose-token-service.d.ts +14 -0
  31. package/dist/auth/login.d.ts +32 -0
  32. package/dist/auth/refresh.d.ts +1 -0
  33. package/dist/factory.d.ts +39 -0
  34. package/dist/features/automations/action-executors/create-entry.executor.d.ts +7 -0
  35. package/dist/features/automations/action-executors/edit-field.executor.d.ts +7 -0
  36. package/dist/features/automations/action-executors/index.d.ts +13 -0
  37. package/dist/features/automations/action-executors/send-mail.executor.d.ts +7 -0
  38. package/dist/features/automations/action-executors/set-variable.executor.d.ts +15 -0
  39. package/dist/features/automations/action-executors/webhook.executor.d.ts +7 -0
  40. package/dist/features/automations/automation-runner.d.ts +13 -0
  41. package/dist/features/automations/automation-runner.utils.d.ts +3 -0
  42. package/dist/features/automations/automations.handler.d.ts +7 -0
  43. package/dist/features/automations/automations.schema.d.ts +191 -0
  44. package/dist/features/automations/context-resolver.d.ts +8 -0
  45. package/dist/features/automations/cron-runner.d.ts +9 -0
  46. package/dist/features/automations/cron-runner.utils.d.ts +1 -0
  47. package/dist/features/automations/filter-translation.d.ts +9 -0
  48. package/dist/features/automations/index.d.ts +7 -0
  49. package/dist/features/automations/template-grammar.d.ts +73 -0
  50. package/dist/features/automations/var-access-resolver.d.ts +6 -0
  51. package/dist/features/automations/when-evaluator.d.ts +3 -0
  52. package/dist/features/automations/when-pushdown.d.ts +2 -0
  53. package/dist/features/backrefs/backrefs.handler.d.ts +3 -0
  54. package/dist/features/backrefs/d1-backref.repository.d.ts +17 -0
  55. package/dist/features/backrefs/index.d.ts +1 -0
  56. package/dist/features/content/constants.d.ts +12 -0
  57. package/dist/features/content/handlers/bulk.handler.d.ts +3 -0
  58. package/dist/features/content/handlers/create.d.ts +3 -0
  59. package/dist/features/content/handlers/delete.d.ts +3 -0
  60. package/dist/features/content/handlers/facets.d.ts +3 -0
  61. package/dist/features/content/handlers/get.d.ts +4 -0
  62. package/dist/features/content/handlers/helpers.d.ts +12 -0
  63. package/dist/features/content/handlers/list.d.ts +3 -0
  64. package/dist/features/content/handlers/update.d.ts +3 -0
  65. package/dist/features/content/index.d.ts +4 -0
  66. package/dist/features/dashboard-layout/dashboard-layout.handler.d.ts +7 -0
  67. package/dist/features/dashboard-layout/index.d.ts +1 -0
  68. package/dist/features/draft/draft.handler.d.ts +4 -0
  69. package/dist/features/draft/draft.middleware.d.ts +9 -0
  70. package/dist/features/draft/index.d.ts +1 -0
  71. package/{src/features/email/email.provider.ts → dist/features/email/email.provider.d.ts} +37 -42
  72. package/dist/features/email/email.service.d.ts +13 -0
  73. package/dist/features/email/email.types.d.ts +94 -0
  74. package/{src/features/email/index.ts → dist/features/email/index.d.ts} +20 -33
  75. package/dist/features/email/providers/resend.d.ts +36 -0
  76. package/dist/features/email/providers/smtp.d.ts +11 -0
  77. package/dist/features/email/templates/automation-mail.d.ts +8 -0
  78. package/dist/features/email/templates/password-changed.d.ts +15 -0
  79. package/dist/features/email/templates/password-reset.d.ts +18 -0
  80. package/dist/features/email/templates/shell.d.ts +58 -0
  81. package/dist/features/notifications/index.d.ts +1 -0
  82. package/dist/features/notifications/notifications.handler.d.ts +15 -0
  83. package/dist/features/password-reset/index.d.ts +6 -0
  84. package/dist/features/password-reset/request.d.ts +10 -0
  85. package/dist/features/password-reset/reset.d.ts +9 -0
  86. package/dist/features/rotate-field/index.d.ts +1 -0
  87. package/dist/features/rotate-field/rotate-field.handler.d.ts +7 -0
  88. package/dist/features/rotate-field/rotate-field.schema.d.ts +11 -0
  89. package/dist/features/schema/schema.handler.d.ts +7 -0
  90. package/dist/features/seeds/index.d.ts +1 -0
  91. package/dist/features/seeds/seeds.handler.d.ts +6 -0
  92. package/dist/features/settings/settings.handler.d.ts +7 -0
  93. package/dist/features/setup/index.d.ts +7 -0
  94. package/dist/features/stats/index.d.ts +1 -0
  95. package/dist/features/stats/stats.handler.d.ts +7 -0
  96. package/dist/features/webhooks/index.d.ts +3 -0
  97. package/dist/index.d.ts +6 -0
  98. package/dist/index.js +9666 -0
  99. package/dist/media-utils.d.ts +25 -0
  100. package/dist/middleware/auth-providers.middleware.d.ts +8 -0
  101. package/dist/middleware/observability.middleware.d.ts +20 -0
  102. package/dist/middleware/rate-limit.middleware.d.ts +9 -0
  103. package/dist/middleware/repository.middleware.d.ts +34 -0
  104. package/dist/middleware/seed-registry.middleware.d.ts +10 -0
  105. package/dist/middleware/storage.middleware.d.ts +7 -0
  106. package/dist/middleware.d.ts +11 -0
  107. package/dist/public/access-policy.d.ts +12 -0
  108. package/dist/public/api-key-middleware.d.ts +6 -0
  109. package/dist/public/cache-utils.d.ts +10 -0
  110. package/dist/public/entry-projection.d.ts +2 -0
  111. package/dist/public/idempotency.d.ts +9 -0
  112. package/dist/public/index.d.ts +11 -0
  113. package/dist/public/problem-details.d.ts +31 -0
  114. package/dist/public/public-add.d.ts +3 -0
  115. package/dist/public/public-edit.d.ts +5 -0
  116. package/dist/public/public-errors.d.ts +13 -0
  117. package/dist/public/public-read.d.ts +3 -0
  118. package/dist/public/public-routes.d.ts +3 -0
  119. package/dist/public/query-builder.d.ts +28 -0
  120. package/dist/public/rate-limit-middleware.d.ts +3 -0
  121. package/dist/public/read-list.d.ts +26 -0
  122. package/dist/public/read-single.d.ts +22 -0
  123. package/dist/public/response-builder.d.ts +22 -0
  124. package/dist/public/sanitize.d.ts +23 -0
  125. package/dist/public/slug-utils.d.ts +11 -0
  126. package/dist/rate-limit/cloudflare-rate-limiter.d.ts +6 -0
  127. package/dist/rate-limit/in-memory-rate-limiter.d.ts +7 -0
  128. package/dist/rate-limit/no-op-rate-limiter.d.ts +4 -0
  129. package/dist/search-utils.d.ts +54 -0
  130. package/dist/search.d.ts +6 -0
  131. package/dist/shared/apply-policies.d.ts +10 -0
  132. package/dist/shared/automations.repository.d1.d.ts +12 -0
  133. package/dist/shared/background-notification-service.d.ts +18 -0
  134. package/dist/shared/base.repository.d1.d.ts +18 -0
  135. package/dist/shared/content-utils.d.ts +21 -0
  136. package/dist/shared/content.repository.d1.d.ts +68 -0
  137. package/dist/shared/d1-activity-log.repository.d.ts +13 -0
  138. package/dist/shared/d1-activity-logger.d.ts +20 -0
  139. package/dist/shared/d1-analytics.repository.d.ts +20 -0
  140. package/dist/shared/d1-content-scan.repository.d.ts +7 -0
  141. package/dist/shared/d1-notification.repository.d.ts +17 -0
  142. package/dist/shared/d1-password-reset-token.repository.d.ts +10 -0
  143. package/dist/shared/d1-search.repository.d.ts +14 -0
  144. package/dist/shared/d1-session.repository.d.ts +12 -0
  145. package/dist/shared/d1-setup-checklist.repository.d.ts +12 -0
  146. package/dist/shared/d1-user.repository.d.ts +18 -0
  147. package/dist/shared/d1-widget.repository.d.ts +31 -0
  148. package/dist/shared/dashboard-layout.repository.d1.d.ts +9 -0
  149. package/dist/shared/demo-data-sql.d.ts +1 -0
  150. package/dist/shared/demo-data.repository.d1.d.ts +6 -0
  151. package/dist/shared/execution-context-scheduler.d.ts +6 -0
  152. package/dist/shared/fixed-clock.d.ts +13 -0
  153. package/dist/shared/fts-sync.d.ts +1 -0
  154. package/dist/shared/idempotency.repository.d1.d.ts +16 -0
  155. package/dist/shared/in-memory-activity-logger.d.ts +11 -0
  156. package/dist/shared/in-memory-notification-service.d.ts +11 -0
  157. package/dist/shared/in-memory-seed.repository.d.ts +18 -0
  158. package/dist/shared/media.repository.d1.d.ts +35 -0
  159. package/dist/shared/qstash-notification-service.d.ts +18 -0
  160. package/dist/shared/query-utils.d.ts +36 -0
  161. package/dist/shared/request-utils.d.ts +13 -0
  162. package/dist/shared/schema-mutator.d1.d.ts +12 -0
  163. package/dist/shared/seed-layout.repository.d1.d.ts +9 -0
  164. package/dist/shared/seed-registry-cache.d.ts +12 -0
  165. package/dist/shared/seed.repository.d1.d.ts +13 -0
  166. package/dist/shared/sequential-id-generator.d.ts +13 -0
  167. package/dist/shared/site-settings.repository.d1.d.ts +7 -0
  168. package/dist/shared/storage/factory.d.ts +8 -0
  169. package/dist/shared/storage/s3-bucket.d.ts +43 -0
  170. package/dist/shared/storage-utils.d.ts +7 -0
  171. package/dist/shared/system-stats.repository.d1.d.ts +25 -0
  172. package/dist/types.d.ts +85 -0
  173. package/dist/upload.d.ts +16 -0
  174. package/dist/widget.d.ts +7 -0
  175. package/migrations/0000_v040_base.sql +265 -265
  176. package/migrations/0029_automations.sql +14 -14
  177. package/migrations/0030_test_seeds.sql +125 -125
  178. package/package.json +27 -18
  179. package/assets/dashboard/assets/index-BSjk0Sx9.js +0 -634
  180. package/assets/dashboard/assets/index-kRCYVN2B.css +0 -1
  181. package/src/auth/__fixtures__/in-memory-hash-provider.ts +0 -17
  182. package/src/auth/__fixtures__/static-token-service.ts +0 -22
  183. package/src/auth/bcrypt-hash-provider.ts +0 -24
  184. package/src/auth/constants.ts +0 -14
  185. package/src/auth/generate-refresh-token.test.ts +0 -23
  186. package/src/auth/hash-provider.test.ts +0 -55
  187. package/src/auth/jose-token-service.ts +0 -62
  188. package/src/auth/jwt-claims-passthrough.test.ts +0 -87
  189. package/src/auth/login.test.ts +0 -100
  190. package/src/auth/login.ts +0 -78
  191. package/src/auth/refresh.ts +0 -11
  192. package/src/auth/token-service.test.ts +0 -90
  193. package/src/factory.ts +0 -368
  194. package/src/features/automations/__tests__/action-executors.test.ts +0 -255
  195. package/src/features/automations/__tests__/automation-runner.test.ts +0 -196
  196. package/src/features/automations/__tests__/automation-runner.utils.test.ts +0 -60
  197. package/src/features/automations/__tests__/automations.handler.test.ts +0 -264
  198. package/src/features/automations/__tests__/automations.repository.test.ts +0 -163
  199. package/src/features/automations/__tests__/automations.schema.test.ts +0 -224
  200. package/src/features/automations/__tests__/context-resolver.test.ts +0 -126
  201. package/src/features/automations/__tests__/cron-runner.test.ts +0 -267
  202. package/src/features/automations/__tests__/cron-runner.utils.test.ts +0 -144
  203. package/src/features/automations/__tests__/set-variable.executor.test.ts +0 -383
  204. package/src/features/automations/__tests__/template-grammar.test.ts +0 -308
  205. package/src/features/automations/__tests__/webhook.executor.test.ts +0 -142
  206. package/src/features/automations/__tests__/when-evaluator.test.ts +0 -274
  207. package/src/features/automations/__tests__/when-pushdown.test.ts +0 -281
  208. package/src/features/automations/action-executors/create-entry.executor.ts +0 -27
  209. package/src/features/automations/action-executors/edit-field.executor.ts +0 -26
  210. package/src/features/automations/action-executors/index.ts +0 -37
  211. package/src/features/automations/action-executors/send-mail.executor.ts +0 -53
  212. package/src/features/automations/action-executors/set-variable.executor.ts +0 -159
  213. package/src/features/automations/action-executors/webhook.executor.ts +0 -67
  214. package/src/features/automations/automation-runner.ts +0 -85
  215. package/src/features/automations/automation-runner.utils.ts +0 -47
  216. package/src/features/automations/automations.handler.ts +0 -197
  217. package/src/features/automations/automations.schema.ts +0 -177
  218. package/src/features/automations/context-resolver.ts +0 -152
  219. package/src/features/automations/cron-runner.ts +0 -140
  220. package/src/features/automations/cron-runner.utils.ts +0 -44
  221. package/src/features/automations/filter-translation.ts +0 -46
  222. package/src/features/automations/index.ts +0 -16
  223. package/src/features/automations/template-grammar.ts +0 -245
  224. package/src/features/automations/var-access-resolver.ts +0 -140
  225. package/src/features/automations/when-evaluator.ts +0 -87
  226. package/src/features/automations/when-pushdown.ts +0 -57
  227. package/src/features/backrefs/__tests__/backrefs.handler.test.ts +0 -359
  228. package/src/features/backrefs/backrefs.handler.ts +0 -168
  229. package/src/features/backrefs/d1-backref.repository.ts +0 -84
  230. package/src/features/backrefs/index.ts +0 -5
  231. package/src/features/content/constants.ts +0 -16
  232. package/src/features/content/handlers/bulk.handler.ts +0 -185
  233. package/src/features/content/handlers/create.ts +0 -129
  234. package/src/features/content/handlers/delete.ts +0 -62
  235. package/src/features/content/handlers/facets.ts +0 -49
  236. package/src/features/content/handlers/get.ts +0 -120
  237. package/src/features/content/handlers/helpers.test.ts +0 -180
  238. package/src/features/content/handlers/helpers.ts +0 -93
  239. package/src/features/content/handlers/list.ts +0 -174
  240. package/src/features/content/handlers/update.ts +0 -174
  241. package/src/features/content/index.ts +0 -26
  242. package/src/features/draft/draft.handler.ts +0 -195
  243. package/src/features/draft/draft.middleware.ts +0 -66
  244. package/src/features/draft/index.ts +0 -5
  245. package/src/features/email/email.service.ts +0 -98
  246. package/src/features/email/email.types.ts +0 -119
  247. package/src/features/email/providers/resend.ts +0 -67
  248. package/src/features/email/providers/smtp.ts +0 -55
  249. package/src/features/email/templates/automation-mail.ts +0 -19
  250. package/src/features/email/templates/password-changed.ts +0 -63
  251. package/src/features/email/templates/password-reset.ts +0 -68
  252. package/src/features/email/templates/shell.ts +0 -96
  253. package/src/features/notifications/index.ts +0 -5
  254. package/src/features/notifications/notifications.handler.ts +0 -105
  255. package/src/features/password-reset/index.ts +0 -19
  256. package/src/features/password-reset/request.ts +0 -92
  257. package/src/features/password-reset/reset.ts +0 -102
  258. package/src/features/rotate-field/index.ts +0 -5
  259. package/src/features/rotate-field/rotate-field.handler.ts +0 -132
  260. package/src/features/rotate-field/rotate-field.schema.ts +0 -17
  261. package/src/features/schema/schema.handler.ts +0 -131
  262. package/src/features/seeds/index.ts +0 -5
  263. package/src/features/seeds/seeds.handler.test.ts +0 -632
  264. package/src/features/seeds/seeds.handler.ts +0 -693
  265. package/src/features/settings/__tests__/settings.handler.test.ts +0 -555
  266. package/src/features/settings/settings.handler.ts +0 -392
  267. package/src/features/setup/index.ts +0 -204
  268. package/src/features/stats/index.ts +0 -5
  269. package/src/features/stats/stats.handler.ts +0 -420
  270. package/src/features/webhooks/index.ts +0 -63
  271. package/src/index.ts +0 -65
  272. package/src/media-utils.ts +0 -82
  273. package/src/middleware/auth-providers.middleware.ts +0 -45
  274. package/src/middleware/observability.middleware.ts +0 -70
  275. package/src/middleware/rate-limit.middleware.ts +0 -45
  276. package/src/middleware/repository.middleware.ts +0 -114
  277. package/src/middleware/seed-registry.middleware.test.ts +0 -97
  278. package/src/middleware/seed-registry.middleware.ts +0 -21
  279. package/src/middleware/storage.middleware.ts +0 -25
  280. package/src/middleware.ts +0 -45
  281. package/src/public/access-policy.ts +0 -27
  282. package/src/public/api-key-middleware.ts +0 -57
  283. package/src/public/cache-utils.ts +0 -38
  284. package/src/public/entry-projection.ts +0 -46
  285. package/src/public/idempotency.ts +0 -23
  286. package/src/public/index.ts +0 -16
  287. package/src/public/problem-details.ts +0 -124
  288. package/src/public/public-add.ts +0 -114
  289. package/src/public/public-edit.ts +0 -163
  290. package/src/public/public-errors.ts +0 -19
  291. package/src/public/public-read.ts +0 -63
  292. package/src/public/public-routes.ts +0 -88
  293. package/src/public/query-builder.test.ts +0 -224
  294. package/src/public/query-builder.ts +0 -156
  295. package/src/public/rate-limit-middleware.ts +0 -34
  296. package/src/public/read-list.ts +0 -54
  297. package/src/public/read-single.ts +0 -48
  298. package/src/public/response-builder.ts +0 -30
  299. package/src/public/sanitize.ts +0 -69
  300. package/src/public/slug-utils.ts +0 -18
  301. package/src/rate-limit/cloudflare-rate-limiter.test.ts +0 -30
  302. package/src/rate-limit/cloudflare-rate-limiter.ts +0 -15
  303. package/src/rate-limit/in-memory-rate-limiter.test.ts +0 -37
  304. package/src/rate-limit/in-memory-rate-limiter.ts +0 -17
  305. package/src/rate-limit/no-op-rate-limiter.test.ts +0 -22
  306. package/src/rate-limit/no-op-rate-limiter.ts +0 -11
  307. package/src/search-utils.test.ts +0 -211
  308. package/src/search-utils.ts +0 -213
  309. package/src/search.ts +0 -65
  310. package/src/shared/apply-policies.test.ts +0 -81
  311. package/src/shared/apply-policies.ts +0 -67
  312. package/src/shared/automations.repository.d1.ts +0 -150
  313. package/src/shared/background-notification-service.test.ts +0 -62
  314. package/src/shared/background-notification-service.ts +0 -52
  315. package/src/shared/base.repository.d1.ts +0 -32
  316. package/src/shared/content-utils.test.ts +0 -165
  317. package/src/shared/content-utils.ts +0 -86
  318. package/src/shared/content.repository.d1.test.ts +0 -458
  319. package/src/shared/content.repository.d1.ts +0 -851
  320. package/src/shared/d1-activity-log.repository.test.ts +0 -140
  321. package/src/shared/d1-activity-log.repository.ts +0 -105
  322. package/src/shared/d1-activity-logger.test.ts +0 -86
  323. package/src/shared/d1-activity-logger.ts +0 -67
  324. package/src/shared/d1-analytics.repository.test.ts +0 -78
  325. package/src/shared/d1-analytics.repository.ts +0 -85
  326. package/src/shared/d1-content-scan.repository.test.ts +0 -80
  327. package/src/shared/d1-content-scan.repository.ts +0 -37
  328. package/src/shared/d1-notification.repository.test.ts +0 -128
  329. package/src/shared/d1-notification.repository.ts +0 -118
  330. package/src/shared/d1-password-reset-token.repository.test.ts +0 -81
  331. package/src/shared/d1-password-reset-token.repository.ts +0 -56
  332. package/src/shared/d1-search.repository.test.ts +0 -87
  333. package/src/shared/d1-search.repository.ts +0 -88
  334. package/src/shared/d1-session.repository.test.ts +0 -125
  335. package/src/shared/d1-session.repository.ts +0 -102
  336. package/src/shared/d1-setup-checklist.repository.ts +0 -36
  337. package/src/shared/d1-user.repository.test.ts +0 -151
  338. package/src/shared/d1-user.repository.ts +0 -119
  339. package/src/shared/d1-widget.repository.test.ts +0 -221
  340. package/src/shared/d1-widget.repository.ts +0 -341
  341. package/src/shared/demo-data-sql.ts +0 -54
  342. package/src/shared/demo-data.repository.d1.ts +0 -20
  343. package/src/shared/execution-context-scheduler.ts +0 -13
  344. package/src/shared/fixed-clock.ts +0 -25
  345. package/src/shared/fts-sync.ts +0 -8
  346. package/src/shared/idempotency.repository.d1.test.ts +0 -83
  347. package/src/shared/idempotency.repository.d1.ts +0 -60
  348. package/src/shared/in-memory-activity-logger.ts +0 -19
  349. package/src/shared/in-memory-notification-service.ts +0 -19
  350. package/src/shared/in-memory-seed.repository.ts +0 -51
  351. package/src/shared/media.repository.d1.test.ts +0 -107
  352. package/src/shared/media.repository.d1.ts +0 -68
  353. package/src/shared/qstash-notification-service.test.ts +0 -67
  354. package/src/shared/qstash-notification-service.ts +0 -55
  355. package/src/shared/query-utils.ts +0 -141
  356. package/src/shared/request-utils.ts +0 -26
  357. package/src/shared/schema-mutator.d1.ts +0 -64
  358. package/src/shared/seed-layout.repository.d1.test.ts +0 -114
  359. package/src/shared/seed-layout.repository.d1.ts +0 -62
  360. package/src/shared/seed-registry-cache.test.ts +0 -68
  361. package/src/shared/seed-registry-cache.ts +0 -49
  362. package/src/shared/seed.repository.d1.test.ts +0 -143
  363. package/src/shared/seed.repository.d1.ts +0 -98
  364. package/src/shared/sequential-id-generator.ts +0 -33
  365. package/src/shared/site-settings.repository.d1.ts +0 -53
  366. package/src/shared/storage/factory.ts +0 -41
  367. package/src/shared/storage/s3-bucket.ts +0 -195
  368. package/src/shared/storage-utils.ts +0 -40
  369. package/src/shared/system-stats.repository.d1.test.ts +0 -58
  370. package/src/shared/system-stats.repository.d1.ts +0 -48
  371. package/src/types.ts +0 -87
  372. package/src/upload.ts +0 -198
  373. package/src/widget.ts +0 -212
@@ -1,37 +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 { InMemoryRateLimiter } from './in-memory-rate-limiter'
7
-
8
- describe('InMemoryRateLimiter', () => {
9
- it('allows requests up to the configured maximum hit count', async () => {
10
- const limiter = new InMemoryRateLimiter(3)
11
- expect((await limiter.checkLimit('key')).isAllowed).toBe(true)
12
- expect((await limiter.checkLimit('key')).isAllowed).toBe(true)
13
- expect((await limiter.checkLimit('key')).isAllowed).toBe(true)
14
- })
15
-
16
- it('blocks the very next request after maxAllowedHits is reached', async () => {
17
- const limiter = new InMemoryRateLimiter(2)
18
- await limiter.checkLimit('key')
19
- await limiter.checkLimit('key')
20
- expect((await limiter.checkLimit('key')).isAllowed).toBe(false)
21
- })
22
-
23
- it('tracks hit counts independently for each key', async () => {
24
- const limiter = new InMemoryRateLimiter(1)
25
- expect((await limiter.checkLimit('ip-a')).isAllowed).toBe(true)
26
- expect((await limiter.checkLimit('ip-b')).isAllowed).toBe(true)
27
- // Both keys exhausted independently
28
- expect((await limiter.checkLimit('ip-a')).isAllowed).toBe(false)
29
- expect((await limiter.checkLimit('ip-b')).isAllowed).toBe(false)
30
- })
31
-
32
- it('maxAllowedHits = 1 allows exactly one request before blocking', async () => {
33
- const limiter = new InMemoryRateLimiter(1)
34
- expect((await limiter.checkLimit('k')).isAllowed).toBe(true)
35
- expect((await limiter.checkLimit('k')).isAllowed).toBe(false)
36
- })
37
- })
@@ -1,17 +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 { IRateLimiter, RateLimitResult } from '@beechcms/core'
6
-
7
- export class InMemoryRateLimiter implements IRateLimiter {
8
- private readonly hitCounts = new Map<string, number>()
9
-
10
- constructor(private readonly maxAllowedHits: number) {}
11
-
12
- async checkLimit(key: string): Promise<RateLimitResult> {
13
- const currentHits = (this.hitCounts.get(key) ?? 0) + 1
14
- this.hitCounts.set(key, currentHits)
15
- return { isAllowed: currentHits <= this.maxAllowedHits }
16
- }
17
- }
@@ -1,22 +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 { NoOpRateLimiter } from './no-op-rate-limiter'
7
-
8
- describe('NoOpRateLimiter', () => {
9
- it('always returns isAllowed: true regardless of the key', async () => {
10
- const limiter = new NoOpRateLimiter()
11
- for (let i = 0; i < 100; i++) {
12
- expect((await limiter.checkLimit('any-key')).isAllowed).toBe(true)
13
- }
14
- })
15
-
16
- it('allows all keys without accumulating state', async () => {
17
- const limiter = new NoOpRateLimiter()
18
- expect((await limiter.checkLimit('ip-a')).isAllowed).toBe(true)
19
- expect((await limiter.checkLimit('ip-b')).isAllowed).toBe(true)
20
- expect((await limiter.checkLimit('ip-a')).isAllowed).toBe(true)
21
- })
22
- })
@@ -1,11 +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 { IRateLimiter, RateLimitResult } from '@beechcms/core'
6
-
7
- export class NoOpRateLimiter implements IRateLimiter {
8
- async checkLimit(_key: string): Promise<RateLimitResult> {
9
- return { isAllowed: true }
10
- }
11
- }
@@ -1,211 +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 { encodeCursor, decodeCursor, buildFtsQuery, mapFtsRow } from './search-utils'
7
- import type { Seed } from '@beechcms/core'
8
-
9
- const TEXT_SEED = {
10
- slug: 'articoli',
11
- displayNameAlias: 'title',
12
- branches: [
13
- { id: 'br_01', alias: 'title', type: 'text', policies: { search: true } },
14
- ],
15
- } as unknown as Seed
16
-
17
- const SECOND_SEED = {
18
- slug: 'team',
19
- displayNameAlias: 'name',
20
- branches: [
21
- { id: 'br_01', alias: 'name', type: 'text', policies: { search: true } },
22
- ],
23
- } as unknown as Seed
24
-
25
- const NO_FTS_SEED = {
26
- slug: 'prodotti',
27
- displayNameAlias: 'nome',
28
- branches: [
29
- { id: 'br_01', alias: 'price', type: 'number' },
30
- ],
31
- } as unknown as Seed
32
-
33
- // ─── encodeCursor / decodeCursor ─────────────────────────────────────────────
34
-
35
- describe('encodeCursor / decodeCursor', () => {
36
- it('roundtrip preserves rank and entryId', () => {
37
- const cursor = encodeCursor(-1.23456, 'entry-abc')
38
- const decoded = decodeCursor(cursor)
39
- expect(decoded?.rank).toBeCloseTo(-1.23456)
40
- expect(decoded?.entryId).toBe('entry-abc')
41
- })
42
-
43
- it('entryId without colons roundtrips correctly (UUID-style IDs)', () => {
44
- const entryId = 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
45
- const cursor = encodeCursor(-3.7, entryId)
46
- const decoded = decodeCursor(cursor)
47
- expect(decoded?.rank).toBeCloseTo(-3.7)
48
- expect(decoded?.entryId).toBe(entryId)
49
- })
50
-
51
- it('returns null for invalid base64', () => {
52
- expect(decodeCursor('!!!not-base64!!!')).toBeNull()
53
- })
54
-
55
- it('returns null when decoded string has no colon separator', () => {
56
- expect(decodeCursor(btoa('noseparator'))).toBeNull()
57
- })
58
- })
59
-
60
- // ─── buildFtsQuery ───────────────────────────────────────────────────────────
61
-
62
- describe('buildFtsQuery', () => {
63
- it('returns empty-result query when no seed has a searchable FTS branch', () => {
64
- const result = buildFtsQuery(
65
- { q: 'hello', schemaSlug: null, status: null, limit: 20, cursor: null },
66
- [NO_FTS_SEED],
67
- )
68
- expect(result.sql).toContain('WHERE 1=0')
69
- expect(result.binds).toHaveLength(0)
70
- expect(result.countSql).toContain('SELECT 0 as total')
71
- })
72
-
73
- it('throws EMPTY_QUERY when all terms are stripped or too short (single chars)', () => {
74
- expect(() =>
75
- buildFtsQuery({ q: 'a b', schemaSlug: null, status: null, limit: 20, cursor: null }, [TEXT_SEED]),
76
- ).toThrow('EMPTY_QUERY')
77
- })
78
-
79
- it('generates a query referencing the seed FTS and content tables', () => {
80
- const result = buildFtsQuery(
81
- { q: 'hello', schemaSlug: null, status: null, limit: 20, cursor: null },
82
- [TEXT_SEED],
83
- )
84
- expect(result.sql).toContain('fts_articoli')
85
- expect(result.sql).toContain('content_articoli')
86
- expect(result.sql).toContain('LIMIT ?')
87
- expect(result.binds.at(-1)).toBe(21) // limit + 1 for has-more detection
88
- })
89
-
90
- it('UNION ALLs multiple seeds when no schemaSlug filter is set', () => {
91
- const result = buildFtsQuery(
92
- { q: 'test', schemaSlug: null, status: null, limit: 10, cursor: null },
93
- [TEXT_SEED, SECOND_SEED],
94
- )
95
- expect(result.sql).toContain('UNION ALL')
96
- expect(result.sql).toContain('fts_articoli')
97
- expect(result.sql).toContain('fts_team')
98
- })
99
-
100
- it('limits query to the requested schemaSlug when provided', () => {
101
- const result = buildFtsQuery(
102
- { q: 'test', schemaSlug: 'articoli', status: null, limit: 20, cursor: null },
103
- [TEXT_SEED, SECOND_SEED],
104
- )
105
- expect(result.sql).toContain('fts_articoli')
106
- expect(result.sql).not.toContain('fts_team')
107
- })
108
-
109
- it('adds status filter to WHERE clause and bind values', () => {
110
- const result = buildFtsQuery(
111
- { q: 'hello', schemaSlug: null, status: 'published', limit: 20, cursor: null },
112
- [TEXT_SEED],
113
- )
114
- expect(result.sql).toContain('ce.status = ?')
115
- expect(result.binds).toContain('published')
116
- })
117
-
118
- it('adds cursor-based pagination condition when cursor is valid', () => {
119
- const cursor = encodeCursor(-1.5, 'entry-123')
120
- const result = buildFtsQuery(
121
- { q: 'hello', schemaSlug: null, status: null, limit: 20, cursor },
122
- [TEXT_SEED],
123
- )
124
- expect(result.sql).toContain('bm25')
125
- expect(result.binds).toContain('entry-123')
126
- })
127
-
128
- it('ignores an invalid cursor and produces no pagination condition', () => {
129
- const result = buildFtsQuery(
130
- { q: 'hello', schemaSlug: null, status: null, limit: 20, cursor: 'bad-cursor' },
131
- [TEXT_SEED],
132
- )
133
- expect(result.binds).not.toContain('entry-123')
134
- })
135
-
136
- it('countSql wraps each seed count in a SUM', () => {
137
- const result = buildFtsQuery(
138
- { q: 'test', schemaSlug: null, status: null, limit: 10, cursor: null },
139
- [TEXT_SEED, SECOND_SEED],
140
- )
141
- expect(result.countSql).toContain('SUM')
142
- expect(result.countSql).toContain('fts_articoli')
143
- expect(result.countSql).toContain('fts_team')
144
- })
145
-
146
- it('count binds do not include the limit+1 sentinel', () => {
147
- const result = buildFtsQuery(
148
- { q: 'hello', schemaSlug: null, status: null, limit: 5, cursor: null },
149
- [TEXT_SEED],
150
- )
151
- expect(result.countBinds).not.toContain(6) // limit + 1 must not appear in count binds
152
- })
153
-
154
- it('single-character terms (length < 2) are filtered out', () => {
155
- // 'a b c' — single-char terms discarded; result depends on remaining terms
156
- expect(() =>
157
- buildFtsQuery({ q: 'a b c', schemaSlug: null, status: null, limit: 20, cursor: null }, [TEXT_SEED]),
158
- ).toThrow('EMPTY_QUERY')
159
- })
160
-
161
- it('numeric terms are quoted without prefix expansion', () => {
162
- const result = buildFtsQuery(
163
- { q: '2024', schemaSlug: null, status: null, limit: 20, cursor: null },
164
- [TEXT_SEED],
165
- )
166
- expect(result.binds[0]).toContain('"2024"')
167
- })
168
- })
169
-
170
- // ─── mapFtsRow ───────────────────────────────────────────────────────────────
171
-
172
- describe('mapFtsRow', () => {
173
- it('maps all FtsRow fields to SearchResultItem', () => {
174
- const row = {
175
- entry_id: 'e1', schema_slug: 'articoli', slug: 'my-post',
176
- status: 'published', title: 'My Post', excerpt: 'A snippet', rank: -1,
177
- }
178
- const result = mapFtsRow(row)
179
- expect(result.id).toBe('e1')
180
- expect(result.schema_slug).toBe('articoli')
181
- expect(result.slug).toBe('my-post')
182
- expect(result.status).toBe('published')
183
- expect(result.title).toBe('My Post')
184
- expect(result.excerpt).toBe('A snippet')
185
- expect(result.data).toEqual({})
186
- })
187
-
188
- it('strips HTML tags from excerpt but preserves <mark> and </mark>', () => {
189
- const row = {
190
- entry_id: 'e1', schema_slug: 'a', slug: null, status: 'draft',
191
- title: null, excerpt: '<p>A <mark>word</mark> here</p>', rank: 0,
192
- }
193
- expect(mapFtsRow(row).excerpt).toBe('A <mark>word</mark> here')
194
- })
195
-
196
- it('collapses multiple whitespace characters in excerpt', () => {
197
- const row = {
198
- entry_id: 'e1', schema_slug: 'a', slug: null, status: 'draft',
199
- title: null, excerpt: '<p> lots of space </p>', rank: 0,
200
- }
201
- expect(mapFtsRow(row).excerpt).toBe('lots of space')
202
- })
203
-
204
- it('returns empty string for null title', () => {
205
- const row = {
206
- entry_id: 'e1', schema_slug: 'a', slug: null, status: 'draft',
207
- title: null, excerpt: '', rank: 0,
208
- }
209
- expect(mapFtsRow(row).title).toBe('')
210
- })
211
- })
@@ -1,213 +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
- // apps/api/src/search-utils.ts
6
- // Pure functions — zero Hono dependencies, importable from Vitest.
7
- // v0.4.0: FTS is per-seed (fts_{slug}), joined with content_{slug} for metadata.
8
-
9
- import type { Seed, SearchResultRow } from "@beechcms/core"
10
-
11
- // ─── Types ───────────────────────────────────────────────────────────────────
12
-
13
- // Row returned by UNION ALL query across fts_{slug} JOIN content_{slug}
14
- export interface FtsRow {
15
- entry_id: string
16
- schema_slug: string
17
- slug: string | null
18
- status: string
19
- title: string | null
20
- excerpt: string
21
- rank: number
22
- }
23
-
24
- export interface SearchResultItem {
25
- id: string
26
- schema_slug: string
27
- slug: string | null
28
- status: string
29
- title: string
30
- excerpt: string
31
- data: Record<string, unknown>
32
- }
33
-
34
- export interface SearchResponse {
35
- items: SearchResultItem[]
36
- nextCursor: string | null
37
- total: number
38
- }
39
-
40
- export interface SearchQueryParams {
41
- q: string
42
- schemaSlug: string | null
43
- status: string | null
44
- limit: number // already clamped 1–50 by handler
45
- cursor: string | null
46
- }
47
-
48
- // ─── Cursor ──────────────────────────────────────────────────────────────────
49
-
50
- export function encodeCursor(rank: number, entryId: string): string {
51
- return btoa(`${rank}:${entryId}`)
52
- }
53
-
54
- export function decodeCursor(cursor: string): { rank: number; entryId: string } | null {
55
- try {
56
- const decoded = atob(cursor)
57
- const sep = decoded.lastIndexOf(":")
58
- if (sep === -1) return null
59
- return {
60
- rank: Number.parseFloat(decoded.slice(0, sep)),
61
- entryId: decoded.slice(sep + 1),
62
- }
63
- } catch {
64
- return null
65
- }
66
- }
67
-
68
- // ─── Helpers ─────────────────────────────────────────────────────────────────
69
-
70
- function hasSearchableFts(seed: Seed): boolean {
71
- return seed.branches.some(b =>
72
- (b.type === 'text' || b.type === 'richtext') && b.policies?.search !== false
73
- )
74
- }
75
-
76
- function buildMatchExpr(q: string): string {
77
- const MIN_PREFIX = 3
78
- const safeQ = q.replace(/["*^()]/g, " ").trim()
79
- const terms = safeQ.split(/\s+/).filter(t => t.length >= 2)
80
- if (terms.length === 0) throw new Error("EMPTY_QUERY")
81
-
82
- return terms
83
- .map(t => {
84
- if (/^\d+$/.test(t)) return `"${t}"`
85
- if (t.length <= MIN_PREFIX) return `"${t}"*`
86
- const prefixes: string[] = []
87
- for (let i = MIN_PREFIX; i <= t.length; i++) {
88
- prefixes.push(`"${t.slice(0, i)}"*`)
89
- }
90
- return `(${prefixes.join(" OR ")})`
91
- })
92
- .join(" ")
93
- }
94
-
95
- // ─── Query builder ───────────────────────────────────────────────────────────
96
-
97
- /**
98
- * Builds a UNION ALL query across all per-seed fts_{slug} tables (v0.4.0).
99
- * Each SELECT joins fts_{slug} with content_{slug} to fetch title, slug, status.
100
- * Seeds param: full registry — filtered internally by schemaSlug and FTS availability.
101
- */
102
- export function buildFtsQuery(
103
- params: SearchQueryParams,
104
- seeds: Seed[],
105
- ): {
106
- sql: string
107
- binds: unknown[]
108
- countSql: string
109
- countBinds: unknown[]
110
- } {
111
- const { q, schemaSlug, status, limit, cursor } = params
112
-
113
- const matchExpr = buildMatchExpr(q) // throws EMPTY_QUERY if needed
114
-
115
- const targetSeeds = seeds.filter(s =>
116
- hasSearchableFts(s) && (schemaSlug === null || s.slug === schemaSlug)
117
- )
118
-
119
- if (targetSeeds.length === 0) {
120
- return {
121
- sql: "SELECT NULL as entry_id, NULL as schema_slug, NULL as slug, NULL as status, NULL as title, '' as excerpt, 0 as rank WHERE 1=0",
122
- binds: [],
123
- countSql: "SELECT 0 as total",
124
- countBinds: [],
125
- }
126
- }
127
-
128
- const decoded = cursor ? decodeCursor(cursor) : null
129
-
130
- const parts: string[] = []
131
- const binds: unknown[] = []
132
- const countParts: string[] = []
133
- const countBinds: unknown[] = []
134
-
135
- for (const seed of targetSeeds) {
136
- const fts = `fts_${seed.slug}`
137
- const table = `content_${seed.slug}`
138
- const title = seed.displayNameAlias
139
-
140
- // Main query per seed
141
- const where: string[] = [`${fts} MATCH ?`]
142
- const lb: unknown[] = [matchExpr]
143
-
144
- if (status) { where.push("ce.status = ?"); lb.push(status) }
145
-
146
- if (decoded) {
147
- where.push(`(bm25(${fts}) > ? OR (bm25(${fts}) = ? AND f.entry_id > ?))`)
148
- lb.push(decoded.rank, decoded.rank, decoded.entryId)
149
- }
150
-
151
- parts.push(
152
- `SELECT f.entry_id, '${seed.slug}' AS schema_slug, ce.slug, ce.status,` +
153
- ` ce.${title} AS title,` +
154
- ` snippet(${fts}, 1, '<mark>', '</mark>', '…', 16) AS excerpt,` +
155
- ` bm25(${fts}) AS rank` +
156
- ` FROM ${fts} f JOIN ${table} ce ON ce.id = f.entry_id` +
157
- ` WHERE ${where.join(' AND ')}`
158
- )
159
- binds.push(...lb)
160
-
161
- // Count per seed (no cursor, no limit)
162
- const cw: string[] = [`${fts} MATCH ?`]
163
- const cb: unknown[] = [matchExpr]
164
- if (status) { cw.push("ce.status = ?"); cb.push(status) }
165
-
166
- countParts.push(
167
- `SELECT COUNT(*) as c FROM ${fts} f JOIN ${table} ce ON ce.id = f.entry_id WHERE ${cw.join(' AND ')}`
168
- )
169
- countBinds.push(...cb)
170
- }
171
-
172
- const sql = `${parts.join(' UNION ALL ')} ORDER BY rank, entry_id LIMIT ?`
173
- binds.push(limit + 1)
174
-
175
- const countSql = `SELECT SUM(c) as total FROM (${countParts.join(' UNION ALL ')})`
176
-
177
- return { sql, binds, countSql, countBinds }
178
- }
179
-
180
- // ─── Mapper ──────────────────────────────────────────────────────────────────
181
-
182
- function stripHtmlPreserveMark(html: string): string {
183
- return html.replace(/<(?!\/?mark\b)[^>]*>/gi, ' ').replace(/\s+/g, ' ').trim()
184
- }
185
-
186
- export function mapFtsRow(row: FtsRow): SearchResultItem {
187
- return {
188
- id: row.entry_id,
189
- schema_slug: row.schema_slug,
190
- slug: row.slug,
191
- status: row.status,
192
- title: row.title ?? "",
193
- excerpt: stripHtmlPreserveMark(row.excerpt ?? ""),
194
- data: {},
195
- }
196
- }
197
-
198
- /**
199
- * Maps a repository-shaped SearchResultRow (camelCase) to the wire-format
200
- * SearchResultItem returned by the /api/search route. Keeps the HTML
201
- * stripping behaviour previously inlined inside mapFtsRow.
202
- */
203
- export function mapSearchResultRow(row: SearchResultRow): SearchResultItem {
204
- return {
205
- id: row.entryId,
206
- schema_slug: row.schemaSlug,
207
- slug: row.slug,
208
- status: row.status,
209
- title: row.title ?? "",
210
- excerpt: stripHtmlPreserveMark(row.excerpt ?? ""),
211
- data: {},
212
- }
213
- }
package/src/search.ts DELETED
@@ -1,65 +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
- // apps/api/src/search.ts
6
-
7
- import { Hono } from "hono"
8
- import type { Env, Variables } from "./types"
9
- import { authMiddleware } from "./middleware"
10
- import {
11
- encodeCursor,
12
- mapSearchResultRow,
13
- type SearchResponse,
14
- } from "./search-utils"
15
-
16
- export const searchRouter = new Hono<{ Bindings: Env; Variables: Variables }>()
17
-
18
- searchRouter.use("*", authMiddleware())
19
-
20
- // GET /api/search?q=...&schema_slug=...&status=...&limit=20&cursor=...
21
- searchRouter.get("/", async (c) => {
22
- const queryText = c.req.query("q")?.trim() ?? ""
23
- const schemaSlug = c.req.query("schema_slug") ?? null
24
- const status = c.req.query("status") ?? null
25
- const rawLimit = Number.parseInt(c.req.query("limit") ?? "20", 10)
26
- const limit = Math.min(Math.max(rawLimit, 1), 50)
27
- const cursor = c.req.query("cursor") ?? null
28
-
29
- if (queryText.length < 2) {
30
- return c.json({ error: "Il parametro 'q' deve avere almeno 2 caratteri." }, 400)
31
- }
32
-
33
- const seeds = c.get('seedRegistry').all()
34
- const searchRepository = c.get('searchRepository')
35
-
36
- const queryOptions = {
37
- queryText,
38
- schemaSlug,
39
- statusFilter: status,
40
- limit,
41
- cursor,
42
- }
43
- const countOptions = { queryText, schemaSlug, statusFilter: status }
44
-
45
- const [rawRows, countResult] = await Promise.all([
46
- searchRepository.search(queryOptions, seeds),
47
- searchRepository.count(countOptions, seeds),
48
- ])
49
-
50
- const hasMore = rawRows.length > limit
51
- const pageRows = hasMore ? rawRows.slice(0, limit) : rawRows
52
-
53
- const lastRow = pageRows.at(-1)
54
- const nextCursor = hasMore && lastRow
55
- ? encodeCursor(lastRow.rank, lastRow.entryId)
56
- : null
57
-
58
- if (pageRows.length === 0) {
59
- return c.json({ items: [], nextCursor: null, total: countResult.total } satisfies SearchResponse)
60
- }
61
-
62
- const items = pageRows.map(mapSearchResultRow)
63
-
64
- return c.json({ items, nextCursor, total: countResult.total } satisfies SearchResponse)
65
- })
@@ -1,81 +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 { applyPrivacy, applyVisibility, PrivacyPolicyError } from './apply-policies'
7
- import type { Seed } from '@beechcms/core'
8
-
9
- function makeSeed(branches: any[]): Seed {
10
- return { slug: 'test', displayNameAlias: 'title', branches } as unknown as Seed
11
- }
12
-
13
- // ─── applyPrivacy ─────────────────────────────────────────────────────────────
14
-
15
- describe('applyPrivacy', () => {
16
- it('passes through fields with no privacy policy (default: public)', async () => {
17
- const seed = makeSeed([{ id: 'br_01', alias: 'title', type: 'text' }])
18
- const result = await applyPrivacy({ title: 'Hello' }, seed)
19
- expect(result.title).toBe('Hello')
20
- })
21
-
22
- it('hashes the value when branch privacy is "hash"', async () => {
23
- const seed = makeSeed([{ id: 'br_01', alias: 'email', type: 'text', policies: { privacy: 'hash' } }])
24
- const result = await applyPrivacy({ email: 'user@test.com' }, seed)
25
- // sha256 produces a 64-char hex string
26
- expect(typeof result.email).toBe('string')
27
- expect((result.email as string).length).toBe(64)
28
- expect(result.email).not.toBe('user@test.com')
29
- })
30
-
31
- it('leaves null/undefined values unhashed even when privacy is "hash"', async () => {
32
- const seed = makeSeed([{ id: 'br_01', alias: 'email', type: 'text', policies: { privacy: 'hash' } }])
33
- const result = await applyPrivacy({ email: null }, seed)
34
- expect(result.email).toBeNull()
35
- })
36
-
37
- it('throws PrivacyPolicyError for "encrypt" privacy (not yet implemented)', async () => {
38
- const seed = makeSeed([{ id: 'br_01', alias: 'secret', type: 'text', policies: { privacy: 'encrypt' } }])
39
- await expect(applyPrivacy({ secret: 'value' }, seed)).rejects.toBeInstanceOf(PrivacyPolicyError)
40
- })
41
-
42
- it('passes through fields not present in the seed branches unchanged', async () => {
43
- const seed = makeSeed([{ id: 'br_01', alias: 'title', type: 'text' }])
44
- const result = await applyPrivacy({ title: 'Hi', extraField: 'extra' }, seed)
45
- expect(result.extraField).toBe('extra')
46
- })
47
- })
48
-
49
- // ─── applyVisibility ──────────────────────────────────────────────────────────
50
-
51
- describe('applyVisibility', () => {
52
- it('includes fields with default (visible) visibility', () => {
53
- const seed = makeSeed([{ id: 'br_01', alias: 'title', type: 'text' }])
54
- const result = applyVisibility({ title: 'Hello' }, seed)
55
- expect(result.title).toBe('Hello')
56
- })
57
-
58
- it('omits fields with visibility "hidden"', () => {
59
- const seed = makeSeed([{ id: 'br_01', alias: 'internal', type: 'text', policies: { visibility: 'hidden' } }])
60
- const result = applyVisibility({ internal: 'secret' }, seed)
61
- expect(result).not.toHaveProperty('internal')
62
- })
63
-
64
- it('masks non-empty string fields with visibility "masked"', () => {
65
- const seed = makeSeed([{ id: 'br_01', alias: 'password', type: 'text', policies: { visibility: 'masked' } }])
66
- const result = applyVisibility({ password: 'secret123' }, seed)
67
- expect(result.password).toBe('••••••••')
68
- })
69
-
70
- it('returns null for empty string fields with visibility "masked"', () => {
71
- const seed = makeSeed([{ id: 'br_01', alias: 'password', type: 'text', policies: { visibility: 'masked' } }])
72
- const result = applyVisibility({ password: '' }, seed)
73
- expect(result.password).toBeNull()
74
- })
75
-
76
- it('passes through fields not present in the seed branches', () => {
77
- const seed = makeSeed([{ id: 'br_01', alias: 'title', type: 'text' }])
78
- const result = applyVisibility({ title: 'Hi', extraField: 'pass' }, seed)
79
- expect(result.extraField).toBe('pass')
80
- })
81
- })