emdash 0.16.1 → 0.17.1

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 (566) hide show
  1. package/dist/{adapters-C4yd_UJR.d.mts → adapters-C5AWLJSD.d.mts} +1 -1
  2. package/dist/{adapters-C4yd_UJR.d.mts.map → adapters-C5AWLJSD.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-D0fFk9a6.mjs → allowed-origins-CyYLEJkp.mjs} +2 -2
  4. package/dist/{allowed-origins-D0fFk9a6.mjs.map → allowed-origins-CyYLEJkp.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +16 -16
  7. package/dist/api/schemas/index.d.mts +2 -2
  8. package/dist/api/schemas/index.mjs +3 -3
  9. package/dist/{api-BNKqxyFX.mjs → api-Dmz40c2V.mjs} +44 -22
  10. package/dist/api-Dmz40c2V.mjs.map +1 -0
  11. package/dist/{api-tokens-ucpcNXDt.mjs → api-tokens-VrXNiNvV.mjs} +2 -2
  12. package/dist/{api-tokens-ucpcNXDt.mjs.map → api-tokens-VrXNiNvV.mjs.map} +1 -1
  13. package/dist/{apply-BOPaD-s9.mjs → apply-CuuZG6op.mjs} +93 -31
  14. package/dist/apply-CuuZG6op.mjs.map +1 -0
  15. package/dist/astro/index.d.mts +10 -10
  16. package/dist/astro/index.mjs +28 -3
  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/redirect.d.mts.map +1 -1
  21. package/dist/astro/middleware/redirect.mjs +9 -5
  22. package/dist/astro/middleware/redirect.mjs.map +1 -1
  23. package/dist/astro/middleware/request-context.mjs +2 -2
  24. package/dist/astro/middleware/setup.mjs +1 -1
  25. package/dist/astro/middleware.mjs +66 -65
  26. package/dist/astro/middleware.mjs.map +1 -1
  27. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
  28. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
  29. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
  30. package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
  31. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.d.mts +8 -0
  32. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.d.mts.map +1 -0
  33. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +23 -0
  34. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs.map +1 -0
  35. package/dist/astro/routes/api/admin/byline-fields/_slug_.d.mts +10 -0
  36. package/dist/astro/routes/api/admin/byline-fields/_slug_.d.mts.map +1 -0
  37. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +55 -0
  38. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -0
  39. package/dist/astro/routes/api/admin/byline-fields/index.d.mts +9 -0
  40. package/dist/astro/routes/api/admin/byline-fields/index.d.mts.map +1 -0
  41. package/dist/astro/routes/api/admin/byline-fields/index.mjs +43 -0
  42. package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -0
  43. package/dist/astro/routes/api/admin/byline-fields/reorder.d.mts +8 -0
  44. package/dist/astro/routes/api/admin/byline-fields/reorder.d.mts.map +1 -0
  45. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +27 -0
  46. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -0
  47. package/dist/astro/routes/api/admin/bylines/_id_/index.d.mts.map +1 -1
  48. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +27 -28
  49. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
  50. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +13 -12
  51. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
  52. package/dist/astro/routes/api/admin/bylines/index.mjs +15 -13
  53. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  54. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +10 -10
  55. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  56. package/dist/astro/routes/api/admin/comments/bulk.mjs +8 -8
  57. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  58. package/dist/astro/routes/api/admin/comments/index.mjs +8 -8
  59. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  60. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  61. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
  62. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
  63. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +35 -34
  64. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  65. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +35 -34
  66. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  67. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +34 -33
  68. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  69. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +34 -33
  70. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  71. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +34 -33
  72. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  73. package/dist/astro/routes/api/admin/plugins/index.mjs +34 -33
  74. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  75. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  76. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +34 -33
  77. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  78. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +34 -33
  79. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  80. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +34 -33
  81. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  82. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +34 -33
  83. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  84. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +35 -34
  85. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  86. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +34 -33
  87. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  88. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +35 -34
  89. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  90. package/dist/astro/routes/api/admin/plugins/updates.mjs +34 -33
  91. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  92. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +34 -33
  93. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  94. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  95. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +34 -33
  96. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  97. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  98. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  99. package/dist/astro/routes/api/admin/users/_id_/index.mjs +5 -5
  100. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
  101. package/dist/astro/routes/api/admin/users/index.mjs +5 -5
  102. package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -5
  103. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  104. package/dist/astro/routes/api/auth/invite/complete.mjs +9 -9
  105. package/dist/astro/routes/api/auth/invite/index.mjs +6 -6
  106. package/dist/astro/routes/api/auth/invite/register-options.mjs +8 -8
  107. package/dist/astro/routes/api/auth/logout.mjs +3 -3
  108. package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
  109. package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
  110. package/dist/astro/routes/api/auth/me.d.mts.map +1 -1
  111. package/dist/astro/routes/api/auth/me.mjs +18 -11
  112. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  113. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  114. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
  115. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  116. package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
  117. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  118. package/dist/astro/routes/api/auth/passkey/options.mjs +10 -10
  119. package/dist/astro/routes/api/auth/passkey/register/options.mjs +8 -8
  120. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +9 -9
  121. package/dist/astro/routes/api/auth/passkey/verify.mjs +9 -9
  122. package/dist/astro/routes/api/auth/signup/complete.mjs +9 -9
  123. package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
  124. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  125. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -11
  126. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  127. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  128. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  129. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  130. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +9 -9
  131. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +6 -6
  132. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  133. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  134. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +6 -6
  135. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts.map +1 -1
  136. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +18 -13
  137. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  138. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  139. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  140. package/dist/astro/routes/api/content/_collection_/_id_.d.mts.map +1 -1
  141. package/dist/astro/routes/api/content/_collection_/_id_.mjs +9 -7
  142. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  143. package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
  144. package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
  145. package/dist/astro/routes/api/dashboard.mjs +7 -7
  146. package/dist/astro/routes/api/dev/emails.mjs +3 -3
  147. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  148. package/dist/astro/routes/api/import/probe.mjs +10 -10
  149. package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
  150. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  151. package/dist/astro/routes/api/import/wordpress/execute.mjs +11 -10
  152. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  153. package/dist/astro/routes/api/import/wordpress/media.mjs +8 -8
  154. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  155. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
  156. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  157. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +10 -10
  158. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  159. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +13 -11
  160. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  161. package/dist/astro/routes/api/manifest.mjs +4 -4
  162. package/dist/astro/routes/api/mcp.mjs +34 -30
  163. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  164. package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
  165. package/dist/astro/routes/api/media/_id_.mjs +6 -6
  166. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  167. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  168. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  169. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  170. package/dist/astro/routes/api/media/upload-url.mjs +8 -8
  171. package/dist/astro/routes/api/media.d.mts.map +1 -1
  172. package/dist/astro/routes/api/media.mjs +13 -12
  173. package/dist/astro/routes/api/media.mjs.map +1 -1
  174. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +7 -7
  175. package/dist/astro/routes/api/menus/_name_/items.mjs +7 -7
  176. package/dist/astro/routes/api/menus/_name_/reorder.mjs +7 -7
  177. package/dist/astro/routes/api/menus/_name_/translations.mjs +7 -7
  178. package/dist/astro/routes/api/menus/_name_.mjs +7 -7
  179. package/dist/astro/routes/api/menus/index.mjs +7 -7
  180. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  181. package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
  182. package/dist/astro/routes/api/oauth/device/code.mjs +9 -9
  183. package/dist/astro/routes/api/oauth/device/token.mjs +8 -8
  184. package/dist/astro/routes/api/oauth/register.mjs +3 -3
  185. package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
  186. package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
  187. package/dist/astro/routes/api/oauth/token.mjs +6 -6
  188. package/dist/astro/routes/api/openapi.json.mjs +10 -7
  189. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  190. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  191. package/dist/astro/routes/api/redirects/404s/index.mjs +8 -8
  192. package/dist/astro/routes/api/redirects/404s/summary.mjs +8 -8
  193. package/dist/astro/routes/api/redirects/_id_.mjs +9 -9
  194. package/dist/astro/routes/api/redirects/index.mjs +9 -9
  195. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  196. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  197. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +34 -33
  198. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  199. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +34 -33
  200. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  201. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +34 -33
  202. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  203. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +34 -33
  204. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  205. package/dist/astro/routes/api/schema/collections/index.mjs +34 -33
  206. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  207. package/dist/astro/routes/api/schema/index.mjs +6 -6
  208. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +34 -33
  209. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  210. package/dist/astro/routes/api/schema/orphans/index.mjs +34 -33
  211. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  212. package/dist/astro/routes/api/search/enable.mjs +9 -9
  213. package/dist/astro/routes/api/search/index.mjs +8 -8
  214. package/dist/astro/routes/api/search/rebuild.mjs +9 -9
  215. package/dist/astro/routes/api/search/stats.mjs +6 -6
  216. package/dist/astro/routes/api/search/suggest.mjs +8 -8
  217. package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
  218. package/dist/astro/routes/api/sections/index.mjs +8 -8
  219. package/dist/astro/routes/api/settings/email.mjs +4 -4
  220. package/dist/astro/routes/api/settings.mjs +11 -11
  221. package/dist/astro/routes/api/setup/admin-verify.mjs +10 -10
  222. package/dist/astro/routes/api/setup/admin.mjs +9 -9
  223. package/dist/astro/routes/api/setup/dev-bypass.mjs +24 -23
  224. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  225. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  226. package/dist/astro/routes/api/setup/index.mjs +24 -23
  227. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  228. package/dist/astro/routes/api/setup/status.mjs +4 -4
  229. package/dist/astro/routes/api/snapshot.mjs +5 -5
  230. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +12 -12
  231. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +12 -12
  232. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +12 -12
  233. package/dist/astro/routes/api/taxonomies/index.mjs +12 -12
  234. package/dist/astro/routes/api/themes/preview.mjs +5 -5
  235. package/dist/astro/routes/api/typegen.mjs +5 -5
  236. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  237. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  238. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  239. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
  240. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +8 -8
  241. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +8 -8
  242. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  243. package/dist/astro/routes/api/widget-areas/index.mjs +8 -8
  244. package/dist/astro/routes/api/widget-components.mjs +3 -3
  245. package/dist/astro/routes/robots.txt.mjs +6 -6
  246. package/dist/astro/routes/sitemap-_collection_.xml.mjs +8 -8
  247. package/dist/astro/routes/sitemap.xml.mjs +7 -7
  248. package/dist/astro/types.d.mts +13 -12
  249. package/dist/astro/types.d.mts.map +1 -1
  250. package/dist/auth/providers/github.d.mts +1 -1
  251. package/dist/auth/providers/google.d.mts +1 -1
  252. package/dist/{authorize-Bn4S4DUT.mjs → authorize-_wWM_44T.mjs} +2 -2
  253. package/dist/{authorize-Bn4S4DUT.mjs.map → authorize-_wWM_44T.mjs.map} +1 -1
  254. package/dist/byline-BrIVWLm-.mjs +925 -0
  255. package/dist/byline-BrIVWLm-.mjs.map +1 -0
  256. package/dist/{bylines-DWLnr6-k.d.mts → byline-fields-BNy7Ng1U.d.mts} +151 -23
  257. package/dist/byline-fields-BNy7Ng1U.d.mts.map +1 -0
  258. package/dist/byline-fields-DC3Wkk-U.mjs +123 -0
  259. package/dist/byline-fields-DC3Wkk-U.mjs.map +1 -0
  260. package/dist/byline-fields-Dr-xcb6S.mjs +238 -0
  261. package/dist/byline-fields-Dr-xcb6S.mjs.map +1 -0
  262. package/dist/byline-registry-CxK5g559.mjs +406 -0
  263. package/dist/byline-registry-CxK5g559.mjs.map +1 -0
  264. package/dist/{bylines-n6nykUyI.mjs → bylines-C_POWmGT.mjs} +25 -11
  265. package/dist/{bylines-n6nykUyI.mjs.map → bylines-C_POWmGT.mjs.map} +1 -1
  266. package/dist/bylines-sqExMElV.mjs +204 -0
  267. package/dist/bylines-sqExMElV.mjs.map +1 -0
  268. package/dist/{cache-BcI1yUjR.mjs → cache-wsDkA8ru.mjs} +2 -2
  269. package/dist/{cache-BcI1yUjR.mjs.map → cache-wsDkA8ru.mjs.map} +1 -1
  270. package/dist/{challenge-store-Dng1SxKT.mjs → challenge-store-DGwuCc4R.mjs} +1 -1
  271. package/dist/{challenge-store-Dng1SxKT.mjs.map → challenge-store-DGwuCc4R.mjs.map} +1 -1
  272. package/dist/{chunks-cYG4SnIP.mjs → chunks-BAYkM-CF.mjs} +2 -2
  273. package/dist/{chunks-cYG4SnIP.mjs.map → chunks-BAYkM-CF.mjs.map} +1 -1
  274. package/dist/cli/index.mjs +140 -32
  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 +2 -1
  278. package/dist/client/index.d.mts.map +1 -1
  279. package/dist/client/index.mjs +4 -2
  280. package/dist/client/index.mjs.map +1 -1
  281. package/dist/{comment-C76G-9tz.mjs → comment-Cd29aktf.mjs} +2 -2
  282. package/dist/{comment-C76G-9tz.mjs.map → comment-Cd29aktf.mjs.map} +1 -1
  283. package/dist/{comments-CCxFFGY1.mjs → comments-B7ufhkxN.mjs} +3 -3
  284. package/dist/{comments-CCxFFGY1.mjs.map → comments-B7ufhkxN.mjs.map} +1 -1
  285. package/dist/{components-Dx3DM0gg.mjs → components-CTfpu3PZ.mjs} +1 -1
  286. package/dist/{components-Dx3DM0gg.mjs.map → components-CTfpu3PZ.mjs.map} +1 -1
  287. package/dist/{content-8voQNTXX.mjs → content-BbqKo3Kc.mjs} +22 -3
  288. package/dist/content-BbqKo3Kc.mjs.map +1 -0
  289. package/dist/{context-B7qiYrz2.mjs → context-BsF1rhoI.mjs} +9 -9
  290. package/dist/{context-B7qiYrz2.mjs.map → context-BsF1rhoI.mjs.map} +1 -1
  291. package/dist/{cron-Bd3b3iuj.mjs → cron-DZovZUnC.mjs} +1 -1
  292. package/dist/{cron-Bd3b3iuj.mjs.map → cron-DZovZUnC.mjs.map} +1 -1
  293. package/dist/{dashboard-BeaFSPpx.mjs → dashboard-BwIX9r-X.mjs} +4 -4
  294. package/dist/{dashboard-BeaFSPpx.mjs.map → dashboard-BwIX9r-X.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/{db-errors-BiYqoX-n.mjs → db-errors-CtzxKBxe.mjs} +1 -1
  301. package/dist/{db-errors-BiYqoX-n.mjs.map → db-errors-CtzxKBxe.mjs.map} +1 -1
  302. package/dist/{default-BvTAYCzx.mjs → default-xLFNSsZ9.mjs} +1 -1
  303. package/dist/{default-BvTAYCzx.mjs.map → default-xLFNSsZ9.mjs.map} +1 -1
  304. package/dist/{device-flow-B9oG8PwP.mjs → device-flow-ptLrVINd.mjs} +4 -4
  305. package/dist/{device-flow-B9oG8PwP.mjs.map → device-flow-ptLrVINd.mjs.map} +1 -1
  306. package/dist/{email-console-CubRll9q.mjs → email-console-DHT2Fbpj.mjs} +1 -1
  307. package/dist/{email-console-CubRll9q.mjs.map → email-console-DHT2Fbpj.mjs.map} +1 -1
  308. package/dist/{error-ChfADBuu.mjs → error-npZWBSb7.mjs} +7 -3
  309. package/dist/error-npZWBSb7.mjs.map +1 -0
  310. package/dist/{escape-Cg6kMELH.mjs → escape-bIyGoW5W.mjs} +1 -1
  311. package/dist/{escape-Cg6kMELH.mjs.map → escape-bIyGoW5W.mjs.map} +1 -1
  312. package/dist/{fts-manager-C_b-4x8u.mjs → fts-manager-DmUAk-kQ.mjs} +2 -2
  313. package/dist/{fts-manager-C_b-4x8u.mjs.map → fts-manager-DmUAk-kQ.mjs.map} +1 -1
  314. package/dist/{hash-DlUxGhQS.mjs → hash-9w3pd3-m.mjs} +1 -1
  315. package/dist/{hash-DlUxGhQS.mjs.map → hash-9w3pd3-m.mjs.map} +1 -1
  316. package/dist/{import-DG80rC_I.mjs → import-Dh8bWmyq.mjs} +3 -3
  317. package/dist/{import-DG80rC_I.mjs.map → import-Dh8bWmyq.mjs.map} +1 -1
  318. package/dist/{index-D_p_jIP1.d.mts → index-CjKdMZ3U.d.mts} +38 -16
  319. package/dist/index-CjKdMZ3U.d.mts.map +1 -0
  320. package/dist/{index-CC42STEm.d.mts → index-D60_SzHG.d.mts} +3 -3
  321. package/dist/{index-CC42STEm.d.mts.map → index-D60_SzHG.d.mts.map} +1 -1
  322. package/dist/index.d.mts +17 -17
  323. package/dist/index.mjs +55 -54
  324. package/dist/{load-CLFRjk9r.mjs → load-DsoLq7ex.mjs} +2 -2
  325. package/dist/{load-CLFRjk9r.mjs.map → load-DsoLq7ex.mjs.map} +1 -1
  326. package/dist/{loader-D-vIJjfY.mjs → loader-CJ6lWO0d.mjs} +75 -19
  327. package/dist/loader-CJ6lWO0d.mjs.map +1 -0
  328. package/dist/{manifest-schema-Czqf0TLu.mjs → manifest-schema-Cj-YrzrF.mjs} +1 -1
  329. package/dist/{manifest-schema-Czqf0TLu.mjs.map → manifest-schema-Cj-YrzrF.mjs.map} +1 -1
  330. package/dist/media/index.d.mts +1 -1
  331. package/dist/media/index.mjs +2 -2
  332. package/dist/media/local-runtime.d.mts +11 -11
  333. package/dist/media/local-runtime.mjs +5 -5
  334. package/dist/{media-allowlist-BNloC69x.mjs → media-allowlist-CMcoYIjQ.mjs} +2 -2
  335. package/dist/{media-allowlist-BNloC69x.mjs.map → media-allowlist-CMcoYIjQ.mjs.map} +1 -1
  336. package/dist/{media-CKQd8AYU.mjs → media-jk_HzzOl.mjs} +7 -2
  337. package/dist/media-jk_HzzOl.mjs.map +1 -0
  338. package/dist/{menus-arUNspyU.mjs → menus-B-5-3aon.mjs} +2 -2
  339. package/dist/{menus-arUNspyU.mjs.map → menus-B-5-3aon.mjs.map} +1 -1
  340. package/dist/{menus-C-nWT5Tu.mjs → menus-CyMO6GBx.mjs} +27 -11
  341. package/dist/menus-CyMO6GBx.mjs.map +1 -0
  342. package/dist/{mime-KV5TqkMN.mjs → mime-CCEzze7W.mjs} +1 -1
  343. package/dist/{mime-KV5TqkMN.mjs.map → mime-CCEzze7W.mjs.map} +1 -1
  344. package/dist/{mode-CaaiebZI.mjs → mode-BjlXswIw.mjs} +1 -1
  345. package/dist/{mode-CaaiebZI.mjs.map → mode-BjlXswIw.mjs.map} +1 -1
  346. package/dist/{normalize-CN5kRSMC.mjs → normalize-DVV8nbrL.mjs} +1 -1
  347. package/dist/{normalize-CN5kRSMC.mjs.map → normalize-DVV8nbrL.mjs.map} +1 -1
  348. package/dist/{oauth-authorization-CTMeVfvj.mjs → oauth-authorization-DvBAL75d.mjs} +4 -4
  349. package/dist/{oauth-authorization-CTMeVfvj.mjs.map → oauth-authorization-DvBAL75d.mjs.map} +1 -1
  350. package/dist/{oauth-clients-eJCbkVSG.mjs → oauth-clients-8mPDStMv.mjs} +1 -1
  351. package/dist/{oauth-clients-eJCbkVSG.mjs.map → oauth-clients-8mPDStMv.mjs.map} +1 -1
  352. package/dist/{oauth-state-store-vOSdOeGe.mjs → oauth-state-store-BJ7YtrfD.mjs} +1 -1
  353. package/dist/{oauth-state-store-vOSdOeGe.mjs.map → oauth-state-store-BJ7YtrfD.mjs.map} +1 -1
  354. package/dist/{oauth-user-lookup-3JwsVw6N.mjs → oauth-user-lookup-BdDSDvjF.mjs} +1 -1
  355. package/dist/{oauth-user-lookup-3JwsVw6N.mjs.map → oauth-user-lookup-BdDSDvjF.mjs.map} +1 -1
  356. package/dist/{options-DhV-gwJb.d.mts → options-tb7DJROi.d.mts} +3 -3
  357. package/dist/{options-DhV-gwJb.d.mts.map → options-tb7DJROi.d.mts.map} +1 -1
  358. package/dist/page/index.d.mts +2 -2
  359. package/dist/{parse-DHbXfvxO.mjs → parse-4zO5Y2DL.mjs} +2 -2
  360. package/dist/{parse-DHbXfvxO.mjs.map → parse-4zO5Y2DL.mjs.map} +1 -1
  361. package/dist/{passkey-config-BloQOT3y.mjs → passkey-config-BDVM86Tj.mjs} +1 -1
  362. package/dist/{passkey-config-BloQOT3y.mjs.map → passkey-config-BDVM86Tj.mjs.map} +1 -1
  363. package/dist/{placeholder-KCkkCtgQ.d.mts → placeholder-B9lUUEmj.d.mts} +1 -1
  364. package/dist/{placeholder-KCkkCtgQ.d.mts.map → placeholder-B9lUUEmj.d.mts.map} +1 -1
  365. package/dist/{placeholder-LqmHqvBw.mjs → placeholder-BZxr8W1j.mjs} +1 -1
  366. package/dist/{placeholder-LqmHqvBw.mjs.map → placeholder-BZxr8W1j.mjs.map} +1 -1
  367. package/dist/plugin-types.d.mts +1 -1
  368. package/dist/plugin-utils.d.mts +9 -9
  369. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  370. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  371. package/dist/{preview-D4z0WONU.mjs → preview-BfuRkVKW.mjs} +2 -2
  372. package/dist/{preview-D4z0WONU.mjs.map → preview-BfuRkVKW.mjs.map} +1 -1
  373. package/dist/{public-url-CUWWFME2.mjs → public-url-egRHCy1m.mjs} +1 -1
  374. package/dist/{public-url-CUWWFME2.mjs.map → public-url-egRHCy1m.mjs.map} +1 -1
  375. package/dist/{query-7m6-l0f_.mjs → query-Bt52mHXp.mjs} +19 -18
  376. package/dist/query-Bt52mHXp.mjs.map +1 -0
  377. package/dist/{rate-limit-D8RAXN8b.mjs → rate-limit-D6VQqBk_.mjs} +2 -2
  378. package/dist/{rate-limit-D8RAXN8b.mjs.map → rate-limit-D6VQqBk_.mjs.map} +1 -1
  379. package/dist/{redirect-CjfDGrTd.mjs → redirect-BZUJltlj.mjs} +2 -2
  380. package/dist/{redirect-CjfDGrTd.mjs.map → redirect-BZUJltlj.mjs.map} +1 -1
  381. package/dist/{redirect-BINiRYq4.mjs → redirect-Cw3JTlmj.mjs} +1 -1
  382. package/dist/{redirect-BINiRYq4.mjs.map → redirect-Cw3JTlmj.mjs.map} +1 -1
  383. package/dist/{redirects-COMLwsV5.mjs → redirects-C0L9JUk4.mjs} +19 -6
  384. package/dist/redirects-C0L9JUk4.mjs.map +1 -0
  385. package/dist/{redirects-CowoEHdE.mjs → redirects-DnYuqsEf.mjs} +3 -3
  386. package/dist/{redirects-CowoEHdE.mjs.map → redirects-DnYuqsEf.mjs.map} +1 -1
  387. package/dist/{registry-Cyp-dx6J.mjs → registry-Dn6gsx3L.mjs} +13 -5
  388. package/dist/{registry-Cyp-dx6J.mjs.map → registry-Dn6gsx3L.mjs.map} +1 -1
  389. package/dist/{request-cache-dzCt8TZB.mjs → request-cache-BYMs-BGX.mjs} +23 -2
  390. package/dist/{request-cache-dzCt8TZB.mjs.map → request-cache-BYMs-BGX.mjs.map} +1 -1
  391. package/dist/{request-meta-C_Cjii-T.mjs → request-meta-7ByVLxB-.mjs} +2 -2
  392. package/dist/{request-meta-C_Cjii-T.mjs.map → request-meta-7ByVLxB-.mjs.map} +1 -1
  393. package/dist/{resolve-D6sM-SgF.mjs → resolve-BqYMVG0D.mjs} +1 -1
  394. package/dist/{resolve-D6sM-SgF.mjs.map → resolve-BqYMVG0D.mjs.map} +1 -1
  395. package/dist/{runner-DSQBurMS.d.mts → runner-DM1yR5qd.d.mts} +2 -2
  396. package/dist/{runner-DSQBurMS.d.mts.map → runner-DM1yR5qd.d.mts.map} +1 -1
  397. package/dist/{runner-Drnvs96u.mjs → runner-eAgyIkeg.mjs} +284 -158
  398. package/dist/runner-eAgyIkeg.mjs.map +1 -0
  399. package/dist/runtime.d.mts +10 -10
  400. package/dist/runtime.mjs +2 -2
  401. package/dist/{schema-CI9mYPX3.mjs → schema--mYZX4D7.mjs} +5 -5
  402. package/dist/{schema-CI9mYPX3.mjs.map → schema--mYZX4D7.mjs.map} +1 -1
  403. package/dist/{search-DKz_mGBP.mjs → search-C6U_NvZI.mjs} +4 -4
  404. package/dist/{search-DKz_mGBP.mjs.map → search-C6U_NvZI.mjs.map} +1 -1
  405. package/dist/{secrets-rPdhEBkD.mjs → secrets-YYbTgB1w.mjs} +1 -1
  406. package/dist/{secrets-rPdhEBkD.mjs.map → secrets-YYbTgB1w.mjs.map} +1 -1
  407. package/dist/{sections-DBbCDIAT.mjs → sections-Ba-rJLKb.mjs} +3 -3
  408. package/dist/{sections-DBbCDIAT.mjs.map → sections-Ba-rJLKb.mjs.map} +1 -1
  409. package/dist/seed/index.d.mts +2 -2
  410. package/dist/seed/index.mjs +18 -17
  411. package/dist/seo/index.d.mts +1 -1
  412. package/dist/{seo-BGCyDlkb.mjs → seo-BTzb5ksq.mjs} +2 -2
  413. package/dist/{seo-BGCyDlkb.mjs.map → seo-BTzb5ksq.mjs.map} +1 -1
  414. package/dist/{seo-Dq707mNQ.mjs → seo-DfjLvu8i.mjs} +1 -1
  415. package/dist/{seo-Dq707mNQ.mjs.map → seo-DfjLvu8i.mjs.map} +1 -1
  416. package/dist/{service-B0H7U1Y9.mjs → service-Cn-kIfZn.mjs} +3 -3
  417. package/dist/{service-B0H7U1Y9.mjs.map → service-Cn-kIfZn.mjs.map} +1 -1
  418. package/dist/{settings-DfwNyQkf.mjs → settings-C65OSm41.mjs} +3 -3
  419. package/dist/{settings-DfwNyQkf.mjs.map → settings-C65OSm41.mjs.map} +1 -1
  420. package/dist/{settings-BSXRtTzk.mjs → settings-ChlQbwU0.mjs} +4 -4
  421. package/dist/{settings-BSXRtTzk.mjs.map → settings-ChlQbwU0.mjs.map} +1 -1
  422. package/dist/{setup-complete-MzzN9u0b.mjs → setup-complete-VoEZfasi.mjs} +1 -1
  423. package/dist/{setup-complete-MzzN9u0b.mjs.map → setup-complete-VoEZfasi.mjs.map} +1 -1
  424. package/dist/{setup-nonce-DXuriHsg.mjs → setup-nonce-Bm0uKqmf.mjs} +1 -1
  425. package/dist/{setup-nonce-DXuriHsg.mjs.map → setup-nonce-Bm0uKqmf.mjs.map} +1 -1
  426. package/dist/{site-url-xkhw1tcz.mjs → site-url-Cm8-sJy7.mjs} +1 -1
  427. package/dist/{site-url-xkhw1tcz.mjs.map → site-url-Cm8-sJy7.mjs.map} +1 -1
  428. package/dist/{ssrf-MZ-zrG6-.mjs → ssrf-BsVGIE0Z.mjs} +1 -1
  429. package/dist/{ssrf-MZ-zrG6-.mjs.map → ssrf-BsVGIE0Z.mjs.map} +1 -1
  430. package/dist/storage/local.d.mts +1 -1
  431. package/dist/storage/local.mjs +1 -1
  432. package/dist/storage/s3.d.mts +1 -1
  433. package/dist/storage/s3.mjs +1 -1
  434. package/dist/{taxonomies-CcvrMLbR.mjs → taxonomies-ByLlXrv5.mjs} +8 -8
  435. package/dist/{taxonomies-CcvrMLbR.mjs.map → taxonomies-ByLlXrv5.mjs.map} +1 -1
  436. package/dist/{taxonomies-4vx0nmMr.mjs → taxonomies-CbO6v7EE.mjs} +4 -4
  437. package/dist/{taxonomies-4vx0nmMr.mjs.map → taxonomies-CbO6v7EE.mjs.map} +1 -1
  438. package/dist/{taxonomy-zqGQUqgu.mjs → taxonomy-BBK-UAEo.mjs} +3 -3
  439. package/dist/{taxonomy-zqGQUqgu.mjs.map → taxonomy-BBK-UAEo.mjs.map} +1 -1
  440. package/dist/{tokens-N8otWMmj.mjs → tokens-Bx2afeT-.mjs} +1 -1
  441. package/dist/{tokens-N8otWMmj.mjs.map → tokens-Bx2afeT-.mjs.map} +1 -1
  442. package/dist/{transport-B6CHddbu.mjs → transport--Ck3RBin.mjs} +1 -1
  443. package/dist/{transport-B6CHddbu.mjs.map → transport--Ck3RBin.mjs.map} +1 -1
  444. package/dist/{transport-C2MGqtL6.d.mts → transport-OnMNbsIA.d.mts} +1 -1
  445. package/dist/{transport-C2MGqtL6.d.mts.map → transport-OnMNbsIA.d.mts.map} +1 -1
  446. package/dist/{trusted-proxy-97pajC2f.mjs → trusted-proxy-B4AfnoAp.mjs} +1 -1
  447. package/dist/{trusted-proxy-97pajC2f.mjs.map → trusted-proxy-B4AfnoAp.mjs.map} +1 -1
  448. package/dist/types-D8bhH891.mjs +125 -0
  449. package/dist/{types-DSZl1Dsv.mjs.map → types-D8bhH891.mjs.map} +1 -1
  450. package/dist/{types-DGHWRQgr.d.mts → types-DMwSpvcw.d.mts} +2 -2
  451. package/dist/{types-DGHWRQgr.d.mts.map → types-DMwSpvcw.d.mts.map} +1 -1
  452. package/dist/{types-bYmRn_Uy.d.mts → types-DWnN7weG.d.mts} +1 -1
  453. package/dist/{types-bYmRn_Uy.d.mts.map → types-DWnN7weG.d.mts.map} +1 -1
  454. package/dist/{types-Dgo6y-Ut.d.mts → types-DX6v9KzJ.d.mts} +1 -1
  455. package/dist/{types-Dgo6y-Ut.d.mts.map → types-DX6v9KzJ.d.mts.map} +1 -1
  456. package/dist/{types-DaqNzqVt.d.mts → types-DawhLFwy.d.mts} +35 -1
  457. package/dist/{types-DaqNzqVt.d.mts.map → types-DawhLFwy.d.mts.map} +1 -1
  458. package/dist/{types-CpUuGcd5.d.mts → types-DbCWhHet.d.mts} +8 -2
  459. package/dist/{types-CpUuGcd5.d.mts.map → types-DbCWhHet.d.mts.map} +1 -1
  460. package/dist/{types-Cd9UCu3t.mjs → types-DpFmlNyB.mjs} +1 -1
  461. package/dist/{types-Cd9UCu3t.mjs.map → types-DpFmlNyB.mjs.map} +1 -1
  462. package/dist/{types-D599-ruj.d.mts → types-Qa7-HJJC.d.mts} +1 -1
  463. package/dist/{types-D599-ruj.d.mts.map → types-Qa7-HJJC.d.mts.map} +1 -1
  464. package/dist/{types-B0bmgwMG.mjs → types-SF1DwGf2.mjs} +2 -2
  465. package/dist/types-SF1DwGf2.mjs.map +1 -0
  466. package/dist/{types-DaYDYW6g.d.mts → types-i8_uzhMD.d.mts} +40 -2
  467. package/dist/types-i8_uzhMD.d.mts.map +1 -0
  468. package/dist/{types-CkDSF81F.d.mts → types-kwqCOUxj.d.mts} +1 -1
  469. package/dist/{types-CkDSF81F.d.mts.map → types-kwqCOUxj.d.mts.map} +1 -1
  470. package/dist/{user-hUSOaIJy.mjs → user-X4rtyO4Y.mjs} +2 -2
  471. package/dist/{user-hUSOaIJy.mjs.map → user-X4rtyO4Y.mjs.map} +1 -1
  472. package/dist/{utils-C3wTAP-P.mjs → utils-C4Ih4DML.mjs} +1 -1
  473. package/dist/{utils-C3wTAP-P.mjs.map → utils-C4Ih4DML.mjs.map} +1 -1
  474. package/dist/{validate-IGltez8n.mjs → validate-DactmcJG.mjs} +23 -3
  475. package/dist/validate-DactmcJG.mjs.map +1 -0
  476. package/dist/{validate-DQtHw9NT.d.mts → validate-Dy6nkNls.d.mts} +25 -5
  477. package/dist/{validate-DQtHw9NT.d.mts.map → validate-Dy6nkNls.d.mts.map} +1 -1
  478. package/dist/{validation-Bmymau7y.mjs → validation-BYA4i85b.mjs} +6 -6
  479. package/dist/{validation-Bmymau7y.mjs.map → validation-BYA4i85b.mjs.map} +1 -1
  480. package/dist/version-CWbvq9LG.mjs +7 -0
  481. package/dist/{version-ITD3PlQd.mjs.map → version-CWbvq9LG.mjs.map} +1 -1
  482. package/dist/{widgets-yHQa4c6c.mjs → widgets-DG-1jxnz.mjs} +3 -3
  483. package/dist/{widgets-yHQa4c6c.mjs.map → widgets-DG-1jxnz.mjs.map} +1 -1
  484. package/dist/{zod-generator-B80aap1J.mjs → zod-generator-BNAObjSt.mjs} +3 -3
  485. package/dist/{zod-generator-B80aap1J.mjs.map → zod-generator-BNAObjSt.mjs.map} +1 -1
  486. package/package.json +7 -7
  487. package/src/api/errors.ts +7 -0
  488. package/src/api/handlers/byline-fields.ts +212 -0
  489. package/src/api/handlers/bylines.ts +126 -5
  490. package/src/api/handlers/content.ts +43 -2
  491. package/src/api/handlers/media.ts +2 -0
  492. package/src/api/openapi/document.ts +3 -0
  493. package/src/api/schemas/byline-fields.ts +188 -0
  494. package/src/api/schemas/bylines.ts +42 -0
  495. package/src/api/schemas/content.ts +2 -0
  496. package/src/api/schemas/index.ts +1 -0
  497. package/src/api/schemas/media.ts +2 -0
  498. package/src/astro/integration/routes.ts +27 -0
  499. package/src/astro/integration/vite-config.ts +16 -0
  500. package/src/astro/middleware/redirect.ts +5 -1
  501. package/src/astro/routes/api/admin/byline-fields/[slug]/usage.ts +36 -0
  502. package/src/astro/routes/api/admin/byline-fields/[slug].ts +92 -0
  503. package/src/astro/routes/api/admin/byline-fields/index.ts +66 -0
  504. package/src/astro/routes/api/admin/byline-fields/reorder.ts +39 -0
  505. package/src/astro/routes/api/admin/bylines/[id]/index.ts +23 -21
  506. package/src/astro/routes/api/admin/bylines/index.ts +1 -0
  507. package/src/astro/routes/api/auth/me.ts +21 -10
  508. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +15 -3
  509. package/src/astro/routes/api/content/[collection]/[id].ts +3 -1
  510. package/src/astro/routes/api/media.ts +1 -0
  511. package/src/astro/types.ts +1 -0
  512. package/src/bylines/field-defs-cache.ts +138 -0
  513. package/src/bylines/index.ts +37 -4
  514. package/src/cli/commands/content.ts +4 -2
  515. package/src/cli/commands/export-seed.ts +174 -12
  516. package/src/client/index.ts +4 -1
  517. package/src/components/InlinePortableTextEditor.tsx +69 -0
  518. package/src/content/converters/portable-text-to-prosemirror.ts +7 -0
  519. package/src/content/converters/prosemirror-to-portable-text.ts +16 -0
  520. package/src/content/converters/types.ts +10 -0
  521. package/src/database/migrations/041_content_locale_list_index.ts +47 -0
  522. package/src/database/migrations/042_byline_fields.ts +157 -0
  523. package/src/database/migrations/runner.ts +4 -0
  524. package/src/database/repositories/byline.ts +758 -50
  525. package/src/database/repositories/content.ts +43 -3
  526. package/src/database/repositories/media.ts +14 -0
  527. package/src/database/repositories/types.ts +38 -0
  528. package/src/database/types.ts +44 -0
  529. package/src/emdash-runtime.ts +4 -1
  530. package/src/index.ts +1 -0
  531. package/src/loader.ts +98 -10
  532. package/src/mcp/server.ts +10 -1
  533. package/src/query.ts +7 -7
  534. package/src/request-cache.ts +23 -0
  535. package/src/schema/byline-registry.ts +671 -0
  536. package/src/schema/registry.ts +14 -0
  537. package/src/schema/types.ts +133 -0
  538. package/src/seed/apply.ts +101 -14
  539. package/src/seed/types.ts +21 -0
  540. package/src/seed/validate.ts +39 -0
  541. package/dist/api-BNKqxyFX.mjs.map +0 -1
  542. package/dist/apply-BOPaD-s9.mjs.map +0 -1
  543. package/dist/byline-BDylH_m4.mjs +0 -404
  544. package/dist/byline-BDylH_m4.mjs.map +0 -1
  545. package/dist/bylines-B7TFEvFf.mjs +0 -118
  546. package/dist/bylines-B7TFEvFf.mjs.map +0 -1
  547. package/dist/bylines-DWLnr6-k.d.mts.map +0 -1
  548. package/dist/content-8voQNTXX.mjs.map +0 -1
  549. package/dist/error-ChfADBuu.mjs.map +0 -1
  550. package/dist/index-D_p_jIP1.d.mts.map +0 -1
  551. package/dist/loader-D-vIJjfY.mjs.map +0 -1
  552. package/dist/media-CKQd8AYU.mjs.map +0 -1
  553. package/dist/menus-C-nWT5Tu.mjs.map +0 -1
  554. package/dist/query-7m6-l0f_.mjs.map +0 -1
  555. package/dist/redirects-COMLwsV5.mjs.map +0 -1
  556. package/dist/runner-Drnvs96u.mjs.map +0 -1
  557. package/dist/setup-Cf_TyOv5.mjs +0 -137
  558. package/dist/setup-Cf_TyOv5.mjs.map +0 -1
  559. package/dist/types-B0bmgwMG.mjs.map +0 -1
  560. package/dist/types-DSZl1Dsv.mjs +0 -83
  561. package/dist/types-DaYDYW6g.d.mts.map +0 -1
  562. package/dist/validate-IGltez8n.mjs.map +0 -1
  563. package/dist/version-ITD3PlQd.mjs +0 -7
  564. /package/dist/{api-tokens-iPIHAY8N.mjs → api-tokens-B6VgoE6M.mjs} +0 -0
  565. /package/dist/{ssrf-BIcd-aXW.mjs → ssrf-BvgVcfNQ.mjs} +0 -0
  566. /package/dist/{types-1NNkmTIn.mjs → types-Cj2S6FuC.mjs} +0 -0
@@ -0,0 +1,671 @@
1
+ import type { Kysely } from "kysely";
2
+ import { sql } from "kysely";
3
+ import { ulid } from "ulidx";
4
+
5
+ import { withTransaction } from "../database/transaction.js";
6
+ import type { BylineFieldTable, Database } from "../database/types.js";
7
+ import { validateIdentifier } from "../database/validate.js";
8
+ import {
9
+ BYLINE_FIELD_TYPES,
10
+ RESERVED_BYLINE_FIELD_SLUGS,
11
+ type BylineFieldDefinition,
12
+ type BylineFieldType,
13
+ type BylineFieldValidation,
14
+ type CreateBylineFieldInput,
15
+ type UpdateBylineFieldInput,
16
+ } from "./types.js";
17
+
18
+ const RESERVED_SET: ReadonlySet<string> = new Set(RESERVED_BYLINE_FIELD_SLUGS);
19
+ const TYPE_SET: ReadonlySet<string> = new Set(BYLINE_FIELD_TYPES);
20
+
21
+ const VERSION_KEY = "byline_fields_version";
22
+
23
+ /** Hard cap on the choices array for a `select`-type field. */
24
+ const MAX_SELECT_OPTIONS = 200;
25
+ /** Hard cap on a slug — mirrors `SchemaRegistry.validateSlug`. */
26
+ const MAX_SLUG_LENGTH = 63;
27
+ /** Hard cap on a label. Bigger than slugs because labels are display strings. */
28
+ const MAX_LABEL_LENGTH = 200;
29
+
30
+ /**
31
+ * Error thrown for byline-schema validation failures. Mirrors
32
+ * `SchemaError` in `registry.ts` so the admin API layer can map a small
33
+ * set of codes to HTTP statuses without inspecting messages.
34
+ *
35
+ * Codes:
36
+ * - `INVALID_SLUG` — slug fails identifier rules or length cap
37
+ * - `RESERVED_SLUG` — slug collides with a fixed `_emdash_bylines` column
38
+ * - `INVALID_TYPE` — type is not one of the five v1 field types
39
+ * - `INVALID_LABEL` — label missing or exceeds length cap
40
+ * - `INVALID_VALIDATION` — validation payload malformed (e.g. `select` with
41
+ * no `options`, duplicates in `options`)
42
+ * - `FIELD_EXISTS` — slug already registered
43
+ * - `FIELD_NOT_FOUND` — slug not registered
44
+ * - `TRANSLATABLE_LOCKED` — attempt to flip `translatable` while stored
45
+ * values reference the field
46
+ * - `REORDER_MISMATCH` — reorder input doesn't match the registered set
47
+ */
48
+ export class BylineSchemaError extends Error {
49
+ constructor(
50
+ message: string,
51
+ public code: string,
52
+ public details?: Record<string, unknown>,
53
+ ) {
54
+ super(message);
55
+ this.name = "BylineSchemaError";
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Translate a `BylineSchemaError` code to a shared `ErrorCode` for the
61
+ * admin API. HTTP status is then derived by `mapErrorStatus` — this
62
+ * function deliberately doesn't carry one, so the API/handler boundary
63
+ * matches the rest of the codebase (handlers return `ApiResult<T>` with
64
+ * a code, the route layer maps to status via `unwrapResult`).
65
+ *
66
+ * Every code on the right-hand side of `case ... return ...` is defined
67
+ * in `ErrorCode` (`api/errors.ts`). `INVALID_LABEL` and
68
+ * `INVALID_VALIDATION` are intentionally folded into the `default`
69
+ * branch (→ `VALIDATION_ERROR`) so no ad-hoc codes leak out — the
70
+ * registry's domain code names them but the HTTP surface should not.
71
+ *
72
+ * `RESERVED_SLUG` / `INVALID_SLUG` typically don't reach this layer for
73
+ * HTTP callers — the zod schema rejects them first with a clean
74
+ * `VALIDATION_ERROR`. They're still listed so non-HTTP callers (and the
75
+ * test layer) get consistent mapping.
76
+ *
77
+ * `FIELD_NOT_FOUND` is normalised to the shared `NOT_FOUND` code so the
78
+ * admin client can branch on one constant across resource types.
79
+ */
80
+ export function mapBylineSchemaError(error: BylineSchemaError): {
81
+ code: string;
82
+ message: string;
83
+ details?: Record<string, unknown>;
84
+ } {
85
+ switch (error.code) {
86
+ case "FIELD_NOT_FOUND":
87
+ return { code: "NOT_FOUND", message: error.message, details: error.details };
88
+ case "FIELD_EXISTS":
89
+ case "TRANSLATABLE_LOCKED":
90
+ case "REORDER_MISMATCH":
91
+ case "INVALID_SLUG":
92
+ case "RESERVED_SLUG":
93
+ case "INVALID_TYPE":
94
+ return { code: error.code, message: error.message, details: error.details };
95
+ default:
96
+ // Catches INVALID_LABEL, INVALID_VALIDATION, and any future
97
+ // registry codes we forget to wire up explicitly.
98
+ return { code: "VALIDATION_ERROR", message: error.message, details: error.details };
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Registry for byline custom fields (Discussion #1174).
104
+ *
105
+ * Owns CRUD over `_emdash_byline_fields` and the
106
+ * `options.byline_fields_version` counter that drives cache
107
+ * invalidation in `bylines/field-defs-cache.ts`.
108
+ *
109
+ * **Dirty-bit bookend.** Every mutation runs `markVersionDirty` before
110
+ * the schema write and `markVersionClean` after, as standalone writes
111
+ * (not inside `withTransaction`) so concurrent isolates observe the
112
+ * dirty mark *before* the mutation lands. Parity carries meaning:
113
+ * odd = mutation in flight or crashed mid-flight, even = stable.
114
+ * The cache bypasses the global holder while odd.
115
+ *
116
+ * `markVersionDirty` is parity-aware (idempotent on odd) so a
117
+ * crashed prior attempt doesn't invert the bit.
118
+ * `markVersionClean` always advances to a new even value (+2 from
119
+ * even, +1 from odd) so concurrent mutators can't collapse on the
120
+ * same key and pin a stale cache snapshot. Idempotent-retry exits
121
+ * (`FIELD_EXISTS` / `FIELD_NOT_FOUND` / no-op update) call
122
+ * `markVersionClean` too — same code path doubles as crash recovery
123
+ * and false-clean recovery.
124
+ *
125
+ * The residual race: a reader caching between two concurrent
126
+ * `markVersionClean` calls sees a partial-set snapshot until the
127
+ * second clean lands. Bounded by the inter-clean window (~ms).
128
+ * Schema mutations are admin-only and rare; acceptable for now.
129
+ * A CAS-on-bump or dialect-specific lock is tracked as follow-up.
130
+ *
131
+ * **`deleteField` cascade.** Migration 041 already declares
132
+ * `ON DELETE CASCADE` on both value tables. The explicit deletes
133
+ * here are defense-in-depth against FK-pragma misconfig and mirror
134
+ * `BylineRepository.delete`'s app-level cascade for the bylines
135
+ * domain.
136
+ *
137
+ * Reserved-slug rejection runs at the API layer (zod) *and* here so
138
+ * non-HTTP callers (seeds, scripts) can't bypass the check.
139
+ */
140
+ export class BylineSchemaRegistry {
141
+ constructor(private db: Kysely<Database>) {}
142
+
143
+ async listFields(): Promise<BylineFieldDefinition[]> {
144
+ const rows = await this.db
145
+ .selectFrom("_emdash_byline_fields")
146
+ .selectAll()
147
+ .orderBy("sort_order", "asc")
148
+ .orderBy("created_at", "asc")
149
+ .execute();
150
+ return rows.map((row) => mapFieldRow(row));
151
+ }
152
+
153
+ async getField(slug: string): Promise<BylineFieldDefinition | null> {
154
+ const row = await this.db
155
+ .selectFrom("_emdash_byline_fields")
156
+ .selectAll()
157
+ .where("slug", "=", slug)
158
+ .executeTakeFirst();
159
+ return row ? mapFieldRow(row) : null;
160
+ }
161
+
162
+ async getFieldById(id: string): Promise<BylineFieldDefinition | null> {
163
+ const row = await this.db
164
+ .selectFrom("_emdash_byline_fields")
165
+ .selectAll()
166
+ .where("id", "=", id)
167
+ .executeTakeFirst();
168
+ return row ? mapFieldRow(row) : null;
169
+ }
170
+
171
+ async createField(input: CreateBylineFieldInput): Promise<BylineFieldDefinition> {
172
+ this.validateSlug(input.slug);
173
+ this.validateLabel(input.label);
174
+ this.validateType(input.type);
175
+ const validation = this.normaliseValidation(input.type, input.validation ?? null);
176
+
177
+ const existing = await this.getField(input.slug);
178
+ if (existing) {
179
+ // Idempotent retry exit — see class JSDoc.
180
+ await this.markVersionClean();
181
+ throw new BylineSchemaError(`Byline field "${input.slug}" already exists`, "FIELD_EXISTS", {
182
+ slug: input.slug,
183
+ });
184
+ }
185
+
186
+ const id = ulid();
187
+ const sortOrder = input.sortOrder ?? (await this.nextSortOrder());
188
+
189
+ await this.markVersionDirty();
190
+ await withTransaction(this.db, async (trx) => {
191
+ await trx
192
+ .insertInto("_emdash_byline_fields")
193
+ .values({
194
+ id,
195
+ slug: input.slug,
196
+ label: input.label,
197
+ type: input.type,
198
+ required: input.required ? 1 : 0,
199
+ translatable: input.translatable === false ? 0 : 1,
200
+ validation: validation ? JSON.stringify(validation) : null,
201
+ sort_order: sortOrder,
202
+ })
203
+ .execute();
204
+ });
205
+ await this.markVersionClean();
206
+
207
+ const created = await this.getFieldById(id);
208
+ if (!created) {
209
+ // Should be unreachable on a working DB — but a typed error
210
+ // beats letting the route returning null on a successful path.
211
+ throw new BylineSchemaError("Failed to load created field", "FIELD_NOT_FOUND", {
212
+ id,
213
+ });
214
+ }
215
+ return created;
216
+ }
217
+
218
+ async updateField(slug: string, input: UpdateBylineFieldInput): Promise<BylineFieldDefinition> {
219
+ const field = await this.getField(slug);
220
+ if (!field) {
221
+ // Idempotent retry exit — see class JSDoc.
222
+ await this.markVersionClean();
223
+ throw new BylineSchemaError(`Byline field "${slug}" not found`, "FIELD_NOT_FOUND", {
224
+ slug,
225
+ });
226
+ }
227
+
228
+ const updates: Partial<{
229
+ label: string;
230
+ required: number;
231
+ translatable: number;
232
+ validation: string | null;
233
+ sort_order: number;
234
+ updated_at: string;
235
+ }> = {};
236
+
237
+ if (input.label !== undefined) {
238
+ this.validateLabel(input.label);
239
+ updates.label = input.label;
240
+ }
241
+
242
+ if (input.required !== undefined) {
243
+ updates.required = input.required ? 1 : 0;
244
+ }
245
+
246
+ if (input.validation !== undefined) {
247
+ // Validation payload is normalised against the *current* field
248
+ // type — `type` is not updatable, so it's safe to use `field.type`.
249
+ const validation = this.normaliseValidation(field.type, input.validation);
250
+ updates.validation = validation ? JSON.stringify(validation) : null;
251
+ }
252
+
253
+ if (input.translatable !== undefined && input.translatable !== field.translatable) {
254
+ // Flipping `translatable` would orphan any values already stored
255
+ // in the table matching the *current* flag. Reject when any
256
+ // value rows reference this field — admins can delete the field
257
+ // (cascading the values) and re-create it with the new flag if
258
+ // they want a clean re-start. Migrating values across tables is
259
+ // out of scope (Discussion #1174 doesn't authorise it).
260
+ const usage = await this.countFieldValues(field.id);
261
+ if (usage > 0) {
262
+ throw new BylineSchemaError(
263
+ `Cannot change "translatable" on field "${slug}" while ${usage} value row(s) exist. ` +
264
+ `Delete the values (or the field) and re-create with the new setting.`,
265
+ "TRANSLATABLE_LOCKED",
266
+ { slug, valueCount: usage },
267
+ );
268
+ }
269
+ updates.translatable = input.translatable ? 1 : 0;
270
+ }
271
+
272
+ if (input.sortOrder !== undefined) {
273
+ updates.sort_order = input.sortOrder;
274
+ }
275
+
276
+ if (Object.keys(updates).length === 0) {
277
+ // No-op update — still advance the clean marker in case
278
+ // we're recovering a crashed prior attempt.
279
+ await this.markVersionClean();
280
+ return field;
281
+ }
282
+
283
+ updates.updated_at = new Date().toISOString();
284
+
285
+ await this.markVersionDirty();
286
+ await withTransaction(this.db, async (trx) => {
287
+ await trx
288
+ .updateTable("_emdash_byline_fields")
289
+ .set(updates)
290
+ .where("id", "=", field.id)
291
+ .execute();
292
+ });
293
+ await this.markVersionClean();
294
+
295
+ const updated = await this.getFieldById(field.id);
296
+ if (!updated) {
297
+ throw new BylineSchemaError("Failed to load updated field", "FIELD_NOT_FOUND", {
298
+ slug,
299
+ });
300
+ }
301
+ return updated;
302
+ }
303
+
304
+ async deleteField(slug: string): Promise<void> {
305
+ const field = await this.getField(slug);
306
+ if (!field) {
307
+ // Idempotent retry exit — see class JSDoc.
308
+ await this.markVersionClean();
309
+ throw new BylineSchemaError(`Byline field "${slug}" not found`, "FIELD_NOT_FOUND", {
310
+ slug,
311
+ });
312
+ }
313
+
314
+ // Delete order matters on D1 (no tx): value rows first, definition
315
+ // row last, so a crash leaves the definition recoverable on retry
316
+ // rather than orphan values pointing at a vanished id.
317
+ await this.markVersionDirty();
318
+ await withTransaction(this.db, async (trx) => {
319
+ await trx
320
+ .deleteFrom("_emdash_byline_field_values")
321
+ .where("field_id", "=", field.id)
322
+ .execute();
323
+ await trx
324
+ .deleteFrom("_emdash_byline_field_group_values")
325
+ .where("field_id", "=", field.id)
326
+ .execute();
327
+ await trx.deleteFrom("_emdash_byline_fields").where("id", "=", field.id).execute();
328
+ });
329
+ await this.markVersionClean();
330
+ }
331
+
332
+ /**
333
+ * Reorder fields by slug. The input must be the *exact* set of
334
+ * currently registered slugs — no adds, no drops, no duplicates. This
335
+ * keeps the operation invertible (any reorder is followed by a reverse
336
+ * reorder) and removes a class of "did I forget a field?" bugs at the
337
+ * API layer.
338
+ */
339
+ async reorderFields(slugs: string[]): Promise<void> {
340
+ if (new Set(slugs).size !== slugs.length) {
341
+ throw new BylineSchemaError("Reorder input contains duplicate slugs", "REORDER_MISMATCH", {
342
+ slugs,
343
+ });
344
+ }
345
+
346
+ const registered = await this.listFields();
347
+ const registeredSlugs = registered.map((f) => f.slug).toSorted();
348
+ const inputSlugs = slugs.toSorted();
349
+
350
+ if (registeredSlugs.length !== inputSlugs.length) {
351
+ throw new BylineSchemaError(
352
+ `Reorder input has ${inputSlugs.length} slug(s); ${registeredSlugs.length} registered`,
353
+ "REORDER_MISMATCH",
354
+ { registered: registeredSlugs, input: inputSlugs },
355
+ );
356
+ }
357
+ for (let i = 0; i < registeredSlugs.length; i++) {
358
+ if (registeredSlugs[i] !== inputSlugs[i]) {
359
+ throw new BylineSchemaError(
360
+ "Reorder input does not match the registered field set",
361
+ "REORDER_MISMATCH",
362
+ { registered: registeredSlugs, input: inputSlugs },
363
+ );
364
+ }
365
+ }
366
+
367
+ const now = new Date().toISOString();
368
+ await this.markVersionDirty();
369
+ await withTransaction(this.db, async (trx) => {
370
+ for (let i = 0; i < slugs.length; i++) {
371
+ const slug = slugs[i];
372
+ if (slug === undefined) continue;
373
+ await trx
374
+ .updateTable("_emdash_byline_fields")
375
+ .set({ sort_order: i, updated_at: now })
376
+ .where("slug", "=", slug)
377
+ .execute();
378
+ }
379
+ });
380
+ await this.markVersionClean();
381
+ }
382
+
383
+ /**
384
+ * Per-table usage counts for a field, plus the sum. Backs the
385
+ * destructive-delete confirm dialog in the admin UI (Phase 5).
386
+ *
387
+ * Both counts are surfaced separately for diagnostic value: a
388
+ * non-zero count on the table that doesn't match the field's current
389
+ * `translatable` flag indicates historical drift (e.g. a flip from
390
+ * an older code path). Today the registry rejects such flips with
391
+ * `TRANSLATABLE_LOCKED`, so any drift originates pre-Phase-2.
392
+ *
393
+ * Throws `FIELD_NOT_FOUND` when the slug doesn't resolve — callers
394
+ * shouldn't get back zero counts for a missing field.
395
+ */
396
+ async getFieldUsage(slug: string): Promise<{
397
+ translatableValueCount: number;
398
+ groupValueCount: number;
399
+ totalAffectedRows: number;
400
+ }> {
401
+ const field = await this.getField(slug);
402
+ if (!field) {
403
+ throw new BylineSchemaError(`Byline field "${slug}" not found`, "FIELD_NOT_FOUND", {
404
+ slug,
405
+ });
406
+ }
407
+ const tr = await this.db
408
+ .selectFrom("_emdash_byline_field_values")
409
+ .select(({ fn }) => [fn.count<number>("field_id").as("count")])
410
+ .where("field_id", "=", field.id)
411
+ .executeTakeFirst();
412
+ const grp = await this.db
413
+ .selectFrom("_emdash_byline_field_group_values")
414
+ .select(({ fn }) => [fn.count<number>("field_id").as("count")])
415
+ .where("field_id", "=", field.id)
416
+ .executeTakeFirst();
417
+ const translatableValueCount = Number(tr?.count ?? 0);
418
+ const groupValueCount = Number(grp?.count ?? 0);
419
+ return {
420
+ translatableValueCount,
421
+ groupValueCount,
422
+ totalAffectedRows: translatableValueCount + groupValueCount,
423
+ };
424
+ }
425
+
426
+ /**
427
+ * Read the persisted version counter. Used by the field-defs cache
428
+ * (Phase 3) to detect invalidation. Returns `0` when the row is
429
+ * missing — covers the "tests that didn't run migration 041" case
430
+ * without throwing.
431
+ */
432
+ async getVersion(): Promise<number> {
433
+ const row = await this.db
434
+ .selectFrom("options")
435
+ .select("value")
436
+ .where("name", "=", VERSION_KEY)
437
+ .executeTakeFirst();
438
+ if (!row) return 0;
439
+ const parsed = Number.parseInt(row.value, 10);
440
+ return Number.isFinite(parsed) ? parsed : 0;
441
+ }
442
+
443
+ // ============================================
444
+ // Private helpers
445
+ // ============================================
446
+
447
+ /**
448
+ * Force the version counter to an odd integer ("dirty"). Idempotent
449
+ * on odd so a crashed prior attempt can't invert parity. Upsert (not
450
+ * UPDATE) so a missing row still flips parity — `getVersion` returns
451
+ * 0 on missing, which is even, so a bare UPDATE would leave the
452
+ * cache pinned on a stale snapshot. See the class JSDoc.
453
+ *
454
+ * `options.value` qualified: PG's `ON CONFLICT DO UPDATE` puts both
455
+ * the target and `EXCLUDED.value` in scope; bare `value` is ambiguous.
456
+ */
457
+ private async markVersionDirty(): Promise<void> {
458
+ await sql`
459
+ INSERT INTO options (name, value)
460
+ VALUES (${VERSION_KEY}, '1')
461
+ ON CONFLICT(name) DO UPDATE SET value = CASE
462
+ WHEN CAST(options.value AS INTEGER) % 2 = 0
463
+ THEN CAST(CAST(options.value AS INTEGER) + 1 AS TEXT)
464
+ ELSE options.value
465
+ END
466
+ `.execute(this.db);
467
+ }
468
+
469
+ /**
470
+ * Force the version counter to a **new** even integer (+2 from even,
471
+ * +1 from odd). Always-advance — never a no-op — so two concurrent
472
+ * mutators can't collapse on the same even key and pin a stale cache
473
+ * snapshot. See the class JSDoc for the concurrent-collapse rationale.
474
+ *
475
+ * `options.value` qualified — see `markVersionDirty`.
476
+ */
477
+ private async markVersionClean(): Promise<void> {
478
+ await sql`
479
+ INSERT INTO options (name, value)
480
+ VALUES (${VERSION_KEY}, '2')
481
+ ON CONFLICT(name) DO UPDATE SET value = CASE
482
+ WHEN CAST(options.value AS INTEGER) % 2 = 0
483
+ THEN CAST(CAST(options.value AS INTEGER) + 2 AS TEXT)
484
+ ELSE CAST(CAST(options.value AS INTEGER) + 1 AS TEXT)
485
+ END
486
+ `.execute(this.db);
487
+ }
488
+
489
+ private async nextSortOrder(): Promise<number> {
490
+ const row = await this.db
491
+ .selectFrom("_emdash_byline_fields")
492
+ .select(({ fn }) => [fn.max<number | null>("sort_order").as("max")])
493
+ .executeTakeFirst();
494
+ const max = row?.max ?? null;
495
+ return max === null ? 0 : max + 1;
496
+ }
497
+
498
+ private async countFieldValues(fieldId: string): Promise<number> {
499
+ // Count both per-locale and group-shared values. A field can only
500
+ // store in one table at a time (translatable picks), but historic
501
+ // rows might exist in the other if a prior version of this code
502
+ // allowed the flip — count both to be safe.
503
+ const tr = await this.db
504
+ .selectFrom("_emdash_byline_field_values")
505
+ .select(({ fn }) => [fn.count<number>("field_id").as("count")])
506
+ .where("field_id", "=", fieldId)
507
+ .executeTakeFirst();
508
+ const grp = await this.db
509
+ .selectFrom("_emdash_byline_field_group_values")
510
+ .select(({ fn }) => [fn.count<number>("field_id").as("count")])
511
+ .where("field_id", "=", fieldId)
512
+ .executeTakeFirst();
513
+ return Number(tr?.count ?? 0) + Number(grp?.count ?? 0);
514
+ }
515
+
516
+ private validateSlug(slug: string): void {
517
+ if (!slug || typeof slug !== "string") {
518
+ throw new BylineSchemaError("Byline field slug is required", "INVALID_SLUG", { slug });
519
+ }
520
+ if (slug.length > MAX_SLUG_LENGTH) {
521
+ throw new BylineSchemaError(
522
+ `Byline field slug must be ${MAX_SLUG_LENGTH} characters or less`,
523
+ "INVALID_SLUG",
524
+ { slug },
525
+ );
526
+ }
527
+ // `validateIdentifier` enforces /^[a-z][a-z0-9_]*$/ — rejects
528
+ // camelCase, PascalCase, hyphens, leading digits, and identifiers
529
+ // over 128 characters. We hit the 63-char cap above first, which
530
+ // matches the content-collection slug cap.
531
+ try {
532
+ validateIdentifier(slug, "byline field slug");
533
+ } catch (error) {
534
+ throw new BylineSchemaError(
535
+ error instanceof Error ? error.message : "Invalid byline field slug",
536
+ "INVALID_SLUG",
537
+ { slug },
538
+ );
539
+ }
540
+ if (RESERVED_SET.has(slug)) {
541
+ throw new BylineSchemaError(`Byline field slug "${slug}" is reserved`, "RESERVED_SLUG", {
542
+ slug,
543
+ });
544
+ }
545
+ }
546
+
547
+ private validateLabel(label: string): void {
548
+ if (!label || typeof label !== "string") {
549
+ throw new BylineSchemaError("Byline field label is required", "INVALID_LABEL", {
550
+ label,
551
+ });
552
+ }
553
+ if (label.length > MAX_LABEL_LENGTH) {
554
+ throw new BylineSchemaError(
555
+ `Byline field label must be ${MAX_LABEL_LENGTH} characters or less`,
556
+ "INVALID_LABEL",
557
+ { length: label.length },
558
+ );
559
+ }
560
+ }
561
+
562
+ private validateType(type: BylineFieldType): void {
563
+ if (!TYPE_SET.has(type)) {
564
+ throw new BylineSchemaError(
565
+ `Byline field type "${type}" is not supported. Valid types: ${[...TYPE_SET].join(", ")}`,
566
+ "INVALID_TYPE",
567
+ { type },
568
+ );
569
+ }
570
+ }
571
+
572
+ /**
573
+ * Normalise + validate a validation payload for a given field type.
574
+ *
575
+ * - `select`: `options` is required, must be a non-empty array of unique
576
+ * non-empty strings, capped at `MAX_SELECT_OPTIONS`.
577
+ * - any other type: `options` is silently dropped if present (a future
578
+ * field type might use it, but v1 doesn't).
579
+ *
580
+ * Returns `null` when the resulting validation object is empty, so the
581
+ * storage column stays NULL rather than carrying `'{}'`.
582
+ */
583
+ private normaliseValidation(
584
+ type: BylineFieldType,
585
+ validation: BylineFieldValidation | null,
586
+ ): BylineFieldValidation | null {
587
+ if (type === "select") {
588
+ const options = validation?.options;
589
+ if (!Array.isArray(options) || options.length === 0) {
590
+ throw new BylineSchemaError(
591
+ `Byline field of type "select" requires non-empty "validation.options"`,
592
+ "INVALID_VALIDATION",
593
+ { type },
594
+ );
595
+ }
596
+ if (options.length > MAX_SELECT_OPTIONS) {
597
+ throw new BylineSchemaError(
598
+ `Byline field "select" cannot have more than ${MAX_SELECT_OPTIONS} options`,
599
+ "INVALID_VALIDATION",
600
+ { count: options.length },
601
+ );
602
+ }
603
+ const seen = new Set<string>();
604
+ for (const option of options) {
605
+ if (typeof option !== "string" || option.length === 0) {
606
+ throw new BylineSchemaError(
607
+ `Byline field "select" options must be non-empty strings`,
608
+ "INVALID_VALIDATION",
609
+ { option },
610
+ );
611
+ }
612
+ if (seen.has(option)) {
613
+ throw new BylineSchemaError(
614
+ `Byline field "select" options must be unique`,
615
+ "INVALID_VALIDATION",
616
+ { option },
617
+ );
618
+ }
619
+ seen.add(option);
620
+ }
621
+ return { options };
622
+ }
623
+
624
+ if (validation == null) return null;
625
+ // Non-select: drop `options` if present. Strip nothing else — future
626
+ // field types might extend the shape and we don't want to lose
627
+ // payload silently. Today's `BylineFieldValidation` is `{ options? }`
628
+ // only, so this branch is a pass-through; left explicit for clarity.
629
+ const { options: _drop, ...rest } = validation;
630
+ return Object.keys(rest).length === 0 ? null : (rest as BylineFieldValidation);
631
+ }
632
+ }
633
+
634
+ function mapFieldRow(row: {
635
+ id: string;
636
+ slug: string;
637
+ label: string;
638
+ type: string;
639
+ required: number;
640
+ translatable: number;
641
+ validation: string | null;
642
+ sort_order: number;
643
+ created_at: string;
644
+ updated_at: string;
645
+ }): BylineFieldDefinition {
646
+ return {
647
+ id: row.id,
648
+ slug: row.slug,
649
+ label: row.label,
650
+ // `type` is stored as TEXT but `createField` rejects anything outside
651
+ // `BYLINE_FIELD_TYPES` before inserting. The assertion narrows on
652
+ // that write-time guarantee.
653
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- validated at write
654
+ type: row.type as BylineFieldType,
655
+ required: row.required === 1,
656
+ translatable: row.translatable === 1,
657
+ // `validation` is JSON-encoded `BylineFieldValidation | null`, written
658
+ // only through `normaliseValidation`. The cast matches the
659
+ // `JSON.parse(...) as T` pattern in `OptionsRepository`.
660
+ // eslint-disable-next-line typescript/no-unsafe-type-assertion -- validated at write
661
+ validation: row.validation ? (JSON.parse(row.validation) as BylineFieldValidation) : null,
662
+ sortOrder: row.sort_order,
663
+ createdAt: row.created_at,
664
+ updatedAt: row.updated_at,
665
+ };
666
+ }
667
+
668
+ // Re-export the table type for callers that want to spell it explicitly.
669
+ // Most callers should rely on the Database interface; this is convenience
670
+ // for tests that hand-roll Kysely queries.
671
+ export type { BylineFieldTable };
@@ -772,6 +772,20 @@ export class SchemaRegistry {
772
772
  CREATE INDEX ${sql.ref(`idx_${tableName}_deleted_published_id`)}
773
773
  ON ${sql.ref(tableName)} (deleted_at, published_at DESC, id DESC)
774
774
  `.execute(conn);
775
+
776
+ // Locale-aware composite indexes for i18n content lists (see migration 041).
777
+ // Short `loc_upd`/`loc_crt` suffix keeps the updated/created discriminator
778
+ // inside Postgres's 63-byte identifier limit for long slugs; keep these
779
+ // names identical to migration 041.
780
+ await sql`
781
+ CREATE INDEX ${sql.ref(`idx_${tableName}_loc_upd`)}
782
+ ON ${sql.ref(tableName)} (deleted_at, locale, updated_at DESC, id DESC)
783
+ `.execute(conn);
784
+
785
+ await sql`
786
+ CREATE INDEX ${sql.ref(`idx_${tableName}_loc_crt`)}
787
+ ON ${sql.ref(tableName)} (deleted_at, locale, created_at DESC, id DESC)
788
+ `.execute(conn);
775
789
  }
776
790
 
777
791
  /**