emdash 0.14.0 → 0.15.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 (605) hide show
  1. package/dist/{adapters-9DybjTO6.d.mts → adapters-C4yd_UJR.d.mts} +1 -1
  2. package/dist/{adapters-9DybjTO6.d.mts.map → adapters-C4yd_UJR.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-CDdG-4Gd.mjs → allowed-origins-D0fFk9a6.mjs} +2 -2
  4. package/dist/{allowed-origins-CDdG-4Gd.mjs.map → allowed-origins-D0fFk9a6.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +15 -15
  7. package/dist/api/schemas/index.d.mts +2 -2
  8. package/dist/api/schemas/index.mjs +3 -3
  9. package/dist/{api-BMLZuwM4.mjs → api-CLwG_3dh.mjs} +519 -55
  10. package/dist/api-CLwG_3dh.mjs.map +1 -0
  11. package/dist/{api-tokens-eYymBhIT.mjs → api-tokens-ucpcNXDt.mjs} +2 -2
  12. package/dist/{api-tokens-eYymBhIT.mjs.map → api-tokens-ucpcNXDt.mjs.map} +1 -1
  13. package/dist/{apply-v4DBgjPw.mjs → apply-wJhM_bwU.mjs} +17 -17
  14. package/dist/{apply-v4DBgjPw.mjs.map → apply-wJhM_bwU.mjs.map} +1 -1
  15. package/dist/astro/index.d.mts +10 -10
  16. package/dist/astro/index.mjs +21 -5
  17. package/dist/astro/index.mjs.map +1 -1
  18. package/dist/astro/middleware/auth.d.mts +9 -9
  19. package/dist/astro/middleware/auth.mjs +6 -6
  20. package/dist/astro/middleware/auth.mjs.map +1 -1
  21. package/dist/astro/middleware/redirect.mjs +4 -4
  22. package/dist/astro/middleware/request-context.mjs +2 -2
  23. package/dist/astro/middleware/request-context.mjs.map +1 -1
  24. package/dist/astro/middleware/setup.mjs +1 -1
  25. package/dist/astro/middleware.d.mts.map +1 -1
  26. package/dist/astro/middleware.mjs +353 -71
  27. package/dist/astro/middleware.mjs.map +1 -1
  28. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
  29. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
  30. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
  31. package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
  32. package/dist/astro/routes/api/admin/bylines/_id_/index.d.mts.map +1 -1
  33. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +14 -17
  34. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
  35. package/dist/astro/routes/api/admin/bylines/_id_/translations.d.mts +9 -0
  36. package/dist/astro/routes/api/admin/bylines/_id_/translations.d.mts.map +1 -0
  37. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +70 -0
  38. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -0
  39. package/dist/astro/routes/api/admin/bylines/index.d.mts.map +1 -1
  40. package/dist/astro/routes/api/admin/bylines/index.mjs +25 -16
  41. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  42. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +10 -10
  43. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  44. package/dist/astro/routes/api/admin/comments/bulk.mjs +8 -8
  45. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  46. package/dist/astro/routes/api/admin/comments/index.mjs +8 -8
  47. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  48. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  49. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
  50. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
  51. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +32 -31
  52. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  53. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +32 -31
  54. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  55. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +31 -30
  56. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  57. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +31 -30
  58. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  59. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +33 -31
  60. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  61. package/dist/astro/routes/api/admin/plugins/index.mjs +31 -30
  62. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  63. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  64. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +31 -30
  65. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  66. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +33 -31
  67. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  68. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +31 -30
  69. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  70. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.d.mts +8 -0
  71. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.d.mts.map +1 -0
  72. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +59 -0
  73. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -0
  74. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts +8 -0
  75. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -0
  76. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +72 -0
  77. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -0
  78. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +31 -30
  79. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  80. package/dist/astro/routes/api/admin/plugins/updates.d.mts.map +1 -1
  81. package/dist/astro/routes/api/admin/plugins/updates.mjs +44 -31
  82. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  83. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +31 -30
  84. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  85. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  86. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +31 -30
  87. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  88. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  89. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  90. package/dist/astro/routes/api/admin/users/_id_/index.mjs +5 -5
  91. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
  92. package/dist/astro/routes/api/admin/users/index.mjs +5 -5
  93. package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -5
  94. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  95. package/dist/astro/routes/api/auth/invite/complete.mjs +9 -9
  96. package/dist/astro/routes/api/auth/invite/index.mjs +6 -6
  97. package/dist/astro/routes/api/auth/invite/register-options.mjs +8 -8
  98. package/dist/astro/routes/api/auth/logout.mjs +3 -3
  99. package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
  100. package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
  101. package/dist/astro/routes/api/auth/me.mjs +5 -5
  102. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  103. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
  104. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs.map +1 -1
  105. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  106. package/dist/astro/routes/api/auth/oauth/_provider_.mjs.map +1 -1
  107. package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
  108. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  109. package/dist/astro/routes/api/auth/passkey/options.mjs +10 -10
  110. package/dist/astro/routes/api/auth/passkey/register/options.mjs +8 -8
  111. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +9 -9
  112. package/dist/astro/routes/api/auth/passkey/verify.mjs +9 -9
  113. package/dist/astro/routes/api/auth/signup/complete.mjs +9 -9
  114. package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
  115. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  116. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -11
  117. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  118. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  119. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  120. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  121. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs.map +1 -1
  122. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  123. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +9 -9
  124. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +6 -6
  125. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  126. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  127. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs.map +1 -1
  128. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  129. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +6 -6
  130. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  131. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +10 -9
  132. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  133. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  134. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs.map +1 -1
  135. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  136. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  137. package/dist/astro/routes/api/content/_collection_/_id_.mjs +6 -6
  138. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  139. package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
  140. package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
  141. package/dist/astro/routes/api/dashboard.mjs +7 -7
  142. package/dist/astro/routes/api/dev/emails.mjs +3 -3
  143. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  144. package/dist/astro/routes/api/import/probe.mjs +10 -10
  145. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  146. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  147. package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -8
  148. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  149. package/dist/astro/routes/api/import/wordpress/media.mjs +8 -8
  150. package/dist/astro/routes/api/import/wordpress/prepare.mjs +8 -8
  151. package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
  152. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +7 -7
  153. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  154. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  155. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +10 -10
  156. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  157. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +11 -11
  158. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  159. package/dist/astro/routes/api/manifest.mjs +4 -4
  160. package/dist/astro/routes/api/mcp.mjs +29 -29
  161. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  162. package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
  163. package/dist/astro/routes/api/media/_id_.mjs +6 -6
  164. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  165. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  166. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  167. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  168. package/dist/astro/routes/api/media/upload-url.mjs +7 -7
  169. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  170. package/dist/astro/routes/api/media.mjs +8 -8
  171. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +7 -7
  172. package/dist/astro/routes/api/menus/_name_/items.mjs +7 -7
  173. package/dist/astro/routes/api/menus/_name_/reorder.mjs +7 -7
  174. package/dist/astro/routes/api/menus/_name_/translations.mjs +7 -7
  175. package/dist/astro/routes/api/menus/_name_.mjs +7 -7
  176. package/dist/astro/routes/api/menus/index.mjs +7 -7
  177. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  178. package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
  179. package/dist/astro/routes/api/oauth/device/code.mjs +9 -9
  180. package/dist/astro/routes/api/oauth/device/token.mjs +8 -8
  181. package/dist/astro/routes/api/oauth/register.mjs +3 -3
  182. package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
  183. package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
  184. package/dist/astro/routes/api/oauth/token.mjs +6 -6
  185. package/dist/astro/routes/api/openapi.json.mjs +3 -3
  186. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  187. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  188. package/dist/astro/routes/api/redirects/404s/index.mjs +8 -8
  189. package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
  190. package/dist/astro/routes/api/redirects/404s/summary.mjs +8 -8
  191. package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
  192. package/dist/astro/routes/api/redirects/_id_.mjs +9 -9
  193. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  194. package/dist/astro/routes/api/redirects/index.mjs +9 -9
  195. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  196. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  197. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  198. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +31 -30
  199. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  200. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +31 -30
  201. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  202. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +31 -30
  203. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  204. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +31 -30
  205. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  206. package/dist/astro/routes/api/schema/collections/index.mjs +31 -30
  207. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  208. package/dist/astro/routes/api/schema/index.mjs +6 -6
  209. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  210. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +31 -30
  211. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  212. package/dist/astro/routes/api/schema/orphans/index.mjs +31 -30
  213. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  214. package/dist/astro/routes/api/search/enable.mjs +9 -9
  215. package/dist/astro/routes/api/search/index.mjs +8 -8
  216. package/dist/astro/routes/api/search/rebuild.mjs +9 -9
  217. package/dist/astro/routes/api/search/stats.mjs +6 -6
  218. package/dist/astro/routes/api/search/suggest.mjs +8 -8
  219. package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
  220. package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
  221. package/dist/astro/routes/api/sections/index.mjs +8 -8
  222. package/dist/astro/routes/api/sections/index.mjs.map +1 -1
  223. package/dist/astro/routes/api/settings/email.mjs +4 -4
  224. package/dist/astro/routes/api/settings.mjs +10 -10
  225. package/dist/astro/routes/api/setup/admin-verify.mjs +10 -10
  226. package/dist/astro/routes/api/setup/admin.mjs +9 -9
  227. package/dist/astro/routes/api/setup/dev-bypass.mjs +22 -22
  228. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  229. package/dist/astro/routes/api/setup/index.mjs +22 -22
  230. package/dist/astro/routes/api/setup/status.mjs +4 -4
  231. package/dist/astro/routes/api/snapshot.mjs +5 -5
  232. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -10
  233. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  234. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -10
  235. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  236. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -10
  237. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  238. package/dist/astro/routes/api/taxonomies/index.mjs +11 -10
  239. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  240. package/dist/astro/routes/api/themes/preview.mjs +5 -5
  241. package/dist/astro/routes/api/typegen.mjs +5 -5
  242. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  243. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  244. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  245. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
  246. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +8 -8
  247. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +8 -8
  248. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  249. package/dist/astro/routes/api/widget-areas/index.mjs +8 -8
  250. package/dist/astro/routes/api/widget-components.mjs +3 -3
  251. package/dist/astro/routes/robots.txt.mjs +5 -5
  252. package/dist/astro/routes/sitemap-_collection_.xml.mjs +4 -4
  253. package/dist/astro/routes/sitemap.xml.mjs +5 -5
  254. package/dist/astro/types.d.mts +13 -12
  255. package/dist/astro/types.d.mts.map +1 -1
  256. package/dist/auth/providers/github.d.mts +1 -1
  257. package/dist/auth/providers/google.d.mts +1 -1
  258. package/dist/{authorize-BlyCH-96.mjs → authorize-Bkwe8kuL.mjs} +2 -2
  259. package/dist/{authorize-BlyCH-96.mjs.map → authorize-Bkwe8kuL.mjs.map} +1 -1
  260. package/dist/byline-CTaWkMh5.mjs +404 -0
  261. package/dist/byline-CTaWkMh5.mjs.map +1 -0
  262. package/dist/bylines-BYHWU3T7.mjs +174 -0
  263. package/dist/bylines-BYHWU3T7.mjs.map +1 -0
  264. package/dist/{bylines-BdUP8NuI.d.mts → bylines-DtDRNF1n.d.mts} +59 -14
  265. package/dist/bylines-DtDRNF1n.d.mts.map +1 -0
  266. package/dist/bylines-H0Xh5TMy.mjs +118 -0
  267. package/dist/bylines-H0Xh5TMy.mjs.map +1 -0
  268. package/dist/{cache-CXCpjWiL.mjs → cache-CNk1jIxp.mjs} +2 -2
  269. package/dist/{cache-CXCpjWiL.mjs.map → cache-CNk1jIxp.mjs.map} +1 -1
  270. package/dist/{challenge-store-CJ0OOHOr.mjs → challenge-store-Dng1SxKT.mjs} +1 -1
  271. package/dist/{challenge-store-CJ0OOHOr.mjs.map → challenge-store-Dng1SxKT.mjs.map} +1 -1
  272. package/dist/{chunks-DyGtu1Bv.mjs → chunks-BkfVdD-3.mjs} +2 -2
  273. package/dist/{chunks-DyGtu1Bv.mjs.map → chunks-BkfVdD-3.mjs.map} +1 -1
  274. package/dist/cli/index.mjs +21 -29
  275. package/dist/cli/index.mjs.map +1 -1
  276. package/dist/client/cf-access.d.mts +1 -1
  277. package/dist/client/index.d.mts +1 -1
  278. package/dist/client/index.mjs +1 -1
  279. package/dist/client/index.mjs.map +1 -1
  280. package/dist/{comment-Dd9MI82-.mjs → comment-_yzlBYPx.mjs} +2 -2
  281. package/dist/{comment-Dd9MI82-.mjs.map → comment-_yzlBYPx.mjs.map} +1 -1
  282. package/dist/{comments-koGI0FrK.mjs → comments-DxID-rsd.mjs} +3 -3
  283. package/dist/{comments-koGI0FrK.mjs.map → comments-DxID-rsd.mjs.map} +1 -1
  284. package/dist/{components-mZem7pbe.mjs → components-Dx3DM0gg.mjs} +1 -1
  285. package/dist/{components-mZem7pbe.mjs.map → components-Dx3DM0gg.mjs.map} +1 -1
  286. package/dist/config-CVssduLe.mjs.map +1 -1
  287. package/dist/{content-D6YG26WG.mjs → content-C0ooIs-f.mjs} +3 -3
  288. package/dist/{content-D6YG26WG.mjs.map → content-C0ooIs-f.mjs.map} +1 -1
  289. package/dist/{context-qF8d3IPR.mjs → context-sAnCaUIR.mjs} +10 -10
  290. package/dist/context-sAnCaUIR.mjs.map +1 -0
  291. package/dist/{cron-H8eJ46dv.mjs → cron-Bd3b3iuj.mjs} +1 -1
  292. package/dist/{cron-H8eJ46dv.mjs.map → cron-Bd3b3iuj.mjs.map} +1 -1
  293. package/dist/{dashboard-BmWSIUwY.mjs → dashboard-Cqw3ay2X.mjs} +4 -4
  294. package/dist/{dashboard-BmWSIUwY.mjs.map → dashboard-Cqw3ay2X.mjs.map} +1 -1
  295. package/dist/db/index.d.mts +3 -3
  296. package/dist/db/index.mjs +1 -1
  297. package/dist/db/libsql.d.mts +1 -1
  298. package/dist/db/postgres.d.mts +1 -1
  299. package/dist/db/sqlite.d.mts +1 -1
  300. package/dist/{default-Dbs22Gg4.mjs → default-BvTAYCzx.mjs} +1 -1
  301. package/dist/{default-Dbs22Gg4.mjs.map → default-BvTAYCzx.mjs.map} +1 -1
  302. package/dist/{device-flow-BqJRxa0Q.mjs → device-flow-B9oG8PwP.mjs} +4 -4
  303. package/dist/{device-flow-BqJRxa0Q.mjs.map → device-flow-B9oG8PwP.mjs.map} +1 -1
  304. package/dist/{email-console-Dmp5Q-P2.mjs → email-console-CubRll9q.mjs} +1 -1
  305. package/dist/email-console-CubRll9q.mjs.map +1 -0
  306. package/dist/{error-tSQWIl5U.mjs → error-CPh_8eLq.mjs} +16 -8
  307. package/dist/error-CPh_8eLq.mjs.map +1 -0
  308. package/dist/{escape-B8bdIryO.mjs → escape-Cg6kMELH.mjs} +1 -1
  309. package/dist/{escape-B8bdIryO.mjs.map → escape-Cg6kMELH.mjs.map} +1 -1
  310. package/dist/{fts-manager-B633C-kQ.mjs → fts-manager-Mnrtn-r2.mjs} +2 -2
  311. package/dist/{fts-manager-B633C-kQ.mjs.map → fts-manager-Mnrtn-r2.mjs.map} +1 -1
  312. package/dist/{import-CNfLOgDE.mjs → import-DG80rC_I.mjs} +3 -3
  313. package/dist/{import-CNfLOgDE.mjs.map → import-DG80rC_I.mjs.map} +1 -1
  314. package/dist/{index-BV8iJ-6s.d.mts → index-Bv1Wf1zB.d.mts} +235 -18
  315. package/dist/index-Bv1Wf1zB.d.mts.map +1 -0
  316. package/dist/{index-D2gvztOP.d.mts → index-CC42STEm.d.mts} +3 -3
  317. package/dist/{index-D2gvztOP.d.mts.map → index-CC42STEm.d.mts.map} +1 -1
  318. package/dist/index.d.mts +17 -17
  319. package/dist/index.mjs +50 -49
  320. package/dist/{load-QzYRpVN3.mjs → load-DmXNVhst.mjs} +2 -2
  321. package/dist/{load-QzYRpVN3.mjs.map → load-DmXNVhst.mjs.map} +1 -1
  322. package/dist/{loader-Cs6-Bqe6.mjs → loader-Chm5h7Gr.mjs} +3 -3
  323. package/dist/loader-Chm5h7Gr.mjs.map +1 -0
  324. package/dist/{manifest-schema-HCtSh4Jq.mjs → manifest-schema-Czqf0TLu.mjs} +1 -1
  325. package/dist/{manifest-schema-HCtSh4Jq.mjs.map → manifest-schema-Czqf0TLu.mjs.map} +1 -1
  326. package/dist/media/index.d.mts +1 -1
  327. package/dist/media/local-runtime.d.mts +11 -11
  328. package/dist/media/local-runtime.mjs +4 -4
  329. package/dist/{media-allowlist-B8EX01DH.mjs → media-allowlist-BNloC69x.mjs} +1 -1
  330. package/dist/{media-allowlist-B8EX01DH.mjs.map → media-allowlist-BNloC69x.mjs.map} +1 -1
  331. package/dist/{media-Dg7he9uK.mjs → media-oqRcNiQf.mjs} +2 -2
  332. package/dist/media-oqRcNiQf.mjs.map +1 -0
  333. package/dist/{menus-DOzIecHi.mjs → menus-Bjf5R1Qq.mjs} +2 -2
  334. package/dist/menus-Bjf5R1Qq.mjs.map +1 -0
  335. package/dist/{menus-X4Z-eBA1.mjs → menus-C75SSmRy.mjs} +30 -11
  336. package/dist/menus-C75SSmRy.mjs.map +1 -0
  337. package/dist/mime-KV5TqkMN.mjs.map +1 -1
  338. package/dist/{mode-DPRPvJYm.mjs → mode-CaaiebZI.mjs} +1 -1
  339. package/dist/{mode-DPRPvJYm.mjs.map → mode-CaaiebZI.mjs.map} +1 -1
  340. package/dist/{oauth-authorization-62GmpGIH.mjs → oauth-authorization-CTMeVfvj.mjs} +4 -4
  341. package/dist/{oauth-authorization-62GmpGIH.mjs.map → oauth-authorization-CTMeVfvj.mjs.map} +1 -1
  342. package/dist/{oauth-clients-D_B0_-Bz.mjs → oauth-clients-eJCbkVSG.mjs} +1 -1
  343. package/dist/oauth-clients-eJCbkVSG.mjs.map +1 -0
  344. package/dist/{oauth-state-store-DpsZViTu.mjs → oauth-state-store-vOSdOeGe.mjs} +1 -1
  345. package/dist/{oauth-state-store-DpsZViTu.mjs.map → oauth-state-store-vOSdOeGe.mjs.map} +1 -1
  346. package/dist/{oauth-user-lookup-meyS2oB1.mjs → oauth-user-lookup-3JwsVw6N.mjs} +1 -1
  347. package/dist/{oauth-user-lookup-meyS2oB1.mjs.map → oauth-user-lookup-3JwsVw6N.mjs.map} +1 -1
  348. package/dist/options-BL4X94qY.mjs.map +1 -1
  349. package/dist/{options-Cq64Wx0O.d.mts → options-DhV-gwJb.d.mts} +4 -4
  350. package/dist/options-DhV-gwJb.d.mts.map +1 -0
  351. package/dist/page/index.d.mts +2 -2
  352. package/dist/{parse-BFTPon-J.mjs → parse-3-caTKgt.mjs} +2 -2
  353. package/dist/{parse-BFTPon-J.mjs.map → parse-3-caTKgt.mjs.map} +1 -1
  354. package/dist/{passkey-config-Cg86_ISa.mjs → passkey-config-BloQOT3y.mjs} +1 -1
  355. package/dist/{passkey-config-Cg86_ISa.mjs.map → passkey-config-BloQOT3y.mjs.map} +1 -1
  356. package/dist/{placeholder-D3cFCU9y.d.mts → placeholder-KCkkCtgQ.d.mts} +1 -1
  357. package/dist/{placeholder-D3cFCU9y.d.mts.map → placeholder-KCkkCtgQ.d.mts.map} +1 -1
  358. package/dist/plugin-types.d.mts +1 -1
  359. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  360. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  361. package/dist/plugins/adapt-sandbox-entry.mjs +26 -15
  362. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  363. package/dist/{preview-C1LOEbWZ.mjs → preview-D4z0WONU.mjs} +2 -2
  364. package/dist/{preview-C1LOEbWZ.mjs.map → preview-D4z0WONU.mjs.map} +1 -1
  365. package/dist/{public-url-CseXl9Fv.mjs → public-url-CUWWFME2.mjs} +1 -1
  366. package/dist/{public-url-CseXl9Fv.mjs.map → public-url-CUWWFME2.mjs.map} +1 -1
  367. package/dist/{query-axZmO6Tn.mjs → query-BJn8TOPk.mjs} +16 -13
  368. package/dist/{query-axZmO6Tn.mjs.map → query-BJn8TOPk.mjs.map} +1 -1
  369. package/dist/{rate-limit-t5CVjCO6.mjs → rate-limit-D_-gAeJ0.mjs} +2 -2
  370. package/dist/{rate-limit-t5CVjCO6.mjs.map → rate-limit-D_-gAeJ0.mjs.map} +1 -1
  371. package/dist/{redirect-DGRsLO2I.mjs → redirect-BINiRYq4.mjs} +1 -1
  372. package/dist/{redirect-DGRsLO2I.mjs.map → redirect-BINiRYq4.mjs.map} +1 -1
  373. package/dist/{redirect-DkaDxq8e.mjs → redirect-CNv4mHX2.mjs} +2 -2
  374. package/dist/{redirect-DkaDxq8e.mjs.map → redirect-CNv4mHX2.mjs.map} +1 -1
  375. package/dist/{redirects-D1fdd68T.mjs → redirects-B-CUZ1Xh.mjs} +3 -3
  376. package/dist/{redirects-D1fdd68T.mjs.map → redirects-B-CUZ1Xh.mjs.map} +1 -1
  377. package/dist/{redirects-Dmj6KRU3.mjs → redirects-COMLwsV5.mjs} +19 -5
  378. package/dist/redirects-COMLwsV5.mjs.map +1 -0
  379. package/dist/{registry-BnCeHYsf.mjs → registry-DqrAQDXH.mjs} +4 -4
  380. package/dist/{registry-BnCeHYsf.mjs.map → registry-DqrAQDXH.mjs.map} +1 -1
  381. package/dist/request-cache-dzCt8TZB.mjs.map +1 -1
  382. package/dist/request-context.mjs.map +1 -1
  383. package/dist/{request-meta-CLCwSQOS.mjs → request-meta-C_Cjii-T.mjs} +2 -2
  384. package/dist/{request-meta-CLCwSQOS.mjs.map → request-meta-C_Cjii-T.mjs.map} +1 -1
  385. package/dist/resolve-Cj98DuqN.mjs +39 -0
  386. package/dist/resolve-Cj98DuqN.mjs.map +1 -0
  387. package/dist/{runner-DdnQIwz_.mjs → runner-CGlojznK.mjs} +472 -165
  388. package/dist/runner-CGlojznK.mjs.map +1 -0
  389. package/dist/{runner-DcfZewkO.d.mts → runner-CNHRo1mT.d.mts} +2 -2
  390. package/dist/{runner-DcfZewkO.d.mts.map → runner-CNHRo1mT.d.mts.map} +1 -1
  391. package/dist/runtime.d.mts +10 -10
  392. package/dist/runtime.mjs +2 -2
  393. package/dist/{schema-BmqagCwG.mjs → schema-Djdlfi5G.mjs} +4 -4
  394. package/dist/{schema-BmqagCwG.mjs.map → schema-Djdlfi5G.mjs.map} +1 -1
  395. package/dist/{search-CPrvO5u8.mjs → search-By-NN3da.mjs} +4 -4
  396. package/dist/{search-CPrvO5u8.mjs.map → search-By-NN3da.mjs.map} +1 -1
  397. package/dist/{secrets-6pgZyq0K.mjs → secrets-rPdhEBkD.mjs} +1 -1
  398. package/dist/{secrets-6pgZyq0K.mjs.map → secrets-rPdhEBkD.mjs.map} +1 -1
  399. package/dist/{sections-Cm-zb-gZ.mjs → sections-DcBIlOq1.mjs} +3 -3
  400. package/dist/{sections-Cm-zb-gZ.mjs.map → sections-DcBIlOq1.mjs.map} +1 -1
  401. package/dist/seed/index.d.mts +2 -2
  402. package/dist/seed/index.mjs +16 -16
  403. package/dist/seo/index.d.mts +1 -1
  404. package/dist/{seo-DRq9-EPP.mjs → seo-bjDoq9Eg.mjs} +2 -2
  405. package/dist/{seo-DRq9-EPP.mjs.map → seo-bjDoq9Eg.mjs.map} +1 -1
  406. package/dist/{service-vByySp-2.mjs → service-BuuTdGAT.mjs} +3 -3
  407. package/dist/{service-vByySp-2.mjs.map → service-BuuTdGAT.mjs.map} +1 -1
  408. package/dist/{settings-CBBj7HUd.mjs → settings-CJnKiWuR.mjs} +3 -3
  409. package/dist/{settings-CBBj7HUd.mjs.map → settings-CJnKiWuR.mjs.map} +1 -1
  410. package/dist/{settings-xQKsWnzQ.mjs → settings-hcubRfkr.mjs} +3 -3
  411. package/dist/settings-hcubRfkr.mjs.map +1 -0
  412. package/dist/{setup-BGAJ2uXs.mjs → setup-Cf_TyOv5.mjs} +2 -2
  413. package/dist/{setup-BGAJ2uXs.mjs.map → setup-Cf_TyOv5.mjs.map} +1 -1
  414. package/dist/{setup-complete-C6ZCLhKo.mjs → setup-complete-MzzN9u0b.mjs} +1 -1
  415. package/dist/{setup-complete-C6ZCLhKo.mjs.map → setup-complete-MzzN9u0b.mjs.map} +1 -1
  416. package/dist/{setup-nonce-CY1gQiAU.mjs → setup-nonce-DXuriHsg.mjs} +1 -1
  417. package/dist/{setup-nonce-CY1gQiAU.mjs.map → setup-nonce-DXuriHsg.mjs.map} +1 -1
  418. package/dist/{site-url-D-M4Fd8O.mjs → site-url-xkhw1tcz.mjs} +1 -1
  419. package/dist/{site-url-D-M4Fd8O.mjs.map → site-url-xkhw1tcz.mjs.map} +1 -1
  420. package/dist/{ssrf-DzFN_qV-.mjs → ssrf-MZ-zrG6-.mjs} +1 -1
  421. package/dist/{ssrf-DzFN_qV-.mjs.map → ssrf-MZ-zrG6-.mjs.map} +1 -1
  422. package/dist/storage/local.d.mts +1 -1
  423. package/dist/storage/local.mjs +1 -1
  424. package/dist/storage/local.mjs.map +1 -1
  425. package/dist/storage/s3.d.mts +1 -1
  426. package/dist/storage/s3.mjs +1 -1
  427. package/dist/storage/s3.mjs.map +1 -1
  428. package/dist/{taxonomies-Dc0mzlms.mjs → taxonomies-CLs9HPE2.mjs} +4 -4
  429. package/dist/{taxonomies-Dc0mzlms.mjs.map → taxonomies-CLs9HPE2.mjs.map} +1 -1
  430. package/dist/{taxonomies-Cn9UpaR2.mjs → taxonomies-WamPVA2x.mjs} +7 -42
  431. package/dist/taxonomies-WamPVA2x.mjs.map +1 -0
  432. package/dist/{taxonomy-wPfusMK9.mjs → taxonomy-D4Uc2LsZ.mjs} +3 -3
  433. package/dist/{taxonomy-wPfusMK9.mjs.map → taxonomy-D4Uc2LsZ.mjs.map} +1 -1
  434. package/dist/{tokens-DILYNZMi.mjs → tokens-N8otWMmj.mjs} +1 -1
  435. package/dist/{tokens-DILYNZMi.mjs.map → tokens-N8otWMmj.mjs.map} +1 -1
  436. package/dist/{transport-fw-mKJzT.mjs → transport-B6CHddbu.mjs} +1 -1
  437. package/dist/{transport-fw-mKJzT.mjs.map → transport-B6CHddbu.mjs.map} +1 -1
  438. package/dist/{transport-GeXlLscf.d.mts → transport-DOxLfUir.d.mts} +1 -1
  439. package/dist/{transport-GeXlLscf.d.mts.map → transport-DOxLfUir.d.mts.map} +1 -1
  440. package/dist/{trusted-proxy-CJhQIk65.mjs → trusted-proxy-97pajC2f.mjs} +1 -1
  441. package/dist/{trusted-proxy-CJhQIk65.mjs.map → trusted-proxy-97pajC2f.mjs.map} +1 -1
  442. package/dist/{types-CwXMEPRr.mjs → types-ByV5sgsv.mjs} +2 -2
  443. package/dist/types-ByV5sgsv.mjs.map +1 -0
  444. package/dist/{types-Dz9CGX_d.mjs → types-Cd9UCu3t.mjs} +1 -1
  445. package/dist/{types-Dz9CGX_d.mjs.map → types-Cd9UCu3t.mjs.map} +1 -1
  446. package/dist/{types-DmxPPXGf.d.mts → types-CkDSF81F.d.mts} +1 -1
  447. package/dist/{types-DmxPPXGf.d.mts.map → types-CkDSF81F.d.mts.map} +1 -1
  448. package/dist/{types-BWhaSS7U.d.mts → types-CpUuGcd5.d.mts} +1 -1
  449. package/dist/{types-BWhaSS7U.d.mts.map → types-CpUuGcd5.d.mts.map} +1 -1
  450. package/dist/{types-DFowNO60.d.mts → types-D599-ruj.d.mts} +1 -1
  451. package/dist/{types-DFowNO60.d.mts.map → types-D599-ruj.d.mts.map} +1 -1
  452. package/dist/{types-B05e2naf.d.mts → types-DGHWRQgr.d.mts} +3 -3
  453. package/dist/{types-B05e2naf.d.mts.map → types-DGHWRQgr.d.mts.map} +1 -1
  454. package/dist/{types-CzvJd1ND.d.mts → types-DaYDYW6g.d.mts} +14 -1
  455. package/dist/types-DaYDYW6g.d.mts.map +1 -0
  456. package/dist/{types-C1KKK4VP.d.mts → types-DaqNzqVt.d.mts} +16 -1
  457. package/dist/{types-C1KKK4VP.d.mts.map → types-DaqNzqVt.d.mts.map} +1 -1
  458. package/dist/{types-DW1l0gCv.d.mts → types-Dgo6y-Ut.d.mts} +1 -1
  459. package/dist/{types-DW1l0gCv.d.mts.map → types-Dgo6y-Ut.d.mts.map} +1 -1
  460. package/dist/{types-Cb2UCDJg.d.mts → types-bYmRn_Uy.d.mts} +1 -1
  461. package/dist/{types-Cb2UCDJg.d.mts.map → types-bYmRn_Uy.d.mts.map} +1 -1
  462. package/dist/{user-Dr1bOCqS.mjs → user-D3BD5zdT.mjs} +2 -2
  463. package/dist/{user-Dr1bOCqS.mjs.map → user-D3BD5zdT.mjs.map} +1 -1
  464. package/dist/{utils-_F-rWBTN.mjs → utils-C3wTAP-P.mjs} +1 -1
  465. package/dist/{utils-_F-rWBTN.mjs.map → utils-C3wTAP-P.mjs.map} +1 -1
  466. package/dist/{validate-BpQGsmd7.d.mts → validate-DQtHw9NT.d.mts} +5 -5
  467. package/dist/{validate-BpQGsmd7.d.mts.map → validate-DQtHw9NT.d.mts.map} +1 -1
  468. package/dist/{validate-DlFxcVVK.mjs → validate-mz87i8_1.mjs} +2 -2
  469. package/dist/{validate-DlFxcVVK.mjs.map → validate-mz87i8_1.mjs.map} +1 -1
  470. package/dist/{validation-BiFJqUp5.mjs → validation-DKHhXjPr.mjs} +5 -5
  471. package/dist/{validation-BiFJqUp5.mjs.map → validation-DKHhXjPr.mjs.map} +1 -1
  472. package/dist/version-Ct7C6RSo.mjs +7 -0
  473. package/dist/{version-DNmQakZO.mjs.map → version-Ct7C6RSo.mjs.map} +1 -1
  474. package/dist/{widgets-B9j_yzlk.mjs → widgets-lShIQXU5.mjs} +3 -3
  475. package/dist/widgets-lShIQXU5.mjs.map +1 -0
  476. package/dist/{zod-generator-DSyz01KE.mjs → zod-generator-dvxgmd1M.mjs} +2 -2
  477. package/dist/{zod-generator-DSyz01KE.mjs.map → zod-generator-dvxgmd1M.mjs.map} +1 -1
  478. package/package.json +11 -9
  479. package/src/api/error.ts +18 -3
  480. package/src/api/errors.ts +6 -0
  481. package/src/api/handlers/bylines.ts +161 -0
  482. package/src/api/handlers/content.ts +125 -43
  483. package/src/api/handlers/index.ts +6 -0
  484. package/src/api/handlers/marketplace.ts +27 -5
  485. package/src/api/handlers/oauth-clients.ts +1 -1
  486. package/src/api/handlers/registry.ts +553 -4
  487. package/src/api/openapi/document.ts +1 -1
  488. package/src/api/schemas/bylines.ts +46 -0
  489. package/src/astro/integration/index.ts +1 -1
  490. package/src/astro/integration/routes.ts +5 -0
  491. package/src/astro/integration/runtime.ts +12 -1
  492. package/src/astro/integration/virtual-modules.ts +19 -2
  493. package/src/astro/integration/vite-config.ts +2 -2
  494. package/src/astro/middleware/auth.ts +7 -7
  495. package/src/astro/middleware/request-context.ts +1 -1
  496. package/src/astro/middleware.ts +31 -20
  497. package/src/astro/routes/api/admin/bylines/[id]/index.ts +3 -12
  498. package/src/astro/routes/api/admin/bylines/[id]/translations.ts +99 -0
  499. package/src/astro/routes/api/admin/bylines/index.ts +22 -11
  500. package/src/astro/routes/api/admin/plugins/[id]/update.ts +1 -0
  501. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +6 -1
  502. package/src/astro/routes/api/admin/plugins/registry/[id]/uninstall.ts +51 -0
  503. package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +79 -0
  504. package/src/astro/routes/api/admin/plugins/updates.ts +43 -6
  505. package/src/astro/routes/api/admin/themes/marketplace/index.ts +1 -1
  506. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +2 -2
  507. package/src/astro/routes/api/auth/oauth/[provider].ts +2 -2
  508. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +2 -2
  509. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +2 -2
  510. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +2 -2
  511. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +2 -2
  512. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +2 -2
  513. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +6 -6
  514. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +1 -1
  515. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +2 -2
  516. package/src/astro/routes/api/content/[collection]/[id].ts +6 -6
  517. package/src/astro/routes/api/import/wordpress/execute.ts +1 -1
  518. package/src/astro/routes/api/import/wordpress/prepare.ts +2 -2
  519. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +3 -3
  520. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +2 -2
  521. package/src/astro/routes/api/media/upload-url.ts +1 -1
  522. package/src/astro/routes/api/redirects/404s/index.ts +3 -3
  523. package/src/astro/routes/api/redirects/404s/summary.ts +1 -1
  524. package/src/astro/routes/api/redirects/[id].ts +3 -3
  525. package/src/astro/routes/api/redirects/index.ts +2 -2
  526. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +4 -4
  527. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +2 -6
  528. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +1 -1
  529. package/src/astro/routes/api/schema/collections/[slug]/index.ts +6 -6
  530. package/src/astro/routes/api/schema/collections/index.ts +4 -4
  531. package/src/astro/routes/api/schema/index.ts +1 -1
  532. package/src/astro/routes/api/schema/orphans/[slug].ts +1 -1
  533. package/src/astro/routes/api/schema/orphans/index.ts +1 -1
  534. package/src/astro/routes/api/sections/[slug].ts +3 -3
  535. package/src/astro/routes/api/sections/index.ts +2 -2
  536. package/src/astro/types.ts +4 -0
  537. package/src/auth/rate-limit.ts +1 -1
  538. package/src/auth/trusted-proxy.ts +1 -1
  539. package/src/bylines/index.ts +154 -55
  540. package/src/cli/commands/init.ts +4 -8
  541. package/src/client/index.ts +1 -1
  542. package/src/components/InlinePortableTextEditor.tsx +5 -1
  543. package/src/components/inline-code-block.tsx +343 -0
  544. package/src/config/secrets.ts +3 -3
  545. package/src/database/migrations/006_taxonomy_defs.ts +1 -1
  546. package/src/database/migrations/014_draft_revisions.ts +6 -6
  547. package/src/database/migrations/040_byline_i18n.ts +497 -0
  548. package/src/database/migrations/runner.ts +4 -1
  549. package/src/database/repositories/audit.ts +2 -2
  550. package/src/database/repositories/byline.ts +320 -50
  551. package/src/database/repositories/media.ts +2 -2
  552. package/src/database/repositories/menu.ts +1 -1
  553. package/src/database/repositories/options.ts +3 -3
  554. package/src/database/repositories/plugin-storage.ts +3 -3
  555. package/src/database/repositories/types.ts +13 -0
  556. package/src/database/types.ts +15 -0
  557. package/src/emdash-runtime.ts +492 -20
  558. package/src/i18n/config.ts +1 -1
  559. package/src/index.ts +7 -0
  560. package/src/loader.ts +1 -1
  561. package/src/mcp/server.ts +3 -3
  562. package/src/media/mime.ts +1 -1
  563. package/src/page/absolute-url.ts +1 -1
  564. package/src/plugins/adapt-sandbox-entry.ts +45 -40
  565. package/src/plugins/email-console.ts +1 -1
  566. package/src/plugins/index.ts +1 -0
  567. package/src/plugins/marketplace.ts +1 -1
  568. package/src/plugins/sandbox/index.ts +1 -0
  569. package/src/plugins/sandbox/noop.ts +11 -3
  570. package/src/plugins/sandbox/types.ts +28 -0
  571. package/src/query.ts +17 -2
  572. package/src/registry/config.ts +1 -1
  573. package/src/request-cache.ts +3 -3
  574. package/src/request-context.ts +1 -1
  575. package/src/settings/index.ts +4 -4
  576. package/src/storage/local.ts +1 -1
  577. package/src/storage/s3.ts +3 -3
  578. package/src/widgets/index.ts +1 -1
  579. package/dist/api-BMLZuwM4.mjs.map +0 -1
  580. package/dist/byline-D09BaS4j.mjs +0 -220
  581. package/dist/byline-D09BaS4j.mjs.map +0 -1
  582. package/dist/bylines-BTM2xtP8.mjs +0 -113
  583. package/dist/bylines-BTM2xtP8.mjs.map +0 -1
  584. package/dist/bylines-BdUP8NuI.d.mts.map +0 -1
  585. package/dist/context-qF8d3IPR.mjs.map +0 -1
  586. package/dist/email-console-Dmp5Q-P2.mjs.map +0 -1
  587. package/dist/error-tSQWIl5U.mjs.map +0 -1
  588. package/dist/index-BV8iJ-6s.d.mts.map +0 -1
  589. package/dist/loader-Cs6-Bqe6.mjs.map +0 -1
  590. package/dist/media-Dg7he9uK.mjs.map +0 -1
  591. package/dist/menus-DOzIecHi.mjs.map +0 -1
  592. package/dist/menus-X4Z-eBA1.mjs.map +0 -1
  593. package/dist/oauth-clients-D_B0_-Bz.mjs.map +0 -1
  594. package/dist/options-Cq64Wx0O.d.mts.map +0 -1
  595. package/dist/redirects-Dmj6KRU3.mjs.map +0 -1
  596. package/dist/runner-DdnQIwz_.mjs.map +0 -1
  597. package/dist/settings-xQKsWnzQ.mjs.map +0 -1
  598. package/dist/taxonomies-Cn9UpaR2.mjs.map +0 -1
  599. package/dist/types-CwXMEPRr.mjs.map +0 -1
  600. package/dist/types-CzvJd1ND.d.mts.map +0 -1
  601. package/dist/version-DNmQakZO.mjs +0 -7
  602. package/dist/widgets-B9j_yzlk.mjs.map +0 -1
  603. /package/dist/{api-tokens-D3C9v02m.mjs → api-tokens-iPIHAY8N.mjs} +0 -0
  604. /package/dist/{ssrf-CTul4uQi.mjs → ssrf-BIcd-aXW.mjs} +0 -0
  605. /package/dist/{types-Db67HHlU.mjs → types-1NNkmTIn.mjs} +0 -0
@@ -24,6 +24,18 @@ export interface CreateBylineInput {
24
24
  websiteUrl?: string | null;
25
25
  userId?: string | null;
26
26
  isGuest?: boolean;
27
+ /**
28
+ * Locale this byline row belongs to. When omitted, the DB DEFAULT (the
29
+ * configured `defaultLocale` after migration 040) is used. Keeps behaviour
30
+ * consistent with `TaxonomyRepository.create`.
31
+ */
32
+ locale?: string;
33
+ /**
34
+ * When set, the new row joins the source byline's translation_group rather
35
+ * than minting a fresh one. The source must exist; otherwise the create
36
+ * throws. Mirrors `TaxonomyRepository.create`.
37
+ */
38
+ translationOf?: string;
27
39
  }
28
40
 
29
41
  export interface UpdateBylineInput {
@@ -53,9 +65,29 @@ function rowToByline(row: BylineRow): BylineSummary {
53
65
  isGuest: row.is_guest === 1,
54
66
  createdAt: row.created_at,
55
67
  updatedAt: row.updated_at,
68
+ locale: row.locale,
69
+ translationGroup: row.translation_group,
56
70
  };
57
71
  }
58
72
 
73
+ /**
74
+ * Byline repository for content credits.
75
+ *
76
+ * Bylines are per-locale (migration 040). Translations of the same byline
77
+ * share a `translation_group` ULID. `_emdash_content_bylines.byline_id` and
78
+ * `ec_*.primary_byline_id` store the translation_group (not a row id) so a
79
+ * single credit spans every locale variant of a byline.
80
+ *
81
+ * The repository does not resolve locale fallbacks on its own — callers
82
+ * supply the locale they want. Hydration is strict per locale: a credit at
83
+ * locale X renders iff a byline row exists at locale X within the credited
84
+ * translation group. This mirrors `TaxonomyRepository.getTermsForEntry` and
85
+ * the convention established by PR #916.
86
+ *
87
+ * Runtime helpers in `packages/core/src/bylines/index.ts` may layer fallback
88
+ * resolution on top for the "look up one byline by slug" path, but the
89
+ * relation-hydration methods on this class are always strict.
90
+ */
59
91
  export class BylineRepository {
60
92
  constructor(private db: Kysely<Database>) {}
61
93
 
@@ -68,21 +100,28 @@ export class BylineRepository {
68
100
  return row ? rowToByline(row) : null;
69
101
  }
70
102
 
71
- async findBySlug(slug: string): Promise<BylineSummary | null> {
72
- const row = await this.db
73
- .selectFrom("_emdash_bylines")
74
- .selectAll()
75
- .where("slug", "=", slug)
76
- .executeTakeFirst();
103
+ /**
104
+ * Find a byline by slug. When `locale` is provided, filter by it strictly.
105
+ * When omitted, returns the lowest-locale-code match (deterministic across
106
+ * calls). Mirrors `TaxonomyRepository.findBySlug`.
107
+ */
108
+ async findBySlug(slug: string, options?: { locale?: string }): Promise<BylineSummary | null> {
109
+ let query = this.db.selectFrom("_emdash_bylines").selectAll().where("slug", "=", slug);
110
+ if (options?.locale !== undefined) query = query.where("locale", "=", options.locale);
111
+ const row = await query.orderBy("locale", "asc").executeTakeFirst();
77
112
  return row ? rowToByline(row) : null;
78
113
  }
79
114
 
80
- async findByUserId(userId: string): Promise<BylineSummary | null> {
81
- const row = await this.db
82
- .selectFrom("_emdash_bylines")
83
- .selectAll()
84
- .where("user_id", "=", userId)
85
- .executeTakeFirst();
115
+ /**
116
+ * Find the byline linked to a CMS user. Post-migration 040 the partial
117
+ * unique on user_id is `(user_id, locale)`, so `locale` is required to
118
+ * disambiguate when multiple locale variants exist. When omitted, returns
119
+ * the lowest-locale-code match.
120
+ */
121
+ async findByUserId(userId: string, options?: { locale?: string }): Promise<BylineSummary | null> {
122
+ let query = this.db.selectFrom("_emdash_bylines").selectAll().where("user_id", "=", userId);
123
+ if (options?.locale !== undefined) query = query.where("locale", "=", options.locale);
124
+ const row = await query.orderBy("locale", "asc").executeTakeFirst();
86
125
  return row ? rowToByline(row) : null;
87
126
  }
88
127
 
@@ -90,6 +129,7 @@ export class BylineRepository {
90
129
  search?: string;
91
130
  isGuest?: boolean;
92
131
  userId?: string;
132
+ locale?: string;
93
133
  cursor?: string;
94
134
  limit?: number;
95
135
  }): Promise<FindManyResult<BylineSummary>> {
@@ -121,6 +161,10 @@ export class BylineRepository {
121
161
  query = query.where("user_id", "=", options.userId);
122
162
  }
123
163
 
164
+ if (options?.locale !== undefined) {
165
+ query = query.where("locale", "=", options.locale);
166
+ }
167
+
124
168
  if (options?.cursor) {
125
169
  const decoded = decodeCursor(options.cursor);
126
170
  query = query.where((eb) =>
@@ -145,10 +189,44 @@ export class BylineRepository {
145
189
  return result;
146
190
  }
147
191
 
192
+ /**
193
+ * List every sibling row in `translation_group`. Used by the admin
194
+ * `TranslationsPanel` to render one entry per configured locale.
195
+ */
196
+ async listTranslations(id: string): Promise<BylineSummary[]> {
197
+ const anchor = await this.findById(id);
198
+ if (!anchor) return [];
199
+ const group = anchor.translationGroup ?? anchor.id;
200
+ return this.findByTranslationGroup(group);
201
+ }
202
+
203
+ /**
204
+ * Direct lookup by `translation_group`. Returns every locale variant of a
205
+ * byline, ordered by locale code (deterministic).
206
+ */
207
+ async findByTranslationGroup(translationGroup: string): Promise<BylineSummary[]> {
208
+ const rows = await this.db
209
+ .selectFrom("_emdash_bylines")
210
+ .selectAll()
211
+ .where("translation_group", "=", translationGroup)
212
+ .orderBy("locale", "asc")
213
+ .execute();
214
+ return rows.map(rowToByline);
215
+ }
216
+
148
217
  async create(input: CreateBylineInput): Promise<BylineSummary> {
149
218
  const id = ulid();
150
219
  const now = new Date().toISOString();
151
220
 
221
+ // translationOf joins the source byline's group; otherwise we mint a
222
+ // fresh group equal to id (matching migration 040's backfill pattern).
223
+ let translationGroup: string = id;
224
+ if (input.translationOf) {
225
+ const source = await this.findById(input.translationOf);
226
+ if (!source) throw new Error("Source byline for translation not found");
227
+ translationGroup = source.translationGroup ?? source.id;
228
+ }
229
+
152
230
  await this.db
153
231
  .insertInto("_emdash_bylines")
154
232
  .values({
@@ -162,6 +240,10 @@ export class BylineRepository {
162
240
  is_guest: input.isGuest ? 1 : 0,
163
241
  created_at: now,
164
242
  updated_at: now,
243
+ // When omitted the DB DEFAULT (configured defaultLocale) is used —
244
+ // keeps behaviour consistent with TaxonomyRepository.create.
245
+ ...(input.locale !== undefined ? { locale: input.locale } : {}),
246
+ translation_group: translationGroup,
165
247
  })
166
248
  .execute();
167
249
 
@@ -192,22 +274,46 @@ export class BylineRepository {
192
274
  return await this.findById(id);
193
275
  }
194
276
 
277
+ /**
278
+ * Delete a byline row. When this row is the last sibling in its
279
+ * translation group, also drops every junction row pointing at the group
280
+ * and clears `primary_byline_id` references. When other siblings remain
281
+ * in the group, junctions and `primary_byline_id` pointers stay intact —
282
+ * the credit lives on at other locales.
283
+ *
284
+ * Migration 040 dropped the FK on `_emdash_content_bylines.byline_id`, so
285
+ * this cascade is implemented here in application code.
286
+ */
195
287
  async delete(id: string): Promise<boolean> {
196
288
  const existing = await this.findById(id);
197
289
  if (!existing) return false;
198
290
 
199
- await withTransaction(this.db, async (trx) => {
200
- await trx.deleteFrom("_emdash_content_bylines").where("byline_id", "=", id).execute();
291
+ const group = existing.translationGroup ?? existing.id;
201
292
 
293
+ await withTransaction(this.db, async (trx) => {
202
294
  await trx.deleteFrom("_emdash_bylines").where("id", "=", id).execute();
203
295
 
296
+ // Count remaining siblings in the translation group. If none
297
+ // remain, purge dependent rows; otherwise leave them intact so
298
+ // the credit still resolves at other locales.
299
+ const remaining = await trx
300
+ .selectFrom("_emdash_bylines")
301
+ .select(({ fn }) => [fn.count<number>("id").as("count")])
302
+ .where("translation_group", "=", group)
303
+ .executeTakeFirst();
304
+ const remainingCount = Number(remaining?.count ?? 0);
305
+ if (remainingCount > 0) return;
306
+
307
+ // Last sibling gone: cascade in application code.
308
+ await trx.deleteFrom("_emdash_content_bylines").where("byline_id", "=", group).execute();
309
+
204
310
  const tableNames = await listTablesLike(trx, "ec_%");
205
311
  for (const tableName of tableNames) {
206
312
  validateIdentifier(tableName, "content table");
207
313
  await sql`
208
314
  UPDATE ${sql.ref(tableName)}
209
315
  SET primary_byline_id = NULL
210
- WHERE primary_byline_id = ${id}
316
+ WHERE primary_byline_id = ${group}
211
317
  `.execute(trx);
212
318
  }
213
319
  });
@@ -215,13 +321,21 @@ export class BylineRepository {
215
321
  return true;
216
322
  }
217
323
 
324
+ /**
325
+ * Strict per-locale credit hydration. Joins `_emdash_content_bylines` to
326
+ * `_emdash_bylines` on `translation_group = byline_id`, then filters to
327
+ * the requested locale. Credits whose translation group lacks a row at
328
+ * the requested locale are omitted — callers wanting fallback behaviour
329
+ * apply it themselves. Mirrors `TaxonomyRepository.getTermsForEntry`.
330
+ */
218
331
  async getContentBylines(
219
332
  collectionSlug: string,
220
333
  contentId: string,
334
+ options?: { locale?: string },
221
335
  ): Promise<ContentBylineCredit[]> {
222
- const rows = await this.db
336
+ let query = this.db
223
337
  .selectFrom("_emdash_content_bylines as cb")
224
- .innerJoin("_emdash_bylines as b", "b.id", "cb.byline_id")
338
+ .innerJoin("_emdash_bylines as b", "b.translation_group", "cb.byline_id")
225
339
  .select([
226
340
  "cb.sort_order as sort_order",
227
341
  "cb.role_label as role_label",
@@ -235,12 +349,15 @@ export class BylineRepository {
235
349
  "b.is_guest as is_guest",
236
350
  "b.created_at as created_at",
237
351
  "b.updated_at as updated_at",
352
+ "b.locale as locale",
353
+ "b.translation_group as translation_group",
238
354
  ])
239
355
  .where("cb.collection_slug", "=", collectionSlug)
240
356
  .where("cb.content_id", "=", contentId)
241
- .orderBy("cb.sort_order", "asc")
242
- .execute();
357
+ .orderBy("cb.sort_order", "asc");
358
+ if (options?.locale !== undefined) query = query.where("b.locale", "=", options.locale);
243
359
 
360
+ const rows = await query.execute();
244
361
  return rows.map((row) => ({
245
362
  byline: rowToByline(row),
246
363
  sortOrder: row.sort_order,
@@ -249,21 +366,68 @@ export class BylineRepository {
249
366
  }
250
367
 
251
368
  /**
252
- * Batch-fetch byline credits for multiple content items in a single query.
253
- * Returns a Map keyed by contentId.
369
+ * Does this entry have any explicit byline credits at any locale?
370
+ *
371
+ * Used to disambiguate "no credits exist" (fall back to author-linked
372
+ * byline) from "credits exist but don't resolve at the requested locale"
373
+ * (strict per-locale model: render no byline). Without this check the
374
+ * locale-strict hydration would silently turn a missing translation into
375
+ * an author-inferred byline, contradicting editorial intent.
376
+ */
377
+ async hasContentBylines(collectionSlug: string, contentId: string): Promise<boolean> {
378
+ const row = await this.db
379
+ .selectFrom("_emdash_content_bylines")
380
+ .select("id")
381
+ .where("collection_slug", "=", collectionSlug)
382
+ .where("content_id", "=", contentId)
383
+ .limit(1)
384
+ .executeTakeFirst();
385
+ return row !== undefined;
386
+ }
387
+
388
+ /**
389
+ * Batch variant of `hasContentBylines`. Returns the set of content IDs
390
+ * that have at least one junction row (locale-agnostic).
391
+ */
392
+ async hasContentBylinesMany(collectionSlug: string, contentIds: string[]): Promise<Set<string>> {
393
+ const result = new Set<string>();
394
+ if (contentIds.length === 0) return result;
395
+
396
+ const uniqueContentIds = [...new Set(contentIds)];
397
+ for (const chunk of chunks(uniqueContentIds, SQL_BATCH_SIZE)) {
398
+ const rows = await this.db
399
+ .selectFrom("_emdash_content_bylines")
400
+ .select("content_id")
401
+ .distinct()
402
+ .where("collection_slug", "=", collectionSlug)
403
+ .where("content_id", "in", chunk)
404
+ .execute();
405
+ for (const row of rows) result.add(row.content_id);
406
+ }
407
+ return result;
408
+ }
409
+
410
+ /**
411
+ * Batch variant of `getContentBylines`. Same strict-per-locale semantics
412
+ * applied to the requested locale (single value, not per-entry).
413
+ *
414
+ * When callers need per-entry-locale filtering (e.g. a list endpoint
415
+ * returning entries at mixed locales), they should group the input ids by
416
+ * the entry's locale and call this method once per group.
254
417
  */
255
418
  async getContentBylinesMany(
256
419
  collectionSlug: string,
257
420
  contentIds: string[],
421
+ options?: { locale?: string },
258
422
  ): Promise<Map<string, ContentBylineCredit[]>> {
259
423
  const result = new Map<string, ContentBylineCredit[]>();
260
424
  if (contentIds.length === 0) return result;
261
425
 
262
426
  const uniqueContentIds = [...new Set(contentIds)];
263
427
  for (const chunk of chunks(uniqueContentIds, SQL_BATCH_SIZE)) {
264
- const rows = await this.db
428
+ let query = this.db
265
429
  .selectFrom("_emdash_content_bylines as cb")
266
- .innerJoin("_emdash_bylines as b", "b.id", "cb.byline_id")
430
+ .innerJoin("_emdash_bylines as b", "b.translation_group", "cb.byline_id")
267
431
  .select([
268
432
  "cb.content_id as content_id",
269
433
  "cb.sort_order as sort_order",
@@ -278,11 +442,15 @@ export class BylineRepository {
278
442
  "b.is_guest as is_guest",
279
443
  "b.created_at as created_at",
280
444
  "b.updated_at as updated_at",
445
+ "b.locale as locale",
446
+ "b.translation_group as translation_group",
281
447
  ])
282
448
  .where("cb.collection_slug", "=", collectionSlug)
283
449
  .where("cb.content_id", "in", chunk)
284
- .orderBy("cb.sort_order", "asc")
285
- .execute();
450
+ .orderBy("cb.sort_order", "asc");
451
+ if (options?.locale !== undefined) query = query.where("b.locale", "=", options.locale);
452
+
453
+ const rows = await query.execute();
286
454
 
287
455
  for (const row of rows) {
288
456
  const contentId = row.content_id;
@@ -305,18 +473,20 @@ export class BylineRepository {
305
473
 
306
474
  /**
307
475
  * Batch-fetch byline profiles linked to user IDs in a single query.
308
- * Returns a Map keyed by userId.
476
+ * Strict-locale variant of `findByUserId`.
309
477
  */
310
- async findByUserIds(userIds: string[]): Promise<Map<string, BylineSummary>> {
478
+ async findByUserIds(
479
+ userIds: string[],
480
+ options?: { locale?: string },
481
+ ): Promise<Map<string, BylineSummary>> {
311
482
  const result = new Map<string, BylineSummary>();
312
483
  if (userIds.length === 0) return result;
313
484
 
314
485
  for (const chunk of chunks(userIds, SQL_BATCH_SIZE)) {
315
- const rows = await this.db
316
- .selectFrom("_emdash_bylines")
317
- .selectAll()
318
- .where("user_id", "in", chunk)
319
- .execute();
486
+ let query = this.db.selectFrom("_emdash_bylines").selectAll().where("user_id", "in", chunk);
487
+ if (options?.locale !== undefined) query = query.where("locale", "=", options.locale);
488
+
489
+ const rows = await query.execute();
320
490
 
321
491
  for (const row of rows) {
322
492
  if (row.user_id) {
@@ -327,6 +497,85 @@ export class BylineRepository {
327
497
  return result;
328
498
  }
329
499
 
500
+ /**
501
+ * Clone every junction row from `sourceContentId` to `targetContentId`,
502
+ * preserving `sort_order` and `role_label`. Used by the content
503
+ * translation flow: a newly created translation inherits the source's
504
+ * byline credits at the storage level. Because the junction stores
505
+ * `translation_group` (not a row id), the copy is locale-agnostic — the
506
+ * credits resolve to whichever locale variants of each byline exist when
507
+ * the translated entry is hydrated.
508
+ *
509
+ * No-op when the source has no credits. Skips when the target already
510
+ * has credits (idempotent for re-runs).
511
+ */
512
+ async copyContentBylines(
513
+ collection: string,
514
+ sourceContentId: string,
515
+ targetContentId: string,
516
+ ): Promise<void> {
517
+ validateIdentifier(collection, "collection slug");
518
+ const tableName = `ec_${collection}`;
519
+ validateIdentifier(tableName, "content table");
520
+
521
+ // Like `setContentBylines`, this method is expected to be called
522
+ // within a transaction context (content handlers wrap in
523
+ // withTransaction). All operations use `this.db` directly so an
524
+ // outer transaction can serialise the copy alongside the create.
525
+ const existing = await this.db
526
+ .selectFrom("_emdash_content_bylines")
527
+ .select("id")
528
+ .where("collection_slug", "=", collection)
529
+ .where("content_id", "=", targetContentId)
530
+ .executeTakeFirst();
531
+ if (existing) return;
532
+
533
+ const sourceRows = await this.db
534
+ .selectFrom("_emdash_content_bylines")
535
+ .select(["byline_id", "sort_order", "role_label"])
536
+ .where("collection_slug", "=", collection)
537
+ .where("content_id", "=", sourceContentId)
538
+ .orderBy("sort_order", "asc")
539
+ .execute();
540
+ if (sourceRows.length === 0) return;
541
+
542
+ const now = new Date().toISOString();
543
+ await this.db
544
+ .insertInto("_emdash_content_bylines")
545
+ .values(
546
+ sourceRows.map((row) => ({
547
+ id: ulid(),
548
+ collection_slug: collection,
549
+ content_id: targetContentId,
550
+ byline_id: row.byline_id,
551
+ sort_order: row.sort_order,
552
+ role_label: row.role_label,
553
+ created_at: now,
554
+ })),
555
+ )
556
+ .execute();
557
+
558
+ // Mirror primary_byline_id from source so the cached pointer on the
559
+ // target row matches the junction state we just wrote.
560
+ const firstByline = sourceRows[0]?.byline_id ?? null;
561
+ await sql`
562
+ UPDATE ${sql.ref(tableName)}
563
+ SET primary_byline_id = ${firstByline}
564
+ WHERE id = ${targetContentId}
565
+ `.execute(this.db);
566
+ }
567
+
568
+ /**
569
+ * Replace the set of byline credits on a content entry. Accepts row ids
570
+ * at the wire (consistent with how the admin sends them), translates
571
+ * each to its `translation_group` on write, and stores the group in
572
+ * `_emdash_content_bylines.byline_id` and `ec_*.primary_byline_id`.
573
+ *
574
+ * The returned credits are hydrated with strict-locale matching at the
575
+ * locale of the rows the caller supplied (i.e. the locale of the byline
576
+ * each `bylineId` resolves to) — adequate for the autosave round-trip,
577
+ * which then re-hydrates the entry against its own locale separately.
578
+ */
330
579
  async setContentBylines(
331
580
  collectionSlug: string,
332
581
  contentId: string,
@@ -336,29 +585,48 @@ export class BylineRepository {
336
585
  const tableName = `ec_${collectionSlug}`;
337
586
  validateIdentifier(tableName, "content table");
338
587
 
339
- const seen = new Set<string>();
340
- const bylines = inputBylines.filter((item) => {
341
- if (seen.has(item.bylineId)) return false;
342
- seen.add(item.bylineId);
343
- return true;
344
- });
345
-
346
- // This method is expected to be called within a transaction context
347
- // (content handlers wrap in withTransaction, seed applies sequentially).
348
- // All operations use this.db directly -- callers are responsible for
349
- // wrapping in a transaction when atomicity is required.
350
- if (bylines.length > 0) {
351
- const ids = bylines.map((item) => item.bylineId);
588
+ // Resolve each wire row id to its translation_group up front so we
589
+ // can (a) validate the rows exist and (b) dedupe by the value that
590
+ // actually lands in the junction. Deduping by wire row id BEFORE
591
+ // resolving would let two locale siblings of the same byline slip
592
+ // through and trigger a UNIQUE(collection, content, byline_id)
593
+ // failure at insert time. A single SELECT keeps this O(1) DB
594
+ // calls regardless of how many credits are being set.
595
+ const idToGroup = new Map<string, string>();
596
+ if (inputBylines.length > 0) {
597
+ const wireIds = [...new Set(inputBylines.map((item) => item.bylineId))];
352
598
  const rows = await this.db
353
599
  .selectFrom("_emdash_bylines")
354
- .select("id")
355
- .where("id", "in", ids)
600
+ .select(["id", "translation_group"])
601
+ .where("id", "in", wireIds)
356
602
  .execute();
357
- if (rows.length !== ids.length) {
603
+ if (rows.length !== wireIds.length) {
358
604
  throw new Error("One or more byline IDs do not exist");
359
605
  }
606
+ for (const row of rows) {
607
+ idToGroup.set(row.id, row.translation_group ?? row.id);
608
+ }
360
609
  }
361
610
 
611
+ // Dedupe by translation_group. Preserves the order of first
612
+ // occurrence so the editor's intent (which sibling appears first)
613
+ // is honored. `roleLabel` follows the first occurrence too.
614
+ const seenGroups = new Set<string>();
615
+ const bylines: Array<ContentBylineInput & { group: string }> = [];
616
+ for (const item of inputBylines) {
617
+ const group = idToGroup.get(item.bylineId);
618
+ if (!group) {
619
+ throw new Error(`Missing translation_group for byline ${item.bylineId}`);
620
+ }
621
+ if (seenGroups.has(group)) continue;
622
+ seenGroups.add(group);
623
+ bylines.push({ ...item, group });
624
+ }
625
+
626
+ // This method is expected to be called within a transaction context
627
+ // (content handlers wrap in withTransaction, seed applies sequentially).
628
+ // All operations use this.db directly -- callers are responsible for
629
+ // wrapping in a transaction when atomicity is required.
362
630
  await this.db
363
631
  .deleteFrom("_emdash_content_bylines")
364
632
  .where("collection_slug", "=", collectionSlug)
@@ -367,13 +635,14 @@ export class BylineRepository {
367
635
 
368
636
  for (let i = 0; i < bylines.length; i++) {
369
637
  const item = bylines[i];
638
+ if (!item) continue;
370
639
  await this.db
371
640
  .insertInto("_emdash_content_bylines")
372
641
  .values({
373
642
  id: ulid(),
374
643
  collection_slug: collectionSlug,
375
644
  content_id: contentId,
376
- byline_id: item.bylineId,
645
+ byline_id: item.group,
377
646
  sort_order: i,
378
647
  role_label: item.roleLabel ?? null,
379
648
  created_at: new Date().toISOString(),
@@ -381,9 +650,10 @@ export class BylineRepository {
381
650
  .execute();
382
651
  }
383
652
 
653
+ const primaryGroup = bylines[0]?.group ?? null;
384
654
  await sql`
385
655
  UPDATE ${sql.ref(tableName)}
386
- SET primary_byline_id = ${bylines[0]?.bylineId ?? null}
656
+ SET primary_byline_id = ${primaryGroup}
387
657
  WHERE id = ${contentId}
388
658
  `.execute(this.db);
389
659
 
@@ -20,7 +20,7 @@ function normalizeMimeFilter(input?: string | readonly string[]): string[] {
20
20
  return arr
21
21
  .filter((entry): entry is string => typeof entry === "string" && entry.length > 0)
22
22
  .map((entry) =>
23
- entry.endsWith("/") ? entry.toLowerCase() : entry.split(";")[0]!.trim().toLowerCase(),
23
+ entry.endsWith("/") ? entry.toLowerCase() : entry.split(";")[0].trim().toLowerCase(),
24
24
  );
25
25
  }
26
26
 
@@ -365,7 +365,7 @@ export class MediaRepository {
365
365
  contentHash: row.content_hash,
366
366
  blurhash: row.blurhash,
367
367
  dominantColor: row.dominant_color,
368
- // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- DB stores string; validated at insert but linter can't follow
368
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- DB stores string; validated at insert but linter can't follow
369
369
  status: row.status as MediaStatus,
370
370
  createdAt: row.created_at,
371
371
  authorId: row.author_id,
@@ -463,7 +463,7 @@ export class MenuRepository {
463
463
  .where("menu_id", "=", menuId)
464
464
  .where("parent_id", "is", input.parentId ?? null)
465
465
  .executeTakeFirst();
466
- // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Kysely fn.max returns unknown; always a number for sort_order column
466
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- Kysely fn.max returns unknown; always a number for sort_order column
467
467
  sortOrder = ((maxOrder?.max as number) ?? -1) + 1;
468
468
  }
469
469
 
@@ -26,7 +26,7 @@ export class OptionsRepository {
26
26
  .executeTakeFirst();
27
27
 
28
28
  if (!row) return null;
29
- // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- JSON.parse returns any; generic callers provide T
29
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T
30
30
  return JSON.parse(row.value) as T;
31
31
  }
32
32
 
@@ -116,7 +116,7 @@ export class OptionsRepository {
116
116
 
117
117
  const result = new Map<string, T>();
118
118
  for (const row of rows) {
119
- // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- JSON.parse returns any; generic callers provide T
119
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T
120
120
  result.set(row.name, JSON.parse(row.value) as T);
121
121
  }
122
122
  return result;
@@ -160,7 +160,7 @@ export class OptionsRepository {
160
160
 
161
161
  const result = new Map<string, T>();
162
162
  for (const row of rows) {
163
- // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- JSON.parse returns any; generic callers provide T
163
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T
164
164
  result.set(row.name, JSON.parse(row.value) as T);
165
165
  }
166
166
  return result;
@@ -57,7 +57,7 @@ export class PluginStorageRepository<T = unknown> implements StorageCollection<T
57
57
  .executeTakeFirst();
58
58
 
59
59
  if (!row) return null;
60
- // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- JSON.parse returns any; generic callers provide T
60
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T
61
61
  return JSON.parse(row.data) as T;
62
62
  }
63
63
 
@@ -132,7 +132,7 @@ export class PluginStorageRepository<T = unknown> implements StorageCollection<T
132
132
 
133
133
  const result = new Map<string, T>();
134
134
  for (const row of rows) {
135
- // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- JSON.parse returns any; generic callers provide T
135
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T
136
136
  result.set(row.id, JSON.parse(row.data) as T);
137
137
  }
138
138
  return result;
@@ -255,7 +255,7 @@ export class PluginStorageRepository<T = unknown> implements StorageCollection<T
255
255
  const hasMore = rows.length > limit;
256
256
  const items = rows.slice(0, limit).map((row) => ({
257
257
  id: row.id,
258
- // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- JSON.parse returns any; generic callers provide T
258
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T
259
259
  data: JSON.parse(row.data) as T,
260
260
  }));
261
261
 
@@ -63,6 +63,19 @@ export interface BylineSummary {
63
63
  isGuest: boolean;
64
64
  createdAt: string;
65
65
  updatedAt: string;
66
+ /**
67
+ * Locale this byline row is presented in. Added by migration 040.
68
+ * `(slug, locale)` is unique; a single slug can repeat across locales.
69
+ */
70
+ locale: string;
71
+ /**
72
+ * Shared across translations of the same byline. Added by migration 040.
73
+ * `_emdash_content_bylines.byline_id` and `ec_*.primary_byline_id` store
74
+ * this value, so a credit spans every locale variant of a byline.
75
+ * Nullable in storage for backwards compatibility; new rows always
76
+ * populate it.
77
+ */
78
+ translationGroup: string | null;
66
79
  }
67
80
 
68
81
  export interface ContentBylineCredit {
@@ -502,6 +502,21 @@ export interface BylineTable {
502
502
  is_guest: number;
503
503
  created_at: Generated<string>;
504
504
  updated_at: Generated<string>;
505
+ /**
506
+ * Locale this byline row is presented in. Added by migration 040. Backfilled
507
+ * to the configured `defaultLocale` for pre-040 rows. `(slug, locale)` is
508
+ * unique; the partial unique on `user_id` widens to `(user_id, locale)`.
509
+ */
510
+ locale: Generated<string>;
511
+ /**
512
+ * Shared across translations of the same byline. Added by migration 040.
513
+ * Equals `id` for the anchor row; siblings inherit it from their source.
514
+ * `_emdash_content_bylines.byline_id` and `ec_*.primary_byline_id` store
515
+ * this value rather than a row id, so credits span every locale variant of
516
+ * a byline. Nullable in the schema for backwards compatibility; new rows
517
+ * always populate it.
518
+ */
519
+ translation_group: string | null;
505
520
  }
506
521
 
507
522
  export interface ContentBylineTable {