emdash 0.30.0 → 0.31.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 (387) hide show
  1. package/dist/api/route-utils.mjs +13 -13
  2. package/dist/api/schemas/index.d.mts +1 -1
  3. package/dist/api/schemas/index.mjs +3 -3
  4. package/dist/{api-b8WIiGU4.mjs → api-EibxGP-V.mjs} +23 -23
  5. package/dist/api-EibxGP-V.mjs.map +1 -0
  6. package/dist/{apply-CmIJK9j8.mjs → apply-fGHCAwWN.mjs} +26 -25
  7. package/dist/apply-fGHCAwWN.mjs.map +1 -0
  8. package/dist/astro/index.d.mts +2 -2
  9. package/dist/astro/index.mjs +1 -1
  10. package/dist/astro/middleware/auth.d.mts +2 -2
  11. package/dist/astro/middleware/auth.mjs +2 -2
  12. package/dist/astro/middleware/redirect.mjs +5 -5
  13. package/dist/astro/middleware/request-context.mjs +1 -1
  14. package/dist/astro/middleware.d.mts +2 -2
  15. package/dist/astro/middleware.mjs +139 -68
  16. package/dist/astro/middleware.mjs.map +1 -1
  17. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
  18. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
  19. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  20. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  21. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
  22. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +7 -7
  23. package/dist/astro/routes/api/admin/byline-fields/index.mjs +7 -7
  24. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +7 -7
  25. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +14 -14
  26. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +14 -14
  27. package/dist/astro/routes/api/admin/bylines/index.d.mts.map +1 -1
  28. package/dist/astro/routes/api/admin/bylines/index.mjs +18 -17
  29. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  30. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +10 -10
  31. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  32. package/dist/astro/routes/api/admin/comments/bulk.mjs +9 -9
  33. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  34. package/dist/astro/routes/api/admin/comments/index.mjs +9 -9
  35. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  36. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  37. package/dist/astro/routes/api/admin/media-usage/repair.mjs +10 -10
  38. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  39. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  40. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +34 -34
  41. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +34 -34
  42. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +34 -34
  43. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +4 -4
  44. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +34 -34
  45. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +34 -34
  46. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +34 -34
  47. package/dist/astro/routes/api/admin/plugins/index.mjs +34 -34
  48. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  49. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +34 -34
  50. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +34 -34
  51. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +34 -34
  52. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +34 -34
  53. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +35 -35
  54. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +34 -34
  55. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +35 -35
  56. package/dist/astro/routes/api/admin/plugins/updates.mjs +34 -34
  57. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +34 -34
  58. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  59. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +34 -34
  60. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  61. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  62. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  63. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  64. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  65. package/dist/astro/routes/api/auth/dev-bypass.mjs +4 -4
  66. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  67. package/dist/astro/routes/api/auth/invite/complete.mjs +6 -6
  68. package/dist/astro/routes/api/auth/invite/index.mjs +6 -6
  69. package/dist/astro/routes/api/auth/invite/register-options.mjs +6 -6
  70. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  71. package/dist/astro/routes/api/auth/magic-link/send.mjs +7 -7
  72. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  73. package/dist/astro/routes/api/auth/me.mjs +7 -7
  74. package/dist/astro/routes/api/auth/mode.mjs +2 -2
  75. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  76. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  77. package/dist/astro/routes/api/auth/passkey/options.mjs +7 -7
  78. package/dist/astro/routes/api/auth/passkey/register/options.mjs +6 -6
  79. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +6 -6
  80. package/dist/astro/routes/api/auth/passkey/verify.mjs +6 -6
  81. package/dist/astro/routes/api/auth/signup/complete.mjs +6 -6
  82. package/dist/astro/routes/api/auth/signup/request.mjs +7 -7
  83. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  84. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +10 -10
  85. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +10 -10
  86. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  87. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  88. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  89. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  90. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +9 -9
  91. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +7 -7
  92. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +14 -14
  93. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +14 -14
  94. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  95. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  96. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +7 -7
  97. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +13 -13
  98. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  99. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  100. package/dist/astro/routes/api/content/_collection_/_id_.mjs +7 -7
  101. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  102. package/dist/astro/routes/api/content/_collection_/index.mjs +7 -7
  103. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  104. package/dist/astro/routes/api/dashboard.mjs +7 -7
  105. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  106. package/dist/astro/routes/api/import/probe.mjs +12 -12
  107. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  108. package/dist/astro/routes/api/import/wordpress/execute.d.mts +2 -2
  109. package/dist/astro/routes/api/import/wordpress/execute.mjs +11 -11
  110. package/dist/astro/routes/api/import/wordpress/media.d.mts +2 -2
  111. package/dist/astro/routes/api/import/wordpress/media.mjs +7 -7
  112. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  113. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +2 -2
  114. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +9 -9
  115. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +12 -12
  116. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +2 -2
  117. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +19 -19
  118. package/dist/astro/routes/api/manifest.mjs +3 -3
  119. package/dist/astro/routes/api/mcp.mjs +39 -39
  120. package/dist/astro/routes/api/media/_id_/confirm.mjs +7 -7
  121. package/dist/astro/routes/api/media/_id_/usage.mjs +11 -11
  122. package/dist/astro/routes/api/media/_id_.mjs +10 -10
  123. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  124. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  125. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  126. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  127. package/dist/astro/routes/api/media/upload-url.mjs +7 -7
  128. package/dist/astro/routes/api/media.mjs +10 -10
  129. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +8 -8
  130. package/dist/astro/routes/api/menus/_name_/items.mjs +8 -8
  131. package/dist/astro/routes/api/menus/_name_/reorder.mjs +8 -8
  132. package/dist/astro/routes/api/menus/_name_/translations.mjs +8 -8
  133. package/dist/astro/routes/api/menus/_name_.mjs +8 -8
  134. package/dist/astro/routes/api/menus/index.mjs +8 -8
  135. package/dist/astro/routes/api/oauth/authorize.d.mts.map +1 -1
  136. package/dist/astro/routes/api/oauth/authorize.mjs +23 -9
  137. package/dist/astro/routes/api/oauth/authorize.mjs.map +1 -1
  138. package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
  139. package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
  140. package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
  141. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  142. package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
  143. package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
  144. package/dist/astro/routes/api/oauth/token.mjs +2 -2
  145. package/dist/astro/routes/api/openapi.json.mjs +4 -4
  146. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  147. package/dist/astro/routes/api/redirects/404s/index.mjs +10 -10
  148. package/dist/astro/routes/api/redirects/404s/summary.mjs +10 -10
  149. package/dist/astro/routes/api/redirects/_id_.mjs +11 -11
  150. package/dist/astro/routes/api/redirects/index.mjs +11 -11
  151. package/dist/astro/routes/api/relations/_id_/index.mjs +14 -14
  152. package/dist/astro/routes/api/relations/_id_/translations.mjs +10 -10
  153. package/dist/astro/routes/api/relations/index.mjs +14 -14
  154. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  155. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  156. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +34 -34
  157. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +34 -34
  158. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +34 -34
  159. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +34 -34
  160. package/dist/astro/routes/api/schema/collections/index.mjs +34 -34
  161. package/dist/astro/routes/api/schema/index.mjs +8 -8
  162. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +34 -34
  163. package/dist/astro/routes/api/schema/orphans/index.mjs +34 -34
  164. package/dist/astro/routes/api/search/enable.mjs +11 -11
  165. package/dist/astro/routes/api/search/index.mjs +10 -10
  166. package/dist/astro/routes/api/search/rebuild.mjs +11 -11
  167. package/dist/astro/routes/api/search/stats.mjs +7 -7
  168. package/dist/astro/routes/api/search/suggest.mjs +10 -10
  169. package/dist/astro/routes/api/sections/_slug_.mjs +10 -10
  170. package/dist/astro/routes/api/sections/index.mjs +10 -10
  171. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +4 -4
  172. package/dist/astro/routes/api/settings/backups/archives/index.mjs +4 -4
  173. package/dist/astro/routes/api/settings/backups/export.mjs +4 -4
  174. package/dist/astro/routes/api/settings/backups/index.mjs +5 -5
  175. package/dist/astro/routes/api/settings/email.mjs +4 -4
  176. package/dist/astro/routes/api/settings.mjs +12 -12
  177. package/dist/astro/routes/api/setup/admin-verify.mjs +6 -6
  178. package/dist/astro/routes/api/setup/admin.mjs +6 -6
  179. package/dist/astro/routes/api/setup/dev-bypass.mjs +18 -18
  180. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  181. package/dist/astro/routes/api/setup/index.mjs +22 -22
  182. package/dist/astro/routes/api/setup/status.mjs +3 -3
  183. package/dist/astro/routes/api/snapshot.mjs +4 -4
  184. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +13 -13
  185. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +13 -13
  186. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +13 -13
  187. package/dist/astro/routes/api/taxonomies/index.mjs +13 -13
  188. package/dist/astro/routes/api/themes/preview.mjs +4 -4
  189. package/dist/astro/routes/api/typegen.mjs +5 -5
  190. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +7 -7
  191. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +10 -10
  192. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +10 -10
  193. package/dist/astro/routes/api/widget-areas/_name_.mjs +6 -6
  194. package/dist/astro/routes/api/widget-areas/index.mjs +10 -10
  195. package/dist/astro/routes/api/widget-components.mjs +2 -2
  196. package/dist/astro/routes/robots.txt.mjs +5 -5
  197. package/dist/astro/routes/sitemap-_collection_.xml.mjs +7 -7
  198. package/dist/astro/routes/sitemap.xml.mjs +5 -5
  199. package/dist/astro/types.d.mts +2 -2
  200. package/dist/{authorize-WxsiePzy.mjs → authorize-6WPZa1xZ.mjs} +2 -2
  201. package/dist/{authorize-WxsiePzy.mjs.map → authorize-6WPZa1xZ.mjs.map} +1 -1
  202. package/dist/{backup-S8kFWtwD.mjs → backup-Qr9PaGUo.mjs} +2 -2
  203. package/dist/{backup-S8kFWtwD.mjs.map → backup-Qr9PaGUo.mjs.map} +1 -1
  204. package/dist/{byline-XEjchwzZ.mjs → byline-BTOxUlFF.mjs} +6 -6
  205. package/dist/{byline-XEjchwzZ.mjs.map → byline-BTOxUlFF.mjs.map} +1 -1
  206. package/dist/{bylines-czseViYo.mjs → bylines-FyZu6TbJ.mjs} +6 -6
  207. package/dist/{bylines-czseViYo.mjs.map → bylines-FyZu6TbJ.mjs.map} +1 -1
  208. package/dist/{bylines-BJbT4gKS.mjs → bylines-oVN0iPwX.mjs} +12 -8
  209. package/dist/bylines-oVN0iPwX.mjs.map +1 -0
  210. package/dist/{cache-CGCd6AVM.mjs → cache-DDwy3JDW.mjs} +2 -2
  211. package/dist/{cache-CGCd6AVM.mjs.map → cache-DDwy3JDW.mjs.map} +1 -1
  212. package/dist/{chunks-BxXyunY-.mjs → chunks-Bq-5DPrD.mjs} +2 -2
  213. package/dist/{chunks-BxXyunY-.mjs.map → chunks-Bq-5DPrD.mjs.map} +1 -1
  214. package/dist/cli/index.mjs +18 -18
  215. package/dist/{comment-DPT0WKyd.mjs → comment-B9cUud8H.mjs} +2 -2
  216. package/dist/{comment-DPT0WKyd.mjs.map → comment-B9cUud8H.mjs.map} +1 -1
  217. package/dist/{comment-reaction-C65MldIB.mjs → comment-reaction-VUXO981F.mjs} +2 -2
  218. package/dist/{comment-reaction-C65MldIB.mjs.map → comment-reaction-VUXO981F.mjs.map} +1 -1
  219. package/dist/{comments-Bz6sCbgD.mjs → comments-PXj1E9Si.mjs} +3 -3
  220. package/dist/{comments-Bz6sCbgD.mjs.map → comments-PXj1E9Si.mjs.map} +1 -1
  221. package/dist/{config-CVssduLe.mjs → config-B6b_BobR.mjs} +6 -2
  222. package/dist/config-B6b_BobR.mjs.map +1 -0
  223. package/dist/{content-Ci04z2z-.mjs → content-BC43pICM.mjs} +4 -4
  224. package/dist/{content-Ci04z2z-.mjs.map → content-BC43pICM.mjs.map} +1 -1
  225. package/dist/{content-refresh-D4khvC0R.mjs → content-refresh-CPMSauD3.mjs} +4 -4
  226. package/dist/{content-refresh-D4khvC0R.mjs.map → content-refresh-CPMSauD3.mjs.map} +1 -1
  227. package/dist/{context-B6hc7zJL.mjs → context-DOurRIrb.mjs} +10 -10
  228. package/dist/{context-B6hc7zJL.mjs.map → context-DOurRIrb.mjs.map} +1 -1
  229. package/dist/{dashboard-C5NkXFbi.mjs → dashboard-CBm4zhc3.mjs} +4 -4
  230. package/dist/{dashboard-C5NkXFbi.mjs.map → dashboard-CBm4zhc3.mjs.map} +1 -1
  231. package/dist/database/pg-migration-lock.d.mts.map +1 -1
  232. package/dist/database/pg-migration-lock.mjs +1 -4
  233. package/dist/database/pg-migration-lock.mjs.map +1 -1
  234. package/dist/db/index.mjs +2 -2
  235. package/dist/{dialect-helpers-HxEQGl_1.mjs → dialect-helpers-BtOwo2us.mjs} +4 -4
  236. package/dist/dialect-helpers-BtOwo2us.mjs.map +1 -0
  237. package/dist/{error-CEGF6UZb.mjs → error-DJ98TRbQ.mjs} +2 -2
  238. package/dist/{error-CEGF6UZb.mjs.map → error-DJ98TRbQ.mjs.map} +1 -1
  239. package/dist/{field-defs-cache-DvmlgP-D.mjs → field-defs-cache-KfNJ_qLQ.mjs} +2 -2
  240. package/dist/{field-defs-cache-DvmlgP-D.mjs.map → field-defs-cache-KfNJ_qLQ.mjs.map} +1 -1
  241. package/dist/{fts-manager-DzqIBrrW.mjs → fts-manager-hyCU9OOp.mjs} +3 -3
  242. package/dist/{fts-manager-DzqIBrrW.mjs.map → fts-manager-hyCU9OOp.mjs.map} +1 -1
  243. package/dist/{hreflang-db2Tpb2o.mjs → hreflang-CbYeCzl9.mjs} +7 -7
  244. package/dist/{hreflang-db2Tpb2o.mjs.map → hreflang-CbYeCzl9.mjs.map} +1 -1
  245. package/dist/{import-Dmkm8S1W.mjs → import-oDYCgNYQ.mjs} +2 -2
  246. package/dist/{import-Dmkm8S1W.mjs.map → import-oDYCgNYQ.mjs.map} +1 -1
  247. package/dist/{index-wYnC6u4o.d.mts → index-BcEH_FfS.d.mts} +33 -2
  248. package/dist/index-BcEH_FfS.d.mts.map +1 -0
  249. package/dist/index.d.mts +2 -2
  250. package/dist/index.mjs +48 -48
  251. package/dist/{load-Cx27ki1l.mjs → load-Dg5gtWma.mjs} +2 -2
  252. package/dist/{load-Cx27ki1l.mjs.map → load-Dg5gtWma.mjs.map} +1 -1
  253. package/dist/{loader-Be3ouI5L.mjs → loader-D4rigvjR.mjs} +4 -4
  254. package/dist/{loader-Be3ouI5L.mjs.map → loader-D4rigvjR.mjs.map} +1 -1
  255. package/dist/media/local-runtime.d.mts +2 -2
  256. package/dist/media/local-runtime.mjs +5 -5
  257. package/dist/{media-kIV1IxFf.mjs → media-BJ6SRY3g.mjs} +8 -4
  258. package/dist/media-BJ6SRY3g.mjs.map +1 -0
  259. package/dist/{media-BjhhENaJ.mjs → media-its1WLYv.mjs} +2 -2
  260. package/dist/{media-BjhhENaJ.mjs.map → media-its1WLYv.mjs.map} +1 -1
  261. package/dist/{media-upload-D2wk3EIt.mjs → media-upload-DNu3QySc.mjs} +4 -4
  262. package/dist/{media-upload-D2wk3EIt.mjs.map → media-upload-DNu3QySc.mjs.map} +1 -1
  263. package/dist/{media-usage-CljdO1mc.mjs → media-usage-DPQ3K4xB.mjs} +6 -6
  264. package/dist/{media-usage-CljdO1mc.mjs.map → media-usage-DPQ3K4xB.mjs.map} +1 -1
  265. package/dist/{menus-CZyG6rvx.mjs → menus-BMGzWyWH.mjs} +15 -8
  266. package/dist/menus-BMGzWyWH.mjs.map +1 -0
  267. package/dist/{menus-BcyElFtI.mjs → menus-CPf_SW7u.mjs} +86 -8
  268. package/dist/menus-CPf_SW7u.mjs.map +1 -0
  269. package/dist/{parse-C_-6klII.mjs → parse-53zyxlpv.mjs} +2 -2
  270. package/dist/{parse-C_-6klII.mjs.map → parse-53zyxlpv.mjs.map} +1 -1
  271. package/dist/plugin-utils.d.mts +2 -2
  272. package/dist/plugins/adapt-sandbox-entry.d.mts +2 -2
  273. package/dist/{query-DR73ZNfm.mjs → query-BbJQ-dGg.mjs} +14 -14
  274. package/dist/{query-DR73ZNfm.mjs.map → query-BbJQ-dGg.mjs.map} +1 -1
  275. package/dist/{query-Di7DOmPV.mjs → query-DdFGHzQF.mjs} +3 -3
  276. package/dist/{query-Di7DOmPV.mjs.map → query-DdFGHzQF.mjs.map} +1 -1
  277. package/dist/{rate-limit-CMj83JNW.mjs → rate-limit-D3IEiUi2.mjs} +2 -2
  278. package/dist/{rate-limit-CMj83JNW.mjs.map → rate-limit-D3IEiUi2.mjs.map} +1 -1
  279. package/dist/{redirect-CgLPYflR.mjs → redirect-BoUac4NT.mjs} +3 -3
  280. package/dist/{redirect-CgLPYflR.mjs.map → redirect-BoUac4NT.mjs.map} +1 -1
  281. package/dist/{redirects-B7t9bKsL.mjs → redirects-ChbpqKiS.mjs} +3 -3
  282. package/dist/{redirects-B7t9bKsL.mjs.map → redirects-ChbpqKiS.mjs.map} +1 -1
  283. package/dist/{redirects-BBq3n2Rj.mjs → redirects-D457qwt0.mjs} +8 -8
  284. package/dist/redirects-D457qwt0.mjs.map +1 -0
  285. package/dist/{registry-FV15nLge.mjs → registry-y21kjhfG.mjs} +6 -6
  286. package/dist/{registry-FV15nLge.mjs.map → registry-y21kjhfG.mjs.map} +1 -1
  287. package/dist/{relation-9M9j_UWo.mjs → relation-Du0r04E0.mjs} +3 -3
  288. package/dist/{relation-9M9j_UWo.mjs.map → relation-Du0r04E0.mjs.map} +1 -1
  289. package/dist/{relations-Bh8jlFsU.d.mts → relations-BKybBoOX.d.mts} +12 -8
  290. package/dist/relations-BKybBoOX.d.mts.map +1 -0
  291. package/dist/{relations-5_avdrN_.mjs → relations-CZMpBqEd.mjs} +3 -3
  292. package/dist/{relations-5_avdrN_.mjs.map → relations-CZMpBqEd.mjs.map} +1 -1
  293. package/dist/{relations-BKvO39-9.mjs → relations-DtX1UMBH.mjs} +13 -7
  294. package/dist/relations-DtX1UMBH.mjs.map +1 -0
  295. package/dist/{resolve-Cd9dzclN.mjs → resolve-DFr4kq72.mjs} +2 -2
  296. package/dist/{resolve-Cd9dzclN.mjs.map → resolve-DFr4kq72.mjs.map} +1 -1
  297. package/dist/{runner-DfnZ5eUr.mjs → runner-BdeLEgad.mjs} +3 -3
  298. package/dist/{runner-DfnZ5eUr.mjs.map → runner-BdeLEgad.mjs.map} +1 -1
  299. package/dist/runtime.d.mts +2 -2
  300. package/dist/runtime.mjs +3 -3
  301. package/dist/{schema-BXxlHeAf.mjs → schema-BH8P8vaC.mjs} +3 -3
  302. package/dist/{schema-BXxlHeAf.mjs.map → schema-BH8P8vaC.mjs.map} +1 -1
  303. package/dist/{scopes-Bl4IwHA-.mjs → scopes-DTIkIaGA.mjs} +2 -2
  304. package/dist/{scopes-Bl4IwHA-.mjs.map → scopes-DTIkIaGA.mjs.map} +1 -1
  305. package/dist/{search-Bff-7jFt.mjs → search-D-Yhpmvs.mjs} +8 -7
  306. package/dist/search-D-Yhpmvs.mjs.map +1 -0
  307. package/dist/{secrets-CSwQIl4q.mjs → secrets-CQ-69kF9.mjs} +23 -17
  308. package/dist/secrets-CQ-69kF9.mjs.map +1 -0
  309. package/dist/{sections-CwW4s1al.mjs → sections-CzVBAKjw.mjs} +3 -3
  310. package/dist/{sections-CwW4s1al.mjs.map → sections-CzVBAKjw.mjs.map} +1 -1
  311. package/dist/seed/index.mjs +16 -16
  312. package/dist/seo/index.mjs +12 -12
  313. package/dist/{seo-2cmFHZns.mjs → seo-kJzVAmmu.mjs} +2 -2
  314. package/dist/{seo-2cmFHZns.mjs.map → seo-kJzVAmmu.mjs.map} +1 -1
  315. package/dist/{service-DkGTWGIi.mjs → service-CQnc276a.mjs} +2 -2
  316. package/dist/{service-DkGTWGIi.mjs.map → service-CQnc276a.mjs.map} +1 -1
  317. package/dist/{settings-CpA4lQFt.mjs → settings-C2PaBS49.mjs} +3 -3
  318. package/dist/{settings-CpA4lQFt.mjs.map → settings-C2PaBS49.mjs.map} +1 -1
  319. package/dist/{settings-C4s8hFQm.mjs → settings-Dtc0DU31.mjs} +3 -3
  320. package/dist/{settings-C4s8hFQm.mjs.map → settings-Dtc0DU31.mjs.map} +1 -1
  321. package/dist/{taxonomies-DjSKBZpq.mjs → taxonomies-HI9fnTdp.mjs} +7 -7
  322. package/dist/{taxonomies-DjSKBZpq.mjs.map → taxonomies-HI9fnTdp.mjs.map} +1 -1
  323. package/dist/{taxonomies-Ce49uIzY.mjs → taxonomies-rMeBzOjX.mjs} +26 -17
  324. package/dist/taxonomies-rMeBzOjX.mjs.map +1 -0
  325. package/dist/{taxonomy-DfVooU4W.mjs → taxonomy-0mCI25HN.mjs} +3 -3
  326. package/dist/{taxonomy-DfVooU4W.mjs.map → taxonomy-0mCI25HN.mjs.map} +1 -1
  327. package/dist/{types-D1iJ3DpO.mjs → types-CrRUFdej.mjs} +2 -2
  328. package/dist/{types-D1iJ3DpO.mjs.map → types-CrRUFdej.mjs.map} +1 -1
  329. package/dist/{user-Bh-L1qo6.mjs → user-aTXqmXBz.mjs} +3 -3
  330. package/dist/{user-Bh-L1qo6.mjs.map → user-aTXqmXBz.mjs.map} +1 -1
  331. package/dist/{validate-V9nCwq_-.mjs → validate-CDT1jl6_.mjs} +11 -6
  332. package/dist/validate-CDT1jl6_.mjs.map +1 -0
  333. package/dist/validate-DULfgpJP.d.mts.map +1 -1
  334. package/dist/{validation-BsVUJfsP.mjs → validation-DlD0E35W.mjs} +8 -8
  335. package/dist/{validation-BsVUJfsP.mjs.map → validation-DlD0E35W.mjs.map} +1 -1
  336. package/dist/version-Ci6bw6YK.mjs +7 -0
  337. package/dist/{version-907opKac.mjs.map → version-Ci6bw6YK.mjs.map} +1 -1
  338. package/dist/{widgets-DGv1Z04V.mjs → widgets-5fipNkMT.mjs} +2 -2
  339. package/dist/{widgets-DGv1Z04V.mjs.map → widgets-5fipNkMT.mjs.map} +1 -1
  340. package/dist/{wxr-taxonomies-Cs1N6O8z.mjs → wxr-taxonomies-BmUnBNgz.mjs} +4 -4
  341. package/dist/{wxr-taxonomies-Cs1N6O8z.mjs.map → wxr-taxonomies-BmUnBNgz.mjs.map} +1 -1
  342. package/dist/{zod-generator-B5prQ5M4.mjs → zod-generator-CwkHWz4f.mjs} +2 -2
  343. package/dist/{zod-generator-B5prQ5M4.mjs.map → zod-generator-CwkHWz4f.mjs.map} +1 -1
  344. package/package.json +5 -5
  345. package/src/api/handlers/bylines.ts +5 -4
  346. package/src/api/handlers/content.ts +25 -7
  347. package/src/api/handlers/menus.ts +11 -6
  348. package/src/api/handlers/relations.ts +7 -2
  349. package/src/api/handlers/taxonomies.ts +22 -14
  350. package/src/api/schemas/bylines.ts +4 -4
  351. package/src/api/schemas/common.ts +7 -6
  352. package/src/api/schemas/menus.ts +2 -1
  353. package/src/api/schemas/relations.ts +3 -1
  354. package/src/api/schemas/taxonomies.ts +4 -2
  355. package/src/astro/routes/api/admin/bylines/index.ts +5 -8
  356. package/src/astro/routes/api/oauth/authorize.ts +42 -10
  357. package/src/components/Gallery.astro +141 -26
  358. package/src/config/secrets.ts +25 -19
  359. package/src/content/converters/gallery.ts +61 -0
  360. package/src/content/converters/portable-text-to-prosemirror.ts +19 -0
  361. package/src/content/converters/prosemirror-to-portable-text.ts +18 -0
  362. package/src/content/converters/types.ts +36 -0
  363. package/src/database/dialect-helpers.ts +3 -4
  364. package/src/database/pg-migration-lock.ts +1 -4
  365. package/src/emdash-runtime.ts +55 -17
  366. package/src/i18n/config.ts +8 -0
  367. package/src/i18n/repair-locale-casing.ts +51 -0
  368. package/src/search/query.ts +3 -2
  369. package/src/seed/apply.ts +9 -7
  370. package/src/seed/validate.ts +15 -4
  371. package/dist/api-b8WIiGU4.mjs.map +0 -1
  372. package/dist/apply-CmIJK9j8.mjs.map +0 -1
  373. package/dist/bylines-BJbT4gKS.mjs.map +0 -1
  374. package/dist/config-CVssduLe.mjs.map +0 -1
  375. package/dist/dialect-helpers-HxEQGl_1.mjs.map +0 -1
  376. package/dist/index-wYnC6u4o.d.mts.map +0 -1
  377. package/dist/media-kIV1IxFf.mjs.map +0 -1
  378. package/dist/menus-BcyElFtI.mjs.map +0 -1
  379. package/dist/menus-CZyG6rvx.mjs.map +0 -1
  380. package/dist/redirects-BBq3n2Rj.mjs.map +0 -1
  381. package/dist/relations-BKvO39-9.mjs.map +0 -1
  382. package/dist/relations-Bh8jlFsU.d.mts.map +0 -1
  383. package/dist/search-Bff-7jFt.mjs.map +0 -1
  384. package/dist/secrets-CSwQIl4q.mjs.map +0 -1
  385. package/dist/taxonomies-Ce49uIzY.mjs.map +0 -1
  386. package/dist/validate-V9nCwq_-.mjs.map +0 -1
  387. package/dist/version-907opKac.mjs +0 -7
@@ -1 +1 @@
1
- {"version":3,"file":"zod-generator-B5prQ5M4.mjs","names":[],"sources":["../src/schema/zod-generator.ts"],"sourcesContent":["import { z, type ZodTypeAny } from \"zod\";\n\nimport { hashString } from \"../utils/hash.js\";\nimport type { Field, FieldType, CollectionWithFields } from \"./types.js\";\n\n/** Pattern to split on underscores, hyphens, and spaces for PascalCase conversion */\nconst PASCAL_CASE_SPLIT_PATTERN = /[_\\-\\s]+/;\n\n/**\n * Generate a Zod schema from a collection's field definitions\n *\n * This allows runtime validation of content based on dynamically\n * defined schemas stored in D1.\n */\nexport function generateZodSchema(\n\tcollection: CollectionWithFields,\n): z.ZodObject<Record<string, ZodTypeAny>> {\n\tconst shape: Record<string, ZodTypeAny> = {};\n\n\tfor (const field of collection.fields) {\n\t\tshape[field.slug] = generateFieldSchema(field);\n\t}\n\n\treturn z.object(shape);\n}\n\n/**\n * Generate Zod schema for a single field\n */\nexport function generateFieldSchema(field: Field): ZodTypeAny {\n\tlet schema = getBaseSchema(field.type, field);\n\n\t// Apply validation rules\n\tif (field.validation) {\n\t\tschema = applyValidation(schema, field);\n\t}\n\n\t// Apply required/optional. Non-required fields use `.nullish()` rather\n\t// than `.optional()` because the underlying SQLite columns are nullable\n\t// (see `SchemaRegistry.addFieldColumn` -- non-required fields are added\n\t// without `NOT NULL`). The admin re-sends what it loaded from the\n\t// server on autosave, so any field that's actually `null` in the DB\n\t// must round-trip cleanly through the validator. `.optional()` only\n\t// accepts `undefined`; `.nullish()` accepts both `undefined` and\n\t// `null`. (#867 — autosave failures on seeded entries.)\n\tif (!field.required) {\n\t\tschema = schema.nullish();\n\t}\n\n\t// Apply default value\n\tif (field.defaultValue !== undefined) {\n\t\tschema = schema.default(field.defaultValue);\n\t}\n\n\treturn schema;\n}\n\n/**\n * Get base Zod schema for a field type\n */\nfunction getBaseSchema(type: FieldType, field: Field): ZodTypeAny {\n\tswitch (type) {\n\t\tcase \"url\":\n\t\t\treturn z.string().url();\n\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\t\treturn z.string();\n\n\t\tcase \"number\":\n\t\t\treturn z.number();\n\n\t\tcase \"integer\":\n\t\t\treturn z.number().int();\n\n\t\tcase \"boolean\":\n\t\t\t// Boolean fields map to `INTEGER` columns (`FIELD_TYPE_TO_COLUMN`\n\t\t\t// in `schema/types.ts`) and `serializeValue` in\n\t\t\t// `database/repositories/content.ts` writes booleans as 0/1.\n\t\t\t// `deserializeValue` never converts them back, so reads return\n\t\t\t// numbers. Coerce the stored 0/1 shape here so a GET → POST\n\t\t\t// round-trip on a boolean field passes validation. Other inputs\n\t\t\t// (strings, other numbers) fall through to `z.boolean()` and\n\t\t\t// produce its standard rejection.\n\t\t\treturn z.preprocess((v) => (v === 0 || v === 1 ? Boolean(v) : v), z.boolean());\n\n\t\tcase \"datetime\":\n\t\t\t// Accept every value that legitimately round-trips through the admin\n\t\t\t// and seeds: ISO with `Z`, ISO with a timezone offset, a naive\n\t\t\t// datetime (`YYYY-MM-DDTHH:mm[:ss]` -- what `<input type=\"datetime-local\">`\n\t\t\t// and many seeds produce), and a date-only value. The admin re-sends\n\t\t\t// every loaded field on autosave, so a stored naive datetime must\n\t\t\t// validate or the entry becomes unsavable through its own editor\n\t\t\t// (#1368; same class as #867). `z.iso.*` retains semantic validation,\n\t\t\t// so impossible dates are still rejected.\n\t\t\treturn z.iso.datetime({ offset: true, local: true }).or(z.iso.date());\n\n\t\tcase \"select\": {\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\tconst [first, ...rest] = options;\n\t\t\t\treturn z.enum([first, ...rest]);\n\t\t\t}\n\t\t\treturn z.string();\n\t\t}\n\n\t\tcase \"multiSelect\": {\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\tconst [first, ...rest] = multiOptions;\n\t\t\t\treturn z.array(z.enum([first, ...rest]));\n\t\t\t}\n\t\t\treturn z.array(z.string());\n\t\t}\n\n\t\tcase \"portableText\":\n\t\t\t// Portable Text is an array of blocks. We require `_type` because\n\t\t\t// renderers dispatch on it, but `_key` is intentionally optional:\n\t\t\t// it's a UI-layer concern that the editor regenerates on every\n\t\t\t// change (see `PortableTextEditor`), and the rest of this schema\n\t\t\t// uses `.passthrough()` for everything below the top level. Making\n\t\t\t// `_key` strictly required here was an accidentally tight invariant\n\t\t\t// that rejected any seed/import data not authored against the\n\t\t\t// editor (#867 — autosave failures on seeded template content).\n\t\t\treturn z.array(\n\t\t\t\tz\n\t\t\t\t\t.object({\n\t\t\t\t\t\t_type: z.string(),\n\t\t\t\t\t\t_key: z.string().optional(),\n\t\t\t\t\t})\n\t\t\t\t\t.passthrough(),\n\t\t\t);\n\n\t\tcase \"image\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\talt: z.string().optional(),\n\t\t\t\twidth: z.number().optional(),\n\t\t\t\theight: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"cloudflare-images\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Admin-side preview URL for external providers (not persisted by plugins) */\n\t\t\t\tpreviewUrl: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"file\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\tfilename: z.string().optional(),\n\t\t\t\tmimeType: z.string().optional(),\n\t\t\t\tsize: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"s3\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"reference\":\n\t\t\treturn z.string(); // Reference ID\n\n\t\tcase \"json\":\n\t\t\treturn z.unknown();\n\n\t\tdefault:\n\t\t\treturn z.unknown();\n\t}\n}\n\n/**\n * Apply validation rules to a schema\n */\nfunction applyValidation(schema: ZodTypeAny, field: Field): ZodTypeAny {\n\tconst validation = field.validation;\n\tif (!validation) return schema;\n\n\t// String validations\n\tif (schema instanceof z.ZodString) {\n\t\tlet strSchema = schema;\n\t\tif (validation.minLength !== undefined) {\n\t\t\tstrSchema = strSchema.min(validation.minLength);\n\t\t}\n\t\tif (validation.maxLength !== undefined) {\n\t\t\tstrSchema = strSchema.max(validation.maxLength);\n\t\t}\n\t\tif (validation.pattern) {\n\t\t\tstrSchema = strSchema.regex(new RegExp(validation.pattern));\n\t\t}\n\t\treturn strSchema;\n\t}\n\n\t// Number validations\n\tif (schema instanceof z.ZodNumber) {\n\t\tlet numSchema = schema;\n\t\tif (validation.min !== undefined) {\n\t\t\tnumSchema = numSchema.min(validation.min);\n\t\t}\n\t\tif (validation.max !== undefined) {\n\t\t\tnumSchema = numSchema.max(validation.max);\n\t\t}\n\t\treturn numSchema;\n\t}\n\n\treturn schema;\n}\n\n/**\n * Schema cache to avoid regenerating schemas on every request\n */\nconst schemaCache = new Map<string, { schema: z.ZodObject<any>; version: string }>();\n\n/**\n * Get or generate a cached schema for a collection\n */\nexport function getCachedSchema(\n\tcollection: CollectionWithFields,\n\tversion?: string,\n): z.ZodObject<any> {\n\tconst cacheKey = collection.slug;\n\tconst cached = schemaCache.get(cacheKey);\n\n\t// If version matches, return cached schema\n\tif (cached && (!version || cached.version === version)) {\n\t\treturn cached.schema;\n\t}\n\n\t// Generate new schema\n\tconst schema = generateZodSchema(collection);\n\n\t// Cache it\n\tschemaCache.set(cacheKey, {\n\t\tschema,\n\t\tversion: version || collection.updatedAt,\n\t});\n\n\treturn schema;\n}\n\n/**\n * Invalidate cached schema for a collection\n */\nexport function invalidateSchemaCache(slug: string): void {\n\tschemaCache.delete(slug);\n}\n\n/**\n * Clear all cached schemas\n */\nexport function clearSchemaCache(): void {\n\tschemaCache.clear();\n}\n\n/**\n * Validate data against a collection's schema\n */\nexport function validateContent(\n\tcollection: CollectionWithFields,\n\tdata: unknown,\n): { success: true; data: unknown } | { success: false; errors: z.ZodError } {\n\tconst schema = getCachedSchema(collection);\n\n\tconst result = schema.safeParse(data);\n\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\n\treturn { success: false, errors: result.error };\n}\n\n/**\n * Generate TypeScript interface from field definitions\n * Used by CLI `emdash types` to generate types\n */\nexport function generateTypeScript(\n\tcollection: CollectionWithFields,\n\tinterfaceName: string = getInterfaceName(collection),\n): string {\n\tconst lines: string[] = [];\n\n\tlines.push(`export interface ${interfaceName} {`);\n\tlines.push(` id: string;`);\n\tlines.push(` slug: string | null;`);\n\tlines.push(` status: string;`);\n\n\tfor (const field of collection.fields) {\n\t\tconst tsType = fieldTypeToTypeScript(field);\n\t\tconst optional = field.required ? \"\" : \"?\";\n\t\tlines.push(` ${field.slug}${optional}: ${tsType};`);\n\t}\n\n\tlines.push(` createdAt: Date;`);\n\tlines.push(` updatedAt: Date;`);\n\tlines.push(` publishedAt: Date | null;`);\n\t// Bylines are eagerly loaded by getEmDashCollection/getEmDashEntry\n\tlines.push(` bylines?: ContentBylineCredit[];`);\n\t// Taxonomy terms are eagerly loaded by getEmDashCollection/getEmDashEntry,\n\t// keyed by taxonomy name (e.g. data.terms?.tag)\n\tlines.push(` terms?: Record<string, TaxonomyTerm[]>;`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate a complete types file with module augmentation\n * This produces emdash-env.d.ts content that provides typed query functions\n */\nexport function generateTypesFile(collections: CollectionWithFields[]): string {\n\tconst lines: string[] = [];\n\n\t// Header\n\tlines.push(`// Generated by EmDash on dev server start`);\n\tlines.push(`// Do not edit manually`);\n\tlines.push(``);\n\tlines.push(`/// <reference types=\"emdash/locals\" />`);\n\tlines.push(``);\n\n\t// Check if we need PortableTextBlock import\n\tconst needsPortableText = collections.some((c) =>\n\t\tc.fields.some((f) => f.type === \"portableText\"),\n\t);\n\n\t// Build imports - ContentBylineCredit and TaxonomyTerm are always needed\n\t// for the hydrated bylines/terms fields\n\tconst imports = [\"ContentBylineCredit\", \"TaxonomyTerm\"];\n\tif (needsPortableText) {\n\t\timports.push(\"PortableTextBlock\");\n\t}\n\tlines.push(`import type { ${imports.join(\", \")} } from \"emdash\";`);\n\tlines.push(``);\n\n\t// Singularizing the slug can map two distinct slugs to the same name\n\t// (e.g. `book` and `books` both -> `Book`), so resolve collisions up front\n\t// to keep every interface identifier unique within the file.\n\tconst interfaceNames = uniqueInterfaceNames(collections);\n\n\t// Generate individual interfaces\n\tfor (const collection of collections) {\n\t\tlines.push(generateTypeScript(collection, interfaceNames.get(collection.slug)));\n\t\tlines.push(``);\n\t}\n\n\t// Generate the Collections interface for module augmentation\n\tlines.push(`declare module \"emdash\" {`);\n\tlines.push(` interface EmDashCollections {`);\n\tfor (const collection of collections) {\n\t\tlines.push(` ${collection.slug}: ${interfaceNames.get(collection.slug)};`);\n\t}\n\tlines.push(` }`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate schema hash for cache invalidation\n */\nexport async function generateSchemaHash(collections: CollectionWithFields[]): Promise<string> {\n\tconst str = JSON.stringify(\n\t\tcollections.map((c) => ({\n\t\t\tslug: c.slug,\n\t\t\tfields: c.fields.map((f) => ({\n\t\t\t\tslug: f.slug,\n\t\t\t\ttype: f.type,\n\t\t\t\trequired: f.required,\n\t\t\t\tvalidation: f.validation,\n\t\t\t})),\n\t\t})),\n\t);\n\treturn hashString(str);\n}\n\n/**\n * Map field type to TypeScript type\n */\nfunction fieldTypeToTypeScript(field: Field): string {\n\tswitch (field.type) {\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\tcase \"url\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\n\t\tcase \"number\":\n\t\tcase \"integer\":\n\t\t\treturn \"number\";\n\n\t\tcase \"boolean\":\n\t\t\treturn \"boolean\";\n\n\t\tcase \"select\":\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\treturn options.map((o) => `\"${o}\"`).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\n\t\tcase \"multiSelect\":\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\treturn `(${multiOptions.map((o) => `\"${o}\"`).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\n\t\tcase \"portableText\":\n\t\t\treturn \"PortableTextBlock[]\";\n\n\t\tcase \"image\":\n\t\t\treturn \"{ id: string; src?: string; alt?: string; width?: number; height?: number; provider?: string; previewUrl?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"file\":\n\t\t\treturn \"{ id: string; src?: string; filename?: string; mimeType?: string; size?: number; provider?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"reference\":\n\t\t\t// Could be enhanced to include the referenced collection type\n\t\t\treturn \"string\";\n\n\t\tcase \"json\":\n\t\t\treturn \"unknown\";\n\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Convert string to PascalCase (handles slugs, spaces, etc.)\n */\nfunction pascalCase(str: string): string {\n\treturn str\n\t\t.split(PASCAL_CASE_SPLIT_PATTERN)\n\t\t.filter(Boolean)\n\t\t.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n\t\t.join(\"\");\n}\n\n/**\n * Naive singularization for slug-derived interface names. Handles the common\n * English plural endings; intentionally simple, not a full inflector.\n */\nfunction singularize(str: string): string {\n\tif (str.endsWith(\"ies\")) {\n\t\treturn str.slice(0, -3) + \"y\";\n\t}\n\tif (\n\t\tstr.endsWith(\"es\") &&\n\t\t(str.endsWith(\"sses\") || str.endsWith(\"xes\") || str.endsWith(\"ches\") || str.endsWith(\"shes\"))\n\t) {\n\t\treturn str.slice(0, -2);\n\t}\n\tif (str.endsWith(\"s\") && !str.endsWith(\"ss\")) {\n\t\treturn str.slice(0, -1);\n\t}\n\treturn str;\n}\n\n/**\n * Get the interface name for a collection.\n *\n * Derived from the slug, not the human label. Slugs are constrained to\n * `/^[a-z][a-z0-9_]*$/`, so PascalCasing one always yields a valid TS\n * identifier; labels are arbitrary and user-controlled (punctuation, spaces,\n * duplicates across collections), which produced syntactically invalid or\n * duplicate interface names. The slug is singularized first because the\n * interface describes a single entry, not the collection (`posts` -> `Post`).\n *\n * Singularization can map two distinct slugs onto the same name, so callers\n * generating more than one interface must dedupe -- see `uniqueInterfaceNames`.\n */\nfunction getInterfaceName(collection: CollectionWithFields): string {\n\treturn pascalCase(singularize(collection.slug));\n}\n\n/**\n * Resolve interface names for a set of collections, guaranteeing each is\n * unique within the file. Collisions (from singularization or PascalCasing\n * collapsing distinct slugs) get a numeric suffix in collection order, so the\n * generated `.d.ts` never declares two interfaces with the same identifier.\n *\n * The suffix is chosen against the set of names already emitted, not a\n * per-base counter, so a generated name can't collide with another slug's\n * base name (e.g. slugs `book`, `books`, `book2`: `books` -> `Book2` would\n * clash with `book2`, so it advances to `Book3`).\n */\nexport function uniqueInterfaceNames(collections: CollectionWithFields[]): Map<string, string> {\n\tconst used = new Set<string>();\n\tconst names = new Map<string, string>();\n\tfor (const collection of collections) {\n\t\tconst base = getInterfaceName(collection);\n\t\tlet name = base;\n\t\tlet suffix = 2;\n\t\twhile (used.has(name)) {\n\t\t\tname = `${base}${suffix}`;\n\t\t\tsuffix++;\n\t\t}\n\t\tused.add(name);\n\t\tnames.set(collection.slug, name);\n\t}\n\treturn names;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"zod-generator-CwkHWz4f.mjs","names":[],"sources":["../src/schema/zod-generator.ts"],"sourcesContent":["import { z, type ZodTypeAny } from \"zod\";\n\nimport { hashString } from \"../utils/hash.js\";\nimport type { Field, FieldType, CollectionWithFields } from \"./types.js\";\n\n/** Pattern to split on underscores, hyphens, and spaces for PascalCase conversion */\nconst PASCAL_CASE_SPLIT_PATTERN = /[_\\-\\s]+/;\n\n/**\n * Generate a Zod schema from a collection's field definitions\n *\n * This allows runtime validation of content based on dynamically\n * defined schemas stored in D1.\n */\nexport function generateZodSchema(\n\tcollection: CollectionWithFields,\n): z.ZodObject<Record<string, ZodTypeAny>> {\n\tconst shape: Record<string, ZodTypeAny> = {};\n\n\tfor (const field of collection.fields) {\n\t\tshape[field.slug] = generateFieldSchema(field);\n\t}\n\n\treturn z.object(shape);\n}\n\n/**\n * Generate Zod schema for a single field\n */\nexport function generateFieldSchema(field: Field): ZodTypeAny {\n\tlet schema = getBaseSchema(field.type, field);\n\n\t// Apply validation rules\n\tif (field.validation) {\n\t\tschema = applyValidation(schema, field);\n\t}\n\n\t// Apply required/optional. Non-required fields use `.nullish()` rather\n\t// than `.optional()` because the underlying SQLite columns are nullable\n\t// (see `SchemaRegistry.addFieldColumn` -- non-required fields are added\n\t// without `NOT NULL`). The admin re-sends what it loaded from the\n\t// server on autosave, so any field that's actually `null` in the DB\n\t// must round-trip cleanly through the validator. `.optional()` only\n\t// accepts `undefined`; `.nullish()` accepts both `undefined` and\n\t// `null`. (#867 — autosave failures on seeded entries.)\n\tif (!field.required) {\n\t\tschema = schema.nullish();\n\t}\n\n\t// Apply default value\n\tif (field.defaultValue !== undefined) {\n\t\tschema = schema.default(field.defaultValue);\n\t}\n\n\treturn schema;\n}\n\n/**\n * Get base Zod schema for a field type\n */\nfunction getBaseSchema(type: FieldType, field: Field): ZodTypeAny {\n\tswitch (type) {\n\t\tcase \"url\":\n\t\t\treturn z.string().url();\n\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\t\treturn z.string();\n\n\t\tcase \"number\":\n\t\t\treturn z.number();\n\n\t\tcase \"integer\":\n\t\t\treturn z.number().int();\n\n\t\tcase \"boolean\":\n\t\t\t// Boolean fields map to `INTEGER` columns (`FIELD_TYPE_TO_COLUMN`\n\t\t\t// in `schema/types.ts`) and `serializeValue` in\n\t\t\t// `database/repositories/content.ts` writes booleans as 0/1.\n\t\t\t// `deserializeValue` never converts them back, so reads return\n\t\t\t// numbers. Coerce the stored 0/1 shape here so a GET → POST\n\t\t\t// round-trip on a boolean field passes validation. Other inputs\n\t\t\t// (strings, other numbers) fall through to `z.boolean()` and\n\t\t\t// produce its standard rejection.\n\t\t\treturn z.preprocess((v) => (v === 0 || v === 1 ? Boolean(v) : v), z.boolean());\n\n\t\tcase \"datetime\":\n\t\t\t// Accept every value that legitimately round-trips through the admin\n\t\t\t// and seeds: ISO with `Z`, ISO with a timezone offset, a naive\n\t\t\t// datetime (`YYYY-MM-DDTHH:mm[:ss]` -- what `<input type=\"datetime-local\">`\n\t\t\t// and many seeds produce), and a date-only value. The admin re-sends\n\t\t\t// every loaded field on autosave, so a stored naive datetime must\n\t\t\t// validate or the entry becomes unsavable through its own editor\n\t\t\t// (#1368; same class as #867). `z.iso.*` retains semantic validation,\n\t\t\t// so impossible dates are still rejected.\n\t\t\treturn z.iso.datetime({ offset: true, local: true }).or(z.iso.date());\n\n\t\tcase \"select\": {\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\tconst [first, ...rest] = options;\n\t\t\t\treturn z.enum([first, ...rest]);\n\t\t\t}\n\t\t\treturn z.string();\n\t\t}\n\n\t\tcase \"multiSelect\": {\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\tconst [first, ...rest] = multiOptions;\n\t\t\t\treturn z.array(z.enum([first, ...rest]));\n\t\t\t}\n\t\t\treturn z.array(z.string());\n\t\t}\n\n\t\tcase \"portableText\":\n\t\t\t// Portable Text is an array of blocks. We require `_type` because\n\t\t\t// renderers dispatch on it, but `_key` is intentionally optional:\n\t\t\t// it's a UI-layer concern that the editor regenerates on every\n\t\t\t// change (see `PortableTextEditor`), and the rest of this schema\n\t\t\t// uses `.passthrough()` for everything below the top level. Making\n\t\t\t// `_key` strictly required here was an accidentally tight invariant\n\t\t\t// that rejected any seed/import data not authored against the\n\t\t\t// editor (#867 — autosave failures on seeded template content).\n\t\t\treturn z.array(\n\t\t\t\tz\n\t\t\t\t\t.object({\n\t\t\t\t\t\t_type: z.string(),\n\t\t\t\t\t\t_key: z.string().optional(),\n\t\t\t\t\t})\n\t\t\t\t\t.passthrough(),\n\t\t\t);\n\n\t\tcase \"image\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\talt: z.string().optional(),\n\t\t\t\twidth: z.number().optional(),\n\t\t\t\theight: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"cloudflare-images\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Admin-side preview URL for external providers (not persisted by plugins) */\n\t\t\t\tpreviewUrl: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"file\":\n\t\t\treturn z.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tsrc: z.string().optional(),\n\t\t\t\tfilename: z.string().optional(),\n\t\t\t\tmimeType: z.string().optional(),\n\t\t\t\tsize: z.number().optional(),\n\t\t\t\t/** Provider ID (e.g. \"local\", \"s3\") */\n\t\t\t\tprovider: z.string().optional(),\n\t\t\t\t/** Provider-specific metadata; for local media this carries storageKey */\n\t\t\t\tmeta: z.record(z.string(), z.unknown()).optional(),\n\t\t\t});\n\n\t\tcase \"reference\":\n\t\t\treturn z.string(); // Reference ID\n\n\t\tcase \"json\":\n\t\t\treturn z.unknown();\n\n\t\tdefault:\n\t\t\treturn z.unknown();\n\t}\n}\n\n/**\n * Apply validation rules to a schema\n */\nfunction applyValidation(schema: ZodTypeAny, field: Field): ZodTypeAny {\n\tconst validation = field.validation;\n\tif (!validation) return schema;\n\n\t// String validations\n\tif (schema instanceof z.ZodString) {\n\t\tlet strSchema = schema;\n\t\tif (validation.minLength !== undefined) {\n\t\t\tstrSchema = strSchema.min(validation.minLength);\n\t\t}\n\t\tif (validation.maxLength !== undefined) {\n\t\t\tstrSchema = strSchema.max(validation.maxLength);\n\t\t}\n\t\tif (validation.pattern) {\n\t\t\tstrSchema = strSchema.regex(new RegExp(validation.pattern));\n\t\t}\n\t\treturn strSchema;\n\t}\n\n\t// Number validations\n\tif (schema instanceof z.ZodNumber) {\n\t\tlet numSchema = schema;\n\t\tif (validation.min !== undefined) {\n\t\t\tnumSchema = numSchema.min(validation.min);\n\t\t}\n\t\tif (validation.max !== undefined) {\n\t\t\tnumSchema = numSchema.max(validation.max);\n\t\t}\n\t\treturn numSchema;\n\t}\n\n\treturn schema;\n}\n\n/**\n * Schema cache to avoid regenerating schemas on every request\n */\nconst schemaCache = new Map<string, { schema: z.ZodObject<any>; version: string }>();\n\n/**\n * Get or generate a cached schema for a collection\n */\nexport function getCachedSchema(\n\tcollection: CollectionWithFields,\n\tversion?: string,\n): z.ZodObject<any> {\n\tconst cacheKey = collection.slug;\n\tconst cached = schemaCache.get(cacheKey);\n\n\t// If version matches, return cached schema\n\tif (cached && (!version || cached.version === version)) {\n\t\treturn cached.schema;\n\t}\n\n\t// Generate new schema\n\tconst schema = generateZodSchema(collection);\n\n\t// Cache it\n\tschemaCache.set(cacheKey, {\n\t\tschema,\n\t\tversion: version || collection.updatedAt,\n\t});\n\n\treturn schema;\n}\n\n/**\n * Invalidate cached schema for a collection\n */\nexport function invalidateSchemaCache(slug: string): void {\n\tschemaCache.delete(slug);\n}\n\n/**\n * Clear all cached schemas\n */\nexport function clearSchemaCache(): void {\n\tschemaCache.clear();\n}\n\n/**\n * Validate data against a collection's schema\n */\nexport function validateContent(\n\tcollection: CollectionWithFields,\n\tdata: unknown,\n): { success: true; data: unknown } | { success: false; errors: z.ZodError } {\n\tconst schema = getCachedSchema(collection);\n\n\tconst result = schema.safeParse(data);\n\n\tif (result.success) {\n\t\treturn { success: true, data: result.data };\n\t}\n\n\treturn { success: false, errors: result.error };\n}\n\n/**\n * Generate TypeScript interface from field definitions\n * Used by CLI `emdash types` to generate types\n */\nexport function generateTypeScript(\n\tcollection: CollectionWithFields,\n\tinterfaceName: string = getInterfaceName(collection),\n): string {\n\tconst lines: string[] = [];\n\n\tlines.push(`export interface ${interfaceName} {`);\n\tlines.push(` id: string;`);\n\tlines.push(` slug: string | null;`);\n\tlines.push(` status: string;`);\n\n\tfor (const field of collection.fields) {\n\t\tconst tsType = fieldTypeToTypeScript(field);\n\t\tconst optional = field.required ? \"\" : \"?\";\n\t\tlines.push(` ${field.slug}${optional}: ${tsType};`);\n\t}\n\n\tlines.push(` createdAt: Date;`);\n\tlines.push(` updatedAt: Date;`);\n\tlines.push(` publishedAt: Date | null;`);\n\t// Bylines are eagerly loaded by getEmDashCollection/getEmDashEntry\n\tlines.push(` bylines?: ContentBylineCredit[];`);\n\t// Taxonomy terms are eagerly loaded by getEmDashCollection/getEmDashEntry,\n\t// keyed by taxonomy name (e.g. data.terms?.tag)\n\tlines.push(` terms?: Record<string, TaxonomyTerm[]>;`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate a complete types file with module augmentation\n * This produces emdash-env.d.ts content that provides typed query functions\n */\nexport function generateTypesFile(collections: CollectionWithFields[]): string {\n\tconst lines: string[] = [];\n\n\t// Header\n\tlines.push(`// Generated by EmDash on dev server start`);\n\tlines.push(`// Do not edit manually`);\n\tlines.push(``);\n\tlines.push(`/// <reference types=\"emdash/locals\" />`);\n\tlines.push(``);\n\n\t// Check if we need PortableTextBlock import\n\tconst needsPortableText = collections.some((c) =>\n\t\tc.fields.some((f) => f.type === \"portableText\"),\n\t);\n\n\t// Build imports - ContentBylineCredit and TaxonomyTerm are always needed\n\t// for the hydrated bylines/terms fields\n\tconst imports = [\"ContentBylineCredit\", \"TaxonomyTerm\"];\n\tif (needsPortableText) {\n\t\timports.push(\"PortableTextBlock\");\n\t}\n\tlines.push(`import type { ${imports.join(\", \")} } from \"emdash\";`);\n\tlines.push(``);\n\n\t// Singularizing the slug can map two distinct slugs to the same name\n\t// (e.g. `book` and `books` both -> `Book`), so resolve collisions up front\n\t// to keep every interface identifier unique within the file.\n\tconst interfaceNames = uniqueInterfaceNames(collections);\n\n\t// Generate individual interfaces\n\tfor (const collection of collections) {\n\t\tlines.push(generateTypeScript(collection, interfaceNames.get(collection.slug)));\n\t\tlines.push(``);\n\t}\n\n\t// Generate the Collections interface for module augmentation\n\tlines.push(`declare module \"emdash\" {`);\n\tlines.push(` interface EmDashCollections {`);\n\tfor (const collection of collections) {\n\t\tlines.push(` ${collection.slug}: ${interfaceNames.get(collection.slug)};`);\n\t}\n\tlines.push(` }`);\n\tlines.push(`}`);\n\n\treturn lines.join(\"\\n\");\n}\n\n/**\n * Generate schema hash for cache invalidation\n */\nexport async function generateSchemaHash(collections: CollectionWithFields[]): Promise<string> {\n\tconst str = JSON.stringify(\n\t\tcollections.map((c) => ({\n\t\t\tslug: c.slug,\n\t\t\tfields: c.fields.map((f) => ({\n\t\t\t\tslug: f.slug,\n\t\t\t\ttype: f.type,\n\t\t\t\trequired: f.required,\n\t\t\t\tvalidation: f.validation,\n\t\t\t})),\n\t\t})),\n\t);\n\treturn hashString(str);\n}\n\n/**\n * Map field type to TypeScript type\n */\nfunction fieldTypeToTypeScript(field: Field): string {\n\tswitch (field.type) {\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\tcase \"url\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\n\t\tcase \"number\":\n\t\tcase \"integer\":\n\t\t\treturn \"number\";\n\n\t\tcase \"boolean\":\n\t\t\treturn \"boolean\";\n\n\t\tcase \"select\":\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\treturn options.map((o) => `\"${o}\"`).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\n\t\tcase \"multiSelect\":\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\treturn `(${multiOptions.map((o) => `\"${o}\"`).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\n\t\tcase \"portableText\":\n\t\t\treturn \"PortableTextBlock[]\";\n\n\t\tcase \"image\":\n\t\t\treturn \"{ id: string; src?: string; alt?: string; width?: number; height?: number; provider?: string; previewUrl?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"file\":\n\t\t\treturn \"{ id: string; src?: string; filename?: string; mimeType?: string; size?: number; provider?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"reference\":\n\t\t\t// Could be enhanced to include the referenced collection type\n\t\t\treturn \"string\";\n\n\t\tcase \"json\":\n\t\t\treturn \"unknown\";\n\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Convert string to PascalCase (handles slugs, spaces, etc.)\n */\nfunction pascalCase(str: string): string {\n\treturn str\n\t\t.split(PASCAL_CASE_SPLIT_PATTERN)\n\t\t.filter(Boolean)\n\t\t.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n\t\t.join(\"\");\n}\n\n/**\n * Naive singularization for slug-derived interface names. Handles the common\n * English plural endings; intentionally simple, not a full inflector.\n */\nfunction singularize(str: string): string {\n\tif (str.endsWith(\"ies\")) {\n\t\treturn str.slice(0, -3) + \"y\";\n\t}\n\tif (\n\t\tstr.endsWith(\"es\") &&\n\t\t(str.endsWith(\"sses\") || str.endsWith(\"xes\") || str.endsWith(\"ches\") || str.endsWith(\"shes\"))\n\t) {\n\t\treturn str.slice(0, -2);\n\t}\n\tif (str.endsWith(\"s\") && !str.endsWith(\"ss\")) {\n\t\treturn str.slice(0, -1);\n\t}\n\treturn str;\n}\n\n/**\n * Get the interface name for a collection.\n *\n * Derived from the slug, not the human label. Slugs are constrained to\n * `/^[a-z][a-z0-9_]*$/`, so PascalCasing one always yields a valid TS\n * identifier; labels are arbitrary and user-controlled (punctuation, spaces,\n * duplicates across collections), which produced syntactically invalid or\n * duplicate interface names. The slug is singularized first because the\n * interface describes a single entry, not the collection (`posts` -> `Post`).\n *\n * Singularization can map two distinct slugs onto the same name, so callers\n * generating more than one interface must dedupe -- see `uniqueInterfaceNames`.\n */\nfunction getInterfaceName(collection: CollectionWithFields): string {\n\treturn pascalCase(singularize(collection.slug));\n}\n\n/**\n * Resolve interface names for a set of collections, guaranteeing each is\n * unique within the file. Collisions (from singularization or PascalCasing\n * collapsing distinct slugs) get a numeric suffix in collection order, so the\n * generated `.d.ts` never declares two interfaces with the same identifier.\n *\n * The suffix is chosen against the set of names already emitted, not a\n * per-base counter, so a generated name can't collide with another slug's\n * base name (e.g. slugs `book`, `books`, `book2`: `books` -> `Book2` would\n * clash with `book2`, so it advances to `Book3`).\n */\nexport function uniqueInterfaceNames(collections: CollectionWithFields[]): Map<string, string> {\n\tconst used = new Set<string>();\n\tconst names = new Map<string, string>();\n\tfor (const collection of collections) {\n\t\tconst base = getInterfaceName(collection);\n\t\tlet name = base;\n\t\tlet suffix = 2;\n\t\twhile (used.has(name)) {\n\t\t\tname = `${base}${suffix}`;\n\t\t\tsuffix++;\n\t\t}\n\t\tused.add(name);\n\t\tnames.set(collection.slug, name);\n\t}\n\treturn names;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emdash",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "Astro-native CMS with WordPress migration support",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -234,9 +234,9 @@
234
234
  "upng-js": "^2.1.0",
235
235
  "zod": "^4.4.1",
236
236
  "@atcute/client": "^5.0.0",
237
- "@emdash-cms/admin": "0.30.0",
238
- "@emdash-cms/auth": "0.30.0",
239
- "@emdash-cms/gutenberg-to-portable-text": "0.30.0",
237
+ "@emdash-cms/auth": "0.31.0",
238
+ "@emdash-cms/admin": "0.31.0",
239
+ "@emdash-cms/gutenberg-to-portable-text": "0.31.0",
240
240
  "@emdash-cms/plugin-types": "0.3.0",
241
241
  "@emdash-cms/registry-client": "0.3.4"
242
242
  },
@@ -271,7 +271,7 @@
271
271
  "vite": "^6.0.0",
272
272
  "vitest": "^4.1.5",
273
273
  "zod-openapi": "^5.4.6",
274
- "@emdash-cms/blocks": "0.30.0"
274
+ "@emdash-cms/blocks": "0.31.0"
275
275
  },
276
276
  "repository": {
277
277
  "type": "git",
@@ -7,7 +7,7 @@ import {
7
7
  } from "../../database/repositories/byline.js";
8
8
  import { EmDashValidationError, type BylineSummary } from "../../database/repositories/types.js";
9
9
  import type { Database } from "../../database/types.js";
10
- import { getI18nConfig } from "../../i18n/config.js";
10
+ import { getI18nConfig, resolveConfiguredLocale } from "../../i18n/config.js";
11
11
  import type { ApiResult } from "../types.js";
12
12
 
13
13
  // `undefined → null` so a missing field in the create payload matches the
@@ -138,7 +138,8 @@ export async function handleBylineCreate(
138
138
  };
139
139
  }
140
140
 
141
- const localeErr = rejectUnknownLocale(input.locale);
141
+ const locale = input.locale ? resolveConfiguredLocale(input.locale) : undefined;
142
+ const localeErr = rejectUnknownLocale(locale);
142
143
  if (localeErr) return localeErr;
143
144
 
144
145
  const repo = new BylineRepository(db);
@@ -160,7 +161,7 @@ export async function handleBylineCreate(
160
161
  sourceGroup = source.translationGroup ?? source.id;
161
162
  }
162
163
 
163
- const effectiveLocale = input.locale ?? getI18nConfig()?.defaultLocale ?? "en";
164
+ const effectiveLocale = locale ?? getI18nConfig()?.defaultLocale ?? "en";
164
165
 
165
166
  // Translation-group guard: the row-per-locale model (PR #916)
166
167
  // allows exactly one row per (translation_group, locale). Reject
@@ -216,7 +217,7 @@ export async function handleBylineCreate(
216
217
  };
217
218
  }
218
219
 
219
- const byline = await repo.create(input);
220
+ const byline = await repo.create({ ...input, locale: effectiveLocale });
220
221
  return { success: true, data: byline };
221
222
  } catch (error) {
222
223
  // Mirror handleBylineUpdate: surface customFields validation
@@ -30,7 +30,7 @@ import { UserRepository } from "../../database/repositories/user.js";
30
30
  import { withTransaction } from "../../database/transaction.js";
31
31
  import type { Database } from "../../database/types.js";
32
32
  import { validateIdentifier } from "../../database/validate.js";
33
- import { getI18nConfig, isI18nEnabled } from "../../i18n/config.js";
33
+ import { getI18nConfig, isI18nEnabled, resolveConfiguredLocale } from "../../i18n/config.js";
34
34
  import { invalidateRedirectCache } from "../../redirects/cache.js";
35
35
  import { FTSManager } from "../../search/fts-manager.js";
36
36
  import { invalidateTermCache } from "../../taxonomies/index.js";
@@ -269,7 +269,11 @@ async function resolveId(
269
269
  identifier: string,
270
270
  locale?: string,
271
271
  ): Promise<string | null> {
272
- const item = await repo.findByIdOrSlug(collection, identifier, locale);
272
+ const item = await repo.findByIdOrSlug(
273
+ collection,
274
+ identifier,
275
+ locale ? resolveConfiguredLocale(locale) : undefined,
276
+ );
273
277
  return item?.id ?? null;
274
278
  }
275
279
 
@@ -283,7 +287,11 @@ async function resolveIdIncludingTrashed(
283
287
  identifier: string,
284
288
  locale?: string,
285
289
  ): Promise<string | null> {
286
- const item = await repo.findByIdOrSlugIncludingTrashed(collection, identifier, locale);
290
+ const item = await repo.findByIdOrSlugIncludingTrashed(
291
+ collection,
292
+ identifier,
293
+ locale ? resolveConfiguredLocale(locale) : undefined,
294
+ );
287
295
  return item?.id ?? null;
288
296
  }
289
297
 
@@ -427,7 +435,7 @@ export async function handleContentList(
427
435
  const repo = new ContentRepository(db);
428
436
  const where: FindManyOptions["where"] = {};
429
437
  if (params.status) where.status = params.status;
430
- if (params.locale) where.locale = params.locale;
438
+ if (params.locale) where.locale = resolveConfiguredLocale(params.locale);
431
439
  if (params.authorId) where.authorId = params.authorId;
432
440
 
433
441
  // A date range requires a target column; ignore stray from/to without
@@ -579,7 +587,11 @@ export async function handleContentGet(
579
587
  ): Promise<ApiResult<ContentResponse>> {
580
588
  try {
581
589
  const repo = new ContentRepository(db);
582
- const item = await repo.findByIdOrSlug(collection, id, locale);
590
+ const item = await repo.findByIdOrSlug(
591
+ collection,
592
+ id,
593
+ locale ? resolveConfiguredLocale(locale) : undefined,
594
+ );
583
595
 
584
596
  if (!item) {
585
597
  return {
@@ -624,7 +636,11 @@ export async function handleContentGetIncludingTrashed(
624
636
  ): Promise<ApiResult<ContentResponse>> {
625
637
  try {
626
638
  const repo = new ContentRepository(db);
627
- const item = await repo.findByIdOrSlugIncludingTrashed(collection, id, locale);
639
+ const item = await repo.findByIdOrSlugIncludingTrashed(
640
+ collection,
641
+ id,
642
+ locale ? resolveConfiguredLocale(locale) : undefined,
643
+ );
628
644
 
629
645
  if (!item) {
630
646
  return {
@@ -706,7 +722,9 @@ export async function handleContentCreate(
706
722
  // Default to the configured site locale rather than the repo's
707
723
  // hard-coded "en" — otherwise non-English default-locale sites
708
724
  // silently create entries in a locale the editor never chose.
709
- const effectiveLocale = body.locale ?? getI18nConfig()?.defaultLocale;
725
+ const effectiveLocale = body.locale
726
+ ? resolveConfiguredLocale(body.locale)
727
+ : getI18nConfig()?.defaultLocale;
710
728
 
711
729
  let slug: string | null | undefined = body.slug;
712
730
  if (!slug) {
@@ -25,7 +25,7 @@ import {
25
25
  type UpdateMenuItemInput as UpdateMenuItemRepoInput,
26
26
  } from "../../database/repositories/menu.js";
27
27
  import type { Database } from "../../database/types.js";
28
- import { getI18nConfig } from "../../i18n/config.js";
28
+ import { getI18nConfig, resolveConfiguredLocale } from "../../i18n/config.js";
29
29
  import type { ApiResult } from "../types.js";
30
30
 
31
31
  // Re-export entity types so route files and tests can import them from the
@@ -89,7 +89,8 @@ async function resolveMenu(
89
89
  name: string,
90
90
  options: { locale?: string },
91
91
  ): Promise<ResolveMenuResult> {
92
- const matches = await repo.findByName(name, options);
92
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
93
+ const matches = await repo.findByName(name, { locale });
93
94
  if (matches.length === 0) {
94
95
  return {
95
96
  success: false,
@@ -124,7 +125,8 @@ export async function handleMenuList(
124
125
  ): Promise<ApiResult<MenuListItem[]>> {
125
126
  try {
126
127
  const repo = new MenuRepository(db);
127
- const items = await repo.findMany(options);
128
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
129
+ const items = await repo.findMany({ locale });
128
130
  return { success: true, data: items };
129
131
  } catch {
130
132
  return {
@@ -175,7 +177,9 @@ export async function handleMenuCreate(
175
177
 
176
178
  // Duplicate guard: same (name, locale). Falls back to the configured
177
179
  // defaultLocale to match the column DEFAULT set by migration 036.
178
- const effectiveLocale = input.locale ?? getI18nConfig()?.defaultLocale ?? "en";
180
+ const effectiveLocale = input.locale
181
+ ? resolveConfiguredLocale(input.locale)
182
+ : (getI18nConfig()?.defaultLocale ?? "en");
179
183
  if (await repo.existsByNameAndLocale(input.name, effectiveLocale)) {
180
184
  return {
181
185
  success: false,
@@ -188,7 +192,7 @@ export async function handleMenuCreate(
188
192
  };
189
193
  }
190
194
 
191
- const menu = await repo.create(input);
195
+ const menu = await repo.create({ ...input, locale: effectiveLocale });
192
196
  return { success: true, data: menu };
193
197
  } catch {
194
198
  return {
@@ -212,7 +216,8 @@ export async function handleMenuGet(
212
216
  ): Promise<ApiResult<MenuWithItems>> {
213
217
  try {
214
218
  const repo = new MenuRepository(db);
215
- const matches = await repo.findByName(name, options);
219
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
220
+ const matches = await repo.findByName(name, { locale });
216
221
  if (matches.length === 0) {
217
222
  return {
218
223
  success: false,
@@ -10,6 +10,7 @@ import {
10
10
  import { InvalidCursorError } from "../../database/repositories/types.js";
11
11
  import type { ContentItem } from "../../database/repositories/types.js";
12
12
  import type { Database } from "../../database/types.js";
13
+ import { resolveConfiguredLocale } from "../../i18n/config.js";
13
14
  import { SchemaRegistry } from "../../schema/registry.js";
14
15
  import type { ApiResult } from "../types.js";
15
16
 
@@ -70,7 +71,10 @@ export async function handleRelationCreate(
70
71
  }
71
72
  }
72
73
 
73
- const relation = await repo.create(input);
74
+ const relation = await repo.create({
75
+ ...input,
76
+ locale: input.locale ? resolveConfiguredLocale(input.locale) : undefined,
77
+ });
74
78
  return { success: true, data: { relation } };
75
79
  } catch (error) {
76
80
  // A bad `translationOf` makes the repo throw loudly rather than mint an
@@ -127,7 +131,8 @@ export async function handleRelationList(
127
131
  ): Promise<ApiResult<{ relations: Relation[] }>> {
128
132
  try {
129
133
  const repo = new RelationRepository(db);
130
- const relations = await repo.list(opts.locale);
134
+ const locale = opts.locale ? resolveConfiguredLocale(opts.locale) : undefined;
135
+ const relations = await repo.list(locale);
131
136
  return { success: true, data: { relations } };
132
137
  } catch {
133
138
  return {
@@ -12,6 +12,7 @@ import { ulid } from "ulidx";
12
12
 
13
13
  import { TaxonomyRepository } from "../../database/repositories/taxonomy.js";
14
14
  import type { Database, TaxonomyDefTable } from "../../database/types.js";
15
+ import { resolveConfiguredLocale } from "../../i18n/config.js";
15
16
  import { invalidateTaxonomyDefsCache, invalidateTermCache } from "../../taxonomies/index.js";
16
17
  import { fetchVisibleTermCounts } from "../../taxonomies/term-counts.js";
17
18
  import type { ApiResult } from "../types.js";
@@ -174,8 +175,9 @@ export async function handleTaxonomyList(
174
175
  options: { locale?: string } = {},
175
176
  ): Promise<ApiResult<TaxonomyListResponse>> {
176
177
  try {
178
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
177
179
  let query = db.selectFrom("_emdash_taxonomy_defs").selectAll();
178
- if (options.locale !== undefined) query = query.where("locale", "=", options.locale);
180
+ if (locale !== undefined) query = query.where("locale", "=", locale);
179
181
  const [rows, collectionRows] = await Promise.all([
180
182
  query.execute(),
181
183
  db.selectFrom("_emdash_collections").select("slug").execute(),
@@ -216,6 +218,7 @@ export async function handleTaxonomyCreate(
216
218
  },
217
219
  ): Promise<ApiResult<{ taxonomy: TaxonomyDef }>> {
218
220
  try {
221
+ const locale = input.locale ? resolveConfiguredLocale(input.locale) : undefined;
219
222
  if (!NAME_PATTERN.test(input.name)) {
220
223
  return {
221
224
  success: false,
@@ -265,19 +268,19 @@ export async function handleTaxonomyCreate(
265
268
 
266
269
  // Duplicate guard scoped to locale (so the same name can exist in ES
267
270
  // and EN).
268
- if (input.locale !== undefined) {
271
+ if (locale !== undefined) {
269
272
  const existing = await db
270
273
  .selectFrom("_emdash_taxonomy_defs")
271
274
  .select("id")
272
275
  .where("name", "=", input.name)
273
- .where("locale", "=", input.locale)
276
+ .where("locale", "=", locale)
274
277
  .executeTakeFirst();
275
278
  if (existing) {
276
279
  return {
277
280
  success: false,
278
281
  error: {
279
282
  code: "CONFLICT",
280
- message: `Taxonomy '${input.name}' already exists in locale '${input.locale}'`,
283
+ message: `Taxonomy '${input.name}' already exists in locale '${locale}'`,
281
284
  },
282
285
  };
283
286
  }
@@ -293,7 +296,7 @@ export async function handleTaxonomyCreate(
293
296
  label_singular: input.labelSingular ?? null,
294
297
  hierarchical: input.hierarchical ? 1 : 0,
295
298
  collections: JSON.stringify(collections),
296
- ...(input.locale !== undefined ? { locale: input.locale } : {}),
299
+ ...(locale !== undefined ? { locale } : {}),
297
300
  translation_group: translationGroup ?? id,
298
301
  })
299
302
  .execute();
@@ -390,7 +393,8 @@ export async function handleTermList(
390
393
  if (!lookup.success) return lookup;
391
394
 
392
395
  const repo = new TaxonomyRepository(db);
393
- const terms = await repo.findByName(taxonomyName, { locale: options.locale });
396
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
397
+ const terms = await repo.findByName(taxonomyName, { locale });
394
398
 
395
399
  // Counts match what visitors see on the public site: published (or
396
400
  // scheduled-and-due) entries that aren't soft-deleted, scoped to the
@@ -535,6 +539,7 @@ export async function handleTermCreate(
535
539
  },
536
540
  ): Promise<ApiResult<TermResponse>> {
537
541
  try {
542
+ const locale = input.locale ? resolveConfiguredLocale(input.locale) : undefined;
538
543
  // Taxonomy definitions are per-locale, but terms can exist in any locale
539
544
  // regardless of whether the def has been translated there. Look up the
540
545
  // def across all locales — we only care that it *exists*.
@@ -548,14 +553,14 @@ export async function handleTermCreate(
548
553
  input.parentId === "" || input.parentId === undefined ? undefined : input.parentId;
549
554
 
550
555
  // Conflict check is scoped to locale (per-locale slugs are unique).
551
- const existing = await repo.findBySlug(taxonomyName, input.slug, input.locale);
556
+ const existing = await repo.findBySlug(taxonomyName, input.slug, locale);
552
557
  if (existing) {
553
558
  return {
554
559
  success: false,
555
560
  error: {
556
561
  code: "CONFLICT",
557
- message: input.locale
558
- ? `Term '${input.slug}' already exists in '${taxonomyName}' (${input.locale})`
562
+ message: locale
563
+ ? `Term '${input.slug}' already exists in '${taxonomyName}' (${locale})`
559
564
  : `Term with slug '${input.slug}' already exists in taxonomy '${taxonomyName}'`,
560
565
  },
561
566
  };
@@ -594,7 +599,7 @@ export async function handleTermCreate(
594
599
  label: input.label,
595
600
  parentId: parentId ?? undefined,
596
601
  data: input.description ? { description: input.description } : undefined,
597
- locale: input.locale,
602
+ locale,
598
603
  translationOf: input.translationOf,
599
604
  });
600
605
 
@@ -635,7 +640,8 @@ export async function handleTermGet(
635
640
  ): Promise<ApiResult<TermGetResponse>> {
636
641
  try {
637
642
  const repo = new TaxonomyRepository(db);
638
- const term = await repo.findBySlug(taxonomyName, termSlug, options.locale);
643
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
644
+ const term = await repo.findBySlug(taxonomyName, termSlug, locale);
639
645
 
640
646
  if (!term) {
641
647
  return {
@@ -743,7 +749,8 @@ export async function handleTermUpdate(
743
749
  ): Promise<ApiResult<TermResponse>> {
744
750
  try {
745
751
  const repo = new TaxonomyRepository(db);
746
- const term = await repo.findBySlug(taxonomyName, termSlug, options.locale);
752
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
753
+ const term = await repo.findBySlug(taxonomyName, termSlug, locale);
747
754
 
748
755
  if (!term) {
749
756
  return {
@@ -763,7 +770,7 @@ export async function handleTermUpdate(
763
770
 
764
771
  // Check if new slug conflicts (per-locale uniqueness).
765
772
  if (newSlug !== undefined && newSlug !== termSlug) {
766
- const existing = await repo.findBySlug(taxonomyName, newSlug, options.locale);
773
+ const existing = await repo.findBySlug(taxonomyName, newSlug, locale);
767
774
  if (existing && existing.id !== term.id) {
768
775
  return {
769
776
  success: false,
@@ -832,7 +839,8 @@ export async function handleTermDelete(
832
839
  ): Promise<ApiResult<{ deleted: true }>> {
833
840
  try {
834
841
  const repo = new TaxonomyRepository(db);
835
- const term = await repo.findBySlug(taxonomyName, termSlug, options.locale);
842
+ const locale = options.locale ? resolveConfiguredLocale(options.locale) : undefined;
843
+ const term = await repo.findBySlug(taxonomyName, termSlug, locale);
836
844
 
837
845
  if (!term) {
838
846
  return {
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
 
3
- import { cursorPaginationQuery, httpUrl } from "./common.js";
3
+ import { cursorPaginationQuery, httpUrl, localeCode } from "./common.js";
4
4
 
5
5
  /** Slug pattern: lowercase letters, digits, and hyphens; must start with a letter */
6
6
  const bylineSlugPattern = /^[a-z][a-z0-9-]*$/;
@@ -81,7 +81,7 @@ export const bylinesListQuery = cursorPaginationQuery
81
81
  * Rejects empty strings so the picker can't silently fetch the
82
82
  * unfiltered list when the admin URL has `?locale=` with no value.
83
83
  */
84
- locale: z.string().min(1).optional(),
84
+ locale: localeCode.optional(),
85
85
  })
86
86
  .meta({ id: "BylinesListQuery" });
87
87
 
@@ -102,7 +102,7 @@ export const bylineCreateBody = z
102
102
  * configured `defaultLocale`) is used. Rejects empty strings — an
103
103
  * empty locale would create rows no resolver requests.
104
104
  */
105
- locale: z.string().min(1).optional(),
105
+ locale: localeCode.optional(),
106
106
  /**
107
107
  * When set, the new row joins the source byline's translation_group
108
108
  * rather than minting a fresh one. Requires `locale`.
@@ -126,7 +126,7 @@ export const bylineCreateBody = z
126
126
 
127
127
  export const bylineTranslationCreateBody = z
128
128
  .object({
129
- locale: z.string().min(1),
129
+ locale: localeCode,
130
130
  slug: z
131
131
  .string()
132
132
  .min(1)
@@ -53,16 +53,17 @@ export const httpUrl = z
53
53
  .url()
54
54
  .refine((url) => HTTP_SCHEME_RE.test(url), "URL must use http or https");
55
55
 
56
- /** BCP 47 locale code — language with optional script/region subtags (e.g. en, en-US, pt-BR, es-419, zh-Hant) */
57
- export const localeCode = z
58
- .string()
59
- .regex(/^[a-z]{2,3}(-[a-z0-9]{2,8})*$/i, "Invalid locale code")
60
- .transform((v) => v.toLowerCase());
56
+ /**
57
+ * BCP 47 locale code — language with optional script/region subtags (e.g. en, en-US, pt-BR, es-419, zh-Hant).
58
+ * Validation is case-insensitive, but the value is preserved verbatim because the site config, stored
59
+ * `locale` columns, and public query path all keep the raw BCP-47 casing.
60
+ */
61
+ export const localeCode = z.string().regex(/^[a-z]{2,3}(-[a-z0-9]{2,8})*$/i, "Invalid locale code");
61
62
 
62
63
  /** Shared `?locale=xx` query shape for endpoints that filter by locale. */
63
64
  export const localeFilterQuery = z
64
65
  .object({
65
- locale: z.string().min(1).optional(),
66
+ locale: localeCode.optional(),
66
67
  })
67
68
  .meta({ id: "LocaleFilterQuery" });
68
69
 
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  import { isSafeHref } from "../../utils/url.js";
4
+ import { localeCode } from "./common.js";
4
5
 
5
6
  // ---------------------------------------------------------------------------
6
7
  // Menus: Input schemas
@@ -24,7 +25,7 @@ export const createMenuBody = z
24
25
  .object({
25
26
  name: z.string().min(1),
26
27
  label: z.string().min(1),
27
- locale: z.string().min(1).optional(),
28
+ locale: localeCode.optional(),
28
29
  /** When set, clones the items from the source menu. The new menu joins
29
30
  * the source's translation_group. */
30
31
  translationOf: z.string().min(1).optional(),
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
+ import { localeCode } from "./common.js";
4
+
3
5
  const slugPattern = /^[a-z][a-z0-9_]*$/;
4
6
  const collectionSlug = z
5
7
  .string()
@@ -18,7 +20,7 @@ export const createRelationBody = z
18
20
  childCollection: collectionSlug.optional(),
19
21
  parentLabel: z.string().min(1).max(200),
20
22
  childLabel: z.string().min(1).max(200),
21
- locale: z.string().min(1).optional(),
23
+ locale: localeCode.optional(),
22
24
  translationOf: z.string().min(1).optional(),
23
25
  })
24
26
  // A translation inherits its structural fields (name, parentCollection,
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
+ import { localeCode } from "./common.js";
4
+
3
5
  // ---------------------------------------------------------------------------
4
6
  // Taxonomy definitions: Input schemas
5
7
  // ---------------------------------------------------------------------------
@@ -24,7 +26,7 @@ export const createTaxonomyDefBody = z
24
26
  .max(100)
25
27
  .optional()
26
28
  .default([]),
27
- locale: z.string().min(1).optional(),
29
+ locale: localeCode.optional(),
28
30
  translationOf: z.string().min(1).optional(),
29
31
  })
30
32
  .meta({ id: "CreateTaxonomyDefBody" });
@@ -39,7 +41,7 @@ export const createTermBody = z
39
41
  label: z.string().min(1),
40
42
  parentId: z.string().nullish(),
41
43
  description: z.string().optional(),
42
- locale: z.string().min(1).optional(),
44
+ locale: localeCode.optional(),
43
45
  translationOf: z.string().min(1).optional(),
44
46
  })
45
47
  .meta({ id: "CreateTermBody" });
@@ -8,7 +8,7 @@ import { bylineCreateBody, bylinesListQuery } from "#api/schemas.js";
8
8
  import { invalidateBylineCache } from "#bylines/index.js";
9
9
  import { BylineRepository } from "#db/repositories/byline.js";
10
10
 
11
- import { getI18nConfig } from "../../../../../i18n/config.js";
11
+ import { getI18nConfig, resolveConfiguredLocale } from "../../../../../i18n/config.js";
12
12
 
13
13
  export const prerender = false;
14
14
 
@@ -25,13 +25,10 @@ export const GET: APIRoute = async ({ url, locals }) => {
25
25
  const query = parseQuery(url, bylinesListQuery);
26
26
  if (isParseError(query)) return query;
27
27
 
28
+ const locale = query.locale ? resolveConfiguredLocale(query.locale) : undefined;
28
29
  const i18n = getI18nConfig();
29
- if (query.locale && i18n && !i18n.locales.includes(query.locale)) {
30
- return apiError(
31
- "VALIDATION_ERROR",
32
- `Locale "${query.locale}" is not configured for this site`,
33
- 400,
34
- );
30
+ if (locale && i18n && !i18n.locales.includes(locale)) {
31
+ return apiError("VALIDATION_ERROR", `Locale "${locale}" is not configured for this site`, 400);
35
32
  }
36
33
 
37
34
  try {
@@ -40,7 +37,7 @@ export const GET: APIRoute = async ({ url, locals }) => {
40
37
  search: query.search,
41
38
  isGuest: query.isGuest,
42
39
  userId: query.userId,
43
- locale: query.locale,
40
+ locale,
44
41
  cursor: query.cursor,
45
42
  limit: query.limit,
46
43
  });