@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,558 +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 { Hono } from 'hono'
7
- import type { Branch, Seed } from '@beechcms/core'
8
- import {
9
- nextBranchId,
10
- validateSeedDefinitions,
11
- planCreateSeed,
12
- planExtendSeed,
13
- planFtsRebuild,
14
- generateDropTable,
15
- generateDropColumn,
16
- generateRenameColumn,
17
- generateRetypeColumn,
18
- BRANCH_ALIAS_RE,
19
- } from '@beechcms/core'
20
- import { publicProblem, internalErrorDetail } from '../../public/problem-details'
21
- import { deleteR2Objects } from '../../upload'
22
- import { extractMediaKeysFromData } from '../../media-utils'
23
- import type { Env, Variables } from '../../types'
24
-
25
- const SLUG_RE = /^[a-z0-9_]+$/
26
-
27
- function requireAdmin(context: any) {
28
- const role = context.get('jwtPayload')?.role
29
- if (role !== 'admin') {
30
- return publicProblem(context, {
31
- type: 'forbidden',
32
- title: 'Forbidden',
33
- status: 403,
34
- detail: 'Seed management requires admin role.',
35
- })
36
- }
37
- return null
38
- }
39
-
40
- function actorFromContext(context: any) {
41
- const jwt = context.get('jwtPayload')
42
- return {
43
- id: jwt?.sub ?? 'unknown',
44
- email: jwt?.email ?? 'unknown',
45
- name: [jwt?.name, jwt?.surname].filter(Boolean).join(' ') || null,
46
- }
47
- }
48
-
49
- async function parseJsonBody(context: any): Promise<unknown> {
50
- try { return await context.req.json() } catch {
51
- return publicProblem(context, { type: 'invalid-json', title: 'Invalid JSON', status: 400, detail: 'Body must be valid JSON.' })
52
- }
53
- }
54
-
55
- async function getActiveSeed(context: any, slug: string) {
56
- const repo = context.get('seedRepository')
57
- const existing = await repo.get(slug)
58
- if (!existing || existing.status === 'deleted') {
59
- return publicProblem(context, { type: 'seed-not-found', title: 'Seed not found', status: 404, detail: `No active seed with slug '${slug}'.` })
60
- }
61
- return existing
62
- }
63
-
64
- async function validateAndApplySeedDef(context: any, slug: string, candidate: Seed, action: 'create' | 'update', logDetails: any) {
65
- const repo = context.get('seedRepository')
66
- const activeSeeds = await repo.listActive()
67
- const candidateSet = [...activeSeeds.filter((s: any) => s.slug !== slug), candidate]
68
- const issues = validateSeedDefinitions(candidateSet)
69
- const fatalIssues = issues.filter(i => i.fatal && i.slug === slug)
70
- if (fatalIssues.length > 0) {
71
- return publicProblem(context, {
72
- type: 'validation-failed',
73
- title: 'Validation failed',
74
- status: 422,
75
- detail: fatalIssues.flatMap(i => i.messages).join('; '),
76
- })
77
- }
78
-
79
- const schemaMutator = context.get('schemaMutator')
80
- const tableName = `content_${slug}`
81
- const existingCols = await schemaMutator.getColumns(tableName)
82
-
83
- try {
84
- const stmts = existingCols === null
85
- ? planCreateSeed(candidate)
86
- : planExtendSeed(candidate, existingCols).statements
87
- await schemaMutator.execDdl(stmts)
88
- } catch (err) {
89
- return publicProblem(context, {
90
- type: 'ddl-failed',
91
- title: 'DDL execution failed',
92
- status: 422,
93
- detail: internalErrorDetail(context.env, err),
94
- })
95
- }
96
-
97
- await repo.upsert(slug, candidate, 'runtime')
98
- await repo.bumpRegistryVersion()
99
-
100
- const actor = actorFromContext(context)
101
- context.get('activityLogger').log({ action, entityType: 'seed', entityId: slug, details: logDetails, actor })
102
-
103
- return null
104
- }
105
-
106
- async function applyDestructiveSeedDef(context: any, slug: string, updatedDef: Seed, stmts: string[], logDetails: any) {
107
- const repo = context.get('seedRepository')
108
- const schemaMutator = context.get('schemaMutator')
109
-
110
- try {
111
- await schemaMutator.execDestructive(stmts)
112
- } catch (err) {
113
- return publicProblem(context, { type: 'ddl-failed', title: 'DDL failed', status: 422, detail: internalErrorDetail(context.env, err) })
114
- }
115
-
116
- await repo.upsert(slug, updatedDef, 'runtime')
117
- await repo.bumpRegistryVersion()
118
-
119
- const actor = actorFromContext(context)
120
- context.get('activityLogger').log({ action: 'update', entityType: 'seed', entityId: slug, details: logDetails, actor })
121
-
122
- return null
123
- }
124
-
125
- /** Typed confirm guard. Returns a 400 response on mismatch, null on success. */
126
- function requireConfirm(context: any, expected: string, body: unknown) {
127
- const confirm = (body as Record<string, unknown>)?.confirm
128
- if (typeof confirm !== 'string' || confirm !== expected) {
129
- return publicProblem(context, {
130
- type: 'confirmation-required',
131
- title: 'Confirmation required',
132
- status: 400,
133
- detail: `Destructive operation requires body field \`confirm\` equal to "${expected}".`,
134
- })
135
- }
136
- return null
137
- }
138
-
139
- function validateIncomingBranches(incomingBranches: Branch[], storedBranches: Branch[], slug: string, context: any) {
140
- const storedById = new Map(storedBranches.map((b: Branch) => [b.id, b]))
141
-
142
- for (const branch of incomingBranches) {
143
- const stored = branch.id ? storedById.get(branch.id) : undefined
144
- if (stored) {
145
- if (branch.alias !== stored.alias) {
146
- return publicProblem(context, {
147
- type: 'alias-rename-not-supported',
148
- title: 'Alias rename not supported',
149
- status: 422,
150
- detail: `Branch '${branch.id}' alias rename from '${stored.alias}' to '${branch.alias}' is irreversible. Use PATCH /api/seeds/${slug}/branches/${branch.id}/rename with a typed confirmation.`,
151
- })
152
- }
153
- if (branch.type !== stored.type) {
154
- return publicProblem(context, {
155
- type: 'branch-type-change-not-supported',
156
- title: 'Branch type change not supported',
157
- status: 422,
158
- detail: `Branch '${branch.id}' type change from '${stored.type}' to '${branch.type}' is irreversible. Use PATCH /api/seeds/${slug}/branches/${branch.id}/retype with a typed confirmation.`,
159
- })
160
- }
161
- } else if (!branch.id) {
162
- const accSeed = { branches: incomingBranches.filter(b => b.id) }
163
- branch.id = nextBranchId(accSeed)
164
- }
165
- }
166
- return null
167
- }
168
-
169
- async function deleteSeedMediaObjects(context: any, slug: string, seed: Seed, schemaMutator: any) {
170
- const fileBranches = seed.branches.filter((b: Branch) => b.type === 'file')
171
- if (fileBranches.length === 0) return
172
-
173
- try {
174
- const dbCols = await schemaMutator.getColumns(`content_${slug}`)
175
- if (!dbCols) return
176
-
177
- const colList = fileBranches.map((b: Branch) => b.alias).filter((a: string) => dbCols.has(a)).join(', ')
178
- if (!colList) return
179
-
180
- const rs = await context.env.DB.prepare(`SELECT ${colList} FROM content_${slug}`).all()
181
- const rows = (rs.results as Record<string, unknown>[]) ?? []
182
- const r2Keys: string[] = []
183
-
184
- for (const row of rows) {
185
- for (const b of fileBranches) {
186
- const val = row[b.alias]
187
- if (!val) continue
188
- const keys = extractMediaKeysFromData(seed, { [b.alias]: val })
189
- r2Keys.push(...keys)
190
- }
191
- }
192
-
193
- if (r2Keys.length > 0) {
194
- await deleteR2Objects(context, r2Keys).catch(() => { /* non-fatal */ })
195
- }
196
- } catch { /* non-fatal: drop proceeds */ }
197
- }
198
-
199
- export const seedsApp = new Hono<{ Bindings: Env; Variables: Variables }>()
200
-
201
- seedsApp.use('*', async (context, next) => {
202
- const denied = requireAdmin(context)
203
- if (denied) return denied
204
- await next()
205
- })
206
-
207
- /** GET /api/seeds — list all seed records (active + deleted). Admin-only. */
208
- seedsApp.get('/', async (context) => {
209
- const records = await context.get('seedRepository').listAll()
210
- return context.json(records)
211
- })
212
-
213
- /** GET /api/seeds/:slug — single record by slug. Admin-only. */
214
- seedsApp.get('/:slug', async (context) => {
215
- const slug = context.req.param('slug')
216
- const record = await context.get('seedRepository').get(slug)
217
- if (!record) {
218
- return publicProblem(context, {
219
- type: 'seed-not-found',
220
- title: 'Seed not found',
221
- status: 404,
222
- detail: `No seed with slug '${slug}'.`,
223
- })
224
- }
225
- return context.json(record)
226
- })
227
-
228
- /** POST /api/seeds — create a new content type. */
229
- seedsApp.post('/', async (context) => {
230
- const body = await parseJsonBody(context)
231
- if (body instanceof Response) return body
232
-
233
- const candidate = body as Seed
234
- const slug = candidate?.slug
235
-
236
- if (!slug || !SLUG_RE.test(slug)) {
237
- return publicProblem(context, { type: 'invalid-json', title: 'Bad Request', status: 400, detail: `slug must match ${SLUG_RE.source}.` })
238
- }
239
-
240
- const repo = context.get('seedRepository')
241
- const existing = await repo.get(slug)
242
-
243
- if (existing?.status === 'active') {
244
- return publicProblem(context, {
245
- type: 'slug-conflict',
246
- title: 'Slug conflict',
247
- status: 409,
248
- detail: `An active seed with slug '${slug}' already exists.`,
249
- })
250
- }
251
-
252
- // Assign ids to branches that are missing one
253
- const accSeed: Pick<Seed, 'branches'> = { branches: [] }
254
- const branches: Branch[] = Array.isArray(candidate.branches) ? candidate.branches : []
255
- for (const branch of branches) {
256
- if (!branch.id) {
257
- branch.id = nextBranchId(accSeed)
258
- }
259
- accSeed.branches.push({ ...branch })
260
- }
261
-
262
- // Default displayNameAlias to first text branch if not provided
263
- if (!candidate.displayNameAlias) {
264
- const firstText = branches.find(b => b.type === 'text')
265
- if (!firstText) {
266
- return publicProblem(context, { type: 'invalid-json', title: 'Bad Request', status: 400, detail: 'displayNameAlias is required (or include at least one text branch so it can be inferred).' })
267
- }
268
- candidate.displayNameAlias = firstText.alias
269
- }
270
- candidate.branches = branches
271
-
272
- const error = await validateAndApplySeedDef(context, slug, candidate, 'create', { slug })
273
- if (error) return error
274
-
275
- return context.json({ slug }, 201)
276
- })
277
-
278
- /** PUT /api/seeds/:slug — replace definition (additive-only). */
279
- seedsApp.put('/:slug', async (context) => {
280
- const slug = context.req.param('slug')
281
- const body = await parseJsonBody(context)
282
- if (body instanceof Response) return body
283
-
284
- const existing = await getActiveSeed(context, slug)
285
- if (existing instanceof Response) return existing
286
-
287
- const incoming = body as Seed
288
- const storedBranches = existing.definition.branches
289
- const incomingBranches: Branch[] = Array.isArray(incoming.branches) ? incoming.branches : []
290
-
291
- const validationError = validateIncomingBranches(incomingBranches, storedBranches, slug, context)
292
- if (validationError) return validationError
293
-
294
- const candidate: Seed = { ...incoming, slug, branches: incomingBranches }
295
-
296
- const error = await validateAndApplySeedDef(context, slug, candidate, 'update', { slug })
297
- if (error) return error
298
-
299
- return context.json({ slug })
300
- })
301
-
302
- /** POST /api/seeds/:slug/branches — add a single branch. */
303
- seedsApp.post('/:slug/branches', async (context) => {
304
- const slug = context.req.param('slug')
305
- const body = await parseJsonBody(context)
306
- if (body instanceof Response) return body
307
-
308
- const existing = await getActiveSeed(context, slug)
309
- if (existing instanceof Response) return existing
310
-
311
- const newBranch = body as Branch
312
- newBranch.id = nextBranchId(existing.definition)
313
-
314
- const candidate: Seed = {
315
- ...existing.definition,
316
- branches: [...existing.definition.branches, newBranch],
317
- }
318
-
319
- const error = await validateAndApplySeedDef(context, slug, candidate, 'update', { addedBranch: newBranch.id })
320
- if (error) return error
321
-
322
- return context.json({ id: newBranch.id })
323
- })
324
-
325
- /** DELETE /api/seeds/:slug — soft-delete a content type. */
326
- seedsApp.delete('/:slug', async (context) => {
327
- const slug = context.req.param('slug')
328
- const repo = context.get('seedRepository')
329
-
330
- const existing = await getActiveSeed(context, slug)
331
- if (existing instanceof Response) return existing
332
-
333
- const backrefMap = context.get('backrefMap')
334
- const inbound = backrefMap.get(slug)
335
- if (inbound && inbound.length > 0) {
336
- const referencers = [...new Set(inbound.map((r: any) => r.sourceSlug))]
337
- return publicProblem(context, {
338
- type: 'seed-referenced',
339
- title: 'Seed referenced',
340
- status: 409,
341
- detail: `Seed '${slug}' is referenced by: ${referencers.join(', ')}. Remove those relations first.`,
342
- })
343
- }
344
-
345
- await repo.softDelete(slug)
346
- await repo.bumpRegistryVersion()
347
-
348
- const actor = actorFromContext(context)
349
- context.get('activityLogger').log({ action: 'delete', entityType: 'seed', entityId: slug, details: { slug }, actor })
350
-
351
- return context.json({ success: true })
352
- })
353
-
354
- /** GET /api/seeds/:slug/orphans — columns in DB but absent from the definition. */
355
- seedsApp.get('/:slug/orphans', async (context) => {
356
- const slug = context.req.param('slug')
357
- const existing = await getActiveSeed(context, slug)
358
- if (existing instanceof Response) return existing
359
-
360
- const schemaMutator = context.get('schemaMutator')
361
- const dbCols = await schemaMutator.getColumns(`content_${slug}`)
362
- if (!dbCols) return context.json({ orphans: [] })
363
-
364
- const knownAliases = new Set([
365
- 'id', 'slug', 'status', 'created_at', 'updated_at',
366
- ...existing.definition.branches
367
- .filter((b: Branch) => !(b.type === 'relation' && b.multiple === true))
368
- .map((b: Branch) => b.alias),
369
- ])
370
- const orphans = [...dbCols].filter((col: string) => !knownAliases.has(col))
371
- return context.json({ orphans })
372
- })
373
-
374
- /** POST /api/seeds/:slug/fts/rebuild — rebuild FTS table + triggers. */
375
- seedsApp.post('/:slug/fts/rebuild', async (context) => {
376
- const slug = context.req.param('slug')
377
- const existing = await getActiveSeed(context, slug)
378
- if (existing instanceof Response) return existing
379
-
380
- const stmts = planFtsRebuild(existing.definition)
381
- if (stmts.length > 0) {
382
- await context.get('schemaMutator').execDestructive(stmts)
383
- }
384
-
385
- const actor = actorFromContext(context)
386
- context.get('activityLogger').log({ action: 'update', entityType: 'seed', entityId: slug, details: { op: 'fts-rebuild' }, actor })
387
-
388
- return context.json({ success: true })
389
- })
390
-
391
- /** DELETE /api/seeds/:slug/hard — hard delete: drops tables + deletes row. */
392
- seedsApp.delete('/:slug/hard', async (context) => {
393
- const slug = context.req.param('slug')
394
- const body = await parseJsonBody(context)
395
- if (body instanceof Response) return body
396
-
397
- const confirmErr = requireConfirm(context, slug, body)
398
- if (confirmErr) return confirmErr
399
-
400
- const existing = await getActiveSeed(context, slug)
401
- if (existing instanceof Response) return existing
402
-
403
- const backrefMap = context.get('backrefMap')
404
- const inbound = backrefMap.get(slug)
405
- if (inbound && inbound.length > 0) {
406
- const referencers = [...new Set(inbound.map((r: any) => r.sourceSlug))]
407
- return publicProblem(context, {
408
- type: 'seed-referenced',
409
- title: 'Seed referenced',
410
- status: 409,
411
- detail: `Seed '${slug}' is referenced by: ${referencers.join(', ')}. Remove those relations first.`,
412
- })
413
- }
414
-
415
- const repo = context.get('seedRepository')
416
- const schemaMutator = context.get('schemaMutator')
417
- const seed = existing.definition
418
-
419
- await deleteSeedMediaObjects(context, slug, seed, schemaMutator)
420
-
421
- await schemaMutator.execDestructive(generateDropTable(seed))
422
- await repo.hardDelete(slug)
423
- await repo.bumpRegistryVersion()
424
-
425
- const actor = actorFromContext(context)
426
- context.get('activityLogger').log({ action: 'delete', entityType: 'seed', entityId: slug, details: { op: 'hard-delete', slug }, actor })
427
-
428
- return context.json({ success: true })
429
- })
430
-
431
- /** DELETE /api/seeds/:slug/branches/:branchId — drop a single field column. */
432
- seedsApp.delete('/:slug/branches/:branchId', async (context) => {
433
- const slug = context.req.param('slug')
434
- const branchId = context.req.param('branchId')
435
- const body = await parseJsonBody(context)
436
- if (body instanceof Response) return body
437
-
438
- const existing = await getActiveSeed(context, slug)
439
- if (existing instanceof Response) return existing
440
-
441
- const branch = existing.definition.branches.find((b: Branch) => b.id === branchId)
442
- if (!branch) {
443
- return publicProblem(context, { type: 'branch-not-found', title: 'Branch not found', status: 404, detail: `No branch with id '${branchId}' in seed '${slug}'.` })
444
- }
445
-
446
- const confirmErr = requireConfirm(context, `${slug}.${branch.alias}`, body)
447
- if (confirmErr) return confirmErr
448
-
449
- const updatedDef: Seed = {
450
- ...existing.definition,
451
- branches: existing.definition.branches.filter((b: Branch) => b.id !== branchId),
452
- }
453
-
454
- const stmts = generateDropColumn(existing.definition, branch.alias)
455
- const error = await applyDestructiveSeedDef(context, slug, updatedDef, stmts, { op: 'drop-branch', branchId, alias: branch.alias })
456
- if (error) return error
457
-
458
- return context.json({ success: true })
459
- })
460
-
461
- /** PATCH /api/seeds/:slug/branches/:branchId/rename — rename a field alias. */
462
- seedsApp.patch('/:slug/branches/:branchId/rename', async (context) => {
463
- const slug = context.req.param('slug')
464
- const branchId = context.req.param('branchId')
465
- const body = await parseJsonBody(context)
466
- if (body instanceof Response) return body
467
-
468
- const newAlias = (body as Record<string, unknown>)?.newAlias
469
- if (typeof newAlias !== 'string' || !BRANCH_ALIAS_RE.test(newAlias)) {
470
- return publicProblem(context, { type: 'invalid-json', title: 'Bad Request', status: 400, detail: `newAlias must match ${BRANCH_ALIAS_RE.source} (lowercase letter followed by alphanumeric characters or underscores).` })
471
- }
472
-
473
- const existing = await getActiveSeed(context, slug)
474
- if (existing instanceof Response) return existing
475
-
476
- const branch = existing.definition.branches.find((b: Branch) => b.id === branchId)
477
- if (!branch) {
478
- return publicProblem(context, { type: 'branch-not-found', title: 'Branch not found', status: 404, detail: `No branch with id '${branchId}' in seed '${slug}'.` })
479
- }
480
-
481
- const confirmErr = requireConfirm(context, `${slug}.${branch.alias}`, body)
482
- if (confirmErr) return confirmErr
483
-
484
- const renamedDef: Seed = {
485
- ...existing.definition,
486
- branches: existing.definition.branches.map((b: Branch) =>
487
- b.id === branchId ? { ...b, alias: newAlias } : b
488
- ),
489
- }
490
-
491
- const renameStmts = generateRenameColumn(existing.definition, branch.alias, newAlias)
492
- const ftsStmts = planFtsRebuild(renamedDef)
493
-
494
- const error = await applyDestructiveSeedDef(context, slug, renamedDef, [...renameStmts, ...ftsStmts], { op: 'rename-branch', branchId, from: branch.alias, to: newAlias })
495
- if (error) return error
496
-
497
- let affectedAutomations: string[] = []
498
- try {
499
- const automationRepo = context.get('automationRepository')
500
- if (automationRepo) {
501
- const automations = await automationRepo.list(slug)
502
- const oldAlias = branch.alias
503
- affectedAutomations = automations
504
- .filter((a: any) => JSON.stringify(a).includes(oldAlias))
505
- .map((a: any) => a.id)
506
- }
507
- } catch { /* non-fatal */ }
508
-
509
- return context.json({ success: true, affectedAutomations })
510
- })
511
-
512
- /** PATCH /api/seeds/:slug/branches/:branchId/retype — change field SQL type. */
513
- seedsApp.patch('/:slug/branches/:branchId/retype', async (context) => {
514
- const slug = context.req.param('slug')
515
- const branchId = context.req.param('branchId')
516
- const body = await parseJsonBody(context)
517
- if (body instanceof Response) return body
518
-
519
- const newType = (body as Record<string, unknown>)?.newType
520
- const VALID_TYPES = new Set(['text','number','boolean','date','json','richtext','file','tags','relation','repeater'])
521
- if (typeof newType !== 'string' || !VALID_TYPES.has(newType)) {
522
- return publicProblem(context, { type: 'invalid-json', title: 'Bad Request', status: 400, detail: `newType must be one of: ${[...VALID_TYPES].join(', ')}.` })
523
- }
524
-
525
- const existing = await getActiveSeed(context, slug)
526
- if (existing instanceof Response) return existing
527
-
528
- const branch = existing.definition.branches.find((b: Branch) => b.id === branchId)
529
- if (!branch) {
530
- return publicProblem(context, { type: 'branch-not-found', title: 'Branch not found', status: 404, detail: `No branch with id '${branchId}' in seed '${slug}'.` })
531
- }
532
-
533
- if (branch.type === 'repeater' || newType === 'repeater') {
534
- return publicProblem(context, {
535
- type: 'retype-not-supported',
536
- title: 'Retype not supported',
537
- status: 422,
538
- detail: "Retyping to or from 'repeater' is not supported in v1.",
539
- })
540
- }
541
-
542
- const confirmErr = requireConfirm(context, `${slug}.${branch.alias}`, body)
543
- if (confirmErr) return confirmErr
544
-
545
- const retypedBranch: Branch = { ...branch, type: newType as Branch['type'] }
546
- const retypedDef: Seed = {
547
- ...existing.definition,
548
- branches: existing.definition.branches.map((b: Branch) => b.id === branchId ? retypedBranch : b),
549
- }
550
-
551
- const retypeStmts = generateRetypeColumn(existing.definition, retypedBranch)
552
- const ftsStmts = planFtsRebuild(retypedDef)
553
-
554
- const error = await applyDestructiveSeedDef(context, slug, retypedDef, [...retypeStmts, ...ftsStmts], { op: 'retype-branch', branchId, from: branch.type, to: newType })
555
- if (error) return error
556
-
557
- return context.json({ success: true })
558
- })