emdash 0.24.1 → 0.26.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 (422) hide show
  1. package/dist/api/route-utils.d.mts +3 -3
  2. package/dist/api/route-utils.mjs +12 -11
  3. package/dist/api/route-utils.mjs.map +1 -1
  4. package/dist/api/schemas/index.d.mts +1 -1
  5. package/dist/api/schemas/index.mjs +2 -2
  6. package/dist/{api-d_0wi9fW.mjs → api-BMfveMoB.mjs} +24 -20
  7. package/dist/api-BMfveMoB.mjs.map +1 -0
  8. package/dist/{apply-BE9UaWX0.mjs → apply--IVNSdXn.mjs} +19 -16
  9. package/dist/apply--IVNSdXn.mjs.map +1 -0
  10. package/dist/astro/index.d.mts +9 -9
  11. package/dist/astro/index.mjs +3 -2
  12. package/dist/astro/index.mjs.map +1 -1
  13. package/dist/astro/middleware/auth.d.mts +8 -8
  14. package/dist/astro/middleware/auth.mjs +2 -2
  15. package/dist/astro/middleware/redirect.mjs +4 -4
  16. package/dist/astro/middleware.d.mts +1 -1
  17. package/dist/astro/middleware.d.mts.map +1 -1
  18. package/dist/astro/middleware.mjs +99 -70
  19. package/dist/astro/middleware.mjs.map +1 -1
  20. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
  21. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
  22. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  23. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  24. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
  25. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +6 -6
  26. package/dist/astro/routes/api/admin/byline-fields/index.mjs +6 -6
  27. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +6 -6
  28. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -12
  29. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +12 -12
  30. package/dist/astro/routes/api/admin/bylines/index.mjs +12 -12
  31. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +10 -10
  32. package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
  33. package/dist/astro/routes/api/admin/comments/bulk.mjs +9 -9
  34. package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
  35. package/dist/astro/routes/api/admin/comments/index.mjs +9 -9
  36. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  37. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  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 +31 -31
  41. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +31 -31
  42. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +31 -31
  43. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +31 -31
  44. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +31 -31
  45. package/dist/astro/routes/api/admin/plugins/index.mjs +31 -31
  46. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  47. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +31 -31
  48. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +31 -31
  49. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +31 -31
  50. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +31 -31
  51. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +32 -32
  52. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +31 -31
  53. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +32 -32
  54. package/dist/astro/routes/api/admin/plugins/updates.mjs +31 -31
  55. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +31 -31
  56. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  57. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +31 -31
  58. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  59. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  60. package/dist/astro/routes/api/admin/users/_id_/index.mjs +5 -5
  61. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  62. package/dist/astro/routes/api/admin/users/index.mjs +5 -5
  63. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  64. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  65. package/dist/astro/routes/api/auth/invite/complete.mjs +5 -5
  66. package/dist/astro/routes/api/auth/invite/index.mjs +5 -5
  67. package/dist/astro/routes/api/auth/invite/register-options.mjs +5 -5
  68. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  69. package/dist/astro/routes/api/auth/magic-link/send.mjs +6 -6
  70. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  71. package/dist/astro/routes/api/auth/me.mjs +6 -6
  72. package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
  73. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  74. package/dist/astro/routes/api/auth/passkey/options.mjs +6 -6
  75. package/dist/astro/routes/api/auth/passkey/register/options.mjs +5 -5
  76. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +5 -5
  77. package/dist/astro/routes/api/auth/passkey/verify.mjs +5 -5
  78. package/dist/astro/routes/api/auth/signup/complete.mjs +5 -5
  79. package/dist/astro/routes/api/auth/signup/request.mjs +6 -6
  80. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  81. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +9 -9
  82. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +9 -9
  83. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  84. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  85. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  86. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  87. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +6 -6
  88. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +6 -6
  89. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  90. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  91. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +6 -6
  92. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +11 -11
  93. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  94. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  95. package/dist/astro/routes/api/content/_collection_/_id_.mjs +6 -6
  96. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  97. package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
  98. package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
  99. package/dist/astro/routes/api/dashboard.mjs +8 -8
  100. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  101. package/dist/astro/routes/api/import/probe.d.mts +2 -2
  102. package/dist/astro/routes/api/import/probe.mjs +6 -6
  103. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  104. package/dist/astro/routes/api/import/wordpress/execute.d.mts +8 -8
  105. package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
  106. package/dist/astro/routes/api/import/wordpress/media.d.mts +13 -1
  107. package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
  108. package/dist/astro/routes/api/import/wordpress/media.mjs +13 -9
  109. package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
  110. package/dist/astro/routes/api/import/wordpress/prepare.mjs +8 -8
  111. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
  112. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +6 -6
  113. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +9 -9
  114. package/dist/astro/routes/api/manifest.mjs +3 -3
  115. package/dist/astro/routes/api/mcp.mjs +36 -36
  116. package/dist/astro/routes/api/media/_id_/confirm.d.mts.map +1 -1
  117. package/dist/astro/routes/api/media/_id_/confirm.mjs +44 -8
  118. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  119. package/dist/astro/routes/api/media/_id_.mjs +6 -6
  120. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  121. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  122. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  123. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  124. package/dist/astro/routes/api/media/upload-url.mjs +8 -8
  125. package/dist/astro/routes/api/media.mjs +22 -21
  126. package/dist/astro/routes/api/media.mjs.map +1 -1
  127. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +8 -8
  128. package/dist/astro/routes/api/menus/_name_/items.mjs +8 -8
  129. package/dist/astro/routes/api/menus/_name_/reorder.mjs +8 -8
  130. package/dist/astro/routes/api/menus/_name_/translations.mjs +8 -8
  131. package/dist/astro/routes/api/menus/_name_.mjs +8 -8
  132. package/dist/astro/routes/api/menus/index.mjs +8 -8
  133. package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
  134. package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
  135. package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
  136. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  137. package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
  138. package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
  139. package/dist/astro/routes/api/oauth/token.mjs +2 -2
  140. package/dist/astro/routes/api/openapi.json.mjs +3 -3
  141. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
  142. package/dist/astro/routes/api/redirects/404s/index.mjs +8 -8
  143. package/dist/astro/routes/api/redirects/404s/summary.mjs +8 -8
  144. package/dist/astro/routes/api/redirects/_id_.mjs +9 -9
  145. package/dist/astro/routes/api/redirects/index.mjs +9 -9
  146. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  147. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  148. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +31 -31
  149. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +31 -31
  150. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +31 -31
  151. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +31 -31
  152. package/dist/astro/routes/api/schema/collections/index.mjs +31 -31
  153. package/dist/astro/routes/api/schema/index.mjs +6 -6
  154. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +31 -31
  155. package/dist/astro/routes/api/schema/orphans/index.mjs +31 -31
  156. package/dist/astro/routes/api/search/enable.mjs +9 -9
  157. package/dist/astro/routes/api/search/index.mjs +8 -8
  158. package/dist/astro/routes/api/search/rebuild.mjs +9 -9
  159. package/dist/astro/routes/api/search/stats.mjs +6 -6
  160. package/dist/astro/routes/api/search/suggest.mjs +8 -8
  161. package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
  162. package/dist/astro/routes/api/sections/index.mjs +8 -8
  163. package/dist/astro/routes/api/settings/email.mjs +4 -4
  164. package/dist/astro/routes/api/settings.mjs +11 -11
  165. package/dist/astro/routes/api/setup/admin-verify.mjs +5 -5
  166. package/dist/astro/routes/api/setup/admin.mjs +5 -5
  167. package/dist/astro/routes/api/setup/dev-bypass.mjs +17 -17
  168. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  169. package/dist/astro/routes/api/setup/index.mjs +20 -20
  170. package/dist/astro/routes/api/setup/status.mjs +3 -3
  171. package/dist/astro/routes/api/snapshot.mjs +3 -3
  172. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -11
  173. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -11
  174. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -11
  175. package/dist/astro/routes/api/taxonomies/index.mjs +11 -11
  176. package/dist/astro/routes/api/themes/preview.mjs +3 -3
  177. package/dist/astro/routes/api/typegen.mjs +5 -5
  178. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
  179. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +8 -8
  180. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +8 -8
  181. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  182. package/dist/astro/routes/api/widget-areas/index.mjs +8 -8
  183. package/dist/astro/routes/api/widget-components.mjs +2 -2
  184. package/dist/astro/routes/robots.txt.mjs +5 -5
  185. package/dist/astro/routes/sitemap-_collection_.xml.mjs +11 -10
  186. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  187. package/dist/astro/routes/sitemap.xml.mjs +6 -6
  188. package/dist/astro/types.d.mts +10 -10
  189. package/dist/{authorize-B__u-iZJ.mjs → authorize-BZnOGJFo.mjs} +2 -2
  190. package/dist/{authorize-B__u-iZJ.mjs.map → authorize-BZnOGJFo.mjs.map} +1 -1
  191. package/dist/{byline-C-YajfV7.mjs → byline-LL8lyqdb.mjs} +6 -6
  192. package/dist/{byline-C-YajfV7.mjs.map → byline-LL8lyqdb.mjs.map} +1 -1
  193. package/dist/{byline-fields-Ca56gaT6.d.mts → byline-fields-KD3JFRuH.d.mts} +24 -6
  194. package/dist/byline-fields-KD3JFRuH.d.mts.map +1 -0
  195. package/dist/{byline-fields-B-KXkg5R.mjs → byline-fields-eyJMblSr.mjs} +2 -2
  196. package/dist/{byline-fields-B-KXkg5R.mjs.map → byline-fields-eyJMblSr.mjs.map} +1 -1
  197. package/dist/{bylines-D4KADDAi.mjs → bylines-BmJ7Vg7J.mjs} +4 -4
  198. package/dist/{bylines-D4KADDAi.mjs.map → bylines-BmJ7Vg7J.mjs.map} +1 -1
  199. package/dist/{bylines-BMtJnPnG.mjs → bylines-DE47vP8d.mjs} +6 -6
  200. package/dist/{bylines-BMtJnPnG.mjs.map → bylines-DE47vP8d.mjs.map} +1 -1
  201. package/dist/{cache-BUJe2w6Z.mjs → cache-D9jF5bYw.mjs} +2 -2
  202. package/dist/{cache-BUJe2w6Z.mjs.map → cache-D9jF5bYw.mjs.map} +1 -1
  203. package/dist/{chunks-BHgcHh2C.mjs → chunks-OCG163rN.mjs} +2 -2
  204. package/dist/{chunks-BHgcHh2C.mjs.map → chunks-OCG163rN.mjs.map} +1 -1
  205. package/dist/cli/index.mjs +17 -17
  206. package/dist/{comment-BDx50EX5.mjs → comment-D9BLDpZI.mjs} +3 -3
  207. package/dist/{comment-BDx50EX5.mjs.map → comment-D9BLDpZI.mjs.map} +1 -1
  208. package/dist/{comment-reaction-Gv_pgfI3.mjs → comment-reaction-Drl9SkNu.mjs} +2 -2
  209. package/dist/{comment-reaction-Gv_pgfI3.mjs.map → comment-reaction-Drl9SkNu.mjs.map} +1 -1
  210. package/dist/{comments-CXHuQ6jI.mjs → comments-BjJ9SKGU.mjs} +3 -3
  211. package/dist/{comments-CXHuQ6jI.mjs.map → comments-BjJ9SKGU.mjs.map} +1 -1
  212. package/dist/{content-PoVGKjp0.mjs → content-ncUWWjna.mjs} +10 -8
  213. package/dist/content-ncUWWjna.mjs.map +1 -0
  214. package/dist/{context-Dvws41_A.mjs → context-C_g-iJVA.mjs} +14 -8
  215. package/dist/context-C_g-iJVA.mjs.map +1 -0
  216. package/dist/{dashboard-DsYU8JKS.mjs → dashboard-CotyqQrW.mjs} +4 -4
  217. package/dist/{dashboard-DsYU8JKS.mjs.map → dashboard-CotyqQrW.mjs.map} +1 -1
  218. package/dist/db/index.d.mts +2 -2
  219. package/dist/db/index.mjs +1 -1
  220. package/dist/{db-errors-UXB1CK42.mjs → db-errors-CK46D-ly.mjs} +1 -1
  221. package/dist/{db-errors-UXB1CK42.mjs.map → db-errors-CK46D-ly.mjs.map} +1 -1
  222. package/dist/{placeholder-B0s2mWu5.mjs → enrich-BQ0mxJRs.mjs} +61 -10
  223. package/dist/enrich-BQ0mxJRs.mjs.map +1 -0
  224. package/dist/{error-lq_fL-RS.mjs → error-rVGiqWDt.mjs} +3 -2
  225. package/dist/error-rVGiqWDt.mjs.map +1 -0
  226. package/dist/{field-defs-cache-CYYx8xlG.mjs → field-defs-cache-ljXzJCfc.mjs} +2 -2
  227. package/dist/{field-defs-cache-CYYx8xlG.mjs.map → field-defs-cache-ljXzJCfc.mjs.map} +1 -1
  228. package/dist/{fts-manager-DW_NR-Fs.mjs → fts-manager-BbCCbEKA.mjs} +2 -2
  229. package/dist/{fts-manager-DW_NR-Fs.mjs.map → fts-manager-BbCCbEKA.mjs.map} +1 -1
  230. package/dist/{index-BroXT-vD.d.mts → index-BRNabKrx.d.mts} +53 -15
  231. package/dist/index-BRNabKrx.d.mts.map +1 -0
  232. package/dist/{index-qbL02uhs.d.mts → index-Dt0OKJB4.d.mts} +2 -2
  233. package/dist/{index-qbL02uhs.d.mts.map → index-Dt0OKJB4.d.mts.map} +1 -1
  234. package/dist/index.d.mts +12 -12
  235. package/dist/index.mjs +43 -43
  236. package/dist/{load-DMFi4n1p.mjs → load-DMjXghbZ.mjs} +2 -2
  237. package/dist/{load-DMFi4n1p.mjs.map → load-DMjXghbZ.mjs.map} +1 -1
  238. package/dist/{loader-BR2SgfEc.mjs → loader-BA_zhkS_.mjs} +82 -19
  239. package/dist/loader-BA_zhkS_.mjs.map +1 -0
  240. package/dist/{manifest-schema-BWKnQYQF.mjs → manifest-schema-kPGX7VS-.mjs} +4 -1
  241. package/dist/manifest-schema-kPGX7VS-.mjs.map +1 -0
  242. package/dist/media/image-endpoint.mjs +1 -1
  243. package/dist/media/index.d.mts +45 -2
  244. package/dist/media/index.d.mts.map +1 -1
  245. package/dist/media/index.mjs +5 -3
  246. package/dist/media/index.mjs.map +1 -1
  247. package/dist/media/local-runtime.d.mts +9 -9
  248. package/dist/media/local-runtime.d.mts.map +1 -1
  249. package/dist/media/local-runtime.mjs +17 -5
  250. package/dist/media/local-runtime.mjs.map +1 -1
  251. package/dist/{media-CTjJoJTh.mjs → media-C5t_eCnk.mjs} +4 -2
  252. package/dist/media-C5t_eCnk.mjs.map +1 -0
  253. package/dist/{media-allowlist-B77I5_bw.mjs → media-allowlist-Du8t1u6a.mjs} +2 -2
  254. package/dist/{media-allowlist-B77I5_bw.mjs.map → media-allowlist-Du8t1u6a.mjs.map} +1 -1
  255. package/dist/{menus-8O6xNcYW.mjs → menus-CBb6UrMU.mjs} +3 -3
  256. package/dist/{menus-8O6xNcYW.mjs.map → menus-CBb6UrMU.mjs.map} +1 -1
  257. package/dist/{menus-GvRSpgIA.mjs → menus-CYH09a41.mjs} +65 -41
  258. package/dist/menus-CYH09a41.mjs.map +1 -0
  259. package/dist/{mime-BTzvzJ8M.mjs → mime-CCEzze7W.mjs} +1 -1
  260. package/dist/{mime-BTzvzJ8M.mjs.map → mime-CCEzze7W.mjs.map} +1 -1
  261. package/dist/{normalize-ClbNIenm.mjs → normalize-CeglrBT9.mjs} +11 -2
  262. package/dist/normalize-CeglrBT9.mjs.map +1 -0
  263. package/dist/{object-cache-SEb2IM4Z.mjs → object-cache-CHbHv83-.mjs} +8 -4
  264. package/dist/object-cache-CHbHv83-.mjs.map +1 -0
  265. package/dist/{options-CqECG0gM.d.mts → options-41nCWqi9.d.mts} +3 -3
  266. package/dist/{options-CqECG0gM.d.mts.map → options-41nCWqi9.d.mts.map} +1 -1
  267. package/dist/page/index.d.mts +5 -2
  268. package/dist/page/index.d.mts.map +1 -1
  269. package/dist/page/index.mjs +3 -2
  270. package/dist/page/index.mjs.map +1 -1
  271. package/dist/{parse-DHI873qU.mjs → parse-BwAdS7FZ.mjs} +2 -2
  272. package/dist/{parse-DHI873qU.mjs.map → parse-BwAdS7FZ.mjs.map} +1 -1
  273. package/dist/{placeholder-QoI4gEzP.d.mts → placeholder-Cuce9U-m.d.mts} +20 -5
  274. package/dist/placeholder-Cuce9U-m.d.mts.map +1 -0
  275. package/dist/plugin-types.d.mts +5 -2
  276. package/dist/plugin-types.d.mts.map +1 -1
  277. package/dist/plugin-utils.d.mts +8 -8
  278. package/dist/plugins/adapt-sandbox-entry.d.mts +8 -8
  279. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  280. package/dist/{query-DZdlS6Cu.mjs → query-BpQb-blG.mjs} +15 -15
  281. package/dist/{query-DZdlS6Cu.mjs.map → query-BpQb-blG.mjs.map} +1 -1
  282. package/dist/{rate-limit-D1Y6VFVP.mjs → rate-limit-Dap6HMNM.mjs} +2 -2
  283. package/dist/{rate-limit-D1Y6VFVP.mjs.map → rate-limit-Dap6HMNM.mjs.map} +1 -1
  284. package/dist/{redirect-C1KVGYWe.mjs → redirect-_M0trWoo.mjs} +2 -2
  285. package/dist/{redirect-C1KVGYWe.mjs.map → redirect-_M0trWoo.mjs.map} +1 -1
  286. package/dist/{redirects-x9eFPeyi.mjs → redirects-Dcbuisoj.mjs} +2 -1
  287. package/dist/redirects-Dcbuisoj.mjs.map +1 -0
  288. package/dist/{redirects-DgnypVir.mjs → redirects-w0V9vj_j.mjs} +3 -3
  289. package/dist/{redirects-DgnypVir.mjs.map → redirects-w0V9vj_j.mjs.map} +1 -1
  290. package/dist/{registry-CJkCubOJ.mjs → registry-g8yYt4cC.mjs} +14 -4
  291. package/dist/registry-g8yYt4cC.mjs.map +1 -0
  292. package/dist/{runner-CEtq-6Sm.d.mts → runner-BbR3DfrL.d.mts} +2 -2
  293. package/dist/{runner-CEtq-6Sm.d.mts.map → runner-BbR3DfrL.d.mts.map} +1 -1
  294. package/dist/{runner-BsquExlK.mjs → runner-DPdt_Wpa.mjs} +250 -178
  295. package/dist/runner-DPdt_Wpa.mjs.map +1 -0
  296. package/dist/runtime.d.mts +9 -9
  297. package/dist/runtime.mjs +2 -2
  298. package/dist/{schema-DMjXuvNU.mjs → schema-DoLd3j2e.mjs} +5 -5
  299. package/dist/{schema-DMjXuvNU.mjs.map → schema-DoLd3j2e.mjs.map} +1 -1
  300. package/dist/{search-D1wKk3lc.mjs → search-BR0ZeZmh.mjs} +4 -4
  301. package/dist/{search-D1wKk3lc.mjs.map → search-BR0ZeZmh.mjs.map} +1 -1
  302. package/dist/{sections-CCMic8dB.mjs → sections-5J2YJPqJ.mjs} +3 -3
  303. package/dist/{sections-CCMic8dB.mjs.map → sections-5J2YJPqJ.mjs.map} +1 -1
  304. package/dist/seed/index.d.mts +2 -2
  305. package/dist/seed/index.mjs +15 -15
  306. package/dist/seo/index.d.mts +1 -1
  307. package/dist/{seo-DCEF-AVr.mjs → seo-BkhuuaaE.mjs} +25 -3
  308. package/dist/seo-BkhuuaaE.mjs.map +1 -0
  309. package/dist/{seo-3QrZ1EPT.mjs → seo-DKeXHkzE.mjs} +3 -3
  310. package/dist/{seo-3QrZ1EPT.mjs.map → seo-DKeXHkzE.mjs.map} +1 -1
  311. package/dist/{service-CQHK_10l.mjs → service-BbO2s8gT.mjs} +2 -2
  312. package/dist/{service-CQHK_10l.mjs.map → service-BbO2s8gT.mjs.map} +1 -1
  313. package/dist/{settings-C3Yi5Co3.mjs → settings-BMNr9Dmi.mjs} +3 -3
  314. package/dist/{settings-C3Yi5Co3.mjs.map → settings-BMNr9Dmi.mjs.map} +1 -1
  315. package/dist/{settings-D9rvaLuY.mjs → settings-DJeki-97.mjs} +4 -4
  316. package/dist/{settings-D9rvaLuY.mjs.map → settings-DJeki-97.mjs.map} +1 -1
  317. package/dist/{taxonomies-DhLaNJji.mjs → taxonomies-CM_fUqLU.mjs} +5 -4
  318. package/dist/taxonomies-CM_fUqLU.mjs.map +1 -0
  319. package/dist/{taxonomies-CurpLtNC.mjs → taxonomies-D13i2ctt.mjs} +76 -21
  320. package/dist/taxonomies-D13i2ctt.mjs.map +1 -0
  321. package/dist/{taxonomy-BBUlGug5.mjs → taxonomy-BU0LjdVb.mjs} +4 -4
  322. package/dist/{taxonomy-BBUlGug5.mjs.map → taxonomy-BU0LjdVb.mjs.map} +1 -1
  323. package/dist/{types-TTx8uP7V.d.mts → types-BYnDBVwe.d.mts} +27 -5
  324. package/dist/{types-TTx8uP7V.d.mts.map → types-BYnDBVwe.d.mts.map} +1 -1
  325. package/dist/{types-CTLJYMhN.d.mts → types-CNlaBFzx.d.mts} +11 -1
  326. package/dist/{types-CTLJYMhN.d.mts.map → types-CNlaBFzx.d.mts.map} +1 -1
  327. package/dist/{types-CmI1OyQg.mjs → types-CZgK3EMW.mjs} +2 -2
  328. package/dist/{types-CmI1OyQg.mjs.map → types-CZgK3EMW.mjs.map} +1 -1
  329. package/dist/{types-BWh2MjmA.d.mts → types-ETmO_jQr.d.mts} +2 -2
  330. package/dist/{types-BWh2MjmA.d.mts.map → types-ETmO_jQr.d.mts.map} +1 -1
  331. package/dist/types-tM44hEcf.mjs.map +1 -1
  332. package/dist/{types-B-gIxFCE.d.mts → types-xIfVRNLp.d.mts} +38 -1
  333. package/dist/types-xIfVRNLp.d.mts.map +1 -0
  334. package/dist/{user-JAuc8VUf.mjs → user-DkG2jKCp.mjs} +3 -3
  335. package/dist/{user-JAuc8VUf.mjs.map → user-DkG2jKCp.mjs.map} +1 -1
  336. package/dist/{validate-DOFrIb-c.d.mts → validate-CG0V0wNR.d.mts} +3 -3
  337. package/dist/{validate-DOFrIb-c.d.mts.map → validate-CG0V0wNR.d.mts.map} +1 -1
  338. package/dist/{validate-BzXHWnkV.mjs → validate-CuJ1Ps9j.mjs} +2 -2
  339. package/dist/{validate-BzXHWnkV.mjs.map → validate-CuJ1Ps9j.mjs.map} +1 -1
  340. package/dist/{validation-CY1gk4Ar.mjs → validation-CwUfTdGF.mjs} +6 -6
  341. package/dist/{validation-CY1gk4Ar.mjs.map → validation-CwUfTdGF.mjs.map} +1 -1
  342. package/dist/version-BoNBItP_.mjs +7 -0
  343. package/dist/{version-C0JPFUG8.mjs.map → version-BoNBItP_.mjs.map} +1 -1
  344. package/dist/{widgets-Dri0spI4.mjs → widgets-BfOZ-ajY.mjs} +2 -2
  345. package/dist/{widgets-Dri0spI4.mjs.map → widgets-BfOZ-ajY.mjs.map} +1 -1
  346. package/dist/{zod-generator-xEHODMJf.mjs → zod-generator-gFsRuNNy.mjs} +6 -3
  347. package/dist/zod-generator-gFsRuNNy.mjs.map +1 -0
  348. package/package.json +6 -6
  349. package/src/api/errors.ts +1 -0
  350. package/src/api/handlers/content.ts +4 -4
  351. package/src/api/handlers/seo.ts +30 -2
  352. package/src/api/handlers/taxonomies.ts +5 -1
  353. package/src/api/schemas/schema.ts +1 -0
  354. package/src/astro/integration/virtual-modules.ts +15 -0
  355. package/src/astro/middleware.ts +14 -1
  356. package/src/astro/routes/api/import/wordpress/media.ts +9 -3
  357. package/src/astro/routes/api/media/[id]/confirm.ts +62 -2
  358. package/src/astro/routes/api/media.ts +17 -19
  359. package/src/astro/routes/sitemap-[collection].xml.ts +5 -4
  360. package/src/components/EmDashHead.astro +23 -3
  361. package/src/components/EmDashImage.astro +4 -7
  362. package/src/components/Image.astro +14 -4
  363. package/src/components/InlinePortableTextEditor.tsx +48 -1
  364. package/src/components/JsonLdScript.astro +9 -0
  365. package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +16 -0
  366. package/src/database/migrations/046_media_usage_index.ts +115 -0
  367. package/src/database/migrations/047_restore_taxonomy_parent_index.ts +29 -0
  368. package/src/database/migrations/runner.ts +4 -0
  369. package/src/database/repositories/content.ts +8 -5
  370. package/src/database/repositories/media-usage.ts +444 -0
  371. package/src/database/repositories/media.ts +9 -1
  372. package/src/database/types.ts +39 -0
  373. package/src/emdash-runtime.ts +72 -25
  374. package/src/index.ts +3 -0
  375. package/src/loader.ts +110 -31
  376. package/src/media/enrich.ts +78 -0
  377. package/src/media/index.ts +1 -0
  378. package/src/media/local-runtime.ts +19 -0
  379. package/src/media/normalize.ts +20 -1
  380. package/src/media/placeholder.ts +29 -9
  381. package/src/media/types.ts +14 -0
  382. package/src/media/usage/extractor.ts +302 -0
  383. package/src/media/usage/types.ts +46 -0
  384. package/src/object-cache/index.ts +8 -2
  385. package/src/page/metadata.ts +23 -3
  386. package/src/plugin-types.ts +12 -0
  387. package/src/plugins/context.ts +8 -0
  388. package/src/plugins/define-plugin.ts +14 -5
  389. package/src/plugins/hooks.ts +66 -33
  390. package/src/plugins/index.ts +6 -0
  391. package/src/plugins/manager.ts +33 -0
  392. package/src/plugins/manifest-schema.ts +3 -0
  393. package/src/plugins/types.ts +41 -2
  394. package/src/schema/registry.ts +25 -0
  395. package/src/schema/types.ts +10 -0
  396. package/src/schema/zod-generator.ts +9 -1
  397. package/src/seed/apply.ts +7 -0
  398. package/src/taxonomies/index.ts +121 -18
  399. package/dist/api-d_0wi9fW.mjs.map +0 -1
  400. package/dist/apply-BE9UaWX0.mjs.map +0 -1
  401. package/dist/byline-fields-Ca56gaT6.d.mts.map +0 -1
  402. package/dist/content-PoVGKjp0.mjs.map +0 -1
  403. package/dist/context-Dvws41_A.mjs.map +0 -1
  404. package/dist/error-lq_fL-RS.mjs.map +0 -1
  405. package/dist/index-BroXT-vD.d.mts.map +0 -1
  406. package/dist/loader-BR2SgfEc.mjs.map +0 -1
  407. package/dist/manifest-schema-BWKnQYQF.mjs.map +0 -1
  408. package/dist/media-CTjJoJTh.mjs.map +0 -1
  409. package/dist/menus-GvRSpgIA.mjs.map +0 -1
  410. package/dist/normalize-ClbNIenm.mjs.map +0 -1
  411. package/dist/object-cache-SEb2IM4Z.mjs.map +0 -1
  412. package/dist/placeholder-B0s2mWu5.mjs.map +0 -1
  413. package/dist/placeholder-QoI4gEzP.d.mts.map +0 -1
  414. package/dist/redirects-x9eFPeyi.mjs.map +0 -1
  415. package/dist/registry-CJkCubOJ.mjs.map +0 -1
  416. package/dist/runner-BsquExlK.mjs.map +0 -1
  417. package/dist/seo-DCEF-AVr.mjs.map +0 -1
  418. package/dist/taxonomies-CurpLtNC.mjs.map +0 -1
  419. package/dist/taxonomies-DhLaNJji.mjs.map +0 -1
  420. package/dist/types-B-gIxFCE.d.mts.map +0 -1
  421. package/dist/version-C0JPFUG8.mjs +0 -7
  422. package/dist/zod-generator-xEHODMJf.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"seo-DCEF-AVr.mjs","names":[],"sources":["../src/api/handlers/seo.ts"],"sourcesContent":["/**\n * SEO Handlers\n *\n * Business logic for sitemap generation and robots.txt.\n */\n\nimport { sql, type Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { validateIdentifier } from \"../../database/validate.js\";\nimport type { ApiResult } from \"../types.js\";\n\n/** Raw content data for sitemap generation — the route builds the actual URLs */\nexport interface SitemapContentEntry {\n\t/** Content ID (ULID) */\n\tid: string;\n\t/** Content slug, or null when the entry has no slug */\n\tslug: string | null;\n\t/** ISO date of last modification */\n\tupdatedAt: string;\n\t/**\n\t * Locale of this row (e.g. `\"en\"`, `\"fr\"`). Always present — rows in\n\t * pre-i18n databases are backfilled to the configured `defaultLocale`.\n\t */\n\tlocale: string;\n\t/**\n\t * `translation_group` ULID shared across all locale variants of the\n\t * same content. Used by the sitemap route to emit `hreflang`\n\t * alternates between siblings.\n\t */\n\ttranslationGroup: string | null;\n\t/**\n\t * Stored SEO image reference (`_emdash_seo.seo_image`), or null when\n\t * the entry has no SEO image. The route resolves it to an absolute\n\t * URL and emits it as an `<image:image>` sitemap entry.\n\t */\n\timage: string | null;\n}\n\n/** Per-collection sitemap data with entries and URL pattern */\nexport interface SitemapCollectionData {\n\t/** Collection slug (e.g., \"post\", \"page\") */\n\tcollection: string;\n\t/** URL pattern with {slug} placeholder, or null for default /{collection}/{slug} */\n\turlPattern: string | null;\n\t/** Most recent updated_at across all entries (for sitemap index lastmod) */\n\tlastmod: string;\n\t/** Individual content entries */\n\tentries: SitemapContentEntry[];\n}\n\nexport interface SitemapDataResponse {\n\tcollections: SitemapCollectionData[];\n}\n\n/** Maximum entries per sitemap (per spec) */\nconst SITEMAP_MAX_ENTRIES = 50_000;\n\n/**\n * Collect all published, indexable content across SEO-enabled collections\n * for sitemap generation, grouped by collection.\n *\n * Only includes content from collections with `has_seo = 1`.\n * Excludes content with `seo_no_index = 1` in the `_emdash_seo` table.\n *\n * Returns raw data grouped per collection. The caller (route) is\n * responsible for building absolute URLs — this handler does NOT\n * assume a URL structure.\n */\nexport async function handleSitemapData(\n\tdb: Kysely<Database>,\n\t/** When set, only return data for this collection. */\n\tcollectionSlug?: string,\n): Promise<ApiResult<SitemapDataResponse>> {\n\ttry {\n\t\t// Find SEO-enabled collections (optionally filtered)\n\t\tlet query = db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select([\"slug\", \"url_pattern\"])\n\t\t\t.where(\"has_seo\", \"=\", 1);\n\n\t\tif (collectionSlug) {\n\t\t\tquery = query.where(\"slug\", \"=\", collectionSlug);\n\t\t}\n\n\t\tconst collections = await query.execute();\n\n\t\tconst result: SitemapCollectionData[] = [];\n\n\t\tfor (const col of collections) {\n\t\t\t// Validate the slug before using it as a table name identifier.\n\t\t\t// Should always pass (slugs are validated on creation), but\n\t\t\t// guards against corrupted DB data.\n\t\t\ttry {\n\t\t\t\tvalidateIdentifier(col.slug, \"collection slug\");\n\t\t\t} catch {\n\t\t\t\tconsole.warn(`[SITEMAP] Skipping collection with invalid slug: ${col.slug}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst tableName = `ec_${col.slug}`;\n\n\t\t\t// Query published, non-deleted content.\n\t\t\t// LEFT JOIN _emdash_seo to check noindex flag.\n\t\t\t// Content without an SEO row is assumed indexable (default).\n\t\t\t// Wrapped in try/catch so a missing/broken table doesn't fail the\n\t\t\t// entire sitemap — we skip that collection and continue.\n\t\t\ttry {\n\t\t\t\tconst rows = await sql<{\n\t\t\t\t\tslug: string | null;\n\t\t\t\t\tid: string;\n\t\t\t\t\tupdated_at: string;\n\t\t\t\t\tlocale: string;\n\t\t\t\t\ttranslation_group: string | null;\n\t\t\t\t\tseo_image: string | null;\n\t\t\t\t}>`\n\t\t\t\t\tSELECT c.slug, c.id, c.updated_at, c.locale, c.translation_group, s.seo_image\n\t\t\t\t\tFROM ${sql.ref(tableName)} c\n\t\t\t\t\tLEFT JOIN _emdash_seo s\n\t\t\t\t\t\tON s.collection = ${col.slug}\n\t\t\t\t\t\tAND s.content_id = c.id\n\t\t\t\t\tWHERE c.status = 'published'\n\t\t\t\t\tAND c.deleted_at IS NULL\n\t\t\t\t\tAND (s.seo_no_index IS NULL OR s.seo_no_index = 0)\n\t\t\t\t\tORDER BY c.updated_at DESC\n\t\t\t\t\tLIMIT ${SITEMAP_MAX_ENTRIES}\n\t\t\t\t`.execute(db);\n\n\t\t\t\tif (rows.rows.length === 0) continue;\n\n\t\t\t\tconst entries: SitemapContentEntry[] = [];\n\t\t\t\tfor (const row of rows.rows) {\n\t\t\t\t\tentries.push({\n\t\t\t\t\t\tid: row.id,\n\t\t\t\t\t\tslug: row.slug,\n\t\t\t\t\t\tupdatedAt: row.updated_at,\n\t\t\t\t\t\tlocale: row.locale,\n\t\t\t\t\t\ttranslationGroup: row.translation_group,\n\t\t\t\t\t\timage: row.seo_image ?? null,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tresult.push({\n\t\t\t\t\tcollection: col.slug,\n\t\t\t\t\turlPattern: col.url_pattern,\n\t\t\t\t\t// Rows are ordered by updated_at DESC, so first row is the latest\n\t\t\t\t\tlastmod: rows.rows[0].updated_at,\n\t\t\t\t\tentries,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\t// Table missing or query error — skip this collection\n\t\t\t\tconsole.warn(`[SITEMAP] Failed to query collection \"${col.slug}\":`, err);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\treturn { success: true, data: { collections: result } };\n\t} catch (error) {\n\t\tconsole.error(\"[SITEMAP_ERROR]\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"SITEMAP_ERROR\", message: \"Failed to generate sitemap data\" },\n\t\t};\n\t}\n}\n"],"mappings":";;;;;;;;;;AAwDA,MAAM,sBAAsB;;;;;;;;;;;;AAa5B,eAAsB,kBACrB,IAEA,gBAC0C;AAC1C,KAAI;EAEH,IAAI,QAAQ,GACV,WAAW,sBAAsB,CACjC,OAAO,CAAC,QAAQ,cAAc,CAAC,CAC/B,MAAM,WAAW,KAAK,EAAE;AAE1B,MAAI,eACH,SAAQ,MAAM,MAAM,QAAQ,KAAK,eAAe;EAGjD,MAAM,cAAc,MAAM,MAAM,SAAS;EAEzC,MAAM,SAAkC,EAAE;AAE1C,OAAK,MAAM,OAAO,aAAa;AAI9B,OAAI;AACH,uBAAmB,IAAI,MAAM,kBAAkB;WACxC;AACP,YAAQ,KAAK,oDAAoD,IAAI,OAAO;AAC5E;;GAGD,MAAM,YAAY,MAAM,IAAI;AAO5B,OAAI;IACH,MAAM,OAAO,MAAM,GAOjB;;YAEM,IAAI,IAAI,UAAU,CAAC;;0BAEL,IAAI,KAAK;;;;;;aAMtB,oBAAoB;MAC3B,QAAQ,GAAG;AAEb,QAAI,KAAK,KAAK,WAAW,EAAG;IAE5B,MAAM,UAAiC,EAAE;AACzC,SAAK,MAAM,OAAO,KAAK,KACtB,SAAQ,KAAK;KACZ,IAAI,IAAI;KACR,MAAM,IAAI;KACV,WAAW,IAAI;KACf,QAAQ,IAAI;KACZ,kBAAkB,IAAI;KACtB,OAAO,IAAI,aAAa;KACxB,CAAC;AAGH,WAAO,KAAK;KACX,YAAY,IAAI;KAChB,YAAY,IAAI;KAEhB,SAAS,KAAK,KAAK,GAAG;KACtB;KACA,CAAC;YACM,KAAK;AAEb,YAAQ,KAAK,yCAAyC,IAAI,KAAK,KAAK,IAAI;AACxE;;;AAIF,SAAO;GAAE,SAAS;GAAM,MAAM,EAAE,aAAa,QAAQ;GAAE;UAC/C,OAAO;AACf,UAAQ,MAAM,mBAAmB,MAAM;AACvC,SAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAAmC;GAC5E"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"taxonomies-CurpLtNC.mjs","names":[],"sources":["../src/taxonomies/index.ts"],"sourcesContent":["/**\n * Runtime API for taxonomies.\n *\n * All helpers are locale-aware. When a locale is not passed explicitly we fall\n * back to the request context or the configured `defaultLocale` (see\n * `i18n/resolve.ts`).\n *\n * Because `content_taxonomies.taxonomy_id` stores the translation_group (not a\n * specific term id), the joins here are `taxonomies.translation_group =\n * content_taxonomies.taxonomy_id` + filter by `taxonomies.locale`, which picks\n * the right per-locale term.\n */\n\nimport { resolveLocale, resolveLocaleChain } from \"../i18n/resolve.js\";\nimport { getDb } from \"../loader.js\";\nimport {\n\tcachedQuery,\n\tCacheNamespace,\n\tcontentNamespace,\n\tinvalidateTaxonomyObjectCache,\n} from \"../object-cache/index.js\";\nimport { peekRequestCache, requestCached, setRequestCacheEntry } from \"../request-cache.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../utils/chunks.js\";\nimport { isMissingTableError } from \"../utils/db-errors.js\";\nimport type { TaxonomyDef, TaxonomyTerm, TaxonomyTermRow } from \"./types.js\";\n\nexport interface TaxonomyQueryOptions {\n\tlocale?: string;\n}\n\n/**\n * Invalidate cached taxonomy data in the distributed object cache (and any\n * content that hydrates taxonomy terms). The legacy in-isolate term cache was\n * removed, so this used to be a no-op; it now drives object-cache invalidation.\n */\nexport function invalidateTermCache(): void {\n\tinvalidateTaxonomyObjectCache();\n}\n\n/**\n * Get every taxonomy definition. Definitions are per-locale (one row per\n * locale inside the same translation_group) — by default we resolve to the\n * active locale.\n */\nexport async function getTaxonomyDefs(options: TaxonomyQueryOptions = {}): Promise<TaxonomyDef[]> {\n\tconst locale = resolveLocale(options.locale);\n\treturn requestCached(`taxonomy-defs:${locale ?? \"*\"}`, () =>\n\t\tcachedQuery({\n\t\t\tnamespace: CacheNamespace.TAXONOMIES,\n\t\t\tkey: `defs:${locale ?? \"*\"}`,\n\t\t\tload: async () => {\n\t\t\t\tconst db = await getDb();\n\t\t\t\tlet query = db.selectFrom(\"_emdash_taxonomy_defs\").selectAll();\n\t\t\t\tif (locale !== undefined) query = query.where(\"locale\", \"=\", locale);\n\t\t\t\tconst rows = await query.execute();\n\t\t\t\treturn rows.map(rowToTaxonomyDef);\n\t\t\t},\n\t\t}),\n\t);\n}\n\n/**\n * Get a single taxonomy definition by name. Uses the fallback chain so even\n * if there is no translation for the active locale we still return something.\n *\n * If `getTaxonomyDefs()` has already loaded the full list in this request\n * (which happens during entry-term hydration on every page that renders a\n * collection), search the matching def in memory rather than running a\n * second query against `_emdash_taxonomy_defs`.\n */\nexport async function getTaxonomyDef(\n\tname: string,\n\toptions: TaxonomyQueryOptions = {},\n): Promise<TaxonomyDef | null> {\n\tconst chain = resolveLocaleChain(options.locale);\n\tconst peekKey = `taxonomy-defs:${resolveLocale(options.locale) ?? \"*\"}`;\n\tconst allDefs = peekRequestCache<TaxonomyDef[]>(peekKey);\n\tif (allDefs) {\n\t\tconst defs = await allDefs;\n\t\tif (chain.length === 0) return defs.find((d) => d.name === name) ?? null;\n\t\tfor (const locale of chain) {\n\t\t\tconst found = defs.find((d) => d.name === name && d.locale === locale);\n\t\t\tif (found) return found;\n\t\t}\n\t\treturn null;\n\t}\n\n\treturn requestCached(`taxonomy-def:${name}:${chain.join(\",\")}`, async () => {\n\t\tconst db = await getDb();\n\n\t\tif (chain.length === 0) {\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t.orderBy(\"locale\", \"asc\")\n\t\t\t\t.executeTakeFirst();\n\t\t\treturn row ? rowToTaxonomyDef(row) : null;\n\t\t}\n\n\t\tfor (const locale of chain) {\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t.where(\"locale\", \"=\", locale)\n\t\t\t\t.executeTakeFirst();\n\t\t\tif (row) return rowToTaxonomyDef(row);\n\t\t}\n\t\treturn null;\n\t});\n}\n\n/**\n * All terms of a taxonomy in a specific locale (flat for non-hierarchical,\n * tree for hierarchical).\n */\nexport async function getTaxonomyTerms(\n\ttaxonomyName: string,\n\toptions: TaxonomyQueryOptions = {},\n): Promise<TaxonomyTerm[]> {\n\tconst locale = resolveLocale(options.locale);\n\treturn requestCached(`taxonomy-terms:${taxonomyName}:${locale ?? \"*\"}`, () =>\n\t\tcachedQuery({\n\t\t\tnamespace: CacheNamespace.TAXONOMIES,\n\t\t\tkey: `terms:${taxonomyName}:${locale ?? \"*\"}`,\n\t\t\tload: () => loadTaxonomyTerms(taxonomyName, locale, options),\n\t\t}),\n\t);\n}\n\nasync function loadTaxonomyTerms(\n\ttaxonomyName: string,\n\tlocale: string | undefined,\n\toptions: TaxonomyQueryOptions,\n): Promise<TaxonomyTerm[]> {\n\tconst db = await getDb();\n\n\tconst def = await getTaxonomyDef(taxonomyName, options);\n\tif (!def) return [];\n\n\tlet termsQuery = db\n\t\t.selectFrom(\"taxonomies\")\n\t\t.selectAll()\n\t\t.where(\"name\", \"=\", taxonomyName)\n\t\t.orderBy(\"label\", \"asc\");\n\tif (locale !== undefined) termsQuery = termsQuery.where(\"locale\", \"=\", locale);\n\tconst rows = await termsQuery.execute();\n\n\t// Counts are keyed by translation_group (what the pivot stores) and are\n\t// locale-independent, so the aggregate is shared across every taxonomy\n\t// rendered in this request (Categories + Tags widgets, etc.).\n\tconst counts = await getTaxonomyTermCounts();\n\n\tconst flatTerms: TaxonomyTermRow[] = rows.map((row) => ({\n\t\tid: row.id,\n\t\tname: row.name,\n\t\tslug: row.slug,\n\t\tlabel: row.label,\n\t\tparent_id: row.parent_id,\n\t\tdata: row.data,\n\t\tlocale: row.locale,\n\t\ttranslation_group: row.translation_group,\n\t}));\n\n\tif (def.hierarchical) return buildTree(flatTerms, counts);\n\n\treturn flatTerms.map((term) => ({\n\t\tid: term.id,\n\t\tname: term.name,\n\t\tslug: term.slug,\n\t\tlabel: term.label,\n\t\tdescription: term.data ? JSON.parse(term.data).description : undefined,\n\t\tchildren: [],\n\t\tcount: counts.get(term.translation_group ?? term.id) ?? 0,\n\t\tlocale: term.locale,\n\t\ttranslationGroup: term.translation_group,\n\t}));\n}\n\n/**\n * Per-translation-group usage counts across all taxonomies, in one aggregate\n * scan of `content_taxonomies`. Counts are locale-independent (the pivot stores\n * translation_group), so a single request-cached entry serves every taxonomy\n * that renders during the request.\n */\nfunction getTaxonomyTermCounts(): Promise<Map<string, number>> {\n\treturn requestCached(\"taxonomy-term-counts\", async () => {\n\t\tconst db = await getDb();\n\t\tconst countsResult = await db\n\t\t\t.selectFrom(\"content_taxonomies\")\n\t\t\t.select([\"taxonomy_id\"])\n\t\t\t.select((eb) => eb.fn.count<number>(\"entry_id\").as(\"count\"))\n\t\t\t.groupBy(\"taxonomy_id\")\n\t\t\t.execute();\n\t\tconst counts = new Map<string, number>();\n\t\tfor (const row of countsResult) counts.set(row.taxonomy_id, row.count);\n\t\treturn counts;\n\t});\n}\n\n/**\n * Get a single term by (taxonomy, slug). Honours the fallback chain — if the\n * slug exists in a fallback locale, we return that row (useful for deep-linking\n * to a term page when the translation is missing).\n */\nexport async function getTerm(\n\ttaxonomyName: string,\n\tslug: string,\n\toptions: TaxonomyQueryOptions = {},\n): Promise<TaxonomyTerm | null> {\n\tconst chain = resolveLocaleChain(options.locale);\n\t// Cached under the shared taxonomies epoch (bumped on any taxonomy / term\n\t// assignment write). The `count` reflects content_taxonomies rows; a stale\n\t// count after a bare content delete is bounded by the entry's TTL.\n\treturn cachedQuery({\n\t\tnamespace: CacheNamespace.TAXONOMIES,\n\t\tkey: `term:${taxonomyName}:${slug}:${chain.join(\",\")}`,\n\t\tload: () => loadTerm(taxonomyName, slug, chain),\n\t});\n}\n\nasync function loadTerm(\n\ttaxonomyName: string,\n\tslug: string,\n\tchain: string[],\n): Promise<TaxonomyTerm | null> {\n\tconst db = await getDb();\n\n\tlet row: Awaited<ReturnType<ReturnType<typeof selectTerm>[\"executeTakeFirst\"]>>;\n\tconst selectTerm = () =>\n\t\tdb\n\t\t\t.selectFrom(\"taxonomies\")\n\t\t\t.selectAll()\n\t\t\t.where(\"name\", \"=\", taxonomyName)\n\t\t\t.where(\"slug\", \"=\", slug);\n\n\tif (chain.length === 0) {\n\t\trow = await selectTerm().orderBy(\"locale\", \"asc\").executeTakeFirst();\n\t} else {\n\t\trow = undefined;\n\t\tfor (const locale of chain) {\n\t\t\trow = await selectTerm().where(\"locale\", \"=\", locale).executeTakeFirst();\n\t\t\tif (row) break;\n\t\t}\n\t}\n\n\tif (!row) return null;\n\n\tlet childrenQuery = db\n\t\t.selectFrom(\"taxonomies\")\n\t\t.selectAll()\n\t\t// Children store the parent's translation_group in parent_id (not a row\n\t\t// id), so a translated parent still owns its children in its own locale.\n\t\t.where(\"parent_id\", \"=\", row.translation_group ?? row.id)\n\t\t.orderBy(\"label\", \"asc\");\n\tconst termLocale = row.locale;\n\tif (termLocale) childrenQuery = childrenQuery.where(\"locale\", \"=\", termLocale);\n\n\t// The usage-count and children queries both depend only on the term row,\n\t// so run them concurrently to save a round trip on remote databases.\n\tconst [countResult, childRows] = await Promise.all([\n\t\tdb\n\t\t\t.selectFrom(\"content_taxonomies\")\n\t\t\t.select((eb) => eb.fn.count<number>(\"entry_id\").as(\"count\"))\n\t\t\t.where(\"taxonomy_id\", \"=\", row.translation_group ?? row.id)\n\t\t\t.executeTakeFirst(),\n\t\tchildrenQuery.execute(),\n\t]);\n\tconst count = countResult?.count ?? 0;\n\n\tconst children = childRows.map<TaxonomyTerm>((child) => ({\n\t\tid: child.id,\n\t\tname: child.name,\n\t\tslug: child.slug,\n\t\tlabel: child.label,\n\t\tparentId: child.parent_id ?? undefined,\n\t\tchildren: [],\n\t\tlocale: child.locale,\n\t\ttranslationGroup: child.translation_group,\n\t}));\n\n\treturn {\n\t\tid: row.id,\n\t\tname: row.name,\n\t\tslug: row.slug,\n\t\tlabel: row.label,\n\t\tparentId: row.parent_id ?? undefined,\n\t\tdescription: row.data ? JSON.parse(row.data).description : undefined,\n\t\tchildren,\n\t\tcount,\n\t\tlocale: row.locale,\n\t\ttranslationGroup: row.translation_group,\n\t};\n}\n\n/**\n * Terms assigned to a content entry, resolved into the active locale. Terms\n * whose translation_group lacks a row in the requested locale are omitted.\n */\nexport function getEntryTerms(\n\tcollection: string,\n\tentryId: string,\n\ttaxonomyName?: string,\n\toptions: TaxonomyQueryOptions = {},\n): Promise<TaxonomyTerm[]> {\n\tconst locale = resolveLocale(options.locale);\n\t// requestCached short-circuits to values primed by getAllTermsForEntries\n\t// during entry hydration (same key shape). On a warm content-cache hit\n\t// hydration doesn't run, so the inner cachedQuery serves this from KV\n\t// instead of falling through to D1 on every request.\n\treturn requestCached(\n\t\t`terms:${collection}:${entryId}:${taxonomyName ?? \"*\"}:${locale ?? \"*\"}`,\n\t\t() =>\n\t\t\tcachedQuery({\n\t\t\t\tnamespace: [contentNamespace(collection), CacheNamespace.TAXONOMIES],\n\t\t\t\tkey: `entryTerms:${collection}:${entryId}:${taxonomyName ?? \"*\"}:${locale ?? \"*\"}`,\n\t\t\t\tload: async () => {\n\t\t\t\t\tconst db = await getDb();\n\n\t\t\t\t\tlet query = db\n\t\t\t\t\t\t.selectFrom(\"content_taxonomies\")\n\t\t\t\t\t\t.innerJoin(\n\t\t\t\t\t\t\t\"taxonomies\",\n\t\t\t\t\t\t\t\"taxonomies.translation_group\",\n\t\t\t\t\t\t\t\"content_taxonomies.taxonomy_id\",\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.selectAll(\"taxonomies\")\n\t\t\t\t\t\t.where(\"content_taxonomies.collection\", \"=\", collection)\n\t\t\t\t\t\t.where(\"content_taxonomies.entry_id\", \"=\", entryId);\n\n\t\t\t\t\tif (taxonomyName) query = query.where(\"taxonomies.name\", \"=\", taxonomyName);\n\t\t\t\t\tif (locale !== undefined) query = query.where(\"taxonomies.locale\", \"=\", locale);\n\n\t\t\t\t\tconst rows = await query.execute();\n\t\t\t\t\treturn rows.map<TaxonomyTerm>((row) => ({\n\t\t\t\t\t\tid: row.id,\n\t\t\t\t\t\tname: row.name,\n\t\t\t\t\t\tslug: row.slug,\n\t\t\t\t\t\tlabel: row.label,\n\t\t\t\t\t\tparentId: row.parent_id ?? undefined,\n\t\t\t\t\t\tchildren: [],\n\t\t\t\t\t\tlocale: row.locale,\n\t\t\t\t\t\ttranslationGroup: row.translation_group,\n\t\t\t\t\t}));\n\t\t\t\t},\n\t\t\t}),\n\t);\n}\n\n/**\n * Terms for multiple entries of one taxonomy, single query.\n */\nexport async function getTermsForEntries(\n\tcollection: string,\n\tentryIds: string[],\n\ttaxonomyName: string,\n\toptions: TaxonomyQueryOptions = {},\n): Promise<Map<string, TaxonomyTerm[]>> {\n\tconst uniqueIds = [...new Set(entryIds)];\n\tif (uniqueIds.length === 0) return new Map();\n\tconst locale = resolveLocale(options.locale);\n\tconst localeKey = locale ?? \"*\";\n\n\t// The query result is a Map, which JSON can't represent — cache it as an\n\t// array of [entryId, terms] pairs and rebuild the Map on read.\n\tconst load = async (): Promise<Array<[string, TaxonomyTerm[]]>> => {\n\t\tconst result = new Map<string, TaxonomyTerm[]>();\n\t\tfor (const id of uniqueIds) result.set(id, []);\n\n\t\t// Entry-term hydration (getAllTermsForEntries -> primeEntryTermsCache)\n\t\t// seeds the per-entry cache under the same key getEntryTerms uses:\n\t\t// `terms:${collection}:${entryId}:${taxonomyName}:${localeKey}`, storing a\n\t\t// TaxonomyTerm[] (including `[]` for entries with no terms). Satisfy those\n\t\t// from cache and run the batched query only for the ids that missed.\n\t\tconst missedIds: string[] = [];\n\t\ttype CacheRead = { id: string; terms: TaxonomyTerm[] } | { id: string; miss: true };\n\t\tconst cacheReads: Array<Promise<CacheRead>> = [];\n\t\tfor (const id of uniqueIds) {\n\t\t\tconst cached = peekRequestCache<TaxonomyTerm[]>(\n\t\t\t\t`terms:${collection}:${id}:${taxonomyName}:${localeKey}`,\n\t\t\t);\n\t\t\tif (cached) {\n\t\t\t\t// A peeked promise can reject (e.g. a sibling getEntryTerms hit a\n\t\t\t\t// missing table). Treat a rejection as a cache miss so the batched\n\t\t\t\t// query path -- and its isMissingTableError guard below -- still runs,\n\t\t\t\t// rather than propagating an uncaught error.\n\t\t\t\tcacheReads.push(\n\t\t\t\t\tcached.then(\n\t\t\t\t\t\t(terms): CacheRead => ({ id, terms }),\n\t\t\t\t\t\t(): CacheRead => ({ id, miss: true }),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tmissedIds.push(id);\n\t\t\t}\n\t\t}\n\t\tfor (const read of await Promise.all(cacheReads)) {\n\t\t\tif (\"miss\" in read) {\n\t\t\t\tmissedIds.push(read.id);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Return a private copy. The cached array and its term objects are shared\n\t\t\t// with getEntryTerms/getAllTermsForEntries (primeEntryTermsCache stores\n\t\t\t// the same references), so a caller that mutates the result -- sorting in\n\t\t\t// place, pushing into `children` -- must not poison the cache. The\n\t\t\t// pre-cache implementation always returned freshly built arrays.\n\t\t\tresult.set(\n\t\t\t\tread.id,\n\t\t\t\tread.terms.map((t) => ({ ...t, children: [...t.children] })),\n\t\t\t);\n\t\t}\n\n\t\tif (missedIds.length === 0) return [...result.entries()];\n\n\t\tconst db = await getDb();\n\t\tfor (const chunk of chunks(missedIds, SQL_BATCH_SIZE)) {\n\t\t\tlet rows;\n\t\t\ttry {\n\t\t\t\tlet query = db\n\t\t\t\t\t.selectFrom(\"content_taxonomies\")\n\t\t\t\t\t.innerJoin(\"taxonomies\", \"taxonomies.translation_group\", \"content_taxonomies.taxonomy_id\")\n\t\t\t\t\t.select([\n\t\t\t\t\t\t\"content_taxonomies.entry_id\",\n\t\t\t\t\t\t\"taxonomies.id\",\n\t\t\t\t\t\t\"taxonomies.name\",\n\t\t\t\t\t\t\"taxonomies.slug\",\n\t\t\t\t\t\t\"taxonomies.label\",\n\t\t\t\t\t\t\"taxonomies.parent_id\",\n\t\t\t\t\t\t\"taxonomies.locale\",\n\t\t\t\t\t\t\"taxonomies.translation_group\",\n\t\t\t\t\t])\n\t\t\t\t\t.where(\"content_taxonomies.collection\", \"=\", collection)\n\t\t\t\t\t.where(\"content_taxonomies.entry_id\", \"in\", chunk)\n\t\t\t\t\t.where(\"taxonomies.name\", \"=\", taxonomyName)\n\t\t\t\t\t// Match the order getAllTermsForEntries (the cache primer) uses, so\n\t\t\t\t\t// cache-hit and DB-miss entries in one result are ordered consistently.\n\t\t\t\t\t.orderBy(\"taxonomies.label\", \"asc\");\n\t\t\t\tif (locale !== undefined) query = query.where(\"taxonomies.locale\", \"=\", locale);\n\t\t\t\trows = await query.execute();\n\t\t\t} catch (error) {\n\t\t\t\tif (isMissingTableError(error)) return [...result.entries()];\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst term: TaxonomyTerm = {\n\t\t\t\t\tid: row.id,\n\t\t\t\t\tname: row.name,\n\t\t\t\t\tslug: row.slug,\n\t\t\t\t\tlabel: row.label,\n\t\t\t\t\tparentId: row.parent_id ?? undefined,\n\t\t\t\t\tchildren: [],\n\t\t\t\t\tlocale: row.locale,\n\t\t\t\t\ttranslationGroup: row.translation_group,\n\t\t\t\t};\n\t\t\t\tconst terms = result.get(row.entry_id);\n\t\t\t\tif (terms) terms.push(term);\n\t\t\t}\n\t\t}\n\n\t\treturn [...result.entries()];\n\t};\n\n\t// Key on the sorted unique ids. Bound the key length: very large batches\n\t// (rare; they come from collection hydration, already served by the content\n\t// cache) bypass the object cache rather than blow past KV's key limit.\n\tconst idKey = uniqueIds.toSorted().join(\",\");\n\tconst pairs =\n\t\tidKey.length <= 256\n\t\t\t? await cachedQuery({\n\t\t\t\t\tnamespace: [contentNamespace(collection), CacheNamespace.TAXONOMIES],\n\t\t\t\t\tkey: `termsForEntries:${collection}:${taxonomyName}:${locale ?? \"*\"}:${idKey}`,\n\t\t\t\t\tload,\n\t\t\t\t})\n\t\t\t: await load();\n\n\treturn new Map(pairs);\n}\n\n/**\n * Batch-fetch terms for multiple entries across ALL taxonomies in one query.\n * Primes the request-cache for subsequent per-entry calls to `getEntryTerms`.\n */\nexport async function getAllTermsForEntries(\n\tcollection: string,\n\tentryIds: string[],\n\toptions: TaxonomyQueryOptions = {},\n): Promise<Map<string, Record<string, TaxonomyTerm[]>>> {\n\tconst result = new Map<string, Record<string, TaxonomyTerm[]>>();\n\tconst uniqueIds = [...new Set(entryIds)];\n\tfor (const id of uniqueIds) result.set(id, {});\n\tif (uniqueIds.length === 0) return result;\n\n\tconst db = await getDb();\n\tconst locale = resolveLocale(options.locale);\n\tconst applicableTaxonomyNames = await getCollectionTaxonomyNames(collection, { locale });\n\n\tfor (const chunk of chunks(uniqueIds, SQL_BATCH_SIZE)) {\n\t\tlet rows;\n\t\ttry {\n\t\t\tlet query = db\n\t\t\t\t.selectFrom(\"content_taxonomies\")\n\t\t\t\t.innerJoin(\"taxonomies\", \"taxonomies.translation_group\", \"content_taxonomies.taxonomy_id\")\n\t\t\t\t.select([\n\t\t\t\t\t\"content_taxonomies.entry_id\",\n\t\t\t\t\t\"taxonomies.id\",\n\t\t\t\t\t\"taxonomies.name\",\n\t\t\t\t\t\"taxonomies.slug\",\n\t\t\t\t\t\"taxonomies.label\",\n\t\t\t\t\t\"taxonomies.parent_id\",\n\t\t\t\t\t\"taxonomies.locale\",\n\t\t\t\t\t\"taxonomies.translation_group\",\n\t\t\t\t])\n\t\t\t\t.where(\"content_taxonomies.collection\", \"=\", collection)\n\t\t\t\t.where(\"content_taxonomies.entry_id\", \"in\", chunk)\n\t\t\t\t.orderBy(\"taxonomies.label\", \"asc\");\n\t\t\tif (locale !== undefined) query = query.where(\"taxonomies.locale\", \"=\", locale);\n\t\t\trows = await query.execute();\n\t\t} catch (error) {\n\t\t\tif (isMissingTableError(error)) {\n\t\t\t\tfor (const id of uniqueIds) {\n\t\t\t\t\tprimeEntryTermsCache(collection, id, {}, applicableTaxonomyNames, locale);\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\n\t\tfor (const row of rows) {\n\t\t\tconst term: TaxonomyTerm = {\n\t\t\t\tid: row.id,\n\t\t\t\tname: row.name,\n\t\t\t\tslug: row.slug,\n\t\t\t\tlabel: row.label,\n\t\t\t\tparentId: row.parent_id ?? undefined,\n\t\t\t\tchildren: [],\n\t\t\t\tlocale: row.locale,\n\t\t\t\ttranslationGroup: row.translation_group,\n\t\t\t};\n\t\t\tconst byTaxonomy = result.get(row.entry_id);\n\t\t\tif (!byTaxonomy) continue;\n\t\t\tconst existing = byTaxonomy[row.name];\n\t\t\tif (existing) existing.push(term);\n\t\t\telse byTaxonomy[row.name] = [term];\n\t\t}\n\t}\n\n\tfor (const [entryId, byTaxonomy] of result) {\n\t\tprimeEntryTermsCache(collection, entryId, byTaxonomy, applicableTaxonomyNames, locale);\n\t}\n\n\treturn result;\n}\n\n/**\n * Return the list of taxonomy names applicable to a collection, request-\n * cached so a page render only pays for it once.\n *\n * Returns an empty list when taxonomies haven't been defined yet.\n */\nasync function getCollectionTaxonomyNames(\n\tcollection: string,\n\toptions: TaxonomyQueryOptions,\n): Promise<string[]> {\n\ttry {\n\t\tconst defs = await getTaxonomyDefs(options);\n\t\treturn defs.filter((d) => d.collections.includes(collection)).map((d) => d.name);\n\t} catch (error) {\n\t\tif (isMissingTableError(error)) return [];\n\t\tthrow error;\n\t}\n}\n\n/**\n * Pre-populate the request-cache for every getEntryTerms call-shape that\n * could hit this entry:\n *\n * getEntryTerms(collection, entryId) -> key `terms:C:E:*`\n * getEntryTerms(collection, entryId, \"tag\") -> key `terms:C:E:tag`\n * getEntryTerms(collection, entryId, \"category\") -> key `terms:C:E:category`\n * ...one per taxonomy that applies to this collection\n *\n * Taxonomies with no rows on this entry are seeded with `[]` so legacy\n * callers short-circuit to the cached empty array instead of re-querying.\n */\nfunction primeEntryTermsCache(\n\tcollection: string,\n\tentryId: string,\n\tbyTaxonomy: Record<string, TaxonomyTerm[]>,\n\tapplicableTaxonomyNames: string[],\n\tlocale: string | undefined,\n): void {\n\tconst localeKey = locale ?? \"*\";\n\tfor (const name of applicableTaxonomyNames) {\n\t\tsetRequestCacheEntry(\n\t\t\t`terms:${collection}:${entryId}:${name}:${localeKey}`,\n\t\t\tbyTaxonomy[name] ?? [],\n\t\t);\n\t}\n\tfor (const [name, terms] of Object.entries(byTaxonomy)) {\n\t\tsetRequestCacheEntry(`terms:${collection}:${entryId}:${name}:${localeKey}`, terms);\n\t}\n\tconst allTerms = Object.values(byTaxonomy).flat();\n\tsetRequestCacheEntry(`terms:${collection}:${entryId}:*:${localeKey}`, allTerms);\n}\n\n/**\n * Prime the per-entry request cache from terms that were folded into the\n * content query (query.ts `hydrateEntryTerms` fast path), so subsequent\n * `getEntryTerms` calls in the same render hit the cache instead of issuing an\n * N+1 query. Seeds the wildcard key and one key per taxonomy present on the\n * entry — purely from the folded data, with no DB lookup.\n *\n * Unlike `getAllTermsForEntries`, this deliberately does NOT seed `[]` for\n * taxonomies that apply to the collection but have no rows on the entry: doing\n * so would require a `getTaxonomyDefs` query, adding a round trip to every fold\n * render to serve the rarer `getEntryTerms(id, absentTaxonomy)` case from cache.\n * That call simply falls through to its own cached query. Keeping the key shape\n * here (rather than in query.ts) prevents the two from drifting.\n */\nexport function primeFoldedEntryTerms(\n\tcollection: string,\n\tperEntry: Array<{ entryId: string; byTaxonomy: Record<string, TaxonomyTerm[]> }>,\n\toptions: TaxonomyQueryOptions = {},\n): void {\n\tif (perEntry.length === 0) return;\n\tconst locale = resolveLocale(options.locale);\n\tfor (const { entryId, byTaxonomy } of perEntry) {\n\t\tprimeEntryTermsCache(collection, entryId, byTaxonomy, [], locale);\n\t}\n}\n\n/**\n * Get entries by term. Both the lookup (term slug in the active locale) and\n * the content query respect the active locale.\n */\nexport async function getEntriesByTerm(\n\tcollection: string,\n\ttaxonomyName: string,\n\ttermSlug: string,\n\toptions: TaxonomyQueryOptions = {},\n): Promise<Array<{ id: string; data: Record<string, unknown> }>> {\n\tconst { getEmDashCollection } = await import(\"../query.js\");\n\n\tconst queryOptions: Record<string, unknown> = {\n\t\twhere: { [taxonomyName]: termSlug },\n\t};\n\tif (options.locale !== undefined) queryOptions.locale = options.locale;\n\tconst { entries } = await getEmDashCollection(collection, queryOptions);\n\treturn entries;\n}\n\nfunction rowToTaxonomyDef(row: {\n\tid: string;\n\tname: string;\n\tlabel: string;\n\tlabel_singular: string | null;\n\thierarchical: number;\n\tcollections: string | null;\n\tlocale: string;\n\ttranslation_group: string | null;\n}): TaxonomyDef {\n\treturn {\n\t\tid: row.id,\n\t\tname: row.name,\n\t\tlabel: row.label,\n\t\tlabelSingular: row.label_singular ?? undefined,\n\t\thierarchical: row.hierarchical === 1,\n\t\tcollections: row.collections ? JSON.parse(row.collections) : [],\n\t\tlocale: row.locale,\n\t\ttranslationGroup: row.translation_group,\n\t};\n}\n\n/**\n * Build tree structure from flat terms\n */\nfunction buildTree(flatTerms: TaxonomyTermRow[], counts: Map<string, number>): TaxonomyTerm[] {\n\t// parent_id holds the parent's translation_group, so link children by it.\n\t// Key by (locale, group): a child's parent lives in the same locale, and an\n\t// unfiltered set mixes locales whose translated siblings share a group —\n\t// keying by group alone would collide and misattach children across locales.\n\tconst byLocaleGroup = new Map<string, TaxonomyTerm>();\n\tconst nodes: TaxonomyTerm[] = [];\n\tconst roots: TaxonomyTerm[] = [];\n\n\tfor (const term of flatTerms) {\n\t\tconst node: TaxonomyTerm = {\n\t\t\tid: term.id,\n\t\t\tname: term.name,\n\t\t\tslug: term.slug,\n\t\t\tlabel: term.label,\n\t\t\tparentId: term.parent_id ?? undefined,\n\t\t\tdescription: term.data ? JSON.parse(term.data).description : undefined,\n\t\t\tchildren: [],\n\t\t\tcount: counts.get(term.translation_group ?? term.id) ?? 0,\n\t\t\tlocale: term.locale,\n\t\t\ttranslationGroup: term.translation_group,\n\t\t};\n\t\tbyLocaleGroup.set(`${term.locale}::${term.translation_group ?? term.id}`, node);\n\t\tnodes.push(node);\n\t}\n\n\tfor (const node of nodes) {\n\t\tconst parent = node.parentId\n\t\t\t? byLocaleGroup.get(`${node.locale}::${node.parentId}`)\n\t\t\t: undefined;\n\t\tif (parent) {\n\t\t\tparent.children.push(node);\n\t\t} else {\n\t\t\troots.push(node);\n\t\t}\n\t}\n\n\treturn roots;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"taxonomies-DhLaNJji.mjs","names":[],"sources":["../src/api/handlers/taxonomies.ts"],"sourcesContent":["/**\n * Taxonomy and term CRUD handlers.\n *\n * i18n: terms and defs are per-locale. `(name, slug, locale)` is unique for\n * terms; `(name, locale)` for defs. Translations of the same term/def share a\n * `translation_group`. The content_taxonomies pivot stores translation_groups\n * so assignments span every locale of a post.\n */\n\nimport type { Kysely, Selectable } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { TaxonomyRepository } from \"../../database/repositories/taxonomy.js\";\nimport type { Database, TaxonomyDefTable } from \"../../database/types.js\";\nimport { invalidateTermCache } from \"../../taxonomies/index.js\";\nimport type { ApiResult } from \"../types.js\";\n\nconst NAME_PATTERN = /^[a-z][a-z0-9_]*$/;\n\n// ---------------------------------------------------------------------------\n// Response types\n// ---------------------------------------------------------------------------\n\nexport interface TaxonomyDef {\n\tid: string;\n\tname: string;\n\tlabel: string;\n\tlabelSingular?: string;\n\thierarchical: boolean;\n\tcollections: string[];\n\tlocale: string;\n\ttranslationGroup: string | null;\n}\n\nexport interface TaxonomyListResponse {\n\ttaxonomies: TaxonomyDef[];\n}\n\nexport interface TermData {\n\tid: string;\n\tname: string;\n\tslug: string;\n\tlabel: string;\n\tparentId: string | null;\n\tdescription?: string;\n\tlocale: string;\n\ttranslationGroup: string | null;\n}\n\nexport interface TermWithCount extends TermData {\n\tcount: number;\n\tchildren: TermWithCount[];\n}\n\nexport interface TermListResponse {\n\tterms: TermWithCount[];\n}\n\nexport interface TermResponse {\n\tterm: TermData;\n}\n\nexport interface TermGetResponse {\n\tterm: TermData & {\n\t\tcount: number;\n\t\tchildren: Array<{ id: string; slug: string; label: string }>;\n\t};\n}\n\nexport interface TermTranslationsResponse {\n\ttranslationGroup: string | null;\n\ttranslations: Array<{\n\t\tid: string;\n\t\tslug: string;\n\t\tlabel: string;\n\t\tlocale: string;\n\t}>;\n}\n\nexport interface TaxonomyDefTranslationsResponse {\n\ttranslationGroup: string | null;\n\ttranslations: Array<{\n\t\tid: string;\n\t\tname: string;\n\t\tlabel: string;\n\t\tlocale: string;\n\t}>;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Build tree structure from flat terms\n */\nfunction buildTree(flatTerms: TermWithCount[]): TermWithCount[] {\n\t// `parentId` holds the parent's translation_group, so resolve links by group.\n\t// Key by (locale, group): a child's parent lives in the same locale, and an\n\t// unfiltered list mixes locales whose translated siblings share a group —\n\t// keying by group alone would collide and misattach children across locales.\n\tconst byLocaleGroup = new Map<string, TermWithCount>();\n\tconst roots: TermWithCount[] = [];\n\tfor (const term of flatTerms) {\n\t\tbyLocaleGroup.set(`${term.locale}::${term.translationGroup ?? term.id}`, term);\n\t}\n\tfor (const term of flatTerms) {\n\t\tconst parent = term.parentId\n\t\t\t? byLocaleGroup.get(`${term.locale}::${term.parentId}`)\n\t\t\t: undefined;\n\t\tif (parent) {\n\t\t\tparent.children.push(term);\n\t\t} else {\n\t\t\troots.push(term);\n\t\t}\n\t}\n\treturn roots;\n}\n\n/**\n * Look up a taxonomy definition by name (optionally scoped to a locale).\n * Returns the lowest-locale match when no locale is provided.\n */\nasync function requireTaxonomyDef(\n\tdb: Kysely<Database>,\n\tname: string,\n\tlocale?: string,\n): Promise<\n\t| { success: true; def: Selectable<TaxonomyDefTable> }\n\t| { success: false; error: { code: string; message: string } }\n> {\n\tlet query = db.selectFrom(\"_emdash_taxonomy_defs\").selectAll().where(\"name\", \"=\", name);\n\tif (locale !== undefined) query = query.where(\"locale\", \"=\", locale);\n\tconst def = await query.orderBy(\"locale\", \"asc\").executeTakeFirst();\n\tif (!def) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"NOT_FOUND\", message: `Taxonomy '${name}' not found` },\n\t\t};\n\t}\n\treturn { success: true, def };\n}\n\nfunction rowToDef(row: Selectable<TaxonomyDefTable>): TaxonomyDef {\n\treturn {\n\t\tid: row.id,\n\t\tname: row.name,\n\t\tlabel: row.label,\n\t\tlabelSingular: row.label_singular ?? undefined,\n\t\thierarchical: row.hierarchical === 1,\n\t\tcollections: row.collections ? JSON.parse(row.collections) : [],\n\t\tlocale: row.locale,\n\t\ttranslationGroup: row.translation_group,\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Taxonomy definition handlers\n// ---------------------------------------------------------------------------\n\n/**\n * List all taxonomy definitions\n */\nexport async function handleTaxonomyList(\n\tdb: Kysely<Database>,\n\toptions: { locale?: string } = {},\n): Promise<ApiResult<TaxonomyListResponse>> {\n\ttry {\n\t\tlet query = db.selectFrom(\"_emdash_taxonomy_defs\").selectAll();\n\t\tif (options.locale !== undefined) query = query.where(\"locale\", \"=\", options.locale);\n\t\tconst [rows, collectionRows] = await Promise.all([\n\t\t\tquery.execute(),\n\t\t\tdb.selectFrom(\"_emdash_collections\").select(\"slug\").execute(),\n\t\t]);\n\n\t\t// Filter orphan collection references on read so the response stays\n\t\t// consistent with `schema_list_collections`. Storage is untouched —\n\t\t// re-creating the collection re-links automatically.\n\t\tconst realCollections = new Set(collectionRows.map((r) => r.slug));\n\n\t\tconst taxonomies: TaxonomyDef[] = rows.map((row) => {\n\t\t\tconst def = rowToDef(row);\n\t\t\treturn { ...def, collections: def.collections.filter((slug) => realCollections.has(slug)) };\n\t\t});\n\n\t\treturn { success: true, data: { taxonomies } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TAXONOMY_LIST_ERROR\", message: \"Failed to list taxonomies\" },\n\t\t};\n\t}\n}\n\n/**\n * Create a new taxonomy definition\n */\nexport async function handleTaxonomyCreate(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tname: string;\n\t\tlabel: string;\n\t\tlabelSingular?: string;\n\t\thierarchical?: boolean;\n\t\tcollections?: string[];\n\t\tlocale?: string;\n\t\ttranslationOf?: string;\n\t},\n): Promise<ApiResult<{ taxonomy: TaxonomyDef }>> {\n\ttry {\n\t\tif (!NAME_PATTERN.test(input.name)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Taxonomy name must start with a letter and contain only lowercase letters, numbers, and underscores\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst collections = [...new Set(input.collections ?? [])];\n\t\tif (collections.length > 0) {\n\t\t\tconst existingCollections = await db\n\t\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t\t.select(\"slug\")\n\t\t\t\t.where(\"slug\", \"in\", collections)\n\t\t\t\t.execute();\n\t\t\tconst existingSlugs = new Set(existingCollections.map((c) => c.slug));\n\t\t\tconst invalid = collections.filter((c) => !existingSlugs.has(c));\n\t\t\tif (invalid.length > 0) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\t\tmessage: `Unknown collection(s): ${invalid.join(\", \")}`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tlet translationGroup: string | null = null;\n\t\tif (input.translationOf) {\n\t\t\tconst source = await db\n\t\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"id\", \"=\", input.translationOf)\n\t\t\t\t.executeTakeFirst();\n\t\t\tif (!source) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: { code: \"NOT_FOUND\", message: \"Source taxonomy for translation not found\" },\n\t\t\t\t};\n\t\t\t}\n\t\t\ttranslationGroup = source.translation_group ?? source.id;\n\t\t}\n\n\t\t// Duplicate guard scoped to locale (so the same name can exist in ES\n\t\t// and EN).\n\t\tif (input.locale !== undefined) {\n\t\t\tconst existing = await db\n\t\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t\t.select(\"id\")\n\t\t\t\t.where(\"name\", \"=\", input.name)\n\t\t\t\t.where(\"locale\", \"=\", input.locale)\n\t\t\t\t.executeTakeFirst();\n\t\t\tif (existing) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\t\tmessage: `Taxonomy '${input.name}' already exists in locale '${input.locale}'`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst id = ulid();\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_taxonomy_defs\")\n\t\t\t.values({\n\t\t\t\tid,\n\t\t\t\tname: input.name,\n\t\t\t\tlabel: input.label,\n\t\t\t\tlabel_singular: input.labelSingular ?? null,\n\t\t\t\thierarchical: input.hierarchical ? 1 : 0,\n\t\t\t\tcollections: JSON.stringify(collections),\n\t\t\t\t...(input.locale !== undefined ? { locale: input.locale } : {}),\n\t\t\t\ttranslation_group: translationGroup ?? id,\n\t\t\t})\n\t\t\t.execute();\n\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirstOrThrow();\n\t\treturn { success: true, data: { taxonomy: rowToDef(row) } };\n\t} catch (error) {\n\t\tif (error instanceof Error && error.message.includes(\"UNIQUE constraint failed\")) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"CONFLICT\", message: `Taxonomy '${input.name}' already exists` },\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TAXONOMY_CREATE_ERROR\", message: \"Failed to create taxonomy\" },\n\t\t};\n\t}\n}\n\n/**\n * List every locale translation of a taxonomy def (by id or translation_group).\n */\nexport async function handleTaxonomyDefTranslations(\n\tdb: Kysely<Database>,\n\tidOrGroup: string,\n): Promise<ApiResult<TaxonomyDefTranslationsResponse>> {\n\ttry {\n\t\tconst anchor = await db\n\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t.selectAll()\n\t\t\t.where((eb) => eb.or([eb(\"id\", \"=\", idOrGroup), eb(\"translation_group\", \"=\", idOrGroup)]))\n\t\t\t.executeTakeFirst();\n\t\tif (!anchor) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"Taxonomy not found\" },\n\t\t\t};\n\t\t}\n\t\tconst group = anchor.translation_group ?? anchor.id;\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t.selectAll()\n\t\t\t.where(\"translation_group\", \"=\", group)\n\t\t\t.orderBy(\"locale\", \"asc\")\n\t\t\t.execute();\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\ttranslationGroup: group,\n\t\t\t\ttranslations: rows.map((r) => ({\n\t\t\t\t\tid: r.id,\n\t\t\t\t\tname: r.name,\n\t\t\t\t\tlabel: r.label,\n\t\t\t\t\tlocale: r.locale,\n\t\t\t\t})),\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TAXONOMY_TRANSLATIONS_ERROR\",\n\t\t\t\tmessage: \"Failed to list taxonomy translations\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Term handlers\n// ---------------------------------------------------------------------------\n\n/**\n * List all terms for a taxonomy (returns tree for hierarchical taxonomies)\n */\nexport async function handleTermList(\n\tdb: Kysely<Database>,\n\ttaxonomyName: string,\n\toptions: { locale?: string } = {},\n): Promise<ApiResult<TermListResponse>> {\n\ttry {\n\t\t// Definitions are per-locale but terms aren't bound to the def's locale —\n\t\t// just ensure the taxonomy exists somewhere.\n\t\tconst lookup = await requireTaxonomyDef(db, taxonomyName);\n\t\tif (!lookup.success) return lookup;\n\n\t\tconst repo = new TaxonomyRepository(db);\n\t\tconst terms = await repo.findByName(taxonomyName, { locale: options.locale });\n\n\t\t// Batch count entries per term in a single query (replaces N+1 pattern).\n\t\t// content_taxonomies.taxonomy_id stores the translation_group, so we\n\t\t// look up by group and map back to each term's id.\n\t\tconst groups = terms.map((t) => t.translationGroup ?? t.id);\n\t\tconst countsByGroup = await repo.countEntriesForTerms(groups);\n\n\t\tconst termData: TermWithCount[] = terms.map((term) => ({\n\t\t\tid: term.id,\n\t\t\tname: term.name,\n\t\t\tslug: term.slug,\n\t\t\tlabel: term.label,\n\t\t\tparentId: term.parentId,\n\t\t\tdescription: typeof term.data?.description === \"string\" ? term.data.description : undefined,\n\t\t\tchildren: [],\n\t\t\tcount: countsByGroup.get(term.translationGroup ?? term.id) ?? 0,\n\t\t\tlocale: term.locale,\n\t\t\ttranslationGroup: term.translationGroup,\n\t\t}));\n\n\t\tconst isHierarchical = lookup.def.hierarchical === 1;\n\t\tconst result = isHierarchical ? buildTree(termData) : termData;\n\t\treturn { success: true, data: { terms: result } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TERM_LIST_ERROR\", message: \"Failed to list terms\" },\n\t\t};\n\t}\n}\n\n/**\n * Validate a parent term reference for create/update.\n *\n * Returns `null` on success or a structured error message that callers\n * wrap in their own ApiResult.\n *\n * - `parentId === undefined` -> no-op (no parent change requested).\n * - `parentId === null` -> caller intends to detach; no-op here.\n * - parent must exist (FK exists -> term row not soft-deleted).\n * - parent must live in the same taxonomy.\n * - reject a parent in the term's own translation_group (self-parent),\n * including the create path: a translation inherits its source's group, so\n * `selfGroup` carries that prospective group when `termId` is absent.\n * - on update, walk up the parent chain to detect cycles.\n */\nasync function validateParentTerm(\n\trepo: TaxonomyRepository,\n\ttaxonomyName: string,\n\ttermId: string | undefined,\n\tparentId: string | null | undefined,\n\tselfGroup?: string | null,\n): Promise<{ code: \"VALIDATION_ERROR\"; message: string } | null> {\n\tif (parentId === undefined || parentId === null) return null;\n\n\tconst parent = await repo.findById(parentId);\n\tif (!parent) {\n\t\treturn {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: `Parent term '${parentId}' not found`,\n\t\t};\n\t}\n\tif (parent.name !== taxonomyName) {\n\t\treturn {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: `Parent term '${parentId}' belongs to taxonomy '${parent.name}', not '${taxonomyName}'`,\n\t\t};\n\t}\n\n\t// Parentage keys off translation_group (what `parent_id` persists), so the\n\t// self-parent and cycle checks compare groups, not row ids — picking a\n\t// sibling translation of the term as its parent is still self-parenting.\n\tconst parentGroup = parent.translationGroup ?? parent.id;\n\tlet termGroup: string | null = selfGroup ?? null;\n\tif (termId !== undefined) {\n\t\tconst term = await repo.findById(termId);\n\t\ttermGroup = term ? (term.translationGroup ?? term.id) : termId;\n\t}\n\tif (termGroup !== null && parentGroup === termGroup) {\n\t\treturn {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: \"A term cannot be its own parent\",\n\t\t};\n\t}\n\n\t// Walk up the parent chain. Two checks fold into one walk:\n\t// - Cycle detection (only on update — a non-existent term-being-\n\t// created can't be its own ancestor): if the walk revisits termId\n\t// the proposed parent makes the term a descendant of itself.\n\t// - Depth bound: refuse to extend a chain past MAX_DEPTH ancestors.\n\t// Runs on both create and update so a malicious or buggy caller\n\t// can't grow the tree without limit.\n\t//\n\t// The depth-exceeded error fires only when we hit the limit AND there\n\t// was still chain to walk — a legitimate chain of exactly MAX_DEPTH\n\t// ancestors exits with `cursor === null` and is accepted.\n\t// `parent_id` stores a translation_group, which always equals the anchor\n\t// row's id, so findById(cursor) still resolves each ancestor.\n\tconst MAX_DEPTH = 100;\n\tlet cursor: string | null = parent.parentId;\n\tlet steps = 0;\n\twhile (cursor !== null && steps < MAX_DEPTH) {\n\t\tif (termGroup !== null && cursor === termGroup) {\n\t\t\treturn {\n\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\tmessage: \"Cycle detected: cannot make a descendant the parent\",\n\t\t\t};\n\t\t}\n\t\tconst next = await repo.findById(cursor);\n\t\tif (!next) break;\n\t\tcursor = next.parentId;\n\t\tsteps++;\n\t}\n\tif (cursor !== null && steps >= MAX_DEPTH) {\n\t\treturn {\n\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\tmessage: \"Parent chain exceeds maximum depth\",\n\t\t};\n\t}\n\n\treturn null;\n}\n\n/**\n * Create a new term in a taxonomy\n */\nexport async function handleTermCreate(\n\tdb: Kysely<Database>,\n\ttaxonomyName: string,\n\tinput: {\n\t\tslug: string;\n\t\tlabel: string;\n\t\tparentId?: string | null;\n\t\tdescription?: string;\n\t\tlocale?: string;\n\t\ttranslationOf?: string;\n\t},\n): Promise<ApiResult<TermResponse>> {\n\ttry {\n\t\t// Taxonomy definitions are per-locale, but terms can exist in any locale\n\t\t// regardless of whether the def has been translated there. Look up the\n\t\t// def across all locales — we only care that it *exists*.\n\t\tconst lookup = await requireTaxonomyDef(db, taxonomyName);\n\t\tif (!lookup.success) return lookup;\n\n\t\tconst repo = new TaxonomyRepository(db);\n\n\t\t// Coerce empty-string parentId to undefined (treat as \"no parent\").\n\t\tconst parentId =\n\t\t\tinput.parentId === \"\" || input.parentId === undefined ? undefined : input.parentId;\n\n\t\t// Conflict check is scoped to locale (per-locale slugs are unique).\n\t\tconst existing = await repo.findBySlug(taxonomyName, input.slug, input.locale);\n\t\tif (existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\tmessage: input.locale\n\t\t\t\t\t\t? `Term '${input.slug}' already exists in '${taxonomyName}' (${input.locale})`\n\t\t\t\t\t\t: `Term with slug '${input.slug}' already exists in taxonomy '${taxonomyName}'`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// No locale re-pointing needed: `repo.create` persists the parent's\n\t\t// translation_group in `parent_id`, so a child stays nested under the\n\t\t// parent in every locale automatically — including parents translated\n\t\t// after the child was created.\n\n\t\t// A translation inherits its source's translation_group; pass that\n\t\t// prospective group so validateParentTerm can reject a parent in the same\n\t\t// group (cross-locale self-parent) even though the term doesn't exist yet.\n\t\tlet selfGroup: string | null = null;\n\t\tif (input.translationOf) {\n\t\t\tconst source = await repo.findById(input.translationOf);\n\t\t\tselfGroup = source ? (source.translationGroup ?? source.id) : null;\n\t\t}\n\n\t\t// Validate parentId: must exist AND belong to the same taxonomy.\n\t\t// (Cycle check is N/A on create — the term doesn't exist yet.)\n\t\tconst parentError = await validateParentTerm(\n\t\t\trepo,\n\t\t\ttaxonomyName,\n\t\t\tundefined,\n\t\t\tparentId,\n\t\t\tselfGroup,\n\t\t);\n\t\tif (parentError) {\n\t\t\treturn { success: false, error: parentError };\n\t\t}\n\n\t\tconst term = await repo.create({\n\t\t\tname: taxonomyName,\n\t\t\tslug: input.slug,\n\t\t\tlabel: input.label,\n\t\t\tparentId: parentId ?? undefined,\n\t\t\tdata: input.description ? { description: input.description } : undefined,\n\t\t\tlocale: input.locale,\n\t\t\ttranslationOf: input.translationOf,\n\t\t});\n\n\t\tinvalidateTermCache();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tterm: {\n\t\t\t\t\tid: term.id,\n\t\t\t\t\tname: term.name,\n\t\t\t\t\tslug: term.slug,\n\t\t\t\t\tlabel: term.label,\n\t\t\t\t\tparentId: term.parentId,\n\t\t\t\t\tdescription:\n\t\t\t\t\t\ttypeof term.data?.description === \"string\" ? term.data.description : undefined,\n\t\t\t\t\tlocale: term.locale,\n\t\t\t\t\ttranslationGroup: term.translationGroup,\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TERM_CREATE_ERROR\", message: \"Failed to create term\" },\n\t\t};\n\t}\n}\n\n/**\n * Get a single term by slug\n */\nexport async function handleTermGet(\n\tdb: Kysely<Database>,\n\ttaxonomyName: string,\n\ttermSlug: string,\n\toptions: { locale?: string } = {},\n): Promise<ApiResult<TermGetResponse>> {\n\ttry {\n\t\tconst repo = new TaxonomyRepository(db);\n\t\tconst term = await repo.findBySlug(taxonomyName, termSlug, options.locale);\n\n\t\tif (!term) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Term '${termSlug}' not found in taxonomy '${taxonomyName}'`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst count = await repo.countEntriesWithTerm(term.id);\n\t\t// Children share this term's translation_group as their parent_id; scope\n\t\t// to the term's own locale so the response stays within one locale's tree.\n\t\tconst children = await repo.findChildren(term.id, term.locale);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tterm: {\n\t\t\t\t\tid: term.id,\n\t\t\t\t\tname: term.name,\n\t\t\t\t\tslug: term.slug,\n\t\t\t\t\tlabel: term.label,\n\t\t\t\t\tparentId: term.parentId,\n\t\t\t\t\tdescription:\n\t\t\t\t\t\ttypeof term.data?.description === \"string\" ? term.data.description : undefined,\n\t\t\t\t\tcount,\n\t\t\t\t\tchildren: children.map((c) => ({ id: c.id, slug: c.slug, label: c.label })),\n\t\t\t\t\tlocale: term.locale,\n\t\t\t\t\ttranslationGroup: term.translationGroup,\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TERM_GET_ERROR\", message: \"Failed to get term\" },\n\t\t};\n\t}\n}\n\n/** List every translation of a term (by id or translation_group). */\nexport async function handleTermTranslations(\n\tdb: Kysely<Database>,\n\tidOrGroup: string,\n): Promise<ApiResult<TermTranslationsResponse>> {\n\ttry {\n\t\tconst anchor = await db\n\t\t\t.selectFrom(\"taxonomies\")\n\t\t\t.selectAll()\n\t\t\t.where((eb) => eb.or([eb(\"id\", \"=\", idOrGroup), eb(\"translation_group\", \"=\", idOrGroup)]))\n\t\t\t.executeTakeFirst();\n\t\tif (!anchor) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"Term not found\" },\n\t\t\t};\n\t\t}\n\t\tconst group = anchor.translation_group ?? anchor.id;\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"taxonomies\")\n\t\t\t.selectAll()\n\t\t\t.where(\"translation_group\", \"=\", group)\n\t\t\t.orderBy(\"locale\", \"asc\")\n\t\t\t.execute();\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\ttranslationGroup: group,\n\t\t\t\ttranslations: rows.map((r) => ({\n\t\t\t\t\tid: r.id,\n\t\t\t\t\tslug: r.slug,\n\t\t\t\t\tlabel: r.label,\n\t\t\t\t\tlocale: r.locale,\n\t\t\t\t})),\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TERM_TRANSLATIONS_ERROR\", message: \"Failed to list term translations\" },\n\t\t};\n\t}\n}\n\n/**\n * Update a term\n */\nexport async function handleTermUpdate(\n\tdb: Kysely<Database>,\n\ttaxonomyName: string,\n\ttermSlug: string,\n\tinput: { slug?: string; label?: string; parentId?: string | null; description?: string },\n\toptions: { locale?: string } = {},\n): Promise<ApiResult<TermResponse>> {\n\ttry {\n\t\tconst repo = new TaxonomyRepository(db);\n\t\tconst term = await repo.findBySlug(taxonomyName, termSlug, options.locale);\n\n\t\tif (!term) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Term '${termSlug}' not found in taxonomy '${taxonomyName}'`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Coerce empty-string slug/parentId to undefined (treat as \"no change\").\n\t\t// `null` parentId is a valid request meaning \"detach from parent\".\n\t\tconst newSlug = input.slug === \"\" || input.slug === undefined ? undefined : input.slug;\n\t\tconst newParentId =\n\t\t\tinput.parentId === \"\" || input.parentId === undefined ? undefined : input.parentId;\n\n\t\t// Check if new slug conflicts (per-locale uniqueness).\n\t\tif (newSlug !== undefined && newSlug !== termSlug) {\n\t\t\tconst existing = await repo.findBySlug(taxonomyName, newSlug, options.locale);\n\t\t\tif (existing && existing.id !== term.id) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\t\tmessage: `Term with slug '${newSlug}' already exists in taxonomy '${taxonomyName}'`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Validate parentId: existence, same-taxonomy, no self-parent, no cycle.\n\t\tconst parentError = await validateParentTerm(repo, taxonomyName, term.id, newParentId);\n\t\tif (parentError) {\n\t\t\treturn { success: false, error: parentError };\n\t\t}\n\n\t\tconst updated = await repo.update(term.id, {\n\t\t\tslug: newSlug,\n\t\t\tlabel: input.label,\n\t\t\tparentId: newParentId,\n\t\t\tdata: input.description !== undefined ? { description: input.description } : undefined,\n\t\t});\n\n\t\tinvalidateTermCache();\n\n\t\tif (!updated) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"TERM_UPDATE_ERROR\", message: \"Failed to update term\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tterm: {\n\t\t\t\t\tid: updated.id,\n\t\t\t\t\tname: updated.name,\n\t\t\t\t\tslug: updated.slug,\n\t\t\t\t\tlabel: updated.label,\n\t\t\t\t\tparentId: updated.parentId,\n\t\t\t\t\tdescription:\n\t\t\t\t\t\ttypeof updated.data?.description === \"string\" ? updated.data.description : undefined,\n\t\t\t\t\tlocale: updated.locale,\n\t\t\t\t\ttranslationGroup: updated.translationGroup,\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TERM_UPDATE_ERROR\", message: \"Failed to update term\" },\n\t\t};\n\t}\n}\n\n/**\n * Delete a term\n */\nexport async function handleTermDelete(\n\tdb: Kysely<Database>,\n\ttaxonomyName: string,\n\ttermSlug: string,\n\toptions: { locale?: string } = {},\n): Promise<ApiResult<{ deleted: true }>> {\n\ttry {\n\t\tconst repo = new TaxonomyRepository(db);\n\t\tconst term = await repo.findBySlug(taxonomyName, termSlug, options.locale);\n\n\t\tif (!term) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Term '${termSlug}' not found in taxonomy '${taxonomyName}'`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Block deletion if the term's group still parents any child in any\n\t\t// locale — children store this group in parent_id, so removing the term\n\t\t// would orphan them (or null their parent_id via the self-FK).\n\t\tconst children = await repo.findChildren(term.id);\n\t\tif (children.length > 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"Cannot delete term with children. Delete children first.\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst deleted = await repo.delete(term.id);\n\t\tif (!deleted) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"TERM_DELETE_ERROR\", message: \"Failed to delete term\" },\n\t\t\t};\n\t\t}\n\n\t\tinvalidateTermCache();\n\t\treturn { success: true, data: { deleted: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"TERM_DELETE_ERROR\", message: \"Failed to delete term\" },\n\t\t};\n\t}\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-B-gIxFCE.d.mts","names":[],"sources":["../src/database/types.ts"],"mappings":"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"}
@@ -1,7 +0,0 @@
1
- //#region src/version.ts
2
- const VERSION = "0.24.1";
3
- const COMMIT = "76116410";
4
-
5
- //#endregion
6
- export { VERSION as n, COMMIT as t };
7
- //# sourceMappingURL=version-C0JPFUG8.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"zod-generator-xEHODMJf.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\treturn z.string().datetime().or(z.string().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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"}