@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,128 +0,0 @@
1
- // SPDX-License-Identifier: BUSL-1.1
2
- // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
- // See LICENSE in the repository root for license terms.
4
-
5
- import { describe, it, expect, vi } from 'vitest'
6
- import { D1NotificationRepository } from './d1-notification.repository'
7
- import { FixedClock } from './fixed-clock'
8
- import { SequentialIdGenerator } from './sequential-id-generator'
9
-
10
- const clock = new FixedClock(1700000000_000)
11
- const makeIdGen = () => new SequentialIdGenerator()
12
-
13
- function makeMockDb(opts: { firstResult?: unknown; allResults?: unknown[] } = {}) {
14
- const { firstResult = null, allResults = [] } = opts
15
- const runMock = vi.fn().mockResolvedValue({ success: true })
16
- const firstMock = vi.fn().mockResolvedValue(firstResult)
17
- const allMock = vi.fn().mockResolvedValue({ results: allResults })
18
- const bindMock = vi.fn(() => ({ first: firstMock, all: allMock, run: runMock }))
19
- const prepareMock = vi.fn(() => ({
20
- bind: bindMock,
21
- first: firstMock,
22
- all: allMock,
23
- run: runMock,
24
- }))
25
- return { db: { prepare: prepareMock } as any, prepareMock, bindMock, runMock, firstMock, allMock }
26
- }
27
-
28
- describe('D1NotificationRepository', () => {
29
- describe('list', () => {
30
- it('maps rows to NotificationRecord with isRead boolean', async () => {
31
- const { db } = makeMockDb({
32
- allResults: [
33
- {
34
- id: 'n-1',
35
- title: 'T',
36
- message: 'M',
37
- type: 'success',
38
- is_read: 0,
39
- created_at: 100,
40
- },
41
- {
42
- id: 'n-2',
43
- title: 'T2',
44
- message: 'M2',
45
- type: 'info',
46
- is_read: 1,
47
- created_at: 99,
48
- },
49
- ],
50
- })
51
- const records = await new D1NotificationRepository(db, clock, makeIdGen()).list(10)
52
- expect(records).toEqual([
53
- { id: 'n-1', title: 'T', message: 'M', type: 'success', isRead: false, createdAt: 100 },
54
- { id: 'n-2', title: 'T2', message: 'M2', type: 'info', isRead: true, createdAt: 99 },
55
- ])
56
- })
57
-
58
- it('orders by created_at DESC with LIMIT bound', async () => {
59
- const { db, prepareMock, bindMock } = makeMockDb()
60
- await new D1NotificationRepository(db, clock, makeIdGen()).list(25)
61
- expect(prepareMock.mock.calls[0][0]).toMatch(/ORDER BY created_at DESC/)
62
- expect(bindMock).toHaveBeenCalledWith(25)
63
- })
64
- })
65
-
66
- describe('stats', () => {
67
- it('returns aggregate counters mapped to camelCase', async () => {
68
- const { db } = makeMockDb({
69
- firstResult: { total_count: 7, latest_created_at: 1234, read_count: 3 },
70
- })
71
- const stats = await new D1NotificationRepository(db, clock, makeIdGen()).stats()
72
- expect(stats).toEqual({ totalCount: 7, latestCreatedAt: 1234, readCount: 3 })
73
- })
74
-
75
- it('returns zeros when the table is empty', async () => {
76
- const { db } = makeMockDb({
77
- firstResult: { total_count: 0, latest_created_at: null, read_count: null },
78
- })
79
- const stats = await new D1NotificationRepository(db, clock, makeIdGen()).stats()
80
- expect(stats).toEqual({ totalCount: 0, latestCreatedAt: 0, readCount: 0 })
81
- })
82
- })
83
-
84
- describe('create', () => {
85
- it('inserts a new notification and returns the generated id', async () => {
86
- const { db, prepareMock, bindMock } = makeMockDb()
87
- const id = await new D1NotificationRepository(db, clock, makeIdGen()).create({
88
- title: 'Hello',
89
- message: 'World',
90
- type: 'info',
91
- })
92
- expect(typeof id).toBe('string')
93
- expect(id.length).toBeGreaterThan(0)
94
- expect(prepareMock.mock.calls[0][0]).toMatch(/INSERT INTO notifications/)
95
- expect(bindMock).toHaveBeenCalledWith(id, 'Hello', 'World', 'info')
96
- })
97
- })
98
-
99
- describe('markRead / markUnread / delete / markAllRead', () => {
100
- it('markRead binds the given id', async () => {
101
- const { db, prepareMock, bindMock } = makeMockDb()
102
- await new D1NotificationRepository(db, clock, makeIdGen()).markRead('n-1')
103
- expect(prepareMock.mock.calls[0][0]).toMatch(/UPDATE notifications SET is_read = 1 WHERE id = \?/)
104
- expect(bindMock).toHaveBeenCalledWith('n-1')
105
- })
106
-
107
- it('markUnread binds the given id', async () => {
108
- const { db, prepareMock, bindMock } = makeMockDb()
109
- await new D1NotificationRepository(db, clock, makeIdGen()).markUnread('n-2')
110
- expect(prepareMock.mock.calls[0][0]).toMatch(/UPDATE notifications SET is_read = 0 WHERE id = \?/)
111
- expect(bindMock).toHaveBeenCalledWith('n-2')
112
- })
113
-
114
- it('delete binds the given id', async () => {
115
- const { db, prepareMock, bindMock } = makeMockDb()
116
- await new D1NotificationRepository(db, clock, makeIdGen()).delete('n-3')
117
- expect(prepareMock.mock.calls[0][0]).toMatch(/DELETE FROM notifications WHERE id = \?/)
118
- expect(bindMock).toHaveBeenCalledWith('n-3')
119
- })
120
-
121
- it('markAllRead runs without bindings', async () => {
122
- const { db, prepareMock, runMock } = makeMockDb()
123
- await new D1NotificationRepository(db, clock, makeIdGen()).markAllRead()
124
- expect(prepareMock.mock.calls[0][0]).toMatch(/^UPDATE notifications SET is_read = 1$/)
125
- expect(runMock).toHaveBeenCalled()
126
- })
127
- })
128
- })
@@ -1,118 +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
- /// <reference types="@cloudflare/workers-types" />
6
- import type {
7
- INotificationRepository,
8
- NotificationRecord,
9
- NotificationStats,
10
- NotificationType,
11
- IClock,
12
- IIdGenerator,
13
- } from '@beechcms/core'
14
-
15
- interface NotificationRow {
16
- id: string
17
- title: string
18
- message: string
19
- type: string
20
- is_read: number
21
- created_at: number
22
- }
23
-
24
- interface NotificationStatsRow {
25
- total_count: number | null
26
- latest_created_at: number | null
27
- read_count: number | null
28
- }
29
-
30
- /**
31
- * D1-backed implementation of {@link INotificationRepository}.
32
- */
33
- export class D1NotificationRepository implements INotificationRepository {
34
- constructor(
35
- private readonly database: D1Database,
36
- private readonly clock: IClock,
37
- private readonly idGenerator: IIdGenerator,
38
- ) {}
39
-
40
- async list(limit: number): Promise<NotificationRecord[]> {
41
- const queryResult = await this.database
42
- .prepare(
43
- `SELECT id, title, message, type, is_read, created_at
44
- FROM notifications
45
- ORDER BY created_at DESC
46
- LIMIT ?`
47
- )
48
- .bind(limit)
49
- .all<NotificationRow>()
50
-
51
- return (queryResult.results ?? []).map(mapRowToRecord)
52
- }
53
-
54
- async stats(): Promise<NotificationStats> {
55
- const statsRow = await this.database
56
- .prepare(
57
- `SELECT COUNT(*) as total_count,
58
- MAX(created_at) as latest_created_at,
59
- SUM(is_read) as read_count
60
- FROM notifications`
61
- )
62
- .first<NotificationStatsRow>()
63
-
64
- return {
65
- totalCount: statsRow?.total_count ?? 0,
66
- latestCreatedAt: statsRow?.latest_created_at ?? 0,
67
- readCount: statsRow?.read_count ?? 0,
68
- }
69
- }
70
-
71
- async create(record: Omit<NotificationRecord, 'id' | 'createdAt' | 'isRead'>): Promise<string> {
72
- const generatedId = this.idGenerator.uuid()
73
- await this.database
74
- .prepare(
75
- `INSERT INTO notifications (id, title, message, type)
76
- VALUES (?, ?, ?, ?)`
77
- )
78
- .bind(generatedId, record.title, record.message, record.type)
79
- .run()
80
- return generatedId
81
- }
82
-
83
- async markRead(notificationId: string): Promise<void> {
84
- await this.database
85
- .prepare('UPDATE notifications SET is_read = 1 WHERE id = ?')
86
- .bind(notificationId)
87
- .run()
88
- }
89
-
90
- async markUnread(notificationId: string): Promise<void> {
91
- await this.database
92
- .prepare('UPDATE notifications SET is_read = 0 WHERE id = ?')
93
- .bind(notificationId)
94
- .run()
95
- }
96
-
97
- async markAllRead(): Promise<void> {
98
- await this.database.prepare('UPDATE notifications SET is_read = 1').run()
99
- }
100
-
101
- async delete(notificationId: string): Promise<void> {
102
- await this.database
103
- .prepare('DELETE FROM notifications WHERE id = ?')
104
- .bind(notificationId)
105
- .run()
106
- }
107
- }
108
-
109
- function mapRowToRecord(row: NotificationRow): NotificationRecord {
110
- return {
111
- id: row.id,
112
- title: row.title,
113
- message: row.message,
114
- type: row.type as NotificationType,
115
- isRead: row.is_read === 1,
116
- createdAt: row.created_at,
117
- }
118
- }
@@ -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, vi } from 'vitest'
6
- import { D1PasswordResetTokenRepository } from './d1-password-reset-token.repository'
7
-
8
- const NOW = Math.floor(Date.now() / 1000)
9
- const fixedIdGen = { uuid: () => 'prt-1' }
10
-
11
- function makeMockDb(opts: { firstResult?: unknown; runChanges?: number } = {}) {
12
- const { firstResult = null, runChanges = 1 } = opts
13
- const runMock = vi.fn().mockResolvedValue({ success: true, meta: { changes: runChanges } })
14
- const firstMock = vi.fn().mockResolvedValue(firstResult)
15
- const bindMock = vi.fn(() => ({ first: firstMock, run: runMock }))
16
- const prepareMock = vi.fn(() => ({ bind: bindMock }))
17
- return { db: { prepare: prepareMock } as any, prepareMock, bindMock, runMock }
18
- }
19
-
20
- describe('D1PasswordResetTokenRepository', () => {
21
- describe('create', () => {
22
- it('calls INSERT INTO password_reset_tokens', async () => {
23
- const { db, prepareMock } = makeMockDb()
24
- await new D1PasswordResetTokenRepository(db, fixedIdGen).create({
25
- userId: 'u1', tokenHash: 'h', expiresAt: NOW + 1800,
26
- })
27
- expect(prepareMock).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO password_reset_tokens'))
28
- })
29
-
30
- it('binds id, userId, tokenHash, and expiresAt in the correct order', async () => {
31
- const { db, bindMock } = makeMockDb()
32
- await new D1PasswordResetTokenRepository(db, fixedIdGen).create({
33
- userId: 'u1', tokenHash: 'h', expiresAt: NOW + 1800,
34
- })
35
- expect(bindMock).toHaveBeenCalledWith('prt-1', 'u1', 'h', NOW + 1800)
36
- })
37
- })
38
-
39
- describe('findValidByHashWithEmail', () => {
40
- it('returns a ValidatedResetToken with the userId and email from the JOIN', async () => {
41
- const row = { id: 'prt-1', user_id: 'u1', email: 'user@test.com' }
42
- const { db } = makeMockDb({ firstResult: row })
43
- const result = await new D1PasswordResetTokenRepository(db, fixedIdGen).findValidByHashWithEmail('h', NOW)
44
- expect(result).toEqual({ id: 'prt-1', userId: 'u1', email: 'user@test.com' })
45
- })
46
-
47
- it('returns null when the token is not found, expired, or already used', async () => {
48
- const { db } = makeMockDb({ firstResult: null })
49
- expect(await new D1PasswordResetTokenRepository(db, fixedIdGen).findValidByHashWithEmail('bad-hash', NOW)).toBeNull()
50
- })
51
-
52
- it('passes tokenHash and nowTimestamp as bound values', async () => {
53
- const { db, bindMock } = makeMockDb()
54
- await new D1PasswordResetTokenRepository(db, fixedIdGen).findValidByHashWithEmail('my-hash', NOW)
55
- expect(bindMock).toHaveBeenCalledWith('my-hash', NOW)
56
- })
57
- })
58
-
59
- describe('markUsed', () => {
60
- it('calls UPDATE password_reset_tokens SET used_at', async () => {
61
- const { db, prepareMock } = makeMockDb()
62
- await new D1PasswordResetTokenRepository(db, fixedIdGen).markUsed('prt-1', NOW)
63
- expect(prepareMock).toHaveBeenCalledWith(expect.stringContaining('used_at'))
64
- })
65
-
66
- it('binds nowTimestamp and tokenId in the correct order', async () => {
67
- const { db, bindMock } = makeMockDb()
68
- await new D1PasswordResetTokenRepository(db, fixedIdGen).markUsed('prt-1', NOW)
69
- expect(bindMock).toHaveBeenCalledWith(NOW, 'prt-1')
70
- })
71
- })
72
-
73
- describe('invalidatePending', () => {
74
- it('calls UPDATE password_reset_tokens and includes the userId in the bound values', async () => {
75
- const { db, prepareMock, bindMock } = makeMockDb()
76
- await new D1PasswordResetTokenRepository(db, fixedIdGen).invalidatePending('u1', NOW)
77
- expect(prepareMock).toHaveBeenCalledWith(expect.stringContaining('UPDATE password_reset_tokens'))
78
- expect((bindMock.mock.calls[0] as unknown[]).includes('u1')).toBe(true)
79
- })
80
- })
81
- })
@@ -1,56 +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
- /// <reference types="@cloudflare/workers-types" />
6
- import type { IPasswordResetTokenRepository, NewPasswordResetToken, ValidatedResetToken, IIdGenerator } from '@beechcms/core'
7
-
8
- type ValidatedResetTokenRow = {
9
- id: string
10
- user_id: string
11
- email: string
12
- }
13
-
14
- export class D1PasswordResetTokenRepository implements IPasswordResetTokenRepository {
15
- constructor(
16
- private readonly db: D1Database,
17
- private readonly idGenerator: IIdGenerator,
18
- ) {}
19
-
20
- async invalidatePending(userId: string, nowTimestamp: number): Promise<void> {
21
- await this.db
22
- .prepare('UPDATE password_reset_tokens SET used_at = ? WHERE user_id = ? AND used_at IS NULL')
23
- .bind(nowTimestamp, userId)
24
- .run()
25
- }
26
-
27
- async create(record: NewPasswordResetToken): Promise<void> {
28
- const generatedId = this.idGenerator.uuid()
29
- await this.db
30
- .prepare('INSERT INTO password_reset_tokens (id, user_id, token_hash, expires_at) VALUES (?, ?, ?, ?)')
31
- .bind(generatedId, record.userId, record.tokenHash, record.expiresAt)
32
- .run()
33
- }
34
-
35
- async findValidByHashWithEmail(tokenHash: string, nowTimestamp: number): Promise<ValidatedResetToken | null> {
36
- const row = await this.db
37
- .prepare(
38
- `SELECT prt.id, prt.user_id, u.email
39
- FROM password_reset_tokens prt
40
- JOIN users u ON u.id = prt.user_id
41
- WHERE prt.token_hash = ? AND prt.expires_at > ? AND prt.used_at IS NULL`
42
- )
43
- .bind(tokenHash, nowTimestamp)
44
- .first<ValidatedResetTokenRow>()
45
-
46
- if (!row) return null
47
- return { id: row.id, userId: row.user_id, email: row.email }
48
- }
49
-
50
- async markUsed(tokenId: string, nowTimestamp: number): Promise<void> {
51
- await this.db
52
- .prepare('UPDATE password_reset_tokens SET used_at = ? WHERE id = ?')
53
- .bind(nowTimestamp, tokenId)
54
- .run()
55
- }
56
- }
@@ -1,87 +0,0 @@
1
- // SPDX-License-Identifier: BUSL-1.1
2
- // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
- // See LICENSE in the repository root for license terms.
4
-
5
- import { describe, it, expect, vi } from 'vitest'
6
- import type { Seed } from '@beechcms/core'
7
- import { D1SearchRepository } from './d1-search.repository'
8
-
9
- function makeMockDb(allResults: unknown[] = [], firstResult: unknown = null) {
10
- const allMock = vi.fn().mockResolvedValue({ results: allResults })
11
- const firstMock = vi.fn().mockResolvedValue(firstResult)
12
- const bindMock = vi.fn(() => ({ all: allMock, first: firstMock }))
13
- const prepareMock = vi.fn(() => ({ bind: bindMock }))
14
- return { db: { prepare: prepareMock } as any, prepareMock, bindMock, allMock, firstMock }
15
- }
16
-
17
- const seeds: Seed[] = [
18
- {
19
- slug: 'posts',
20
- label: 'Post',
21
- displayNameAlias: 'title',
22
- branches: [
23
- { id: 'br_01', alias: 'title', type: 'text', label: 'Title' },
24
- { id: 'br_02', alias: 'body', type: 'richtext', label: 'Body' },
25
- ],
26
- },
27
- ]
28
-
29
- describe('D1SearchRepository', () => {
30
- it('maps FtsRow to camelCase SearchResultRow', async () => {
31
- const ftsRow = {
32
- entry_id: 'e1',
33
- schema_slug: 'posts',
34
- slug: 'hello',
35
- status: 'published',
36
- title: 'Hello',
37
- excerpt: '<mark>Hi</mark>',
38
- rank: -3.2,
39
- }
40
- const { db } = makeMockDb([ftsRow])
41
- const result = await new D1SearchRepository(db).search(
42
- { queryText: 'hello world', schemaSlug: null, statusFilter: null, limit: 10, cursor: null },
43
- seeds,
44
- )
45
- expect(result).toEqual([
46
- {
47
- entryId: 'e1',
48
- schemaSlug: 'posts',
49
- slug: 'hello',
50
- status: 'published',
51
- title: 'Hello',
52
- excerpt: '<mark>Hi</mark>',
53
- rank: -3.2,
54
- },
55
- ])
56
- })
57
-
58
- it('returns [] on EMPTY_QUERY (single-character terms)', async () => {
59
- const { db, prepareMock } = makeMockDb()
60
- const result = await new D1SearchRepository(db).search(
61
- { queryText: 'a', schemaSlug: null, statusFilter: null, limit: 10, cursor: null },
62
- seeds,
63
- )
64
- expect(result).toEqual([])
65
- expect(prepareMock).not.toHaveBeenCalled()
66
- })
67
-
68
- it('count returns { total: 0 } on EMPTY_QUERY', async () => {
69
- const { db } = makeMockDb()
70
- const result = await new D1SearchRepository(db).count(
71
- { queryText: 'a', schemaSlug: null, statusFilter: null },
72
- seeds,
73
- )
74
- expect(result).toEqual({ total: 0 })
75
- })
76
-
77
- it('count returns total from countSql', async () => {
78
- const { db, prepareMock } = makeMockDb([], { total: 17 })
79
- const result = await new D1SearchRepository(db).count(
80
- { queryText: 'hello', schemaSlug: null, statusFilter: null },
81
- seeds,
82
- )
83
- expect(result).toEqual({ total: 17 })
84
- const sql = prepareMock.mock.calls[0]![0] as string
85
- expect(sql).toMatch(/SUM\(c\)/)
86
- })
87
- })
@@ -1,88 +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
- /// <reference types="@cloudflare/workers-types" />
6
- import type {
7
- Seed,
8
- ISearchRepository,
9
- SearchQueryOptions,
10
- SearchResultRow,
11
- SearchCountResult,
12
- } from '@beechcms/core'
13
- import { buildFtsQuery, type FtsRow } from '../search-utils'
14
-
15
- const EMPTY_QUERY_ERROR = 'EMPTY_QUERY'
16
-
17
- /**
18
- * D1-backed implementation of {@link ISearchRepository}.
19
- *
20
- * Delegates SQL composition to the pure `buildFtsQuery` helper to keep this
21
- * class focused on D1 wiring. Returns raw rows; the route handler is
22
- * responsible for mapping them via `mapFtsRow`.
23
- */
24
- export class D1SearchRepository implements ISearchRepository {
25
- constructor(private readonly database: D1Database) {}
26
-
27
- async search(options: SearchQueryOptions, seeds: Seed[]): Promise<SearchResultRow[]> {
28
- let queryParts
29
- try {
30
- queryParts = buildFtsQuery(
31
- {
32
- q: options.queryText,
33
- schemaSlug: options.schemaSlug,
34
- status: options.statusFilter,
35
- limit: options.limit,
36
- cursor: options.cursor,
37
- },
38
- seeds,
39
- )
40
- } catch (error) {
41
- if (error instanceof Error && error.message === EMPTY_QUERY_ERROR) return []
42
- throw error
43
- }
44
-
45
- const result = await this.database.prepare(queryParts.sql).bind(...queryParts.binds).all<FtsRow>()
46
- return (result.results ?? []).map(mapFtsRowToResultRow)
47
- }
48
-
49
- async count(
50
- options: Omit<SearchQueryOptions, 'limit' | 'cursor'>,
51
- seeds: Seed[],
52
- ): Promise<SearchCountResult> {
53
- let queryParts
54
- try {
55
- queryParts = buildFtsQuery(
56
- {
57
- q: options.queryText,
58
- schemaSlug: options.schemaSlug,
59
- status: options.statusFilter,
60
- limit: 0,
61
- cursor: null,
62
- },
63
- seeds,
64
- )
65
- } catch (error) {
66
- if (error instanceof Error && error.message === EMPTY_QUERY_ERROR) return { total: 0 }
67
- throw error
68
- }
69
-
70
- const row = await this.database
71
- .prepare(queryParts.countSql)
72
- .bind(...queryParts.countBinds)
73
- .first<{ total: number }>()
74
- return { total: row?.total ?? 0 }
75
- }
76
- }
77
-
78
- function mapFtsRowToResultRow(row: FtsRow): SearchResultRow {
79
- return {
80
- entryId: row.entry_id,
81
- schemaSlug: row.schema_slug,
82
- slug: row.slug,
83
- status: row.status,
84
- title: row.title,
85
- excerpt: row.excerpt,
86
- rank: row.rank,
87
- }
88
- }
@@ -1,125 +0,0 @@
1
- // SPDX-License-Identifier: BUSL-1.1
2
- // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
- // See LICENSE in the repository root for license terms.
4
-
5
- import { describe, it, expect, vi } from 'vitest'
6
- import { D1SessionRepository } from './d1-session.repository'
7
- import { FixedClock } from './fixed-clock'
8
-
9
- const FIXED_NOW_MS = 1700000000_000
10
- const NOW = Math.floor(FIXED_NOW_MS / 1000)
11
- const FUTURE = NOW + 7 * 24 * 3600
12
- const clock = new FixedClock(FIXED_NOW_MS)
13
-
14
- function makeMockDb(opts: {
15
- firstResult?: unknown
16
- runChanges?: number
17
- allResults?: unknown[]
18
- } = {}) {
19
- const { firstResult = null, runChanges = 1, allResults = [] } = opts
20
- const runMock = vi.fn().mockResolvedValue({ success: true, meta: { changes: runChanges } })
21
- const firstMock = vi.fn().mockResolvedValue(firstResult)
22
- const allMock = vi.fn().mockResolvedValue({ results: allResults })
23
- const bindMock = vi.fn(() => ({ first: firstMock, all: allMock, run: runMock }))
24
- const prepareMock = vi.fn(() => ({ bind: bindMock }))
25
- return { db: { prepare: prepareMock } as any, prepareMock, bindMock, runMock, firstMock, allMock }
26
- }
27
-
28
- describe('D1SessionRepository', () => {
29
- describe('saveRefreshToken', () => {
30
- it('calls INSERT INTO refresh_tokens with the correct bound values', async () => {
31
- const { db, prepareMock, bindMock } = makeMockDb()
32
- await new D1SessionRepository(db, clock).saveRefreshToken({
33
- id: 'rt-1', userId: 'u1', tokenHash: 'h', expiresAt: FUTURE,
34
- })
35
- expect(prepareMock).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO refresh_tokens'))
36
- expect(bindMock).toHaveBeenCalledWith('rt-1', 'u1', 'h', FUTURE, NOW)
37
- })
38
- })
39
-
40
- describe('findActiveByHash', () => {
41
- it('returns a RefreshTokenRecord with camelCase keys mapped from the snake_case row', async () => {
42
- const row = {
43
- id: 'rt-1', user_id: 'u1', token_hash: 'h',
44
- expires_at: FUTURE, created_at: NOW, revoked_at: null,
45
- }
46
- const { db } = makeMockDb({ firstResult: row })
47
- const result = await new D1SessionRepository(db, clock).findActiveByHash('h', NOW)
48
- expect(result).toEqual({
49
- id: 'rt-1', userId: 'u1', tokenHash: 'h',
50
- expiresAt: FUTURE, createdAt: NOW, revokedAt: null,
51
- })
52
- })
53
-
54
- it('returns null when no active token is found', async () => {
55
- const { db } = makeMockDb({ firstResult: null })
56
- expect(await new D1SessionRepository(db, clock).findActiveByHash('missing', NOW)).toBeNull()
57
- })
58
-
59
- it('passes tokenHash and nowTimestamp as bound values', async () => {
60
- const { db, bindMock } = makeMockDb()
61
- await new D1SessionRepository(db, clock).findActiveByHash('my-hash', NOW)
62
- expect(bindMock).toHaveBeenCalledWith('my-hash', NOW)
63
- })
64
- })
65
-
66
- describe('revokeByHash', () => {
67
- it('returns true when one row is updated (changes = 1)', async () => {
68
- const { db } = makeMockDb({ runChanges: 1 })
69
- expect(await new D1SessionRepository(db, clock).revokeByHash('h', NOW)).toBe(true)
70
- })
71
-
72
- it('returns false when no row is updated (token already revoked or not found)', async () => {
73
- const { db } = makeMockDb({ runChanges: 0 })
74
- expect(await new D1SessionRepository(db, clock).revokeByHash('h', NOW)).toBe(false)
75
- })
76
- })
77
-
78
- describe('revokeAllForUser', () => {
79
- it('calls UPDATE refresh_tokens and includes the userId in the bound values', async () => {
80
- const { db, prepareMock, bindMock } = makeMockDb()
81
- await new D1SessionRepository(db, clock).revokeAllForUser('u1', NOW)
82
- expect(prepareMock).toHaveBeenCalledWith(expect.stringContaining('UPDATE refresh_tokens'))
83
- expect((bindMock.mock.calls[0] as unknown[]).includes('u1')).toBe(true)
84
- })
85
- })
86
-
87
- describe('listActiveForUser', () => {
88
- it('returns mapped ActiveSessionSummary objects in the original order', async () => {
89
- const rows = [
90
- { id: 's1', created_at: NOW - 100, expires_at: FUTURE },
91
- { id: 's2', created_at: NOW - 200, expires_at: FUTURE },
92
- ]
93
- const { db } = makeMockDb({ allResults: rows })
94
- const sessions = await new D1SessionRepository(db, clock).listActiveForUser('u1', NOW, 20)
95
- expect(sessions).toHaveLength(2)
96
- expect(sessions[0]).toEqual({ id: 's1', createdAt: NOW - 100, expiresAt: FUTURE })
97
- expect(sessions[1]).toEqual({ id: 's2', createdAt: NOW - 200, expiresAt: FUTURE })
98
- })
99
-
100
- it('returns an empty array when no active sessions exist', async () => {
101
- const { db } = makeMockDb({ allResults: [] })
102
- expect(await new D1SessionRepository(db, clock).listActiveForUser('u1', NOW, 20)).toEqual([])
103
- })
104
- })
105
-
106
- describe('revokeById', () => {
107
- it('returns true when the session is revoked (changes = 1)', async () => {
108
- const { db } = makeMockDb({ runChanges: 1 })
109
- expect(await new D1SessionRepository(db, clock).revokeById('s1', 'u1', NOW)).toBe(true)
110
- })
111
-
112
- it('returns false when the session is not found or belongs to another user (changes = 0)', async () => {
113
- const { db } = makeMockDb({ runChanges: 0 })
114
- expect(await new D1SessionRepository(db, clock).revokeById('s1', 'other-user', NOW)).toBe(false)
115
- })
116
-
117
- it('binds both sessionId and userId to prevent cross-user revocation', async () => {
118
- const { db, bindMock } = makeMockDb({ runChanges: 1 })
119
- await new D1SessionRepository(db, clock).revokeById('my-session', 'my-user', NOW)
120
- const args = bindMock.mock.calls[0] as unknown[]
121
- expect(args).toContain('my-session')
122
- expect(args).toContain('my-user')
123
- })
124
- })
125
- })