emdash 0.33.0 → 0.34.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 (860) hide show
  1. package/dist/{adapters-BMyNSSZi.d.mts → adapters-34EN2eXO.d.mts} +26 -2
  2. package/dist/adapters-34EN2eXO.d.mts.map +1 -0
  3. package/dist/{allowed-origins-CG1pACtS.mjs → allowed-origins-DW7sYyqg.mjs} +2 -2
  4. package/dist/{allowed-origins-CG1pACtS.mjs.map → allowed-origins-DW7sYyqg.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +22 -21
  7. package/dist/api/route-utils.mjs.map +1 -1
  8. package/dist/api/schemas/index.d.mts +2 -2
  9. package/dist/api/schemas/index.mjs +6 -5
  10. package/dist/{api-D2hunekQ.mjs → api-DdAsirBe.mjs} +98 -407
  11. package/dist/api-DdAsirBe.mjs.map +1 -0
  12. package/dist/{api-tokens-CSND0i1z.mjs → api-tokens-CFXu8F_2.mjs} +2 -2
  13. package/dist/{api-tokens-CSND0i1z.mjs.map → api-tokens-CFXu8F_2.mjs.map} +1 -1
  14. package/dist/{apply-kwNEJW2l.mjs → apply-D5zfeD09.mjs} +131 -107
  15. package/dist/apply-D5zfeD09.mjs.map +1 -0
  16. package/dist/astro/index.d.mts +12 -12
  17. package/dist/astro/index.d.mts.map +1 -1
  18. package/dist/astro/index.mjs +93 -35
  19. package/dist/astro/index.mjs.map +1 -1
  20. package/dist/astro/middleware/auth.d.mts +10 -10
  21. package/dist/astro/middleware/auth.mjs +8 -8
  22. package/dist/astro/middleware/media-usage-write-fence.d.mts +7 -0
  23. package/dist/astro/middleware/media-usage-write-fence.d.mts.map +1 -0
  24. package/dist/astro/middleware/media-usage-write-fence.mjs +35 -0
  25. package/dist/astro/middleware/media-usage-write-fence.mjs.map +1 -0
  26. package/dist/astro/middleware/redirect.d.mts.map +1 -1
  27. package/dist/astro/middleware/redirect.mjs +10 -7
  28. package/dist/astro/middleware/redirect.mjs.map +1 -1
  29. package/dist/astro/middleware/request-context.mjs +3 -3
  30. package/dist/astro/middleware/setup.mjs +1 -1
  31. package/dist/astro/middleware.d.mts +12 -11
  32. package/dist/astro/middleware.d.mts.map +1 -1
  33. package/dist/astro/middleware.mjs +1677 -156
  34. package/dist/astro/middleware.mjs.map +1 -1
  35. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +9 -8
  36. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
  37. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +9 -8
  38. package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
  39. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -5
  40. package/dist/astro/routes/api/admin/api-tokens/index.mjs +6 -6
  41. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -6
  42. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +12 -11
  43. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
  44. package/dist/astro/routes/api/admin/byline-fields/index.mjs +12 -11
  45. package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
  46. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +12 -11
  47. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
  48. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +17 -17
  49. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +17 -17
  50. package/dist/astro/routes/api/admin/bylines/index.mjs +17 -17
  51. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +14 -13
  52. package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
  53. package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
  54. package/dist/astro/routes/api/admin/comments/bulk.mjs +12 -11
  55. package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
  56. package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
  57. package/dist/astro/routes/api/admin/comments/index.mjs +12 -11
  58. package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
  59. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
  60. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
  61. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.d.mts +8 -0
  62. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.d.mts.map +1 -0
  63. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +36 -0
  64. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -0
  65. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.d.mts +8 -0
  66. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.d.mts.map +1 -0
  67. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +36 -0
  68. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -0
  69. package/dist/astro/routes/api/admin/media-usage/repair.mjs +15 -14
  70. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
  71. package/dist/astro/routes/api/admin/media-usage/work/index.d.mts +8 -0
  72. package/dist/astro/routes/api/admin/media-usage/work/index.d.mts.map +1 -0
  73. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +37 -0
  74. package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -0
  75. package/dist/astro/routes/api/admin/media-usage/work/retry.d.mts +8 -0
  76. package/dist/astro/routes/api/admin/media-usage/work/retry.d.mts.map +1 -0
  77. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +37 -0
  78. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -0
  79. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
  80. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
  81. package/dist/astro/routes/api/admin/plugins/_id_/disable.d.mts.map +1 -1
  82. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +51 -43
  83. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  84. package/dist/astro/routes/api/admin/plugins/_id_/enable.d.mts.map +1 -1
  85. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +51 -43
  86. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  87. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +47 -42
  88. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  89. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +6 -6
  90. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +47 -42
  91. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
  92. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.d.mts.map +1 -1
  93. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +50 -42
  94. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  95. package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts.map +1 -1
  96. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +50 -42
  97. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  98. package/dist/astro/routes/api/admin/plugins/index.mjs +47 -42
  99. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  100. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -4
  101. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +47 -42
  102. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  103. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts.map +1 -1
  104. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +50 -42
  105. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  106. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +47 -42
  107. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  108. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.d.mts.map +1 -1
  109. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +50 -42
  110. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  111. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -1
  112. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +51 -43
  113. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  114. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +47 -42
  115. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  116. package/dist/astro/routes/api/admin/plugins/registry/install.d.mts.map +1 -1
  117. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +51 -43
  118. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  119. package/dist/astro/routes/api/admin/plugins/updates.mjs +47 -42
  120. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  121. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +47 -42
  122. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  123. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +4 -4
  124. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +47 -42
  125. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  126. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
  127. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +3 -3
  128. package/dist/astro/routes/api/admin/users/_id_/index.mjs +9 -8
  129. package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
  130. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
  131. package/dist/astro/routes/api/admin/users/index.mjs +9 -8
  132. package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
  133. package/dist/astro/routes/api/auth/dev-bypass.mjs +7 -7
  134. package/dist/astro/routes/api/auth/invite/accept.mjs +3 -3
  135. package/dist/astro/routes/api/auth/invite/complete.mjs +13 -12
  136. package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
  137. package/dist/astro/routes/api/auth/invite/index.mjs +10 -9
  138. package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
  139. package/dist/astro/routes/api/auth/invite/register-options.mjs +12 -11
  140. package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
  141. package/dist/astro/routes/api/auth/logout.mjs +4 -4
  142. package/dist/astro/routes/api/auth/magic-link/send.mjs +12 -11
  143. package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
  144. package/dist/astro/routes/api/auth/magic-link/verify.mjs +4 -4
  145. package/dist/astro/routes/api/auth/me.mjs +10 -9
  146. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  147. package/dist/astro/routes/api/auth/mode.mjs +4 -4
  148. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
  149. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  150. package/dist/astro/routes/api/auth/passkey/_id_.mjs +9 -8
  151. package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
  152. package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
  153. package/dist/astro/routes/api/auth/passkey/options.mjs +14 -13
  154. package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
  155. package/dist/astro/routes/api/auth/passkey/register/options.mjs +12 -11
  156. package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
  157. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +13 -12
  158. package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
  159. package/dist/astro/routes/api/auth/passkey/verify.mjs +13 -12
  160. package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
  161. package/dist/astro/routes/api/auth/signup/complete.mjs +13 -12
  162. package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
  163. package/dist/astro/routes/api/auth/signup/request.mjs +12 -11
  164. package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
  165. package/dist/astro/routes/api/auth/signup/verify.mjs +3 -3
  166. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +15 -14
  167. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
  168. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +14 -13
  169. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs.map +1 -1
  170. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +4 -4
  171. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +5 -5
  172. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +5 -5
  173. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +4 -4
  174. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +13 -13
  175. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +11 -10
  176. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  177. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +18 -17
  178. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  179. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +18 -17
  180. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  181. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +5 -5
  182. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +4 -4
  183. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +11 -10
  184. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  185. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts.map +1 -1
  186. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +44 -31
  187. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  188. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +4 -4
  189. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +5 -5
  190. package/dist/astro/routes/api/content/_collection_/_id_.mjs +11 -10
  191. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  192. package/dist/astro/routes/api/content/_collection_/authors.mjs +4 -4
  193. package/dist/astro/routes/api/content/_collection_/index.mjs +11 -10
  194. package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
  195. package/dist/astro/routes/api/content/_collection_/trash.mjs +10 -9
  196. package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
  197. package/dist/astro/routes/api/dashboard.mjs +10 -8
  198. package/dist/astro/routes/api/dashboard.mjs.map +1 -1
  199. package/dist/astro/routes/api/dev/emails.mjs +4 -4
  200. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  201. package/dist/astro/routes/api/import/probe.mjs +18 -16
  202. package/dist/astro/routes/api/import/probe.mjs.map +1 -1
  203. package/dist/astro/routes/api/import/wordpress/analyze.mjs +5 -5
  204. package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
  205. package/dist/astro/routes/api/import/wordpress/execute.mjs +15 -15
  206. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  207. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
  208. package/dist/astro/routes/api/import/wordpress/media.mjs +14 -12
  209. package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
  210. package/dist/astro/routes/api/import/wordpress/prepare.mjs +13 -12
  211. package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
  212. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
  213. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +14 -14
  214. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  215. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +18 -16
  216. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
  217. package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
  218. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
  219. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +28 -27
  220. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  221. package/dist/astro/routes/api/manifest.mjs +5 -5
  222. package/dist/astro/routes/api/mcp.d.mts.map +1 -1
  223. package/dist/astro/routes/api/mcp.mjs +174 -72
  224. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  225. package/dist/astro/routes/api/media/_id_/confirm.mjs +13 -11
  226. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  227. package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
  228. package/dist/astro/routes/api/media/_id_/usage.mjs +17 -16
  229. package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
  230. package/dist/astro/routes/api/media/_id_.mjs +16 -15
  231. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  232. package/dist/astro/routes/api/media/file/_...key_.mjs +3 -3
  233. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
  234. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
  235. package/dist/astro/routes/api/media/providers/index.mjs +4 -4
  236. package/dist/astro/routes/api/media/upload-url.mjs +11 -10
  237. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  238. package/dist/astro/routes/api/media.mjs +18 -15
  239. package/dist/astro/routes/api/media.mjs.map +1 -1
  240. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +11 -11
  241. package/dist/astro/routes/api/menus/_name_/items.mjs +11 -11
  242. package/dist/astro/routes/api/menus/_name_/reorder.mjs +11 -11
  243. package/dist/astro/routes/api/menus/_name_/translations.mjs +11 -11
  244. package/dist/astro/routes/api/menus/_name_.mjs +11 -11
  245. package/dist/astro/routes/api/menus/index.mjs +11 -11
  246. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  247. package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -7
  248. package/dist/astro/routes/api/oauth/device/code.mjs +10 -10
  249. package/dist/astro/routes/api/oauth/device/token.mjs +9 -9
  250. package/dist/astro/routes/api/oauth/register.mjs +4 -4
  251. package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -7
  252. package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -7
  253. package/dist/astro/routes/api/oauth/token.mjs +7 -7
  254. package/dist/astro/routes/api/openapi.json.mjs +152 -6
  255. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  256. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts.map +1 -1
  257. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +8 -7
  258. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs.map +1 -1
  259. package/dist/astro/routes/api/redirects/404s/index.mjs +12 -11
  260. package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
  261. package/dist/astro/routes/api/redirects/404s/summary.mjs +12 -11
  262. package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
  263. package/dist/astro/routes/api/redirects/_id_.mjs +13 -12
  264. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  265. package/dist/astro/routes/api/redirects/index.mjs +13 -12
  266. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  267. package/dist/astro/routes/api/relations/_id_/index.mjs +18 -17
  268. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  269. package/dist/astro/routes/api/relations/_id_/translations.mjs +13 -12
  270. package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
  271. package/dist/astro/routes/api/relations/index.mjs +18 -17
  272. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  273. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
  274. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +5 -5
  275. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +47 -42
  276. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  277. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +47 -42
  278. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  279. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +47 -42
  280. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  281. package/dist/astro/routes/api/schema/collections/_slug_/index.d.mts.map +1 -1
  282. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +47 -42
  283. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  284. package/dist/astro/routes/api/schema/collections/index.mjs +47 -42
  285. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  286. package/dist/astro/routes/api/schema/collections/reorder.mjs +47 -42
  287. package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
  288. package/dist/astro/routes/api/schema/index.mjs +10 -9
  289. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  290. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +47 -42
  291. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  292. package/dist/astro/routes/api/schema/orphans/index.mjs +47 -42
  293. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  294. package/dist/astro/routes/api/search/enable.mjs +13 -13
  295. package/dist/astro/routes/api/search/index.mjs +12 -12
  296. package/dist/astro/routes/api/search/rebuild.mjs +13 -13
  297. package/dist/astro/routes/api/search/stats.mjs +8 -8
  298. package/dist/astro/routes/api/search/suggest.mjs +12 -12
  299. package/dist/astro/routes/api/sections/_slug_.mjs +12 -11
  300. package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
  301. package/dist/astro/routes/api/sections/index.mjs +12 -11
  302. package/dist/astro/routes/api/sections/index.mjs.map +1 -1
  303. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +7 -7
  304. package/dist/astro/routes/api/settings/backups/archives/index.mjs +6 -6
  305. package/dist/astro/routes/api/settings/backups/export.mjs +7 -7
  306. package/dist/astro/routes/api/settings/backups/index.mjs +8 -8
  307. package/dist/astro/routes/api/settings/email.mjs +5 -5
  308. package/dist/astro/routes/api/settings.mjs +14 -13
  309. package/dist/astro/routes/api/settings.mjs.map +1 -1
  310. package/dist/astro/routes/api/setup/admin-verify.mjs +14 -13
  311. package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
  312. package/dist/astro/routes/api/setup/admin.mjs +13 -12
  313. package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
  314. package/dist/astro/routes/api/setup/dev-bypass.mjs +30 -28
  315. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  316. package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
  317. package/dist/astro/routes/api/setup/index.mjs +32 -30
  318. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  319. package/dist/astro/routes/api/setup/status.mjs +5 -5
  320. package/dist/astro/routes/api/snapshot.mjs +8 -8
  321. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +16 -15
  322. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
  323. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +16 -15
  324. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  325. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +16 -15
  326. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  327. package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts.map +1 -1
  328. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +18 -16
  329. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  330. package/dist/astro/routes/api/taxonomies/_name_/translations.d.mts +8 -0
  331. package/dist/astro/routes/api/taxonomies/_name_/translations.d.mts.map +1 -0
  332. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +46 -0
  333. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs.map +1 -0
  334. package/dist/astro/routes/api/taxonomies/_name_.d.mts +19 -0
  335. package/dist/astro/routes/api/taxonomies/_name_.d.mts.map +1 -0
  336. package/dist/astro/routes/api/taxonomies/_name_.mjs +88 -0
  337. package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -0
  338. package/dist/astro/routes/api/taxonomies/index.mjs +16 -15
  339. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  340. package/dist/astro/routes/api/themes/preview.mjs +6 -6
  341. package/dist/astro/routes/api/typegen.mjs +6 -6
  342. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  343. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  344. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  345. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +10 -9
  346. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  347. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +12 -11
  348. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  349. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +12 -11
  350. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  351. package/dist/astro/routes/api/widget-areas/_name_.mjs +7 -6
  352. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  353. package/dist/astro/routes/api/widget-areas/index.mjs +12 -11
  354. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  355. package/dist/astro/routes/api/widget-components.mjs +4 -4
  356. package/dist/astro/routes/robots.txt.mjs +7 -6
  357. package/dist/astro/routes/robots.txt.mjs.map +1 -1
  358. package/dist/astro/routes/sitemap-_collection_.xml.mjs +10 -10
  359. package/dist/astro/routes/sitemap.xml.mjs +8 -7
  360. package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
  361. package/dist/astro/types.d.mts +35 -17
  362. package/dist/astro/types.d.mts.map +1 -1
  363. package/dist/auth/providers/github.d.mts +1 -1
  364. package/dist/auth/providers/google.d.mts +1 -1
  365. package/dist/{authorize-qRhWQnel.mjs → authorize-1iFJzPaZ.mjs} +2 -2
  366. package/dist/{authorize-qRhWQnel.mjs.map → authorize-1iFJzPaZ.mjs.map} +1 -1
  367. package/dist/{backup-CtA6LDNw.mjs → backup-LHjIAVNP.mjs} +4 -4
  368. package/dist/{backup-CtA6LDNw.mjs.map → backup-LHjIAVNP.mjs.map} +1 -1
  369. package/dist/{base64-B-PsqheR.mjs → base64-CvLhUKQv.mjs} +1 -1
  370. package/dist/{base64-B-PsqheR.mjs.map → base64-CvLhUKQv.mjs.map} +1 -1
  371. package/dist/bundle-CBEXrBnb.mjs +663 -0
  372. package/dist/bundle-CBEXrBnb.mjs.map +1 -0
  373. package/dist/{byline-fields-CNc0Yq5i.mjs → byline-fields-C5S8wygO.mjs} +2 -2
  374. package/dist/{byline-fields-CNc0Yq5i.mjs.map → byline-fields-C5S8wygO.mjs.map} +1 -1
  375. package/dist/{byline-Cp6csL84.mjs → byline-pEcoDps8.mjs} +5 -5
  376. package/dist/{byline-Cp6csL84.mjs.map → byline-pEcoDps8.mjs.map} +1 -1
  377. package/dist/{byline-registry-ZkPKqEwC.mjs → byline-registry-DDun8sxh.mjs} +2 -2
  378. package/dist/{byline-registry-ZkPKqEwC.mjs.map → byline-registry-DDun8sxh.mjs.map} +1 -1
  379. package/dist/{bylines-CEj5CdT4.mjs → bylines-BWemdX2k.mjs} +7 -7
  380. package/dist/{bylines-CEj5CdT4.mjs.map → bylines-BWemdX2k.mjs.map} +1 -1
  381. package/dist/{bylines-cf-C9lmF.mjs → bylines-DJfSfXCL.mjs} +5 -5
  382. package/dist/{bylines-cf-C9lmF.mjs.map → bylines-DJfSfXCL.mjs.map} +1 -1
  383. package/dist/{cache-C9FptTFR.mjs → cache-CBJaDy_E.mjs} +3 -3
  384. package/dist/{cache-C9FptTFR.mjs.map → cache-CBJaDy_E.mjs.map} +1 -1
  385. package/dist/{challenge-store-D9VNKELo.mjs → challenge-store-CZyFVaad.mjs} +1 -1
  386. package/dist/{challenge-store-D9VNKELo.mjs.map → challenge-store-CZyFVaad.mjs.map} +1 -1
  387. package/dist/{chunks-DbYzib_k.mjs → chunks-DfPax1I0.mjs} +2 -2
  388. package/dist/{chunks-DbYzib_k.mjs.map → chunks-DfPax1I0.mjs.map} +1 -1
  389. package/dist/cli/index.mjs +176 -698
  390. package/dist/cli/index.mjs.map +1 -1
  391. package/dist/client/cf-access.d.mts +1 -1
  392. package/dist/client/index.d.mts +74 -4
  393. package/dist/client/index.d.mts.map +1 -1
  394. package/dist/client/index.mjs +25 -2
  395. package/dist/client/index.mjs.map +1 -1
  396. package/dist/collection-deletion-C8BugSGz.mjs +779 -0
  397. package/dist/collection-deletion-C8BugSGz.mjs.map +1 -0
  398. package/dist/{comment-BIkJ6Fil.mjs → comment-Dxlep6cE.mjs} +2 -2
  399. package/dist/{comment-BIkJ6Fil.mjs.map → comment-Dxlep6cE.mjs.map} +1 -1
  400. package/dist/{comment-reaction-D-laEceb.mjs → comment-reaction-D6qfHKBc.mjs} +2 -2
  401. package/dist/{comment-reaction-D-laEceb.mjs.map → comment-reaction-D6qfHKBc.mjs.map} +1 -1
  402. package/dist/{comments-0_w6KChA.mjs → comments-CTOuhTSX.mjs} +3 -3
  403. package/dist/{comments-0_w6KChA.mjs.map → comments-CTOuhTSX.mjs.map} +1 -1
  404. package/dist/{components-BPxrRjpE.mjs → components-CQy3lh9x.mjs} +1 -1
  405. package/dist/{components-BPxrRjpE.mjs.map → components-CQy3lh9x.mjs.map} +1 -1
  406. package/dist/{config-DyF3OKg_.mjs → config-C3EBstHi.mjs} +18 -2
  407. package/dist/config-C3EBstHi.mjs.map +1 -0
  408. package/dist/{content-CJ94NEx5.mjs → content-BNfwh7xX.mjs} +509 -78
  409. package/dist/content-BNfwh7xX.mjs.map +1 -0
  410. package/dist/content-list-query-DhQ84eVV.d.mts +26 -0
  411. package/dist/content-list-query-DhQ84eVV.d.mts.map +1 -0
  412. package/dist/content-refresh-DNERP4-f.mjs +2560 -0
  413. package/dist/content-refresh-DNERP4-f.mjs.map +1 -0
  414. package/dist/{context-C_1nrqnW.mjs → context-DRgOvSnW.mjs} +25 -17
  415. package/dist/context-DRgOvSnW.mjs.map +1 -0
  416. package/dist/{cron-DRV-UC4K.mjs → cron-Y9aLtgrA.mjs} +1 -1
  417. package/dist/{cron-DRV-UC4K.mjs.map → cron-Y9aLtgrA.mjs.map} +1 -1
  418. package/dist/{dashboard-DINnNVVU.mjs → dashboard-CB8J89rJ.mjs} +44 -10
  419. package/dist/dashboard-CB8J89rJ.mjs.map +1 -0
  420. package/dist/db/index.d.mts +4 -4
  421. package/dist/db/index.mjs +2 -2
  422. package/dist/db/index.mjs.map +1 -1
  423. package/dist/db/libsql.d.mts +1 -1
  424. package/dist/db/postgres.d.mts +1 -1
  425. package/dist/db/sqlite.d.mts +1 -1
  426. package/dist/{db-errors-CcWLaRiR.mjs → db-errors-Cl-sKYTm.mjs} +1 -1
  427. package/dist/{db-errors-CcWLaRiR.mjs.map → db-errors-Cl-sKYTm.mjs.map} +1 -1
  428. package/dist/{default-BD2FG0xV.mjs → default-DdDl3fxv.mjs} +1 -1
  429. package/dist/{default-BD2FG0xV.mjs.map → default-DdDl3fxv.mjs.map} +1 -1
  430. package/dist/{deferred-tasks-C-WRCcKo.d.mts → deferred-tasks-BMsa9F64.d.mts} +1 -1
  431. package/dist/{deferred-tasks-C-WRCcKo.d.mts.map → deferred-tasks-BMsa9F64.d.mts.map} +1 -1
  432. package/dist/{device-flow-CTqd8mud.mjs → device-flow-CaioYGK3.mjs} +4 -4
  433. package/dist/{device-flow-CTqd8mud.mjs.map → device-flow-CaioYGK3.mjs.map} +1 -1
  434. package/dist/dist-D_6CKR8z.mjs +810 -0
  435. package/dist/dist-D_6CKR8z.mjs.map +1 -0
  436. package/dist/{email-console-C6WdcWbL.mjs → email-console-BHNjIwiB.mjs} +1 -1
  437. package/dist/{email-console-C6WdcWbL.mjs.map → email-console-BHNjIwiB.mjs.map} +1 -1
  438. package/dist/{enrich-CFJJgxs_.mjs → enrich-_ySMEvEE.mjs} +2 -2
  439. package/dist/{enrich-CFJJgxs_.mjs.map → enrich-_ySMEvEE.mjs.map} +1 -1
  440. package/dist/{error-oh_9i6bR.mjs → error-DupyJZE2.mjs} +3 -3
  441. package/dist/{error-oh_9i6bR.mjs.map → error-DupyJZE2.mjs.map} +1 -1
  442. package/dist/{errors-BucQ8VYz.mjs → errors-CLUPm7aD.mjs} +14 -2
  443. package/dist/errors-CLUPm7aD.mjs.map +1 -0
  444. package/dist/{escape-CXps5F_v.mjs → escape-C_aLKP3H.mjs} +1 -1
  445. package/dist/{escape-CXps5F_v.mjs.map → escape-C_aLKP3H.mjs.map} +1 -1
  446. package/dist/{field-defs-cache-WD82dMQq.mjs → field-defs-cache-hg6QkjV9.mjs} +4 -4
  447. package/dist/{field-defs-cache-WD82dMQq.mjs.map → field-defs-cache-hg6QkjV9.mjs.map} +1 -1
  448. package/dist/{fts-manager-BITakW_R.mjs → fts-manager-CZ-5dVwJ.mjs} +76 -50
  449. package/dist/fts-manager-CZ-5dVwJ.mjs.map +1 -0
  450. package/dist/{hreflang-BaLCNcDx.d.mts → hreflang-D3Uehfv2.d.mts} +2 -2
  451. package/dist/{hreflang-BaLCNcDx.d.mts.map → hreflang-D3Uehfv2.d.mts.map} +1 -1
  452. package/dist/{hreflang-BdZuVBhL.mjs → hreflang-DNswoYsi.mjs} +7 -7
  453. package/dist/{hreflang-BdZuVBhL.mjs.map → hreflang-DNswoYsi.mjs.map} +1 -1
  454. package/dist/{import-C-9uqyT3.mjs → import-B41u8Rot.mjs} +5 -5
  455. package/dist/{import-C-9uqyT3.mjs.map → import-B41u8Rot.mjs.map} +1 -1
  456. package/dist/{index-M37iz29I.d.mts → index-C3Y-rsyN.d.mts} +226 -73
  457. package/dist/index-C3Y-rsyN.d.mts.map +1 -0
  458. package/dist/{index-MGw47uWq.d.mts → index-Cof4-Fab.d.mts} +5 -10
  459. package/dist/index-Cof4-Fab.d.mts.map +1 -0
  460. package/dist/index.d.mts +20 -19
  461. package/dist/index.mjs +73 -67
  462. package/dist/{init-lock-DJkX6Hto.mjs → init-lock-6b309ZrF.mjs} +1 -1
  463. package/dist/{init-lock-DJkX6Hto.mjs.map → init-lock-6b309ZrF.mjs.map} +1 -1
  464. package/dist/{load-DV1DZuUJ.mjs → load-DmvJrtxS.mjs} +2 -2
  465. package/dist/{load-DV1DZuUJ.mjs.map → load-DmvJrtxS.mjs.map} +1 -1
  466. package/dist/{loader-CGugY7Zl.mjs → loader-DLBqRiGP.mjs} +95 -53
  467. package/dist/loader-DLBqRiGP.mjs.map +1 -0
  468. package/dist/{manifest-schema-Da7kj23y.mjs → manifest-schema-BXbUAHod.mjs} +1 -1
  469. package/dist/{manifest-schema-Da7kj23y.mjs.map → manifest-schema-BXbUAHod.mjs.map} +1 -1
  470. package/dist/media/image-endpoint.mjs +1 -1
  471. package/dist/media/index.d.mts +1 -1
  472. package/dist/media/index.mjs +3 -2
  473. package/dist/media/index.mjs.map +1 -1
  474. package/dist/media/local-runtime.d.mts +12 -12
  475. package/dist/media/local-runtime.mjs +6 -5
  476. package/dist/media/local-runtime.mjs.map +1 -1
  477. package/dist/{media-OhYL-7Io.mjs → media-BWWWb8kg.mjs} +92 -3
  478. package/dist/media-BWWWb8kg.mjs.map +1 -0
  479. package/dist/{media-BP7S-nHT.mjs → media-CzhAn2--.mjs} +2 -2
  480. package/dist/{media-BP7S-nHT.mjs.map → media-CzhAn2--.mjs.map} +1 -1
  481. package/dist/{media-allowlist-D-peH1H5.mjs → media-allowlist-BD6HhgXA.mjs} +14 -3
  482. package/dist/media-allowlist-BD6HhgXA.mjs.map +1 -0
  483. package/dist/{media-upload-DtrwU2B0.mjs → media-upload-9yas9zGH.mjs} +10 -8
  484. package/dist/{media-upload-DtrwU2B0.mjs.map → media-upload-9yas9zGH.mjs.map} +1 -1
  485. package/dist/{media-url-DYs_YLOx.mjs → media-url-BvoX88FA.mjs} +1 -1
  486. package/dist/{media-url-DYs_YLOx.mjs.map → media-url-BvoX88FA.mjs.map} +1 -1
  487. package/dist/{media-usage-BlxfTkN0.mjs → media-usage-De3qvwjc.mjs} +137 -60
  488. package/dist/media-usage-De3qvwjc.mjs.map +1 -0
  489. package/dist/media-usage-work-B9Bksh_b.mjs +152 -0
  490. package/dist/media-usage-work-B9Bksh_b.mjs.map +1 -0
  491. package/dist/media-usage-work-DrCbqBcl.mjs +424 -0
  492. package/dist/media-usage-work-DrCbqBcl.mjs.map +1 -0
  493. package/dist/media-usage-write-fence-aHEgN1zB.mjs +42 -0
  494. package/dist/media-usage-write-fence-aHEgN1zB.mjs.map +1 -0
  495. package/dist/{menus-Y54RDPIN.mjs → menus-CC-yn1F9.mjs} +351 -95
  496. package/dist/menus-CC-yn1F9.mjs.map +1 -0
  497. package/dist/{menus-BcQWxeXu.mjs → menus-fxvEDYE6.mjs} +3 -3
  498. package/dist/{menus-BcQWxeXu.mjs.map → menus-fxvEDYE6.mjs.map} +1 -1
  499. package/dist/{mode-BWyTBe3I.mjs → mode-Bpe-tNNy.mjs} +1 -1
  500. package/dist/{mode-BWyTBe3I.mjs.map → mode-Bpe-tNNy.mjs.map} +1 -1
  501. package/dist/{normalize-C-SHXmra.mjs → normalize-Bc-s_r-a.mjs} +1 -1
  502. package/dist/{normalize-C-SHXmra.mjs.map → normalize-Bc-s_r-a.mjs.map} +1 -1
  503. package/dist/{oauth-authorization-ClvktmrL.mjs → oauth-authorization-BXO1MKej.mjs} +4 -4
  504. package/dist/{oauth-authorization-ClvktmrL.mjs.map → oauth-authorization-BXO1MKej.mjs.map} +1 -1
  505. package/dist/{oauth-clients-8ao-U66l.mjs → oauth-clients-C_DNqtRu.mjs} +1 -1
  506. package/dist/{oauth-clients-8ao-U66l.mjs.map → oauth-clients-C_DNqtRu.mjs.map} +1 -1
  507. package/dist/{oauth-state-store-B1ofdTiQ.mjs → oauth-state-store-DwAxMtk4.mjs} +1 -1
  508. package/dist/{oauth-state-store-B1ofdTiQ.mjs.map → oauth-state-store-DwAxMtk4.mjs.map} +1 -1
  509. package/dist/{oauth-user-lookup-CscEuIiR.mjs → oauth-user-lookup-BxfZizdk.mjs} +1 -1
  510. package/dist/{oauth-user-lookup-CscEuIiR.mjs.map → oauth-user-lookup-BxfZizdk.mjs.map} +1 -1
  511. package/dist/object-cache/memory.d.mts +1 -1
  512. package/dist/{options-CA4eVX6P.d.mts → options-DRe1pIBL.d.mts} +4 -3
  513. package/dist/options-DRe1pIBL.d.mts.map +1 -0
  514. package/dist/page/index.d.mts +2 -2
  515. package/dist/{parse-CJKNuewM.mjs → parse-DFbuc-Fh.mjs} +2 -2
  516. package/dist/{parse-CJKNuewM.mjs.map → parse-DFbuc-Fh.mjs.map} +1 -1
  517. package/dist/{passkey-config-D-zrzMJI.mjs → passkey-config-_2GOwwK8.mjs} +1 -1
  518. package/dist/{passkey-config-D-zrzMJI.mjs.map → passkey-config-_2GOwwK8.mjs.map} +1 -1
  519. package/dist/{placeholder-DaeNS-VG.d.mts → placeholder-CzW7Lshs.d.mts} +1 -1
  520. package/dist/{placeholder-DaeNS-VG.d.mts.map → placeholder-CzW7Lshs.d.mts.map} +1 -1
  521. package/dist/plugin-types.d.mts +8 -1
  522. package/dist/plugin-types.d.mts.map +1 -1
  523. package/dist/plugin-utils.d.mts +10 -10
  524. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
  525. package/dist/plugins/adapt-sandbox-entry.mjs +7 -4
  526. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  527. package/dist/{portable-text-B_kqBmXB.mjs → portable-text-DhKbDAGr.mjs} +1 -1
  528. package/dist/{portable-text-B_kqBmXB.mjs.map → portable-text-DhKbDAGr.mjs.map} +1 -1
  529. package/dist/{preview-BU6PaLpM.mjs → preview-PW-nOnq4.mjs} +2 -2
  530. package/dist/{preview-BU6PaLpM.mjs.map → preview-PW-nOnq4.mjs.map} +1 -1
  531. package/dist/{public-url-Ox9oOlgp.mjs → public-url-CWoFhQT8.mjs} +1 -1
  532. package/dist/{public-url-Ox9oOlgp.mjs.map → public-url-CWoFhQT8.mjs.map} +1 -1
  533. package/dist/{query-C5w7PsRQ.mjs → query-BgjfiLUQ.mjs} +31 -49
  534. package/dist/query-BgjfiLUQ.mjs.map +1 -0
  535. package/dist/{query-rC9HKv1N.mjs → query-vgZNwIhh.mjs} +3 -3
  536. package/dist/{query-rC9HKv1N.mjs.map → query-vgZNwIhh.mjs.map} +1 -1
  537. package/dist/{rate-limit-kug6q99V.mjs → rate-limit-L5anykgo.mjs} +2 -2
  538. package/dist/{rate-limit-kug6q99V.mjs.map → rate-limit-L5anykgo.mjs.map} +1 -1
  539. package/dist/{redirect-DIJxXKOG.mjs → redirect-COsS6SWJ.mjs} +2 -2
  540. package/dist/{redirect-DIJxXKOG.mjs.map → redirect-COsS6SWJ.mjs.map} +1 -1
  541. package/dist/{redirect-Egf2R9yP.mjs → redirect-KBNE3UEs.mjs} +1 -1
  542. package/dist/{redirect-Egf2R9yP.mjs.map → redirect-KBNE3UEs.mjs.map} +1 -1
  543. package/dist/{redirects-Bzm812bY.mjs → redirects-CufRQt0Q.mjs} +4 -4
  544. package/dist/{redirects-Bzm812bY.mjs.map → redirects-CufRQt0Q.mjs.map} +1 -1
  545. package/dist/{redirects-G8wAhWAf.mjs → redirects-cV9g7V-0.mjs} +133 -14
  546. package/dist/redirects-cV9g7V-0.mjs.map +1 -0
  547. package/dist/registry-C581V4TP.mjs +1275 -0
  548. package/dist/registry-C581V4TP.mjs.map +1 -0
  549. package/dist/{relation-Cbe3Sd3m.mjs → relation-DtmAQH7s.mjs} +3 -3
  550. package/dist/{relation-Cbe3Sd3m.mjs.map → relation-DtmAQH7s.mjs.map} +1 -1
  551. package/dist/{relations-Bw_E1TXo.mjs → relations-BBApI4e2.mjs} +3 -3
  552. package/dist/{relations-Bw_E1TXo.mjs.map → relations-BBApI4e2.mjs.map} +1 -1
  553. package/dist/{relations-qHwpUe66.mjs → relations-DICSyfsh.mjs} +6 -6
  554. package/dist/{relations-qHwpUe66.mjs.map → relations-DICSyfsh.mjs.map} +1 -1
  555. package/dist/{relations-G2ta7GIl.d.mts → relations-DItI3MN9.d.mts} +369 -35
  556. package/dist/relations-DItI3MN9.d.mts.map +1 -0
  557. package/dist/request-context.d.mts +1 -1
  558. package/dist/{request-meta-Df-jFbAd.mjs → request-meta-DgZJh3zG.mjs} +2 -2
  559. package/dist/{request-meta-Df-jFbAd.mjs.map → request-meta-DgZJh3zG.mjs.map} +1 -1
  560. package/dist/{resolve-DZzG4HqM.mjs → resolve-DOhVEvty.mjs} +2 -2
  561. package/dist/{resolve-DZzG4HqM.mjs.map → resolve-DOhVEvty.mjs.map} +1 -1
  562. package/dist/{runner-BFTrkYhu.mjs → runner-BiDvq5dv.mjs} +1157 -254
  563. package/dist/runner-BiDvq5dv.mjs.map +1 -0
  564. package/dist/{runner-BaADli8l.d.mts → runner-CgQbELYC.d.mts} +2 -2
  565. package/dist/{runner-BaADli8l.d.mts.map → runner-CgQbELYC.d.mts.map} +1 -1
  566. package/dist/runtime.d.mts +11 -11
  567. package/dist/runtime.mjs +4 -3
  568. package/dist/runtime.mjs.map +1 -1
  569. package/dist/{schema-CiQXr6_F.mjs → schema-BFyTg38T.mjs} +3 -3
  570. package/dist/{schema-CiQXr6_F.mjs.map → schema-BFyTg38T.mjs.map} +1 -1
  571. package/dist/schema-CeQSNwB3.mjs +390 -0
  572. package/dist/schema-CeQSNwB3.mjs.map +1 -0
  573. package/dist/{scopes-CNJVT5i5.mjs → scopes-BVeuhGBE.mjs} +3 -3
  574. package/dist/{scopes-CNJVT5i5.mjs.map → scopes-BVeuhGBE.mjs.map} +1 -1
  575. package/dist/{search-FZhx5dDU.mjs → search-BpuPKZb0.mjs} +25 -15
  576. package/dist/search-BpuPKZb0.mjs.map +1 -0
  577. package/dist/{secrets-DLd8RXee.mjs → secrets-dtYs2hDo.mjs} +3 -3
  578. package/dist/{secrets-DLd8RXee.mjs.map → secrets-dtYs2hDo.mjs.map} +1 -1
  579. package/dist/{sections-BrIrMUCn.mjs → sections-COctk6hf.mjs} +3 -3
  580. package/dist/{sections-BrIrMUCn.mjs.map → sections-COctk6hf.mjs.map} +1 -1
  581. package/dist/seed/index.d.mts +2 -2
  582. package/dist/seed/index.mjs +24 -22
  583. package/dist/seo/index.d.mts +3 -3
  584. package/dist/seo/index.mjs +16 -15
  585. package/dist/seo/index.mjs.map +1 -1
  586. package/dist/{seo-CobNfOvb.mjs → seo-AFpVvTqr.mjs} +5 -3
  587. package/dist/seo-AFpVvTqr.mjs.map +1 -0
  588. package/dist/{seo-DTHRntNC.mjs → seo-CvDxfvFb.mjs} +2 -2
  589. package/dist/{seo-DTHRntNC.mjs.map → seo-CvDxfvFb.mjs.map} +1 -1
  590. package/dist/{service-B-pYGPgR.mjs → service-xMiMSTsR.mjs} +3 -3
  591. package/dist/{service-B-pYGPgR.mjs.map → service-xMiMSTsR.mjs.map} +1 -1
  592. package/dist/{session-user-BvM9-zNl.mjs → session-user-BzVemLkw.mjs} +1 -1
  593. package/dist/{session-user-BvM9-zNl.mjs.map → session-user-BzVemLkw.mjs.map} +1 -1
  594. package/dist/{settings-kJhGiQ4z.mjs → settings-B372MV0p.mjs} +4 -4
  595. package/dist/{settings-kJhGiQ4z.mjs.map → settings-B372MV0p.mjs.map} +1 -1
  596. package/dist/{settings-DPHau-WH.mjs → settings-BE9aqn_9.mjs} +3 -3
  597. package/dist/{settings-DPHau-WH.mjs.map → settings-BE9aqn_9.mjs.map} +1 -1
  598. package/dist/{setup-complete-u50Qm8rY.mjs → setup-complete-DF7TAfD5.mjs} +1 -1
  599. package/dist/{setup-complete-u50Qm8rY.mjs.map → setup-complete-DF7TAfD5.mjs.map} +1 -1
  600. package/dist/{setup-nonce-DCjiiOD2.mjs → setup-nonce-Do-U_cg9.mjs} +1 -1
  601. package/dist/{setup-nonce-DCjiiOD2.mjs.map → setup-nonce-Do-U_cg9.mjs.map} +1 -1
  602. package/dist/{single-flight-cache-DW3w11Pu.mjs → single-flight-cache-B0eLKS6K.mjs} +2 -2
  603. package/dist/{single-flight-cache-DW3w11Pu.mjs.map → single-flight-cache-B0eLKS6K.mjs.map} +1 -1
  604. package/dist/{site-url-I-lJ5kma.mjs → site-url-Bowsi9T-.mjs} +2 -2
  605. package/dist/{site-url-I-lJ5kma.mjs.map → site-url-Bowsi9T-.mjs.map} +1 -1
  606. package/dist/slugify-B994zkXC.mjs +17 -0
  607. package/dist/slugify-B994zkXC.mjs.map +1 -0
  608. package/dist/{snapshot-I_fjLDaD.mjs → snapshot-B5h6aPMh.mjs} +1 -1
  609. package/dist/{snapshot-I_fjLDaD.mjs.map → snapshot-B5h6aPMh.mjs.map} +1 -1
  610. package/dist/{ssrf-CTa6v21i.mjs → ssrf-Cpvg_n3A.mjs} +1 -1
  611. package/dist/{ssrf-CTa6v21i.mjs.map → ssrf-Cpvg_n3A.mjs.map} +1 -1
  612. package/dist/{state-CQvoefYP.mjs → state-DYwPue40.mjs} +1 -1
  613. package/dist/{state-CQvoefYP.mjs.map → state-DYwPue40.mjs.map} +1 -1
  614. package/dist/{status-CIsmlC1E.mjs → status-CWVYpbD8.mjs} +1 -1
  615. package/dist/{status-CIsmlC1E.mjs.map → status-CWVYpbD8.mjs.map} +1 -1
  616. package/dist/storage/local.d.mts +1 -1
  617. package/dist/storage/local.mjs +1 -1
  618. package/dist/storage/s3.d.mts +1 -1
  619. package/dist/storage/s3.mjs +1 -1
  620. package/dist/{taxonomies-CLN1N92E.mjs → taxonomies-B0o9ExNV.mjs} +75 -64
  621. package/dist/taxonomies-B0o9ExNV.mjs.map +1 -0
  622. package/dist/{taxonomies-CKuaZER5.mjs → taxonomies-Dk3MAklL.mjs} +280 -56
  623. package/dist/taxonomies-Dk3MAklL.mjs.map +1 -0
  624. package/dist/{taxonomy-pOrB2kZe.mjs → taxonomy-CPeM8d2o.mjs} +131 -78
  625. package/dist/taxonomy-CPeM8d2o.mjs.map +1 -0
  626. package/dist/{tokens-DagaJGK8.mjs → tokens-C39klX8R.mjs} +2 -2
  627. package/dist/{tokens-DagaJGK8.mjs.map → tokens-C39klX8R.mjs.map} +1 -1
  628. package/dist/{transport-DVsa5YYo.mjs → transport--CfOflrp.mjs} +1 -1
  629. package/dist/{transport-DVsa5YYo.mjs.map → transport--CfOflrp.mjs.map} +1 -1
  630. package/dist/{transport-CAwTaTp_.d.mts → transport-B_rfeU-a.d.mts} +1 -1
  631. package/dist/{transport-CAwTaTp_.d.mts.map → transport-B_rfeU-a.d.mts.map} +1 -1
  632. package/dist/{trusted-proxy-D20_7fyX.mjs → trusted-proxy-CEVekpby.mjs} +1 -1
  633. package/dist/{trusted-proxy-D20_7fyX.mjs.map → trusted-proxy-CEVekpby.mjs.map} +1 -1
  634. package/dist/{types-NBI3rF-_.mjs → types-75KIqzL9.mjs} +18 -2
  635. package/dist/types-75KIqzL9.mjs.map +1 -0
  636. package/dist/{types-CbUcuxxc.d.mts → types-BPuloofb.d.mts} +1 -1
  637. package/dist/{types-CbUcuxxc.d.mts.map → types-BPuloofb.d.mts.map} +1 -1
  638. package/dist/{types-BjxTKxrZ.mjs → types-BW1syrlw.mjs} +1 -1
  639. package/dist/{types-BjxTKxrZ.mjs.map → types-BW1syrlw.mjs.map} +1 -1
  640. package/dist/{types-BnFP1M3J.d.mts → types-C3_AbkDP.d.mts} +32 -10
  641. package/dist/types-C3_AbkDP.d.mts.map +1 -0
  642. package/dist/{types-Dc4ntQjQ.d.mts → types-CAGdePf3.d.mts} +124 -1
  643. package/dist/types-CAGdePf3.d.mts.map +1 -0
  644. package/dist/{types-DLG4IFEQ.d.mts → types-CFUfyt1s.d.mts} +1 -1
  645. package/dist/{types-DLG4IFEQ.d.mts.map → types-CFUfyt1s.d.mts.map} +1 -1
  646. package/dist/{types-LoagAGzH.d.mts → types-CPx2lIGo.d.mts} +1 -1
  647. package/dist/{types-LoagAGzH.d.mts.map → types-CPx2lIGo.d.mts.map} +1 -1
  648. package/dist/{types-Cd8iuS1J.mjs → types-CQPWhPxE.mjs} +9 -12
  649. package/dist/types-CQPWhPxE.mjs.map +1 -0
  650. package/dist/{types-Cz8hNch2.d.mts → types-Cs-TFLNf.d.mts} +25 -5
  651. package/dist/types-Cs-TFLNf.d.mts.map +1 -0
  652. package/dist/types-CtzytVXn.mjs.map +1 -1
  653. package/dist/{types-BGWPMiBO.d.mts → types-D4_1Ga4L.d.mts} +44 -5
  654. package/dist/types-D4_1Ga4L.d.mts.map +1 -0
  655. package/dist/{types-C8oz_ayJ.d.mts → types-DefV3OiO.d.mts} +1 -1
  656. package/dist/{types-C8oz_ayJ.d.mts.map → types-DefV3OiO.d.mts.map} +1 -1
  657. package/dist/{types-BK6U01Ro.d.mts → types-X0MThr7x.d.mts} +1 -1
  658. package/dist/{types-BK6U01Ro.d.mts.map → types-X0MThr7x.d.mts.map} +1 -1
  659. package/dist/{upload-attempts-D8QAFLUJ.mjs → upload-attempts-hlne3iA1.mjs} +1 -1
  660. package/dist/{upload-attempts-D8QAFLUJ.mjs.map → upload-attempts-hlne3iA1.mjs.map} +1 -1
  661. package/dist/url-pattern-BHS0V5Ng.mjs +33 -0
  662. package/dist/url-pattern-BHS0V5Ng.mjs.map +1 -0
  663. package/dist/{user-DBrq4UVq.mjs → user-sHgJ62SP.mjs} +3 -3
  664. package/dist/{user-DBrq4UVq.mjs.map → user-sHgJ62SP.mjs.map} +1 -1
  665. package/dist/{utils-hLzi_2Fn.mjs → utils-D4vD_Y98.mjs} +3 -3
  666. package/dist/{utils-hLzi_2Fn.mjs.map → utils-D4vD_Y98.mjs.map} +1 -1
  667. package/dist/{validate-mKo3RxBM.mjs → validate-BkqIG_Uj.mjs} +24 -5
  668. package/dist/validate-BkqIG_Uj.mjs.map +1 -0
  669. package/dist/{validate-C3oSmA7F.d.mts → validate-DeGtBN84.d.mts} +15 -7
  670. package/dist/validate-DeGtBN84.d.mts.map +1 -0
  671. package/dist/{validation-BgpucsvF.mjs → validation-CiJb_X5o.mjs} +11 -10
  672. package/dist/{validation-BgpucsvF.mjs.map → validation-CiJb_X5o.mjs.map} +1 -1
  673. package/dist/version-x0NjTScG.mjs +7 -0
  674. package/dist/{version-C4PnpmoV.mjs.map → version-x0NjTScG.mjs.map} +1 -1
  675. package/dist/{widgets-CJJtkiKh.mjs → widgets-Cwzvww0W.mjs} +3 -3
  676. package/dist/{widgets-CJJtkiKh.mjs.map → widgets-Cwzvww0W.mjs.map} +1 -1
  677. package/dist/{wxr-taxonomies-Bkpp_2tK.d.mts → wxr-taxonomies-CmC0ZsDr.d.mts} +1 -1
  678. package/dist/{wxr-taxonomies-Bkpp_2tK.d.mts.map → wxr-taxonomies-CmC0ZsDr.d.mts.map} +1 -1
  679. package/dist/{wxr-taxonomies-9PPSy8X6.mjs → wxr-taxonomies-QZjWxjyk.mjs} +13 -32
  680. package/dist/{wxr-taxonomies-9PPSy8X6.mjs.map → wxr-taxonomies-QZjWxjyk.mjs.map} +1 -1
  681. package/dist/{zod-generator-CG64Hnbh.mjs → zod-generator-D11MhxG4.mjs} +52 -5
  682. package/dist/zod-generator-D11MhxG4.mjs.map +1 -0
  683. package/package.json +16 -8
  684. package/src/api/errors.ts +12 -0
  685. package/src/api/handlers/content.ts +127 -29
  686. package/src/api/handlers/dashboard.ts +12 -2
  687. package/src/api/handlers/index.ts +16 -0
  688. package/src/api/handlers/manifest.ts +2 -0
  689. package/src/api/handlers/media-allowlist.ts +13 -2
  690. package/src/api/handlers/media-usage-work.ts +188 -0
  691. package/src/api/handlers/media-usage.ts +5 -3
  692. package/src/api/handlers/schema.ts +9 -3
  693. package/src/api/handlers/seo.ts +5 -2
  694. package/src/api/handlers/taxonomies.ts +345 -82
  695. package/src/api/media-usage-write-fence.ts +64 -0
  696. package/src/api/openapi/document.ts +199 -0
  697. package/src/api/schemas/common.ts +3 -1
  698. package/src/api/schemas/content.ts +122 -1
  699. package/src/api/schemas/media-usage.ts +133 -0
  700. package/src/api/schemas/schema.ts +78 -3
  701. package/src/api/schemas/taxonomies.ts +40 -8
  702. package/src/api/types.ts +1 -0
  703. package/src/astro/integration/index.ts +55 -42
  704. package/src/astro/integration/routes.ts +34 -4
  705. package/src/astro/integration/runtime.ts +15 -0
  706. package/src/astro/integration/virtual-modules.ts +25 -1
  707. package/src/astro/integration/vite-config.ts +16 -1
  708. package/src/astro/middleware/media-usage-write-fence.ts +30 -0
  709. package/src/astro/middleware/redirect.ts +12 -2
  710. package/src/astro/middleware.ts +75 -8
  711. package/src/astro/routes/api/admin/media-usage/collection-deletions/index.ts +22 -0
  712. package/src/astro/routes/api/admin/media-usage/collection-deletions/retry.ts +22 -0
  713. package/src/astro/routes/api/admin/media-usage/work/index.ts +26 -0
  714. package/src/astro/routes/api/admin/media-usage/work/retry.ts +26 -0
  715. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +4 -0
  716. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +4 -0
  717. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +4 -0
  718. package/src/astro/routes/api/admin/plugins/[id]/update.ts +4 -0
  719. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +4 -0
  720. package/src/astro/routes/api/admin/plugins/registry/[id]/uninstall.ts +4 -0
  721. package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +4 -0
  722. package/src/astro/routes/api/admin/plugins/registry/install.ts +4 -0
  723. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +67 -31
  724. package/src/astro/routes/api/import/wordpress/execute.ts +3 -10
  725. package/src/astro/routes/api/mcp.ts +1 -0
  726. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +7 -1
  727. package/src/astro/routes/api/schema/collections/[slug]/index.ts +1 -7
  728. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +1 -0
  729. package/src/astro/routes/api/taxonomies/[name]/translations.ts +48 -0
  730. package/src/astro/routes/api/taxonomies/[name].ts +102 -0
  731. package/src/astro/types.ts +25 -4
  732. package/src/cleanup.ts +6 -12
  733. package/src/cli/commands/doctor.ts +193 -6
  734. package/src/cli/commands/export-seed.ts +4 -1
  735. package/src/client/index.ts +119 -0
  736. package/src/components/Block.astro +12 -0
  737. package/src/components/InlinePortableTextEditor.tsx +89 -1
  738. package/src/content/converters/mark-safety.ts +120 -0
  739. package/src/content/converters/portable-text-to-prosemirror.ts +25 -16
  740. package/src/content/converters/prosemirror-to-portable-text.ts +12 -2
  741. package/src/content-list-query.ts +29 -0
  742. package/src/database/migrations/060_collection_admin_config.ts +16 -0
  743. package/src/database/migrations/061_media_usage_cleanup.ts +77 -0
  744. package/src/database/migrations/062_media_usage_cleanup_fence.ts +295 -0
  745. package/src/database/migrations/063_media_usage_incremental_work.ts +397 -0
  746. package/src/database/migrations/064_fts_plain_text.ts +253 -0
  747. package/src/database/migrations/065_media_usage_collection_deletion.ts +85 -0
  748. package/src/database/migrations/066_media_usage_reconciliation.ts +115 -0
  749. package/src/database/migrations/067_indexed_content_fields.ts +39 -0
  750. package/src/database/migrations/068_content_taxonomy_entry_groups.ts +61 -0
  751. package/src/database/migrations/069_collection_title_date_fields.ts +45 -0
  752. package/src/database/migrations/070_collection_routable.ts +18 -0
  753. package/src/database/migrations/runner.ts +22 -0
  754. package/src/database/repositories/content.ts +861 -96
  755. package/src/database/repositories/media-usage-work.ts +848 -0
  756. package/src/database/repositories/media-usage.ts +1502 -138
  757. package/src/database/repositories/revision.ts +17 -6
  758. package/src/database/repositories/taxonomy.ts +234 -103
  759. package/src/database/repositories/types.ts +51 -2
  760. package/src/database/types.ts +134 -16
  761. package/src/db/adapters.ts +28 -0
  762. package/src/db/index.ts +3 -0
  763. package/src/emdash-runtime.ts +227 -20
  764. package/src/fields/file.ts +7 -4
  765. package/src/fields/image.ts +8 -1
  766. package/src/fields/types.ts +18 -5
  767. package/src/i18n/config.ts +32 -0
  768. package/src/i18n/repair-locale-casing.ts +0 -14
  769. package/src/i18n/taxonomy-locale-diagnostic.ts +47 -0
  770. package/src/import/wxr-taxonomies.ts +7 -47
  771. package/src/index.ts +25 -2
  772. package/src/loader.ts +119 -65
  773. package/src/mcp/server.ts +242 -34
  774. package/src/media/usage/activation.ts +626 -0
  775. package/src/media/usage/capture-triggers.ts +543 -0
  776. package/src/media/usage/cleanup.ts +305 -0
  777. package/src/media/usage/collection-deletion-processor.ts +356 -0
  778. package/src/media/usage/collection-deletion.ts +836 -0
  779. package/src/media/usage/content-fields.ts +40 -3
  780. package/src/media/usage/content-refresh.ts +356 -47
  781. package/src/media/usage/content-repair.ts +180 -61
  782. package/src/media/usage/content-snapshots.ts +85 -105
  783. package/src/media/usage/projection-fingerprint.ts +129 -0
  784. package/src/media/usage/reconciliation-processor.ts +372 -0
  785. package/src/media/usage/reconciliation.ts +1117 -0
  786. package/src/media/usage/source-key.ts +4 -0
  787. package/src/media/usage/types.ts +2 -0
  788. package/src/media/usage/work-processor.ts +248 -0
  789. package/src/menus/index.ts +246 -141
  790. package/src/plugin-types.ts +8 -0
  791. package/src/plugins/adapt-sandbox-entry.ts +24 -5
  792. package/src/plugins/context.ts +24 -7
  793. package/src/plugins/index.ts +9 -0
  794. package/src/plugins/routes.ts +43 -1
  795. package/src/plugins/sandbox/index.ts +10 -1
  796. package/src/plugins/sandbox/types.ts +78 -1
  797. package/src/plugins/scheduler/node.ts +13 -1
  798. package/src/plugins/scheduler/types.ts +2 -0
  799. package/src/plugins/types.ts +39 -2
  800. package/src/query.ts +29 -37
  801. package/src/scheduler-health.ts +46 -0
  802. package/src/schema/registry.ts +777 -255
  803. package/src/schema/types.ts +50 -8
  804. package/src/schema/url-pattern.ts +33 -0
  805. package/src/schema/zod-generator.ts +77 -5
  806. package/src/search/fts-manager.ts +117 -76
  807. package/src/search/query.ts +32 -11
  808. package/src/search/types.ts +2 -0
  809. package/src/seed/apply.ts +68 -15
  810. package/src/seed/types.ts +11 -3
  811. package/src/seed/validate.ts +52 -2
  812. package/src/taxonomies/index.ts +77 -75
  813. package/src/taxonomies/term-counts.ts +21 -8
  814. package/src/utils/slugify.ts +1 -29
  815. package/src/virtual-modules.d.ts +16 -0
  816. package/dist/adapters-BMyNSSZi.d.mts.map +0 -1
  817. package/dist/api-D2hunekQ.mjs.map +0 -1
  818. package/dist/apply-kwNEJW2l.mjs.map +0 -1
  819. package/dist/config-DyF3OKg_.mjs.map +0 -1
  820. package/dist/content-CJ94NEx5.mjs.map +0 -1
  821. package/dist/content-refresh-Xw7aKmou.mjs +0 -1629
  822. package/dist/content-refresh-Xw7aKmou.mjs.map +0 -1
  823. package/dist/context-C_1nrqnW.mjs.map +0 -1
  824. package/dist/dashboard-DINnNVVU.mjs.map +0 -1
  825. package/dist/errors-BucQ8VYz.mjs.map +0 -1
  826. package/dist/fts-manager-BITakW_R.mjs.map +0 -1
  827. package/dist/index-M37iz29I.d.mts.map +0 -1
  828. package/dist/index-MGw47uWq.d.mts.map +0 -1
  829. package/dist/loader-CGugY7Zl.mjs.map +0 -1
  830. package/dist/media-OhYL-7Io.mjs.map +0 -1
  831. package/dist/media-allowlist-D-peH1H5.mjs.map +0 -1
  832. package/dist/media-usage-BlxfTkN0.mjs.map +0 -1
  833. package/dist/menus-Y54RDPIN.mjs.map +0 -1
  834. package/dist/options-CA4eVX6P.d.mts.map +0 -1
  835. package/dist/query-C5w7PsRQ.mjs.map +0 -1
  836. package/dist/redirects-G8wAhWAf.mjs.map +0 -1
  837. package/dist/registry-Dbodb4Uo.mjs +0 -840
  838. package/dist/registry-Dbodb4Uo.mjs.map +0 -1
  839. package/dist/relations-G2ta7GIl.d.mts.map +0 -1
  840. package/dist/runner-BFTrkYhu.mjs.map +0 -1
  841. package/dist/search-FZhx5dDU.mjs.map +0 -1
  842. package/dist/seo-CobNfOvb.mjs.map +0 -1
  843. package/dist/slugify-C_tqlU4G.mjs +0 -30
  844. package/dist/slugify-C_tqlU4G.mjs.map +0 -1
  845. package/dist/taxonomies-CKuaZER5.mjs.map +0 -1
  846. package/dist/taxonomies-CLN1N92E.mjs.map +0 -1
  847. package/dist/taxonomy-pOrB2kZe.mjs.map +0 -1
  848. package/dist/types-BGWPMiBO.d.mts.map +0 -1
  849. package/dist/types-BnFP1M3J.d.mts.map +0 -1
  850. package/dist/types-Cd8iuS1J.mjs.map +0 -1
  851. package/dist/types-Cz8hNch2.d.mts.map +0 -1
  852. package/dist/types-Dc4ntQjQ.d.mts.map +0 -1
  853. package/dist/types-NBI3rF-_.mjs.map +0 -1
  854. package/dist/validate-C3oSmA7F.d.mts.map +0 -1
  855. package/dist/validate-mKo3RxBM.mjs.map +0 -1
  856. package/dist/version-C4PnpmoV.mjs +0 -7
  857. package/dist/zod-generator-CG64Hnbh.mjs.map +0 -1
  858. /package/dist/{api-tokens-Dai9dND0.mjs → api-tokens-CyTsLszZ.mjs} +0 -0
  859. /package/dist/{ssrf-Cj568Dgs.mjs → ssrf-DpXgKsna.mjs} +0 -0
  860. /package/dist/{types-DKuWPYV5.mjs → types-CP1CflWf.mjs} +0 -0
@@ -1,6 +1,6 @@
1
- import { t as TaxonomyRepository } from "./taxonomy-pOrB2kZe.mjs";
2
- import { i as resolveLocaleChain } from "./resolve-DZzG4HqM.mjs";
3
- import { u as invalidateTermCache } from "./taxonomies-CLN1N92E.mjs";
1
+ import { t as TaxonomyRepository } from "./taxonomy-CPeM8d2o.mjs";
2
+ import { i as resolveLocaleChain } from "./resolve-DOhVEvty.mjs";
3
+ import { u as invalidateTermCache } from "./taxonomies-B0o9ExNV.mjs";
4
4
 
5
5
  //#region src/import/wxr-taxonomies.ts
6
6
  /**
@@ -305,19 +305,18 @@ function resolvePostTermAssignments(collection, post, plan) {
305
305
  async function attachPostTaxonomies(db, collection, entryId, post, plan) {
306
306
  const repo = new TaxonomyRepository(db);
307
307
  const resolved = resolvePostTermAssignments(collection, post, plan);
308
- let attached = 0;
309
- for (const [, termIds] of resolved) for (const termId of termIds) if (await attachToEntryCountingInserts(db, repo, plan, collection, entryId, termId)) attached++;
310
- return attached;
308
+ const groups = /* @__PURE__ */ new Set();
309
+ for (const [, termIds] of resolved) for (const termId of termIds) {
310
+ const group = await termTranslationGroup(repo, plan, termId);
311
+ if (group) groups.add(group);
312
+ }
313
+ return repo.attachGroupsToEntry(collection, entryId, [...groups]);
311
314
  }
312
315
  /**
313
316
  * Replace assignments per-taxonomy from a parsed WXR post. Used for
314
- * translations: WPML's "Translate Independently" mode lets translators
315
- * override term assignments per-taxonomy, not per-post. A translation that
316
- * overrides `category` shouldn't lose its inherited `tag` or `genre`. We
317
- * only call `setTermsForEntry(name, ids)` for taxonomies where the
318
- * translation actually resolved at least one term -- taxonomies with no
319
- * resolvable+permitted terms are left alone so inherited rows from
320
- * `copyEntryTerms` stay intact.
317
+ * translations. Assignments belong to the content translation_group, so a
318
+ * translated item's explicit taxonomy replaces that taxonomy for every
319
+ * locale. Taxonomies with no resolvable permitted terms are left unchanged.
321
320
  *
322
321
  * Returns the number of pivot rows after replacement (sum of `termIds`
323
322
  * lists across taxonomies actually touched). Note this counts logical
@@ -348,24 +347,6 @@ async function termTranslationGroup(repo, plan, termId) {
348
347
  return group;
349
348
  }
350
349
  /**
351
- * Wrapper around `TaxonomyRepository.attachToEntry` that returns whether
352
- * an actual row was inserted (vs. silently skipped by the `ON CONFLICT DO
353
- * NOTHING` branch). Lets the importer's `assignments` counter reflect real
354
- * writes rather than re-import no-ops.
355
- *
356
- * Best-effort: we check pivot existence first, then call `attachToEntry`.
357
- * A concurrent insert between the check and the attach would make us
358
- * report `false` while a row was in fact inserted -- the count is for
359
- * summary display only, never correctness.
360
- */
361
- async function attachToEntryCountingInserts(db, repo, plan, collection, entryId, termId) {
362
- const group = await termTranslationGroup(repo, plan, termId);
363
- if (!group) return false;
364
- if (await db.selectFrom("content_taxonomies").select("collection").where("collection", "=", collection).where("entry_id", "=", entryId).where("taxonomy_id", "=", group).executeTakeFirst()) return false;
365
- await repo.attachToEntry(collection, entryId, termId);
366
- return true;
367
- }
368
- /**
369
350
  * Mirror every term in the plan into each additional locale used by the
370
351
  * incoming posts. New rows share the canonical term's `translation_group`
371
352
  * so per-locale lookups (`getTermsForEntry(..., locale)`) resolve correctly
@@ -422,4 +403,4 @@ async function mirrorTermsToLocales(db, plan, postLocales, canonicalLocale) {
422
403
 
423
404
  //#endregion
424
405
  export { preImportWxrTaxonomies as a, mirrorTermsToLocales as i, isWxrTaxonomyConflictError as n, setPostTermAssignmentsReplacing as o, loadTaxonomyPlanFromDb as r, attachPostTaxonomies as t };
425
- //# sourceMappingURL=wxr-taxonomies-9PPSy8X6.mjs.map
406
+ //# sourceMappingURL=wxr-taxonomies-QZjWxjyk.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"wxr-taxonomies-9PPSy8X6.mjs","names":[],"sources":["../src/import/wxr-taxonomies.ts"],"sourcesContent":["/**\n * WXR taxonomy import helpers.\n *\n * Bridges parsed WordPress taxonomy data (`WxrCategory`, `WxrTag`, `WxrTerm`,\n * and per-item `WxrPost.categories` / `WxrPost.tags` / `WxrPost.customTaxonomies`)\n * onto EmDash's term + content_taxonomies tables.\n *\n * Why this isn't inline in `execute.ts`: pre-creating all terms before any\n * post is created lets us (a) build a lookup once for every (taxonomy, slug)\n * the import needs, and (b) keep the per-post attachment loop cheap. It also\n * makes the logic testable without spinning up an Astro request.\n *\n * Behaviour:\n * - `wp:category` -> EmDash `category` taxonomy (seeded by migration 006).\n * - `wp:tag` -> EmDash `tag` taxonomy.\n * - `wp:term` -> matching EmDash taxonomy by `name` (case-sensitive).\n * If no matching def exists in the target locale, the\n * term is skipped — we don't auto-create defs because\n * the user controls their schema through the admin.\n * - Terms are created idempotently by `(taxonomy, slug, locale)`. Existing\n * terms are reused.\n * - Assignments respect the def's `collections` array. If the post's target\n * collection isn't listed on the taxonomy def, the assignment is skipped\n * (matches admin UI behaviour: you can't tag a \"products\" post with a\n * \"category\" if `category.collections` only includes \"posts\").\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport type { WxrCategory, WxrPost, WxrTag, WxrTerm } from \"../cli/wxr/parser.js\";\nimport { TaxonomyRepository } from \"../database/repositories/taxonomy.js\";\nimport type { Database } from \"../database/types.js\";\nimport { resolveLocaleChain } from \"../i18n/resolve.js\";\nimport { invalidateTermCache } from \"../taxonomies/index.js\";\n\n/**\n * Thrown by `mirrorTermsToLocales` when a pre-existing locale row at the\n * same `(taxonomy, slug)` belongs to a different `translation_group` than\n * the canonical term. Callers in the route layer surface\n * `publicMessage` to the operator (no internal data) while logging\n * `cause` server-side.\n *\n * Marker class so the route layer can distinguish \"operator-actionable\n * taxonomy conflict\" from any other DB / repository error that might\n * escape the helper.\n */\nexport class WxrTaxonomyConflictError extends Error {\n\treadonly publicMessage: string;\n\tconstructor(publicMessage: string, options?: { cause?: unknown }) {\n\t\tsuper(publicMessage, options);\n\t\tthis.name = \"WxrTaxonomyConflictError\";\n\t\tthis.publicMessage = publicMessage;\n\t}\n}\n\nexport function isWxrTaxonomyConflictError(error: unknown): error is WxrTaxonomyConflictError {\n\treturn error instanceof WxrTaxonomyConflictError;\n}\n\n/**\n * Result of pre-importing taxonomy terms from a WXR file.\n */\nexport interface TaxonomyImportPlan {\n\t/** terms created during this run (per taxonomy name) */\n\ttermsCreated: Record<string, number>;\n\t/** terms that already existed and were reused (per taxonomy name) */\n\ttermsReused: Record<string, number>;\n\t/** custom taxonomies (`wp:term`) skipped because no matching EmDash def exists */\n\tmissingTaxonomies: string[];\n\t/**\n\t * Lookup table: `taxonomy name` -> `term slug` -> term id.\n\t * Used by `attachPostTaxonomies` to translate WXR assignments into pivot rows.\n\t */\n\ttermIdByNameAndSlug: Map<string, Map<string, string>>;\n\t/**\n\t * Lookup table: `taxonomy name` -> set of collection slugs the def allows.\n\t * Empty (or missing) means \"any collection\" — we only enforce the filter\n\t * when the def explicitly lists collections.\n\t */\n\tcollectionsByTaxonomy: Map<string, Set<string>>;\n\t/**\n\t * Lookup table: `term id` -> the term's `translation_group` (or `null`\n\t * if the term doesn't exist any more). Populated lazily by helpers that\n\t * need to check pivot existence without repeating per-term DB reads.\n\t */\n\ttranslationGroupByTermId: Map<string, string | null>;\n}\n\n/**\n * Track running counts plus the lookup maps.\n */\ninterface TaxonomyImportState {\n\tplan: TaxonomyImportPlan;\n}\n\nfunction makeState(): TaxonomyImportState {\n\treturn {\n\t\tplan: {\n\t\t\ttermsCreated: {},\n\t\t\ttermsReused: {},\n\t\t\tmissingTaxonomies: [],\n\t\t\ttermIdByNameAndSlug: new Map(),\n\t\t\tcollectionsByTaxonomy: new Map(),\n\t\t\ttranslationGroupByTermId: new Map(),\n\t\t},\n\t};\n}\n\n/**\n * Record-keeping helpers — keep mutations centralised so the result object\n * stays consistent.\n */\nfunction bump(record: Record<string, number>, key: string): void {\n\trecord[key] = (record[key] ?? 0) + 1;\n}\n\nfunction rememberTerm(\n\tstate: TaxonomyImportState,\n\ttaxonomyName: string,\n\tslug: string,\n\ttermId: string,\n): void {\n\tlet bySlug = state.plan.termIdByNameAndSlug.get(taxonomyName);\n\tif (!bySlug) {\n\t\tbySlug = new Map();\n\t\tstate.plan.termIdByNameAndSlug.set(taxonomyName, bySlug);\n\t}\n\tbySlug.set(slug, termId);\n}\n\n/**\n * Look up an EmDash taxonomy def by name. Definitions are per-locale but\n * a def is conceptually site-wide -- the per-locale row carries only the\n * label translations.\n *\n * Match the runtime helper `getTaxonomyDef` (in `src/taxonomies/index.ts`):\n * walk `resolveLocaleChain(locale)` so the importer picks the same def the\n * runtime would later resolve to. When the chain is empty (i18n disabled)\n * or every locale in the chain misses, fall through to the lowest-locale\n * row so single-locale installs still see seeded defs that were inserted\n * at some non-empty locale value.\n *\n * Without this fallback, a user importing into a non-default locale would\n * see every category dropped as `missingTaxonomies` even though the seeded\n * defs exist (just at the site's default locale).\n */\nfunction parseDefCollections(raw: string | null): string[] {\n\tif (!raw) return [];\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(raw);\n\t\tif (Array.isArray(parsed)) {\n\t\t\treturn parsed.filter((c): c is string => typeof c === \"string\");\n\t\t}\n\t} catch {\n\t\t// malformed JSON in the def -- treat as \"no collection filter\"\n\t}\n\treturn [];\n}\n\nasync function findTaxonomyDef(\n\tdb: Kysely<Database>,\n\tname: string,\n\tlocale: string | undefined,\n): Promise<{ id: string; collections: string[] } | null> {\n\tconst chain = resolveLocaleChain(locale);\n\n\tif (chain.length === 0) {\n\t\t// i18n disabled and no explicit locale. The runtime treats this\n\t\t// as \"no locale filter\" and picks the lowest-locale row. We do the\n\t\t// same so the importer agrees with how the runtime later reads\n\t\t// the def.\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t.selectAll()\n\t\t\t.where(\"name\", \"=\", name)\n\t\t\t.orderBy(\"locale\", \"asc\")\n\t\t\t.executeTakeFirst();\n\t\treturn row ? { id: row.id, collections: parseDefCollections(row.collections) } : null;\n\t}\n\n\t// Non-empty chain: walk it in order, return null if every entry misses.\n\t// This matches `getTaxonomyDef` exactly. We deliberately do NOT fall\n\t// through to any-locale lookup: doing so would let the importer pick a\n\t// def at a locale the runtime would never resolve to, producing\n\t// content the user can't see in the admin or on the rendered site.\n\tfor (const tryLocale of chain) {\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t.selectAll()\n\t\t\t.where(\"name\", \"=\", name)\n\t\t\t.where(\"locale\", \"=\", tryLocale)\n\t\t\t.executeTakeFirst();\n\t\tif (row) {\n\t\t\treturn { id: row.id, collections: parseDefCollections(row.collections) };\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Rebuild the taxonomy lookup maps from the database without creating\n * anything. The chunked WP import runs term creation once (first content\n * chunk); every later chunk only needs the maps to attach per-post\n * assignments, and two SELECTs are far cheaper than re-walking every term\n * through the ensure path.\n *\n * ponytail: first row wins per (name, slug), ordered by locale asc — the\n * same row `findBySlug` without a locale resolves to. Per-locale term\n * variants beyond that are the mirror pass's job, not the importer's.\n */\nexport async function loadTaxonomyPlanFromDb(db: Kysely<Database>): Promise<TaxonomyImportPlan> {\n\tconst state = makeState();\n\n\tconst defs = await db\n\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t.select([\"name\", \"collections\"])\n\t\t.orderBy(\"locale\", \"asc\")\n\t\t.execute();\n\tfor (const def of defs) {\n\t\tif (!state.plan.collectionsByTaxonomy.has(def.name)) {\n\t\t\tstate.plan.collectionsByTaxonomy.set(def.name, new Set(parseDefCollections(def.collections)));\n\t\t}\n\t}\n\n\tconst terms = await db\n\t\t.selectFrom(\"taxonomies\")\n\t\t.select([\"name\", \"slug\", \"id\"])\n\t\t.orderBy(\"locale\", \"asc\")\n\t\t.execute();\n\tfor (const term of terms) {\n\t\tif (!state.plan.termIdByNameAndSlug.get(term.name)?.has(term.slug)) {\n\t\t\trememberTerm(state, term.name, term.slug, term.id);\n\t\t}\n\t}\n\n\treturn state.plan;\n}\n\n/**\n * Find or create a term in the given taxonomy. Returns the term id. Callers\n * must verify the taxonomy def exists before calling — this helper assumes\n * the def is present.\n *\n * Note: we don't resolve WordPress parent slugs into EmDash parent ids in\n * this pass. WXR exports list categories in arbitrary order, so a category's\n * parent may not exist yet when we first see it. Hierarchy is preserved at\n * the data level (the parent slug is on `WxrCategory.parent`) but flattens\n * in EmDash for now; restoring the tree is a follow-up improvement.\n */\nasync function ensureTerm(\n\trepo: TaxonomyRepository,\n\tstate: TaxonomyImportState,\n\ttaxonomyName: string,\n\tslug: string,\n\tlabel: string,\n\tdescription: string | undefined,\n\tlocale: string | undefined,\n): Promise<string> {\n\t// Already resolved in this run (e.g. seen in `wp:category` AND in a per-\n\t// item `<category>` element).\n\tconst cached = state.plan.termIdByNameAndSlug.get(taxonomyName)?.get(slug);\n\tif (cached) return cached;\n\n\tconst existing = await repo.findBySlug(taxonomyName, slug, locale);\n\tif (existing) {\n\t\tbump(state.plan.termsReused, taxonomyName);\n\t\trememberTerm(state, taxonomyName, slug, existing.id);\n\t\treturn existing.id;\n\t}\n\n\t// No row at the requested locale. Before creating, check whether a\n\t// `(name, slug)` row exists in some OTHER locale -- e.g. the admin\n\t// pre-created an Arabic translation, and now an `en` import wants the\n\t// canonical row. We need to mint the new row inside the existing row's\n\t// `translation_group` so per-locale lookups across the family work.\n\t// Without this, the mirror pass would later refuse to reconcile (it\n\t// sees pre-existing rows in a different group as a no-op) and pivots\n\t// would point at a group that has no row in the requested locale.\n\tconst anyLocale = await repo.findBySlug(taxonomyName, slug);\n\tconst translationOf = anyLocale?.id;\n\n\tconst created = await repo.create({\n\t\tname: taxonomyName,\n\t\tslug,\n\t\tlabel,\n\t\tdata: description ? { description } : undefined,\n\t\tlocale,\n\t\ttranslationOf,\n\t});\n\tbump(state.plan.termsCreated, taxonomyName);\n\trememberTerm(state, taxonomyName, slug, created.id);\n\treturn created.id;\n}\n\n/**\n * Retrieve the human label captured by the parser for a per-item\n * `<category>` text body, falling back to the slug when the parser didn't\n * see a label (e.g. self-closing tags or whitespace-only bodies).\n */\nfunction labelFor(post: WxrPost, taxonomy: string, slug: string): string {\n\tconst key = `${taxonomy}\\u0000${slug}`;\n\treturn post.taxonomyLabels?.get(key) ?? slug;\n}\n\n/**\n * Pre-import every term referenced by the WXR file.\n *\n * Pass 1: `wp:category` blocks. Each becomes a term in EmDash's seeded\n * `category` taxonomy.\n * Pass 2: `wp:tag` blocks. Each becomes a term in `tag`.\n * Pass 3: `wp:term` blocks (custom taxonomies). Skipped when no matching\n * EmDash def exists.\n * Pass 4: per-item `<category domain=\"…\" nicename=\"…\">` assignments. WXR\n * exports sometimes reference taxonomies/terms that weren't declared\n * at the top level (older exports especially), so we backfill terms\n * from per-item assignments. Categories and tags use the seeded defs\n * and pick up the assignment text as the label; custom domains fall\n * back to the same \"def must exist\" rule.\n */\nexport async function preImportWxrTaxonomies(\n\tdb: Kysely<Database>,\n\tposts: WxrPost[],\n\tcategories: WxrCategory[],\n\ttags: WxrTag[],\n\tterms: WxrTerm[],\n\tlocale: string | undefined,\n): Promise<TaxonomyImportPlan> {\n\tconst state = makeState();\n\tconst repo = new TaxonomyRepository(db);\n\n\t// Cache def lookups for the duration of the import. Keyed by name; value\n\t// is `null` when we've already determined the def is missing in this\n\t// locale (so we only report the \"missing\" warning once per taxonomy).\n\tconst defCache = new Map<string, { id: string; collections: string[] } | null>();\n\tconst lookupDef = async (name: string): Promise<{ id: string; collections: string[] } | null> => {\n\t\tif (defCache.has(name)) return defCache.get(name) ?? null;\n\t\tconst def = await findTaxonomyDef(db, name, locale);\n\t\tdefCache.set(name, def);\n\t\tif (def) {\n\t\t\tstate.plan.collectionsByTaxonomy.set(name, new Set(def.collections));\n\t\t}\n\t\treturn def;\n\t};\n\n\t// Pass 1: top-level <wp:category> blocks -> EmDash `category` taxonomy.\n\tconst categoryDef = await lookupDef(\"category\");\n\tif (categoryDef) {\n\t\tfor (const cat of categories) {\n\t\t\tconst slug = cat.nicename;\n\t\t\tconst label = cat.name;\n\t\t\tif (!slug || !label) continue;\n\t\t\tawait ensureTerm(repo, state, \"category\", slug, label, cat.description, locale);\n\t\t}\n\t} else if (categories.length > 0) {\n\t\t// Seeded `category` def was deleted by the user — record so the\n\t\t// import response can surface why none of the categories landed.\n\t\tstate.plan.missingTaxonomies.push(\"category\");\n\t}\n\n\t// Pass 2: top-level <wp:tag> blocks -> EmDash `tag` taxonomy.\n\tconst tagDef = await lookupDef(\"tag\");\n\tif (tagDef) {\n\t\tfor (const tag of tags) {\n\t\t\tconst slug = tag.slug;\n\t\t\tconst label = tag.name;\n\t\t\tif (!slug || !label) continue;\n\t\t\tawait ensureTerm(repo, state, \"tag\", slug, label, tag.description, locale);\n\t\t}\n\t} else if (tags.length > 0) {\n\t\tstate.plan.missingTaxonomies.push(\"tag\");\n\t}\n\n\t// Pass 3: <wp:term> blocks for custom taxonomies (genre, etc.). Skipped:\n\t// - `nav_menu`: menus are handled by `importMenusFromWxr`.\n\t// - `language`: Polylang's locale signal; promoted to `WxrPost.locale`\n\t// by the parser and not a content taxonomy in EmDash.\n\tfor (const term of terms) {\n\t\tif (term.taxonomy === \"nav_menu\" || term.taxonomy === \"language\") continue;\n\t\t// Normalize WordPress' `post_tag` synonym -> EmDash `tag`. WordPress\n\t\t// emits `<wp:tag>` for some exports and `<wp:term wp:term_taxonomy=\"post_tag\">`\n\t\t// for others; both must land in the same EmDash taxonomy.\n\t\tconst taxonomyName = term.taxonomy === \"post_tag\" ? \"tag\" : term.taxonomy;\n\t\tconst def = await lookupDef(taxonomyName);\n\t\tif (!def) {\n\t\t\tif (!state.plan.missingTaxonomies.includes(taxonomyName)) {\n\t\t\t\tstate.plan.missingTaxonomies.push(taxonomyName);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tawait ensureTerm(repo, state, taxonomyName, term.slug, term.name, term.description, locale);\n\t}\n\n\t// Pass 4: per-item assignments. Backfills terms missing from the top-\n\t// level blocks (rare, but observed in hand-edited or partial exports).\n\t// Labels come from the per-item `<category>` text body when the parser\n\t// captured one; otherwise we fall back to the slug. This is the path\n\t// for older exports that skip top-level `<wp:category>` definitions.\n\tlet recordedMissingCategoryFromPosts = false;\n\tlet recordedMissingTagFromPosts = false;\n\tfor (const post of posts) {\n\t\tfor (const slug of post.categories) {\n\t\t\tif (!categoryDef) {\n\t\t\t\tif (\n\t\t\t\t\t!recordedMissingCategoryFromPosts &&\n\t\t\t\t\t!state.plan.missingTaxonomies.includes(\"category\")\n\t\t\t\t) {\n\t\t\t\t\tstate.plan.missingTaxonomies.push(\"category\");\n\t\t\t\t\trecordedMissingCategoryFromPosts = true;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (state.plan.termIdByNameAndSlug.get(\"category\")?.has(slug)) continue;\n\t\t\tawait ensureTerm(\n\t\t\t\trepo,\n\t\t\t\tstate,\n\t\t\t\t\"category\",\n\t\t\t\tslug,\n\t\t\t\tlabelFor(post, \"category\", slug),\n\t\t\t\tundefined,\n\t\t\t\tlocale,\n\t\t\t);\n\t\t}\n\t\tfor (const slug of post.tags) {\n\t\t\tif (!tagDef) {\n\t\t\t\tif (!recordedMissingTagFromPosts && !state.plan.missingTaxonomies.includes(\"tag\")) {\n\t\t\t\t\tstate.plan.missingTaxonomies.push(\"tag\");\n\t\t\t\t\trecordedMissingTagFromPosts = true;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (state.plan.termIdByNameAndSlug.get(\"tag\")?.has(slug)) continue;\n\t\t\tawait ensureTerm(repo, state, \"tag\", slug, labelFor(post, \"tag\", slug), undefined, locale);\n\t\t}\n\t\tif (post.customTaxonomies) {\n\t\t\tfor (const [rawName, slugs] of post.customTaxonomies) {\n\t\t\t\t// `nav_menu` is handled by the menu importer; `language` is\n\t\t\t\t// Polylang's per-post locale signal, already promoted by the\n\t\t\t\t// parser.\n\t\t\t\tif (rawName === \"nav_menu\" || rawName === \"language\") continue;\n\t\t\t\tconst taxonomyName = rawName === \"post_tag\" ? \"tag\" : rawName;\n\t\t\t\tconst def = await lookupDef(taxonomyName);\n\t\t\t\tif (!def) {\n\t\t\t\t\tif (!state.plan.missingTaxonomies.includes(taxonomyName)) {\n\t\t\t\t\t\tstate.plan.missingTaxonomies.push(taxonomyName);\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tfor (const slug of slugs) {\n\t\t\t\t\tif (state.plan.termIdByNameAndSlug.get(taxonomyName)?.has(slug)) continue;\n\t\t\t\t\tawait ensureTerm(\n\t\t\t\t\t\trepo,\n\t\t\t\t\t\tstate,\n\t\t\t\t\t\ttaxonomyName,\n\t\t\t\t\t\tslug,\n\t\t\t\t\t\tlabelFor(post, taxonomyName, slug),\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tlocale,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// `content_taxonomies` writes happen later in `attachPostTaxonomies`, but\n\t// term inserts above already invalidate the in-memory \"has any terms\" probe.\n\t// We flush once at the end of the pre-import to keep the runtime cache hot.\n\tinvalidateTermCache();\n\n\treturn state.plan;\n}\n\n/**\n * Walk a parsed WXR post's per-item taxonomy assignments and return only\n * the ones that resolve to a real EmDash term AND aren't filtered out by\n * the taxonomy def's `collections` allowlist. Grouped by EmDash taxonomy\n * name (so `post_tag` is already folded into `tag`). Deduplicated.\n *\n * This is the single source of truth for \"what will the importer try to\n * write for this post\". Both the anchor (additive `attachToEntry`) and\n * translation (per-taxonomy `setTermsForEntry`) paths drive from this map\n * so they agree on which taxonomies need touching. In particular, the\n * translation path uses the keys here -- not `postAssignedTaxonomies` --\n * to decide which inherited pivot rows to clear, so a translation whose\n * only assignment is filtered out by `collections` doesn't lose its\n * inherited terms (see #1087 review feedback).\n *\n * Skipped taxonomies: `nav_menu` (handled by the menu importer) and\n * `language` (Polylang's locale signal, already promoted to `post.locale`\n * by the parser).\n */\nexport function resolvePostTermAssignments(\n\tcollection: string,\n\tpost: WxrPost,\n\tplan: TaxonomyImportPlan,\n): Map<string, string[]> {\n\tconst result = new Map<string, string[]>();\n\tconst seen = new Set<string>();\n\n\tconst tryResolve = (taxonomyName: string, slug: string): void => {\n\t\tconst termId = plan.termIdByNameAndSlug.get(taxonomyName)?.get(slug);\n\t\tif (!termId) return;\n\t\tconst collectionFilter = plan.collectionsByTaxonomy.get(taxonomyName);\n\t\t// Empty set means \"no filter\" (def has no collections array). A\n\t\t// non-empty set is enforced: skip assignments to collections the\n\t\t// def doesn't list. Matches admin UI: a `category` term linked\n\t\t// only to `posts` shouldn't end up on a `products` row just\n\t\t// because the WXR happened to mention it.\n\t\tif (collectionFilter && collectionFilter.size > 0 && !collectionFilter.has(collection)) {\n\t\t\treturn;\n\t\t}\n\t\tconst dedupeKey = `${taxonomyName}\\u0000${termId}`;\n\t\tif (seen.has(dedupeKey)) return;\n\t\tseen.add(dedupeKey);\n\t\tconst existing = result.get(taxonomyName);\n\t\tif (existing) existing.push(termId);\n\t\telse result.set(taxonomyName, [termId]);\n\t};\n\n\tfor (const slug of post.categories) tryResolve(\"category\", slug);\n\tfor (const slug of post.tags) tryResolve(\"tag\", slug);\n\tif (post.customTaxonomies) {\n\t\tfor (const [rawName, slugs] of post.customTaxonomies) {\n\t\t\tif (rawName === \"nav_menu\" || rawName === \"language\") continue;\n\t\t\tconst taxonomyName = rawName === \"post_tag\" ? \"tag\" : rawName;\n\t\t\tfor (const slug of slugs) tryResolve(taxonomyName, slug);\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Attach the taxonomy assignments parsed for a single WXR post to a freshly-\n * created EmDash content row. Additive (`attachToEntry` + `ON CONFLICT DO\n * NOTHING`). Used for anchors -- translations need replace-semantics per\n * taxonomy and should use `setPostTermAssignmentsReplacing` instead.\n *\n * Returns the number of pivot rows actually inserted (excludes rows that\n * already existed via the `ON CONFLICT DO NOTHING` path), so the caller can\n * roll them up into the import summary without over-counting on re-imports.\n */\nexport async function attachPostTaxonomies(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tentryId: string,\n\tpost: WxrPost,\n\tplan: TaxonomyImportPlan,\n): Promise<number> {\n\tconst repo = new TaxonomyRepository(db);\n\tconst resolved = resolvePostTermAssignments(collection, post, plan);\n\n\tlet attached = 0;\n\tfor (const [, termIds] of resolved) {\n\t\tfor (const termId of termIds) {\n\t\t\tconst wrote = await attachToEntryCountingInserts(db, repo, plan, collection, entryId, termId);\n\t\t\tif (wrote) attached++;\n\t\t}\n\t}\n\treturn attached;\n}\n\n/**\n * Replace assignments per-taxonomy from a parsed WXR post. Used for\n * translations: WPML's \"Translate Independently\" mode lets translators\n * override term assignments per-taxonomy, not per-post. A translation that\n * overrides `category` shouldn't lose its inherited `tag` or `genre`. We\n * only call `setTermsForEntry(name, ids)` for taxonomies where the\n * translation actually resolved at least one term -- taxonomies with no\n * resolvable+permitted terms are left alone so inherited rows from\n * `copyEntryTerms` stay intact.\n *\n * Returns the number of pivot rows after replacement (sum of `termIds`\n * lists across taxonomies actually touched). Note this counts logical\n * assignments, not the delta from the prior state; the import summary\n * treats this as an additive count for compatibility with `attachPost-\n * Taxonomies`.\n */\nexport async function setPostTermAssignmentsReplacing(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tentryId: string,\n\tpost: WxrPost,\n\tplan: TaxonomyImportPlan,\n): Promise<number> {\n\tconst repo = new TaxonomyRepository(db);\n\tconst resolved = resolvePostTermAssignments(collection, post, plan);\n\n\tlet attached = 0;\n\tfor (const [taxonomyName, termIds] of resolved) {\n\t\tawait repo.setTermsForEntry(collection, entryId, taxonomyName, termIds);\n\t\tattached += termIds.length;\n\t}\n\treturn attached;\n}\n\n/**\n * Resolve a term id to its `translation_group` (the value\n * `content_taxonomies` stores). Caches the result on the plan so\n * repeated attaches of the same term don't repeat the lookup.\n */\nasync function termTranslationGroup(\n\trepo: TaxonomyRepository,\n\tplan: TaxonomyImportPlan,\n\ttermId: string,\n): Promise<string | null> {\n\tconst cached = plan.translationGroupByTermId.get(termId);\n\tif (cached !== undefined) return cached;\n\tconst term = await repo.findById(termId);\n\tconst group = term?.translationGroup ?? null;\n\tplan.translationGroupByTermId.set(termId, group);\n\treturn group;\n}\n\n/**\n * Wrapper around `TaxonomyRepository.attachToEntry` that returns whether\n * an actual row was inserted (vs. silently skipped by the `ON CONFLICT DO\n * NOTHING` branch). Lets the importer's `assignments` counter reflect real\n * writes rather than re-import no-ops.\n *\n * Best-effort: we check pivot existence first, then call `attachToEntry`.\n * A concurrent insert between the check and the attach would make us\n * report `false` while a row was in fact inserted -- the count is for\n * summary display only, never correctness.\n */\nasync function attachToEntryCountingInserts(\n\tdb: Kysely<Database>,\n\trepo: TaxonomyRepository,\n\tplan: TaxonomyImportPlan,\n\tcollection: string,\n\tentryId: string,\n\ttermId: string,\n): Promise<boolean> {\n\tconst group = await termTranslationGroup(repo, plan, termId);\n\tif (!group) return false;\n\n\tconst existing = await db\n\t\t.selectFrom(\"content_taxonomies\")\n\t\t.select(\"collection\")\n\t\t.where(\"collection\", \"=\", collection)\n\t\t.where(\"entry_id\", \"=\", entryId)\n\t\t.where(\"taxonomy_id\", \"=\", group)\n\t\t.executeTakeFirst();\n\tif (existing) return false;\n\n\tawait repo.attachToEntry(collection, entryId, termId);\n\treturn true;\n}\n\n/**\n * Mirror every term in the plan into each additional locale used by the\n * incoming posts. New rows share the canonical term's `translation_group`\n * so per-locale lookups (`getTermsForEntry(..., locale)`) resolve correctly\n * for translations whose locale differs from the import-wide one.\n *\n * Without this pass, multilingual WXR imports (#1080) write all term rows\n * at the upload-wide locale; the `content_taxonomies` pivot is correct (it\n * stores `translation_group`, not `term id`), but\n * `getTermsForEntry(collection, arabicPostId, \"category\", \"ar\")` filters on\n * `taxonomies.locale = \"ar\"` and returns zero rows. Users see \"no tags\" on\n * every non-canonical translation.\n *\n * Idempotent: skips a locale when a row already exists at `(name, slug,\n * locale)`. Safe to call after `preImportWxrTaxonomies` on subsequent\n * imports.\n */\nexport async function mirrorTermsToLocales(\n\tdb: Kysely<Database>,\n\tplan: TaxonomyImportPlan,\n\tpostLocales: Iterable<string>,\n\tcanonicalLocale: string | undefined,\n): Promise<void> {\n\tconst localeSet = new Set<string>();\n\tfor (const locale of postLocales) {\n\t\tif (!locale || locale === canonicalLocale) continue;\n\t\tlocaleSet.add(locale);\n\t}\n\tif (localeSet.size === 0) return;\n\n\tconst repo = new TaxonomyRepository(db);\n\n\tfor (const [taxonomyName, bySlug] of plan.termIdByNameAndSlug) {\n\t\tfor (const [slug, canonicalTermId] of bySlug) {\n\t\t\t// Resolve the canonical's translation_group once; we'll compare\n\t\t\t// against any pre-existing rows we find at the target locales.\n\t\t\t// Cache on the plan so subsequent attaches (which also need\n\t\t\t// this resolution) don't repeat the lookup.\n\t\t\tconst cachedGroup = await termTranslationGroup(repo, plan, canonicalTermId);\n\t\t\tif (!cachedGroup) {\n\t\t\t\t// The canonical term id is in the plan but the row is no\n\t\t\t\t// longer in the DB. Shouldn't happen during a single\n\t\t\t\t// import run; skip rather than crash so the rest of the\n\t\t\t\t// import can complete.\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst canonicalGroup = cachedGroup;\n\n\t\t\tfor (const locale of localeSet) {\n\t\t\t\tconst existing = await repo.findBySlug(taxonomyName, slug, locale);\n\t\t\t\tif (existing) {\n\t\t\t\t\t// `ensureTerm` resolves cross-locale grouping when it\n\t\t\t\t\t// creates the canonical row, so a pre-existing sibling\n\t\t\t\t\t// row at this locale should already share the\n\t\t\t\t\t// canonical's `translation_group`. If it doesn't, the\n\t\t\t\t\t// import would write pivots pointing at a group that\n\t\t\t\t\t// has no row in this locale -- a silent data-integrity\n\t\t\t\t\t// bug. Fail closed: throw so the operator reconciles\n\t\t\t\t\t// the existing rows in the admin before retrying. This\n\t\t\t\t\t// happens when the canonical row was created in an\n\t\t\t\t\t// earlier import and a sibling-locale row was added\n\t\t\t\t\t// manually afterwards (or vice versa) without linking\n\t\t\t\t\t// them via translationOf.\n\t\t\t\t\tif (existing.translationGroup !== canonicalGroup) {\n\t\t\t\t\t\tthrow new WxrTaxonomyConflictError(\n\t\t\t\t\t\t\t`Cannot import: term \"${taxonomyName}/${slug}\" already exists at locale \"${locale}\" in a different translation group than the canonical row at this import's locale. Reconcile the rows in the admin (re-link via translationOf, or delete one) and retry.`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tawait repo.create({\n\t\t\t\t\t\tname: taxonomyName,\n\t\t\t\t\t\tslug,\n\t\t\t\t\t\tlabel: slug, // we don't have a per-locale label from the WXR\n\t\t\t\t\t\tlocale,\n\t\t\t\t\t\ttranslationOf: canonicalTermId,\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// `findBySlug` + `create` is not atomic. A concurrent\n\t\t\t\t\t// import racing us into the same `(name, slug, locale)`\n\t\t\t\t\t// will trip the UNIQUE constraint. Re-read the row that\n\t\t\t\t\t// won the race and verify its `translation_group`\n\t\t\t\t\t// matches the canonical's; if not, the pivot will\n\t\t\t\t\t// resolve to a group that has no row in this locale\n\t\t\t\t\t// (silent data-integrity bug) so we surface that loudly\n\t\t\t\t\t// rather than continue.\n\t\t\t\t\t//\n\t\t\t\t\t// Other errors (validation, connectivity) re-throw so\n\t\t\t\t\t// the import fails closed rather than silently shipping\n\t\t\t\t\t// translations that resolve to empty taxonomy queries.\n\t\t\t\t\tconst message = error instanceof Error ? error.message.toLowerCase() : \"\";\n\t\t\t\t\tconst isUniqueRace =\n\t\t\t\t\t\tmessage.includes(\"unique constraint failed\") || message.includes(\"duplicate key\");\n\t\t\t\t\tif (!isUniqueRace) throw error;\n\n\t\t\t\t\tconst winner = await repo.findBySlug(taxonomyName, slug, locale);\n\t\t\t\t\tif (!winner) {\n\t\t\t\t\t\t// UNIQUE conflict but no row visible? Shouldn't\n\t\t\t\t\t\t// happen unless the racing transaction rolled back;\n\t\t\t\t\t\t// fail loudly so the operator can investigate.\n\t\t\t\t\t\tthrow new WxrTaxonomyConflictError(\n\t\t\t\t\t\t\t`Cannot import: term \"${taxonomyName}/${slug}\" raced UNIQUE at locale \"${locale}\" but no row is visible afterwards. The concurrent transaction may have rolled back; retry the import.`,\n\t\t\t\t\t\t\t{ cause: error },\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (winner.translationGroup !== canonicalGroup) {\n\t\t\t\t\t\tthrow new WxrTaxonomyConflictError(\n\t\t\t\t\t\t\t`Cannot import: term \"${taxonomyName}/${slug}\" raced UNIQUE at locale \"${locale}\" with a different translation group. Reconcile the rows in the admin and retry.`,\n\t\t\t\t\t\t\t{ cause: error },\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`[WXR import] concurrent writer beat us to term \"${taxonomyName}/${slug}\" at locale \"${locale}\"; using existing row (same group).`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"wxr-taxonomies-QZjWxjyk.mjs","names":[],"sources":["../src/import/wxr-taxonomies.ts"],"sourcesContent":["/**\n * WXR taxonomy import helpers.\n *\n * Bridges parsed WordPress taxonomy data (`WxrCategory`, `WxrTag`, `WxrTerm`,\n * and per-item `WxrPost.categories` / `WxrPost.tags` / `WxrPost.customTaxonomies`)\n * onto EmDash's term + content_taxonomies tables.\n *\n * Why this isn't inline in `execute.ts`: pre-creating all terms before any\n * post is created lets us (a) build a lookup once for every (taxonomy, slug)\n * the import needs, and (b) keep the per-post attachment loop cheap. It also\n * makes the logic testable without spinning up an Astro request.\n *\n * Behaviour:\n * - `wp:category` -> EmDash `category` taxonomy (seeded by migration 006).\n * - `wp:tag` -> EmDash `tag` taxonomy.\n * - `wp:term` -> matching EmDash taxonomy by `name` (case-sensitive).\n * If no matching def exists in the target locale, the\n * term is skipped — we don't auto-create defs because\n * the user controls their schema through the admin.\n * - Terms are created idempotently by `(taxonomy, slug, locale)`. Existing\n * terms are reused.\n * - Assignments respect the def's `collections` array. If the post's target\n * collection isn't listed on the taxonomy def, the assignment is skipped\n * (matches admin UI behaviour: you can't tag a \"products\" post with a\n * \"category\" if `category.collections` only includes \"posts\").\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport type { WxrCategory, WxrPost, WxrTag, WxrTerm } from \"../cli/wxr/parser.js\";\nimport { TaxonomyRepository } from \"../database/repositories/taxonomy.js\";\nimport type { Database } from \"../database/types.js\";\nimport { resolveLocaleChain } from \"../i18n/resolve.js\";\nimport { invalidateTermCache } from \"../taxonomies/index.js\";\n\n/**\n * Thrown by `mirrorTermsToLocales` when a pre-existing locale row at the\n * same `(taxonomy, slug)` belongs to a different `translation_group` than\n * the canonical term. Callers in the route layer surface\n * `publicMessage` to the operator (no internal data) while logging\n * `cause` server-side.\n *\n * Marker class so the route layer can distinguish \"operator-actionable\n * taxonomy conflict\" from any other DB / repository error that might\n * escape the helper.\n */\nexport class WxrTaxonomyConflictError extends Error {\n\treadonly publicMessage: string;\n\tconstructor(publicMessage: string, options?: { cause?: unknown }) {\n\t\tsuper(publicMessage, options);\n\t\tthis.name = \"WxrTaxonomyConflictError\";\n\t\tthis.publicMessage = publicMessage;\n\t}\n}\n\nexport function isWxrTaxonomyConflictError(error: unknown): error is WxrTaxonomyConflictError {\n\treturn error instanceof WxrTaxonomyConflictError;\n}\n\n/**\n * Result of pre-importing taxonomy terms from a WXR file.\n */\nexport interface TaxonomyImportPlan {\n\t/** terms created during this run (per taxonomy name) */\n\ttermsCreated: Record<string, number>;\n\t/** terms that already existed and were reused (per taxonomy name) */\n\ttermsReused: Record<string, number>;\n\t/** custom taxonomies (`wp:term`) skipped because no matching EmDash def exists */\n\tmissingTaxonomies: string[];\n\t/**\n\t * Lookup table: `taxonomy name` -> `term slug` -> term id.\n\t * Used by `attachPostTaxonomies` to translate WXR assignments into pivot rows.\n\t */\n\ttermIdByNameAndSlug: Map<string, Map<string, string>>;\n\t/**\n\t * Lookup table: `taxonomy name` -> set of collection slugs the def allows.\n\t * Empty (or missing) means \"any collection\" — we only enforce the filter\n\t * when the def explicitly lists collections.\n\t */\n\tcollectionsByTaxonomy: Map<string, Set<string>>;\n\t/**\n\t * Lookup table: `term id` -> the term's `translation_group` (or `null`\n\t * if the term doesn't exist any more). Populated lazily by helpers that\n\t * need to check pivot existence without repeating per-term DB reads.\n\t */\n\ttranslationGroupByTermId: Map<string, string | null>;\n}\n\n/**\n * Track running counts plus the lookup maps.\n */\ninterface TaxonomyImportState {\n\tplan: TaxonomyImportPlan;\n}\n\nfunction makeState(): TaxonomyImportState {\n\treturn {\n\t\tplan: {\n\t\t\ttermsCreated: {},\n\t\t\ttermsReused: {},\n\t\t\tmissingTaxonomies: [],\n\t\t\ttermIdByNameAndSlug: new Map(),\n\t\t\tcollectionsByTaxonomy: new Map(),\n\t\t\ttranslationGroupByTermId: new Map(),\n\t\t},\n\t};\n}\n\n/**\n * Record-keeping helpers — keep mutations centralised so the result object\n * stays consistent.\n */\nfunction bump(record: Record<string, number>, key: string): void {\n\trecord[key] = (record[key] ?? 0) + 1;\n}\n\nfunction rememberTerm(\n\tstate: TaxonomyImportState,\n\ttaxonomyName: string,\n\tslug: string,\n\ttermId: string,\n): void {\n\tlet bySlug = state.plan.termIdByNameAndSlug.get(taxonomyName);\n\tif (!bySlug) {\n\t\tbySlug = new Map();\n\t\tstate.plan.termIdByNameAndSlug.set(taxonomyName, bySlug);\n\t}\n\tbySlug.set(slug, termId);\n}\n\n/**\n * Look up an EmDash taxonomy def by name. Definitions are per-locale but\n * a def is conceptually site-wide -- the per-locale row carries only the\n * label translations.\n *\n * Match the runtime helper `getTaxonomyDef` (in `src/taxonomies/index.ts`):\n * walk `resolveLocaleChain(locale)` so the importer picks the same def the\n * runtime would later resolve to. When the chain is empty (i18n disabled)\n * or every locale in the chain misses, fall through to the lowest-locale\n * row so single-locale installs still see seeded defs that were inserted\n * at some non-empty locale value.\n *\n * Without this fallback, a user importing into a non-default locale would\n * see every category dropped as `missingTaxonomies` even though the seeded\n * defs exist (just at the site's default locale).\n */\nfunction parseDefCollections(raw: string | null): string[] {\n\tif (!raw) return [];\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(raw);\n\t\tif (Array.isArray(parsed)) {\n\t\t\treturn parsed.filter((c): c is string => typeof c === \"string\");\n\t\t}\n\t} catch {\n\t\t// malformed JSON in the def -- treat as \"no collection filter\"\n\t}\n\treturn [];\n}\n\nasync function findTaxonomyDef(\n\tdb: Kysely<Database>,\n\tname: string,\n\tlocale: string | undefined,\n): Promise<{ id: string; collections: string[] } | null> {\n\tconst chain = resolveLocaleChain(locale);\n\n\tif (chain.length === 0) {\n\t\t// i18n disabled and no explicit locale. The runtime treats this\n\t\t// as \"no locale filter\" and picks the lowest-locale row. We do the\n\t\t// same so the importer agrees with how the runtime later reads\n\t\t// the def.\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t.selectAll()\n\t\t\t.where(\"name\", \"=\", name)\n\t\t\t.orderBy(\"locale\", \"asc\")\n\t\t\t.executeTakeFirst();\n\t\treturn row ? { id: row.id, collections: parseDefCollections(row.collections) } : null;\n\t}\n\n\t// Non-empty chain: walk it in order, return null if every entry misses.\n\t// This matches `getTaxonomyDef` exactly. We deliberately do NOT fall\n\t// through to any-locale lookup: doing so would let the importer pick a\n\t// def at a locale the runtime would never resolve to, producing\n\t// content the user can't see in the admin or on the rendered site.\n\tfor (const tryLocale of chain) {\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t.selectAll()\n\t\t\t.where(\"name\", \"=\", name)\n\t\t\t.where(\"locale\", \"=\", tryLocale)\n\t\t\t.executeTakeFirst();\n\t\tif (row) {\n\t\t\treturn { id: row.id, collections: parseDefCollections(row.collections) };\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Rebuild the taxonomy lookup maps from the database without creating\n * anything. The chunked WP import runs term creation once (first content\n * chunk); every later chunk only needs the maps to attach per-post\n * assignments, and two SELECTs are far cheaper than re-walking every term\n * through the ensure path.\n *\n * ponytail: first row wins per (name, slug), ordered by locale asc — the\n * same row `findBySlug` without a locale resolves to. Per-locale term\n * variants beyond that are the mirror pass's job, not the importer's.\n */\nexport async function loadTaxonomyPlanFromDb(db: Kysely<Database>): Promise<TaxonomyImportPlan> {\n\tconst state = makeState();\n\n\tconst defs = await db\n\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t.select([\"name\", \"collections\"])\n\t\t.orderBy(\"locale\", \"asc\")\n\t\t.execute();\n\tfor (const def of defs) {\n\t\tif (!state.plan.collectionsByTaxonomy.has(def.name)) {\n\t\t\tstate.plan.collectionsByTaxonomy.set(def.name, new Set(parseDefCollections(def.collections)));\n\t\t}\n\t}\n\n\tconst terms = await db\n\t\t.selectFrom(\"taxonomies\")\n\t\t.select([\"name\", \"slug\", \"id\"])\n\t\t.orderBy(\"locale\", \"asc\")\n\t\t.execute();\n\tfor (const term of terms) {\n\t\tif (!state.plan.termIdByNameAndSlug.get(term.name)?.has(term.slug)) {\n\t\t\trememberTerm(state, term.name, term.slug, term.id);\n\t\t}\n\t}\n\n\treturn state.plan;\n}\n\n/**\n * Find or create a term in the given taxonomy. Returns the term id. Callers\n * must verify the taxonomy def exists before calling — this helper assumes\n * the def is present.\n *\n * Note: we don't resolve WordPress parent slugs into EmDash parent ids in\n * this pass. WXR exports list categories in arbitrary order, so a category's\n * parent may not exist yet when we first see it. Hierarchy is preserved at\n * the data level (the parent slug is on `WxrCategory.parent`) but flattens\n * in EmDash for now; restoring the tree is a follow-up improvement.\n */\nasync function ensureTerm(\n\trepo: TaxonomyRepository,\n\tstate: TaxonomyImportState,\n\ttaxonomyName: string,\n\tslug: string,\n\tlabel: string,\n\tdescription: string | undefined,\n\tlocale: string | undefined,\n): Promise<string> {\n\t// Already resolved in this run (e.g. seen in `wp:category` AND in a per-\n\t// item `<category>` element).\n\tconst cached = state.plan.termIdByNameAndSlug.get(taxonomyName)?.get(slug);\n\tif (cached) return cached;\n\n\tconst existing = await repo.findBySlug(taxonomyName, slug, locale);\n\tif (existing) {\n\t\tbump(state.plan.termsReused, taxonomyName);\n\t\trememberTerm(state, taxonomyName, slug, existing.id);\n\t\treturn existing.id;\n\t}\n\n\t// No row at the requested locale. Before creating, check whether a\n\t// `(name, slug)` row exists in some OTHER locale -- e.g. the admin\n\t// pre-created an Arabic translation, and now an `en` import wants the\n\t// canonical row. We need to mint the new row inside the existing row's\n\t// `translation_group` so per-locale lookups across the family work.\n\t// Without this, the mirror pass would later refuse to reconcile (it\n\t// sees pre-existing rows in a different group as a no-op) and pivots\n\t// would point at a group that has no row in the requested locale.\n\tconst anyLocale = await repo.findBySlug(taxonomyName, slug);\n\tconst translationOf = anyLocale?.id;\n\n\tconst created = await repo.create({\n\t\tname: taxonomyName,\n\t\tslug,\n\t\tlabel,\n\t\tdata: description ? { description } : undefined,\n\t\tlocale,\n\t\ttranslationOf,\n\t});\n\tbump(state.plan.termsCreated, taxonomyName);\n\trememberTerm(state, taxonomyName, slug, created.id);\n\treturn created.id;\n}\n\n/**\n * Retrieve the human label captured by the parser for a per-item\n * `<category>` text body, falling back to the slug when the parser didn't\n * see a label (e.g. self-closing tags or whitespace-only bodies).\n */\nfunction labelFor(post: WxrPost, taxonomy: string, slug: string): string {\n\tconst key = `${taxonomy}\\u0000${slug}`;\n\treturn post.taxonomyLabels?.get(key) ?? slug;\n}\n\n/**\n * Pre-import every term referenced by the WXR file.\n *\n * Pass 1: `wp:category` blocks. Each becomes a term in EmDash's seeded\n * `category` taxonomy.\n * Pass 2: `wp:tag` blocks. Each becomes a term in `tag`.\n * Pass 3: `wp:term` blocks (custom taxonomies). Skipped when no matching\n * EmDash def exists.\n * Pass 4: per-item `<category domain=\"…\" nicename=\"…\">` assignments. WXR\n * exports sometimes reference taxonomies/terms that weren't declared\n * at the top level (older exports especially), so we backfill terms\n * from per-item assignments. Categories and tags use the seeded defs\n * and pick up the assignment text as the label; custom domains fall\n * back to the same \"def must exist\" rule.\n */\nexport async function preImportWxrTaxonomies(\n\tdb: Kysely<Database>,\n\tposts: WxrPost[],\n\tcategories: WxrCategory[],\n\ttags: WxrTag[],\n\tterms: WxrTerm[],\n\tlocale: string | undefined,\n): Promise<TaxonomyImportPlan> {\n\tconst state = makeState();\n\tconst repo = new TaxonomyRepository(db);\n\n\t// Cache def lookups for the duration of the import. Keyed by name; value\n\t// is `null` when we've already determined the def is missing in this\n\t// locale (so we only report the \"missing\" warning once per taxonomy).\n\tconst defCache = new Map<string, { id: string; collections: string[] } | null>();\n\tconst lookupDef = async (name: string): Promise<{ id: string; collections: string[] } | null> => {\n\t\tif (defCache.has(name)) return defCache.get(name) ?? null;\n\t\tconst def = await findTaxonomyDef(db, name, locale);\n\t\tdefCache.set(name, def);\n\t\tif (def) {\n\t\t\tstate.plan.collectionsByTaxonomy.set(name, new Set(def.collections));\n\t\t}\n\t\treturn def;\n\t};\n\n\t// Pass 1: top-level <wp:category> blocks -> EmDash `category` taxonomy.\n\tconst categoryDef = await lookupDef(\"category\");\n\tif (categoryDef) {\n\t\tfor (const cat of categories) {\n\t\t\tconst slug = cat.nicename;\n\t\t\tconst label = cat.name;\n\t\t\tif (!slug || !label) continue;\n\t\t\tawait ensureTerm(repo, state, \"category\", slug, label, cat.description, locale);\n\t\t}\n\t} else if (categories.length > 0) {\n\t\t// Seeded `category` def was deleted by the user — record so the\n\t\t// import response can surface why none of the categories landed.\n\t\tstate.plan.missingTaxonomies.push(\"category\");\n\t}\n\n\t// Pass 2: top-level <wp:tag> blocks -> EmDash `tag` taxonomy.\n\tconst tagDef = await lookupDef(\"tag\");\n\tif (tagDef) {\n\t\tfor (const tag of tags) {\n\t\t\tconst slug = tag.slug;\n\t\t\tconst label = tag.name;\n\t\t\tif (!slug || !label) continue;\n\t\t\tawait ensureTerm(repo, state, \"tag\", slug, label, tag.description, locale);\n\t\t}\n\t} else if (tags.length > 0) {\n\t\tstate.plan.missingTaxonomies.push(\"tag\");\n\t}\n\n\t// Pass 3: <wp:term> blocks for custom taxonomies (genre, etc.). Skipped:\n\t// - `nav_menu`: menus are handled by `importMenusFromWxr`.\n\t// - `language`: Polylang's locale signal; promoted to `WxrPost.locale`\n\t// by the parser and not a content taxonomy in EmDash.\n\tfor (const term of terms) {\n\t\tif (term.taxonomy === \"nav_menu\" || term.taxonomy === \"language\") continue;\n\t\t// Normalize WordPress' `post_tag` synonym -> EmDash `tag`. WordPress\n\t\t// emits `<wp:tag>` for some exports and `<wp:term wp:term_taxonomy=\"post_tag\">`\n\t\t// for others; both must land in the same EmDash taxonomy.\n\t\tconst taxonomyName = term.taxonomy === \"post_tag\" ? \"tag\" : term.taxonomy;\n\t\tconst def = await lookupDef(taxonomyName);\n\t\tif (!def) {\n\t\t\tif (!state.plan.missingTaxonomies.includes(taxonomyName)) {\n\t\t\t\tstate.plan.missingTaxonomies.push(taxonomyName);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tawait ensureTerm(repo, state, taxonomyName, term.slug, term.name, term.description, locale);\n\t}\n\n\t// Pass 4: per-item assignments. Backfills terms missing from the top-\n\t// level blocks (rare, but observed in hand-edited or partial exports).\n\t// Labels come from the per-item `<category>` text body when the parser\n\t// captured one; otherwise we fall back to the slug. This is the path\n\t// for older exports that skip top-level `<wp:category>` definitions.\n\tlet recordedMissingCategoryFromPosts = false;\n\tlet recordedMissingTagFromPosts = false;\n\tfor (const post of posts) {\n\t\tfor (const slug of post.categories) {\n\t\t\tif (!categoryDef) {\n\t\t\t\tif (\n\t\t\t\t\t!recordedMissingCategoryFromPosts &&\n\t\t\t\t\t!state.plan.missingTaxonomies.includes(\"category\")\n\t\t\t\t) {\n\t\t\t\t\tstate.plan.missingTaxonomies.push(\"category\");\n\t\t\t\t\trecordedMissingCategoryFromPosts = true;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (state.plan.termIdByNameAndSlug.get(\"category\")?.has(slug)) continue;\n\t\t\tawait ensureTerm(\n\t\t\t\trepo,\n\t\t\t\tstate,\n\t\t\t\t\"category\",\n\t\t\t\tslug,\n\t\t\t\tlabelFor(post, \"category\", slug),\n\t\t\t\tundefined,\n\t\t\t\tlocale,\n\t\t\t);\n\t\t}\n\t\tfor (const slug of post.tags) {\n\t\t\tif (!tagDef) {\n\t\t\t\tif (!recordedMissingTagFromPosts && !state.plan.missingTaxonomies.includes(\"tag\")) {\n\t\t\t\t\tstate.plan.missingTaxonomies.push(\"tag\");\n\t\t\t\t\trecordedMissingTagFromPosts = true;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (state.plan.termIdByNameAndSlug.get(\"tag\")?.has(slug)) continue;\n\t\t\tawait ensureTerm(repo, state, \"tag\", slug, labelFor(post, \"tag\", slug), undefined, locale);\n\t\t}\n\t\tif (post.customTaxonomies) {\n\t\t\tfor (const [rawName, slugs] of post.customTaxonomies) {\n\t\t\t\t// `nav_menu` is handled by the menu importer; `language` is\n\t\t\t\t// Polylang's per-post locale signal, already promoted by the\n\t\t\t\t// parser.\n\t\t\t\tif (rawName === \"nav_menu\" || rawName === \"language\") continue;\n\t\t\t\tconst taxonomyName = rawName === \"post_tag\" ? \"tag\" : rawName;\n\t\t\t\tconst def = await lookupDef(taxonomyName);\n\t\t\t\tif (!def) {\n\t\t\t\t\tif (!state.plan.missingTaxonomies.includes(taxonomyName)) {\n\t\t\t\t\t\tstate.plan.missingTaxonomies.push(taxonomyName);\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tfor (const slug of slugs) {\n\t\t\t\t\tif (state.plan.termIdByNameAndSlug.get(taxonomyName)?.has(slug)) continue;\n\t\t\t\t\tawait ensureTerm(\n\t\t\t\t\t\trepo,\n\t\t\t\t\t\tstate,\n\t\t\t\t\t\ttaxonomyName,\n\t\t\t\t\t\tslug,\n\t\t\t\t\t\tlabelFor(post, taxonomyName, slug),\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tlocale,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// `content_taxonomies` writes happen later in `attachPostTaxonomies`, but\n\t// term inserts above already invalidate the in-memory \"has any terms\" probe.\n\t// We flush once at the end of the pre-import to keep the runtime cache hot.\n\tinvalidateTermCache();\n\n\treturn state.plan;\n}\n\n/**\n * Walk a parsed WXR post's per-item taxonomy assignments and return only\n * the ones that resolve to a real EmDash term AND aren't filtered out by\n * the taxonomy def's `collections` allowlist. Grouped by EmDash taxonomy\n * name (so `post_tag` is already folded into `tag`). Deduplicated.\n *\n * This is the single source of truth for \"what will the importer try to\n * write for this post\". Both the anchor (additive `attachToEntry`) and\n * translation (per-taxonomy `setTermsForEntry`) paths drive from this map\n * so they agree on which taxonomies need touching. In particular, the\n * translation path uses the keys here -- not `postAssignedTaxonomies` --\n * to decide which inherited pivot rows to clear, so a translation whose\n * only assignment is filtered out by `collections` doesn't lose its\n * inherited terms (see #1087 review feedback).\n *\n * Skipped taxonomies: `nav_menu` (handled by the menu importer) and\n * `language` (Polylang's locale signal, already promoted to `post.locale`\n * by the parser).\n */\nexport function resolvePostTermAssignments(\n\tcollection: string,\n\tpost: WxrPost,\n\tplan: TaxonomyImportPlan,\n): Map<string, string[]> {\n\tconst result = new Map<string, string[]>();\n\tconst seen = new Set<string>();\n\n\tconst tryResolve = (taxonomyName: string, slug: string): void => {\n\t\tconst termId = plan.termIdByNameAndSlug.get(taxonomyName)?.get(slug);\n\t\tif (!termId) return;\n\t\tconst collectionFilter = plan.collectionsByTaxonomy.get(taxonomyName);\n\t\t// Empty set means \"no filter\" (def has no collections array). A\n\t\t// non-empty set is enforced: skip assignments to collections the\n\t\t// def doesn't list. Matches admin UI: a `category` term linked\n\t\t// only to `posts` shouldn't end up on a `products` row just\n\t\t// because the WXR happened to mention it.\n\t\tif (collectionFilter && collectionFilter.size > 0 && !collectionFilter.has(collection)) {\n\t\t\treturn;\n\t\t}\n\t\tconst dedupeKey = `${taxonomyName}\\u0000${termId}`;\n\t\tif (seen.has(dedupeKey)) return;\n\t\tseen.add(dedupeKey);\n\t\tconst existing = result.get(taxonomyName);\n\t\tif (existing) existing.push(termId);\n\t\telse result.set(taxonomyName, [termId]);\n\t};\n\n\tfor (const slug of post.categories) tryResolve(\"category\", slug);\n\tfor (const slug of post.tags) tryResolve(\"tag\", slug);\n\tif (post.customTaxonomies) {\n\t\tfor (const [rawName, slugs] of post.customTaxonomies) {\n\t\t\tif (rawName === \"nav_menu\" || rawName === \"language\") continue;\n\t\t\tconst taxonomyName = rawName === \"post_tag\" ? \"tag\" : rawName;\n\t\t\tfor (const slug of slugs) tryResolve(taxonomyName, slug);\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Attach the taxonomy assignments parsed for a single WXR post to a freshly-\n * created EmDash content row. Additive (`attachToEntry` + `ON CONFLICT DO\n * NOTHING`). Used for anchors -- translations need replace-semantics per\n * taxonomy and should use `setPostTermAssignmentsReplacing` instead.\n *\n * Returns the number of pivot rows actually inserted (excludes rows that\n * already existed via the `ON CONFLICT DO NOTHING` path), so the caller can\n * roll them up into the import summary without over-counting on re-imports.\n */\nexport async function attachPostTaxonomies(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tentryId: string,\n\tpost: WxrPost,\n\tplan: TaxonomyImportPlan,\n): Promise<number> {\n\tconst repo = new TaxonomyRepository(db);\n\tconst resolved = resolvePostTermAssignments(collection, post, plan);\n\tconst groups = new Set<string>();\n\tfor (const [, termIds] of resolved) {\n\t\tfor (const termId of termIds) {\n\t\t\tconst group = await termTranslationGroup(repo, plan, termId);\n\t\t\tif (group) groups.add(group);\n\t\t}\n\t}\n\treturn repo.attachGroupsToEntry(collection, entryId, [...groups]);\n}\n\n/**\n * Replace assignments per-taxonomy from a parsed WXR post. Used for\n * translations. Assignments belong to the content translation_group, so a\n * translated item's explicit taxonomy replaces that taxonomy for every\n * locale. Taxonomies with no resolvable permitted terms are left unchanged.\n *\n * Returns the number of pivot rows after replacement (sum of `termIds`\n * lists across taxonomies actually touched). Note this counts logical\n * assignments, not the delta from the prior state; the import summary\n * treats this as an additive count for compatibility with `attachPost-\n * Taxonomies`.\n */\nexport async function setPostTermAssignmentsReplacing(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tentryId: string,\n\tpost: WxrPost,\n\tplan: TaxonomyImportPlan,\n): Promise<number> {\n\tconst repo = new TaxonomyRepository(db);\n\tconst resolved = resolvePostTermAssignments(collection, post, plan);\n\n\tlet attached = 0;\n\tfor (const [taxonomyName, termIds] of resolved) {\n\t\tawait repo.setTermsForEntry(collection, entryId, taxonomyName, termIds);\n\t\tattached += termIds.length;\n\t}\n\treturn attached;\n}\n\n/**\n * Resolve a term id to its `translation_group` (the value\n * `content_taxonomies` stores). Caches the result on the plan so\n * repeated attaches of the same term don't repeat the lookup.\n */\nasync function termTranslationGroup(\n\trepo: TaxonomyRepository,\n\tplan: TaxonomyImportPlan,\n\ttermId: string,\n): Promise<string | null> {\n\tconst cached = plan.translationGroupByTermId.get(termId);\n\tif (cached !== undefined) return cached;\n\tconst term = await repo.findById(termId);\n\tconst group = term?.translationGroup ?? null;\n\tplan.translationGroupByTermId.set(termId, group);\n\treturn group;\n}\n\n/**\n * Mirror every term in the plan into each additional locale used by the\n * incoming posts. New rows share the canonical term's `translation_group`\n * so per-locale lookups (`getTermsForEntry(..., locale)`) resolve correctly\n * for translations whose locale differs from the import-wide one.\n *\n * Without this pass, multilingual WXR imports (#1080) write all term rows\n * at the upload-wide locale; the `content_taxonomies` pivot is correct (it\n * stores `translation_group`, not `term id`), but\n * `getTermsForEntry(collection, arabicPostId, \"category\", \"ar\")` filters on\n * `taxonomies.locale = \"ar\"` and returns zero rows. Users see \"no tags\" on\n * every non-canonical translation.\n *\n * Idempotent: skips a locale when a row already exists at `(name, slug,\n * locale)`. Safe to call after `preImportWxrTaxonomies` on subsequent\n * imports.\n */\nexport async function mirrorTermsToLocales(\n\tdb: Kysely<Database>,\n\tplan: TaxonomyImportPlan,\n\tpostLocales: Iterable<string>,\n\tcanonicalLocale: string | undefined,\n): Promise<void> {\n\tconst localeSet = new Set<string>();\n\tfor (const locale of postLocales) {\n\t\tif (!locale || locale === canonicalLocale) continue;\n\t\tlocaleSet.add(locale);\n\t}\n\tif (localeSet.size === 0) return;\n\n\tconst repo = new TaxonomyRepository(db);\n\n\tfor (const [taxonomyName, bySlug] of plan.termIdByNameAndSlug) {\n\t\tfor (const [slug, canonicalTermId] of bySlug) {\n\t\t\t// Resolve the canonical's translation_group once; we'll compare\n\t\t\t// against any pre-existing rows we find at the target locales.\n\t\t\t// Cache on the plan so subsequent attaches (which also need\n\t\t\t// this resolution) don't repeat the lookup.\n\t\t\tconst cachedGroup = await termTranslationGroup(repo, plan, canonicalTermId);\n\t\t\tif (!cachedGroup) {\n\t\t\t\t// The canonical term id is in the plan but the row is no\n\t\t\t\t// longer in the DB. Shouldn't happen during a single\n\t\t\t\t// import run; skip rather than crash so the rest of the\n\t\t\t\t// import can complete.\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst canonicalGroup = cachedGroup;\n\n\t\t\tfor (const locale of localeSet) {\n\t\t\t\tconst existing = await repo.findBySlug(taxonomyName, slug, locale);\n\t\t\t\tif (existing) {\n\t\t\t\t\t// `ensureTerm` resolves cross-locale grouping when it\n\t\t\t\t\t// creates the canonical row, so a pre-existing sibling\n\t\t\t\t\t// row at this locale should already share the\n\t\t\t\t\t// canonical's `translation_group`. If it doesn't, the\n\t\t\t\t\t// import would write pivots pointing at a group that\n\t\t\t\t\t// has no row in this locale -- a silent data-integrity\n\t\t\t\t\t// bug. Fail closed: throw so the operator reconciles\n\t\t\t\t\t// the existing rows in the admin before retrying. This\n\t\t\t\t\t// happens when the canonical row was created in an\n\t\t\t\t\t// earlier import and a sibling-locale row was added\n\t\t\t\t\t// manually afterwards (or vice versa) without linking\n\t\t\t\t\t// them via translationOf.\n\t\t\t\t\tif (existing.translationGroup !== canonicalGroup) {\n\t\t\t\t\t\tthrow new WxrTaxonomyConflictError(\n\t\t\t\t\t\t\t`Cannot import: term \"${taxonomyName}/${slug}\" already exists at locale \"${locale}\" in a different translation group than the canonical row at this import's locale. Reconcile the rows in the admin (re-link via translationOf, or delete one) and retry.`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tawait repo.create({\n\t\t\t\t\t\tname: taxonomyName,\n\t\t\t\t\t\tslug,\n\t\t\t\t\t\tlabel: slug, // we don't have a per-locale label from the WXR\n\t\t\t\t\t\tlocale,\n\t\t\t\t\t\ttranslationOf: canonicalTermId,\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// `findBySlug` + `create` is not atomic. A concurrent\n\t\t\t\t\t// import racing us into the same `(name, slug, locale)`\n\t\t\t\t\t// will trip the UNIQUE constraint. Re-read the row that\n\t\t\t\t\t// won the race and verify its `translation_group`\n\t\t\t\t\t// matches the canonical's; if not, the pivot will\n\t\t\t\t\t// resolve to a group that has no row in this locale\n\t\t\t\t\t// (silent data-integrity bug) so we surface that loudly\n\t\t\t\t\t// rather than continue.\n\t\t\t\t\t//\n\t\t\t\t\t// Other errors (validation, connectivity) re-throw so\n\t\t\t\t\t// the import fails closed rather than silently shipping\n\t\t\t\t\t// translations that resolve to empty taxonomy queries.\n\t\t\t\t\tconst message = error instanceof Error ? error.message.toLowerCase() : \"\";\n\t\t\t\t\tconst isUniqueRace =\n\t\t\t\t\t\tmessage.includes(\"unique constraint failed\") || message.includes(\"duplicate key\");\n\t\t\t\t\tif (!isUniqueRace) throw error;\n\n\t\t\t\t\tconst winner = await repo.findBySlug(taxonomyName, slug, locale);\n\t\t\t\t\tif (!winner) {\n\t\t\t\t\t\t// UNIQUE conflict but no row visible? Shouldn't\n\t\t\t\t\t\t// happen unless the racing transaction rolled back;\n\t\t\t\t\t\t// fail loudly so the operator can investigate.\n\t\t\t\t\t\tthrow new WxrTaxonomyConflictError(\n\t\t\t\t\t\t\t`Cannot import: term \"${taxonomyName}/${slug}\" raced UNIQUE at locale \"${locale}\" but no row is visible afterwards. The concurrent transaction may have rolled back; retry the import.`,\n\t\t\t\t\t\t\t{ cause: error },\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (winner.translationGroup !== canonicalGroup) {\n\t\t\t\t\t\tthrow new WxrTaxonomyConflictError(\n\t\t\t\t\t\t\t`Cannot import: term \"${taxonomyName}/${slug}\" raced UNIQUE at locale \"${locale}\" with a different translation group. Reconcile the rows in the admin and retry.`,\n\t\t\t\t\t\t\t{ cause: error },\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`[WXR import] concurrent writer beat us to term \"${taxonomyName}/${slug}\" at locale \"${locale}\"; using existing row (same group).`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { o as __exportAll } from "./runner-BFTrkYhu.mjs";
1
+ import { o as __exportAll } from "./runner-BiDvq5dv.mjs";
2
2
  import { r as hashString } from "./hash-Cg0TXJPV.mjs";
3
3
  import { z } from "zod";
4
4
 
@@ -9,6 +9,7 @@ var zod_generator_exports = /* @__PURE__ */ __exportAll({
9
9
  generateTypeScript: () => generateTypeScript,
10
10
  generateTypesFile: () => generateTypesFile,
11
11
  generateZodSchema: () => generateZodSchema,
12
+ invalidateSchemaCache: () => invalidateSchemaCache,
12
13
  uniqueInterfaceNames: () => uniqueInterfaceNames
13
14
  });
14
15
  /** Pattern to split on underscores, hyphens, and spaces for PascalCase conversion */
@@ -66,6 +67,7 @@ function getBaseSchema(type, field) {
66
67
  }
67
68
  return z.array(z.string());
68
69
  }
70
+ case "repeater": return z.array(generateRepeaterRowSchema(field.validation?.subFields ?? []));
69
71
  case "portableText": return z.array(z.object({
70
72
  _type: z.string(),
71
73
  _key: z.string().optional()
@@ -76,12 +78,17 @@ function getBaseSchema(type, field) {
76
78
  alt: z.string().optional(),
77
79
  width: z.number().optional(),
78
80
  height: z.number().optional(),
81
+ filename: z.string().optional(),
82
+ mimeType: z.string().optional(),
83
+ blurhash: z.string().optional(),
84
+ dominantColor: z.string().optional(),
79
85
  provider: z.string().optional(),
80
86
  previewUrl: z.string().optional(),
81
87
  meta: z.record(z.string(), z.unknown()).optional()
82
88
  });
83
89
  case "file": return z.object({
84
90
  id: z.string(),
91
+ url: z.string().optional(),
85
92
  src: z.string().optional(),
86
93
  filename: z.string().optional(),
87
94
  mimeType: z.string().optional(),
@@ -94,6 +101,16 @@ function getBaseSchema(type, field) {
94
101
  default: return z.unknown();
95
102
  }
96
103
  }
104
+ function generateRepeaterRowSchema(subFields) {
105
+ const shape = {};
106
+ for (const subField of subFields) {
107
+ let schema = getBaseSchema(subField.type, { validation: subField.options ? { options: subField.options } : void 0 });
108
+ if (subField.required && schema instanceof z.ZodString) schema = schema.min(1, "required (empty value not allowed)");
109
+ if (!subField.required) schema = schema.nullish();
110
+ shape[subField.slug] = schema;
111
+ }
112
+ return z.object(shape).passthrough();
113
+ }
97
114
  /**
98
115
  * Apply validation rules to a schema
99
116
  */
@@ -113,9 +130,25 @@ function applyValidation(schema, field) {
113
130
  if (validation.max !== void 0) numSchema = numSchema.max(validation.max);
114
131
  return numSchema;
115
132
  }
133
+ if (field.type === "repeater" && schema instanceof z.ZodArray) {
134
+ let arraySchema = schema;
135
+ if (validation.minItems !== void 0) arraySchema = arraySchema.min(validation.minItems);
136
+ if (validation.maxItems !== void 0) arraySchema = arraySchema.max(validation.maxItems);
137
+ return arraySchema;
138
+ }
116
139
  return schema;
117
140
  }
118
141
  /**
142
+ * Schema cache to avoid regenerating schemas on every request
143
+ */
144
+ const schemaCache = /* @__PURE__ */ new Map();
145
+ /**
146
+ * Invalidate cached schema for a collection
147
+ */
148
+ function invalidateSchemaCache(slug) {
149
+ schemaCache.delete(slug);
150
+ }
151
+ /**
119
152
  * Generate TypeScript interface from field definitions
120
153
  * Used by CLI `emdash types` to generate types
121
154
  */
@@ -201,9 +234,23 @@ function fieldTypeToTypeScript(field) {
201
234
  const multiOptions = field.validation?.options;
202
235
  if (multiOptions && multiOptions.length > 0) return `(${multiOptions.map((o) => `"${o}"`).join(" | ")})[]`;
203
236
  return "string[]";
237
+ case "repeater": {
238
+ const subFields = field.validation?.subFields;
239
+ if (!Array.isArray(subFields) || subFields.length === 0) return "unknown";
240
+ const members = /* @__PURE__ */ new Map();
241
+ for (const subField of subFields) {
242
+ const type = fieldTypeToTypeScript({
243
+ type: subField.type,
244
+ validation: subField.options ? { options: subField.options } : void 0
245
+ });
246
+ const name = JSON.stringify(subField.slug);
247
+ members.set(subField.slug, subField.required ? `${name}: ${type}` : `${name}?: ${type} | null`);
248
+ }
249
+ return `{ ${[...members.values()].join("; ")} }[]`;
250
+ }
204
251
  case "portableText": return "PortableTextBlock[]";
205
- case "image": return "{ id: string; src?: string; alt?: string; width?: number; height?: number; provider?: string; previewUrl?: string; meta?: Record<string, unknown> }";
206
- case "file": return "{ id: string; src?: string; filename?: string; mimeType?: string; size?: number; provider?: string; meta?: Record<string, unknown> }";
252
+ case "image": return "{ id: string; src?: string; alt?: string; width?: number; height?: number; filename?: string; mimeType?: string; blurhash?: string; dominantColor?: string; provider?: string; previewUrl?: string; meta?: Record<string, unknown> }";
253
+ case "file": return "{ id: string; url?: string; src?: string; filename?: string; mimeType?: string; size?: number; provider?: string; meta?: Record<string, unknown> }";
207
254
  case "reference": return "string";
208
255
  case "json": return "unknown";
209
256
  default: return "unknown";
@@ -270,5 +317,5 @@ function uniqueInterfaceNames(collections) {
270
317
  }
271
318
 
272
319
  //#endregion
273
- export { zod_generator_exports as i, generateZodSchema as n, uniqueInterfaceNames as r, generateTypeScript as t };
274
- //# sourceMappingURL=zod-generator-CG64Hnbh.mjs.map
320
+ export { zod_generator_exports as a, uniqueInterfaceNames as i, generateZodSchema as n, invalidateSchemaCache as r, generateTypeScript as t };
321
+ //# sourceMappingURL=zod-generator-D11MhxG4.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-generator-D11MhxG4.mjs","names":[],"sources":["../src/schema/zod-generator.ts"],"sourcesContent":["import { z, type ZodTypeAny } from \"zod\";\n\nimport { hashString } from \"../utils/hash.js\";\nimport type { CollectionWithFields, Field, FieldType, RepeaterSubField } 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: Pick<Field, \"validation\">): ZodTypeAny {\n\tswitch (type) {\n\t\tcase \"url\":\n\t\t\treturn z.string().url();\n\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"slug\":\n\t\t\treturn z.string();\n\n\t\tcase \"number\":\n\t\t\treturn z.number();\n\n\t\tcase \"integer\":\n\t\t\treturn z.number().int();\n\n\t\tcase \"boolean\":\n\t\t\t// Boolean fields map to `INTEGER` columns (`FIELD_TYPE_TO_COLUMN`\n\t\t\t// in `schema/types.ts`) and `serializeValue` in\n\t\t\t// `database/repositories/content.ts` writes booleans as 0/1.\n\t\t\t// `deserializeValue` never converts them back, so reads return\n\t\t\t// numbers. Coerce the stored 0/1 shape here so a GET → POST\n\t\t\t// round-trip on a boolean field passes validation. Other inputs\n\t\t\t// (strings, other numbers) fall through to `z.boolean()` and\n\t\t\t// produce its standard rejection.\n\t\t\treturn z.preprocess((v) => (v === 0 || v === 1 ? Boolean(v) : v), z.boolean());\n\n\t\tcase \"datetime\":\n\t\t\t// Accept every value that legitimately round-trips through the admin\n\t\t\t// and seeds: ISO with `Z`, ISO with a timezone offset, a naive\n\t\t\t// datetime (`YYYY-MM-DDTHH:mm[:ss]` -- what `<input type=\"datetime-local\">`\n\t\t\t// and many seeds produce), and a date-only value. The admin re-sends\n\t\t\t// every loaded field on autosave, so a stored naive datetime must\n\t\t\t// validate or the entry becomes unsavable through its own editor\n\t\t\t// (#1368; same class as #867). `z.iso.*` retains semantic validation,\n\t\t\t// so impossible dates are still rejected.\n\t\t\treturn z.iso.datetime({ offset: true, local: true }).or(z.iso.date());\n\n\t\tcase \"select\": {\n\t\t\tconst options = field.validation?.options;\n\t\t\tif (options && options.length > 0) {\n\t\t\t\tconst [first, ...rest] = options;\n\t\t\t\treturn z.enum([first, ...rest]);\n\t\t\t}\n\t\t\treturn z.string();\n\t\t}\n\n\t\tcase \"multiSelect\": {\n\t\t\tconst multiOptions = field.validation?.options;\n\t\t\tif (multiOptions && multiOptions.length > 0) {\n\t\t\t\tconst [first, ...rest] = multiOptions;\n\t\t\t\treturn z.array(z.enum([first, ...rest]));\n\t\t\t}\n\t\t\treturn z.array(z.string());\n\t\t}\n\n\t\tcase \"repeater\":\n\t\t\treturn z.array(generateRepeaterRowSchema(field.validation?.subFields ?? []));\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\tfilename: z.string().optional(),\n\t\t\t\tmimeType: z.string().optional(),\n\t\t\t\tblurhash: z.string().optional(),\n\t\t\t\tdominantColor: z.string().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\turl: z.string().optional(),\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\nfunction generateRepeaterRowSchema(\n\tsubFields: readonly RepeaterSubField[],\n): z.ZodObject<Record<string, ZodTypeAny>> {\n\tconst shape: Record<string, ZodTypeAny> = {};\n\n\tfor (const subField of subFields) {\n\t\tlet schema = getBaseSchema(subField.type, {\n\t\t\tvalidation: subField.options ? { options: subField.options } : undefined,\n\t\t});\n\n\t\tif (subField.required && schema instanceof z.ZodString) {\n\t\t\tschema = schema.min(1, \"required (empty value not allowed)\");\n\t\t}\n\t\tif (!subField.required) {\n\t\t\tschema = schema.nullish();\n\t\t}\n\n\t\tshape[subField.slug] = schema;\n\t}\n\n\treturn z.object(shape).passthrough();\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\tif (field.type === \"repeater\" && schema instanceof z.ZodArray) {\n\t\tlet arraySchema = schema;\n\t\tif (validation.minItems !== undefined) {\n\t\t\tarraySchema = arraySchema.min(validation.minItems);\n\t\t}\n\t\tif (validation.maxItems !== undefined) {\n\t\t\tarraySchema = arraySchema.max(validation.maxItems);\n\t\t}\n\t\treturn arraySchema;\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: {\n\ttype: Field[\"type\"];\n\tvalidation?: Field[\"validation\"];\n}): 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 \"repeater\": {\n\t\t\tconst subFields = field.validation?.subFields;\n\t\t\t// `validation` is unvalidated JSON on the seed and registry paths.\n\t\t\tif (!Array.isArray(subFields) || subFields.length === 0) return \"unknown\";\n\n\t\t\t// A duplicated slug keeps its first position and last declaration, as\n\t\t\t// `generateRepeaterRowSchema`'s `shape[subField.slug]` does.\n\t\t\tconst members = new Map<string, string>();\n\n\t\t\tfor (const subField of subFields) {\n\t\t\t\tconst type = fieldTypeToTypeScript({\n\t\t\t\t\ttype: subField.type,\n\t\t\t\t\tvalidation: subField.options ? { options: subField.options } : undefined,\n\t\t\t\t});\n\t\t\t\t// A sub-field slug is not guaranteed to be a valid identifier, so it is\n\t\t\t\t// quoted rather than emitted bare.\n\t\t\t\tconst name = JSON.stringify(subField.slug);\n\t\t\t\t// A sub-field that is not required may be null at runtime.\n\t\t\t\tmembers.set(\n\t\t\t\t\tsubField.slug,\n\t\t\t\t\tsubField.required ? `${name}: ${type}` : `${name}?: ${type} | null`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn `{ ${[...members.values()].join(\"; \")} }[]`;\n\t\t}\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; filename?: string; mimeType?: string; blurhash?: string; dominantColor?: string; provider?: string; previewUrl?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"file\":\n\t\t\treturn \"{ id: string; url?: string; src?: string; filename?: string; mimeType?: string; size?: number; provider?: string; meta?: Record<string, unknown> }\";\n\n\t\tcase \"reference\":\n\t\t\t// Could be enhanced to include the referenced collection type\n\t\t\treturn \"string\";\n\n\t\tcase \"json\":\n\t\t\treturn \"unknown\";\n\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Convert string to PascalCase (handles slugs, spaces, etc.)\n */\nfunction pascalCase(str: string): string {\n\treturn str\n\t\t.split(PASCAL_CASE_SPLIT_PATTERN)\n\t\t.filter(Boolean)\n\t\t.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n\t\t.join(\"\");\n}\n\n/**\n * Naive singularization for slug-derived interface names. Handles the common\n * English plural endings; intentionally simple, not a full inflector.\n */\nfunction singularize(str: string): string {\n\tif (str.endsWith(\"ies\")) {\n\t\treturn str.slice(0, -3) + \"y\";\n\t}\n\tif (\n\t\tstr.endsWith(\"es\") &&\n\t\t(str.endsWith(\"sses\") || str.endsWith(\"xes\") || str.endsWith(\"ches\") || str.endsWith(\"shes\"))\n\t) {\n\t\treturn str.slice(0, -2);\n\t}\n\tif (str.endsWith(\"s\") && !str.endsWith(\"ss\")) {\n\t\treturn str.slice(0, -1);\n\t}\n\treturn str;\n}\n\n/**\n * Get the interface name for a collection.\n *\n * Derived from the slug, not the human label. Slugs are constrained to\n * `/^[a-z][a-z0-9_]*$/`, so PascalCasing one always yields a valid TS\n * identifier; labels are arbitrary and user-controlled (punctuation, spaces,\n * duplicates across collections), which produced syntactically invalid or\n * duplicate interface names. The slug is singularized first because the\n * interface describes a single entry, not the collection (`posts` -> `Post`).\n *\n * Singularization can map two distinct slugs onto the same name, so callers\n * generating more than one interface must dedupe -- see `uniqueInterfaceNames`.\n */\nfunction getInterfaceName(collection: CollectionWithFields): string {\n\treturn pascalCase(singularize(collection.slug));\n}\n\n/**\n * Resolve interface names for a set of collections, guaranteeing each is\n * unique within the file. Collisions (from singularization or PascalCasing\n * collapsing distinct slugs) get a numeric suffix in collection order, so the\n * generated `.d.ts` never declares two interfaces with the same identifier.\n *\n * The suffix is chosen against the set of names already emitted, not a\n * per-base counter, so a generated name can't collide with another slug's\n * base name (e.g. slugs `book`, `books`, `book2`: `books` -> `Book2` would\n * clash with `book2`, so it advances to `Book3`).\n */\nexport function uniqueInterfaceNames(collections: CollectionWithFields[]): Map<string, string> {\n\tconst used = new Set<string>();\n\tconst names = new Map<string, string>();\n\tfor (const collection of collections) {\n\t\tconst base = getInterfaceName(collection);\n\t\tlet name = base;\n\t\tlet suffix = 2;\n\t\twhile (used.has(name)) {\n\t\t\tname = `${base}${suffix}`;\n\t\t\tsuffix++;\n\t\t}\n\t\tused.add(name);\n\t\tnames.set(collection.slug, name);\n\t}\n\treturn names;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emdash",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "Astro-native CMS with WordPress migration support",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -37,6 +37,10 @@
37
37
  "types": "./dist/astro/middleware/auth.d.mts",
38
38
  "default": "./dist/astro/middleware/auth.mjs"
39
39
  },
40
+ "./middleware/media-usage-write-fence": {
41
+ "types": "./dist/astro/middleware/media-usage-write-fence.d.mts",
42
+ "default": "./dist/astro/middleware/media-usage-write-fence.mjs"
43
+ },
40
44
  "./middleware/redirect": {
41
45
  "types": "./dist/astro/middleware/redirect.d.mts",
42
46
  "default": "./dist/astro/middleware/redirect.mjs"
@@ -225,24 +229,25 @@
225
229
  "citty": "^0.2.2",
226
230
  "consola": "^3.4.2",
227
231
  "croner": "^10.0.1",
228
- "image-size": "^2.0.2",
229
232
  "jose": "^6.1.3",
230
233
  "jpeg-js": "^0.4.4",
234
+ "jsonc-parser": "^3.3.1",
231
235
  "kysely": "^0.29.0",
232
236
  "mime": "^4.1.0",
233
237
  "modern-tar": "^0.7.5",
234
238
  "picocolors": "^1.1.1",
235
239
  "sanitize-html": "^2.17.1",
236
240
  "sax": "^1.4.1",
241
+ "smol-toml": "^1.6.0",
237
242
  "ulidx": "^2.4.1",
238
243
  "upng-js": "^2.1.0",
239
244
  "zod": "^4.4.1",
240
245
  "@atcute/client": "^5.0.0",
241
- "@emdash-cms/gutenberg-to-portable-text": "0.33.0",
242
- "@emdash-cms/plugin-types": "0.3.0",
243
- "@emdash-cms/auth": "0.33.0",
244
- "@emdash-cms/admin": "0.33.0",
245
- "@emdash-cms/registry-client": "0.3.4"
246
+ "@emdash-cms/admin": "0.34.0",
247
+ "@emdash-cms/auth": "0.34.0",
248
+ "@emdash-cms/gutenberg-to-portable-text": "0.34.0",
249
+ "@emdash-cms/registry-client": "0.3.4",
250
+ "@emdash-cms/plugin-types": "0.3.0"
246
251
  },
247
252
  "optionalDependencies": {
248
253
  "@libsql/kysely-libsql": "^0.4.0",
@@ -263,19 +268,21 @@
263
268
  "devDependencies": {
264
269
  "@apidevtools/swagger-parser": "^12.1.0",
265
270
  "@arethetypeswrong/cli": "^0.18.2",
271
+ "@cloudflare/vitest-pool-workers": "^0.16.3",
266
272
  "@types/better-sqlite3": "^7.6.12",
267
273
  "@types/pg": "^8.16.0",
268
274
  "@types/react": "19.2.14",
269
275
  "@types/sanitize-html": "^2.16.0",
270
276
  "@types/sax": "^1.2.7",
271
277
  "@vitest/ui": "^4.1.10",
278
+ "image-size": "2.0.2",
272
279
  "publint": "0.3.17",
273
280
  "tsdown": "0.20.3",
274
281
  "typescript": "^6.0.3",
275
282
  "vite": "^6.0.0",
276
283
  "vitest": "^4.1.5",
277
284
  "zod-openapi": "^5.4.6",
278
- "@emdash-cms/blocks": "0.33.0"
285
+ "@emdash-cms/blocks": "0.34.0"
279
286
  },
280
287
  "repository": {
281
288
  "type": "git",
@@ -297,6 +304,7 @@
297
304
  "typecheck": "tsgo --noEmit",
298
305
  "check": "publint && attw --pack --ignore-rules=cjs-resolves-to-esm --ignore-rules=no-resolution --ignore-rules=internal-resolution-error",
299
306
  "test": "vitest",
307
+ "test:workerd": "vitest run --config vitest.workerd.config.ts",
300
308
  "test:smoke": "vitest run --config vitest.smoke.config.ts",
301
309
  "test:integration": "vitest run --config vitest.integration.config.ts",
302
310
  "test:repro": "vitest run --config vitest.repro.config.ts"
package/src/api/errors.ts CHANGED
@@ -88,6 +88,12 @@ export const ErrorCode = {
88
88
  MEDIA_DELETE_ERROR: "MEDIA_DELETE_ERROR",
89
89
  MEDIA_USAGE_READ_ERROR: "MEDIA_USAGE_READ_ERROR",
90
90
  MEDIA_USAGE_REPAIR_ERROR: "MEDIA_USAGE_REPAIR_ERROR",
91
+ MEDIA_USAGE_WORK_LIST_ERROR: "MEDIA_USAGE_WORK_LIST_ERROR",
92
+ MEDIA_USAGE_WORK_RETRY_ERROR: "MEDIA_USAGE_WORK_RETRY_ERROR",
93
+ MEDIA_USAGE_COLLECTION_DELETION_LIST_ERROR: "MEDIA_USAGE_COLLECTION_DELETION_LIST_ERROR",
94
+ MEDIA_USAGE_COLLECTION_DELETION_RETRY_ERROR: "MEDIA_USAGE_COLLECTION_DELETION_RETRY_ERROR",
95
+ WORK_LEASE_ACTIVE: "WORK_LEASE_ACTIVE",
96
+ WORK_CHANGED: "WORK_CHANGED",
91
97
  NO_STORAGE: "NO_STORAGE",
92
98
  NO_FILE: "NO_FILE",
93
99
  INVALID_TYPE: "INVALID_TYPE",
@@ -241,6 +247,10 @@ export const ErrorCode = {
241
247
  // Taxonomies
242
248
  TAXONOMY_LIST_ERROR: "TAXONOMY_LIST_ERROR",
243
249
  TAXONOMY_CREATE_ERROR: "TAXONOMY_CREATE_ERROR",
250
+ TAXONOMY_GET_ERROR: "TAXONOMY_GET_ERROR",
251
+ TAXONOMY_UPDATE_ERROR: "TAXONOMY_UPDATE_ERROR",
252
+ TAXONOMY_DELETE_ERROR: "TAXONOMY_DELETE_ERROR",
253
+ TAXONOMY_TRANSLATIONS_ERROR: "TAXONOMY_TRANSLATIONS_ERROR",
244
254
  TERM_LIST_ERROR: "TERM_LIST_ERROR",
245
255
  TERM_CREATE_ERROR: "TERM_CREATE_ERROR",
246
256
  TERM_GET_ERROR: "TERM_GET_ERROR",
@@ -449,6 +459,8 @@ export function mapErrorStatus(code: string | undefined): number {
449
459
  case ErrorCode.ALREADY_UP_TO_DATE:
450
460
  case ErrorCode.TRANSLATABLE_LOCKED:
451
461
  case ErrorCode.ENV_INCOMPATIBLE:
462
+ case ErrorCode.WORK_LEASE_ACTIVE:
463
+ case ErrorCode.WORK_CHANGED:
452
464
  return 409;
453
465
 
454
466
  // 410 Gone