emdash 1.3.0 → 1.4.0

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 (970) hide show
  1. package/dist/{activation-Dim4YMM7.mjs → activation-CGlyigmg.mjs} +3 -3
  2. package/dist/{activation-Dim4YMM7.mjs.map → activation-CGlyigmg.mjs.map} +1 -1
  3. package/dist/{adapters-F_xorvWv.d.mts → adapters-D9a1PfqH.d.mts} +1 -1
  4. package/dist/{adapters-F_xorvWv.d.mts.map → adapters-D9a1PfqH.d.mts.map} +1 -1
  5. package/dist/{after-PcEvMY_6.mjs → after-32yNRvd2.mjs} +5 -2
  6. package/dist/{after-PcEvMY_6.mjs.map → after-32yNRvd2.mjs.map} +1 -1
  7. package/dist/api/route-utils.d.mts +4 -4
  8. package/dist/api/route-utils.mjs +50 -49
  9. package/dist/api/route-utils.mjs.map +1 -1
  10. package/dist/api/schemas/index.d.mts +1 -1
  11. package/dist/api/schemas/index.mjs +7 -7
  12. package/dist/{api-D-7BNHoz.mjs → api--PYoyeTo.mjs} +121 -48
  13. package/dist/api--PYoyeTo.mjs.map +1 -0
  14. package/dist/api-tokens-DqSlkDVI.mjs +9 -0
  15. package/dist/api-tokens-DqSlkDVI.mjs.map +1 -0
  16. package/dist/{api-tokens-Bx38ZCQp.mjs → api-tokens-GNCsIegW.mjs} +3 -3
  17. package/dist/{api-tokens-Bx38ZCQp.mjs.map → api-tokens-GNCsIegW.mjs.map} +1 -1
  18. package/dist/{apply-T7ATgzCh.mjs → apply-DVnupNeS.mjs} +149 -91
  19. package/dist/apply-DVnupNeS.mjs.map +1 -0
  20. package/dist/{approvals-D-m6wvGl.mjs → approvals-DTtYSB27.mjs} +2 -2
  21. package/dist/{approvals-D-m6wvGl.mjs.map → approvals-DTtYSB27.mjs.map} +1 -1
  22. package/dist/{artifacts-htGyfCSt.mjs → artifacts-Dvdp2KK-.mjs} +29 -13
  23. package/dist/artifacts-Dvdp2KK-.mjs.map +1 -0
  24. package/dist/astro/image-endpoint.mjs +12 -2
  25. package/dist/astro/image-endpoint.mjs.map +1 -1
  26. package/dist/astro/index.d.mts +15 -14
  27. package/dist/astro/index.d.mts.map +1 -1
  28. package/dist/astro/index.mjs +59 -8
  29. package/dist/astro/index.mjs.map +1 -1
  30. package/dist/astro/middleware/auth.d.mts +13 -12
  31. package/dist/astro/middleware/auth.d.mts.map +1 -1
  32. package/dist/astro/middleware/auth.mjs +10 -8
  33. package/dist/astro/middleware/auth.mjs.map +1 -1
  34. package/dist/astro/middleware/media-usage-write-fence.mjs +5 -5
  35. package/dist/astro/middleware/redirect.d.mts.map +1 -1
  36. package/dist/astro/middleware/redirect.mjs +129 -49
  37. package/dist/astro/middleware/redirect.mjs.map +1 -1
  38. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  39. package/dist/astro/middleware/request-context.mjs +18 -9
  40. package/dist/astro/middleware/request-context.mjs.map +1 -1
  41. package/dist/astro/middleware.d.mts +13 -12
  42. package/dist/astro/middleware.d.mts.map +1 -1
  43. package/dist/astro/middleware.mjs +111 -101
  44. package/dist/astro/middleware.mjs.map +1 -1
  45. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +11 -11
  46. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +11 -11
  47. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +6 -6
  48. package/dist/astro/routes/api/admin/api-tokens/index.mjs +7 -7
  49. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -6
  50. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +14 -14
  51. package/dist/astro/routes/api/admin/byline-fields/index.mjs +14 -14
  52. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +14 -14
  53. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +25 -25
  54. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +25 -25
  55. package/dist/astro/routes/api/admin/bylines/index.mjs +25 -25
  56. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +17 -17
  57. package/dist/astro/routes/api/admin/comments/_id_.mjs +8 -8
  58. package/dist/astro/routes/api/admin/comments/bulk.mjs +16 -16
  59. package/dist/astro/routes/api/admin/comments/counts.mjs +8 -8
  60. package/dist/astro/routes/api/admin/comments/index.mjs +16 -16
  61. package/dist/astro/routes/api/admin/core-update.mjs +6 -6
  62. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
  63. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
  64. package/dist/astro/routes/api/admin/media-usage/activation.mjs +18 -18
  65. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +19 -19
  66. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +19 -19
  67. package/dist/astro/routes/api/admin/media-usage/progress.mjs +22 -21
  68. package/dist/astro/routes/api/admin/media-usage/progress.mjs.map +1 -1
  69. package/dist/astro/routes/api/admin/media-usage/repair.mjs +26 -25
  70. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
  71. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +19 -19
  72. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +19 -19
  73. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
  74. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
  75. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +7 -7
  76. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +7 -7
  77. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +66 -65
  78. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  79. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +5 -5
  80. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +66 -65
  81. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
  82. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +66 -65
  83. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  84. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +73 -72
  85. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  86. package/dist/astro/routes/api/admin/plugins/index.mjs +66 -65
  87. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  88. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -4
  89. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +66 -65
  90. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  91. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +73 -72
  92. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  93. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +66 -65
  94. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  95. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +66 -65
  96. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  97. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +74 -73
  98. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  99. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +4 -4
  100. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +68 -67
  101. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  102. package/dist/astro/routes/api/admin/plugins/registry/publisher-handle.mjs +4 -4
  103. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +67 -66
  104. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -1
  105. package/dist/astro/routes/api/admin/plugins/updates.mjs +66 -65
  106. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  107. package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.mjs +5 -5
  108. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +66 -65
  109. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  110. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +4 -4
  111. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +66 -65
  112. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  113. package/dist/astro/routes/api/admin/transfer/_...path_.mjs +3 -3
  114. package/dist/astro/routes/api/admin/transfer/approvals/_id_/approve.mjs +33 -32
  115. package/dist/astro/routes/api/admin/transfer/approvals/_id_/approve.mjs.map +1 -1
  116. package/dist/astro/routes/api/admin/transfer/approvals/_id_/deny.mjs +33 -32
  117. package/dist/astro/routes/api/admin/transfer/approvals/_id_/deny.mjs.map +1 -1
  118. package/dist/astro/routes/api/admin/transfer/approvals/index.mjs +35 -34
  119. package/dist/astro/routes/api/admin/transfer/approvals/index.mjs.map +1 -1
  120. package/dist/astro/routes/api/admin/transfer/capabilities.mjs +35 -34
  121. package/dist/astro/routes/api/admin/transfer/capabilities.mjs.map +1 -1
  122. package/dist/astro/routes/api/admin/transfer/exports/_id_/advance.mjs +35 -34
  123. package/dist/astro/routes/api/admin/transfer/exports/_id_/advance.mjs.map +1 -1
  124. package/dist/astro/routes/api/admin/transfer/exports/_id_/archive.mjs +36 -35
  125. package/dist/astro/routes/api/admin/transfer/exports/_id_/archive.mjs.map +1 -1
  126. package/dist/astro/routes/api/admin/transfer/exports/_id_/files/_...path_.mjs +36 -35
  127. package/dist/astro/routes/api/admin/transfer/exports/_id_/files/_...path_.mjs.map +1 -1
  128. package/dist/astro/routes/api/admin/transfer/exports/_id_/index.mjs +35 -34
  129. package/dist/astro/routes/api/admin/transfer/exports/_id_/index.mjs.map +1 -1
  130. package/dist/astro/routes/api/admin/transfer/exports/_id_/manifest.mjs +36 -35
  131. package/dist/astro/routes/api/admin/transfer/exports/_id_/manifest.mjs.map +1 -1
  132. package/dist/astro/routes/api/admin/transfer/exports/index.mjs +37 -36
  133. package/dist/astro/routes/api/admin/transfer/exports/index.mjs.map +1 -1
  134. package/dist/astro/routes/api/admin/transfer/imports/_id_/abandon.mjs +35 -34
  135. package/dist/astro/routes/api/admin/transfer/imports/_id_/abandon.mjs.map +1 -1
  136. package/dist/astro/routes/api/admin/transfer/imports/_id_/advance.mjs +35 -34
  137. package/dist/astro/routes/api/admin/transfer/imports/_id_/advance.mjs.map +1 -1
  138. package/dist/astro/routes/api/admin/transfer/imports/_id_/analyze.mjs +37 -36
  139. package/dist/astro/routes/api/admin/transfer/imports/_id_/analyze.mjs.map +1 -1
  140. package/dist/astro/routes/api/admin/transfer/imports/_id_/cancel.mjs +35 -34
  141. package/dist/astro/routes/api/admin/transfer/imports/_id_/cancel.mjs.map +1 -1
  142. package/dist/astro/routes/api/admin/transfer/imports/_id_/execute.mjs +37 -36
  143. package/dist/astro/routes/api/admin/transfer/imports/_id_/execute.mjs.map +1 -1
  144. package/dist/astro/routes/api/admin/transfer/imports/_id_/files/_...path_.mjs +35 -34
  145. package/dist/astro/routes/api/admin/transfer/imports/_id_/files/_...path_.mjs.map +1 -1
  146. package/dist/astro/routes/api/admin/transfer/imports/_id_/index.mjs +35 -34
  147. package/dist/astro/routes/api/admin/transfer/imports/_id_/index.mjs.map +1 -1
  148. package/dist/astro/routes/api/admin/transfer/imports/_id_/missing.mjs +37 -36
  149. package/dist/astro/routes/api/admin/transfer/imports/_id_/missing.mjs.map +1 -1
  150. package/dist/astro/routes/api/admin/transfer/imports/_id_/plan.mjs +35 -34
  151. package/dist/astro/routes/api/admin/transfer/imports/_id_/plan.mjs.map +1 -1
  152. package/dist/astro/routes/api/admin/transfer/imports/_id_/receipt.mjs +35 -34
  153. package/dist/astro/routes/api/admin/transfer/imports/_id_/receipt.mjs.map +1 -1
  154. package/dist/astro/routes/api/admin/transfer/imports/index.mjs +37 -36
  155. package/dist/astro/routes/api/admin/transfer/imports/index.mjs.map +1 -1
  156. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +4 -4
  157. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +3 -3
  158. package/dist/astro/routes/api/admin/users/_id_/index.mjs +12 -12
  159. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
  160. package/dist/astro/routes/api/admin/users/index.mjs +11 -11
  161. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  162. package/dist/astro/routes/api/auth/handover.mjs +5 -5
  163. package/dist/astro/routes/api/auth/invite/accept.mjs +3 -3
  164. package/dist/astro/routes/api/auth/invite/complete.mjs +15 -15
  165. package/dist/astro/routes/api/auth/invite/index.mjs +12 -12
  166. package/dist/astro/routes/api/auth/invite/register-options.mjs +13 -13
  167. package/dist/astro/routes/api/auth/logout.mjs +3 -3
  168. package/dist/astro/routes/api/auth/magic-link/send.mjs +14 -14
  169. package/dist/astro/routes/api/auth/magic-link/verify.mjs +13 -13
  170. package/dist/astro/routes/api/auth/me.mjs +12 -12
  171. package/dist/astro/routes/api/auth/mode.mjs +3 -3
  172. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +8 -8
  173. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +1 -1
  174. package/dist/astro/routes/api/auth/passkey/_id_.mjs +11 -11
  175. package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
  176. package/dist/astro/routes/api/auth/passkey/options.mjs +15 -15
  177. package/dist/astro/routes/api/auth/passkey/register/options.mjs +13 -13
  178. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +13 -13
  179. package/dist/astro/routes/api/auth/passkey/verify.mjs +15 -15
  180. package/dist/astro/routes/api/auth/signup/complete.mjs +15 -15
  181. package/dist/astro/routes/api/auth/signup/request.mjs +13 -13
  182. package/dist/astro/routes/api/auth/signup/verify.mjs +3 -3
  183. package/dist/astro/routes/api/calendar.mjs +12 -12
  184. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +12 -12
  185. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +17 -17
  186. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +4 -4
  187. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +15 -15
  188. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +4 -4
  189. package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs +15 -15
  190. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +4 -4
  191. package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.mjs +9 -9
  192. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +29 -28
  193. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
  194. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +15 -15
  195. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +34 -33
  196. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  197. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +34 -33
  198. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  199. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +4 -4
  200. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +4 -4
  201. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +15 -15
  202. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts.map +1 -1
  203. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +35 -27
  204. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  205. package/dist/astro/routes/api/content/_collection_/_id_/translations.d.mts.map +1 -1
  206. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +8 -4
  207. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs.map +1 -1
  208. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +15 -15
  209. package/dist/astro/routes/api/content/_collection_/_id_.mjs +15 -15
  210. package/dist/astro/routes/api/content/_collection_/authors.mjs +4 -4
  211. package/dist/astro/routes/api/content/_collection_/index.mjs +12 -12
  212. package/dist/astro/routes/api/content/_collection_/trash.mjs +12 -12
  213. package/dist/astro/routes/api/dashboard.mjs +22 -16
  214. package/dist/astro/routes/api/dashboard.mjs.map +1 -1
  215. package/dist/astro/routes/api/dev/emails.mjs +3 -3
  216. package/dist/astro/routes/api/dev/scheduled-tasks.mjs +97 -95
  217. package/dist/astro/routes/api/dev/scheduled-tasks.mjs.map +1 -1
  218. package/dist/astro/routes/api/health.mjs +4 -4
  219. package/dist/astro/routes/api/import/probe.d.mts +4 -3
  220. package/dist/astro/routes/api/import/probe.d.mts.map +1 -1
  221. package/dist/astro/routes/api/import/probe.mjs +27 -25
  222. package/dist/astro/routes/api/import/probe.mjs.map +1 -1
  223. package/dist/astro/routes/api/import/wordpress/analyze.d.mts +4 -3
  224. package/dist/astro/routes/api/import/wordpress/analyze.d.mts.map +1 -1
  225. package/dist/astro/routes/api/import/wordpress/analyze.mjs +7 -29
  226. package/dist/astro/routes/api/import/wordpress/analyze.mjs.map +1 -1
  227. package/dist/astro/routes/api/import/wordpress/execute.d.mts +14 -13
  228. package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
  229. package/dist/astro/routes/api/import/wordpress/execute.mjs +29 -29
  230. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  231. package/dist/astro/routes/api/import/wordpress/media.d.mts +13 -12
  232. package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
  233. package/dist/astro/routes/api/import/wordpress/media.mjs +14 -14
  234. package/dist/astro/routes/api/import/wordpress/prepare.mjs +19 -19
  235. package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
  236. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +14 -13
  237. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts.map +1 -1
  238. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +25 -24
  239. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  240. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  241. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +27 -25
  242. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
  243. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +14 -13
  244. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts.map +1 -1
  245. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +39 -38
  246. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  247. package/dist/astro/routes/api/manifest.mjs +4 -4
  248. package/dist/astro/routes/api/mcp.mjs +99 -83
  249. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  250. package/dist/astro/routes/api/media/_id_/confirm.mjs +16 -16
  251. package/dist/astro/routes/api/media/_id_/replace.mjs +12 -12
  252. package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
  253. package/dist/astro/routes/api/media/_id_/usage.mjs +28 -27
  254. package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
  255. package/dist/astro/routes/api/media/_id_.mjs +26 -25
  256. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  257. package/dist/astro/routes/api/media/asset/_id_/_filename_.mjs +5 -5
  258. package/dist/astro/routes/api/media/file/_...key_.d.mts.map +1 -1
  259. package/dist/astro/routes/api/media/file/_...key_.mjs +62 -8
  260. package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
  261. package/dist/astro/routes/api/media/folders/_id_.mjs +13 -13
  262. package/dist/astro/routes/api/media/folders/index.mjs +13 -13
  263. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
  264. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
  265. package/dist/astro/routes/api/media/providers/index.mjs +4 -4
  266. package/dist/astro/routes/api/media/upload-url.mjs +15 -15
  267. package/dist/astro/routes/api/media.mjs +30 -29
  268. package/dist/astro/routes/api/media.mjs.map +1 -1
  269. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +17 -15
  270. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
  271. package/dist/astro/routes/api/menus/_name_/items.mjs +17 -15
  272. package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
  273. package/dist/astro/routes/api/menus/_name_/reorder.mjs +17 -15
  274. package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
  275. package/dist/astro/routes/api/menus/_name_/translations.mjs +17 -15
  276. package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
  277. package/dist/astro/routes/api/menus/_name_.mjs +17 -15
  278. package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
  279. package/dist/astro/routes/api/menus/index.mjs +17 -15
  280. package/dist/astro/routes/api/menus/index.mjs.map +1 -1
  281. package/dist/astro/routes/api/oauth/authorize.mjs +2 -2
  282. package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
  283. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  284. package/dist/astro/routes/api/oauth/device/token.mjs +8 -8
  285. package/dist/astro/routes/api/oauth/register.mjs +5 -5
  286. package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
  287. package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
  288. package/dist/astro/routes/api/oauth/token.d.mts.map +1 -1
  289. package/dist/astro/routes/api/oauth/token.mjs +7 -7
  290. package/dist/astro/routes/api/oauth/token.mjs.map +1 -1
  291. package/dist/astro/routes/api/openapi.json.mjs +12 -12
  292. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +9 -9
  293. package/dist/astro/routes/api/redirects/404s/index.mjs +20 -19
  294. package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
  295. package/dist/astro/routes/api/redirects/404s/summary.mjs +20 -19
  296. package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
  297. package/dist/astro/routes/api/redirects/_id_.mjs +21 -20
  298. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  299. package/dist/astro/routes/api/redirects/index.mjs +21 -20
  300. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  301. package/dist/astro/routes/api/relations/_id_/index.mjs +34 -33
  302. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  303. package/dist/astro/routes/api/relations/index.mjs +34 -33
  304. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  305. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
  306. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +15 -15
  307. package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs +66 -65
  308. package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs.map +1 -1
  309. package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs +66 -65
  310. package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs.map +1 -1
  311. package/dist/astro/routes/api/schema/block-types/index.mjs +66 -65
  312. package/dist/astro/routes/api/schema/block-types/index.mjs.map +1 -1
  313. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +66 -65
  314. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  315. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +66 -65
  316. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  317. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +66 -65
  318. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  319. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +66 -65
  320. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  321. package/dist/astro/routes/api/schema/collections/index.mjs +66 -65
  322. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  323. package/dist/astro/routes/api/schema/collections/reorder.mjs +66 -65
  324. package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
  325. package/dist/astro/routes/api/schema/index.mjs +17 -16
  326. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  327. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +66 -65
  328. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  329. package/dist/astro/routes/api/schema/orphans/index.mjs +66 -65
  330. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  331. package/dist/astro/routes/api/search/enable.mjs +20 -18
  332. package/dist/astro/routes/api/search/enable.mjs.map +1 -1
  333. package/dist/astro/routes/api/search/index.mjs +19 -17
  334. package/dist/astro/routes/api/search/index.mjs.map +1 -1
  335. package/dist/astro/routes/api/search/rebuild.mjs +20 -18
  336. package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
  337. package/dist/astro/routes/api/search/stats.mjs +13 -11
  338. package/dist/astro/routes/api/search/stats.mjs.map +1 -1
  339. package/dist/astro/routes/api/search/suggest.mjs +19 -17
  340. package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
  341. package/dist/astro/routes/api/sections/_slug_.mjs +19 -17
  342. package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
  343. package/dist/astro/routes/api/sections/index.mjs +19 -17
  344. package/dist/astro/routes/api/sections/index.mjs.map +1 -1
  345. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +7 -7
  346. package/dist/astro/routes/api/settings/backups/archives/index.mjs +7 -7
  347. package/dist/astro/routes/api/settings/backups/export.mjs +7 -7
  348. package/dist/astro/routes/api/settings/backups/index.mjs +8 -8
  349. package/dist/astro/routes/api/settings/domain-notify.mjs +7 -7
  350. package/dist/astro/routes/api/settings/domain.mjs +19 -17
  351. package/dist/astro/routes/api/settings/domain.mjs.map +1 -1
  352. package/dist/astro/routes/api/settings/email.mjs +5 -5
  353. package/dist/astro/routes/api/settings.mjs +25 -23
  354. package/dist/astro/routes/api/settings.mjs.map +1 -1
  355. package/dist/astro/routes/api/setup/admin-verify.mjs +18 -18
  356. package/dist/astro/routes/api/setup/admin.mjs +14 -14
  357. package/dist/astro/routes/api/setup/dev-bypass.mjs +45 -44
  358. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  359. package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
  360. package/dist/astro/routes/api/setup/index.mjs +48 -47
  361. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  362. package/dist/astro/routes/api/setup/status.mjs +4 -4
  363. package/dist/astro/routes/api/site/domain-proof.mjs +17 -15
  364. package/dist/astro/routes/api/site/domain-proof.mjs.map +1 -1
  365. package/dist/astro/routes/api/snapshot.mjs +9 -9
  366. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +25 -24
  367. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
  368. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +25 -24
  369. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  370. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +25 -24
  371. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  372. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +25 -24
  373. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  374. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +25 -24
  375. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs.map +1 -1
  376. package/dist/astro/routes/api/taxonomies/_name_.mjs +25 -24
  377. package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -1
  378. package/dist/astro/routes/api/taxonomies/bulk-tag.mjs +35 -34
  379. package/dist/astro/routes/api/taxonomies/bulk-tag.mjs.map +1 -1
  380. package/dist/astro/routes/api/taxonomies/index.mjs +25 -24
  381. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  382. package/dist/astro/routes/api/themes/preview.mjs +6 -6
  383. package/dist/astro/routes/api/typegen.mjs +5 -5
  384. package/dist/astro/routes/api/visual-editing/action-token.mjs +5 -5
  385. package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.mjs +17 -17
  386. package/dist/astro/routes/api/visual-editing/toolbar-labels.mjs +3 -3
  387. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +1 -1
  388. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +1 -1
  389. package/dist/astro/routes/api/widget-areas/_name_/reorder.d.mts.map +1 -1
  390. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +24 -13
  391. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  392. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.d.mts.map +1 -1
  393. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +22 -17
  394. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  395. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +21 -17
  396. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  397. package/dist/astro/routes/api/widget-areas/_name_.mjs +14 -10
  398. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  399. package/dist/astro/routes/api/widget-areas/index.mjs +21 -17
  400. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  401. package/dist/astro/routes/api/widget-components.mjs +3 -3
  402. package/dist/astro/routes/robots.txt.mjs +16 -14
  403. package/dist/astro/routes/robots.txt.mjs.map +1 -1
  404. package/dist/astro/routes/sitemap-_collection_.xml.mjs +18 -16
  405. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  406. package/dist/astro/routes/sitemap.xml.mjs +16 -14
  407. package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
  408. package/dist/astro/types.d.mts +22 -20
  409. package/dist/astro/types.d.mts.map +1 -1
  410. package/dist/{audit-_gieLVGU.mjs → audit-B8XE-0UV.mjs} +2 -2
  411. package/dist/{audit-_gieLVGU.mjs.map → audit-B8XE-0UV.mjs.map} +1 -1
  412. package/dist/auth/providers/github.d.mts +1 -1
  413. package/dist/auth/providers/google.d.mts +1 -1
  414. package/dist/auth/providers/microsoft.d.mts +1 -1
  415. package/dist/{authorize-DPFBGbE6.mjs → authorize-NJgp8kLI.mjs} +2 -2
  416. package/dist/{authorize-DPFBGbE6.mjs.map → authorize-NJgp8kLI.mjs.map} +1 -1
  417. package/dist/{backup-3FgVxiDJ.mjs → backup-8rPSJ28a.mjs} +4 -4
  418. package/dist/{backup-3FgVxiDJ.mjs.map → backup-8rPSJ28a.mjs.map} +1 -1
  419. package/dist/{block-types-CYcG56JB.mjs → block-types-BbOrfnWs.mjs} +5 -5
  420. package/dist/{block-types-CYcG56JB.mjs.map → block-types-BbOrfnWs.mjs.map} +1 -1
  421. package/dist/{block-values-gCX4HBSl.mjs → block-values-Dg0-8PPS.mjs} +4 -4
  422. package/dist/{block-values-gCX4HBSl.mjs.map → block-values-Dg0-8PPS.mjs.map} +1 -1
  423. package/dist/{byline-BR1qhyiw.mjs → byline-DHJS-GGb.mjs} +123 -38
  424. package/dist/byline-DHJS-GGb.mjs.map +1 -0
  425. package/dist/{byline-fields-BLXZnX7N.mjs → byline-fields-CiR8FvvF.mjs} +3 -3
  426. package/dist/{byline-fields-BLXZnX7N.mjs.map → byline-fields-CiR8FvvF.mjs.map} +1 -1
  427. package/dist/{byline-fields-4ClDC88c.mjs → byline-fields-D6KYsEa7.mjs} +2 -2
  428. package/dist/{byline-fields-4ClDC88c.mjs.map → byline-fields-D6KYsEa7.mjs.map} +1 -1
  429. package/dist/{byline-registry-dvt4N3xV.mjs → byline-registry-BhzpO41z.mjs} +3 -3
  430. package/dist/{byline-registry-dvt4N3xV.mjs.map → byline-registry-BhzpO41z.mjs.map} +1 -1
  431. package/dist/{bylines-CVOvRUL7.mjs → bylines-Bsg5cH4S.mjs} +7 -7
  432. package/dist/bylines-Bsg5cH4S.mjs.map +1 -0
  433. package/dist/{bylines-DxOs9B7z.mjs → bylines-f_ZEc6Sn.mjs} +8 -8
  434. package/dist/{bylines-DxOs9B7z.mjs.map → bylines-f_ZEc6Sn.mjs.map} +1 -1
  435. package/dist/{cache-CEU9wo_h.mjs → cache-5DHbpEWu.mjs} +4 -4
  436. package/dist/{cache-CEU9wo_h.mjs.map → cache-5DHbpEWu.mjs.map} +1 -1
  437. package/dist/{capture-triggers-CT6dK9zd.mjs → capture-triggers-BezAvWcB.mjs} +2 -2
  438. package/dist/{capture-triggers-CT6dK9zd.mjs.map → capture-triggers-BezAvWcB.mjs.map} +1 -1
  439. package/dist/{challenge-store-C17BYx2-.mjs → challenge-store-B1C0V4b3.mjs} +1 -1
  440. package/dist/{challenge-store-C17BYx2-.mjs.map → challenge-store-B1C0V4b3.mjs.map} +1 -1
  441. package/dist/{chunks-xC4UD5-S.mjs → chunks-DiW8VMwz.mjs} +2 -2
  442. package/dist/{chunks-xC4UD5-S.mjs.map → chunks-DiW8VMwz.mjs.map} +1 -1
  443. package/dist/cli/index.mjs +53 -52
  444. package/dist/cli/index.mjs.map +1 -1
  445. package/dist/client/cf-access.d.mts +1 -1
  446. package/dist/client/index.d.mts +61 -61
  447. package/dist/client/index.d.mts.map +1 -1
  448. package/dist/client/index.mjs +3 -2
  449. package/dist/client/index.mjs.map +1 -1
  450. package/dist/{collection-slugs-cache-e09QeJQt.mjs → collection-slugs-cache-DIrLjqH-.mjs} +20 -14
  451. package/dist/collection-slugs-cache-DIrLjqH-.mjs.map +1 -0
  452. package/dist/{comment-UvIWDKcW.mjs → comment-DB74Zpa4.mjs} +18 -8
  453. package/dist/comment-DB74Zpa4.mjs.map +1 -0
  454. package/dist/{comment-reaction-dGp6_FRo.mjs → comment-reaction-BScxIWUn.mjs} +2 -2
  455. package/dist/{comment-reaction-dGp6_FRo.mjs.map → comment-reaction-BScxIWUn.mjs.map} +1 -1
  456. package/dist/{comments-pej-_BUO.mjs → comments-Cor_zKml.mjs} +3 -3
  457. package/dist/{comments-pej-_BUO.mjs.map → comments-Cor_zKml.mjs.map} +1 -1
  458. package/dist/config-Cs_AwWXI.mjs +106 -0
  459. package/dist/config-Cs_AwWXI.mjs.map +1 -0
  460. package/dist/{config-CFMojMFK.d.mts → config-D44_8G9J.d.mts} +3 -1
  461. package/dist/config-D44_8G9J.d.mts.map +1 -0
  462. package/dist/{config-Bsm_u_oE.d.mts → config-sDhZvkLR.d.mts} +56 -20
  463. package/dist/config-sDhZvkLR.d.mts.map +1 -0
  464. package/dist/{content-DQSRZTVw.mjs → content-BKHUVsE5.mjs} +67 -30
  465. package/dist/content-BKHUVsE5.mjs.map +1 -0
  466. package/dist/{content-list-query-CYKcY6Vi.d.mts → content-list-query-ByP4jRpZ.d.mts} +1 -1
  467. package/dist/{content-list-query-CYKcY6Vi.d.mts.map → content-list-query-ByP4jRpZ.d.mts.map} +1 -1
  468. package/dist/{content-policy-Dm32n9C6.mjs → content-policy-BmE0MPZY.mjs} +1 -1
  469. package/dist/{content-policy-Dm32n9C6.mjs.map → content-policy-BmE0MPZY.mjs.map} +1 -1
  470. package/dist/{content-repair-DNh5--x5.mjs → content-repair-Ukdxoq-R.mjs} +4 -4
  471. package/dist/{content-repair-DNh5--x5.mjs.map → content-repair-Ukdxoq-R.mjs.map} +1 -1
  472. package/dist/{context-BYptmCN0.mjs → context-CzjejcsE.mjs} +32 -33
  473. package/dist/context-CzjejcsE.mjs.map +1 -0
  474. package/dist/{credits-B9iORdWB.mjs → credits-BKREepht.mjs} +3 -3
  475. package/dist/{credits-B9iORdWB.mjs.map → credits-BKREepht.mjs.map} +1 -1
  476. package/dist/{dashboard-D0jcgBD7.mjs → dashboard-ynr1qJWM.mjs} +10 -9
  477. package/dist/dashboard-ynr1qJWM.mjs.map +1 -0
  478. package/dist/database/migration-lock.mjs +1 -1
  479. package/dist/db/index.d.mts +3 -3
  480. package/dist/db/index.mjs +3 -3
  481. package/dist/db/libsql-migrations.d.mts +1 -1
  482. package/dist/db/libsql-migrations.mjs +5 -5
  483. package/dist/db/libsql.d.mts +1 -1
  484. package/dist/db/postgres-migrations.d.mts +1 -1
  485. package/dist/db/postgres-migrations.mjs +5 -5
  486. package/dist/db/postgres.d.mts +1 -1
  487. package/dist/db/sqlite-migrations.d.mts +1 -1
  488. package/dist/db/sqlite-migrations.mjs +5 -5
  489. package/dist/db/sqlite.d.mts +1 -1
  490. package/dist/{db-errors-CBD5yMro.mjs → db-errors-unnO6yiZ.mjs} +12 -2
  491. package/dist/db-errors-unnO6yiZ.mjs.map +1 -0
  492. package/dist/deferred-tasks-wlMFJL95.d.mts +10 -0
  493. package/dist/deferred-tasks-wlMFJL95.d.mts.map +1 -0
  494. package/dist/{destination-r2zIAZ2S.mjs → destination-DR3IHjmy.mjs} +1 -1
  495. package/dist/{destination-r2zIAZ2S.mjs.map → destination-DR3IHjmy.mjs.map} +1 -1
  496. package/dist/{device-flow-BkFKBoAn.mjs → device-flow-D1U392GC.mjs} +3 -5
  497. package/dist/device-flow-D1U392GC.mjs.map +1 -0
  498. package/dist/{dialect-helpers-7_IqPdh9.mjs → dialect-helpers-B1XN2HGB.mjs} +10 -3
  499. package/dist/dialect-helpers-B1XN2HGB.mjs.map +1 -0
  500. package/dist/{direct-executor-Cbxzg4qe.mjs → direct-executor-Bs2g0ake.mjs} +4 -4
  501. package/dist/{direct-executor-Cbxzg4qe.mjs.map → direct-executor-Bs2g0ake.mjs.map} +1 -1
  502. package/dist/{dist-Cfu8mMBs.mjs → dist-BUmWMTbN.mjs} +1 -1
  503. package/dist/{dist-Cfu8mMBs.mjs.map → dist-BUmWMTbN.mjs.map} +1 -1
  504. package/dist/{email-locale-BJ8DjTzZ.mjs → email-locale-Bs3HFdnq.mjs} +1 -1
  505. package/dist/{email-locale-BJ8DjTzZ.mjs.map → email-locale-Bs3HFdnq.mjs.map} +1 -1
  506. package/dist/{emdash-runtime-DVi7vtXC.mjs → emdash-runtime-C8T-pZpS.mjs} +108 -62
  507. package/dist/emdash-runtime-C8T-pZpS.mjs.map +1 -0
  508. package/dist/{enrich-Duu4sV3a.mjs → enrich-DFlfnd3u.mjs} +3 -3
  509. package/dist/{enrich-Duu4sV3a.mjs.map → enrich-DFlfnd3u.mjs.map} +1 -1
  510. package/dist/{entry-lock-C7VX0NGK.d.mts → entry-lock-BulbgPBX.d.mts} +23 -23
  511. package/dist/{entry-lock-C7VX0NGK.d.mts.map → entry-lock-BulbgPBX.d.mts.map} +1 -1
  512. package/dist/{entry-lock-Df6-XhIu.mjs → entry-lock-CRVmruM0.mjs} +4 -4
  513. package/dist/{entry-lock-Df6-XhIu.mjs.map → entry-lock-CRVmruM0.mjs.map} +1 -1
  514. package/dist/{entry-lock-DLxCffFW.mjs → entry-lock-D-zJQ4q6.mjs} +3 -3
  515. package/dist/{entry-lock-DLxCffFW.mjs.map → entry-lock-D-zJQ4q6.mjs.map} +1 -1
  516. package/dist/{entry-locks-DJAqVweV.mjs → entry-locks-CJ9uhqg1.mjs} +2 -2
  517. package/dist/{entry-locks-DJAqVweV.mjs.map → entry-locks-CJ9uhqg1.mjs.map} +1 -1
  518. package/dist/{env-types-C3FCVBWe.mjs → env-types-BE3bCvfP.mjs} +5 -5
  519. package/dist/{env-types-C3FCVBWe.mjs.map → env-types-BE3bCvfP.mjs.map} +1 -1
  520. package/dist/{error-CVWeIOku.mjs → error-BCMgPxdn.mjs} +3 -3
  521. package/dist/{error-CVWeIOku.mjs.map → error-BCMgPxdn.mjs.map} +1 -1
  522. package/dist/{errors-BOYNdIo3.mjs → errors-8gJqv-XV.mjs} +15 -2
  523. package/dist/{errors-BOYNdIo3.mjs.map → errors-8gJqv-XV.mjs.map} +1 -1
  524. package/dist/{fence-C3MUitUN.mjs → fence-Dagicj14.mjs} +4 -4
  525. package/dist/{fence-C3MUitUN.mjs.map → fence-Dagicj14.mjs.map} +1 -1
  526. package/dist/{field-map-Bft9LKak.mjs → field-map-BSXdNGt5.mjs} +11 -10
  527. package/dist/field-map-BSXdNGt5.mjs.map +1 -0
  528. package/dist/{focal-point-JX8kcOTQ.mjs → focal-point-CgCcr1Ca.mjs} +1 -1
  529. package/dist/{focal-point-JX8kcOTQ.mjs.map → focal-point-CgCcr1Ca.mjs.map} +1 -1
  530. package/dist/{fts-manager-Beg3e4bI.mjs → fts-manager-6861qcwk.mjs} +68 -22
  531. package/dist/fts-manager-6861qcwk.mjs.map +1 -0
  532. package/dist/{gc-ByyAYiYE.mjs → gc-CNkXB3mE.mjs} +8 -8
  533. package/dist/{gc-ByyAYiYE.mjs.map → gc-CNkXB3mE.mjs.map} +1 -1
  534. package/dist/{hook-result-BrdLyOdI.d.mts → hook-result-f8t7hIuu.d.mts} +1 -1
  535. package/dist/{hook-result-BrdLyOdI.d.mts.map → hook-result-f8t7hIuu.d.mts.map} +1 -1
  536. package/dist/{hreflang-CY_F5OjF.d.mts → hreflang-8dUOJAPJ.d.mts} +2 -2
  537. package/dist/{hreflang-CY_F5OjF.d.mts.map → hreflang-8dUOJAPJ.d.mts.map} +1 -1
  538. package/dist/{hreflang-C8Jb-hrf.mjs → hreflang-BW5A4FLK.mjs} +8 -8
  539. package/dist/{hreflang-C8Jb-hrf.mjs.map → hreflang-BW5A4FLK.mjs.map} +1 -1
  540. package/dist/{http-route-dispatch-CyeW6iPv.mjs → http-route-dispatch-rXKvTNm9.mjs} +5 -5
  541. package/dist/{http-route-dispatch-CyeW6iPv.mjs.map → http-route-dispatch-rXKvTNm9.mjs.map} +1 -1
  542. package/dist/{identity-Cael5uTT.mjs → identity-CPRRSovU.mjs} +3 -3
  543. package/dist/{identity-Cael5uTT.mjs.map → identity-CPRRSovU.mjs.map} +1 -1
  544. package/dist/{import-BUXjdZUn.mjs → import-yVbfPyfd.mjs} +5 -5
  545. package/dist/{import-BUXjdZUn.mjs.map → import-yVbfPyfd.mjs.map} +1 -1
  546. package/dist/index-VM7Fzxwe.d.mts +260 -0
  547. package/dist/index-VM7Fzxwe.d.mts.map +1 -0
  548. package/dist/index.d.mts +25 -24
  549. package/dist/index.mjs +89 -88
  550. package/dist/{install-finalization-3JX5qQEA.mjs → install-finalization-RU6VrHOX.mjs} +1 -1
  551. package/dist/{install-finalization-3JX5qQEA.mjs.map → install-finalization-RU6VrHOX.mjs.map} +1 -1
  552. package/dist/{json-recordset-BUjxhr77.mjs → json-recordset-D0WhN2UA.mjs} +2 -2
  553. package/dist/{json-recordset-BUjxhr77.mjs.map → json-recordset-D0WhN2UA.mjs.map} +1 -1
  554. package/dist/{lifecycle-7vUJeFRe.mjs → lifecycle-B8d7Rdqx.mjs} +1 -1
  555. package/dist/{lifecycle-7vUJeFRe.mjs.map → lifecycle-B8d7Rdqx.mjs.map} +1 -1
  556. package/dist/{load-CdAG9wz-.mjs → load-D4xtzm6h.mjs} +2 -2
  557. package/dist/{load-CdAG9wz-.mjs.map → load-D4xtzm6h.mjs.map} +1 -1
  558. package/dist/{loader-CswroUbe.mjs → loader-DhnedqY4.mjs} +199 -101
  559. package/dist/loader-DhnedqY4.mjs.map +1 -0
  560. package/dist/{loops-uA4iTOzd.mjs → loops-B2YS-Zzr.mjs} +2 -2
  561. package/dist/{loops-uA4iTOzd.mjs.map → loops-B2YS-Zzr.mjs.map} +1 -1
  562. package/dist/{manifest-builder-B8zjuH3f.mjs → manifest-builder-g1FLviel.mjs} +10 -3
  563. package/dist/manifest-builder-g1FLviel.mjs.map +1 -0
  564. package/dist/media/image-endpoint.d.mts +3 -2
  565. package/dist/media/image-endpoint.d.mts.map +1 -1
  566. package/dist/media/image-endpoint.mjs +17 -8
  567. package/dist/media/image-endpoint.mjs.map +1 -1
  568. package/dist/media/index.d.mts +1 -1
  569. package/dist/media/index.mjs +7 -6
  570. package/dist/media/index.mjs.map +1 -1
  571. package/dist/media/local-runtime.d.mts +15 -14
  572. package/dist/media/local-runtime.d.mts.map +1 -1
  573. package/dist/media/local-runtime.mjs +20 -15
  574. package/dist/media/local-runtime.mjs.map +1 -1
  575. package/dist/{media-1WC65OCB.mjs → media-BR4_t-Wd.mjs} +3 -3
  576. package/dist/{media-1WC65OCB.mjs.map → media-BR4_t-Wd.mjs.map} +1 -1
  577. package/dist/{media--dJRU6i2.mjs → media-CikS-q6E.mjs} +4 -4
  578. package/dist/{media--dJRU6i2.mjs.map → media-CikS-q6E.mjs.map} +1 -1
  579. package/dist/{media-Bws9f18T.mjs → media-DepNLhPq.mjs} +14 -4
  580. package/dist/media-DepNLhPq.mjs.map +1 -0
  581. package/dist/{media-allowlist-BJB7MFx6.mjs → media-allowlist-BMBLqyWh.mjs} +2 -2
  582. package/dist/{media-allowlist-BJB7MFx6.mjs.map → media-allowlist-BMBLqyWh.mjs.map} +1 -1
  583. package/dist/{media-folders-Dn3C16B8.mjs → media-folders-CI0Wydfo.mjs} +2 -2
  584. package/dist/{media-folders-Dn3C16B8.mjs.map → media-folders-CI0Wydfo.mjs.map} +1 -1
  585. package/dist/{media-usage-CF0WVcLO.mjs → media-usage-X1_20MY-.mjs} +8 -8
  586. package/dist/{media-usage-CF0WVcLO.mjs.map → media-usage-X1_20MY-.mjs.map} +1 -1
  587. package/dist/{media-usage-activation-C9HwVw5w.mjs → media-usage-activation-bWusy3M2.mjs} +3 -3
  588. package/dist/{media-usage-activation-C9HwVw5w.mjs.map → media-usage-activation-bWusy3M2.mjs.map} +1 -1
  589. package/dist/{media-usage-work-m3AWTmmC.mjs → media-usage-work-B2RaoJIa.mjs} +4 -4
  590. package/dist/{media-usage-work-m3AWTmmC.mjs.map → media-usage-work-B2RaoJIa.mjs.map} +1 -1
  591. package/dist/{media-usage-work-CkLa5-nc.mjs → media-usage-work-BCK2J-v_.mjs} +7 -7
  592. package/dist/{media-usage-work-CkLa5-nc.mjs.map → media-usage-work-BCK2J-v_.mjs.map} +1 -1
  593. package/dist/{menus-BCVSSp1z.mjs → menus-B0Qqv_cS.mjs} +176 -92
  594. package/dist/menus-B0Qqv_cS.mjs.map +1 -0
  595. package/dist/{menus-Cl4kwdt7.mjs → menus-oypxT1pI.mjs} +134 -53
  596. package/dist/menus-oypxT1pI.mjs.map +1 -0
  597. package/dist/{menus-BSzwObhV.mjs → menus-uEAxWAm1.mjs} +3 -3
  598. package/dist/{menus-BSzwObhV.mjs.map → menus-uEAxWAm1.mjs.map} +1 -1
  599. package/dist/migrations/index.d.mts +1 -1
  600. package/dist/migrations/index.mjs +5 -5
  601. package/dist/{mime-B5M9R53H.mjs → mime-D1We9jUx.mjs} +1 -1
  602. package/dist/{mime-B5M9R53H.mjs.map → mime-D1We9jUx.mjs.map} +1 -1
  603. package/dist/{normalize-BuWa0lvd.mjs → normalize-Cx-bNwhP.mjs} +41 -12
  604. package/dist/normalize-Cx-bNwhP.mjs.map +1 -0
  605. package/dist/{oauth-authorization-TpEJrYB7.mjs → oauth-authorization-CPLgBrdo.mjs} +19 -6
  606. package/dist/oauth-authorization-CPLgBrdo.mjs.map +1 -0
  607. package/dist/{oauth-state-store-CHMn_mwc.mjs → oauth-state-store-BndCfRum.mjs} +1 -1
  608. package/dist/{oauth-state-store-CHMn_mwc.mjs.map → oauth-state-store-BndCfRum.mjs.map} +1 -1
  609. package/dist/object-cache/memory.d.mts +1 -1
  610. package/dist/{object-cache-Bo6DGaC9.mjs → object-cache-DmKr09La.mjs} +21 -4
  611. package/dist/{object-cache-Bo6DGaC9.mjs.map → object-cache-DmKr09La.mjs.map} +1 -1
  612. package/dist/{options-DJWaq3Ie.d.mts → options-BUUgroHg.d.mts} +3 -3
  613. package/dist/{options-DJWaq3Ie.d.mts.map → options-BUUgroHg.d.mts.map} +1 -1
  614. package/dist/page/index.d.mts +2 -2
  615. package/dist/page/index.mjs +16 -14
  616. package/dist/page/index.mjs.map +1 -1
  617. package/dist/{parse-DgQ2NABT.mjs → parse-B_b6gZxG.mjs} +2 -2
  618. package/dist/{parse-DgQ2NABT.mjs.map → parse-B_b6gZxG.mjs.map} +1 -1
  619. package/dist/{parse-CWHOFc6z.d.mts → parse-Dndx1QmN.d.mts} +3 -3
  620. package/dist/{parse-CWHOFc6z.d.mts.map → parse-Dndx1QmN.d.mts.map} +1 -1
  621. package/dist/{passkey-config-D0kdzEao.mjs → passkey-config-D2Lua5__.mjs} +1 -1
  622. package/dist/{passkey-config-D0kdzEao.mjs.map → passkey-config-D2Lua5__.mjs.map} +1 -1
  623. package/dist/{patterns-CMNnYCgf.mjs → patterns-CryGz_EV.mjs} +1 -1
  624. package/dist/{patterns-CMNnYCgf.mjs.map → patterns-CryGz_EV.mjs.map} +1 -1
  625. package/dist/{placeholder-D41vk11_.d.mts → placeholder-CSKu5OkD.d.mts} +9 -1
  626. package/dist/placeholder-CSKu5OkD.d.mts.map +1 -0
  627. package/dist/plugin-test-runtime.d.mts +17 -15
  628. package/dist/plugin-test-runtime.d.mts.map +1 -1
  629. package/dist/plugin-test-runtime.mjs +99 -98
  630. package/dist/plugin-types.d.mts +2 -2
  631. package/dist/plugin-utils.d.mts +13 -12
  632. package/dist/plugin-utils.d.mts.map +1 -1
  633. package/dist/plugins/adapt-sandbox-entry.d.mts +13 -12
  634. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  635. package/dist/plugins/host.d.mts +5 -5
  636. package/dist/plugins/host.mjs +48 -47
  637. package/dist/{policy-DLQ--Hnm.mjs → policy-BPugqmeV.mjs} +13 -5
  638. package/dist/policy-BPugqmeV.mjs.map +1 -0
  639. package/dist/{protocol-79kUO7jP.d.mts → protocol-PoAfD_9W.d.mts} +2 -2
  640. package/dist/{protocol-79kUO7jP.d.mts.map → protocol-PoAfD_9W.d.mts.map} +1 -1
  641. package/dist/{public-plugin-api-routes-BuAMJ8nN.mjs → public-plugin-api-routes-CMljrTby.mjs} +9 -9
  642. package/dist/{public-plugin-api-routes-BuAMJ8nN.mjs.map → public-plugin-api-routes-CMljrTby.mjs.map} +1 -1
  643. package/dist/{public-submission-Di3TsgzX.mjs → public-submission-ayqoyee4.mjs} +10 -10
  644. package/dist/{public-submission-Di3TsgzX.mjs.map → public-submission-ayqoyee4.mjs.map} +1 -1
  645. package/dist/query-BFHjCzSh.mjs +37 -0
  646. package/dist/query-BFHjCzSh.mjs.map +1 -0
  647. package/dist/{query-CpnRlBh8.mjs → query-D62NntCZ.mjs} +55 -65
  648. package/dist/query-D62NntCZ.mjs.map +1 -0
  649. package/dist/{query-haKvLAWl.mjs → query-LmPY2y0C.mjs} +5 -5
  650. package/dist/{query-haKvLAWl.mjs.map → query-LmPY2y0C.mjs.map} +1 -1
  651. package/dist/{rate-limit-Xp4d0NBY.mjs → rate-limit-C04Llsoc.mjs} +3 -3
  652. package/dist/{rate-limit-Xp4d0NBY.mjs.map → rate-limit-C04Llsoc.mjs.map} +1 -1
  653. package/dist/{redirect-DiS_3mA4.mjs → redirect-DjLdw427.mjs} +6 -6
  654. package/dist/{redirect-DiS_3mA4.mjs.map → redirect-DjLdw427.mjs.map} +1 -1
  655. package/dist/{redirect-paths-CNYq7w80.mjs → redirect-paths-DOaVBp6E.mjs} +1 -1
  656. package/dist/{redirect-paths-CNYq7w80.mjs.map → redirect-paths-DOaVBp6E.mjs.map} +1 -1
  657. package/dist/{redirects-BHpy-VsZ.mjs → redirects-Cpq1catN.mjs} +6 -6
  658. package/dist/{redirects-BHpy-VsZ.mjs.map → redirects-Cpq1catN.mjs.map} +1 -1
  659. package/dist/{redirects-BEVSRMa2.mjs → redirects-CyvvZrCJ.mjs} +2 -2
  660. package/dist/{redirects-BEVSRMa2.mjs.map → redirects-CyvvZrCJ.mjs.map} +1 -1
  661. package/dist/{registry-TUVaid9Z.mjs → registry-B0-O575m.mjs} +25 -20
  662. package/dist/registry-B0-O575m.mjs.map +1 -0
  663. package/dist/{relations-CA_QuMTU.mjs → relations-WIOojTL4.mjs} +24 -11
  664. package/dist/relations-WIOojTL4.mjs.map +1 -0
  665. package/dist/{request-cache-C4CIo5ad.mjs → request-cache-5z2WzWmh.mjs} +1 -1
  666. package/dist/{request-cache-C4CIo5ad.mjs.map → request-cache-5z2WzWmh.mjs.map} +1 -1
  667. package/dist/request-context.d.mts +12 -1
  668. package/dist/request-context.d.mts.map +1 -1
  669. package/dist/request-context.mjs.map +1 -1
  670. package/dist/{resolve-DwNM-CXl.mjs → resolve-BNnm8EkB.mjs} +14 -13
  671. package/dist/{resolve-DwNM-CXl.mjs.map → resolve-BNnm8EkB.mjs.map} +1 -1
  672. package/dist/{resolve-D-kqyKns.mjs → resolve-DdNXBJSr.mjs} +4 -4
  673. package/dist/resolve-DdNXBJSr.mjs.map +1 -0
  674. package/dist/{revision-BVp8S0e_.mjs → revision-Dx7Uth1X.mjs} +5 -5
  675. package/dist/{revision-BVp8S0e_.mjs.map → revision-Dx7Uth1X.mjs.map} +1 -1
  676. package/dist/{routes-BerZFrvV.mjs → routes-avqgc-8r.mjs} +3 -3
  677. package/dist/{routes-BerZFrvV.mjs.map → routes-avqgc-8r.mjs.map} +1 -1
  678. package/dist/{runner-C5NeAkBQ.mjs → runner-Bkkfk4O8.mjs} +78 -4
  679. package/dist/runner-Bkkfk4O8.mjs.map +1 -0
  680. package/dist/{runner-BObHz5FC.d.mts → runner-Dfgp6-S_.d.mts} +7 -2
  681. package/dist/runner-Dfgp6-S_.d.mts.map +1 -0
  682. package/dist/runtime.d.mts +14 -13
  683. package/dist/runtime.d.mts.map +1 -1
  684. package/dist/runtime.mjs +8 -6
  685. package/dist/runtime.mjs.map +1 -1
  686. package/dist/schema/project-env-types.mjs +43 -42
  687. package/dist/schema/project-env-types.mjs.map +1 -1
  688. package/dist/{schema-DJRfvM_o.mjs → schema-B-3rntlX.mjs} +11 -11
  689. package/dist/{schema-DJRfvM_o.mjs.map → schema-B-3rntlX.mjs.map} +1 -1
  690. package/dist/{schema-C4OBuv6Z.mjs → schema-BqBlUZtb.mjs} +4 -4
  691. package/dist/{schema-C4OBuv6Z.mjs.map → schema-BqBlUZtb.mjs.map} +1 -1
  692. package/dist/{scopes-BU03K7Uo.mjs → scopes-DNaY7Ey0.mjs} +3 -3
  693. package/dist/{scopes-BU03K7Uo.mjs.map → scopes-DNaY7Ey0.mjs.map} +1 -1
  694. package/dist/{search-DfVzks89.mjs → search-BPWTnpnD.mjs} +32 -32
  695. package/dist/search-BPWTnpnD.mjs.map +1 -0
  696. package/dist/{secrets-Cc_O3Kp-.mjs → secrets-Dswzrz3k.mjs} +2 -2
  697. package/dist/{secrets-Cc_O3Kp-.mjs.map → secrets-Dswzrz3k.mjs.map} +1 -1
  698. package/dist/{sections-BMy4JVUz.mjs → sections-rSao6rBG.mjs} +3 -3
  699. package/dist/{sections-BMy4JVUz.mjs.map → sections-rSao6rBG.mjs.map} +1 -1
  700. package/dist/seed/index.d.mts +2 -2
  701. package/dist/seed/index.mjs +42 -41
  702. package/dist/seo/index.d.mts +3 -3
  703. package/dist/seo/index.mjs +30 -28
  704. package/dist/seo/index.mjs.map +1 -1
  705. package/dist/{seo-VZYOEy8f.mjs → seo-B_H_pqOd.mjs} +3 -3
  706. package/dist/{seo-VZYOEy8f.mjs.map → seo-B_H_pqOd.mjs.map} +1 -1
  707. package/dist/{service-Bo_usgSk.mjs → service-DpiUQVpp.mjs} +2 -2
  708. package/dist/{service-Bo_usgSk.mjs.map → service-DpiUQVpp.mjs.map} +1 -1
  709. package/dist/{session-user-DlpPdf8L.mjs → session-user-DiE4JKPP.mjs} +4 -4
  710. package/dist/{session-user-DlpPdf8L.mjs.map → session-user-DiE4JKPP.mjs.map} +1 -1
  711. package/dist/{settings-DS914jjE.mjs → settings--XlUdUaI.mjs} +3 -3
  712. package/dist/{settings-DS914jjE.mjs.map → settings--XlUdUaI.mjs.map} +1 -1
  713. package/dist/{settings-CWb84sDm.mjs → settings-D932YP2-.mjs} +111 -44
  714. package/dist/settings-D932YP2-.mjs.map +1 -0
  715. package/dist/{setup-complete-BuNxAUiN.mjs → setup-complete-qsJYWutC.mjs} +2 -2
  716. package/dist/{setup-complete-BuNxAUiN.mjs.map → setup-complete-qsJYWutC.mjs.map} +1 -1
  717. package/dist/{setup-nonce-BnEAX0iy.mjs → setup-nonce-CBFgUFXw.mjs} +1 -1
  718. package/dist/{setup-nonce-BnEAX0iy.mjs.map → setup-nonce-CBFgUFXw.mjs.map} +1 -1
  719. package/dist/{site-domain-Bq5j5bVg.mjs → site-domain-oRGS_JfA.mjs} +2 -2
  720. package/dist/{site-domain-Bq5j5bVg.mjs.map → site-domain-oRGS_JfA.mjs.map} +1 -1
  721. package/dist/{sitemap-name-BTg2NJJv.mjs → sitemap-name-B8QP8wud.mjs} +1 -1
  722. package/dist/{sitemap-name-BTg2NJJv.mjs.map → sitemap-name-B8QP8wud.mjs.map} +1 -1
  723. package/dist/{slugify-bu2-jKia.mjs → slugify-BDEG9pLk.mjs} +1 -1
  724. package/dist/{slugify-bu2-jKia.mjs.map → slugify-BDEG9pLk.mjs.map} +1 -1
  725. package/dist/{snapshot-DYsG4uOq.mjs → snapshot-BjaXDZQ5.mjs} +2 -2
  726. package/dist/{snapshot-DYsG4uOq.mjs.map → snapshot-BjaXDZQ5.mjs.map} +1 -1
  727. package/dist/{stage-D8DWgPcO.mjs → stage-CZQnXM_G.mjs} +3 -3
  728. package/dist/{stage-D8DWgPcO.mjs.map → stage-CZQnXM_G.mjs.map} +1 -1
  729. package/dist/{staged-CD_imitH.mjs → staged-ByBmPFtX.mjs} +3 -3
  730. package/dist/{staged-CD_imitH.mjs.map → staged-ByBmPFtX.mjs.map} +1 -1
  731. package/dist/storage/local.d.mts +1 -1
  732. package/dist/storage/s3.d.mts +1 -1
  733. package/dist/{storage-query-DRyu3bSU.d.mts → storage-query-BFp06Y0s.d.mts} +22 -10
  734. package/dist/storage-query-BFp06Y0s.d.mts.map +1 -0
  735. package/dist/{taxonomies-Dd9JJv_a.mjs → taxonomies-5SR76j11.mjs} +15 -17
  736. package/dist/taxonomies-5SR76j11.mjs.map +1 -0
  737. package/dist/{taxonomies-ChnU7Mc8.mjs → taxonomies-B__VmCKQ.mjs} +64 -58
  738. package/dist/taxonomies-B__VmCKQ.mjs.map +1 -0
  739. package/dist/{taxonomy-C_C_LBAU.mjs → taxonomy-CMI5fczd.mjs} +30 -17
  740. package/dist/taxonomy-CMI5fczd.mjs.map +1 -0
  741. package/dist/{transaction-BVYIo_yu.mjs → transaction-CkoQ5wqQ.mjs} +1 -1
  742. package/dist/{transaction-BVYIo_yu.mjs.map → transaction-CkoQ5wqQ.mjs.map} +1 -1
  743. package/dist/{transfer-WmWiR3xf.mjs → transfer-BbKfufJ4.mjs} +2 -2
  744. package/dist/{transfer-WmWiR3xf.mjs.map → transfer-BbKfufJ4.mjs.map} +1 -1
  745. package/dist/{transfer-ClH6w6BV.mjs → transfer-DTGblAj2.mjs} +71 -34
  746. package/dist/{transfer-ClH6w6BV.mjs.map → transfer-DTGblAj2.mjs.map} +1 -1
  747. package/dist/{transfer-download--ofRj9Cw.mjs → transfer-download-D9LQEDX0.mjs} +1 -1
  748. package/dist/{transfer-download--ofRj9Cw.mjs.map → transfer-download-D9LQEDX0.mjs.map} +1 -1
  749. package/dist/{transport-DxiXnbXk.d.mts → transport-Dm5QzpT_.d.mts} +1 -1
  750. package/dist/{transport-DxiXnbXk.d.mts.map → transport-Dm5QzpT_.d.mts.map} +1 -1
  751. package/dist/{transport-BjbqNiPI.mjs → transport-Dzpn5b3i.mjs} +4 -3
  752. package/dist/transport-Dzpn5b3i.mjs.map +1 -0
  753. package/dist/{turnstile-CJwvsOqw.mjs → turnstile-Cs2wfkJG.mjs} +1 -1
  754. package/dist/{turnstile-CJwvsOqw.mjs.map → turnstile-Cs2wfkJG.mjs.map} +1 -1
  755. package/dist/{types-BA3ey2QH.d.mts → types-BAsCk5KU.d.mts} +1 -1
  756. package/dist/{types-BA3ey2QH.d.mts.map → types-BAsCk5KU.d.mts.map} +1 -1
  757. package/dist/{types-RLgeIpnF.d.mts → types-BuvSwT9Y.d.mts} +8 -7
  758. package/dist/{types-RLgeIpnF.d.mts.map → types-BuvSwT9Y.d.mts.map} +1 -1
  759. package/dist/{types-CNB-dj6F.d.mts → types-C4KT3Md2.d.mts} +10 -6
  760. package/dist/types-C4KT3Md2.d.mts.map +1 -0
  761. package/dist/{types-DvqVxZXa.d.mts → types-CVXHwIBB.d.mts} +1 -1
  762. package/dist/{types-DvqVxZXa.d.mts.map → types-CVXHwIBB.d.mts.map} +1 -1
  763. package/dist/{types-BGmQ0uXs.mjs → types-C_asjp1J.mjs} +1 -1
  764. package/dist/types-C_asjp1J.mjs.map +1 -0
  765. package/dist/{types-DHO7Pd25.d.mts → types-CadaL_k8.d.mts} +1 -1
  766. package/dist/{types-DHO7Pd25.d.mts.map → types-CadaL_k8.d.mts.map} +1 -1
  767. package/dist/{types-QoPHm-2w.d.mts → types-CjftrmZR.d.mts} +3 -3
  768. package/dist/{types-QoPHm-2w.d.mts.map → types-CjftrmZR.d.mts.map} +1 -1
  769. package/dist/{types-DCFavOii.d.mts → types-DKwRUA-x.d.mts} +1 -1
  770. package/dist/{types-DCFavOii.d.mts.map → types-DKwRUA-x.d.mts.map} +1 -1
  771. package/dist/{types-D2NEXtgB.mjs → types-DwhEvIVb.mjs} +8 -2
  772. package/dist/types-DwhEvIVb.mjs.map +1 -0
  773. package/dist/{types-Bwpzb5o6.d.mts → types-GwAyS5SH.d.mts} +1 -1
  774. package/dist/{types-Bwpzb5o6.d.mts.map → types-GwAyS5SH.d.mts.map} +1 -1
  775. package/dist/{types-slSqujQL.d.mts → types-uU2YNYSt.d.mts} +6 -1
  776. package/dist/types-uU2YNYSt.d.mts.map +1 -0
  777. package/dist/{update-check-BxNg9T6N.mjs → update-check-ABBJgg6M.mjs} +4 -4
  778. package/dist/{update-check-BxNg9T6N.mjs.map → update-check-ABBJgg6M.mjs.map} +1 -1
  779. package/dist/{url-Dn3IStq8.mjs → url-DTfGDJhI.mjs} +1 -1
  780. package/dist/{url-Dn3IStq8.mjs.map → url-DTfGDJhI.mjs.map} +1 -1
  781. package/dist/{user-BlCTKVAm.mjs → user-By8SzQxS.mjs} +3 -3
  782. package/dist/{user-BlCTKVAm.mjs.map → user-By8SzQxS.mjs.map} +1 -1
  783. package/dist/{utils-TPt7w2cS.mjs → utils-D5uOi6WS.mjs} +13 -5
  784. package/dist/utils-D5uOi6WS.mjs.map +1 -0
  785. package/dist/{index-BB9sVisO.d.mts → utils-Szp4iBQB.d.mts} +9 -256
  786. package/dist/utils-Szp4iBQB.d.mts.map +1 -0
  787. package/dist/{validate-emKxGC5F.d.mts → validate-DDvxy6qG.d.mts} +8 -8
  788. package/dist/{validate-emKxGC5F.d.mts.map → validate-DDvxy6qG.d.mts.map} +1 -1
  789. package/dist/{validate-DxYhJQA-.mjs → validate-Dz-TIOFB.mjs} +7 -7
  790. package/dist/{validate-DxYhJQA-.mjs.map → validate-Dz-TIOFB.mjs.map} +1 -1
  791. package/dist/{validation-Ckq4vsVg.mjs → validation-Fc9ZSUS_.mjs} +17 -16
  792. package/dist/{validation-Ckq4vsVg.mjs.map → validation-Fc9ZSUS_.mjs.map} +1 -1
  793. package/dist/version-Bu6Nlbb5.mjs +7 -0
  794. package/dist/{version-DHmGpcch.mjs.map → version-Bu6Nlbb5.mjs.map} +1 -1
  795. package/dist/version-DqB7LIO7.mjs +49 -0
  796. package/dist/version-DqB7LIO7.mjs.map +1 -0
  797. package/dist/{widgets-DA5XmvVC.mjs → widgets-BUx_Mf2q.mjs} +18 -5
  798. package/dist/widgets-BUx_Mf2q.mjs.map +1 -0
  799. package/dist/{wxr-taxonomies-RgRG0d5e.mjs → wxr-taxonomies-DbJtWrWM.mjs} +7 -6
  800. package/dist/wxr-taxonomies-DbJtWrWM.mjs.map +1 -0
  801. package/dist/{wxr-taxonomies-CrMonejI.d.mts → wxr-taxonomies-Dy2i2ON3.d.mts} +1 -1
  802. package/dist/wxr-taxonomies-Dy2i2ON3.d.mts.map +1 -0
  803. package/dist/{zod-generator-D0JKy94C.mjs → zod-generator--nlM0930.mjs} +5 -4
  804. package/dist/zod-generator--nlM0930.mjs.map +1 -0
  805. package/package.json +5 -5
  806. package/src/api/handlers/bulk-tag.ts +11 -3
  807. package/src/api/handlers/bylines.ts +6 -2
  808. package/src/api/handlers/content.ts +154 -23
  809. package/src/api/handlers/dashboard.ts +13 -6
  810. package/src/api/handlers/device-flow.ts +1 -3
  811. package/src/api/handlers/menus.ts +27 -13
  812. package/src/api/handlers/oauth-authorization.ts +1 -3
  813. package/src/api/handlers/relations.ts +15 -1
  814. package/src/api/handlers/taxonomies.ts +27 -17
  815. package/src/astro/image-endpoint.ts +23 -2
  816. package/src/astro/integration/admin-nav.ts +52 -0
  817. package/src/astro/integration/index.ts +7 -0
  818. package/src/astro/integration/runtime.ts +14 -0
  819. package/src/astro/integration/shims/use-sync-external-store-with-selector.js +2 -0
  820. package/src/astro/integration/shims/use-sync-external-store.js +4 -1
  821. package/src/astro/integration/vite-config.ts +16 -8
  822. package/src/astro/middleware/auth.ts +9 -2
  823. package/src/astro/middleware/redirect.ts +158 -63
  824. package/src/astro/middleware/request-context.ts +1 -0
  825. package/src/astro/middleware.ts +17 -4
  826. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +4 -1
  827. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +14 -1
  828. package/src/astro/routes/api/dashboard.ts +2 -0
  829. package/src/astro/routes/api/import/wordpress/analyze.ts +5 -29
  830. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -3
  831. package/src/astro/routes/api/media/file/[...key].ts +31 -7
  832. package/src/astro/routes/api/oauth/token.ts +5 -1
  833. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +7 -1
  834. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +3 -1
  835. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +2 -1
  836. package/src/astro/routes/api/widget-areas/[name].ts +2 -1
  837. package/src/astro/routes/api/widget-areas/index.ts +2 -1
  838. package/src/astro/types.ts +4 -3
  839. package/src/auth/api-tokens.ts +3 -0
  840. package/src/auth/types.ts +6 -0
  841. package/src/cleanup.ts +44 -0
  842. package/src/cli/commands/export-seed.ts +6 -4
  843. package/src/cli/commands/login.ts +2 -4
  844. package/src/client/index.ts +1 -0
  845. package/src/client/transport.ts +11 -7
  846. package/src/components/EmDashImage.astro +14 -2
  847. package/src/components/InlinePortableTextEditor.tsx +4 -1
  848. package/src/components/inline-bubble-menu.tsx +109 -95
  849. package/src/components/inline-image.tsx +54 -48
  850. package/src/database/dialect-helpers.ts +10 -3
  851. package/src/database/migrations/content-sized.ts +82 -0
  852. package/src/database/migrations/policy.ts +28 -2
  853. package/src/database/migrations/runner.ts +48 -0
  854. package/src/database/repositories/byline.ts +95 -31
  855. package/src/database/repositories/comment.ts +17 -7
  856. package/src/database/repositories/content.ts +96 -23
  857. package/src/database/repositories/media.ts +17 -0
  858. package/src/database/repositories/menu.ts +236 -139
  859. package/src/database/repositories/taxonomy-def.ts +9 -2
  860. package/src/database/repositories/taxonomy.ts +47 -14
  861. package/src/database/repositories/types.ts +14 -3
  862. package/src/database/types.ts +1 -1
  863. package/src/emdash-runtime.ts +60 -17
  864. package/src/fields/image.ts +1 -0
  865. package/src/i18n/config.ts +58 -10
  866. package/src/i18n/normalize.ts +16 -2
  867. package/src/i18n/query.ts +47 -0
  868. package/src/i18n/resolve.ts +8 -3
  869. package/src/import/utils.ts +15 -2
  870. package/src/import/wxr-taxonomies.ts +3 -2
  871. package/src/index.ts +1 -0
  872. package/src/loader.ts +183 -114
  873. package/src/mcp/server.ts +28 -1
  874. package/src/media/caption.ts +30 -0
  875. package/src/media/content-disposition.ts +56 -0
  876. package/src/media/image-endpoint.ts +13 -6
  877. package/src/media/local-runtime.ts +3 -0
  878. package/src/media/normalize.ts +17 -8
  879. package/src/media/responsive.ts +12 -2
  880. package/src/media/types.ts +16 -3
  881. package/src/media/usage/content-fields.ts +2 -1
  882. package/src/menus/index.ts +158 -36
  883. package/src/migrations/manifest.ts +1 -0
  884. package/src/object-cache/index.ts +22 -0
  885. package/src/plugin-test-runtime.ts +1 -0
  886. package/src/plugins/byline-access.ts +3 -1
  887. package/src/plugins/context.ts +1 -2
  888. package/src/plugins/storage-indexes.ts +7 -3
  889. package/src/query.ts +67 -53
  890. package/src/redirects/artifacts.ts +39 -5
  891. package/src/references/field-map.ts +6 -4
  892. package/src/request-context.ts +8 -0
  893. package/src/schema/collection-slugs-cache.ts +31 -17
  894. package/src/schema/registry.ts +9 -0
  895. package/src/schema/types.ts +7 -6
  896. package/src/schema/zod-generator.ts +2 -1
  897. package/src/search/fts-manager.ts +103 -31
  898. package/src/search/query.ts +31 -64
  899. package/src/seed/apply.ts +221 -109
  900. package/src/seed/types.ts +6 -4
  901. package/src/settings/index.ts +135 -35
  902. package/src/settings/version.ts +54 -0
  903. package/src/taxonomies/index.ts +99 -85
  904. package/src/taxonomies/term-counts.ts +2 -1
  905. package/src/transfer/import/advance.ts +12 -1
  906. package/src/transfer/import/scaffold.ts +7 -1
  907. package/src/transfer/import/stages.ts +53 -2
  908. package/src/utils/db-errors.ts +15 -0
  909. package/src/utils/revalidating-cache.ts +85 -0
  910. package/src/virtual-modules.d.ts +1 -0
  911. package/src/visual-editing/toolbar.ts +7 -2
  912. package/src/widgets/index.ts +27 -13
  913. package/dist/api-D-7BNHoz.mjs.map +0 -1
  914. package/dist/api-tokens-CdP49zVF.mjs +0 -3
  915. package/dist/apply-T7ATgzCh.mjs.map +0 -1
  916. package/dist/artifacts-htGyfCSt.mjs.map +0 -1
  917. package/dist/byline-BR1qhyiw.mjs.map +0 -1
  918. package/dist/bylines-CVOvRUL7.mjs.map +0 -1
  919. package/dist/collection-slugs-cache-e09QeJQt.mjs.map +0 -1
  920. package/dist/comment-UvIWDKcW.mjs.map +0 -1
  921. package/dist/config-Bsm_u_oE.d.mts.map +0 -1
  922. package/dist/config-CFMojMFK.d.mts.map +0 -1
  923. package/dist/config-CvmjJcVw.mjs +0 -73
  924. package/dist/config-CvmjJcVw.mjs.map +0 -1
  925. package/dist/content-DQSRZTVw.mjs.map +0 -1
  926. package/dist/context-BYptmCN0.mjs.map +0 -1
  927. package/dist/dashboard-D0jcgBD7.mjs.map +0 -1
  928. package/dist/db-errors-CBD5yMro.mjs.map +0 -1
  929. package/dist/deferred-tasks-ZvUECSzI.d.mts +0 -9
  930. package/dist/deferred-tasks-ZvUECSzI.d.mts.map +0 -1
  931. package/dist/device-flow-BkFKBoAn.mjs.map +0 -1
  932. package/dist/dialect-helpers-7_IqPdh9.mjs.map +0 -1
  933. package/dist/emdash-runtime-DVi7vtXC.mjs.map +0 -1
  934. package/dist/field-defs-cache-Dez5iWJJ.mjs +0 -77
  935. package/dist/field-defs-cache-Dez5iWJJ.mjs.map +0 -1
  936. package/dist/field-map-Bft9LKak.mjs.map +0 -1
  937. package/dist/fts-manager-Beg3e4bI.mjs.map +0 -1
  938. package/dist/index-BB9sVisO.d.mts.map +0 -1
  939. package/dist/loader-CswroUbe.mjs.map +0 -1
  940. package/dist/manifest-builder-B8zjuH3f.mjs.map +0 -1
  941. package/dist/media-Bws9f18T.mjs.map +0 -1
  942. package/dist/menus-BCVSSp1z.mjs.map +0 -1
  943. package/dist/menus-Cl4kwdt7.mjs.map +0 -1
  944. package/dist/normalize-BuWa0lvd.mjs.map +0 -1
  945. package/dist/oauth-authorization-TpEJrYB7.mjs.map +0 -1
  946. package/dist/placeholder-D41vk11_.d.mts.map +0 -1
  947. package/dist/policy-DLQ--Hnm.mjs.map +0 -1
  948. package/dist/query-CpnRlBh8.mjs.map +0 -1
  949. package/dist/registry-TUVaid9Z.mjs.map +0 -1
  950. package/dist/relations-CA_QuMTU.mjs.map +0 -1
  951. package/dist/resolve-D-kqyKns.mjs.map +0 -1
  952. package/dist/runner-BObHz5FC.d.mts.map +0 -1
  953. package/dist/runner-C5NeAkBQ.mjs.map +0 -1
  954. package/dist/search-DfVzks89.mjs.map +0 -1
  955. package/dist/settings-CWb84sDm.mjs.map +0 -1
  956. package/dist/storage-query-DRyu3bSU.d.mts.map +0 -1
  957. package/dist/taxonomies-ChnU7Mc8.mjs.map +0 -1
  958. package/dist/taxonomies-Dd9JJv_a.mjs.map +0 -1
  959. package/dist/taxonomy-C_C_LBAU.mjs.map +0 -1
  960. package/dist/transport-BjbqNiPI.mjs.map +0 -1
  961. package/dist/types-BGmQ0uXs.mjs.map +0 -1
  962. package/dist/types-CNB-dj6F.d.mts.map +0 -1
  963. package/dist/types-D2NEXtgB.mjs.map +0 -1
  964. package/dist/types-slSqujQL.d.mts.map +0 -1
  965. package/dist/utils-TPt7w2cS.mjs.map +0 -1
  966. package/dist/version-DHmGpcch.mjs +0 -7
  967. package/dist/widgets-DA5XmvVC.mjs.map +0 -1
  968. package/dist/wxr-taxonomies-CrMonejI.d.mts.map +0 -1
  969. package/dist/wxr-taxonomies-RgRG0d5e.mjs.map +0 -1
  970. package/dist/zod-generator-D0JKy94C.mjs.map +0 -1
@@ -1,3 +0,0 @@
1
- import { TOKEN_PREFIXES, TRANSFER_SCOPES, VALID_SCOPES, generatePrefixedToken, hasScope as hasScope$1, hashPrefixedToken as hashApiToken, isValidScope } from "@emdash-cms/auth";
2
-
3
- export { hasScope$1 as a, generatePrefixedToken as i, TRANSFER_SCOPES as n, hashApiToken as o, VALID_SCOPES as r, isValidScope as s, TOKEN_PREFIXES as t };
@@ -1 +0,0 @@
1
- {"version":3,"file":"apply-T7ATgzCh.mjs","names":["#queries","#mediaDownloads"],"sources":["../src/media/url.ts","../src/content/converters/gallery.ts","../src/seed/apply.ts"],"sourcesContent":["/**\n * Public media URL resolution.\n *\n * Used at render time by the Image components to decide whether a storage\n * key should be served from the configured `publicUrl` (R2 custom domain,\n * S3 CDN) or through the internal `/_emdash/api/media/file/{key}` route.\n */\nimport type { Storage } from \"../storage/types.js\";\nimport { INTERNAL_MEDIA_PREFIX } from \"./normalize.js\";\n\n// Keys accepted by the public-URL rewrite: the `{ulid}{ext}` shape produced by\n// the upload pipeline, with letters, digits, dots, dashes, and underscores.\n// Slashes, `?`, `#`, and `%` are rejected so attacker-controlled content in a\n// portable-text `asset.url` cannot traverse or reroute on the CDN origin.\nconst SAFE_STORAGE_KEY = /^[A-Za-z0-9._-]+$/;\n\n/**\n * Build the internal file route URL for a storage key, encoding each path\n * segment so `?`, `#` and `%` stay inside the path. A key with an empty, `.` or\n * `..` segment is encoded whole, because the URL parser would collapse those\n * segments and move the request off the route. Mirrors the admin's\n * `localMediaFileUrl`.\n */\nexport function localMediaFileUrl(key: string): string {\n\tconst segments = key.split(\"/\");\n\tif (segments.some((segment) => segment === \"\" || segment === \".\" || segment === \"..\")) {\n\t\treturn `${INTERNAL_MEDIA_PREFIX}${encodeURIComponent(key)}`;\n\t}\n\treturn `${INTERNAL_MEDIA_PREFIX}${segments.map(encodeURIComponent).join(\"/\")}`;\n}\n\n/**\n * Resolve the public URL for a locally stored media key. Returns an empty\n * string when no key is given. When a storage adapter is supplied, defers to\n * `storage.getPublicUrl()`; otherwise returns the internal proxy route.\n */\nexport function resolvePublicMediaUrl(\n\tstorage: Storage | null | undefined,\n\tstorageKey: string,\n): string {\n\tif (!storageKey) return \"\";\n\tif (storage) return storage.getPublicUrl(storageKey);\n\treturn `/_emdash/api/media/file/${storageKey}`;\n}\n\n/**\n * Build the `getPublicMediaUrl` closure attached to `Astro.locals.emdash`.\n * Shared by the anonymous fast path and the full-runtime path in middleware.\n *\n * @internal\n */\nexport function createPublicMediaUrlResolver(\n\tstorage: Storage | null | undefined,\n): (key: string) => string {\n\treturn (key) => resolvePublicMediaUrl(storage, key);\n}\n\n/** Input shape for {@link buildRenderMediaUrl}. */\nexport interface RenderMediaRef {\n\t/** Storage key with extension (the canonical shape from the upload pipeline). */\n\tstorageKey?: string;\n\t/** Pre-baked URL (either an internal proxy URL or an external URL). */\n\turl?: string;\n\t/** Bare media id (ULID without extension); only the internal proxy can look this up. */\n\tid?: string;\n}\n\n/**\n * Build a render-time media URL. Prefers `storageKey`, then rewrites an\n * internal `url` via `resolve`, then falls back to the internal proxy for a\n * bare `id`. External URLs and non-matching internal-looking URLs pass\n * through untouched. Returns `\"\"` when nothing usable is present.\n *\n * @internal\n */\nexport function buildRenderMediaUrl(\n\tresolve: ((key: string) => string) | undefined,\n\tref: RenderMediaRef,\n): string {\n\tconst { storageKey, url, id } = ref;\n\tif (storageKey) {\n\t\treturn resolve ? resolve(storageKey) : `${INTERNAL_MEDIA_PREFIX}${storageKey}`;\n\t}\n\tif (url) {\n\t\tif (resolve && url.startsWith(INTERNAL_MEDIA_PREFIX)) {\n\t\t\tconst key = url.slice(INTERNAL_MEDIA_PREFIX.length);\n\t\t\tif (SAFE_STORAGE_KEY.test(key)) return resolve(key);\n\t\t}\n\t\treturn url;\n\t}\n\tif (id) return `${INTERNAL_MEDIA_PREFIX}${id}`;\n\treturn \"\";\n}\n","/**\n * Shared sanitization for gallery block images and image media, used by both\n * converters so the editor round-trip and the stored shape stay in lockstep.\n */\n\nimport { localMediaFileUrl } from \"../../media/url.js\";\nimport type { PortableTextGalleryImage } from \"./types.js\";\n\nexport interface ImageMedia {\n\tasset: PortableTextGalleryImage[\"asset\"];\n\talt?: string;\n\twidth?: number;\n\theight?: number;\n}\n\n/**\n * Read an image's media reference, alt text, and dimensions. Accepts the\n * reference shape (`_ref`, `url`) and the MediaValue that seeded `$media`\n * stores instead (`id`, `src`, `meta.storageKey`, dimensions). The image's own\n * `alt`, `width`, and `height` take precedence over the asset's.\n */\nexport function resolveImageMedia(image: unknown): ImageMedia {\n\tconst record: Record<string, unknown> = isRecord(image) ? image : {};\n\tconst asset: Record<string, unknown> = isRecord(record.asset) ? record.asset : {};\n\t// The media id is not a storage key, so local files need `url`.\n\tconst storageKey = isRecord(asset.meta) ? nonEmptyString(asset.meta.storageKey) : undefined;\n\tconst url =\n\t\tnonEmptyString(asset.url) ??\n\t\tnonEmptyString(asset.src) ??\n\t\t(storageKey ? localMediaFileUrl(storageKey) : undefined);\n\tconst provider = nonEmptyString(asset.provider);\n\tconst alt = nonEmptyString(record.alt) ?? nonEmptyString(asset.alt);\n\tconst width = typeof record.width === \"number\" ? record.width : asset.width;\n\tconst height = typeof record.height === \"number\" ? record.height : asset.height;\n\n\tconst media: ImageMedia = {\n\t\tasset: {\n\t\t\t_type: \"reference\",\n\t\t\t_ref: nonEmptyString(asset._ref) ?? nonEmptyString(asset.id) ?? \"\",\n\t\t\t...(url ? { url } : {}),\n\t\t\t...(provider ? { provider } : {}),\n\t\t},\n\t};\n\tif (alt) media.alt = alt;\n\tif (typeof width === \"number\") media.width = width;\n\tif (typeof height === \"number\") media.height = height;\n\treturn media;\n}\n\n/**\n * Normalize an untrusted `images` value into well-formed gallery images.\n * Non-object entries and entries without an asset object are dropped.\n * Missing `_key`s are filled via `generateKey` when provided (PM → PT);\n * left empty otherwise (PT → PM keeps whatever the block carried).\n */\nexport function sanitizeGalleryImages(\n\tvalue: unknown,\n\tgenerateKey?: () => string,\n): PortableTextGalleryImage[] {\n\tif (!Array.isArray(value)) return [];\n\n\tconst images: PortableTextGalleryImage[] = [];\n\tfor (const entry of value as unknown[]) {\n\t\tif (!isRecord(entry)) continue;\n\t\tconst record = entry;\n\t\tif (!isRecord(record.asset)) continue;\n\t\tconst { asset, alt, width, height } = resolveImageMedia(record);\n\n\t\tconst image: PortableTextGalleryImage = {\n\t\t\t_type: \"image\",\n\t\t\t_key:\n\t\t\t\ttypeof record._key === \"string\" && record._key\n\t\t\t\t\t? record._key\n\t\t\t\t\t: generateKey\n\t\t\t\t\t\t? generateKey()\n\t\t\t\t\t\t: \"\",\n\t\t\tasset,\n\t\t};\n\t\tif (alt) image.alt = alt;\n\t\tif (typeof record.caption === \"string\" && record.caption) image.caption = record.caption;\n\t\tif (width !== undefined) image.width = width;\n\t\tif (height !== undefined) image.height = height;\n\t\tif (typeof record.focalX === \"number\") image.focalX = record.focalX;\n\t\tif (typeof record.focalY === \"number\") image.focalY = record.focalY;\n\t\tif (typeof record.blurhash === \"string\" && record.blurhash) image.blurhash = record.blurhash;\n\t\tif (typeof record.dominantColor === \"string\" && record.dominantColor)\n\t\t\timage.dominantColor = record.dominantColor;\n\n\t\timages.push(image);\n\t}\n\n\treturn images;\n}\n\nfunction nonEmptyString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" && value ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","/**\n * Seed engine - applies seed files to database\n *\n * This is the core implementation that bootstraps an EmDash site from a seed file.\n * Apply order is critical for foreign keys and references.\n */\n\nimport { imageSize } from \"image-size\";\nimport type {\n\tKysely,\n\tKyselyPlugin,\n\tPluginTransformQueryArgs,\n\tPluginTransformResultArgs,\n\tQueryResult,\n\tRootOperationNode,\n\tUnknownRow,\n} from \"kysely\";\nimport mime from \"mime/lite\";\nimport { ulid } from \"ulidx\";\n\nimport { setReferenceSelection } from \"../api/handlers/relations.js\";\nimport { bindReferenceField, createFieldRelation } from \"../api/handlers/schema.js\";\nimport { sanitizeGalleryImages } from \"../content/converters/gallery.js\";\nimport type { DatetimeContextCache } from \"../database/content-datetime.js\";\nimport { BylineRepository } from \"../database/repositories/byline.js\";\nimport { ContentRepository } from \"../database/repositories/content.js\";\nimport { MediaRepository } from \"../database/repositories/media.js\";\nimport { OptionsRepository } from \"../database/repositories/options.js\";\nimport { RedirectRepository } from \"../database/repositories/redirect.js\";\nimport { RevisionRepository } from \"../database/repositories/revision.js\";\nimport {\n\tfindTaxonomyStructure,\n\tsaveTaxonomyStructure,\n} from \"../database/repositories/taxonomy-def.js\";\nimport { TaxonomyRepository } from \"../database/repositories/taxonomy.js\";\nimport type { ContentItem } from \"../database/repositories/types.js\";\nimport { withTransaction } from \"../database/transaction.js\";\nimport type { Database } from \"../database/types.js\";\nimport type { MediaValue } from \"../fields/types.js\";\nimport { getI18nConfig, resolveConfiguredLocale } from \"../i18n/config.js\";\nimport { ssrfSafeFetch, validateExternalUrl } from \"../import/ssrf.js\";\nimport { markContentMediaUsageCollectionStaleSafely } from \"../media/usage/content-refresh.js\";\nimport { coalesceObjectCacheWrites, invalidateMenuObjectCache } from \"../object-cache/index.js\";\nimport { BlockTypeRegistry } from \"../schema/block-type-registry.js\";\nimport { normalizeBlocksData, resolveBlockTypes } from \"../schema/block-values.js\";\nimport { SchemaError, SchemaRegistry } from \"../schema/registry.js\";\nimport type { CollectionWithFields, Field } from \"../schema/types.js\";\nimport { FTSManager } from \"../search/fts-manager.js\";\nimport { invalidateSiteSettingsCache, setSiteSettings } from \"../settings/index.js\";\nimport type { SiteSettings } from \"../settings/types.js\";\nimport type { Storage } from \"../storage/types.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../utils/chunks.js\";\nimport { computeContentHash } from \"../utils/hash.js\";\nimport type {\n\tSeedFile,\n\tSeedField,\n\tSeedApplyOptions,\n\tSeedApplyResult,\n\tSeedContentEntry,\n\tSeedCollection,\n\tSeedRelation,\n\tSeedTaxonomy,\n\tSeedTaxonomyTerm,\n\tSeedMenuItem,\n\tSeedWidget,\n\tSeedMediaReference,\n\tSeedBylineAvatar,\n} from \"./types.js\";\n\nasync function applySiteSettings(\n\tdb: Kysely<Database>,\n\tsettings: Partial<SiteSettings>,\n\tonConflict: Exclude<SeedApplyOptions[\"onConflict\"], undefined>,\n\tresult: SeedApplyResult,\n): Promise<void> {\n\tconst entries = Object.entries(settings).filter(([, value]) => value !== undefined);\n\tif (entries.length === 0) return;\n\n\tif (onConflict === \"update\") {\n\t\tawait setSiteSettings(settings, db);\n\t\tresult.settings.applied += entries.length;\n\t\treturn;\n\t}\n\n\tconst options = new OptionsRepository(db);\n\tlet applied = 0;\n\ttry {\n\t\tfor (const [key, value] of entries) {\n\t\t\tconst write = await options.compareAndSet(`site:${key}`, null, value);\n\t\t\tif (!write.applied && onConflict === \"error\") {\n\t\t\t\tthrow new Error(`Conflict: site setting \"site:${key}\" already exists`);\n\t\t\t}\n\t\t\tif (write.applied) applied++;\n\t\t}\n\t} finally {\n\t\tif (applied > 0) {\n\t\t\tresult.settings.applied += applied;\n\t\t\tinvalidateSiteSettingsCache();\n\t\t}\n\t}\n}\n\n/**\n * Set a collection's `titleField`/`dateField`: a separate write run after the\n * fields exist, so `updateCollection` can validate them. No-op when neither is set.\n */\nasync function applyDisplayDateFields(\n\tregistry: SchemaRegistry,\n\tcollection: SeedCollection,\n): Promise<void> {\n\tif (collection.titleField === undefined && collection.dateField === undefined) return;\n\tawait registry.updateCollection(collection.slug, {\n\t\ttitleField: collection.titleField,\n\t\tdateField: collection.dateField,\n\t});\n}\n\nconst FILE_EXTENSION_PATTERN = /\\.([a-z0-9]+)(?:\\?|$)/i;\nconst SEED_RELATION_NAME_MAX_ATTEMPTS = 5;\nconst SEED_RELATION_INSERT_BATCH_SIZE = 10;\nimport { findTaxonomyStructureSource, validateSeed } from \"./validate.js\";\n\n/** Pattern to remove file extensions */\nconst EXTENSION_PATTERN = /\\.[^.]+$/;\n\n/** Pattern to remove query parameters */\nconst QUERY_PARAM_PATTERN = /\\?.*$/;\n\n/** Pattern to remove non-alphanumeric characters (except dash and underscore) */\nconst SANITIZE_PATTERN = /[^a-zA-Z0-9_-]/g;\n\n/** Pattern to collapse multiple hyphens */\nconst MULTIPLE_HYPHENS_PATTERN = /-+/g;\n\n/** The `category` and `tag` defs that migrations insert on every new database. */\nconst BUILT_IN_TAXONOMY_DEFS = new Map([\n\t[\n\t\t\"taxdef_category\",\n\t\t{ label: \"Categories\", label_singular: \"Category\", hierarchical: 1, collections: '[\"posts\"]' },\n\t],\n\t[\n\t\t\"taxdef_tag\",\n\t\t{ label: \"Tags\", label_singular: \"Tag\", hierarchical: 0, collections: '[\"posts\"]' },\n\t],\n]);\n\n/** Whether `def` is a built-in `category`/`tag` definition still holding its migration defaults. */\nfunction isUntouchedBuiltInTaxonomyDef(def: {\n\tid: string;\n\tlabel: string;\n\tlabel_singular: string | null;\n\thierarchical: number | null;\n\tcollections: string | null;\n}): boolean {\n\tconst builtIn = BUILT_IN_TAXONOMY_DEFS.get(def.id);\n\treturn (\n\t\tbuiltIn !== undefined &&\n\t\tdef.label === builtIn.label &&\n\t\tdef.label_singular === builtIn.label_singular &&\n\t\tdef.hierarchical === builtIn.hierarchical &&\n\t\tdef.collections === builtIn.collections\n\t);\n}\n\n/**\n * Apply a seed file to the database\n *\n * This function is idempotent - safe to run multiple times.\n *\n * @param db - Kysely database instance\n * @param seed - Seed file to apply\n * @param options - Application options\n * @returns Result summary\n */\nexport async function applySeed(\n\tdb: Kysely<Database>,\n\tseed: SeedFile,\n\toptions: SeedApplyOptions = {},\n): Promise<SeedApplyResult> {\n\tconst { result } = await coalesceObjectCacheWrites(() =>\n\t\tapplySeedWrites(db, seed, options, null),\n\t);\n\treturn result;\n}\n\n/** Per-call limits for `applySeedWithinBudget`. A limit that is left out is not checked. */\nexport interface SeedApplyBudget {\n\tqueries?: number;\n\tmediaDownloads?: number;\n}\n\n/** How many of a seed's items (taxonomy terms, bylines, content entries) exist after a call. */\nexport interface SeedApplyProgress {\n\tdone: number;\n\ttotal: number;\n}\n\n/**\n * Apply a seed over several calls, for a platform that caps the queries or\n * fetches of one request.\n *\n * The limits are thresholds, checked before each taxonomy term, byline and\n * content entry is created and before the phases that follow content. Schema,\n * taxonomy definitions and settings are applied in every call, the item that\n * crosses a threshold finishes, and the phases after content (menus,\n * redirects, widgets, sections) run in one call, so set the limits well below\n * the platform's. A call that stops returns `complete: false`; applying the\n * same seed again continues with the first item not yet created. A call\n * creates at least one item before it stops, so repeated calls finish. A\n * `$media` URL used by entries in different calls is still downloaded each time\n * so its content hash can be computed, but the resulting media row is reused\n * when an existing ready row has the same hash. Requires\n * `onConflict: \"skip\"`.\n */\nexport async function applySeedWithinBudget(\n\tdb: Kysely<Database>,\n\tseed: SeedFile,\n\toptions: SeedApplyOptions,\n\tlimits: SeedApplyBudget,\n): Promise<{ result: SeedApplyResult; complete: boolean; progress: SeedApplyProgress }> {\n\tconst budget = new SeedBudget(limits);\n\treturn coalesceObjectCacheWrites(() =>\n\t\tapplySeedWrites(db.withPlugin(budget), seed, options, budget),\n\t);\n}\n\n/**\n * Counts what a budgeted call spends. As a Kysely plugin it sees every query\n * the call runs, including those inside transactions.\n */\nclass SeedBudget implements KyselyPlugin {\n\t#queries = 0;\n\t#mediaDownloads = 0;\n\n\tconstructor(private readonly limits: SeedApplyBudget) {}\n\n\ttransformQuery(args: PluginTransformQueryArgs): RootOperationNode {\n\t\tthis.#queries++;\n\t\treturn args.node;\n\t}\n\n\ttransformResult(args: PluginTransformResultArgs): Promise<QueryResult<UnknownRow>> {\n\t\treturn Promise.resolve(args.result);\n\t}\n\n\tcountMediaDownload(): void {\n\t\tthis.#mediaDownloads++;\n\t}\n\n\tisSpent(): boolean {\n\t\tconst { queries, mediaDownloads } = this.limits;\n\t\treturn (\n\t\t\t(queries !== undefined && this.#queries >= queries) ||\n\t\t\t(mediaDownloads !== undefined && this.#mediaDownloads >= mediaDownloads)\n\t\t);\n\t}\n}\n\nasync function applySeedWrites(\n\tdb: Kysely<Database>,\n\tseed: SeedFile,\n\toptions: SeedApplyOptions,\n\tbudget: SeedBudget | null,\n): Promise<{ result: SeedApplyResult; complete: boolean; progress: SeedApplyProgress }> {\n\t// Validate seed first\n\tconst validation = validateSeed(seed);\n\tif (!validation.valid) {\n\t\tthrow new Error(`Invalid seed file:\\n${validation.errors.join(\"\\n\")}`);\n\t}\n\n\tconst {\n\t\tincludeContent = false,\n\t\tstorage,\n\t\tskipMediaDownload = false,\n\t\tonConflict = \"skip\",\n\t} = options;\n\n\tif (budget && onConflict !== \"skip\") {\n\t\tthrow new Error('A seed budget requires onConflict: \"skip\"');\n\t}\n\n\t// Result counters\n\tconst result: SeedApplyResult = {\n\t\tblockTypes: { created: 0, skipped: 0, updated: 0 },\n\t\tcollections: { created: 0, skipped: 0, updated: 0 },\n\t\tfields: { created: 0, skipped: 0, updated: 0 },\n\t\trelations: { created: 0, skipped: 0, updated: 0 },\n\t\ttaxonomies: { created: 0, skipped: 0, terms: 0 },\n\t\tbylines: { created: 0, skipped: 0, updated: 0 },\n\t\tmenus: { created: 0, items: 0 },\n\t\tredirects: { created: 0, skipped: 0, updated: 0 },\n\t\twidgetAreas: { created: 0, widgets: 0 },\n\t\tsections: { created: 0, skipped: 0, updated: 0 },\n\t\tsettings: { applied: 0 },\n\t\tcontent: { created: 0, skipped: 0, updated: 0 },\n\t\tmedia: { created: 0, skipped: 0 },\n\t};\n\tlet complete = true;\n\tconst progress: SeedApplyProgress = { done: 0, total: countSeedItems(seed, includeContent) };\n\t// With a budget, `onConflict` is \"skip\", so these counters count creations only.\n\tconst mayCreateItem = (): boolean =>\n\t\t!budget ||\n\t\tresult.taxonomies.terms + result.bylines.created + result.content.created === 0 ||\n\t\t!budget.isSpent();\n\n\t// Media context for $media resolution\n\tconst mediaContext: MediaContext = {\n\t\tdb,\n\t\tstorage: storage ?? null,\n\t\tskipMediaDownload,\n\t\tmediaCache: new Map(), // Cache downloaded media by URL to avoid re-downloading\n\t\tbudget,\n\t};\n\n\t// Apply order (critical for foreign keys and references):\n\t// 1. Site settings\n\t// 2. Relations (so the fields that name them can resolve)\n\t// 3. Collections + Fields\n\t// 4. Taxonomy definitions + Terms\n\t// 5. Content (so menu refs can resolve)\n\t// 6. Menus + Menu items (can now resolve content refs)\n\t// 7. Redirects\n\t// 8. Widget areas + Widgets\n\n\t// Track seed content IDs for reference resolution (shared across content and menus)\n\tconst seedIdMap = new Map<string, string>(); // seed id -> real entry id\n\tconst seedBylineIdMap = new Map<string, string>(); // seed byline id -> real byline id\n\tconst staleMarkedContentCollections = new Set<string>();\n\tconst failedStaleContentCollections = new Set<string>();\n\n\t// Fallback locale for rows that omit an explicit `locale`. Prefer the runtime\n\t// config (runtime-driven seeds), then the seed's self-described `defaultLocale`\n\t// (CLI exports run outside the runtime), and only then `en`. Without the\n\t// seed-carried default, a non-`en` single-locale project would be rewritten to\n\t// `en` on apply (#1421).\n\tconst defaultLocale = getI18nConfig()?.defaultLocale ?? seed.defaultLocale ?? \"en\";\n\tconst markSeedContentCollectionStale = async (collectionSlug: string): Promise<void> => {\n\t\tif (staleMarkedContentCollections.has(collectionSlug)) return;\n\t\tconst marked = await markContentMediaUsageCollectionStaleSafely(\n\t\t\tdb,\n\t\t\tcollectionSlug,\n\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t);\n\t\tif (marked) {\n\t\t\tstaleMarkedContentCollections.add(collectionSlug);\n\t\t\tfailedStaleContentCollections.delete(collectionSlug);\n\t\t} else {\n\t\t\tfailedStaleContentCollections.add(collectionSlug);\n\t\t}\n\t};\n\tconst retryFailedSeedContentStaleMarks = async (): Promise<void> => {\n\t\tfor (const collectionSlug of failedStaleContentCollections) {\n\t\t\tconst marked = await markContentMediaUsageCollectionStaleSafely(\n\t\t\t\tdb,\n\t\t\t\tcollectionSlug,\n\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t);\n\t\t\tif (marked) {\n\t\t\t\tstaleMarkedContentCollections.add(collectionSlug);\n\t\t\t\tfailedStaleContentCollections.delete(collectionSlug);\n\t\t\t}\n\t\t}\n\t};\n\n\t// 1. Site settings\n\tif (seed.settings) {\n\t\tawait applySiteSettings(db, seed.settings, onConflict, result);\n\t}\n\n\t// 2. Declared relations, before the fields that name them\n\tif (seed.relations) {\n\t\tawait applySeedRelations(db, seed.relations, seed.collections ?? [], onConflict, result);\n\t}\n\n\tif (seed.blockTypes) {\n\t\tconst registry = new BlockTypeRegistry(db);\n\t\tfor (const blockType of seed.blockTypes) {\n\t\t\tconst existing = await registry.getBlockType(blockType.slug);\n\t\t\tawait registry.applySeedBlockType(blockType, onConflict);\n\t\t\tif (!existing) result.blockTypes.created++;\n\t\t\telse if (onConflict === \"update\") result.blockTypes.updated++;\n\t\t\telse result.blockTypes.skipped++;\n\t\t}\n\t}\n\n\t// 3. Collections and Fields\n\tif (seed.collections) {\n\t\tconst registry = new SchemaRegistry(db);\n\t\tconst seedCollectionSlugs = new Set(seed.collections.map((collection) => collection.slug));\n\t\tconst knownRelations = await readRelationEnds(db);\n\t\tconst relationSlugs = new Set(knownRelations.keys());\n\t\tconst externalTargetExists = new Map<string, boolean>();\n\t\tconst pendingRelations: Array<{\n\t\t\tid: string;\n\t\t\tslug: string;\n\t\t\tparent_collection: string;\n\t\t\tchild_collection: string;\n\t\t\tparent_label: string;\n\t\t\tparent_label_singular: string | null;\n\t\t\tchild_label: string;\n\t\t\tmax_children_per_parent: number | null;\n\t\t}> = [];\n\n\t\tfor (const collection of seed.collections) {\n\t\t\t// Check if collection exists\n\t\t\tconst existing = await registry.getCollection(collection.slug);\n\n\t\t\tif (existing) {\n\t\t\t\tif (onConflict === \"error\") {\n\t\t\t\t\tthrow new Error(`Conflict: collection \"${collection.slug}\" already exists`);\n\t\t\t\t}\n\n\t\t\t\tif (onConflict === \"update\") {\n\t\t\t\t\tawait registry.updateCollection(collection.slug, {\n\t\t\t\t\t\tlabel: collection.label,\n\t\t\t\t\t\tlabelSingular: collection.labelSingular,\n\t\t\t\t\t\tdescription: collection.description,\n\t\t\t\t\t\ticon: collection.icon,\n\t\t\t\t\t\tadmin: collection.admin,\n\t\t\t\t\t\tsupports: collection.supports || [],\n\t\t\t\t\t\turlPattern: collection.urlPattern,\n\t\t\t\t\t\troutable: collection.routable,\n\t\t\t\t\t\thidden: collection.hidden,\n\t\t\t\t\t\tsortOrder: collection.sortOrder,\n\t\t\t\t\t\tgroup: collection.group,\n\t\t\t\t\t\tcommentsEnabled: collection.commentsEnabled,\n\t\t\t\t\t\teditLocking: collection.editLocking,\n\t\t\t\t\t});\n\t\t\t\t\tresult.collections.updated++;\n\n\t\t\t\t\t// Update or create fields\n\t\t\t\t\tfor (const field of collection.fields) {\n\t\t\t\t\t\tconst existingField = await registry.getField(collection.slug, field.slug);\n\t\t\t\t\t\tawait upsertSeedField(db, collection.slug, field, existingField, knownRelations);\n\t\t\t\t\t\tif (existingField) result.fields.updated++;\n\t\t\t\t\t\telse result.fields.created++;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Second write: display/date fields, now that fields exist.\n\t\t\t\t\tawait applyDisplayDateFields(registry, collection);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// skip\n\t\t\t\tresult.collections.skipped++;\n\t\t\t\tresult.fields.skipped += collection.fields.length;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fields = [];\n\t\t\tfor (const field of collection.fields) {\n\t\t\t\tlet fieldValidation = field.validation;\n\t\t\t\t// A field naming a relation binds to it; one naming only a target\n\t\t\t\t// collection gets a relation created for it below.\n\t\t\t\tconst bound = resolveSeedFieldRelation(collection.slug, field, knownRelations);\n\t\t\t\tconst targetCollection =\n\t\t\t\t\t!bound &&\n\t\t\t\t\tfield.type === \"reference\" &&\n\t\t\t\t\ttypeof fieldValidation?.targetCollection === \"string\"\n\t\t\t\t\t\t? fieldValidation.targetCollection\n\t\t\t\t\t\t: undefined;\n\t\t\t\tif (bound) {\n\t\t\t\t\tfieldValidation = { ...fieldValidation, ...bound };\n\t\t\t\t}\n\t\t\t\tif (targetCollection) {\n\t\t\t\t\tlet targetExists = seedCollectionSlugs.has(targetCollection);\n\t\t\t\t\tif (!targetExists) {\n\t\t\t\t\t\ttargetExists = externalTargetExists.get(targetCollection) ?? false;\n\t\t\t\t\t\tif (!externalTargetExists.has(targetCollection)) {\n\t\t\t\t\t\t\ttargetExists = Boolean(await registry.getCollection(targetCollection));\n\t\t\t\t\t\t\texternalTargetExists.set(targetCollection, targetExists);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!targetExists) {\n\t\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t\t`Target collection \"${targetCollection}\" not found`,\n\t\t\t\t\t\t\t\"COLLECTION_NOT_FOUND\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst relationId = ulid();\n\t\t\t\t\tconst relationSlug = allocateSeedRelationName(collection.slug, field.slug, relationSlugs);\n\t\t\t\t\tpendingRelations.push({\n\t\t\t\t\t\tid: relationId,\n\t\t\t\t\t\tslug: relationSlug,\n\t\t\t\t\t\tparent_collection: collection.slug,\n\t\t\t\t\t\tchild_collection: targetCollection,\n\t\t\t\t\t\tparent_label: collection.label,\n\t\t\t\t\t\tparent_label_singular: collection.labelSingular ?? null,\n\t\t\t\t\t\tchild_label: field.label,\n\t\t\t\t\t\tmax_children_per_parent: fieldValidation?.multiple ? null : 1,\n\t\t\t\t\t});\n\t\t\t\t\tfieldValidation = {\n\t\t\t\t\t\t...fieldValidation,\n\t\t\t\t\t\trelation: relationSlug,\n\t\t\t\t\t\trelationSide: \"parent\",\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tfields.push({\n\t\t\t\t\tslug: field.slug,\n\t\t\t\t\tlabel: field.label,\n\t\t\t\t\ttype: field.type,\n\t\t\t\t\trequired: field.required || false,\n\t\t\t\t\tunique: field.unique || false,\n\t\t\t\t\tsearchable: field.searchable || false,\n\t\t\t\t\tindexed: field.indexed || false,\n\t\t\t\t\ttranslatable: field.translatable,\n\t\t\t\t\tdefaultValue: field.defaultValue,\n\t\t\t\t\tvalidation: fieldValidation,\n\t\t\t\t\twidget: field.widget,\n\t\t\t\t\toptions: field.options,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Create a fresh seed schema in bulk to stay within D1's query budget.\n\t\t\tawait registry.createSeedCollection(\n\t\t\t\t{\n\t\t\t\t\tslug: collection.slug,\n\t\t\t\t\tlabel: collection.label,\n\t\t\t\t\tlabelSingular: collection.labelSingular,\n\t\t\t\t\tdescription: collection.description,\n\t\t\t\t\ticon: collection.icon,\n\t\t\t\t\tadmin: collection.admin,\n\t\t\t\t\tsupports: collection.supports || [],\n\t\t\t\t\turlPattern: collection.urlPattern,\n\t\t\t\t\troutable: collection.routable,\n\t\t\t\t\thidden: collection.hidden,\n\t\t\t\t\tsortOrder: collection.sortOrder,\n\t\t\t\t\tgroup: collection.group,\n\t\t\t\t\tcommentsEnabled: collection.commentsEnabled,\n\t\t\t\t\teditLocking: collection.editLocking,\n\t\t\t\t},\n\t\t\t\tfields,\n\t\t\t);\n\t\t\t// titleField/dateField reference existing fields, so set them after\n\t\t\t// the schema exists.\n\t\t\tawait applyDisplayDateFields(registry, collection);\n\t\t\tresult.collections.created++;\n\t\t\tresult.fields.created += collection.fields.length;\n\t\t}\n\n\t\tfor (const relationBatch of chunks(pendingRelations, SEED_RELATION_INSERT_BATCH_SIZE)) {\n\t\t\tawait db.insertInto(\"_emdash_relations\").values(relationBatch).execute();\n\t\t}\n\t}\n\n\t// 4-5. Taxonomies\n\tif (seed.taxonomies) {\n\t\tconst termSeedIdMap = new Map<string, string>();\n\t\tconst taxonomiesBySeedId = new Map<string, SeedTaxonomy>();\n\t\tfor (const taxonomy of seed.taxonomies) {\n\t\t\tif (taxonomy.id) taxonomiesBySeedId.set(taxonomy.id, taxonomy);\n\t\t}\n\t\t// Read before any entry applies: a structure write rewrites every locale's\n\t\t// definition, which would make a built-in look edited to later entries.\n\t\tconst untouchedBuiltInDefIds = new Set(\n\t\t\t(\n\t\t\t\tawait db\n\t\t\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t\t\t.select([\"id\", \"label\", \"label_singular\", \"hierarchical\", \"collections\"])\n\t\t\t\t\t.where(\"id\", \"in\", [...BUILT_IN_TAXONOMY_DEFS.keys()])\n\t\t\t\t\t.execute()\n\t\t\t)\n\t\t\t\t.filter(isUntouchedBuiltInTaxonomyDef)\n\t\t\t\t.map((def) => def.id),\n\t\t);\n\t\t// Entries that declare their taxonomy's structure apply first: a translation's\n\t\t// terms need the structure its source entry may still replace.\n\t\tconst declaresOwnStructure = (taxonomy: SeedTaxonomy) =>\n\t\t\tfindTaxonomyStructureSource(taxonomy, taxonomiesBySeedId) === taxonomy;\n\t\tconst orderedTaxonomies = [\n\t\t\t...seed.taxonomies.filter(declaresOwnStructure),\n\t\t\t...seed.taxonomies.filter((taxonomy) => !declaresOwnStructure(taxonomy)),\n\t\t];\n\n\t\tfor (const taxonomy of orderedTaxonomies) {\n\t\t\tconst defLocale = resolveConfiguredLocale(taxonomy.locale ?? defaultLocale);\n\n\t\t\tconst defsOfName = await db\n\t\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t\t.select([\"id\", \"locale\"])\n\t\t\t\t.where(\"name\", \"=\", taxonomy.name)\n\t\t\t\t.execute();\n\t\t\t// (name, locale) is the UNIQUE key after migration 036.\n\t\t\tconst existingDef = defsOfName.find((def) => def.locale === defLocale);\n\t\t\tconst unclaimed = existingDef !== undefined && untouchedBuiltInDefIds.has(existingDef.id);\n\t\t\tif (existingDef && onConflict === \"error\" && !unclaimed) {\n\t\t\t\tthrow new Error(`Conflict: taxonomy \"${taxonomy.name}\" (${defLocale}) already exists`);\n\t\t\t}\n\t\t\tconst replacesDef = onConflict === \"update\" || unclaimed;\n\n\t\t\t// The structure belongs to the taxonomy, not the locale: a translation takes\n\t\t\t// the one its source entry left, and an existing taxonomy's is rewritten only\n\t\t\t// by a source entry that replaces its definition or finds nothing but untouched\n\t\t\t// built-in definitions of it, in any locale.\n\t\t\tconst existingStructure = await findTaxonomyStructure(db, taxonomy.name);\n\t\t\tconst replacesStructure =\n\t\t\t\treplacesDef || defsOfName.every((def) => untouchedBuiltInDefIds.has(def.id));\n\t\t\tconst writesStructure = !existingStructure || (replacesStructure && !taxonomy.translationOf);\n\t\t\tconst structure =\n\t\t\t\texistingStructure && !writesStructure\n\t\t\t\t\t? existingStructure\n\t\t\t\t\t: {\n\t\t\t\t\t\t\thierarchical: taxonomy.hierarchical ?? existingStructure?.hierarchical ?? false,\n\t\t\t\t\t\t\tcollections: taxonomy.collections ?? existingStructure?.collections ?? [],\n\t\t\t\t\t\t};\n\t\t\tconst defId = existingDef?.id ?? ulid();\n\t\t\tconst translationGroup = writesStructure\n\t\t\t\t? await saveTaxonomyStructure(db, taxonomy.name, existingStructure?.id ?? defId, structure)\n\t\t\t\t: existingStructure.id;\n\n\t\t\tif (existingDef) {\n\t\t\t\tif (replacesDef) {\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.updateTable(\"_emdash_taxonomy_defs\")\n\t\t\t\t\t\t.set({ label: taxonomy.label, label_singular: taxonomy.labelSingular ?? null })\n\t\t\t\t\t\t.where(\"id\", \"=\", existingDef.id)\n\t\t\t\t\t\t.execute();\n\t\t\t\t} else {\n\t\t\t\t\tresult.taxonomies.skipped++;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tawait db\n\t\t\t\t\t.insertInto(\"_emdash_taxonomy_defs\")\n\t\t\t\t\t.values({\n\t\t\t\t\t\tid: defId,\n\t\t\t\t\t\tname: taxonomy.name,\n\t\t\t\t\t\tlabel: taxonomy.label,\n\t\t\t\t\t\tlabel_singular: taxonomy.labelSingular ?? null,\n\t\t\t\t\t\thierarchical: structure.hierarchical ? 1 : 0,\n\t\t\t\t\t\tcollections: JSON.stringify(structure.collections),\n\t\t\t\t\t\tlocale: defLocale,\n\t\t\t\t\t\ttranslation_group: translationGroup,\n\t\t\t\t\t})\n\t\t\t\t\t.execute();\n\t\t\t\tresult.taxonomies.created++;\n\t\t\t}\n\n\t\t\t// Create terms (if provided)\n\t\t\tif (includeContent && taxonomy.terms && taxonomy.terms.length > 0) {\n\t\t\t\tconst termRepo = new TaxonomyRepository(db);\n\t\t\t\tconst existingTerms = await findExistingSeedTerms(db, taxonomy.name, taxonomy.terms);\n\n\t\t\t\tif (structure.hierarchical) {\n\t\t\t\t\tconst applied = await applyHierarchicalTerms(\n\t\t\t\t\t\ttermRepo,\n\t\t\t\t\t\ttaxonomy.name,\n\t\t\t\t\t\tdefLocale,\n\t\t\t\t\t\ttaxonomy.terms,\n\t\t\t\t\t\texistingTerms,\n\t\t\t\t\t\ttermSeedIdMap,\n\t\t\t\t\t\tresult,\n\t\t\t\t\t\tonConflict,\n\t\t\t\t\t\tmayCreateItem,\n\t\t\t\t\t);\n\t\t\t\t\tprogress.done += applied.processed;\n\t\t\t\t\tcomplete = applied.complete;\n\t\t\t\t} else {\n\t\t\t\t\tfor (const term of taxonomy.terms) {\n\t\t\t\t\t\tconst termLocale = resolveConfiguredLocale(term.locale ?? defLocale);\n\t\t\t\t\t\tconst termKey = seedTermKey(termLocale, term.slug);\n\t\t\t\t\t\tconst existingId = existingTerms.get(termKey);\n\t\t\t\t\t\tif (existingId) {\n\t\t\t\t\t\t\tif (onConflict === \"error\") {\n\t\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\t`Conflict: taxonomy term \"${term.slug}\" in \"${taxonomy.name}\" (${termLocale}) already exists`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (onConflict === \"update\") {\n\t\t\t\t\t\t\t\tawait termRepo.update(existingId, {\n\t\t\t\t\t\t\t\t\tlabel: term.label,\n\t\t\t\t\t\t\t\t\tdata: term.description ? { description: term.description } : {},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tresult.taxonomies.terms++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (term.id) termSeedIdMap.set(term.id, existingId);\n\t\t\t\t\t\t\tprogress.done++;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (!mayCreateItem()) {\n\t\t\t\t\t\t\t\tcomplete = false;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst translationOf = term.translationOf\n\t\t\t\t\t\t\t\t? termSeedIdMap.get(term.translationOf)\n\t\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\t\tconst created = await termRepo.create({\n\t\t\t\t\t\t\t\tname: taxonomy.name,\n\t\t\t\t\t\t\t\tslug: term.slug,\n\t\t\t\t\t\t\t\tlabel: term.label,\n\t\t\t\t\t\t\t\tdata: term.description ? { description: term.description } : undefined,\n\t\t\t\t\t\t\t\tlocale: termLocale,\n\t\t\t\t\t\t\t\ttranslationOf,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\texistingTerms.set(termKey, created.id);\n\t\t\t\t\t\t\tif (term.id) termSeedIdMap.set(term.id, created.id);\n\t\t\t\t\t\t\tresult.taxonomies.terms++;\n\t\t\t\t\t\t\tprogress.done++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!complete) break;\n\t\t}\n\n\t\t// Seeded/updated defs change which taxonomies exist — clear the\n\t\t// isolate-wide defs + names caches so later reads in this isolate\n\t\t// (e.g. an auto-seed triggered mid-request) reflect them immediately.\n\t\tconst { invalidateTaxonomyDefsCache } = await import(\"../taxonomies/index.js\");\n\t\tinvalidateTaxonomyDefsCache();\n\t}\n\n\t// 6. Bylines\n\tif (complete && includeContent && seed.bylines) {\n\t\tconst bylineRepo = new BylineRepository(db);\n\t\tconst existingBylines = await findExistingSeedBylines(\n\t\t\tdb,\n\t\t\tseed.bylines.map((byline) => byline.slug),\n\t\t);\n\t\tfor (const byline of seed.bylines) {\n\t\t\tconst existingId = existingBylines.get(byline.slug);\n\t\t\tif (existingId) {\n\t\t\t\tif (onConflict === \"error\") {\n\t\t\t\t\tthrow new Error(`Conflict: byline \"${byline.slug}\" already exists`);\n\t\t\t\t}\n\n\t\t\t\tif (onConflict === \"update\") {\n\t\t\t\t\t// Resolve the avatar (reusing an existing media row by storage\n\t\t\t\t\t// key, so re-running an update stays idempotent). Only relink\n\t\t\t\t\t// when the seed supplies an avatar; otherwise leave the existing\n\t\t\t\t\t// one untouched.\n\t\t\t\t\tconst avatar = byline.avatar ? await resolveSeedBylineAvatar(db, byline.avatar) : null;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst updated = await bylineRepo.update(existingId, {\n\t\t\t\t\t\t\tdisplayName: byline.displayName,\n\t\t\t\t\t\t\tbio: byline.bio ?? null,\n\t\t\t\t\t\t\twebsiteUrl: byline.websiteUrl ?? null,\n\t\t\t\t\t\t\tisGuest: byline.isGuest,\n\t\t\t\t\t\t\t...(avatar ? { avatarMediaId: avatar.id } : {}),\n\t\t\t\t\t\t});\n\t\t\t\t\t\t// update() returns null (no throw) if the row vanished between\n\t\t\t\t\t\t// the lookup and here; treat that as a failure so the catch\n\t\t\t\t\t\t// cleans up any freshly-created avatar media instead of leaking it.\n\t\t\t\t\t\tif (!updated) {\n\t\t\t\t\t\t\tthrow new Error(`Byline \"${byline.slug}\" disappeared during update`);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t// withTransaction is a no-op on D1, so undo a freshly-created\n\t\t\t\t\t\t// media row by hand to avoid orphaning it.\n\t\t\t\t\t\tif (avatar?.created) await deleteMediaRow(db, avatar.id);\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t\tseedBylineIdMap.set(byline.id, existingId);\n\t\t\t\t\tresult.bylines.updated++;\n\t\t\t\t\tif (avatar?.created) result.media.created++;\n\t\t\t\t\tprogress.done++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// skip\n\t\t\t\tseedBylineIdMap.set(byline.id, existingId);\n\t\t\t\tresult.bylines.skipped++;\n\t\t\t\tprogress.done++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!mayCreateItem()) {\n\t\t\t\tcomplete = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tconst avatar = byline.avatar ? await resolveSeedBylineAvatar(db, byline.avatar) : null;\n\t\t\tlet createdId: string;\n\t\t\ttry {\n\t\t\t\tconst created = await bylineRepo.create({\n\t\t\t\t\tslug: byline.slug,\n\t\t\t\t\tdisplayName: byline.displayName,\n\t\t\t\t\tbio: byline.bio ?? null,\n\t\t\t\t\twebsiteUrl: byline.websiteUrl ?? null,\n\t\t\t\t\tisGuest: byline.isGuest,\n\t\t\t\t\tavatarMediaId: avatar?.id ?? null,\n\t\t\t\t});\n\t\t\t\tcreatedId = created.id;\n\t\t\t} catch (error) {\n\t\t\t\tif (avatar?.created) await deleteMediaRow(db, avatar.id);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\texistingBylines.set(byline.slug, createdId);\n\t\t\tseedBylineIdMap.set(byline.id, createdId);\n\t\t\tresult.bylines.created++;\n\t\t\tif (avatar?.created) result.media.created++;\n\t\t\tprogress.done++;\n\t\t}\n\t}\n\n\t// 7. Content (created before menus so refs can resolve)\n\tif (complete && includeContent && seed.content) {\n\t\tconst contentRepo = new ContentRepository(db);\n\t\tconst schemaRegistry = new SchemaRegistry(db);\n\t\t// Settings and fields are all written above, so every entry can share the\n\t\t// timezone and datetime fields read for its collection.\n\t\tconst datetimeContexts: DatetimeContextCache = new Map();\n\n\t\ttry {\n\t\t\t// Create content entries\n\t\t\tfor (const [collectionSlug, entries] of Object.entries(seed.content)) {\n\t\t\t\tconst collectionInfo = await schemaRegistry.getCollectionWithFields(collectionSlug);\n\t\t\t\tconst collectionRoutable = collectionInfo?.routable !== false;\n\t\t\t\tconst referenceFields = referenceFieldsOf(collectionInfo);\n\t\t\t\tconst resolvedBlockTypes = collectionInfo?.fields.some((field) => field.type === \"blocks\")\n\t\t\t\t\t? await resolveBlockTypes(db)\n\t\t\t\t\t: undefined;\n\t\t\t\tconst existingEntries = await findExistingSeedEntries(\n\t\t\t\t\tcontentRepo,\n\t\t\t\t\tcollectionSlug,\n\t\t\t\t\tentries,\n\t\t\t\t\tdefaultLocale,\n\t\t\t\t);\n\t\t\t\tconst entriesWithoutLiveMatch = entries.filter((entry) => {\n\t\t\t\t\tconst { slug, locale } = seedEntryIdentity(entry, defaultLocale);\n\t\t\t\t\treturn !existingEntries.has(seedEntryKey(entry, slug, locale));\n\t\t\t\t});\n\t\t\t\tconst trashedEntries = await findExistingSeedEntries(\n\t\t\t\t\tcontentRepo,\n\t\t\t\t\tcollectionSlug,\n\t\t\t\t\tentriesWithoutLiveMatch,\n\t\t\t\t\tdefaultLocale,\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tconst { slug: entrySlug, locale: entryLocale } = seedEntryIdentity(entry, defaultLocale);\n\t\t\t\t\tconst entryKey = seedEntryKey(entry, entrySlug, entryLocale);\n\t\t\t\t\tconst existing = existingEntries.get(entryKey);\n\n\t\t\t\t\tif (!existing) {\n\t\t\t\t\t\tconst trashed = trashedEntries.get(entryKey);\n\t\t\t\t\t\tif (trashed) {\n\t\t\t\t\t\t\tif (onConflict === \"error\") {\n\t\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\t`Conflict: content \"${entrySlug ?? entry.id}\" in \"${collectionSlug}\" already exists (in trash)`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t`content.${collectionSlug}: \"${entrySlug ?? entry.id}\" (${entryLocale}) exists in the trash — skipping`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tresult.content.skipped++;\n\t\t\t\t\t\t\tprogress.done++;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (existing) {\n\t\t\t\t\t\tif (onConflict === \"error\") {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t`Conflict: content \"${entrySlug ?? entry.id}\" in \"${collectionSlug}\" already exists`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (onConflict === \"update\") {\n\t\t\t\t\t\t\t// Resolve $ref and $media in data\n\t\t\t\t\t\t\tlet resolvedData = await resolveReferences(\n\t\t\t\t\t\t\t\tentry.data,\n\t\t\t\t\t\t\t\tseedIdMap,\n\t\t\t\t\t\t\t\tmediaContext,\n\t\t\t\t\t\t\t\tresult,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (collectionInfo) {\n\t\t\t\t\t\t\t\tresolvedData = await normalizeBlocksData(\n\t\t\t\t\t\t\t\t\tdb,\n\t\t\t\t\t\t\t\t\tcollectionInfo,\n\t\t\t\t\t\t\t\t\tresolvedData,\n\t\t\t\t\t\t\t\t\texisting.data,\n\t\t\t\t\t\t\t\t\t{ restoreBlocks: true },\n\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\tresolvedBlockTypes,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Reference fields are storage-less — route their resolved values to\n\t\t\t\t\t\t\t// edges and keep them out of the column/revision data.\n\t\t\t\t\t\t\tconst { columnData, edges } = splitReferenceFields(\n\t\t\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\t\treferenceFields,\n\t\t\t\t\t\t\t\tresolvedData,\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t// Update content + bylines + taxonomies atomically\n\t\t\t\t\t\t\tconst status = entry.status || \"published\";\n\t\t\t\t\t\t\tlet contentMutated = false;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait withTransaction(db, async (trx) => {\n\t\t\t\t\t\t\t\t\tconst trxContentRepo = new ContentRepository(trx, datetimeContexts);\n\t\t\t\t\t\t\t\t\tconst trxBylineRepo = new BylineRepository(trx);\n\t\t\t\t\t\t\t\t\tconst trxRevisionRepo = new RevisionRepository(trx, datetimeContexts);\n\n\t\t\t\t\t\t\t\t\tawait trxContentRepo.update(collectionSlug, existing.id, {\n\t\t\t\t\t\t\t\t\t\tstatus,\n\t\t\t\t\t\t\t\t\t\tdata: columnData,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tcontentMutated = true;\n\n\t\t\t\t\t\t\t\t\tawait applyContentBylines(\n\t\t\t\t\t\t\t\t\t\ttrxBylineRepo,\n\t\t\t\t\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\t\t\t\texisting.id,\n\t\t\t\t\t\t\t\t\t\tentry,\n\t\t\t\t\t\t\t\t\t\tseedBylineIdMap,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tawait applyContentTaxonomies(trx, collectionSlug, existing.id, entry, true);\n\t\t\t\t\t\t\t\t\tawait applyContentReferences(trx, collectionSlug, existing.id, edges);\n\n\t\t\t\t\t\t\t\t\t// Seed is declarative — when status is \"published\", promote to a live\n\t\t\t\t\t\t\t\t\t// revision so the admin UI shows \"Unpublish\" instead of \"Save & Publish\"\n\t\t\t\t\t\t\t\t\t// and `live_revision_id` is populated for downstream queries.\n\t\t\t\t\t\t\t\t\t//\n\t\t\t\t\t\t\t\t\t// Create a fresh revision from the updated data and stage it as the\n\t\t\t\t\t\t\t\t\t// draft so `publish()` picks it up instead of re-syncing stale data\n\t\t\t\t\t\t\t\t\t// from an existing live revision.\n\t\t\t\t\t\t\t\t\tif (status === \"published\") {\n\t\t\t\t\t\t\t\t\t\tconst draft = await trxRevisionRepo.create({\n\t\t\t\t\t\t\t\t\t\t\tcollection: collectionSlug,\n\t\t\t\t\t\t\t\t\t\t\tentryId: existing.id,\n\t\t\t\t\t\t\t\t\t\t\tdata: columnData,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\tawait trxContentRepo.setDraftRevision(collectionSlug, existing.id, draft.id);\n\t\t\t\t\t\t\t\t\t\t\tawait trxContentRepo.publish(\n\t\t\t\t\t\t\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\t\t\t\t\t\texisting.id,\n\t\t\t\t\t\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\t\tcollectionRoutable,\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\t\tawait trxRevisionRepo.deleteIfUnreferenced(\n\t\t\t\t\t\t\t\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\t\t\t\t\t\t\texisting.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\tdraft.id,\n\t\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\t} catch (cleanupError) {\n\t\t\t\t\t\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t\t\t\t\t\t`[seed] Failed to clean up unstaged revision ${draft.id}:`,\n\t\t\t\t\t\t\t\t\t\t\t\t\tcleanupError,\n\t\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tif (contentMutated) await markSeedContentCollectionStale(collectionSlug);\n\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tseedIdMap.set(entry.id, existing.id);\n\t\t\t\t\t\t\tresult.content.updated++;\n\t\t\t\t\t\t\tprogress.done++;\n\t\t\t\t\t\t\tawait markSeedContentCollectionStale(collectionSlug);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// skip\n\t\t\t\t\t\tresult.content.skipped++;\n\t\t\t\t\t\tseedIdMap.set(entry.id, existing.id);\n\t\t\t\t\t\tprogress.done++;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!mayCreateItem()) {\n\t\t\t\t\t\tcomplete = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Resolve $ref and $media in data\n\t\t\t\t\tlet resolvedData = await resolveReferences(entry.data, seedIdMap, mediaContext, result);\n\t\t\t\t\tif (collectionInfo) {\n\t\t\t\t\t\tresolvedData = await normalizeBlocksData(\n\t\t\t\t\t\t\tdb,\n\t\t\t\t\t\t\tcollectionInfo,\n\t\t\t\t\t\t\tresolvedData,\n\t\t\t\t\t\t\t{},\n\t\t\t\t\t\t\t{ restoreBlocks: true },\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\tresolvedBlockTypes,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\t// Reference fields are storage-less — route their resolved values to\n\t\t\t\t\t// edges and keep them out of the column/revision data.\n\t\t\t\t\tconst { columnData, edges } = splitReferenceFields(\n\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\treferenceFields,\n\t\t\t\t\t\tresolvedData,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Resolve translationOf: map from seed-local ID to real EmDash ID\n\t\t\t\t\tlet translationOf: string | undefined;\n\t\t\t\t\tif (entry.translationOf) {\n\t\t\t\t\t\tconst sourceId = seedIdMap.get(entry.translationOf);\n\t\t\t\t\t\tif (!sourceId) {\n\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t`content.${collectionSlug}: translationOf \"${entry.translationOf}\" not found (not yet created or missing). Skipping translation link.`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttranslationOf = sourceId;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create entry + bylines + taxonomies atomically\n\t\t\t\t\tconst status = entry.status || \"published\";\n\t\t\t\t\tlet contentMutated = false;\n\t\t\t\t\tlet created: Awaited<ReturnType<ContentRepository[\"create\"]>>;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcreated = await withTransaction(db, async (trx) => {\n\t\t\t\t\t\t\tconst trxContentRepo = new ContentRepository(trx, datetimeContexts);\n\t\t\t\t\t\t\tconst trxBylineRepo = new BylineRepository(trx);\n\n\t\t\t\t\t\t\tconst item = await trxContentRepo.create({\n\t\t\t\t\t\t\t\t...(entrySlug ? {} : { id: entry.id }),\n\t\t\t\t\t\t\t\ttype: collectionSlug,\n\t\t\t\t\t\t\t\tslug: entrySlug,\n\t\t\t\t\t\t\t\tstatus,\n\t\t\t\t\t\t\t\tdata: columnData,\n\t\t\t\t\t\t\t\tlocale: entryLocale,\n\t\t\t\t\t\t\t\ttranslationOf,\n\t\t\t\t\t\t\t\tpublishedAt: status === \"published\" ? new Date().toISOString() : null,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tcontentMutated = true;\n\n\t\t\t\t\t\t\tawait applyContentBylines(\n\t\t\t\t\t\t\t\ttrxBylineRepo,\n\t\t\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\t\titem.id,\n\t\t\t\t\t\t\t\tentry,\n\t\t\t\t\t\t\t\tseedBylineIdMap,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tawait applyContentTaxonomies(trx, collectionSlug, item.id, entry, false);\n\t\t\t\t\t\t\tawait applyContentReferences(trx, collectionSlug, item.id, edges);\n\n\t\t\t\t\t\t\t// Seed is declarative — when status is \"published\", promote to a live\n\t\t\t\t\t\t\t// revision so the admin UI shows \"Unpublish\" instead of \"Save & Publish\"\n\t\t\t\t\t\t\t// and `live_revision_id` is populated for downstream queries.\n\t\t\t\t\t\t\tif (status === \"published\") {\n\t\t\t\t\t\t\t\tawait trxContentRepo.publish(\n\t\t\t\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\t\t\titem.id,\n\t\t\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\tcollectionRoutable,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn item;\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tif (contentMutated) await markSeedContentCollectionStale(collectionSlug);\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\n\t\t\t\t\tseedIdMap.set(entry.id, created.id);\n\t\t\t\t\texistingEntries.set(entryKey, created);\n\t\t\t\t\tresult.content.created++;\n\t\t\t\t\tprogress.done++;\n\t\t\t\t\tawait markSeedContentCollectionStale(collectionSlug);\n\t\t\t\t}\n\t\t\t\tif (!complete) break;\n\t\t\t}\n\t\t} finally {\n\t\t\tawait retryFailedSeedContentStaleMarks();\n\t\t}\n\t}\n\n\tif (complete && !mayCreateItem()) {\n\t\tcomplete = false;\n\t}\n\tif (!complete) {\n\t\tawait invalidateSeedCaches();\n\t\treturn { result, complete, progress };\n\t}\n\n\t// 8. Menus and Menu Items (after content so refs can resolve)\n\tif (seed.menus) {\n\t\t// seed-local id -> resolved info, used to wire `translationOf` refs.\n\t\tconst menuSeedIdMap = new Map<string, { id: string; translationGroup: string }>();\n\t\t// Shared across menus: translated items reference anchor items in sibling menus.\n\t\tconst itemSeedIdMap = new Map<string, { id: string; translationGroup: string }>();\n\n\t\ttry {\n\t\t\tfor (const menu of seed.menus) {\n\t\t\t\tconst locale = resolveConfiguredLocale(menu.locale ?? defaultLocale);\n\t\t\t\tlet lookup = db\n\t\t\t\t\t.selectFrom(\"_emdash_menus\")\n\t\t\t\t\t.selectAll()\n\t\t\t\t\t.where(\"name\", \"=\", menu.name)\n\t\t\t\t\t.where(\"locale\", \"=\", locale);\n\t\t\t\tconst existingMenu = await lookup.executeTakeFirst();\n\n\t\t\t\tlet menuId: string;\n\t\t\t\tlet translationGroup: string;\n\n\t\t\t\tif (existingMenu) {\n\t\t\t\t\tmenuId = existingMenu.id;\n\t\t\t\t\ttranslationGroup = existingMenu.translation_group ?? existingMenu.id;\n\t\t\t\t\t// Clear existing items (menus are recreated)\n\t\t\t\t\tawait db.deleteFrom(\"_emdash_menu_items\").where(\"menu_id\", \"=\", menuId).execute();\n\t\t\t\t} else {\n\t\t\t\t\tmenuId = ulid();\n\t\t\t\t\t// Resolve translationOf to the source menu's translation_group.\n\t\t\t\t\ttranslationGroup = menuId;\n\t\t\t\t\tif (menu.translationOf) {\n\t\t\t\t\t\tconst source = menuSeedIdMap.get(menu.translationOf);\n\t\t\t\t\t\tif (source) translationGroup = source.translationGroup;\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t`menu \"${menu.name}\" (${locale}): translationOf \"${menu.translationOf}\" not found yet; minting a fresh group.`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.insertInto(\"_emdash_menus\")\n\t\t\t\t\t\t.values({\n\t\t\t\t\t\t\tid: menuId,\n\t\t\t\t\t\t\tname: menu.name,\n\t\t\t\t\t\t\tlabel: menu.label,\n\t\t\t\t\t\t\tcreated_at: new Date().toISOString(),\n\t\t\t\t\t\t\tupdated_at: new Date().toISOString(),\n\t\t\t\t\t\t\tlocale,\n\t\t\t\t\t\t\ttranslation_group: translationGroup,\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.execute();\n\t\t\t\t\tresult.menus.created++;\n\t\t\t\t}\n\n\t\t\t\tif (menu.id) menuSeedIdMap.set(menu.id, { id: menuId, translationGroup });\n\n\t\t\t\t// Create menu items\n\t\t\t\tconst itemCount = await applyMenuItems(\n\t\t\t\t\tdb,\n\t\t\t\t\tmenuId,\n\t\t\t\t\tlocale,\n\t\t\t\t\tmenu.items,\n\t\t\t\t\tnull, // parent_id\n\t\t\t\t\t0, // sort_order\n\t\t\t\t\tseedIdMap,\n\t\t\t\t\titemSeedIdMap,\n\t\t\t\t);\n\t\t\t\tresult.menus.items += itemCount;\n\t\t\t}\n\t\t} finally {\n\t\t\tinvalidateMenuObjectCache();\n\t\t}\n\t}\n\n\t// 9. Redirects\n\tif (seed.redirects) {\n\t\tconst redirectRepo = new RedirectRepository(db);\n\n\t\tfor (const redirect of seed.redirects) {\n\t\t\tconst existing = await redirectRepo.findBySource(redirect.source);\n\t\t\tif (existing) {\n\t\t\t\tif (onConflict === \"error\") {\n\t\t\t\t\tthrow new Error(`Conflict: redirect \"${redirect.source}\" already exists`);\n\t\t\t\t}\n\n\t\t\t\tif (onConflict === \"update\") {\n\t\t\t\t\tawait redirectRepo.update(existing.id, {\n\t\t\t\t\t\tdestination: redirect.destination,\n\t\t\t\t\t\ttype: redirect.type,\n\t\t\t\t\t\tenabled: redirect.enabled,\n\t\t\t\t\t\tgroupName: redirect.groupName,\n\t\t\t\t\t});\n\t\t\t\t\tresult.redirects.updated++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// skip\n\t\t\t\tresult.redirects.skipped++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tawait redirectRepo.create({\n\t\t\t\tsource: redirect.source,\n\t\t\t\tdestination: redirect.destination,\n\t\t\t\ttype: redirect.type,\n\t\t\t\tenabled: redirect.enabled,\n\t\t\t\tgroupName: redirect.groupName,\n\t\t\t});\n\t\t\tresult.redirects.created++;\n\t\t}\n\t}\n\n\t// 10. Widget Areas and Widgets\n\tif (seed.widgetAreas) {\n\t\tfor (const area of seed.widgetAreas) {\n\t\t\t// Check if area exists\n\t\t\tconst existingArea = await db\n\t\t\t\t.selectFrom(\"_emdash_widget_areas\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"name\", \"=\", area.name)\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tlet areaId: string;\n\n\t\t\tif (existingArea) {\n\t\t\t\tareaId = existingArea.id;\n\t\t\t\t// Clear existing widgets (areas are recreated)\n\t\t\t\tawait db.deleteFrom(\"_emdash_widgets\").where(\"area_id\", \"=\", areaId).execute();\n\t\t\t} else {\n\t\t\t\t// Create area\n\t\t\t\tareaId = ulid();\n\t\t\t\tawait db\n\t\t\t\t\t.insertInto(\"_emdash_widget_areas\")\n\t\t\t\t\t.values({\n\t\t\t\t\t\tid: areaId,\n\t\t\t\t\t\tname: area.name,\n\t\t\t\t\t\tlabel: area.label,\n\t\t\t\t\t\tdescription: area.description ?? null,\n\t\t\t\t\t})\n\t\t\t\t\t.execute();\n\t\t\t\tresult.widgetAreas.created++;\n\t\t\t}\n\n\t\t\t// Create widgets\n\t\t\tfor (let i = 0; i < area.widgets.length; i++) {\n\t\t\t\tconst widget = area.widgets[i];\n\t\t\t\tawait applyWidget(db, areaId, widget, i);\n\t\t\t\tresult.widgetAreas.widgets++;\n\t\t\t}\n\t\t}\n\t}\n\n\t// 11. Sections\n\tif (seed.sections) {\n\t\tfor (const section of seed.sections) {\n\t\t\t// Check if section exists\n\t\t\tconst existing = await db\n\t\t\t\t.selectFrom(\"_emdash_sections\")\n\t\t\t\t.select(\"id\")\n\t\t\t\t.where(\"slug\", \"=\", section.slug)\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tif (existing) {\n\t\t\t\tif (onConflict === \"error\") {\n\t\t\t\t\tthrow new Error(`Conflict: section \"${section.slug}\" already exists`);\n\t\t\t\t}\n\n\t\t\t\tif (onConflict === \"update\") {\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.updateTable(\"_emdash_sections\")\n\t\t\t\t\t\t.set({\n\t\t\t\t\t\t\ttitle: section.title,\n\t\t\t\t\t\t\tdescription: section.description ?? null,\n\t\t\t\t\t\t\tkeywords: section.keywords ? JSON.stringify(section.keywords) : null,\n\t\t\t\t\t\t\tcontent: JSON.stringify(section.content),\n\t\t\t\t\t\t\tsource: section.source || \"theme\",\n\t\t\t\t\t\t\tupdated_at: new Date().toISOString(),\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.where(\"id\", \"=\", existing.id)\n\t\t\t\t\t\t.execute();\n\t\t\t\t\tresult.sections.updated++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// skip\n\t\t\t\tresult.sections.skipped++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst id = ulid();\n\t\t\tconst now = new Date().toISOString();\n\n\t\t\tawait db\n\t\t\t\t.insertInto(\"_emdash_sections\")\n\t\t\t\t.values({\n\t\t\t\t\tid,\n\t\t\t\t\tslug: section.slug,\n\t\t\t\t\ttitle: section.title,\n\t\t\t\t\tdescription: section.description ?? null,\n\t\t\t\t\tkeywords: section.keywords ? JSON.stringify(section.keywords) : null,\n\t\t\t\t\tcontent: JSON.stringify(section.content),\n\t\t\t\t\tpreview_media_id: null,\n\t\t\t\t\tsource: section.source || \"theme\",\n\t\t\t\t\ttheme_id: section.source === \"theme\" ? section.slug : null,\n\t\t\t\t\tcreated_at: now,\n\t\t\t\t\tupdated_at: now,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\tresult.sections.created++;\n\t\t}\n\t}\n\n\t// 11. Enable search for collections that have `search` in supports\n\tif (seed.collections) {\n\t\tconst ftsManager = new FTSManager(db);\n\n\t\tfor (const collection of seed.collections) {\n\t\t\tif (collection.supports?.includes(\"search\")) {\n\t\t\t\t// Check if there are searchable fields\n\t\t\t\tconst searchableFields = await ftsManager.getSearchableFields(collection.slug);\n\t\t\t\tif (searchableFields.length > 0) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait ftsManager.enableSearch(collection.slug);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t// Log but don't fail - search can be enabled manually later\n\t\t\t\t\t\tconsole.warn(`Failed to enable search for ${collection.slug}:`, err);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (result.redirects.created + result.redirects.updated > 0) {\n\t\tconst { publishRedirectChanges } = await import(\"../redirects/artifacts.js\");\n\t\tawait publishRedirectChanges(db);\n\t}\n\tawait invalidateSeedCaches();\n\n\treturn { result, complete, progress };\n}\n\nfunction countSeedItems(seed: SeedFile, includeContent: boolean): number {\n\tif (!includeContent) return 0;\n\tconst terms = (seed.taxonomies ?? []).reduce(\n\t\t(total, taxonomy) => total + (taxonomy.terms?.length ?? 0),\n\t\t0,\n\t);\n\tconst entries = Object.values(seed.content ?? {}).reduce(\n\t\t(total, collectionEntries) => total + collectionEntries.length,\n\t\t0,\n\t);\n\treturn terms + (seed.bylines?.length ?? 0) + entries;\n}\n\nfunction seedTermKey(locale: string, slug: string): string {\n\treturn `${locale}::${slug}`;\n}\n\n/**\n * Find which of a taxonomy's seed terms already exist, as term ids keyed by\n * `seedTermKey`. Each query covers a batch of slugs.\n */\nasync function findExistingSeedTerms(\n\tdb: Kysely<Database>,\n\ttaxonomyName: string,\n\tterms: SeedTaxonomyTerm[],\n): Promise<Map<string, string>> {\n\tconst existing = new Map<string, string>();\n\tfor (const batch of chunks([...new Set(terms.map((term) => term.slug))], SQL_BATCH_SIZE)) {\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"taxonomies\")\n\t\t\t.select([\"id\", \"slug\", \"locale\"])\n\t\t\t.where(\"name\", \"=\", taxonomyName)\n\t\t\t.where(\"slug\", \"in\", batch)\n\t\t\t.execute();\n\t\tfor (const row of rows) existing.set(seedTermKey(row.locale, row.slug), row.id);\n\t}\n\treturn existing;\n}\n\n/**\n * Find which seed bylines already exist, as byline ids keyed by slug. A slug\n * in several locales resolves to the lowest locale code, as\n * `BylineRepository.findBySlug` does. Each query covers a batch of slugs.\n */\nasync function findExistingSeedBylines(\n\tdb: Kysely<Database>,\n\tslugs: string[],\n): Promise<Map<string, string>> {\n\tconst existing = new Map<string, string>();\n\tfor (const batch of chunks([...new Set(slugs)], SQL_BATCH_SIZE)) {\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_bylines\")\n\t\t\t.select([\"id\", \"slug\"])\n\t\t\t.where(\"slug\", \"in\", batch)\n\t\t\t.orderBy(\"locale\", \"asc\")\n\t\t\t.execute();\n\t\tfor (const row of rows) {\n\t\t\tif (!existing.has(row.slug)) existing.set(row.slug, row.id);\n\t\t}\n\t}\n\treturn existing;\n}\n\n/**\n * Invalidate caches that may have been affected by seed data.\n * Seed creates bylines, redirects, and collections, all of which\n * have module-level caches in the hot path.\n */\nasync function invalidateSeedCaches(): Promise<void> {\n\tconst { invalidateBylineCache } = await import(\"../bylines/index.js\");\n\tconst { invalidateRedirectCache } = await import(\"../redirects/cache.js\");\n\tconst { invalidateUrlPatternCache } = await import(\"../query.js\");\n\tinvalidateBylineCache();\n\tinvalidateRedirectCache();\n\tinvalidateUrlPatternCache();\n}\n\nfunction seedEntryIdentity(\n\tentry: SeedContentEntry,\n\tdefaultLocale: string,\n): { slug: string | null; locale: string } {\n\treturn {\n\t\tslug: typeof entry.slug === \"string\" && entry.slug.trim().length > 0 ? entry.slug : null,\n\t\t// Resolve the entry's locale up front so a non-`en` single-locale\n\t\t// export (which omits `locale`) is filed under the project default\n\t\t// rather than `en`.\n\t\tlocale: resolveConfiguredLocale(entry.locale ?? defaultLocale),\n\t};\n}\n\n/** Every relation the database knows, by slug, with the collections it joins. */\ntype RelationEnds = Map<string, { parentCollection: string; childCollection: string }>;\n\nasync function readRelationEnds(db: Kysely<Database>): Promise<RelationEnds> {\n\tconst rows = await db\n\t\t.selectFrom(\"_emdash_relations\")\n\t\t.select([\"slug\", \"parent_collection\", \"child_collection\"])\n\t\t.execute();\n\treturn new Map(\n\t\trows.map((row) => [\n\t\t\trow.slug,\n\t\t\t{ parentCollection: row.parent_collection, childCollection: row.child_collection },\n\t\t]),\n\t);\n}\n\n/**\n * Resolve the relation a seed reference field names, and which end of it this\n * collection sits on.\n *\n * `relationSide` is only needed for a self-referential relation, where both ends\n * are this collection; otherwise the side follows from which end matches.\n * `targetCollection` is the collection at the other end, derived rather than\n * trusted, so a seed cannot declare a target the relation disagrees with.\n */\nfunction resolveSeedFieldRelation(\n\tcollectionSlug: string,\n\tfield: SeedField,\n\tknownRelations: RelationEnds,\n): { relation: string; relationSide: \"parent\" | \"child\"; targetCollection: string } | null {\n\tif (field.type !== \"reference\") return null;\n\tconst named = field.validation?.relation;\n\tif (typeof named !== \"string\" || named.length === 0) return null;\n\n\tconst relation = knownRelations.get(named);\n\tif (!relation) {\n\t\tthrow new SchemaError(`Relation \"${named}\" not found`, \"RELATION_NOT_FOUND\");\n\t}\n\n\tconst declared = field.validation?.relationSide;\n\tconst side =\n\t\tdeclared === \"parent\" || declared === \"child\"\n\t\t\t? declared\n\t\t\t: relation.parentCollection === collectionSlug\n\t\t\t\t? \"parent\"\n\t\t\t\t: \"child\";\n\n\tconst end = side === \"parent\" ? relation.parentCollection : relation.childCollection;\n\tif (end !== collectionSlug) {\n\t\tthrow new SchemaError(\n\t\t\t`Relation \"${named}\" has no ${side} end on collection \"${collectionSlug}\"`,\n\t\t\t\"VALIDATION_ERROR\",\n\t\t);\n\t}\n\n\treturn {\n\t\trelation: named,\n\t\trelationSide: side,\n\t\ttargetCollection: side === \"parent\" ? relation.childCollection : relation.parentCollection,\n\t};\n}\n\n/**\n * Create or update the relations a seed declares, before the fields that name\n * them.\n *\n * A relation's two collections are fixed once it exists: changing one would\n * leave its links pointing into a collection that is no longer an end of it, so\n * a seed that names different ones fails rather than rewriting the row. Labels\n * and limits are updated under `onConflict: \"update\"`.\n */\nasync function applySeedRelations(\n\tdb: Kysely<Database>,\n\trelations: SeedRelation[],\n\tseedCollections: SeedCollection[],\n\tonConflict: \"skip\" | \"update\" | \"error\",\n\tresult: SeedApplyResult,\n): Promise<void> {\n\tconst existing = await readRelationEnds(db);\n\tconst known = new Set(seedCollections.map((collection) => collection.slug));\n\tfor (const row of await db.selectFrom(\"_emdash_collections\").select(\"slug\").execute()) {\n\t\tknown.add(row.slug);\n\t}\n\n\tconst now = new Date().toISOString();\n\tfor (const relation of relations) {\n\t\tfor (const end of [relation.parentCollection, relation.childCollection]) {\n\t\t\tif (!known.has(end)) {\n\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t`Relation \"${relation.slug}\" names collection \"${end}\", which does not exist`,\n\t\t\t\t\t\"COLLECTION_NOT_FOUND\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst current = existing.get(relation.slug);\n\t\tif (current) {\n\t\t\tif (\n\t\t\t\tcurrent.parentCollection !== relation.parentCollection ||\n\t\t\t\tcurrent.childCollection !== relation.childCollection\n\t\t\t) {\n\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t`Relation \"${relation.slug}\" joins ${current.parentCollection} to ${current.childCollection}; ` +\n\t\t\t\t\t\t`a relation's collections cannot change`,\n\t\t\t\t\t\"RELATION_COLLECTIONS_IMMUTABLE\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (onConflict === \"error\") {\n\t\t\t\tthrow new Error(`Conflict: relation \"${relation.slug}\" already exists`);\n\t\t\t}\n\t\t\tif (onConflict !== \"update\") {\n\t\t\t\tresult.relations.skipped++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tawait db\n\t\t\t\t.updateTable(\"_emdash_relations\")\n\t\t\t\t.set({\n\t\t\t\t\tparent_label: relation.parentLabel,\n\t\t\t\t\tparent_label_singular: relation.parentLabelSingular ?? null,\n\t\t\t\t\tchild_label: relation.childLabel,\n\t\t\t\t\tchild_label_singular: relation.childLabelSingular ?? null,\n\t\t\t\t\tmax_children_per_parent: relation.maxChildrenPerParent ?? null,\n\t\t\t\t\tmax_parents_per_child: relation.maxParentsPerChild ?? null,\n\t\t\t\t\tupdated_at: now,\n\t\t\t\t})\n\t\t\t\t.where(\"slug\", \"=\", relation.slug)\n\t\t\t\t.execute();\n\t\t\tresult.relations.updated++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_relations\")\n\t\t\t.values({\n\t\t\t\tid: ulid(),\n\t\t\t\tslug: relation.slug,\n\t\t\t\tparent_collection: relation.parentCollection,\n\t\t\t\tchild_collection: relation.childCollection,\n\t\t\t\tparent_label: relation.parentLabel,\n\t\t\t\tparent_label_singular: relation.parentLabelSingular ?? null,\n\t\t\t\tchild_label: relation.childLabel,\n\t\t\t\tchild_label_singular: relation.childLabelSingular ?? null,\n\t\t\t\tmax_children_per_parent: relation.maxChildrenPerParent ?? null,\n\t\t\t\tmax_parents_per_child: relation.maxParentsPerChild ?? null,\n\t\t\t\tcreated_at: now,\n\t\t\t\tupdated_at: now,\n\t\t\t})\n\t\t\t.execute();\n\t\texisting.set(relation.slug, {\n\t\t\tparentCollection: relation.parentCollection,\n\t\t\tchildCollection: relation.childCollection,\n\t\t});\n\t\tresult.relations.created++;\n\t}\n}\n\nfunction allocateSeedRelationName(\n\tcollectionSlug: string,\n\tfieldSlug: string,\n\tusedNames: Set<string>,\n): string {\n\tconst baseName = `${collectionSlug}_${fieldSlug}`.slice(0, 63);\n\tfor (let attempt = 0; attempt < SEED_RELATION_NAME_MAX_ATTEMPTS; attempt++) {\n\t\tconst suffix = attempt === 0 ? \"\" : `_${attempt + 1}`;\n\t\tconst name = attempt === 0 ? baseName : `${baseName.slice(0, 63 - suffix.length)}${suffix}`;\n\t\tif (!usedNames.has(name)) {\n\t\t\tusedNames.add(name);\n\t\t\treturn name;\n\t\t}\n\t}\n\tthrow new SchemaError(\"Could not allocate a unique relation name\", \"RELATION_NAME_CONFLICT\");\n}\n\n/**\n * Slugful entries use the existing locale-aware key. Slugless seed entries\n * persist their seed ID, which keeps re-application idempotent.\n */\nfunction seedEntryKey(entry: SeedContentEntry, slug: string | null, locale: string): string {\n\treturn slug === null ? `id:${entry.id}` : `slug:${locale}:${slug}`;\n}\n\n/**\n * Find which of a collection's seed entries already exist, keyed by\n * `seedEntryKey`. Each query covers a batch of entries.\n */\nasync function findExistingSeedEntries(\n\trepo: ContentRepository,\n\tcollectionSlug: string,\n\tentries: SeedContentEntry[],\n\tdefaultLocale: string,\n\tincludeTrashed = false,\n): Promise<Map<string, ContentItem>> {\n\tconst identities = entries.map((entry) => ({\n\t\tentry,\n\t\t...seedEntryIdentity(entry, defaultLocale),\n\t}));\n\tconst slugsByLocale = new Map<string, string[]>();\n\tfor (const { slug, locale } of identities) {\n\t\tif (slug === null) continue;\n\t\tconst slugs = slugsByLocale.get(locale);\n\t\tif (slugs) slugs.push(slug);\n\t\telse slugsByLocale.set(locale, [slug]);\n\t}\n\n\tconst bySlug = new Map<string, Map<string, ContentItem>>();\n\tfor (const [locale, slugs] of slugsByLocale) {\n\t\tbySlug.set(\n\t\t\tlocale,\n\t\t\tawait repo.findManyBySlugsInLocale(collectionSlug, slugs, locale, { includeTrashed }),\n\t\t);\n\t}\n\tconst byId = await repo.findManyByIds(\n\t\tcollectionSlug,\n\t\tidentities.filter(({ slug }) => slug === null).map(({ entry }) => entry.id),\n\t\t{ includeTrashed },\n\t);\n\n\tconst existing = new Map<string, ContentItem>();\n\tfor (const { entry, slug, locale } of identities) {\n\t\tconst item = slug === null ? byId.get(entry.id) : bySlug.get(locale)?.get(slug);\n\t\tif (item) existing.set(seedEntryKey(entry, slug, locale), item);\n\t}\n\treturn existing;\n}\n\n/**\n * Apply hierarchical taxonomy terms (parents before children). Stops before\n * creating a term once `mayCreate` returns false, and returns how many terms\n * were found or created.\n */\nasync function applyHierarchicalTerms(\n\ttermRepo: TaxonomyRepository,\n\ttaxonomyName: string,\n\tdefLocale: string,\n\tterms: SeedTaxonomyTerm[],\n\texistingTerms: Map<string, string>,\n\ttermSeedIdMap: Map<string, string>,\n\tresult: SeedApplyResult,\n\tonConflict: \"skip\" | \"update\" | \"error\",\n\tmayCreate: () => boolean,\n): Promise<{ processed: number; complete: boolean }> {\n\tlet processed = 0;\n\t// \"locale::slug\" -> id, so the same slug can resolve per locale.\n\tconst slugToId = new Map<string, string>();\n\tconst resolveTermLocale = (term: SeedTaxonomyTerm) =>\n\t\tresolveConfiguredLocale(term.locale ?? defLocale);\n\n\t// Multiple passes — handles deep nesting and translationOf forward refs.\n\tlet remaining = [...terms];\n\tlet maxPasses = 10;\n\n\twhile (remaining.length > 0 && maxPasses > 0) {\n\t\tconst processedThisPass: string[] = [];\n\n\t\tfor (const term of remaining) {\n\t\t\tconst termLocale = resolveTermLocale(term);\n\t\t\tconst parentReady = !term.parent || slugToId.has(`${termLocale}::${term.parent}`);\n\t\t\tconst translationReady = !term.translationOf || termSeedIdMap.has(term.translationOf);\n\n\t\t\tif (!parentReady || !translationReady) continue;\n\n\t\t\tconst parentId = term.parent ? slugToId.get(`${termLocale}::${term.parent}`) : undefined;\n\t\t\tconst translationOf = term.translationOf ? termSeedIdMap.get(term.translationOf) : undefined;\n\n\t\t\tconst termKey = seedTermKey(termLocale, term.slug);\n\t\t\tconst existingId = existingTerms.get(termKey);\n\t\t\tif (existingId) {\n\t\t\t\tif (onConflict === \"error\") {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Conflict: taxonomy term \"${term.slug}\" in \"${taxonomyName}\" (${termLocale}) already exists`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (onConflict === \"update\") {\n\t\t\t\t\tawait termRepo.update(existingId, {\n\t\t\t\t\t\tlabel: term.label,\n\t\t\t\t\t\tparentId,\n\t\t\t\t\t\tdata: term.description ? { description: term.description } : {},\n\t\t\t\t\t});\n\t\t\t\t\tresult.taxonomies.terms++;\n\t\t\t\t}\n\t\t\t\tslugToId.set(termKey, existingId);\n\t\t\t\tif (term.id) termSeedIdMap.set(term.id, existingId);\n\t\t\t} else {\n\t\t\t\tif (!mayCreate()) return { processed, complete: false };\n\t\t\t\tconst created = await termRepo.create({\n\t\t\t\t\tname: taxonomyName,\n\t\t\t\t\tslug: term.slug,\n\t\t\t\t\tlabel: term.label,\n\t\t\t\t\tparentId,\n\t\t\t\t\tdata: term.description ? { description: term.description } : undefined,\n\t\t\t\t\tlocale: termLocale,\n\t\t\t\t\ttranslationOf,\n\t\t\t\t});\n\t\t\t\texistingTerms.set(termKey, created.id);\n\t\t\t\tslugToId.set(termKey, created.id);\n\t\t\t\tif (term.id) termSeedIdMap.set(term.id, created.id);\n\t\t\t\tresult.taxonomies.terms++;\n\t\t\t}\n\t\t\tprocessed++;\n\n\t\t\tprocessedThisPass.push(term.slug + \"::\" + termLocale);\n\t\t}\n\n\t\tremaining = remaining.filter(\n\t\t\t(term) => !processedThisPass.includes(term.slug + \"::\" + resolveTermLocale(term)),\n\t\t);\n\t\tmaxPasses--;\n\t}\n\n\tif (remaining.length > 0) {\n\t\tconsole.warn(`Could not process ${remaining.length} terms due to missing parents/translations`);\n\t}\n\treturn { processed, complete: true };\n}\n\n/**\n * Apply byline credits to a content entry.\n * In update mode, clears existing credits even if the seed has none.\n */\nasync function applyContentBylines(\n\tbylineRepo: BylineRepository,\n\tcollectionSlug: string,\n\tcontentId: string,\n\tentry: {\n\t\tid: string;\n\t\tslug?: string | null;\n\t\tbylines?: Array<{ byline: string; roleLabel?: string }>;\n\t},\n\tseedBylineIdMap: Map<string, string>,\n\tisUpdate = false,\n): Promise<void> {\n\tif (!entry.bylines || entry.bylines.length === 0) {\n\t\t// In update mode, clear existing bylines when the seed entry has none\n\t\tif (isUpdate) {\n\t\t\tawait bylineRepo.setContentBylines(collectionSlug, contentId, []);\n\t\t}\n\t\treturn;\n\t}\n\n\tconst credits = entry.bylines\n\t\t.map((credit) => {\n\t\t\tconst bylineId = seedBylineIdMap.get(credit.byline);\n\t\t\tif (!bylineId) return null;\n\t\t\treturn {\n\t\t\t\tbylineId,\n\t\t\t\troleLabel: credit.roleLabel ?? null,\n\t\t\t};\n\t\t})\n\t\t.filter((credit): credit is { bylineId: string; roleLabel: string | null } => Boolean(credit));\n\n\tif (credits.length !== entry.bylines.length) {\n\t\tconsole.warn(\n\t\t\t`content.${collectionSlug}.${entry.slug ?? entry.id}: one or more byline refs could not be resolved`,\n\t\t);\n\t}\n\n\t// In update mode, always call setContentBylines (even with empty credits)\n\t// to clear stale assignments when all byline refs fail to resolve.\n\t// In create mode, only call if there are credits to assign.\n\tif (credits.length > 0 || isUpdate) {\n\t\tawait bylineRepo.setContentBylines(collectionSlug, contentId, credits);\n\t}\n}\n\n/**\n * Apply taxonomy term assignments to a content entry.\n * In update mode, clears existing assignments before re-attaching.\n */\n/**\n * Create or update a field from a seed.\n *\n * A reference field bound to a relation is storage-less: it persists no column\n * and its edges live in `_emdash_content_references`. Seeds create fields\n * through the registry (not the schema handler that owns the relation\n * lifecycle), so this mirrors the handler — it creates the relation on first\n * insert (field + relation in one transaction) and preserves the\n * server-assigned `validation.relation`/`targetCollection` on re-apply, since a\n * seed's field validation omits them and would otherwise orphan the relation. A\n * reference field with no `targetCollection` cannot form a relation, so it is\n * created column-backed, holding a plain entry id.\n */\nasync function upsertSeedField(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tfield: SeedField,\n\texisting: Field | null,\n\tknownRelations: RelationEnds,\n): Promise<void> {\n\tconst bound = resolveSeedFieldRelation(collectionSlug, field, knownRelations);\n\n\tif (existing) {\n\t\tconst update = {\n\t\t\tlabel: field.label,\n\t\t\ttype: field.type,\n\t\t\trequired: field.required || false,\n\t\t\tunique: field.unique || false,\n\t\t\tsearchable: field.searchable || false,\n\t\t\tindexed: field.indexed || false,\n\t\t\ttranslatable: field.translatable,\n\t\t\tdefaultValue: field.defaultValue,\n\t\t\twidget: field.widget,\n\t\t\toptions: field.options,\n\t\t};\n\n\t\t// A reference field from before relations existed has no relation to\n\t\t// preserve, so a seed naming a target binds it the way the admin does\n\t\t// rather than leaving it unbound forever.\n\t\tif (\n\t\t\t!bound &&\n\t\t\tfield.type === \"reference\" &&\n\t\t\t!existing.validation?.relation &&\n\t\t\ttypeof field.validation?.targetCollection === \"string\"\n\t\t) {\n\t\t\tawait bindReferenceField(\n\t\t\t\tdb,\n\t\t\t\tcollectionSlug,\n\t\t\t\texisting,\n\t\t\t\t{ ...update, validation: field.validation },\n\t\t\t\tfield.validation.targetCollection,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// A field naming a relation binds to that one. Otherwise keep whatever\n\t\t// relation the field is already bound to: a seed's field validation omits\n\t\t// the server-assigned keys and would orphan the relation row.\n\t\tconst validation = bound\n\t\t\t? { ...field.validation, ...bound }\n\t\t\t: field.type === \"reference\" && existing.validation?.relation\n\t\t\t\t? {\n\t\t\t\t\t\t...field.validation,\n\t\t\t\t\t\trelation: existing.validation.relation,\n\t\t\t\t\t\trelationSide: existing.validation.relationSide,\n\t\t\t\t\t\ttargetCollection: existing.validation.targetCollection,\n\t\t\t\t\t}\n\t\t\t\t: field.validation;\n\t\tconst registry = new SchemaRegistry(db);\n\t\tawait registry.updateField(collectionSlug, field.slug, { ...update, validation });\n\t\treturn;\n\t}\n\n\tconst input = {\n\t\tslug: field.slug,\n\t\tlabel: field.label,\n\t\ttype: field.type,\n\t\trequired: field.required || false,\n\t\tunique: field.unique || false,\n\t\tsearchable: field.searchable || false,\n\t\tindexed: field.indexed || false,\n\t\ttranslatable: field.translatable,\n\t\tdefaultValue: field.defaultValue,\n\t\tvalidation: field.validation,\n\t\twidget: field.widget,\n\t\toptions: field.options,\n\t};\n\n\tif (bound) {\n\t\tconst registry = new SchemaRegistry(db);\n\t\tawait registry.createField(collectionSlug, {\n\t\t\t...input,\n\t\t\tvalidation: { ...field.validation, ...bound },\n\t\t});\n\t\treturn;\n\t}\n\n\tconst targetCollection =\n\t\tfield.type === \"reference\" && typeof field.validation?.targetCollection === \"string\"\n\t\t\t? field.validation.targetCollection\n\t\t\t: undefined;\n\n\tif (targetCollection) {\n\t\tawait withTransaction(db, async (trx) => {\n\t\t\tconst relation = await createFieldRelation(\n\t\t\t\ttrx,\n\t\t\t\tcollectionSlug,\n\t\t\t\tfield.slug,\n\t\t\t\tfield.label,\n\t\t\t\ttargetCollection,\n\t\t\t\tfield.validation?.multiple ? null : 1,\n\t\t\t);\n\t\t\tconst registry = new SchemaRegistry(trx);\n\t\t\tawait registry.createField(collectionSlug, {\n\t\t\t\t...input,\n\t\t\t\tvalidation: {\n\t\t\t\t\t...field.validation,\n\t\t\t\t\trelation: relation.slug,\n\t\t\t\t\trelationSide: \"parent\" as const,\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t\treturn;\n\t}\n\n\tconst registry = new SchemaRegistry(db);\n\tawait registry.createField(collectionSlug, input);\n}\n\n/**\n * Split resolved content `data` into the plain column data and the reference\n * edge writes. A reference field bound to a relation is storage-less, so its key\n * left in `data` would hit the column writer (and `syncDataColumns` on publish)\n * and throw \"no such column\". Its `$ref:`-resolved value — a child entry id or\n * an array of them — is captured as an edge write instead, keyed by the field's\n * relation. A reference field with no relation still owns its column, so its\n * resolved id is written there like any other string.\n */\n/** One collection's reference fields by slug, read once per collection: the\n * schema phase has finished by the time content is applied. */\nfunction referenceFieldsOf(collection: CollectionWithFields | null): Map<string, Field> {\n\treturn new Map(\n\t\t(collection?.fields ?? []).filter((f) => f.type === \"reference\").map((f) => [f.slug, f]),\n\t);\n}\n\nfunction splitReferenceFields(\n\tcollectionSlug: string,\n\treferenceFields: Map<string, Field>,\n\tdata: Record<string, unknown>,\n): {\n\tcolumnData: Record<string, unknown>;\n\tedges: Array<{ fieldSlug: string; childIds: string[] }>;\n} {\n\tif (referenceFields.size === 0) return { columnData: data, edges: [] };\n\n\tconst columnData: Record<string, unknown> = {};\n\tconst edges: Array<{ fieldSlug: string; childIds: string[] }> = [];\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tconst field = referenceFields.get(key);\n\t\tif (!field?.validation?.relation) {\n\t\t\tcolumnData[key] = value;\n\t\t\tcontinue;\n\t\t}\n\t\tconst childIds: string[] = [];\n\t\tfor (const candidate of Array.isArray(value) ? value : [value]) {\n\t\t\tif (typeof candidate !== \"string\" || candidate.length === 0) continue;\n\t\t\t// `seedIdMap` fills forward-only, so a reference pointing at a collection\n\t\t\t// emitted later in the file arrives here unresolved.\n\t\t\tif (candidate.startsWith(\"$ref:\")) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`content.${collectionSlug}: reference \"${candidate}\" in field \"${key}\" did not resolve (not yet created or missing). Skipping.`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tchildIds.push(candidate);\n\t\t}\n\t\tedges.push({ fieldSlug: key, childIds });\n\t}\n\treturn { columnData, edges };\n}\n\n/**\n * Write reference edges for a content entry, replacing any existing set per\n * relation (so re-applying a seed is idempotent). Throws to abort the enclosing\n * transaction if a child entry cannot be resolved — a half-written entry is\n * worse than a failed apply.\n */\nasync function applyContentReferences(\n\ttrx: Kysely<Database>,\n\tcollectionSlug: string,\n\tcontentId: string,\n\tedges: Array<{ fieldSlug: string; childIds: string[] }>,\n): Promise<void> {\n\tfor (const { fieldSlug, childIds } of edges) {\n\t\tconst result = await setReferenceSelection(trx, collectionSlug, contentId, fieldSlug, childIds);\n\t\tif (!result.success) {\n\t\t\tthrow new Error(\n\t\t\t\t`content.${collectionSlug}: failed to write references for \"${contentId}\": ${result.error.message}`,\n\t\t\t);\n\t\t}\n\t}\n}\n\nasync function applyContentTaxonomies(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tcontentId: string,\n\tentry: { taxonomies?: Record<string, string[]> },\n\tisUpdate: boolean,\n): Promise<void> {\n\tconst termRepo = new TaxonomyRepository(db);\n\t// In update mode, clear existing taxonomy assignments first\n\tif (isUpdate) {\n\t\tawait termRepo.clearEntryTerms(collectionSlug, contentId);\n\t}\n\n\tif (!entry.taxonomies) {\n\t\t// In update mode we may have just deleted rows above; invalidate so\n\t\t// hydration doesn't serve stale \"has terms\" cached value.\n\t\tif (isUpdate) {\n\t\t\tconst { invalidateTermCache } = await import(\"../taxonomies/index.js\");\n\t\t\tinvalidateTermCache();\n\t\t}\n\t\treturn;\n\t}\n\n\tfor (const [taxonomyName, termSlugs] of Object.entries(entry.taxonomies)) {\n\t\tfor (const termSlug of termSlugs) {\n\t\t\tconst term = await termRepo.findBySlug(taxonomyName, termSlug);\n\t\t\tif (term) {\n\t\t\t\tawait termRepo.attachToEntry(collectionSlug, contentId, term.id);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Seed writes directly to content_taxonomies. Clear the cache so\n\t// the worker lifetime cached \"has any term assignments\" probe\n\t// re-runs on the next read.\n\tconst { invalidateTermCache } = await import(\"../taxonomies/index.js\");\n\tinvalidateTermCache();\n}\n\n/**\n * Apply menu items recursively.\n *\n * When a `SeedMenuItem` carries `id`/`translationOf`, the import resolves the\n * source item's `translation_group` so cross-locale \"same nav entry\" links\n * survive export → apply. Items without `translationOf` get a fresh group\n * (= their own id).\n */\nasync function applyMenuItems(\n\tdb: Kysely<Database>,\n\tmenuId: string,\n\tlocale: string,\n\titems: SeedMenuItem[],\n\tparentId: string | null,\n\tstartOrder: number,\n\tseedIdMap: Map<string, string>,\n\titemSeedIdMap: Map<string, { id: string; translationGroup: string }>,\n): Promise<number> {\n\tlet count = 0;\n\tlet order = startOrder;\n\n\tfor (const item of items) {\n\t\tconst itemId = ulid();\n\t\tconst itemLocale = item.locale ?? locale;\n\n\t\t// Resolve reference if needed\n\t\tlet referenceId: string | null = null;\n\t\tlet referenceCollection: string | null = null;\n\n\t\tif (item.type !== \"custom\" && item.type !== \"taxonomy\") {\n\t\t\tconst collection =\n\t\t\t\titem.collection || (item.type === \"page\" || item.type === \"post\" ? `${item.type}s` : null);\n\t\t\tif (item.ref) {\n\t\t\t\t// An unresolved ref stays fully unset: a \"collection\" item that kept\n\t\t\t\t// its collection would render as that collection's archive link.\n\t\t\t\tconst resolved = seedIdMap.get(item.ref);\n\t\t\t\tif (resolved && collection) {\n\t\t\t\t\treferenceId = resolved;\n\t\t\t\t\treferenceCollection = collection;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treferenceCollection = collection;\n\t\t\t}\n\t\t}\n\n\t\tlet translationGroup = itemId;\n\t\tif (item.translationOf) {\n\t\t\tconst source = itemSeedIdMap.get(item.translationOf);\n\t\t\tif (source) translationGroup = source.translationGroup;\n\t\t\telse\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`menu item \"${item.label ?? item.url ?? item.ref ?? \"(unlabeled)\"}\" (${itemLocale}): translationOf \"${item.translationOf}\" not found yet; minting a fresh group.`,\n\t\t\t\t);\n\t\t}\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_menu_items\")\n\t\t\t.values({\n\t\t\t\tid: itemId,\n\t\t\t\tmenu_id: menuId,\n\t\t\t\tparent_id: parentId,\n\t\t\t\tsort_order: order,\n\t\t\t\ttype: item.type,\n\t\t\t\treference_collection: referenceCollection,\n\t\t\t\treference_id: referenceId,\n\t\t\t\tcustom_url: item.url ?? null,\n\t\t\t\tlabel: item.label || \"\",\n\t\t\t\ttitle_attr: item.titleAttr ?? null,\n\t\t\t\ttarget: item.target ?? null,\n\t\t\t\tcss_classes: item.cssClasses ?? null,\n\t\t\t\tcreated_at: new Date().toISOString(),\n\t\t\t\tlocale: itemLocale,\n\t\t\t\ttranslation_group: translationGroup,\n\t\t\t})\n\t\t\t.execute();\n\n\t\tif (item.id) itemSeedIdMap.set(item.id, { id: itemId, translationGroup });\n\n\t\tcount++;\n\t\torder++;\n\n\t\tif (item.children && item.children.length > 0) {\n\t\t\tconst childCount = await applyMenuItems(\n\t\t\t\tdb,\n\t\t\t\tmenuId,\n\t\t\t\titemLocale,\n\t\t\t\titem.children,\n\t\t\t\titemId,\n\t\t\t\t0,\n\t\t\t\tseedIdMap,\n\t\t\t\titemSeedIdMap,\n\t\t\t);\n\t\t\tcount += childCount;\n\t\t}\n\t}\n\n\treturn count;\n}\n\n/**\n * Apply a widget\n */\nasync function applyWidget(\n\tdb: Kysely<Database>,\n\tareaId: string,\n\twidget: SeedWidget,\n\tsortOrder: number,\n): Promise<void> {\n\tawait db\n\t\t.insertInto(\"_emdash_widgets\")\n\t\t.values({\n\t\t\tid: ulid(),\n\t\t\tarea_id: areaId,\n\t\t\tsort_order: sortOrder,\n\t\t\ttype: widget.type,\n\t\t\ttitle: widget.title ?? null,\n\t\t\t// `widget.content` is Portable Text for content-type widgets;\n\t\t\t// for other widget kinds it's null.\n\t\t\tcontent: widget.content ? JSON.stringify(widget.content) : null,\n\t\t\tmenu_name: widget.menuName ?? null,\n\t\t\tcomponent_id: widget.componentId ?? null,\n\t\t\tcomponent_props: widget.props ? JSON.stringify(widget.props) : null,\n\t\t})\n\t\t.execute();\n}\n\n/**\n * Context for media resolution during seed application\n */\ninterface MediaContext {\n\tdb: Kysely<Database>;\n\tstorage: Storage | null;\n\tskipMediaDownload: boolean;\n\tmediaCache: Map<string, MediaValue>; // URL -> resolved MediaValue\n\tbudget: SeedBudget | null;\n}\n\n/**\n * Type guard for $media reference\n */\nfunction isSeedMediaReference(value: unknown): value is SeedMediaReference {\n\tif (typeof value !== \"object\" || value === null || !(\"$media\" in value)) {\n\t\treturn false;\n\t}\n\tconst media = (value as Record<string, unknown>).$media;\n\treturn (\n\t\ttypeof media === \"object\" &&\n\t\tmedia !== null &&\n\t\t\"url\" in media &&\n\t\ttypeof (media as Record<string, unknown>).url === \"string\"\n\t);\n}\n\n/**\n * Resolve $ref: and $media references in content data\n */\nasync function resolveReferences(\n\tdata: Record<string, unknown>,\n\tseedIdMap: Map<string, string>,\n\tmediaContext: MediaContext,\n\tresult: SeedApplyResult,\n): Promise<Record<string, unknown>> {\n\tconst resolved: Record<string, unknown> = {};\n\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tresolved[key] = await resolveValue(value, seedIdMap, mediaContext, result);\n\t}\n\n\treturn resolved;\n}\n\n/**\n * Resolve a single value recursively\n */\nasync function resolveValue(\n\tvalue: unknown,\n\tseedIdMap: Map<string, string>,\n\tmediaContext: MediaContext,\n\tresult: SeedApplyResult,\n): Promise<unknown> {\n\t// Handle $ref: syntax\n\tif (typeof value === \"string\" && value.startsWith(\"$ref:\")) {\n\t\tconst seedId = value.slice(5);\n\t\treturn seedIdMap.get(seedId) ?? value; // Return unresolved if not found\n\t}\n\n\t// Handle $media syntax\n\tif (isSeedMediaReference(value)) {\n\t\treturn resolveMedia(value, mediaContext, result);\n\t}\n\n\t// Handle arrays\n\tif (Array.isArray(value)) {\n\t\treturn Promise.all(value.map((item) => resolveValue(item, seedIdMap, mediaContext, result)));\n\t}\n\n\t// Handle objects recursively\n\tif (typeof value === \"object\" && value !== null) {\n\t\tconst resolved: Record<string, unknown> = {};\n\t\tfor (const [k, v] of Object.entries(value)) {\n\t\t\tresolved[k] = await resolveValue(v, seedIdMap, mediaContext, result);\n\t\t}\n\t\t// Site components and other readers of saved blocks expect `asset._ref`/`asset.url`, not the MediaValue that `$media` yields.\n\t\tif (resolved._type === \"gallery\" && Array.isArray(resolved.images)) {\n\t\t\tresolved.images = sanitizeGalleryImages(resolved.images, ulid);\n\t\t} else if (\n\t\t\tresolved._type === \"image\" &&\n\t\t\t\"asset\" in value &&\n\t\t\tisSeedMediaReference(value.asset)\n\t\t) {\n\t\t\t// Merged over the block because the gallery image shape drops image-block fields such as `alignment`.\n\t\t\tconst [image] = sanitizeGalleryImages([resolved], ulid);\n\t\t\tif (image) Object.assign(resolved, image);\n\t\t}\n\t\treturn resolved;\n\t}\n\n\treturn value;\n}\n\n/**\n * Resolve a seeded byline avatar to a `media` row id. The file is assumed to\n * already exist in storage (the caller supplies its `storageKey`), so nothing\n * is downloaded or uploaded.\n *\n * Idempotent: if a media row with the same `storageKey` already exists it is\n * reused rather than duplicated, so re-applying a seed in `update` mode does\n * not leak rows. `created` reports whether a new row was inserted, so the\n * caller can both account for it and delete it if the subsequent byline write\n * fails (the only cross-dialect way to avoid an orphan — `withTransaction`\n * is a no-op on D1).\n */\nasync function resolveSeedBylineAvatar(\n\tdb: Kysely<Database>,\n\tavatar: SeedBylineAvatar,\n): Promise<{ id: string; created: boolean }> {\n\t// `media.storage_key` has no unique constraint, so order deterministically\n\t// to reuse the same row across runs if duplicates already exist. (Concurrent\n\t// seed applies against one DB are out of scope; seeding is a single-shot\n\t// init operation.)\n\tconst existing = await db\n\t\t.selectFrom(\"media\")\n\t\t.select(\"id\")\n\t\t.where(\"storage_key\", \"=\", avatar.storageKey)\n\t\t.orderBy(\"id\", \"asc\")\n\t\t.executeTakeFirst();\n\tif (existing) return { id: existing.id, created: false };\n\n\tconst basename = avatar.storageKey.split(\"/\").pop();\n\tconst filename =\n\t\tavatar.filename ?? (basename && basename.length > 0 ? basename : avatar.storageKey);\n\tconst created = await new MediaRepository(db).create({\n\t\tfilename,\n\t\tmimeType: avatar.mimeType ?? \"image/jpeg\",\n\t\tstorageKey: avatar.storageKey,\n\t\talt: avatar.alt,\n\t\twidth: avatar.width,\n\t\theight: avatar.height,\n\t\tstatus: \"ready\",\n\t});\n\treturn { id: created.id, created: true };\n}\n\n/**\n * Delete a media row by id. Best-effort cleanup for a failed byline write: a\n * failure here must not mask the original error that triggered the cleanup, so\n * it is logged and swallowed rather than thrown.\n */\nasync function deleteMediaRow(db: Kysely<Database>, id: string): Promise<void> {\n\ttry {\n\t\tawait db.deleteFrom(\"media\").where(\"id\", \"=\", id).execute();\n\t} catch (error) {\n\t\tconsole.warn(`[seed] failed to clean up orphaned avatar media ${id}:`, error);\n\t}\n}\n\n/**\n * Resolve a $media reference by downloading and uploading the media\n */\nasync function resolveMedia(\n\tref: SeedMediaReference,\n\tctx: MediaContext,\n\tresult: SeedApplyResult,\n): Promise<MediaValue | null> {\n\tconst { url, alt, filename, caption } = ref.$media;\n\n\t// Check cache first\n\tconst cached = ctx.mediaCache.get(url);\n\tif (cached) {\n\t\tresult.media.skipped++;\n\t\treturn { ...cached, alt: alt ?? cached.alt };\n\t}\n\n\t// When skipMediaDownload is set, resolve $media to an external URL reference\n\t// without downloading or storing anything. Used by playground mode.\n\tif (ctx.skipMediaDownload) {\n\t\tconst mediaValue: MediaValue = {\n\t\t\tprovider: \"external\",\n\t\t\tid: ulid(),\n\t\t\tsrc: url,\n\t\t\talt: alt ?? undefined,\n\t\t\tfilename: filename ?? undefined,\n\t\t};\n\t\tctx.mediaCache.set(url, mediaValue);\n\t\tresult.media.skipped++;\n\t\treturn mediaValue;\n\t}\n\n\t// Storage is required for $media resolution\n\tif (!ctx.storage) {\n\t\tconsole.warn(`Skipping $media reference (no storage configured): ${url}`);\n\t\tresult.media.skipped++;\n\t\treturn null;\n\t}\n\n\ttry {\n\t\t// SSRF protection: validate URL before downloading\n\t\tvalidateExternalUrl(url);\n\n\t\t// Download the media (ssrfSafeFetch re-validates redirect targets)\n\t\tconsole.log(` 📥 Downloading: ${url}`);\n\t\tctx.budget?.countMediaDownload();\n\t\tconst response = await ssrfSafeFetch(url, {\n\t\t\theaders: {\n\t\t\t\t// Some services like Unsplash require a user-agent\n\t\t\t\t\"User-Agent\": \"EmDash-CMS/1.0\",\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconsole.warn(` ⚠️ Failed to download ${url}: ${response.status}`);\n\t\t\tresult.media.skipped++;\n\t\t\treturn null;\n\t\t}\n\n\t\t// Get content type and determine extension\n\t\tconst contentType = response.headers.get(\"content-type\") || \"application/octet-stream\";\n\t\tconst ext = getExtensionFromContentType(contentType) || getExtensionFromUrl(url) || \".bin\";\n\n\t\t// Get the body as buffer\n\t\tconst arrayBuffer = await response.arrayBuffer();\n\t\tconst body = new Uint8Array(arrayBuffer);\n\n\t\t// Deduplicate by content hash so re-running a seed does not mint a\n\t\t// fresh media row for a file that already exists in the library.\n\t\tconst contentHash = await computeContentHash(body);\n\t\tconst mediaRepo = new MediaRepository(ctx.db);\n\t\tconst existing = await mediaRepo.findByContentHash(contentHash);\n\t\tif (existing) {\n\t\t\tconst mediaValue: MediaValue = {\n\t\t\t\tprovider: \"local\",\n\t\t\t\tid: existing.id,\n\t\t\t\talt: alt ?? existing.alt ?? undefined,\n\t\t\t\twidth: existing.width ?? undefined,\n\t\t\t\theight: existing.height ?? undefined,\n\t\t\t\tmimeType: existing.mimeType,\n\t\t\t\tfilename: existing.filename,\n\t\t\t\tmeta: { storageKey: existing.storageKey },\n\t\t\t};\n\t\t\tctx.mediaCache.set(url, mediaValue);\n\t\t\tresult.media.skipped++;\n\t\t\tconsole.log(` ✅ Reused: ${existing.filename}`);\n\t\t\treturn mediaValue;\n\t\t}\n\n\t\t// Generate filename and storage key\n\t\tconst storageId = ulid();\n\t\tconst finalFilename = filename || generateFilename(url, ext);\n\t\tconst storageKey = `${storageId}${ext}`;\n\n\t\t// Get image dimensions if it's an image\n\t\tlet width: number | undefined;\n\t\tlet height: number | undefined;\n\t\tif (contentType.startsWith(\"image/\")) {\n\t\t\tconst dimensions = getImageDimensions(body);\n\t\t\twidth = dimensions?.width;\n\t\t\theight = dimensions?.height;\n\t\t}\n\n\t\t// Upload to storage\n\t\tawait ctx.storage.upload({\n\t\t\tkey: storageKey,\n\t\t\tbody,\n\t\t\tcontentType,\n\t\t});\n\n\t\t// Create media record\n\t\tconst media = await mediaRepo.create({\n\t\t\tfilename: finalFilename,\n\t\t\tmimeType: contentType,\n\t\t\tsize: body.length,\n\t\t\twidth,\n\t\t\theight,\n\t\t\talt,\n\t\t\tcaption,\n\t\t\tstorageKey,\n\t\t\tcontentHash,\n\t\t\tstatus: \"ready\",\n\t\t});\n\n\t\t// Create the MediaValue - only store id, URL is built at runtime by EmDashMedia\n\t\tconst mediaValue: MediaValue = {\n\t\t\tprovider: \"local\",\n\t\t\tid: media.id,\n\t\t\talt: alt ?? undefined,\n\t\t\twidth,\n\t\t\theight,\n\t\t\tmimeType: contentType,\n\t\t\tfilename: finalFilename,\n\t\t\tmeta: { storageKey },\n\t\t};\n\n\t\t// Cache for reuse\n\t\tctx.mediaCache.set(url, mediaValue);\n\t\tresult.media.created++;\n\n\t\tconsole.log(` ✅ Uploaded: ${finalFilename}`);\n\t\treturn mediaValue;\n\t} catch (error) {\n\t\tconsole.warn(\n\t\t\t` ⚠️ Error processing $media ${url}:`,\n\t\t\terror instanceof Error ? error.message : error,\n\t\t);\n\t\tresult.media.skipped++;\n\t\treturn null;\n\t}\n}\n\n/**\n * Get file extension from content type\n */\nfunction getExtensionFromContentType(contentType: string): string | null {\n\t// Handle content-type with parameters like \"image/jpeg; charset=utf-8\"\n\tconst baseMime = contentType.split(\";\")[0].trim();\n\tconst ext = mime.getExtension(baseMime);\n\treturn ext ? `.${ext}` : null;\n}\n\n/**\n * Get file extension from URL\n */\nfunction getExtensionFromUrl(url: string): string | null {\n\ttry {\n\t\tconst pathname = new URL(url).pathname;\n\t\tconst match = pathname.match(FILE_EXTENSION_PATTERN);\n\t\treturn match ? `.${match[1]}` : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Generate a filename from URL\n */\nfunction generateFilename(url: string, ext: string): string {\n\ttry {\n\t\tconst pathname = new URL(url).pathname;\n\t\tconst basename = pathname.split(\"/\").pop() || \"media\";\n\t\t// Remove any existing extension and query params\n\t\tconst name = basename.replace(EXTENSION_PATTERN, \"\").replace(QUERY_PARAM_PATTERN, \"\");\n\t\t// Sanitize: only alphanumeric, dash, underscore\n\t\tconst sanitized = name.replace(SANITIZE_PATTERN, \"-\").replace(MULTIPLE_HYPHENS_PATTERN, \"-\");\n\t\treturn `${sanitized || \"media\"}${ext}`;\n\t} catch {\n\t\treturn `media${ext}`;\n\t}\n}\n\n/**\n * Get image dimensions from buffer using image-size.\n * Supports PNG, JPEG, GIF, WebP, AVIF, SVG, TIFF, and more.\n */\nfunction getImageDimensions(buffer: Uint8Array): { width: number; height: number } | null {\n\ttry {\n\t\tconst result = imageSize(buffer);\n\t\tif (result.width != null && result.height != null) {\n\t\t\treturn { width: result.width, height: result.height };\n\t\t}\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"artifacts-htGyfCSt.mjs","names":[],"sources":["../src/redirects/artifacts.ts"],"sourcesContent":["/**\n * Published redirect artifacts.\n *\n * Enabled redirect rules are published as compact, content-addressed JSON\n * artifacts grouped into a generation. The middleware loads the active\n * generation in one query and afterwards only polls the small state row, so a\n * site with many redirects never rescans `_emdash_redirects` on the read path.\n *\n * Exact rules are sharded by a hash of their source and pattern rules are\n * chunked in precedence order, so a single rule change usually rewrites one\n * artifact. Writers bump `_emdash_redirect_state.revision` through triggers.\n * A generation that is stale or fails verification is never served: readers\n * fall back to the rules table and schedule a repair.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { after } from \"../after.js\";\nimport { RedirectRepository } from \"../database/repositories/redirect.js\";\nimport { withTransaction } from \"../database/transaction.js\";\nimport type { Database } from \"../database/types.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../utils/chunks.js\";\nimport { isMissingTableError } from \"../utils/db-errors.js\";\nimport {\n\tinvalidateRedirectCache,\n\ttype RedirectRule,\n\ttype RedirectRuleSet,\n\ttype RedirectSource,\n} from \"./cache.js\";\n\ntype ArtifactKind = \"exact\" | \"pattern\";\ntype ArtifactEntry = [id: string, source: string, destination: string, type: number];\n\ninterface RedirectArtifact {\n\tdigest: string;\n\tkind: ArtifactKind;\n\tpayload: string;\n}\n\ninterface RuleRow {\n\tid: string;\n\tsource: string;\n\tdestination: string;\n\ttype: number;\n\tis_pattern: number;\n}\n\nconst ARTIFACT_TARGET_BYTES = 64 * 1024;\nconst GENERATION_PREFIX = \"emdash-redirects-v1\";\nconst REPAIR_LEASE_MS = 60_000;\n/** Three bound parameters per link row keeps each insert within D1's limit. */\nconst LINK_BATCH_SIZE = 30;\n\nconst encoder = new TextEncoder();\n\nasync function sha256Hex(value: string): Promise<string> {\n\tconst digest = await crypto.subtle.digest(\"SHA-256\", encoder.encode(value));\n\treturn Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction fnv1a(value: string): number {\n\tlet hash = 0x811c9dc5;\n\tfor (let i = 0; i < value.length; i++) {\n\t\thash ^= value.charCodeAt(i);\n\t\thash = Math.imul(hash, 0x01000193);\n\t}\n\treturn hash >>> 0;\n}\n\nfunction toEntry(rule: RedirectRule): ArtifactEntry {\n\treturn [rule.id, rule.source, rule.destination, rule.type];\n}\n\nfunction entryBytes(entry: ArtifactEntry): number {\n\treturn encoder.encode(JSON.stringify(entry)).byteLength + 1;\n}\n\nasync function createArtifact(kind: ArtifactKind, entries: ArtifactEntry[]) {\n\tconst payload = JSON.stringify(entries);\n\treturn { digest: await sha256Hex(`${kind}\\n${payload}`), kind, payload };\n}\n\nfunction generationId(digests: string[]): Promise<string> {\n\treturn sha256Hex([GENERATION_PREFIX, ...digests].join(\"\\n\"));\n}\n\n/** Splits enabled rows, in precedence order, into exact and pattern rules. */\nfunction toRuleSet(\n\trows: Array<RedirectRule & { isPattern: boolean }>,\n\tversion: string | null,\n): RedirectRuleSet {\n\tconst exact: RedirectRule[] = [];\n\tconst patterns: RedirectRule[] = [];\n\tfor (const { isPattern, ...rule } of rows) {\n\t\t(isPattern ? patterns : exact).push(rule);\n\t}\n\treturn { version, exact, patterns };\n}\n\n/**\n * Builds the artifacts for a rule set. Exact rules keep the last rule per\n * source, matching lookup semantics, and are sorted so the shards are\n * deterministic. Pattern rules keep precedence order across chunks.\n */\nasync function buildArtifacts(\n\trules: RedirectRuleSet,\n): Promise<{ generation: string; artifacts: RedirectArtifact[] }> {\n\tconst bySource = new Map<string, ArtifactEntry>();\n\tfor (const rule of rules.exact) bySource.set(rule.source, toEntry(rule));\n\tconst exact = [...bySource.values()].toSorted((a, b) => (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0));\n\tconst exactBytes = exact.reduce((total, entry) => total + entryBytes(entry), 0);\n\tconst shardCount = Math.max(1, Math.ceil(exactBytes / ARTIFACT_TARGET_BYTES));\n\tconst shards: ArtifactEntry[][] = Array.from({ length: shardCount }, () => []);\n\tfor (const entry of exact) shards[fnv1a(entry[1]) % shardCount].push(entry);\n\n\tconst artifacts: RedirectArtifact[] = [];\n\tfor (const shard of shards) {\n\t\tif (shard.length > 0) artifacts.push(await createArtifact(\"exact\", shard));\n\t}\n\n\tlet chunk: ArtifactEntry[] = [];\n\tlet chunkBytes = 0;\n\tfor (const rule of rules.patterns) {\n\t\tconst entry = toEntry(rule);\n\t\tconst bytes = entryBytes(entry);\n\t\tif (chunk.length > 0 && chunkBytes + bytes > ARTIFACT_TARGET_BYTES) {\n\t\t\tartifacts.push(await createArtifact(\"pattern\", chunk));\n\t\t\tchunk = [];\n\t\t\tchunkBytes = 0;\n\t\t}\n\t\tchunk.push(entry);\n\t\tchunkBytes += bytes;\n\t}\n\tif (chunk.length > 0) artifacts.push(await createArtifact(\"pattern\", chunk));\n\n\treturn {\n\t\tgeneration: await generationId(artifacts.map((artifact) => artifact.digest)),\n\t\tartifacts,\n\t};\n}\n\n/**\n * Publishes the enabled redirect rules as the active generation.\n *\n * The revision is read before the rules, so the published generation holds\n * at least the changes up to the revision it is labelled with. A publish at\n * the active revision replaces the active generation, which repairs a damaged\n * one; losing the compare-and-set to a newer revision leaves that publish\n * active.\n */\nexport async function publishRedirectArtifacts(db: Kysely<Database>): Promise<void> {\n\tconst state = await db\n\t\t.selectFrom(\"_emdash_redirect_state\")\n\t\t.select(\"revision\")\n\t\t.where(\"id\", \"=\", 1)\n\t\t.executeTakeFirst();\n\tif (!state) throw new Error(\"Redirect state row is missing\");\n\tconst revision = Number(state.revision);\n\n\tconst rows: RuleRow[] = await db\n\t\t.selectFrom(\"_emdash_redirects\")\n\t\t.select([\"id\", \"source\", \"destination\", \"type\", \"is_pattern\"])\n\t\t.where(\"enabled\", \"=\", 1)\n\t\t.orderBy(\"created_at\", (order) => order.asc().nullsFirst())\n\t\t.orderBy(\"id\", \"asc\")\n\t\t.execute();\n\tconst { generation, artifacts } = await buildArtifacts(\n\t\ttoRuleSet(\n\t\t\trows.map((row) => ({\n\t\t\t\tid: row.id,\n\t\t\t\tsource: row.source,\n\t\t\t\tdestination: row.destination,\n\t\t\t\ttype: Number(row.type),\n\t\t\t\tisPattern: Number(row.is_pattern) === 1,\n\t\t\t})),\n\t\t\tnull,\n\t\t),\n\t);\n\n\tawait withTransaction(db, async (trx) => {\n\t\tconst digests = [...new Set(artifacts.map((artifact) => artifact.digest))];\n\t\tconst stored = new Map<string, string>();\n\t\tfor (const batch of chunks(digests, SQL_BATCH_SIZE)) {\n\t\t\tconst found = await trx\n\t\t\t\t.selectFrom(\"_emdash_redirect_artifacts\")\n\t\t\t\t.select([\"digest\", \"kind\", \"payload\"])\n\t\t\t\t.where(\"digest\", \"in\", batch)\n\t\t\t\t.execute();\n\t\t\tfor (const row of found) stored.set(row.digest, `${row.kind}\\n${row.payload}`);\n\t\t}\n\t\tfor (const artifact of artifacts) {\n\t\t\tconst content = `${artifact.kind}\\n${artifact.payload}`;\n\t\t\tif (stored.get(artifact.digest) === content) continue;\n\t\t\tstored.set(artifact.digest, content);\n\t\t\t// A stored row that does not match its digest was damaged; rewrite it.\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_redirect_artifacts\")\n\t\t\t\t.values(artifact)\n\t\t\t\t.onConflict((conflict) =>\n\t\t\t\t\tconflict.column(\"digest\").doUpdateSet({ kind: artifact.kind, payload: artifact.payload }),\n\t\t\t\t)\n\t\t\t\t.execute();\n\t\t}\n\n\t\tconst links = artifacts.map((artifact, position) => ({\n\t\t\tgeneration,\n\t\t\tposition,\n\t\t\tdigest: artifact.digest,\n\t\t}));\n\t\tfor (const batch of chunks(links, LINK_BATCH_SIZE)) {\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_redirect_generation_artifacts\")\n\t\t\t\t.values(batch)\n\t\t\t\t.onConflict((conflict) =>\n\t\t\t\t\tconflict\n\t\t\t\t\t\t.columns([\"generation\", \"position\"])\n\t\t\t\t\t\t.doUpdateSet((eb) => ({ digest: eb.ref(\"excluded.digest\") })),\n\t\t\t\t)\n\t\t\t\t.execute();\n\t\t}\n\t\tawait trx\n\t\t\t.deleteFrom(\"_emdash_redirect_generation_artifacts\")\n\t\t\t.where(\"generation\", \"=\", generation)\n\t\t\t.where(\"position\", \">=\", links.length)\n\t\t\t.execute();\n\n\t\tconst activated = await trx\n\t\t\t.updateTable(\"_emdash_redirect_state\")\n\t\t\t.set({ generation, generation_revision: revision })\n\t\t\t.where(\"id\", \"=\", 1)\n\t\t\t.where(\"generation_revision\", \"<=\", revision)\n\t\t\t.executeTakeFirst();\n\t\tif (BigInt(activated.numUpdatedRows) === 0n) return;\n\n\t\t// Another publish may have activated since this one; keep the active generation.\n\t\tawait trx\n\t\t\t.deleteFrom(\"_emdash_redirect_generation_artifacts\")\n\t\t\t.where((eb) =>\n\t\t\t\teb(\n\t\t\t\t\t\"generation\",\n\t\t\t\t\t\"<>\",\n\t\t\t\t\teb.selectFrom(\"_emdash_redirect_state\").select(\"generation\").where(\"id\", \"=\", 1),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.execute();\n\t\tawait trx\n\t\t\t.deleteFrom(\"_emdash_redirect_artifacts\")\n\t\t\t.where(({ exists, not, selectFrom }) =>\n\t\t\t\tnot(\n\t\t\t\t\texists(\n\t\t\t\t\t\tselectFrom(\"_emdash_redirect_generation_artifacts\")\n\t\t\t\t\t\t\t.select(\"_emdash_redirect_generation_artifacts.digest\")\n\t\t\t\t\t\t\t.whereRef(\n\t\t\t\t\t\t\t\t\"_emdash_redirect_generation_artifacts.digest\",\n\t\t\t\t\t\t\t\t\"=\",\n\t\t\t\t\t\t\t\t\"_emdash_redirect_artifacts.digest\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.execute();\n\t});\n}\n\n/**\n * Publishes after a redirect write, then invalidates this isolate's cache. A\n * failed publish is logged rather than failing the write: readers fall back\n * to the rules table and repair the stale generation.\n */\nexport async function publishRedirectChanges(db: Kysely<Database>): Promise<void> {\n\ttry {\n\t\tawait publishRedirectArtifacts(db);\n\t} catch (error) {\n\t\tconsole.error(\"[emdash:redirects] publishing redirect artifacts failed:\", error);\n\t}\n\tinvalidateRedirectCache();\n}\n\nfunction isArtifactEntry(value: unknown): value is ArtifactEntry {\n\treturn (\n\t\tArray.isArray(value) &&\n\t\tvalue.length === 4 &&\n\t\ttypeof value[0] === \"string\" &&\n\t\ttypeof value[1] === \"string\" &&\n\t\ttypeof value[2] === \"string\" &&\n\t\ttypeof value[3] === \"number\"\n\t);\n}\n\nfunction parseEntries(payload: string): ArtifactEntry[] | null {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(payload);\n\t\treturn Array.isArray(parsed) && parsed.every(isArtifactEntry) ? parsed : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Loads and verifies the active generation. Returns null when nothing is\n * published, the generation is stale, or any artifact is missing, altered, or\n * out of place.\n */\nasync function loadPublishedRedirects(db: Kysely<Database>): Promise<RedirectRuleSet | null> {\n\tconst rows = await db\n\t\t.selectFrom(\"_emdash_redirect_state as state\")\n\t\t.leftJoin(\n\t\t\t\"_emdash_redirect_generation_artifacts as link\",\n\t\t\t\"link.generation\",\n\t\t\t\"state.generation\",\n\t\t)\n\t\t.leftJoin(\"_emdash_redirect_artifacts as artifact\", \"artifact.digest\", \"link.digest\")\n\t\t.select([\n\t\t\t\"state.revision\",\n\t\t\t\"state.generation\",\n\t\t\t\"state.generation_revision\",\n\t\t\t\"link.position\",\n\t\t\t\"link.digest\",\n\t\t\t\"artifact.kind\",\n\t\t\t\"artifact.payload\",\n\t\t])\n\t\t.where(\"state.id\", \"=\", 1)\n\t\t.orderBy(\"link.position\", \"asc\")\n\t\t.execute();\n\n\tconst state = rows[0];\n\tif (!state?.generation) return null;\n\tif (Number(state.revision) > Number(state.generation_revision)) return null;\n\n\tconst exact: RedirectRule[] = [];\n\tconst patterns: RedirectRule[] = [];\n\tconst digests: string[] = [];\n\tconst links = rows.filter((row) => row.position !== null);\n\tfor (const [index, link] of links.entries()) {\n\t\tif (\n\t\t\tNumber(link.position) !== index ||\n\t\t\t!link.digest ||\n\t\t\tlink.payload === null ||\n\t\t\t(link.kind !== \"exact\" && link.kind !== \"pattern\")\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\tif ((await sha256Hex(`${link.kind}\\n${link.payload}`)) !== link.digest) {\n\t\t\treturn null;\n\t\t}\n\t\tconst entries = parseEntries(link.payload);\n\t\tif (!entries) return null;\n\t\tconst target = link.kind === \"exact\" ? exact : patterns;\n\t\tfor (const [id, source, destination, type] of entries) {\n\t\t\ttarget.push({ id, source, destination, type });\n\t\t}\n\t\tdigests.push(link.digest);\n\t}\n\tif ((await generationId(digests)) !== state.generation) return null;\n\n\treturn { version: state.generation, exact, patterns };\n}\n\nasync function loadRulesTable(db: Kysely<Database>): Promise<RedirectRuleSet> {\n\tconst redirects = await new RedirectRepository(db).findAllEnabled();\n\treturn toRuleSet(\n\t\tredirects.map(({ id, source, destination, type, isPattern }) => ({\n\t\t\tid,\n\t\t\tsource,\n\t\t\tdestination,\n\t\t\ttype,\n\t\t\tisPattern,\n\t\t})),\n\t\tnull,\n\t);\n}\n\ninterface RepairState {\n\trunningUntil: number;\n}\n\nconst REPAIR_STATE_KEY = Symbol.for(\"emdash:redirect-artifact-repair\");\nconst g = globalThis as Record<symbol, unknown>;\nconst repairState: RepairState =\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern (see request-context.ts)\n\t(g[REPAIR_STATE_KEY] as RepairState | undefined) ??\n\t(() => {\n\t\tconst state: RepairState = { runningUntil: 0 };\n\t\tg[REPAIR_STATE_KEY] = state;\n\t\treturn state;\n\t})();\n\n/**\n * Republishes the artifacts under a database lease so one isolate repairs a\n * stale or damaged generation while the others keep serving.\n */\nasync function repairRedirectArtifacts(db: Kysely<Database>): Promise<void> {\n\tconst now = Date.now();\n\tconst expiresAt = now + REPAIR_LEASE_MS;\n\tconst lease = await db\n\t\t.updateTable(\"_emdash_redirect_state\")\n\t\t.set({ repair_expires_at: expiresAt })\n\t\t.where(\"id\", \"=\", 1)\n\t\t.where(\"repair_expires_at\", \"<\", now)\n\t\t.executeTakeFirst();\n\tif (BigInt(lease.numUpdatedRows) === 0n) return;\n\ttry {\n\t\tawait publishRedirectArtifacts(db);\n\t} finally {\n\t\tawait db\n\t\t\t.updateTable(\"_emdash_redirect_state\")\n\t\t\t.set({ repair_expires_at: 0 })\n\t\t\t.where(\"id\", \"=\", 1)\n\t\t\t.where(\"repair_expires_at\", \"=\", expiresAt)\n\t\t\t.execute();\n\t}\n}\n\nfunction scheduleRedirectRepair(db: Kysely<Database>): void {\n\tconst now = Date.now();\n\tif (repairState.runningUntil > now) return;\n\tconst runningUntil = now + REPAIR_LEASE_MS;\n\trepairState.runningUntil = runningUntil;\n\tafter(async () => {\n\t\ttry {\n\t\t\tawait repairRedirectArtifacts(db);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[emdash:redirects] repairing redirect artifacts failed:\", error);\n\t\t} finally {\n\t\t\tif (repairState.runningUntil === runningUntil) repairState.runningUntil = 0;\n\t\t}\n\t});\n}\n\n/**\n * The rule source the middleware caches: the published generation when it is\n * current and verifies, otherwise the rules table. Before the artifact tables exist the\n * rules table is read directly.\n */\nexport function createRedirectSource(db: Kysely<Database>): RedirectSource {\n\treturn {\n\t\tasync load() {\n\t\t\tlet published: RedirectRuleSet | null;\n\t\t\ttry {\n\t\t\t\tpublished = await loadPublishedRedirects(db);\n\t\t\t} catch (error) {\n\t\t\t\tif (isMissingTableError(error)) return loadRulesTable(db);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tif (published) return published;\n\t\t\tscheduleRedirectRepair(db);\n\t\t\treturn loadRulesTable(db);\n\t\t},\n\t\tasync isCurrent(version) {\n\t\t\tconst state = await db\n\t\t\t\t.selectFrom(\"_emdash_redirect_state\")\n\t\t\t\t.select([\"revision\", \"generation\", \"generation_revision\"])\n\t\t\t\t.where(\"id\", \"=\", 1)\n\t\t\t\t.executeTakeFirst();\n\t\t\treturn (\n\t\t\t\tstate?.generation === version && Number(state.revision) <= Number(state.generation_revision)\n\t\t\t);\n\t\t},\n\t};\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"byline-BR1qhyiw.mjs","names":[],"sources":["../src/database/repositories/byline.ts"],"sourcesContent":["import { sql, type Kysely, type Selectable } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { getBylineFieldDefs } from \"../../bylines/field-defs-cache.js\";\nimport {\n\tinvalidateBylineObjectCache,\n\tinvalidateCollectionCache,\n} from \"../../object-cache/index.js\";\nimport {\n\tclearRequestCacheEntry,\n\tpeekRequestCache,\n\tsetRequestCacheEntry,\n} from \"../../request-cache.js\";\nimport type { BylineFieldDefinition, CustomFieldValue } from \"../../schema/types.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../../utils/chunks.js\";\nimport { listTablesLike } from \"../dialect-helpers.js\";\nimport { withTransaction } from \"../transaction.js\";\nimport type { BylineTable, Database } from \"../types.js\";\nimport { validateIdentifier } from \"../validate.js\";\nimport {\n\tdecodeCursor,\n\tEmDashValidationError,\n\tencodeCursor,\n\ttype BylineSummary,\n\ttype ContentBylineCredit,\n\ttype FindManyResult,\n} from \"./types.js\";\n\ntype BylineRow = Selectable<BylineTable>;\n\n/**\n * A byline row optionally augmented with the avatar's media columns, folded in\n * by the `LEFT JOIN media` in the credit-hydration and single-row finder\n * queries. `findMany` selects without the join, so the keys are optional and\n * `rowToByline` defaults them to null.\n */\ntype BylineRowWithAvatar = BylineRow & {\n\tavatar_storage_key?: string | null;\n\tavatar_alt?: string | null;\n\tavatar_blurhash?: string | null;\n\tavatar_dominant_color?: string | null;\n};\n\nexport interface CreateBylineInput {\n\tslug: string;\n\tdisplayName: string;\n\tbio?: string | null;\n\tavatarMediaId?: string | null;\n\twebsiteUrl?: string | null;\n\tuserId?: string | null;\n\tisGuest?: boolean;\n\t/**\n\t * Locale this byline row belongs to. When omitted, the DB DEFAULT (the\n\t * configured `defaultLocale` after migration 040) is used. Keeps behaviour\n\t * consistent with `TaxonomyRepository.create`.\n\t */\n\tlocale?: string;\n\t/**\n\t * When set, the new row joins the source byline's translation_group rather\n\t * than minting a fresh one. The source must exist; otherwise the create\n\t * throws. Mirrors `TaxonomyRepository.create`.\n\t */\n\ttranslationOf?: string;\n\t/**\n\t * Byline custom-field values to seed on the new row (Phase 6 of\n\t * Discussion #1174). Same semantics as `UpdateBylineInput.customFields`:\n\t * keys must match registered slugs in `_emdash_byline_fields`, values\n\t * are validated against the field's type, and writes route to\n\t * `_emdash_byline_field_values` (translatable) or\n\t * `_emdash_byline_field_group_values` (group-shared). Validation runs\n\t * before the row insert so a bad value can't leave a bare byline behind.\n\t */\n\tcustomFields?: Record<string, unknown>;\n}\n\nexport interface UpdateBylineInput {\n\tslug?: string;\n\tdisplayName?: string;\n\tbio?: string | null;\n\tavatarMediaId?: string | null;\n\twebsiteUrl?: string | null;\n\tuserId?: string | null;\n\tisGuest?: boolean;\n\t/**\n\t * Byline custom-field values to write (Phase 3 of Discussion #1174).\n\t *\n\t * Each key must match a registered slug in `_emdash_byline_fields`;\n\t * unknown keys throw `EmDashValidationError`. Per-field writes route\n\t * to `_emdash_byline_field_values` (when the field's `translatable`\n\t * flag is true) or `_emdash_byline_field_group_values` (when false).\n\t * A value of `null` clears the row.\n\t *\n\t * Values are validated against the field's type:\n\t * - `string` / `text` / `url` accept a `string`\n\t * - `boolean` accepts a `boolean`\n\t * - `select` accepts a `string` that appears in `validation.options`\n\t *\n\t * Writes are idempotent (`INSERT … ON CONFLICT DO UPDATE`), so\n\t * retrying the same update produces the same DB state.\n\t */\n\tcustomFields?: Record<string, unknown>;\n}\n\nexport interface ContentBylineInput {\n\tbylineId: string;\n\troleLabel?: string | null;\n}\n\nfunction rowToByline(row: BylineRowWithAvatar): BylineSummary {\n\treturn {\n\t\tid: row.id,\n\t\tslug: row.slug,\n\t\tdisplayName: row.display_name,\n\t\tbio: row.bio,\n\t\tavatarMediaId: row.avatar_media_id,\n\t\tavatarStorageKey: row.avatar_storage_key ?? null,\n\t\tavatarAlt: row.avatar_alt ?? null,\n\t\tavatarBlurhash: row.avatar_blurhash ?? null,\n\t\tavatarDominantColor: row.avatar_dominant_color ?? null,\n\t\twebsiteUrl: row.website_url,\n\t\tuserId: row.user_id,\n\t\tisGuest: row.is_guest === 1,\n\t\tcreatedAt: row.created_at,\n\t\tupdatedAt: row.updated_at,\n\t\tlocale: row.locale,\n\t\ttranslationGroup: row.translation_group,\n\t};\n}\n\n/**\n * Merge a single decoded value into a `BylineSummary.customFields` map.\n * Centralised so the merge semantics (null storage, JSON.parse failure\n * handling) live in one place across both translatable and group-shared\n * paths.\n *\n * A stored row with `value = NULL` (representing an explicit null) is\n * surfaced as `null` in `customFields`. A row with a malformed JSON\n * payload is dropped silently with a `console.warn` — a corrupted\n * payload shouldn't break the entire byline hydration; the field-defs\n * cache will let admins replace the value, and the warning makes the\n * issue debuggable. (Storage path uses `JSON.stringify`, so the only\n * way to get malformed JSON is direct DB tampering or a future\n * migration bug.)\n */\nfunction assignCustomFieldValue(\n\tsummary: BylineSummary,\n\tfield: BylineFieldDefinition,\n\tstored: string | null,\n): void {\n\tconst target = summary.customFields ?? {};\n\tif (stored === null) {\n\t\ttarget[field.slug] = null;\n\t} else {\n\t\ttry {\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- coerceFieldValue ran at write time, see field-defs-cache.ts\n\t\t\ttarget[field.slug] = JSON.parse(stored) as CustomFieldValue;\n\t\t} catch {\n\t\t\tconsole.warn(\n\t\t\t\t`[BylineRepository] dropping malformed JSON for byline=${summary.id} ` +\n\t\t\t\t\t`field=${field.slug}: ${stored.slice(0, 60)}`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t}\n\tsummary.customFields = target;\n}\n\n/**\n * Coerce a raw write-path value to `CustomFieldValue`, throwing\n * `EmDashValidationError` on type mismatch. `null` clears the field\n * (DELETE in the write path).\n *\n * TODO: `field.required` is not enforced. The admin UI exposes the\n * toggle but the backend accepts missing values; design pass needed\n * on the enforcement model.\n */\nexport function coerceFieldValue(\n\tfield: Pick<BylineFieldDefinition, \"slug\" | \"type\" | \"validation\">,\n\traw: unknown,\n): CustomFieldValue {\n\tif (raw === null) return null;\n\n\tswitch (field.type) {\n\t\tcase \"string\":\n\t\tcase \"text\": {\n\t\t\tif (typeof raw !== \"string\") {\n\t\t\t\tthrow new EmDashValidationError(\n\t\t\t\t\t`Byline field \"${field.slug}\" expects a string value (received ${typeof raw})`,\n\t\t\t\t\t{ slug: field.slug, type: field.type, received: typeof raw },\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn raw;\n\t\t}\n\t\tcase \"url\": {\n\t\t\tif (typeof raw !== \"string\") {\n\t\t\t\tthrow new EmDashValidationError(\n\t\t\t\t\t`Byline field \"${field.slug}\" expects a string value (received ${typeof raw})`,\n\t\t\t\t\t{ slug: field.slug, type: field.type, received: typeof raw },\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Empty string round-trips as a clear from the admin UI; any\n\t\t\t// non-empty value must be a valid http(s) URL. The scheme\n\t\t\t// allowlist mirrors `httpUrl` in `api/schemas/common.ts` —\n\t\t\t// `new URL` alone would accept `javascript:`/`data:` etc.\n\t\t\tif (raw === \"\") return raw;\n\t\t\tlet parsed: URL;\n\t\t\ttry {\n\t\t\t\tparsed = new URL(raw);\n\t\t\t} catch {\n\t\t\t\tthrow new EmDashValidationError(\n\t\t\t\t\t`Byline field \"${field.slug}\" expects a valid URL (received \"${raw}\")`,\n\t\t\t\t\t{ slug: field.slug, type: field.type, received: raw },\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n\t\t\t\tthrow new EmDashValidationError(\n\t\t\t\t\t`Byline field \"${field.slug}\" must use http or https scheme (received \"${parsed.protocol}\")`,\n\t\t\t\t\t{ slug: field.slug, type: field.type, received: raw, protocol: parsed.protocol },\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn raw;\n\t\t}\n\t\tcase \"boolean\": {\n\t\t\tif (typeof raw !== \"boolean\") {\n\t\t\t\tthrow new EmDashValidationError(\n\t\t\t\t\t`Byline field \"${field.slug}\" expects a boolean value (received ${typeof raw})`,\n\t\t\t\t\t{ slug: field.slug, type: field.type, received: typeof raw },\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn raw;\n\t\t}\n\t\tcase \"select\": {\n\t\t\tif (typeof raw !== \"string\") {\n\t\t\t\tthrow new EmDashValidationError(\n\t\t\t\t\t`Byline field \"${field.slug}\" expects a string value (received ${typeof raw})`,\n\t\t\t\t\t{ slug: field.slug, type: field.type, received: typeof raw },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst options = field.validation?.options ?? [];\n\t\t\tif (!options.includes(raw)) {\n\t\t\t\tthrow new EmDashValidationError(\n\t\t\t\t\t`Byline field \"${field.slug}\" value \"${raw}\" is not one of the registered choices`,\n\t\t\t\t\t{ slug: field.slug, value: raw, options },\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn raw;\n\t\t}\n\t}\n}\n\n/**\n * Byline repository for content credits.\n *\n * Bylines are per-locale (migration 040). Translations of the same byline\n * share a `translation_group` ULID. `_emdash_content_bylines.byline_id` and\n * `ec_*.primary_byline_id` store the translation_group (not a row id) so a\n * single credit spans every locale variant of a byline.\n *\n * The repository does not resolve locale fallbacks on its own — callers\n * supply the locale they want. Hydration is strict per locale: a credit at\n * locale X renders iff a byline row exists at locale X within the credited\n * translation group. This mirrors `TaxonomyRepository.getTermsForEntry` and\n * the convention established by PR #916.\n *\n * Runtime helpers in `packages/core/src/bylines/index.ts` may layer fallback\n * resolution on top for the \"look up one byline by slug\" path, but the\n * relation-hydration methods on this class are always strict.\n */\nexport class BylineRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t// ============================================\n\t// Custom-field hydration (Phase 3 of #1174)\n\t// ============================================\n\n\t/**\n\t * Merge `customFields` onto each `BylineSummary` produced from the\n\t * given rows. Two batched queries total — one against\n\t * `_emdash_byline_field_values` (keyed by `byline_id`), one against\n\t * `_emdash_byline_field_group_values` (keyed by `translation_group`)\n\t * — both chunked at `SQL_BATCH_SIZE` for D1's bound-parameter cap.\n\t *\n\t * When zero fields are registered, every row gets `customFields = {}`\n\t * with no value-table reads (the field-defs cache returns `[]`).\n\t * Group-shared values are looked up via the row's `translation_group`,\n\t * so every locale sibling of the same byline identity sees the same\n\t * non-translatable value without re-reading per row.\n\t *\n\t * **Duplicate-row handling.** Callers (notably `getContentBylinesMany`\n\t * for list views with repeated authors) can pass the same byline row\n\t * multiple times. We assign values by *iterating both `rows` and\n\t * `summaries` in lockstep by index*, not by deduping into a Map keyed\n\t * on byline id. A Map approach silently drops earlier duplicates' merge\n\t * step (last writer wins, earlier instances keep their initial `{}`).\n\t * Iterating by index gives every duplicate its own merged copy.\n\t *\n\t * Hydration is *strict per row* — values are merged onto whichever\n\t * `BylineRow` produced them. Fallback semantics (e.g. \"if no value\n\t * for this locale, show the default-locale value\") are not the\n\t * repository's concern; consumers layer them on top if wanted, the\n\t * same way `BylineRepository` doesn't resolve locale fallback for\n\t * the base byline lookup.\n\t */\n\tprivate async withCustomFields(rows: BylineRowWithAvatar[]): Promise<BylineSummary[]> {\n\t\tconst summaries = rows.map(rowToByline);\n\t\t// Always populate `customFields = {}` (PR plan AC #6) — even when\n\t\t// no fields are registered, every BylineSummary carries the empty\n\t\t// object. A fresh object per summary so duplicate rows don't share\n\t\t// state.\n\t\tfor (const summary of summaries) {\n\t\t\tsummary.customFields = {};\n\t\t}\n\t\tawait this.applyCustomFieldsTo(summaries);\n\t\treturn summaries;\n\t}\n\n\tprivate async withCustomFieldsOne(\n\t\trow: BylineRowWithAvatar | undefined,\n\t): Promise<BylineSummary | null> {\n\t\tif (!row) return null;\n\t\tconst [result] = await this.withCustomFields([row]);\n\t\treturn result ?? null;\n\t}\n\n\t/**\n\t * Hydrate `customFields` on each `BylineSummary`, mutating in place.\n\t *\n\t * The public entry point for callers that fetch byline rows in\n\t * multiple passes (e.g. `getBylinesForEntries`, which buckets by\n\t * locale and calls `getContentBylinesMany` per bucket) and want a\n\t * single batched hydration over the union of bylines, not one per\n\t * pass. Use with the `skipHydration` option on the read methods to\n\t * defer customFields work to a single call here.\n\t *\n\t * Two batched queries total (translatable + group-shared) regardless\n\t * of how many bylines, locales, or translation_groups are in the\n\t * input — meets the Phase 3 query-count envelope for mixed-locale\n\t * list views even when sibling locales reference disjoint\n\t * translation_groups.\n\t *\n\t * Replaces any existing `customFields` on each summary with a freshly\n\t * fetched map. Callers that want to merge rather than replace should\n\t * not use this entry point.\n\t */\n\tasync hydrateBylineCustomFields(summaries: BylineSummary[]): Promise<void> {\n\t\tfor (const summary of summaries) {\n\t\t\tsummary.customFields = {};\n\t\t}\n\t\tawait this.applyCustomFieldsTo(summaries);\n\t}\n\n\t/**\n\t * Shared merge engine for `withCustomFields` and\n\t * `hydrateBylineCustomFields`. Reads field defs (cached), batches the\n\t * translatable + group-shared fetches, and walks `summaries` directly\n\t * to apply values.\n\t *\n\t * Iterates `summaries` (not a `summaryById` map) so duplicate\n\t * `BylineSummary` objects sharing the same `id` — e.g. the same\n\t * author credited to multiple entries — each get their own merged\n\t * values. The previous Map-based dedup silently dropped earlier\n\t * duplicates' merge step.\n\t */\n\tprivate async applyCustomFieldsTo(summaries: BylineSummary[]): Promise<void> {\n\t\tif (summaries.length === 0) return;\n\n\t\tconst defs = await getBylineFieldDefs(this.db);\n\t\tif (defs.length === 0) return;\n\n\t\tconst fieldById = new Map(defs.map((d) => [d.id, d]));\n\n\t\t// Translatable values, batched by byline_id (unique per locale, so\n\t\t// IDs across different locale buckets don't collide — one batched\n\t\t// query covers everything).\n\t\tconst translatableByByline = new Map<string, Map<string, string | null>>();\n\t\tconst bylineIds = [...new Set(summaries.map((s) => s.id))];\n\t\tfor (const chunk of chunks(bylineIds, SQL_BATCH_SIZE)) {\n\t\t\tconst trRows = await this.db\n\t\t\t\t.selectFrom(\"_emdash_byline_field_values\")\n\t\t\t\t.select([\"byline_id\", \"field_id\", \"value\"])\n\t\t\t\t.where(\"byline_id\", \"in\", chunk)\n\t\t\t\t.execute();\n\t\t\tfor (const trRow of trRows) {\n\t\t\t\tlet fieldMap = translatableByByline.get(trRow.byline_id);\n\t\t\t\tif (!fieldMap) {\n\t\t\t\t\tfieldMap = new Map();\n\t\t\t\t\ttranslatableByByline.set(trRow.byline_id, fieldMap);\n\t\t\t\t}\n\t\t\t\tfieldMap.set(trRow.field_id, trRow.value);\n\t\t\t}\n\t\t}\n\n\t\t// Group-shared values, batched over the union of translation_groups,\n\t\t// with per-group request-cache priming so subsequent calls within\n\t\t// the same request share the lookup. Together with the\n\t\t// `hydrateBylineCustomFields` + `skipHydration` flow in\n\t\t// `getBylinesForEntries`, this keeps mixed-locale list views to\n\t\t// **one** group-shared query per request, even for disjoint\n\t\t// translation_groups across locale buckets.\n\t\tconst groups = [\n\t\t\t...new Set(\n\t\t\t\tsummaries\n\t\t\t\t\t.map((s) => s.translationGroup)\n\t\t\t\t\t.filter((g): g is string => typeof g === \"string\" && g.length > 0),\n\t\t\t),\n\t\t];\n\t\tconst groupByGroup = await this.loadGroupValuesByIds(groups);\n\n\t\t// Each loop gates on `field.translatable` so a row in the wrong\n\t\t// owner table (e.g. left over from a translatable flip) can't\n\t\t// leak into hydration.\n\t\tfor (const summary of summaries) {\n\t\t\tconst trValues = translatableByByline.get(summary.id);\n\t\t\tif (trValues) {\n\t\t\t\tfor (const [fieldId, value] of trValues) {\n\t\t\t\t\tconst field = fieldById.get(fieldId);\n\t\t\t\t\tif (!field || !field.translatable) continue;\n\t\t\t\t\tassignCustomFieldValue(summary, field, value);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (summary.translationGroup) {\n\t\t\t\tconst grpValues = groupByGroup.get(summary.translationGroup);\n\t\t\t\tif (grpValues) {\n\t\t\t\t\tfor (const [fieldId, value] of grpValues) {\n\t\t\t\t\t\tconst field = fieldById.get(fieldId);\n\t\t\t\t\t\tif (!field || field.translatable) continue;\n\t\t\t\t\t\tassignCustomFieldValue(summary, field, value);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Resolve the group-shared custom-field values for a set of\n\t * translation_groups, sharing work across hydration calls within the\n\t * same request via per-group `requestCached` entries.\n\t *\n\t * The non-translatable storage table (`_emdash_byline_field_group_values`)\n\t * is keyed by `translation_group`, which is locale-agnostic. Combining\n\t * this method with `skipHydration` on `getContentBylinesMany` and a\n\t * single `hydrateBylineCustomFields` call (see\n\t * `getBylinesForEntries`) keeps mixed-locale list hydration to **one**\n\t * batched group-shared SQL per request — even with disjoint\n\t * translation_groups across locale buckets. Solo callers (`findById`,\n\t * `findMany`, etc.) still get the same per-call batching they had\n\t * before; the cache simply means a second call in the same request\n\t * for an overlapping group is free.\n\t *\n\t * Cache key: `byline-field-group-values:${groupId}` — one entry per\n\t * group. Writes use `setRequestCacheEntry` (idempotent, doesn't\n\t * overwrite); `BylineRepository.update` calls `clearRequestCacheEntry`\n\t * after a group-shared write to keep the cache fresh within the same\n\t * request.\n\t */\n\tprivate async loadGroupValuesByIds(\n\t\tgroups: string[],\n\t): Promise<Map<string, Map<string, string | null>>> {\n\t\tconst result = new Map<string, Map<string, string | null>>();\n\t\tif (groups.length === 0) return result;\n\n\t\t// First pass: pull any already-cached groups from the request scope.\n\t\tconst missing: string[] = [];\n\t\tfor (const g of groups) {\n\t\t\tconst cached = peekRequestCache<Map<string, string | null>>(`byline-field-group-values:${g}`);\n\t\t\tif (cached) {\n\t\t\t\tresult.set(g, await cached);\n\t\t\t} else {\n\t\t\t\tmissing.push(g);\n\t\t\t}\n\t\t}\n\n\t\tif (missing.length === 0) return result;\n\n\t\t// Second pass: one batched SQL for the union of all missing groups\n\t\t// (chunked for D1's bound-parameter cap). Initialise empty maps for\n\t\t// missing groups so the primed cache covers \"this group has no\n\t\t// values\" — preventing a re-fetch on subsequent calls.\n\t\tconst fetched = new Map<string, Map<string, string | null>>();\n\t\tfor (const g of missing) fetched.set(g, new Map());\n\t\tfor (const chunk of chunks(missing, SQL_BATCH_SIZE)) {\n\t\t\tconst grpRows = await this.db\n\t\t\t\t.selectFrom(\"_emdash_byline_field_group_values\")\n\t\t\t\t.select([\"translation_group\", \"field_id\", \"value\"])\n\t\t\t\t.where(\"translation_group\", \"in\", chunk)\n\t\t\t\t.execute();\n\t\t\tfor (const grpRow of grpRows) {\n\t\t\t\tconst fieldMap = fetched.get(grpRow.translation_group);\n\t\t\t\tif (!fieldMap) continue;\n\t\t\t\tfieldMap.set(grpRow.field_id, grpRow.value);\n\t\t\t}\n\t\t}\n\n\t\tfor (const g of missing) {\n\t\t\tconst m = fetched.get(g);\n\t\t\tif (!m) continue;\n\t\t\tsetRequestCacheEntry(`byline-field-group-values:${g}`, m);\n\t\t\tresult.set(g, m);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t// ============================================\n\t// Reads\n\t// ============================================\n\n\t/**\n\t * Every byline column plus the avatar's media columns, folded in by the\n\t * same `LEFT JOIN media` the credit-hydration queries use. Only the\n\t * single-row finders select this; `findMany` stays a single-table scan.\n\t */\n\tprivate selectBylineWithAvatar() {\n\t\treturn this.db\n\t\t\t.selectFrom(\"_emdash_bylines as b\")\n\t\t\t.leftJoin(\"media as m\", \"m.id\", \"b.avatar_media_id\")\n\t\t\t.selectAll(\"b\")\n\t\t\t.select([\n\t\t\t\t\"m.storage_key as avatar_storage_key\",\n\t\t\t\t\"m.alt as avatar_alt\",\n\t\t\t\t\"m.blurhash as avatar_blurhash\",\n\t\t\t\t\"m.dominant_color as avatar_dominant_color\",\n\t\t\t]);\n\t}\n\n\tasync findById(id: string): Promise<BylineSummary | null> {\n\t\tconst row = await this.selectBylineWithAvatar().where(\"b.id\", \"=\", id).executeTakeFirst();\n\t\treturn this.withCustomFieldsOne(row);\n\t}\n\n\t/**\n\t * Find a byline by slug. When `locale` is provided, filter by it strictly.\n\t * When omitted, returns the lowest-locale-code match (deterministic across\n\t * calls). Mirrors `TaxonomyRepository.findBySlug`.\n\t */\n\tasync findBySlug(slug: string, options?: { locale?: string }): Promise<BylineSummary | null> {\n\t\tlet query = this.selectBylineWithAvatar().where(\"b.slug\", \"=\", slug);\n\t\tif (options?.locale !== undefined) query = query.where(\"b.locale\", \"=\", options.locale);\n\t\tconst row = await query.orderBy(\"b.locale\", \"asc\").executeTakeFirst();\n\t\treturn this.withCustomFieldsOne(row);\n\t}\n\n\t/**\n\t * Find the byline linked to a CMS user. Post-migration 040 the partial\n\t * unique on user_id is `(user_id, locale)`, so `locale` is required to\n\t * disambiguate when multiple locale variants exist. When omitted, returns\n\t * the lowest-locale-code match.\n\t */\n\tasync findByUserId(userId: string, options?: { locale?: string }): Promise<BylineSummary | null> {\n\t\tlet query = this.selectBylineWithAvatar().where(\"b.user_id\", \"=\", userId);\n\t\tif (options?.locale !== undefined) query = query.where(\"b.locale\", \"=\", options.locale);\n\t\tconst row = await query.orderBy(\"b.locale\", \"asc\").executeTakeFirst();\n\t\treturn this.withCustomFieldsOne(row);\n\t}\n\n\tasync findMany(options?: {\n\t\tsearch?: string;\n\t\tisGuest?: boolean;\n\t\tuserId?: string;\n\t\tlocale?: string;\n\t\tcursor?: string;\n\t\tlimit?: number;\n\t}): Promise<FindManyResult<BylineSummary>> {\n\t\tconst limit = Math.min(Math.max(options?.limit ?? 50, 1), 100);\n\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"_emdash_bylines\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(\"created_at\", \"desc\")\n\t\t\t.orderBy(\"id\", \"desc\")\n\t\t\t.limit(limit + 1);\n\n\t\tif (options?.search) {\n\t\t\tconst escaped = options.search\n\t\t\t\t.replaceAll(\"\\\\\", \"\\\\\\\\\")\n\t\t\t\t.replaceAll(\"%\", \"\\\\%\")\n\t\t\t\t.replaceAll(\"_\", \"\\\\_\");\n\t\t\tconst term = `%${escaped}%`;\n\t\t\tquery = query.where((eb) =>\n\t\t\t\teb.or([eb(\"display_name\", \"like\", term), eb(\"slug\", \"like\", term)]),\n\t\t\t);\n\t\t}\n\n\t\tif (options?.isGuest !== undefined) {\n\t\t\tquery = query.where(\"is_guest\", \"=\", options.isGuest ? 1 : 0);\n\t\t}\n\n\t\tif (options?.userId !== undefined) {\n\t\t\tquery = query.where(\"user_id\", \"=\", options.userId);\n\t\t}\n\n\t\tif (options?.locale !== undefined) {\n\t\t\tquery = query.where(\"locale\", \"=\", options.locale);\n\t\t}\n\n\t\tif (options?.cursor) {\n\t\t\tconst decoded = decodeCursor(options.cursor);\n\t\t\tquery = query.where((eb) =>\n\t\t\t\teb.or([\n\t\t\t\t\teb(\"created_at\", \"<\", decoded.orderValue),\n\t\t\t\t\teb.and([eb(\"created_at\", \"=\", decoded.orderValue), eb(\"id\", \"<\", decoded.id)]),\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\n\t\tconst rows = await query.execute();\n\t\tconst pageRows = rows.slice(0, limit);\n\t\tconst items = await this.withCustomFields(pageRows);\n\t\tconst result: FindManyResult<BylineSummary> = { items };\n\n\t\tif (rows.length > limit) {\n\t\t\tconst last = items.at(-1);\n\t\t\tif (last) {\n\t\t\t\tresult.nextCursor = encodeCursor(last.createdAt, last.id);\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * List every sibling row in `translation_group`. Used by the admin\n\t * `TranslationsPanel` to render one entry per configured locale.\n\t */\n\tasync listTranslations(id: string): Promise<BylineSummary[]> {\n\t\tconst anchor = await this.findById(id);\n\t\tif (!anchor) return [];\n\t\tconst group = anchor.translationGroup ?? anchor.id;\n\t\treturn this.findByTranslationGroup(group);\n\t}\n\n\t/**\n\t * Direct lookup by `translation_group`. Returns every locale variant of a\n\t * byline, ordered by locale code (deterministic).\n\t */\n\tasync findByTranslationGroup(translationGroup: string): Promise<BylineSummary[]> {\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_bylines\")\n\t\t\t.selectAll()\n\t\t\t.where(\"translation_group\", \"=\", translationGroup)\n\t\t\t.orderBy(\"locale\", \"asc\")\n\t\t\t.execute();\n\t\treturn this.withCustomFields(rows);\n\t}\n\n\t/**\n\t * Validate a `customFields` input map into a write list before any row\n\t * write — throws `EmDashValidationError` on unknown slugs, type\n\t * mismatches, or select-choice misses.\n\t */\n\tprivate async resolveCustomFieldWrites(\n\t\tcustomFields: Record<string, unknown> | undefined,\n\t): Promise<Array<{ field: BylineFieldDefinition; value: CustomFieldValue }>> {\n\t\tif (!customFields || Object.keys(customFields).length === 0) return [];\n\t\tconst defs = await getBylineFieldDefs(this.db);\n\t\tconst bySlug = new Map(defs.map((d) => [d.slug, d]));\n\t\tconst writes: Array<{ field: BylineFieldDefinition; value: CustomFieldValue }> = [];\n\t\tfor (const [slug, raw] of Object.entries(customFields)) {\n\t\t\tconst field = bySlug.get(slug);\n\t\t\tif (!field) {\n\t\t\t\tthrow new EmDashValidationError(`Unknown byline custom field \"${slug}\"`, {\n\t\t\t\t\tslug,\n\t\t\t\t\tregistered: defs.map((d) => d.slug),\n\t\t\t\t});\n\t\t\t}\n\t\t\twrites.push({ field, value: coerceFieldValue(field, raw) });\n\t\t}\n\t\treturn writes;\n\t}\n\n\t/**\n\t * Write a validated custom-field list against a byline row inside the\n\t * caller's transaction. Per-field writes route to\n\t * `_emdash_byline_field_values` (translatable) or\n\t * `_emdash_byline_field_group_values` (group-shared); `null` clears.\n\t * Returns `true` when any group-shared row was touched so the caller\n\t * can invalidate the per-request cache post-commit.\n\t */\n\tprivate async applyCustomFieldWritesInTrx(\n\t\ttrx: Kysely<Database>,\n\t\tbylineId: string,\n\t\ttranslationGroup: string,\n\t\twrites: Array<{ field: BylineFieldDefinition; value: CustomFieldValue }>,\n\t\tnow: string,\n\t): Promise<boolean> {\n\t\tif (writes.length === 0) return false;\n\t\tlet touchedGroupShared = false;\n\t\tfor (const { field, value } of writes) {\n\t\t\tif (!field.translatable) touchedGroupShared = true;\n\t\t\tif (field.translatable) {\n\t\t\t\tif (value === null) {\n\t\t\t\t\tawait trx\n\t\t\t\t\t\t.deleteFrom(\"_emdash_byline_field_values\")\n\t\t\t\t\t\t.where(\"byline_id\", \"=\", bylineId)\n\t\t\t\t\t\t.where(\"field_id\", \"=\", field.id)\n\t\t\t\t\t\t.execute();\n\t\t\t\t} else {\n\t\t\t\t\tconst encoded = JSON.stringify(value);\n\t\t\t\t\tawait trx\n\t\t\t\t\t\t.insertInto(\"_emdash_byline_field_values\")\n\t\t\t\t\t\t.values({\n\t\t\t\t\t\t\tbyline_id: bylineId,\n\t\t\t\t\t\t\tfield_id: field.id,\n\t\t\t\t\t\t\tvalue: encoded,\n\t\t\t\t\t\t\tcreated_at: now,\n\t\t\t\t\t\t\tupdated_at: now,\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.onConflict((oc) =>\n\t\t\t\t\t\t\toc.columns([\"byline_id\", \"field_id\"]).doUpdateSet({\n\t\t\t\t\t\t\t\tvalue: encoded,\n\t\t\t\t\t\t\t\tupdated_at: now,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.execute();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (value === null) {\n\t\t\t\t\tawait trx\n\t\t\t\t\t\t.deleteFrom(\"_emdash_byline_field_group_values\")\n\t\t\t\t\t\t.where(\"translation_group\", \"=\", translationGroup)\n\t\t\t\t\t\t.where(\"field_id\", \"=\", field.id)\n\t\t\t\t\t\t.execute();\n\t\t\t\t} else {\n\t\t\t\t\tconst encoded = JSON.stringify(value);\n\t\t\t\t\tawait trx\n\t\t\t\t\t\t.insertInto(\"_emdash_byline_field_group_values\")\n\t\t\t\t\t\t.values({\n\t\t\t\t\t\t\ttranslation_group: translationGroup,\n\t\t\t\t\t\t\tfield_id: field.id,\n\t\t\t\t\t\t\tvalue: encoded,\n\t\t\t\t\t\t\tcreated_at: now,\n\t\t\t\t\t\t\tupdated_at: now,\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.onConflict((oc) =>\n\t\t\t\t\t\t\toc.columns([\"translation_group\", \"field_id\"]).doUpdateSet({\n\t\t\t\t\t\t\t\tvalue: encoded,\n\t\t\t\t\t\t\t\tupdated_at: now,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.execute();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn touchedGroupShared;\n\t}\n\n\tasync create(input: CreateBylineInput): Promise<BylineSummary> {\n\t\tconst id = ulid();\n\t\tconst now = new Date().toISOString();\n\n\t\t// Validate customFields before opening the transaction so a bad\n\t\t// value surfaces as VALIDATION_ERROR without aborting an insert.\n\t\tconst customFieldWrites = await this.resolveCustomFieldWrites(input.customFields);\n\n\t\t// translationOf joins the source's group; otherwise mint a fresh\n\t\t// group = id (matches migration 040's backfill pattern).\n\t\tlet translationGroup: string = id;\n\t\tif (input.translationOf) {\n\t\t\tconst source = await this.findById(input.translationOf);\n\t\t\tif (!source) throw new Error(\"Source byline for translation not found\");\n\t\t\ttranslationGroup = source.translationGroup ?? source.id;\n\t\t}\n\n\t\t// Wrap insert + custom-field writes in one transaction so a\n\t\t// partial failure rolls both back on Node/PG. D1 still has its\n\t\t// own no-transactions limitation — recovery for that path lives\n\t\t// in `handleBylineCreate`.\n\t\tlet touchedGroupShared = false;\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_bylines\")\n\t\t\t\t.values({\n\t\t\t\t\tid,\n\t\t\t\t\tslug: input.slug,\n\t\t\t\t\tdisplay_name: input.displayName,\n\t\t\t\t\tbio: input.bio ?? null,\n\t\t\t\t\tavatar_media_id: input.avatarMediaId ?? null,\n\t\t\t\t\twebsite_url: input.websiteUrl ?? null,\n\t\t\t\t\tuser_id: input.userId ?? null,\n\t\t\t\t\tis_guest: input.isGuest ? 1 : 0,\n\t\t\t\t\tcreated_at: now,\n\t\t\t\t\tupdated_at: now,\n\t\t\t\t\t// Omit `locale` so the DB DEFAULT (configured defaultLocale)\n\t\t\t\t\t// applies — matches TaxonomyRepository.create.\n\t\t\t\t\t...(input.locale !== undefined ? { locale: input.locale } : {}),\n\t\t\t\t\ttranslation_group: translationGroup,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\ttouchedGroupShared = await this.applyCustomFieldWritesInTrx(\n\t\t\t\ttrx,\n\t\t\t\tid,\n\t\t\t\ttranslationGroup,\n\t\t\t\tcustomFieldWrites,\n\t\t\t\tnow,\n\t\t\t);\n\t\t});\n\n\t\tif (touchedGroupShared) {\n\t\t\tclearRequestCacheEntry(`byline-field-group-values:${translationGroup}`);\n\t\t}\n\t\tinvalidateBylineObjectCache();\n\n\t\tconst byline = await this.findById(id);\n\t\tif (!byline) {\n\t\t\tthrow new Error(\"Failed to create byline\");\n\t\t}\n\t\treturn byline;\n\t}\n\n\tasync update(id: string, input: UpdateBylineInput): Promise<BylineSummary | null> {\n\t\tconst existing = await this.findById(id);\n\t\tif (!existing) return null;\n\n\t\t// Validate customFields before opening the transaction so a bad\n\t\t// value surfaces as VALIDATION_ERROR without aborting an update.\n\t\tconst customFieldWrites = await this.resolveCustomFieldWrites(input.customFields);\n\n\t\tconst now = new Date().toISOString();\n\t\tconst updates: Record<string, unknown> = { updated_at: now };\n\n\t\tif (input.slug !== undefined) updates.slug = input.slug;\n\t\tif (input.displayName !== undefined) updates.display_name = input.displayName;\n\t\tif (input.bio !== undefined) updates.bio = input.bio;\n\t\tif (input.avatarMediaId !== undefined) updates.avatar_media_id = input.avatarMediaId;\n\t\tif (input.websiteUrl !== undefined) updates.website_url = input.websiteUrl;\n\t\tif (input.userId !== undefined) updates.user_id = input.userId;\n\t\tif (input.isGuest !== undefined) updates.is_guest = input.isGuest ? 1 : 0;\n\n\t\tconst group = existing.translationGroup ?? existing.id;\n\t\t// Wrap row update + custom-field writes in one transaction so a\n\t\t// partial failure rolls both back on Node/PG. The post-commit\n\t\t// invalidation below clears the per-request cache that the\n\t\t// top-of-method `findById` populated for this group.\n\t\tlet touchedGroupShared = false;\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tawait trx.updateTable(\"_emdash_bylines\").set(updates).where(\"id\", \"=\", id).execute();\n\t\t\ttouchedGroupShared = await this.applyCustomFieldWritesInTrx(\n\t\t\t\ttrx,\n\t\t\t\tid,\n\t\t\t\tgroup,\n\t\t\t\tcustomFieldWrites,\n\t\t\t\tnow,\n\t\t\t);\n\t\t});\n\n\t\tif (touchedGroupShared) {\n\t\t\tclearRequestCacheEntry(`byline-field-group-values:${group}`);\n\t\t}\n\t\tinvalidateBylineObjectCache();\n\n\t\treturn await this.findById(id);\n\t}\n\n\t/**\n\t * Delete a byline row. When this row is the last sibling in its\n\t * translation group, also drops every junction row pointing at the group,\n\t * clears `primary_byline_id` references, and removes the byline's\n\t * non-translatable custom-field values. When other siblings remain in\n\t * the group, junctions, `primary_byline_id` pointers, and group-shared\n\t * custom-field values stay intact — the credit (and its shared metadata)\n\t * lives on at other locales.\n\t *\n\t * **Application-level cascade.** The byline domain has standardised on\n\t * app-level cascade rather than trusting FK ON DELETE CASCADE, partly\n\t * because migration 040 had to strip its own FK to support the\n\t * translation_group remap (#1021), and partly so cleanup doesn't\n\t * depend on `PRAGMA foreign_keys = ON` (set in production via\n\t * `connection.ts:60`, but easy to bypass in tests, scripts, and\n\t * one-off tools). Every byline-related deletion table is cleared\n\t * explicitly here:\n\t *\n\t * - `_emdash_byline_field_values` (per-byline translatable values) —\n\t * migration 041 declares FK ON DELETE CASCADE on `byline_id`; the\n\t * explicit DELETE removes the dependency on that pragma.\n\t * - `_emdash_content_bylines` — migration 040 dropped its FK.\n\t * - `ec_*.primary_byline_id` — never had an FK.\n\t * - `_emdash_byline_field_group_values` (translation-group-keyed) —\n\t * keyed by a text column with no FK to bylines, so app-level cleanup\n\t * is the only path.\n\t *\n\t * The FKs that remain (migration 041) serve as defense-in-depth.\n\t */\n\tasync delete(id: string): Promise<boolean> {\n\t\tconst existing = await this.findById(id);\n\t\tif (!existing) return false;\n\n\t\tconst group = existing.translationGroup ?? existing.id;\n\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\t// Per-row translatable custom-field values. Done BEFORE the\n\t\t\t// byline row delete so the application-level cleanup is\n\t\t\t// observable in the transaction log even if FK enforcement is\n\t\t\t// off; migration 041's FK ON DELETE CASCADE would catch any\n\t\t\t// row we miss, but the explicit DELETE is what the rest of\n\t\t\t// the byline domain expects to see.\n\t\t\tawait trx.deleteFrom(\"_emdash_byline_field_values\").where(\"byline_id\", \"=\", id).execute();\n\n\t\t\tawait trx.deleteFrom(\"_emdash_bylines\").where(\"id\", \"=\", id).execute();\n\n\t\t\t// Count remaining siblings in the translation group. If none\n\t\t\t// remain, purge dependent rows; otherwise leave them intact so\n\t\t\t// the credit still resolves at other locales.\n\t\t\tconst remaining = await trx\n\t\t\t\t.selectFrom(\"_emdash_bylines\")\n\t\t\t\t.select(({ fn }) => [fn.count<number>(\"id\").as(\"count\")])\n\t\t\t\t.where(\"translation_group\", \"=\", group)\n\t\t\t\t.executeTakeFirst();\n\t\t\tconst remainingCount = Number(remaining?.count ?? 0);\n\t\t\tif (remainingCount > 0) return;\n\n\t\t\t// Last sibling gone: cascade in application code.\n\t\t\tawait trx.deleteFrom(\"_emdash_content_bylines\").where(\"byline_id\", \"=\", group).execute();\n\n\t\t\t// Group-shared custom-field values are keyed by translation_group\n\t\t\t// (no FK to bylines), so they don't cascade with the byline row.\n\t\t\t// Clean them up explicitly so deleting the last sibling of an\n\t\t\t// identity doesn't leave orphan group values pointing at a\n\t\t\t// vanished translation group. Per-row translatable values\n\t\t\t// (`_emdash_byline_field_values` keyed by byline_id) already\n\t\t\t// cascaded when each sibling row was deleted, so no extra\n\t\t\t// cleanup is needed for that table.\n\t\t\tawait trx\n\t\t\t\t.deleteFrom(\"_emdash_byline_field_group_values\")\n\t\t\t\t.where(\"translation_group\", \"=\", group)\n\t\t\t\t.execute();\n\n\t\t\tconst tableNames = await listTablesLike(trx, \"ec_%\");\n\t\t\tfor (const tableName of tableNames) {\n\t\t\t\tvalidateIdentifier(tableName, \"content table\");\n\t\t\t\tawait sql`\n\t\t\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\t\t\tSET primary_byline_id = NULL\n\t\t\t\t\tWHERE primary_byline_id = ${group}\n\t\t\t\t`.execute(trx);\n\t\t\t}\n\t\t});\n\n\t\tinvalidateBylineObjectCache();\n\t\treturn true;\n\t}\n\n\t/**\n\t * Strict per-locale credit hydration. Joins `_emdash_content_bylines` to\n\t * `_emdash_bylines` on `translation_group = byline_id`, then filters to\n\t * the requested locale. Credits whose translation group lacks a row at\n\t * the requested locale are omitted — callers wanting fallback behaviour\n\t * apply it themselves. Mirrors `TaxonomyRepository.getTermsForEntry`.\n\t */\n\tasync getContentBylines(\n\t\tcollectionSlug: string,\n\t\tcontentId: string,\n\t\toptions?: { locale?: string },\n\t): Promise<ContentBylineCredit[]> {\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"_emdash_content_bylines as cb\")\n\t\t\t.innerJoin(\"_emdash_bylines as b\", \"b.translation_group\", \"cb.byline_id\")\n\t\t\t.leftJoin(\"media as m\", \"m.id\", \"b.avatar_media_id\")\n\t\t\t.select([\n\t\t\t\t\"cb.sort_order as sort_order\",\n\t\t\t\t\"cb.role_label as role_label\",\n\t\t\t\t\"b.id as id\",\n\t\t\t\t\"b.slug as slug\",\n\t\t\t\t\"b.display_name as display_name\",\n\t\t\t\t\"b.bio as bio\",\n\t\t\t\t\"b.avatar_media_id as avatar_media_id\",\n\t\t\t\t\"m.storage_key as avatar_storage_key\",\n\t\t\t\t\"m.alt as avatar_alt\",\n\t\t\t\t\"m.blurhash as avatar_blurhash\",\n\t\t\t\t\"m.dominant_color as avatar_dominant_color\",\n\t\t\t\t\"b.website_url as website_url\",\n\t\t\t\t\"b.user_id as user_id\",\n\t\t\t\t\"b.is_guest as is_guest\",\n\t\t\t\t\"b.created_at as created_at\",\n\t\t\t\t\"b.updated_at as updated_at\",\n\t\t\t\t\"b.locale as locale\",\n\t\t\t\t\"b.translation_group as translation_group\",\n\t\t\t])\n\t\t\t.where(\"cb.collection_slug\", \"=\", collectionSlug)\n\t\t\t.where(\"cb.content_id\", \"=\", contentId)\n\t\t\t.orderBy(\"cb.sort_order\", \"asc\");\n\t\tif (options?.locale !== undefined) query = query.where(\"b.locale\", \"=\", options.locale);\n\n\t\tconst rows = await query.execute();\n\t\t// Reconstruct byline rows to feed `withCustomFields`. The JOIN selects\n\t\t// the `BylineRow` columns under the `b.` alias plus the avatar media\n\t\t// columns from the `media` LEFT JOIN; carry both through so\n\t\t// `rowToByline` can populate `avatarStorageKey`/`avatarAlt` (otherwise\n\t\t// the join runs but its values are dropped here).\n\t\tconst bylineRows: BylineRowWithAvatar[] = rows.map((row) => ({\n\t\t\tid: row.id,\n\t\t\tslug: row.slug,\n\t\t\tdisplay_name: row.display_name,\n\t\t\tbio: row.bio,\n\t\t\tavatar_media_id: row.avatar_media_id,\n\t\t\tavatar_storage_key: row.avatar_storage_key,\n\t\t\tavatar_alt: row.avatar_alt,\n\t\t\tavatar_blurhash: row.avatar_blurhash,\n\t\t\tavatar_dominant_color: row.avatar_dominant_color,\n\t\t\twebsite_url: row.website_url,\n\t\t\tuser_id: row.user_id,\n\t\t\tis_guest: row.is_guest,\n\t\t\tcreated_at: row.created_at,\n\t\t\tupdated_at: row.updated_at,\n\t\t\tlocale: row.locale,\n\t\t\ttranslation_group: row.translation_group,\n\t\t}));\n\t\tconst hydrated = await this.withCustomFields(bylineRows);\n\t\treturn rows.map((row, i) => {\n\t\t\tconst byline = hydrated[i];\n\t\t\tif (!byline) {\n\t\t\t\t// Defensive: hydrated and rows are produced in lock-step;\n\t\t\t\t// this branch is unreachable unless `withCustomFields`\n\t\t\t\t// breaks its contract.\n\t\t\t\tthrow new Error(\"getContentBylines: hydration row count mismatch\");\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tbyline,\n\t\t\t\tsortOrder: row.sort_order,\n\t\t\t\troleLabel: row.role_label,\n\t\t\t};\n\t\t});\n\t}\n\n\t/**\n\t * Does this entry have any explicit byline credits — at any locale?\n\t *\n\t * Used to disambiguate \"no credits exist\" (fall back to author-linked\n\t * byline) from \"credits exist but don't resolve at the requested locale\"\n\t * (strict per-locale model: render no byline). Without this check the\n\t * locale-strict hydration would silently turn a missing translation into\n\t * an author-inferred byline, contradicting editorial intent.\n\t */\n\tasync hasContentBylines(collectionSlug: string, contentId: string): Promise<boolean> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_content_bylines\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"collection_slug\", \"=\", collectionSlug)\n\t\t\t.where(\"content_id\", \"=\", contentId)\n\t\t\t.limit(1)\n\t\t\t.executeTakeFirst();\n\t\treturn row !== undefined;\n\t}\n\n\t/**\n\t * Batch variant of `hasContentBylines`. Returns the set of content IDs\n\t * that have at least one junction row (locale-agnostic).\n\t */\n\tasync hasContentBylinesMany(collectionSlug: string, contentIds: string[]): Promise<Set<string>> {\n\t\tconst result = new Set<string>();\n\t\tif (contentIds.length === 0) return result;\n\n\t\tconst uniqueContentIds = [...new Set(contentIds)];\n\t\tfor (const chunk of chunks(uniqueContentIds, SQL_BATCH_SIZE)) {\n\t\t\tconst rows = await this.db\n\t\t\t\t.selectFrom(\"_emdash_content_bylines\")\n\t\t\t\t.select(\"content_id\")\n\t\t\t\t.distinct()\n\t\t\t\t.where(\"collection_slug\", \"=\", collectionSlug)\n\t\t\t\t.where(\"content_id\", \"in\", chunk)\n\t\t\t\t.execute();\n\t\t\tfor (const row of rows) result.add(row.content_id);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Batch variant of `getContentBylines`. Same strict-per-locale semantics\n\t * applied to the requested locale (single value, not per-entry).\n\t *\n\t * When callers need per-entry-locale filtering (e.g. a list endpoint\n\t * returning entries at mixed locales), they should group the input ids by\n\t * the entry's locale and call this method once per group.\n\t *\n\t * When the caller will issue multiple `getContentBylinesMany` calls in\n\t * one request (e.g. per locale bucket) and wants a *single* batched\n\t * customFields hydration over the union of returned bylines, pass\n\t * `skipHydration: true` on each call and finish with\n\t * `hydrateBylineCustomFields(allBylines)`. The returned bylines carry\n\t * `customFields = {}` until that hydration call runs — matching the\n\t * \"always populated\" invariant from AC #6 — so callers that forget to\n\t * hydrate get an empty map rather than `undefined`.\n\t */\n\tasync getContentBylinesMany(\n\t\tcollectionSlug: string,\n\t\tcontentIds: string[],\n\t\toptions?: { locale?: string; skipHydration?: boolean },\n\t): Promise<Map<string, ContentBylineCredit[]>> {\n\t\tconst result = new Map<string, ContentBylineCredit[]>();\n\t\tif (contentIds.length === 0) return result;\n\n\t\tconst uniqueContentIds = [...new Set(contentIds)];\n\t\tfor (const chunk of chunks(uniqueContentIds, SQL_BATCH_SIZE)) {\n\t\t\tlet query = this.db\n\t\t\t\t.selectFrom(\"_emdash_content_bylines as cb\")\n\t\t\t\t.innerJoin(\"_emdash_bylines as b\", \"b.translation_group\", \"cb.byline_id\")\n\t\t\t\t.leftJoin(\"media as m\", \"m.id\", \"b.avatar_media_id\")\n\t\t\t\t.select([\n\t\t\t\t\t\"cb.content_id as content_id\",\n\t\t\t\t\t\"cb.sort_order as sort_order\",\n\t\t\t\t\t\"cb.role_label as role_label\",\n\t\t\t\t\t\"b.id as id\",\n\t\t\t\t\t\"b.slug as slug\",\n\t\t\t\t\t\"b.display_name as display_name\",\n\t\t\t\t\t\"b.bio as bio\",\n\t\t\t\t\t\"b.avatar_media_id as avatar_media_id\",\n\t\t\t\t\t\"m.storage_key as avatar_storage_key\",\n\t\t\t\t\t\"m.alt as avatar_alt\",\n\t\t\t\t\t\"m.blurhash as avatar_blurhash\",\n\t\t\t\t\t\"m.dominant_color as avatar_dominant_color\",\n\t\t\t\t\t\"b.website_url as website_url\",\n\t\t\t\t\t\"b.user_id as user_id\",\n\t\t\t\t\t\"b.is_guest as is_guest\",\n\t\t\t\t\t\"b.created_at as created_at\",\n\t\t\t\t\t\"b.updated_at as updated_at\",\n\t\t\t\t\t\"b.locale as locale\",\n\t\t\t\t\t\"b.translation_group as translation_group\",\n\t\t\t\t])\n\t\t\t\t.where(\"cb.collection_slug\", \"=\", collectionSlug)\n\t\t\t\t.where(\"cb.content_id\", \"in\", chunk)\n\t\t\t\t.orderBy(\"cb.sort_order\", \"asc\");\n\t\t\tif (options?.locale !== undefined) query = query.where(\"b.locale\", \"=\", options.locale);\n\n\t\t\tconst rows = await query.execute();\n\t\t\t// Carry the avatar media columns from the LEFT JOIN through the\n\t\t\t// reshape so `rowToByline` can populate avatarStorageKey/avatarAlt.\n\t\t\tconst bylineRows: BylineRowWithAvatar[] = rows.map((row) => ({\n\t\t\t\tid: row.id,\n\t\t\t\tslug: row.slug,\n\t\t\t\tdisplay_name: row.display_name,\n\t\t\t\tbio: row.bio,\n\t\t\t\tavatar_media_id: row.avatar_media_id,\n\t\t\t\tavatar_storage_key: row.avatar_storage_key,\n\t\t\t\tavatar_alt: row.avatar_alt,\n\t\t\t\tavatar_blurhash: row.avatar_blurhash,\n\t\t\t\tavatar_dominant_color: row.avatar_dominant_color,\n\t\t\t\twebsite_url: row.website_url,\n\t\t\t\tuser_id: row.user_id,\n\t\t\t\tis_guest: row.is_guest,\n\t\t\t\tcreated_at: row.created_at,\n\t\t\t\tupdated_at: row.updated_at,\n\t\t\t\tlocale: row.locale,\n\t\t\t\ttranslation_group: row.translation_group,\n\t\t\t}));\n\n\t\t\t// When `skipHydration` is set, return BylineSummary objects with\n\t\t\t// `customFields = {}`. The caller is responsible for batching\n\t\t\t// `hydrateBylineCustomFields` across multiple\n\t\t\t// `getContentBylinesMany` calls. Otherwise hydrate per-call —\n\t\t\t// the historical behaviour for solo callers.\n\t\t\tlet bylines: BylineSummary[];\n\t\t\tif (options?.skipHydration === true) {\n\t\t\t\tbylines = bylineRows.map(rowToByline);\n\t\t\t\tfor (const b of bylines) b.customFields = {};\n\t\t\t} else {\n\t\t\t\tbylines = await this.withCustomFields(bylineRows);\n\t\t\t}\n\n\t\t\tfor (let i = 0; i < rows.length; i++) {\n\t\t\t\tconst row = rows[i];\n\t\t\t\tconst byline = bylines[i];\n\t\t\t\tif (!row || !byline) continue;\n\t\t\t\tconst contentId = row.content_id;\n\t\t\t\tconst credit: ContentBylineCredit = {\n\t\t\t\t\tbyline,\n\t\t\t\t\tsortOrder: row.sort_order,\n\t\t\t\t\troleLabel: row.role_label,\n\t\t\t\t};\n\t\t\t\tconst existing = result.get(contentId);\n\t\t\t\tif (existing) {\n\t\t\t\t\texisting.push(credit);\n\t\t\t\t} else {\n\t\t\t\t\tresult.set(contentId, [credit]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Batch-fetch byline profiles linked to user IDs in a single query.\n\t * Strict-locale variant of `findByUserId`.\n\t *\n\t * `skipHydration: true` returns bylines with `customFields = {}` so\n\t * callers issuing multiple `findByUserIds` calls in one request (e.g.\n\t * the per-locale-bucket author-fallback path in `getBylinesForEntries`)\n\t * can defer customFields hydration to a single batched\n\t * `hydrateBylineCustomFields` call across the union — keeping the\n\t * Phase 3 query-count envelope at \"+1 group-shared query per\n\t * hydration pass\" even when buckets fetch disjoint author bylines.\n\t */\n\tasync findByUserIds(\n\t\tuserIds: string[],\n\t\toptions?: { locale?: string; skipHydration?: boolean },\n\t): Promise<Map<string, BylineSummary>> {\n\t\tconst result = new Map<string, BylineSummary>();\n\t\tif (userIds.length === 0) return result;\n\n\t\tfor (const chunk of chunks(userIds, SQL_BATCH_SIZE)) {\n\t\t\t// LEFT JOIN media so author-inferred bylines (the fallback path in\n\t\t\t// `getBylinesForEntries`) carry the same render-ready avatar storage\n\t\t\t// key as explicitly-credited bylines do.\n\t\t\tlet query = this.db\n\t\t\t\t.selectFrom(\"_emdash_bylines as b\")\n\t\t\t\t.leftJoin(\"media as m\", \"m.id\", \"b.avatar_media_id\")\n\t\t\t\t.select([\n\t\t\t\t\t\"b.id as id\",\n\t\t\t\t\t\"b.slug as slug\",\n\t\t\t\t\t\"b.display_name as display_name\",\n\t\t\t\t\t\"b.bio as bio\",\n\t\t\t\t\t\"b.avatar_media_id as avatar_media_id\",\n\t\t\t\t\t\"m.storage_key as avatar_storage_key\",\n\t\t\t\t\t\"m.alt as avatar_alt\",\n\t\t\t\t\t\"m.blurhash as avatar_blurhash\",\n\t\t\t\t\t\"m.dominant_color as avatar_dominant_color\",\n\t\t\t\t\t\"b.website_url as website_url\",\n\t\t\t\t\t\"b.user_id as user_id\",\n\t\t\t\t\t\"b.is_guest as is_guest\",\n\t\t\t\t\t\"b.created_at as created_at\",\n\t\t\t\t\t\"b.updated_at as updated_at\",\n\t\t\t\t\t\"b.locale as locale\",\n\t\t\t\t\t\"b.translation_group as translation_group\",\n\t\t\t\t])\n\t\t\t\t.where(\"b.user_id\", \"in\", chunk);\n\t\t\tif (options?.locale !== undefined) query = query.where(\"b.locale\", \"=\", options.locale);\n\n\t\t\tconst rows = await query.execute();\n\t\t\tlet bylines: BylineSummary[];\n\t\t\tif (options?.skipHydration === true) {\n\t\t\t\tbylines = rows.map(rowToByline);\n\t\t\t\tfor (const b of bylines) b.customFields = {};\n\t\t\t} else {\n\t\t\t\tbylines = await this.withCustomFields(rows);\n\t\t\t}\n\n\t\t\tfor (let i = 0; i < rows.length; i++) {\n\t\t\t\tconst row = rows[i];\n\t\t\t\tconst summary = bylines[i];\n\t\t\t\tif (!row || !summary || !row.user_id) continue;\n\t\t\t\tresult.set(row.user_id, summary);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Clone every junction row from `sourceContentId` to `targetContentId`,\n\t * preserving `sort_order` and `role_label`. Used by the content\n\t * translation flow: a newly created translation inherits the source's\n\t * byline credits at the storage level. Because the junction stores\n\t * `translation_group` (not a row id), the copy is locale-agnostic — the\n\t * credits resolve to whichever locale variants of each byline exist when\n\t * the translated entry is hydrated.\n\t *\n\t * No-op when the source has no credits. Skips when the target already\n\t * has credits (idempotent for re-runs).\n\t */\n\tasync copyContentBylines(\n\t\tcollection: string,\n\t\tsourceContentId: string,\n\t\ttargetContentId: string,\n\t): Promise<void> {\n\t\tvalidateIdentifier(collection, \"collection slug\");\n\t\tconst tableName = `ec_${collection}`;\n\t\tvalidateIdentifier(tableName, \"content table\");\n\n\t\t// Like `setContentBylines`, this method is expected to be called\n\t\t// within a transaction context (content handlers wrap in\n\t\t// withTransaction). All operations use `this.db` directly so an\n\t\t// outer transaction can serialise the copy alongside the create.\n\t\tconst existing = await this.db\n\t\t\t.selectFrom(\"_emdash_content_bylines\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"collection_slug\", \"=\", collection)\n\t\t\t.where(\"content_id\", \"=\", targetContentId)\n\t\t\t.executeTakeFirst();\n\t\tif (existing) return;\n\n\t\tconst sourceRows = await this.db\n\t\t\t.selectFrom(\"_emdash_content_bylines\")\n\t\t\t.select([\"byline_id\", \"sort_order\", \"role_label\"])\n\t\t\t.where(\"collection_slug\", \"=\", collection)\n\t\t\t.where(\"content_id\", \"=\", sourceContentId)\n\t\t\t.orderBy(\"sort_order\", \"asc\")\n\t\t\t.execute();\n\t\tif (sourceRows.length === 0) return;\n\n\t\tconst now = new Date().toISOString();\n\t\tawait this.db\n\t\t\t.insertInto(\"_emdash_content_bylines\")\n\t\t\t.values(\n\t\t\t\tsourceRows.map((row) => ({\n\t\t\t\t\tid: ulid(),\n\t\t\t\t\tcollection_slug: collection,\n\t\t\t\t\tcontent_id: targetContentId,\n\t\t\t\t\tbyline_id: row.byline_id,\n\t\t\t\t\tsort_order: row.sort_order,\n\t\t\t\t\trole_label: row.role_label,\n\t\t\t\t\tcreated_at: now,\n\t\t\t\t})),\n\t\t\t)\n\t\t\t.execute();\n\n\t\t// Mirror primary_byline_id from source so the cached pointer on the\n\t\t// target row matches the junction state we just wrote.\n\t\tconst firstByline = sourceRows[0]?.byline_id ?? null;\n\t\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET primary_byline_id = ${firstByline}\n\t\t\tWHERE id = ${targetContentId}\n\t\t`.execute(this.db);\n\n\t\t// Byline credits are folded into the target entry's cached payload.\n\t\tinvalidateCollectionCache(collection);\n\t}\n\n\t/**\n\t * Replace the set of byline credits on a content entry. Accepts row ids\n\t * at the wire (consistent with how the admin sends them), translates\n\t * each to its `translation_group` on write, and stores the group in\n\t * `_emdash_content_bylines.byline_id` and `ec_*.primary_byline_id`.\n\t *\n\t * The returned credits are hydrated with strict-locale matching at the\n\t * locale of the rows the caller supplied (i.e. the locale of the byline\n\t * each `bylineId` resolves to) — adequate for the autosave round-trip,\n\t * which then re-hydrates the entry against its own locale separately.\n\t */\n\tasync setContentBylines(\n\t\tcollectionSlug: string,\n\t\tcontentId: string,\n\t\tinputBylines: ContentBylineInput[],\n\t): Promise<ContentBylineCredit[]> {\n\t\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\t\tconst tableName = `ec_${collectionSlug}`;\n\t\tvalidateIdentifier(tableName, \"content table\");\n\n\t\t// Resolve each wire row id to its translation_group up front so we\n\t\t// can (a) validate the rows exist and (b) dedupe by the value that\n\t\t// actually lands in the junction. Deduping by wire row id BEFORE\n\t\t// resolving would let two locale siblings of the same byline slip\n\t\t// through and trigger a UNIQUE(collection, content, byline_id)\n\t\t// failure at insert time. A single SELECT keeps this O(1) DB\n\t\t// calls regardless of how many credits are being set.\n\t\tconst idToGroup = new Map<string, string>();\n\t\tif (inputBylines.length > 0) {\n\t\t\tconst wireIds = [...new Set(inputBylines.map((item) => item.bylineId))];\n\t\t\tconst rows = await this.db\n\t\t\t\t.selectFrom(\"_emdash_bylines\")\n\t\t\t\t.select([\"id\", \"translation_group\"])\n\t\t\t\t.where(\"id\", \"in\", wireIds)\n\t\t\t\t.execute();\n\t\t\tif (rows.length !== wireIds.length) {\n\t\t\t\tthrow new Error(\"One or more byline IDs do not exist\");\n\t\t\t}\n\t\t\tfor (const row of rows) {\n\t\t\t\tidToGroup.set(row.id, row.translation_group ?? row.id);\n\t\t\t}\n\t\t}\n\n\t\t// Dedupe by translation_group. Preserves the order of first\n\t\t// occurrence so the editor's intent (which sibling appears first)\n\t\t// is honored. `roleLabel` follows the first occurrence too.\n\t\tconst seenGroups = new Set<string>();\n\t\tconst bylines: Array<ContentBylineInput & { group: string }> = [];\n\t\tfor (const item of inputBylines) {\n\t\t\tconst group = idToGroup.get(item.bylineId);\n\t\t\tif (!group) {\n\t\t\t\tthrow new Error(`Missing translation_group for byline ${item.bylineId}`);\n\t\t\t}\n\t\t\tif (seenGroups.has(group)) continue;\n\t\t\tseenGroups.add(group);\n\t\t\tbylines.push({ ...item, group });\n\t\t}\n\n\t\t// This method is expected to be called within a transaction context\n\t\t// (content handlers wrap in withTransaction, seed applies sequentially).\n\t\t// All operations use this.db directly -- callers are responsible for\n\t\t// wrapping in a transaction when atomicity is required.\n\t\tawait this.db\n\t\t\t.deleteFrom(\"_emdash_content_bylines\")\n\t\t\t.where(\"collection_slug\", \"=\", collectionSlug)\n\t\t\t.where(\"content_id\", \"=\", contentId)\n\t\t\t.execute();\n\n\t\tfor (let i = 0; i < bylines.length; i++) {\n\t\t\tconst item = bylines[i];\n\t\t\tif (!item) continue;\n\t\t\tawait this.db\n\t\t\t\t.insertInto(\"_emdash_content_bylines\")\n\t\t\t\t.values({\n\t\t\t\t\tid: ulid(),\n\t\t\t\t\tcollection_slug: collectionSlug,\n\t\t\t\t\tcontent_id: contentId,\n\t\t\t\t\tbyline_id: item.group,\n\t\t\t\t\tsort_order: i,\n\t\t\t\t\trole_label: item.roleLabel ?? null,\n\t\t\t\t\tcreated_at: new Date().toISOString(),\n\t\t\t\t})\n\t\t\t\t.execute();\n\t\t}\n\n\t\tconst primaryGroup = bylines[0]?.group ?? null;\n\t\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET primary_byline_id = ${primaryGroup}\n\t\t\tWHERE id = ${contentId}\n\t\t`.execute(this.db);\n\n\t\t// Byline credits are folded into this entry's cached payload.\n\t\tinvalidateCollectionCache(collectionSlug);\n\n\t\treturn await this.getContentBylines(collectionSlug, contentId);\n\t}\n\n\tasync deleteContentBylines(collectionSlug: string, contentId: string): Promise<number> {\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_emdash_content_bylines\")\n\t\t\t.where(\"collection_slug\", \"=\", collectionSlug)\n\t\t\t.where(\"content_id\", \"=\", contentId)\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numDeletedRows ?? 0);\n\t}\n}\n"],"mappings":"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"}