emdash 0.24.1 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (422) hide show
  1. package/dist/api/route-utils.d.mts +3 -3
  2. package/dist/api/route-utils.mjs +12 -11
  3. package/dist/api/route-utils.mjs.map +1 -1
  4. package/dist/api/schemas/index.d.mts +1 -1
  5. package/dist/api/schemas/index.mjs +2 -2
  6. package/dist/{api-d_0wi9fW.mjs → api-BMfveMoB.mjs} +24 -20
  7. package/dist/api-BMfveMoB.mjs.map +1 -0
  8. package/dist/{apply-BE9UaWX0.mjs → apply--IVNSdXn.mjs} +19 -16
  9. package/dist/apply--IVNSdXn.mjs.map +1 -0
  10. package/dist/astro/index.d.mts +9 -9
  11. package/dist/astro/index.mjs +3 -2
  12. package/dist/astro/index.mjs.map +1 -1
  13. package/dist/astro/middleware/auth.d.mts +8 -8
  14. package/dist/astro/middleware/auth.mjs +2 -2
  15. package/dist/astro/middleware/redirect.mjs +4 -4
  16. package/dist/astro/middleware.d.mts +1 -1
  17. package/dist/astro/middleware.d.mts.map +1 -1
  18. package/dist/astro/middleware.mjs +99 -70
  19. package/dist/astro/middleware.mjs.map +1 -1
  20. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
  21. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
  22. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  23. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  24. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
  25. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +6 -6
  26. package/dist/astro/routes/api/admin/byline-fields/index.mjs +6 -6
  27. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +6 -6
  28. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -12
  29. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +12 -12
  30. package/dist/astro/routes/api/admin/bylines/index.mjs +12 -12
  31. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +10 -10
  32. package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
  33. package/dist/astro/routes/api/admin/comments/bulk.mjs +9 -9
  34. package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
  35. package/dist/astro/routes/api/admin/comments/index.mjs +9 -9
  36. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  37. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  38. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  39. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  40. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +31 -31
  41. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +31 -31
  42. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +31 -31
  43. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +31 -31
  44. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +31 -31
  45. package/dist/astro/routes/api/admin/plugins/index.mjs +31 -31
  46. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  47. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +31 -31
  48. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +31 -31
  49. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +31 -31
  50. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +31 -31
  51. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +32 -32
  52. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +31 -31
  53. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +32 -32
  54. package/dist/astro/routes/api/admin/plugins/updates.mjs +31 -31
  55. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +31 -31
  56. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  57. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +31 -31
  58. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  59. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  60. package/dist/astro/routes/api/admin/users/_id_/index.mjs +5 -5
  61. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  62. package/dist/astro/routes/api/admin/users/index.mjs +5 -5
  63. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  64. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  65. package/dist/astro/routes/api/auth/invite/complete.mjs +5 -5
  66. package/dist/astro/routes/api/auth/invite/index.mjs +5 -5
  67. package/dist/astro/routes/api/auth/invite/register-options.mjs +5 -5
  68. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  69. package/dist/astro/routes/api/auth/magic-link/send.mjs +6 -6
  70. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  71. package/dist/astro/routes/api/auth/me.mjs +6 -6
  72. package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
  73. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  74. package/dist/astro/routes/api/auth/passkey/options.mjs +6 -6
  75. package/dist/astro/routes/api/auth/passkey/register/options.mjs +5 -5
  76. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +5 -5
  77. package/dist/astro/routes/api/auth/passkey/verify.mjs +5 -5
  78. package/dist/astro/routes/api/auth/signup/complete.mjs +5 -5
  79. package/dist/astro/routes/api/auth/signup/request.mjs +6 -6
  80. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  81. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +9 -9
  82. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +9 -9
  83. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  84. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  85. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  86. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  87. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +6 -6
  88. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +6 -6
  89. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  90. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  91. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +6 -6
  92. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +11 -11
  93. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  94. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  95. package/dist/astro/routes/api/content/_collection_/_id_.mjs +6 -6
  96. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  97. package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
  98. package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
  99. package/dist/astro/routes/api/dashboard.mjs +8 -8
  100. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  101. package/dist/astro/routes/api/import/probe.d.mts +2 -2
  102. package/dist/astro/routes/api/import/probe.mjs +6 -6
  103. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  104. package/dist/astro/routes/api/import/wordpress/execute.d.mts +8 -8
  105. package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
  106. package/dist/astro/routes/api/import/wordpress/media.d.mts +13 -1
  107. package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
  108. package/dist/astro/routes/api/import/wordpress/media.mjs +13 -9
  109. package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
  110. package/dist/astro/routes/api/import/wordpress/prepare.mjs +8 -8
  111. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
  112. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +6 -6
  113. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +9 -9
  114. package/dist/astro/routes/api/manifest.mjs +3 -3
  115. package/dist/astro/routes/api/mcp.mjs +36 -36
  116. package/dist/astro/routes/api/media/_id_/confirm.d.mts.map +1 -1
  117. package/dist/astro/routes/api/media/_id_/confirm.mjs +44 -8
  118. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  119. package/dist/astro/routes/api/media/_id_.mjs +6 -6
  120. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  121. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  122. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  123. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  124. package/dist/astro/routes/api/media/upload-url.mjs +8 -8
  125. package/dist/astro/routes/api/media.mjs +22 -21
  126. package/dist/astro/routes/api/media.mjs.map +1 -1
  127. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +8 -8
  128. package/dist/astro/routes/api/menus/_name_/items.mjs +8 -8
  129. package/dist/astro/routes/api/menus/_name_/reorder.mjs +8 -8
  130. package/dist/astro/routes/api/menus/_name_/translations.mjs +8 -8
  131. package/dist/astro/routes/api/menus/_name_.mjs +8 -8
  132. package/dist/astro/routes/api/menus/index.mjs +8 -8
  133. package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
  134. package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
  135. package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
  136. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  137. package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
  138. package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
  139. package/dist/astro/routes/api/oauth/token.mjs +2 -2
  140. package/dist/astro/routes/api/openapi.json.mjs +3 -3
  141. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
  142. package/dist/astro/routes/api/redirects/404s/index.mjs +8 -8
  143. package/dist/astro/routes/api/redirects/404s/summary.mjs +8 -8
  144. package/dist/astro/routes/api/redirects/_id_.mjs +9 -9
  145. package/dist/astro/routes/api/redirects/index.mjs +9 -9
  146. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  147. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  148. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +31 -31
  149. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +31 -31
  150. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +31 -31
  151. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +31 -31
  152. package/dist/astro/routes/api/schema/collections/index.mjs +31 -31
  153. package/dist/astro/routes/api/schema/index.mjs +6 -6
  154. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +31 -31
  155. package/dist/astro/routes/api/schema/orphans/index.mjs +31 -31
  156. package/dist/astro/routes/api/search/enable.mjs +9 -9
  157. package/dist/astro/routes/api/search/index.mjs +8 -8
  158. package/dist/astro/routes/api/search/rebuild.mjs +9 -9
  159. package/dist/astro/routes/api/search/stats.mjs +6 -6
  160. package/dist/astro/routes/api/search/suggest.mjs +8 -8
  161. package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
  162. package/dist/astro/routes/api/sections/index.mjs +8 -8
  163. package/dist/astro/routes/api/settings/email.mjs +4 -4
  164. package/dist/astro/routes/api/settings.mjs +11 -11
  165. package/dist/astro/routes/api/setup/admin-verify.mjs +5 -5
  166. package/dist/astro/routes/api/setup/admin.mjs +5 -5
  167. package/dist/astro/routes/api/setup/dev-bypass.mjs +17 -17
  168. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  169. package/dist/astro/routes/api/setup/index.mjs +20 -20
  170. package/dist/astro/routes/api/setup/status.mjs +3 -3
  171. package/dist/astro/routes/api/snapshot.mjs +3 -3
  172. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -11
  173. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -11
  174. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -11
  175. package/dist/astro/routes/api/taxonomies/index.mjs +11 -11
  176. package/dist/astro/routes/api/themes/preview.mjs +3 -3
  177. package/dist/astro/routes/api/typegen.mjs +5 -5
  178. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
  179. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +8 -8
  180. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +8 -8
  181. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  182. package/dist/astro/routes/api/widget-areas/index.mjs +8 -8
  183. package/dist/astro/routes/api/widget-components.mjs +2 -2
  184. package/dist/astro/routes/robots.txt.mjs +5 -5
  185. package/dist/astro/routes/sitemap-_collection_.xml.mjs +11 -10
  186. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  187. package/dist/astro/routes/sitemap.xml.mjs +6 -6
  188. package/dist/astro/types.d.mts +10 -10
  189. package/dist/{authorize-B__u-iZJ.mjs → authorize-BZnOGJFo.mjs} +2 -2
  190. package/dist/{authorize-B__u-iZJ.mjs.map → authorize-BZnOGJFo.mjs.map} +1 -1
  191. package/dist/{byline-C-YajfV7.mjs → byline-LL8lyqdb.mjs} +6 -6
  192. package/dist/{byline-C-YajfV7.mjs.map → byline-LL8lyqdb.mjs.map} +1 -1
  193. package/dist/{byline-fields-Ca56gaT6.d.mts → byline-fields-KD3JFRuH.d.mts} +24 -6
  194. package/dist/byline-fields-KD3JFRuH.d.mts.map +1 -0
  195. package/dist/{byline-fields-B-KXkg5R.mjs → byline-fields-eyJMblSr.mjs} +2 -2
  196. package/dist/{byline-fields-B-KXkg5R.mjs.map → byline-fields-eyJMblSr.mjs.map} +1 -1
  197. package/dist/{bylines-D4KADDAi.mjs → bylines-BmJ7Vg7J.mjs} +4 -4
  198. package/dist/{bylines-D4KADDAi.mjs.map → bylines-BmJ7Vg7J.mjs.map} +1 -1
  199. package/dist/{bylines-BMtJnPnG.mjs → bylines-DE47vP8d.mjs} +6 -6
  200. package/dist/{bylines-BMtJnPnG.mjs.map → bylines-DE47vP8d.mjs.map} +1 -1
  201. package/dist/{cache-BUJe2w6Z.mjs → cache-D9jF5bYw.mjs} +2 -2
  202. package/dist/{cache-BUJe2w6Z.mjs.map → cache-D9jF5bYw.mjs.map} +1 -1
  203. package/dist/{chunks-BHgcHh2C.mjs → chunks-OCG163rN.mjs} +2 -2
  204. package/dist/{chunks-BHgcHh2C.mjs.map → chunks-OCG163rN.mjs.map} +1 -1
  205. package/dist/cli/index.mjs +17 -17
  206. package/dist/{comment-BDx50EX5.mjs → comment-D9BLDpZI.mjs} +3 -3
  207. package/dist/{comment-BDx50EX5.mjs.map → comment-D9BLDpZI.mjs.map} +1 -1
  208. package/dist/{comment-reaction-Gv_pgfI3.mjs → comment-reaction-Drl9SkNu.mjs} +2 -2
  209. package/dist/{comment-reaction-Gv_pgfI3.mjs.map → comment-reaction-Drl9SkNu.mjs.map} +1 -1
  210. package/dist/{comments-CXHuQ6jI.mjs → comments-BjJ9SKGU.mjs} +3 -3
  211. package/dist/{comments-CXHuQ6jI.mjs.map → comments-BjJ9SKGU.mjs.map} +1 -1
  212. package/dist/{content-PoVGKjp0.mjs → content-ncUWWjna.mjs} +10 -8
  213. package/dist/content-ncUWWjna.mjs.map +1 -0
  214. package/dist/{context-Dvws41_A.mjs → context-C_g-iJVA.mjs} +14 -8
  215. package/dist/context-C_g-iJVA.mjs.map +1 -0
  216. package/dist/{dashboard-DsYU8JKS.mjs → dashboard-CotyqQrW.mjs} +4 -4
  217. package/dist/{dashboard-DsYU8JKS.mjs.map → dashboard-CotyqQrW.mjs.map} +1 -1
  218. package/dist/db/index.d.mts +2 -2
  219. package/dist/db/index.mjs +1 -1
  220. package/dist/{db-errors-UXB1CK42.mjs → db-errors-CK46D-ly.mjs} +1 -1
  221. package/dist/{db-errors-UXB1CK42.mjs.map → db-errors-CK46D-ly.mjs.map} +1 -1
  222. package/dist/{placeholder-B0s2mWu5.mjs → enrich-BQ0mxJRs.mjs} +61 -10
  223. package/dist/enrich-BQ0mxJRs.mjs.map +1 -0
  224. package/dist/{error-lq_fL-RS.mjs → error-rVGiqWDt.mjs} +3 -2
  225. package/dist/error-rVGiqWDt.mjs.map +1 -0
  226. package/dist/{field-defs-cache-CYYx8xlG.mjs → field-defs-cache-ljXzJCfc.mjs} +2 -2
  227. package/dist/{field-defs-cache-CYYx8xlG.mjs.map → field-defs-cache-ljXzJCfc.mjs.map} +1 -1
  228. package/dist/{fts-manager-DW_NR-Fs.mjs → fts-manager-BbCCbEKA.mjs} +2 -2
  229. package/dist/{fts-manager-DW_NR-Fs.mjs.map → fts-manager-BbCCbEKA.mjs.map} +1 -1
  230. package/dist/{index-BroXT-vD.d.mts → index-BRNabKrx.d.mts} +53 -15
  231. package/dist/index-BRNabKrx.d.mts.map +1 -0
  232. package/dist/{index-qbL02uhs.d.mts → index-Dt0OKJB4.d.mts} +2 -2
  233. package/dist/{index-qbL02uhs.d.mts.map → index-Dt0OKJB4.d.mts.map} +1 -1
  234. package/dist/index.d.mts +12 -12
  235. package/dist/index.mjs +43 -43
  236. package/dist/{load-DMFi4n1p.mjs → load-DMjXghbZ.mjs} +2 -2
  237. package/dist/{load-DMFi4n1p.mjs.map → load-DMjXghbZ.mjs.map} +1 -1
  238. package/dist/{loader-BR2SgfEc.mjs → loader-BA_zhkS_.mjs} +82 -19
  239. package/dist/loader-BA_zhkS_.mjs.map +1 -0
  240. package/dist/{manifest-schema-BWKnQYQF.mjs → manifest-schema-kPGX7VS-.mjs} +4 -1
  241. package/dist/manifest-schema-kPGX7VS-.mjs.map +1 -0
  242. package/dist/media/image-endpoint.mjs +1 -1
  243. package/dist/media/index.d.mts +45 -2
  244. package/dist/media/index.d.mts.map +1 -1
  245. package/dist/media/index.mjs +5 -3
  246. package/dist/media/index.mjs.map +1 -1
  247. package/dist/media/local-runtime.d.mts +9 -9
  248. package/dist/media/local-runtime.d.mts.map +1 -1
  249. package/dist/media/local-runtime.mjs +17 -5
  250. package/dist/media/local-runtime.mjs.map +1 -1
  251. package/dist/{media-CTjJoJTh.mjs → media-C5t_eCnk.mjs} +4 -2
  252. package/dist/media-C5t_eCnk.mjs.map +1 -0
  253. package/dist/{media-allowlist-B77I5_bw.mjs → media-allowlist-Du8t1u6a.mjs} +2 -2
  254. package/dist/{media-allowlist-B77I5_bw.mjs.map → media-allowlist-Du8t1u6a.mjs.map} +1 -1
  255. package/dist/{menus-8O6xNcYW.mjs → menus-CBb6UrMU.mjs} +3 -3
  256. package/dist/{menus-8O6xNcYW.mjs.map → menus-CBb6UrMU.mjs.map} +1 -1
  257. package/dist/{menus-GvRSpgIA.mjs → menus-CYH09a41.mjs} +65 -41
  258. package/dist/menus-CYH09a41.mjs.map +1 -0
  259. package/dist/{mime-BTzvzJ8M.mjs → mime-CCEzze7W.mjs} +1 -1
  260. package/dist/{mime-BTzvzJ8M.mjs.map → mime-CCEzze7W.mjs.map} +1 -1
  261. package/dist/{normalize-ClbNIenm.mjs → normalize-CeglrBT9.mjs} +11 -2
  262. package/dist/normalize-CeglrBT9.mjs.map +1 -0
  263. package/dist/{object-cache-SEb2IM4Z.mjs → object-cache-CHbHv83-.mjs} +8 -4
  264. package/dist/object-cache-CHbHv83-.mjs.map +1 -0
  265. package/dist/{options-CqECG0gM.d.mts → options-41nCWqi9.d.mts} +3 -3
  266. package/dist/{options-CqECG0gM.d.mts.map → options-41nCWqi9.d.mts.map} +1 -1
  267. package/dist/page/index.d.mts +5 -2
  268. package/dist/page/index.d.mts.map +1 -1
  269. package/dist/page/index.mjs +3 -2
  270. package/dist/page/index.mjs.map +1 -1
  271. package/dist/{parse-DHI873qU.mjs → parse-BwAdS7FZ.mjs} +2 -2
  272. package/dist/{parse-DHI873qU.mjs.map → parse-BwAdS7FZ.mjs.map} +1 -1
  273. package/dist/{placeholder-QoI4gEzP.d.mts → placeholder-Cuce9U-m.d.mts} +20 -5
  274. package/dist/placeholder-Cuce9U-m.d.mts.map +1 -0
  275. package/dist/plugin-types.d.mts +5 -2
  276. package/dist/plugin-types.d.mts.map +1 -1
  277. package/dist/plugin-utils.d.mts +8 -8
  278. package/dist/plugins/adapt-sandbox-entry.d.mts +8 -8
  279. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  280. package/dist/{query-DZdlS6Cu.mjs → query-BpQb-blG.mjs} +15 -15
  281. package/dist/{query-DZdlS6Cu.mjs.map → query-BpQb-blG.mjs.map} +1 -1
  282. package/dist/{rate-limit-D1Y6VFVP.mjs → rate-limit-Dap6HMNM.mjs} +2 -2
  283. package/dist/{rate-limit-D1Y6VFVP.mjs.map → rate-limit-Dap6HMNM.mjs.map} +1 -1
  284. package/dist/{redirect-C1KVGYWe.mjs → redirect-_M0trWoo.mjs} +2 -2
  285. package/dist/{redirect-C1KVGYWe.mjs.map → redirect-_M0trWoo.mjs.map} +1 -1
  286. package/dist/{redirects-x9eFPeyi.mjs → redirects-Dcbuisoj.mjs} +2 -1
  287. package/dist/redirects-Dcbuisoj.mjs.map +1 -0
  288. package/dist/{redirects-DgnypVir.mjs → redirects-w0V9vj_j.mjs} +3 -3
  289. package/dist/{redirects-DgnypVir.mjs.map → redirects-w0V9vj_j.mjs.map} +1 -1
  290. package/dist/{registry-CJkCubOJ.mjs → registry-g8yYt4cC.mjs} +14 -4
  291. package/dist/registry-g8yYt4cC.mjs.map +1 -0
  292. package/dist/{runner-CEtq-6Sm.d.mts → runner-BbR3DfrL.d.mts} +2 -2
  293. package/dist/{runner-CEtq-6Sm.d.mts.map → runner-BbR3DfrL.d.mts.map} +1 -1
  294. package/dist/{runner-BsquExlK.mjs → runner-DPdt_Wpa.mjs} +250 -178
  295. package/dist/runner-DPdt_Wpa.mjs.map +1 -0
  296. package/dist/runtime.d.mts +9 -9
  297. package/dist/runtime.mjs +2 -2
  298. package/dist/{schema-DMjXuvNU.mjs → schema-DoLd3j2e.mjs} +5 -5
  299. package/dist/{schema-DMjXuvNU.mjs.map → schema-DoLd3j2e.mjs.map} +1 -1
  300. package/dist/{search-D1wKk3lc.mjs → search-BR0ZeZmh.mjs} +4 -4
  301. package/dist/{search-D1wKk3lc.mjs.map → search-BR0ZeZmh.mjs.map} +1 -1
  302. package/dist/{sections-CCMic8dB.mjs → sections-5J2YJPqJ.mjs} +3 -3
  303. package/dist/{sections-CCMic8dB.mjs.map → sections-5J2YJPqJ.mjs.map} +1 -1
  304. package/dist/seed/index.d.mts +2 -2
  305. package/dist/seed/index.mjs +15 -15
  306. package/dist/seo/index.d.mts +1 -1
  307. package/dist/{seo-DCEF-AVr.mjs → seo-BkhuuaaE.mjs} +25 -3
  308. package/dist/seo-BkhuuaaE.mjs.map +1 -0
  309. package/dist/{seo-3QrZ1EPT.mjs → seo-DKeXHkzE.mjs} +3 -3
  310. package/dist/{seo-3QrZ1EPT.mjs.map → seo-DKeXHkzE.mjs.map} +1 -1
  311. package/dist/{service-CQHK_10l.mjs → service-BbO2s8gT.mjs} +2 -2
  312. package/dist/{service-CQHK_10l.mjs.map → service-BbO2s8gT.mjs.map} +1 -1
  313. package/dist/{settings-C3Yi5Co3.mjs → settings-BMNr9Dmi.mjs} +3 -3
  314. package/dist/{settings-C3Yi5Co3.mjs.map → settings-BMNr9Dmi.mjs.map} +1 -1
  315. package/dist/{settings-D9rvaLuY.mjs → settings-DJeki-97.mjs} +4 -4
  316. package/dist/{settings-D9rvaLuY.mjs.map → settings-DJeki-97.mjs.map} +1 -1
  317. package/dist/{taxonomies-DhLaNJji.mjs → taxonomies-CM_fUqLU.mjs} +5 -4
  318. package/dist/taxonomies-CM_fUqLU.mjs.map +1 -0
  319. package/dist/{taxonomies-CurpLtNC.mjs → taxonomies-D13i2ctt.mjs} +76 -21
  320. package/dist/taxonomies-D13i2ctt.mjs.map +1 -0
  321. package/dist/{taxonomy-BBUlGug5.mjs → taxonomy-BU0LjdVb.mjs} +4 -4
  322. package/dist/{taxonomy-BBUlGug5.mjs.map → taxonomy-BU0LjdVb.mjs.map} +1 -1
  323. package/dist/{types-TTx8uP7V.d.mts → types-BYnDBVwe.d.mts} +27 -5
  324. package/dist/{types-TTx8uP7V.d.mts.map → types-BYnDBVwe.d.mts.map} +1 -1
  325. package/dist/{types-CTLJYMhN.d.mts → types-CNlaBFzx.d.mts} +11 -1
  326. package/dist/{types-CTLJYMhN.d.mts.map → types-CNlaBFzx.d.mts.map} +1 -1
  327. package/dist/{types-CmI1OyQg.mjs → types-CZgK3EMW.mjs} +2 -2
  328. package/dist/{types-CmI1OyQg.mjs.map → types-CZgK3EMW.mjs.map} +1 -1
  329. package/dist/{types-BWh2MjmA.d.mts → types-ETmO_jQr.d.mts} +2 -2
  330. package/dist/{types-BWh2MjmA.d.mts.map → types-ETmO_jQr.d.mts.map} +1 -1
  331. package/dist/types-tM44hEcf.mjs.map +1 -1
  332. package/dist/{types-B-gIxFCE.d.mts → types-xIfVRNLp.d.mts} +38 -1
  333. package/dist/types-xIfVRNLp.d.mts.map +1 -0
  334. package/dist/{user-JAuc8VUf.mjs → user-DkG2jKCp.mjs} +3 -3
  335. package/dist/{user-JAuc8VUf.mjs.map → user-DkG2jKCp.mjs.map} +1 -1
  336. package/dist/{validate-DOFrIb-c.d.mts → validate-CG0V0wNR.d.mts} +3 -3
  337. package/dist/{validate-DOFrIb-c.d.mts.map → validate-CG0V0wNR.d.mts.map} +1 -1
  338. package/dist/{validate-BzXHWnkV.mjs → validate-CuJ1Ps9j.mjs} +2 -2
  339. package/dist/{validate-BzXHWnkV.mjs.map → validate-CuJ1Ps9j.mjs.map} +1 -1
  340. package/dist/{validation-CY1gk4Ar.mjs → validation-CwUfTdGF.mjs} +6 -6
  341. package/dist/{validation-CY1gk4Ar.mjs.map → validation-CwUfTdGF.mjs.map} +1 -1
  342. package/dist/version-BoNBItP_.mjs +7 -0
  343. package/dist/{version-C0JPFUG8.mjs.map → version-BoNBItP_.mjs.map} +1 -1
  344. package/dist/{widgets-Dri0spI4.mjs → widgets-BfOZ-ajY.mjs} +2 -2
  345. package/dist/{widgets-Dri0spI4.mjs.map → widgets-BfOZ-ajY.mjs.map} +1 -1
  346. package/dist/{zod-generator-xEHODMJf.mjs → zod-generator-gFsRuNNy.mjs} +6 -3
  347. package/dist/zod-generator-gFsRuNNy.mjs.map +1 -0
  348. package/package.json +6 -6
  349. package/src/api/errors.ts +1 -0
  350. package/src/api/handlers/content.ts +4 -4
  351. package/src/api/handlers/seo.ts +30 -2
  352. package/src/api/handlers/taxonomies.ts +5 -1
  353. package/src/api/schemas/schema.ts +1 -0
  354. package/src/astro/integration/virtual-modules.ts +15 -0
  355. package/src/astro/middleware.ts +14 -1
  356. package/src/astro/routes/api/import/wordpress/media.ts +9 -3
  357. package/src/astro/routes/api/media/[id]/confirm.ts +62 -2
  358. package/src/astro/routes/api/media.ts +17 -19
  359. package/src/astro/routes/sitemap-[collection].xml.ts +5 -4
  360. package/src/components/EmDashHead.astro +23 -3
  361. package/src/components/EmDashImage.astro +4 -7
  362. package/src/components/Image.astro +14 -4
  363. package/src/components/InlinePortableTextEditor.tsx +48 -1
  364. package/src/components/JsonLdScript.astro +9 -0
  365. package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +16 -0
  366. package/src/database/migrations/046_media_usage_index.ts +115 -0
  367. package/src/database/migrations/047_restore_taxonomy_parent_index.ts +29 -0
  368. package/src/database/migrations/runner.ts +4 -0
  369. package/src/database/repositories/content.ts +8 -5
  370. package/src/database/repositories/media-usage.ts +444 -0
  371. package/src/database/repositories/media.ts +9 -1
  372. package/src/database/types.ts +39 -0
  373. package/src/emdash-runtime.ts +72 -25
  374. package/src/index.ts +3 -0
  375. package/src/loader.ts +110 -31
  376. package/src/media/enrich.ts +78 -0
  377. package/src/media/index.ts +1 -0
  378. package/src/media/local-runtime.ts +19 -0
  379. package/src/media/normalize.ts +20 -1
  380. package/src/media/placeholder.ts +29 -9
  381. package/src/media/types.ts +14 -0
  382. package/src/media/usage/extractor.ts +302 -0
  383. package/src/media/usage/types.ts +46 -0
  384. package/src/object-cache/index.ts +8 -2
  385. package/src/page/metadata.ts +23 -3
  386. package/src/plugin-types.ts +12 -0
  387. package/src/plugins/context.ts +8 -0
  388. package/src/plugins/define-plugin.ts +14 -5
  389. package/src/plugins/hooks.ts +66 -33
  390. package/src/plugins/index.ts +6 -0
  391. package/src/plugins/manager.ts +33 -0
  392. package/src/plugins/manifest-schema.ts +3 -0
  393. package/src/plugins/types.ts +41 -2
  394. package/src/schema/registry.ts +25 -0
  395. package/src/schema/types.ts +10 -0
  396. package/src/schema/zod-generator.ts +9 -1
  397. package/src/seed/apply.ts +7 -0
  398. package/src/taxonomies/index.ts +121 -18
  399. package/dist/api-d_0wi9fW.mjs.map +0 -1
  400. package/dist/apply-BE9UaWX0.mjs.map +0 -1
  401. package/dist/byline-fields-Ca56gaT6.d.mts.map +0 -1
  402. package/dist/content-PoVGKjp0.mjs.map +0 -1
  403. package/dist/context-Dvws41_A.mjs.map +0 -1
  404. package/dist/error-lq_fL-RS.mjs.map +0 -1
  405. package/dist/index-BroXT-vD.d.mts.map +0 -1
  406. package/dist/loader-BR2SgfEc.mjs.map +0 -1
  407. package/dist/manifest-schema-BWKnQYQF.mjs.map +0 -1
  408. package/dist/media-CTjJoJTh.mjs.map +0 -1
  409. package/dist/menus-GvRSpgIA.mjs.map +0 -1
  410. package/dist/normalize-ClbNIenm.mjs.map +0 -1
  411. package/dist/object-cache-SEb2IM4Z.mjs.map +0 -1
  412. package/dist/placeholder-B0s2mWu5.mjs.map +0 -1
  413. package/dist/placeholder-QoI4gEzP.d.mts.map +0 -1
  414. package/dist/redirects-x9eFPeyi.mjs.map +0 -1
  415. package/dist/registry-CJkCubOJ.mjs.map +0 -1
  416. package/dist/runner-BsquExlK.mjs.map +0 -1
  417. package/dist/seo-DCEF-AVr.mjs.map +0 -1
  418. package/dist/taxonomies-CurpLtNC.mjs.map +0 -1
  419. package/dist/taxonomies-DhLaNJji.mjs.map +0 -1
  420. package/dist/types-B-gIxFCE.d.mts.map +0 -1
  421. package/dist/version-C0JPFUG8.mjs +0 -7
  422. package/dist/zod-generator-xEHODMJf.mjs.map +0 -1
@@ -0,0 +1,115 @@
1
+ import type { Kysely } from "kysely";
2
+
3
+ import { currentTimestamp } from "../dialect-helpers.js";
4
+
5
+ /**
6
+ * Internal media usage projection tables.
7
+ *
8
+ * This migration is DDL-only by design: no backfill, no runtime media/indexing
9
+ * imports, and no content-table scans. Production rows are introduced by later
10
+ * phases once the central snapshot/indexer path is reviewed.
11
+ */
12
+ export async function up(db: Kysely<unknown>): Promise<void> {
13
+ await db.schema
14
+ .createTable("_emdash_media_usage_sources")
15
+ .ifNotExists()
16
+ .addColumn("source_key", "text", (c) => c.primaryKey())
17
+ .addColumn("source_type", "text", (c) => c.notNull())
18
+ .addColumn("collection_slug", "text")
19
+ .addColumn("content_id", "text")
20
+ .addColumn("source_variant", "text", (c) => c.notNull())
21
+ .addColumn("locale", "text")
22
+ .addColumn("translation_group", "text")
23
+ .addColumn("content_slug", "text")
24
+ .addColumn("content_title", "text")
25
+ .addColumn("content_status", "text")
26
+ .addColumn("content_scheduled_at", "text")
27
+ .addColumn("content_deleted_at", "text")
28
+ .addColumn("revision_id", "text")
29
+ .addColumn("current_generation", "text", (c) => c.notNull())
30
+ .addColumn("schema_version", "integer", (c) => c.notNull().defaultTo(1))
31
+ .addColumn("indexed_at", "text", (c) => c.notNull().defaultTo(currentTimestamp(db)))
32
+ .addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db)))
33
+ .addColumn("updated_at", "text", (c) => c.defaultTo(currentTimestamp(db)))
34
+ .execute();
35
+
36
+ await db.schema
37
+ .createIndex("idx__emdash_media_usage_sources_content")
38
+ .ifNotExists()
39
+ .on("_emdash_media_usage_sources")
40
+ .columns(["source_type", "collection_slug", "content_id"])
41
+ .execute();
42
+ await db.schema
43
+ .createIndex("idx__emdash_media_usage_sources_variant")
44
+ .ifNotExists()
45
+ .on("_emdash_media_usage_sources")
46
+ .columns(["source_type", "source_variant"])
47
+ .execute();
48
+ await db.schema
49
+ .createIndex("idx__emdash_media_usage_sources_locale")
50
+ .ifNotExists()
51
+ .on("_emdash_media_usage_sources")
52
+ .columns(["collection_slug", "locale"])
53
+ .execute();
54
+ await db.schema
55
+ .createIndex("idx__emdash_media_usage_sources_deleted")
56
+ .ifNotExists()
57
+ .on("_emdash_media_usage_sources")
58
+ .column("content_deleted_at")
59
+ .execute();
60
+ await db.schema
61
+ .createIndex("idx__emdash_media_usage_sources_translation_group")
62
+ .ifNotExists()
63
+ .on("_emdash_media_usage_sources")
64
+ .columns(["collection_slug", "translation_group"])
65
+ .execute();
66
+
67
+ await db.schema
68
+ .createTable("_emdash_media_usage")
69
+ .ifNotExists()
70
+ .addColumn("id", "text", (c) => c.primaryKey())
71
+ .addColumn("source_key", "text", (c) => c.notNull())
72
+ .addColumn("generation", "text", (c) => c.notNull())
73
+ .addColumn("field_slug", "text", (c) => c.notNull())
74
+ .addColumn("field_path", "text", (c) => c.notNull())
75
+ .addColumn("occurrence_index", "integer", (c) => c.notNull().defaultTo(0))
76
+ .addColumn("reference_type", "text", (c) => c.notNull())
77
+ .addColumn("media_id", "text")
78
+ .addColumn("provider", "text", (c) => c.notNull().defaultTo("local"))
79
+ .addColumn("provider_asset_id", "text", (c) => c.notNull())
80
+ .addColumn("media_kind", "text")
81
+ .addColumn("mime_type", "text")
82
+ .addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db)))
83
+ .execute();
84
+
85
+ await db.schema
86
+ .createIndex("idx__emdash_media_usage_unique_occurrence")
87
+ .ifNotExists()
88
+ .unique()
89
+ .on("_emdash_media_usage")
90
+ .columns(["source_key", "generation", "field_path", "occurrence_index"])
91
+ .execute();
92
+ await db.schema
93
+ .createIndex("idx__emdash_media_usage_media_id")
94
+ .ifNotExists()
95
+ .on("_emdash_media_usage")
96
+ .column("media_id")
97
+ .execute();
98
+ await db.schema
99
+ .createIndex("idx__emdash_media_usage_provider_asset")
100
+ .ifNotExists()
101
+ .on("_emdash_media_usage")
102
+ .columns(["provider", "provider_asset_id"])
103
+ .execute();
104
+ await db.schema
105
+ .createIndex("idx__emdash_media_usage_source_generation")
106
+ .ifNotExists()
107
+ .on("_emdash_media_usage")
108
+ .columns(["source_key", "generation"])
109
+ .execute();
110
+ }
111
+
112
+ export async function down(db: Kysely<unknown>): Promise<void> {
113
+ await db.schema.dropTable("_emdash_media_usage").ifExists().execute();
114
+ await db.schema.dropTable("_emdash_media_usage_sources").ifExists().execute();
115
+ }
@@ -0,0 +1,29 @@
1
+ import type { Kysely } from "kysely";
2
+
3
+ /**
4
+ * Restore `idx_taxonomies_parent` on `taxonomies(parent_id)` (#1665).
5
+ *
6
+ * Migration 015 created this index, but 036's SQLite table rebuild
7
+ * (`taxonomies_new` → drop → rename) dropped every index attached to the old
8
+ * table and only recreated `name`/`locale`/`translation_group`. The parent
9
+ * index — which backs hierarchical (parent/child) lookups — was silently lost
10
+ * on any install migrated through 036. The Postgres path in 036 alters the
11
+ * table in place and keeps its indexes, so it is unaffected; `ifNotExists`
12
+ * makes this a no-op there.
13
+ *
14
+ * Forward-only: 036 has already shipped, so existing installs only recover the
15
+ * index here. Fresh installs also get it in 036's rebuild for parity with the
16
+ * other rebuilt tables, making this a no-op for them.
17
+ */
18
+ export async function up(db: Kysely<unknown>): Promise<void> {
19
+ await db.schema
20
+ .createIndex("idx_taxonomies_parent")
21
+ .ifNotExists()
22
+ .on("taxonomies")
23
+ .column("parent_id")
24
+ .execute();
25
+ }
26
+
27
+ export async function down(db: Kysely<unknown>): Promise<void> {
28
+ await db.schema.dropIndex("idx_taxonomies_parent").ifExists().execute();
29
+ }
@@ -47,6 +47,8 @@ import * as m042 from "./042_byline_fields.js";
47
47
  import * as m043 from "./043_content_references.js";
48
48
  import * as m044 from "./044_comment_reactions.js";
49
49
  import * as m045 from "./045_taxonomy_parent_group.js";
50
+ import * as m046 from "./046_media_usage_index.js";
51
+ import * as m047 from "./047_restore_taxonomy_parent_index.js";
50
52
 
51
53
  const MIGRATIONS: Readonly<Record<string, Migration>> = Object.freeze({
52
54
  "001_initial": m001,
@@ -93,6 +95,8 @@ const MIGRATIONS: Readonly<Record<string, Migration>> = Object.freeze({
93
95
  "043_content_references": m043,
94
96
  "044_comment_reactions": m044,
95
97
  "045_taxonomy_parent_group": m045,
98
+ "046_media_usage_index": m046,
99
+ "047_restore_taxonomy_parent_index": m047,
96
100
  });
97
101
 
98
102
  /** Total number of registered migrations. Exported for use in tests. */
@@ -656,19 +656,22 @@ export class ContentRepository {
656
656
  /**
657
657
  * Restore content from trash
658
658
  */
659
- async restore(type: string, id: string): Promise<boolean> {
659
+ async restore(type: string, id: string): Promise<ContentItem | null> {
660
660
  const tableName = getTableName(type);
661
661
 
662
- const result = await sql`
662
+ const result = await sql<Record<string, unknown>>`
663
663
  UPDATE ${sql.ref(tableName)}
664
664
  SET deleted_at = NULL
665
665
  WHERE id = ${id}
666
666
  AND deleted_at IS NOT NULL
667
+ RETURNING *
667
668
  `.execute(this.db);
668
669
 
669
- const changed = (result.numAffectedRows ?? 0n) > 0n;
670
- if (changed) invalidateCollectionCache(type);
671
- return changed;
670
+ const restored = result.rows[0];
671
+ if (!restored) return null;
672
+
673
+ invalidateCollectionCache(type);
674
+ return this.mapRow(type, restored);
672
675
  }
673
676
 
674
677
  /**
@@ -0,0 +1,444 @@
1
+ import type { Kysely, Selectable, Transaction } from "kysely";
2
+ import { ulid } from "ulidx";
3
+
4
+ import type { MediaKind, MediaUsageReferenceType } from "../../media/usage/types.js";
5
+ import { chunks, SQL_BATCH_SIZE } from "../../utils/chunks.js";
6
+ import { withTransaction } from "../transaction.js";
7
+ import type { Database, MediaUsageSourceTable, MediaUsageTable } from "../types.js";
8
+
9
+ type DatabaseExecutor = Kysely<Database> | Transaction<Database>;
10
+
11
+ const OCCURRENCE_BIND_COLUMNS = 12;
12
+ const OCCURRENCE_INSERT_BATCH_SIZE = Math.max(
13
+ 1,
14
+ Math.floor(SQL_BATCH_SIZE / OCCURRENCE_BIND_COLUMNS),
15
+ );
16
+
17
+ export interface MediaUsageSourceInput {
18
+ sourceKey: string;
19
+ sourceType: string;
20
+ collectionSlug?: string | null;
21
+ contentId?: string | null;
22
+ sourceVariant: string;
23
+ locale?: string | null;
24
+ translationGroup?: string | null;
25
+ contentSlug?: string | null;
26
+ contentTitle?: string | null;
27
+ contentStatus?: string | null;
28
+ contentScheduledAt?: string | null;
29
+ contentDeletedAt?: string | null;
30
+ revisionId?: string | null;
31
+ schemaVersion?: number;
32
+ }
33
+
34
+ export interface MediaUsageOccurrenceInput {
35
+ fieldSlug: string;
36
+ fieldPath: string;
37
+ occurrenceIndex?: number;
38
+ referenceType: MediaUsageReferenceType;
39
+ mediaId: string | null;
40
+ provider: string;
41
+ providerAssetId: string;
42
+ mediaKind?: MediaKind | null;
43
+ mimeType?: string | null;
44
+ }
45
+
46
+ export interface MediaUsageSource {
47
+ sourceKey: string;
48
+ sourceType: string;
49
+ collectionSlug: string | null;
50
+ contentId: string | null;
51
+ sourceVariant: string;
52
+ locale: string | null;
53
+ translationGroup: string | null;
54
+ contentSlug: string | null;
55
+ contentTitle: string | null;
56
+ contentStatus: string | null;
57
+ contentScheduledAt: string | null;
58
+ contentDeletedAt: string | null;
59
+ revisionId: string | null;
60
+ currentGeneration: string;
61
+ schemaVersion: number;
62
+ indexedAt: string;
63
+ createdAt: string;
64
+ updatedAt: string;
65
+ }
66
+
67
+ export interface MediaUsageOccurrence {
68
+ id: string;
69
+ sourceKey: string;
70
+ generation: string;
71
+ fieldSlug: string;
72
+ fieldPath: string;
73
+ occurrenceIndex: number;
74
+ referenceType: string;
75
+ mediaId: string | null;
76
+ provider: string;
77
+ providerAssetId: string;
78
+ mediaKind: string | null;
79
+ mimeType: string | null;
80
+ createdAt: string;
81
+ }
82
+
83
+ export interface MediaUsageRecord {
84
+ source: MediaUsageSource;
85
+ occurrence: MediaUsageOccurrence;
86
+ }
87
+
88
+ interface JoinedUsageRow {
89
+ source_key: string;
90
+ source_type: string;
91
+ collection_slug: string | null;
92
+ content_id: string | null;
93
+ source_variant: string;
94
+ locale: string | null;
95
+ translation_group: string | null;
96
+ content_slug: string | null;
97
+ content_title: string | null;
98
+ content_status: string | null;
99
+ content_scheduled_at: string | null;
100
+ content_deleted_at: string | null;
101
+ revision_id: string | null;
102
+ current_generation: string;
103
+ schema_version: number;
104
+ indexed_at: string;
105
+ source_created_at: string;
106
+ source_updated_at: string;
107
+ occurrence_id: string;
108
+ generation: string;
109
+ field_slug: string;
110
+ field_path: string;
111
+ occurrence_index: number;
112
+ reference_type: string;
113
+ media_id: string | null;
114
+ provider: string;
115
+ provider_asset_id: string;
116
+ media_kind: string | null;
117
+ mime_type: string | null;
118
+ occurrence_created_at: string;
119
+ }
120
+
121
+ /** Persistence-only repository for the internal media usage projection tables. */
122
+ export class MediaUsageRepository {
123
+ constructor(private db: Kysely<Database>) {}
124
+
125
+ async replaceSource(
126
+ source: MediaUsageSourceInput,
127
+ occurrences: readonly MediaUsageOccurrenceInput[],
128
+ ): Promise<MediaUsageSource> {
129
+ const generation = ulid();
130
+ const now = new Date().toISOString();
131
+
132
+ await withTransaction(this.db, async (trx) => {
133
+ await this.insertOccurrences(trx, source.sourceKey, generation, occurrences);
134
+ await this.upsertSource(trx, source, generation, now);
135
+
136
+ try {
137
+ await this.deleteStaleGenerations(trx, source.sourceKey, generation);
138
+ } catch (error) {
139
+ console.error("[media-usage] failed to delete stale generations:", error);
140
+ }
141
+ });
142
+
143
+ const replaced = await this.findSource(source.sourceKey);
144
+ if (!replaced) {
145
+ throw new Error(`Media usage source ${source.sourceKey} was not persisted`);
146
+ }
147
+ return replaced;
148
+ }
149
+
150
+ async findSource(sourceKey: string): Promise<MediaUsageSource | null> {
151
+ const row = await this.db
152
+ .selectFrom("_emdash_media_usage_sources")
153
+ .selectAll()
154
+ .where("source_key", "=", sourceKey)
155
+ .executeTakeFirst();
156
+
157
+ return row ? rowToSource(row) : null;
158
+ }
159
+
160
+ async findCurrentUsageByMediaId(mediaId: string): Promise<MediaUsageRecord[]> {
161
+ const rows = await this.db
162
+ .selectFrom("_emdash_media_usage_sources as s")
163
+ .innerJoin("_emdash_media_usage as u", (join) =>
164
+ join
165
+ .onRef("u.source_key", "=", "s.source_key")
166
+ .onRef("u.generation", "=", "s.current_generation"),
167
+ )
168
+ .select(currentUsageSelect)
169
+ .where("u.media_id", "=", mediaId)
170
+ .orderBy("s.source_key", "asc")
171
+ .orderBy("u.field_path", "asc")
172
+ .orderBy("u.occurrence_index", "asc")
173
+ .execute();
174
+
175
+ return rows.map(rowToUsageRecord);
176
+ }
177
+
178
+ async findCurrentUsageByProviderAsset(
179
+ provider: string,
180
+ providerAssetId: string,
181
+ ): Promise<MediaUsageRecord[]> {
182
+ const rows = await this.db
183
+ .selectFrom("_emdash_media_usage_sources as s")
184
+ .innerJoin("_emdash_media_usage as u", (join) =>
185
+ join
186
+ .onRef("u.source_key", "=", "s.source_key")
187
+ .onRef("u.generation", "=", "s.current_generation"),
188
+ )
189
+ .select(currentUsageSelect)
190
+ .where("u.provider", "=", provider)
191
+ .where("u.provider_asset_id", "=", providerAssetId)
192
+ .orderBy("s.source_key", "asc")
193
+ .orderBy("u.field_path", "asc")
194
+ .orderBy("u.occurrence_index", "asc")
195
+ .execute();
196
+
197
+ return rows.map(rowToUsageRecord);
198
+ }
199
+
200
+ async deleteSource(sourceKey: string): Promise<number> {
201
+ return withTransaction(this.db, async (trx) => {
202
+ await trx.deleteFrom("_emdash_media_usage").where("source_key", "=", sourceKey).execute();
203
+ const result = await trx
204
+ .deleteFrom("_emdash_media_usage_sources")
205
+ .where("source_key", "=", sourceKey)
206
+ .executeTakeFirst();
207
+ return Number(result.numDeletedRows ?? 0);
208
+ });
209
+ }
210
+
211
+ async deleteContentSources(collectionSlug: string, contentId: string): Promise<number> {
212
+ const sourceRows = await this.db
213
+ .selectFrom("_emdash_media_usage_sources")
214
+ .select("source_key")
215
+ .where("source_type", "=", "content")
216
+ .where("collection_slug", "=", collectionSlug)
217
+ .where("content_id", "=", contentId)
218
+ .execute();
219
+ const sourceKeys = sourceRows.map((row) => row.source_key);
220
+ if (sourceKeys.length === 0) return 0;
221
+
222
+ return withTransaction(this.db, async (trx) => {
223
+ let deleted = 0;
224
+ for (const sourceKeyBatch of chunks(sourceKeys, SQL_BATCH_SIZE)) {
225
+ await trx
226
+ .deleteFrom("_emdash_media_usage")
227
+ .where("source_key", "in", sourceKeyBatch)
228
+ .execute();
229
+ const result = await trx
230
+ .deleteFrom("_emdash_media_usage_sources")
231
+ .where("source_key", "in", sourceKeyBatch)
232
+ .executeTakeFirst();
233
+ deleted += Number(result.numDeletedRows ?? 0);
234
+ }
235
+ return deleted;
236
+ });
237
+ }
238
+
239
+ private async insertOccurrences(
240
+ db: DatabaseExecutor,
241
+ sourceKey: string,
242
+ generation: string,
243
+ occurrences: readonly MediaUsageOccurrenceInput[],
244
+ ): Promise<void> {
245
+ if (occurrences.length === 0) return;
246
+
247
+ const rows = occurrences.map((occurrence) => ({
248
+ id: ulid(),
249
+ source_key: sourceKey,
250
+ generation,
251
+ field_slug: occurrence.fieldSlug,
252
+ field_path: occurrence.fieldPath,
253
+ occurrence_index: occurrence.occurrenceIndex ?? 0,
254
+ reference_type: occurrence.referenceType,
255
+ media_id: occurrence.mediaId,
256
+ provider: occurrence.provider,
257
+ provider_asset_id: occurrence.providerAssetId,
258
+ media_kind: occurrence.mediaKind ?? null,
259
+ mime_type: occurrence.mimeType ?? null,
260
+ }));
261
+
262
+ for (const rowBatch of chunks(rows, OCCURRENCE_INSERT_BATCH_SIZE)) {
263
+ await db.insertInto("_emdash_media_usage").values(rowBatch).execute();
264
+ }
265
+ }
266
+
267
+ private async upsertSource(
268
+ db: DatabaseExecutor,
269
+ source: MediaUsageSourceInput,
270
+ generation: string,
271
+ now: string,
272
+ ): Promise<void> {
273
+ const row = {
274
+ source_key: source.sourceKey,
275
+ source_type: source.sourceType,
276
+ collection_slug: source.collectionSlug ?? null,
277
+ content_id: source.contentId ?? null,
278
+ source_variant: source.sourceVariant,
279
+ locale: source.locale ?? null,
280
+ translation_group: source.translationGroup ?? null,
281
+ content_slug: source.contentSlug ?? null,
282
+ content_title: source.contentTitle ?? null,
283
+ content_status: source.contentStatus ?? null,
284
+ content_scheduled_at: source.contentScheduledAt ?? null,
285
+ content_deleted_at: source.contentDeletedAt ?? null,
286
+ revision_id: source.revisionId ?? null,
287
+ current_generation: generation,
288
+ schema_version: source.schemaVersion ?? 1,
289
+ indexed_at: now,
290
+ updated_at: now,
291
+ };
292
+
293
+ await db
294
+ .insertInto("_emdash_media_usage_sources")
295
+ .values(row)
296
+ .onConflict((oc) =>
297
+ oc.column("source_key").doUpdateSet({
298
+ source_type: row.source_type,
299
+ collection_slug: row.collection_slug,
300
+ content_id: row.content_id,
301
+ source_variant: row.source_variant,
302
+ locale: row.locale,
303
+ translation_group: row.translation_group,
304
+ content_slug: row.content_slug,
305
+ content_title: row.content_title,
306
+ content_status: row.content_status,
307
+ content_scheduled_at: row.content_scheduled_at,
308
+ content_deleted_at: row.content_deleted_at,
309
+ revision_id: row.revision_id,
310
+ current_generation: row.current_generation,
311
+ schema_version: row.schema_version,
312
+ indexed_at: row.indexed_at,
313
+ updated_at: row.updated_at,
314
+ }),
315
+ )
316
+ .execute();
317
+ }
318
+
319
+ private async deleteStaleGenerations(
320
+ db: DatabaseExecutor,
321
+ sourceKey: string,
322
+ currentGeneration: string,
323
+ ): Promise<void> {
324
+ await db
325
+ .deleteFrom("_emdash_media_usage")
326
+ .where("source_key", "=", sourceKey)
327
+ .where("generation", "!=", currentGeneration)
328
+ .execute();
329
+ }
330
+ }
331
+
332
+ const currentUsageSelect = [
333
+ "s.source_key as source_key",
334
+ "s.source_type as source_type",
335
+ "s.collection_slug as collection_slug",
336
+ "s.content_id as content_id",
337
+ "s.source_variant as source_variant",
338
+ "s.locale as locale",
339
+ "s.translation_group as translation_group",
340
+ "s.content_slug as content_slug",
341
+ "s.content_title as content_title",
342
+ "s.content_status as content_status",
343
+ "s.content_scheduled_at as content_scheduled_at",
344
+ "s.content_deleted_at as content_deleted_at",
345
+ "s.revision_id as revision_id",
346
+ "s.current_generation as current_generation",
347
+ "s.schema_version as schema_version",
348
+ "s.indexed_at as indexed_at",
349
+ "s.created_at as source_created_at",
350
+ "s.updated_at as source_updated_at",
351
+ "u.id as occurrence_id",
352
+ "u.generation as generation",
353
+ "u.field_slug as field_slug",
354
+ "u.field_path as field_path",
355
+ "u.occurrence_index as occurrence_index",
356
+ "u.reference_type as reference_type",
357
+ "u.media_id as media_id",
358
+ "u.provider as provider",
359
+ "u.provider_asset_id as provider_asset_id",
360
+ "u.media_kind as media_kind",
361
+ "u.mime_type as mime_type",
362
+ "u.created_at as occurrence_created_at",
363
+ ] as const;
364
+
365
+ function rowToSource(row: Selectable<MediaUsageSourceTable>): MediaUsageSource {
366
+ return {
367
+ sourceKey: row.source_key,
368
+ sourceType: row.source_type,
369
+ collectionSlug: row.collection_slug,
370
+ contentId: row.content_id,
371
+ sourceVariant: row.source_variant,
372
+ locale: row.locale,
373
+ translationGroup: row.translation_group,
374
+ contentSlug: row.content_slug,
375
+ contentTitle: row.content_title,
376
+ contentStatus: row.content_status,
377
+ contentScheduledAt: row.content_scheduled_at,
378
+ contentDeletedAt: row.content_deleted_at,
379
+ revisionId: row.revision_id,
380
+ currentGeneration: row.current_generation,
381
+ schemaVersion: Number(row.schema_version),
382
+ indexedAt: row.indexed_at,
383
+ createdAt: row.created_at,
384
+ updatedAt: row.updated_at,
385
+ };
386
+ }
387
+
388
+ function rowToOccurrence(row: Selectable<MediaUsageTable>): MediaUsageOccurrence {
389
+ return {
390
+ id: row.id,
391
+ sourceKey: row.source_key,
392
+ generation: row.generation,
393
+ fieldSlug: row.field_slug,
394
+ fieldPath: row.field_path,
395
+ occurrenceIndex: Number(row.occurrence_index),
396
+ referenceType: row.reference_type,
397
+ mediaId: row.media_id,
398
+ provider: row.provider,
399
+ providerAssetId: row.provider_asset_id,
400
+ mediaKind: row.media_kind,
401
+ mimeType: row.mime_type,
402
+ createdAt: row.created_at,
403
+ };
404
+ }
405
+
406
+ function rowToUsageRecord(row: JoinedUsageRow): MediaUsageRecord {
407
+ return {
408
+ source: rowToSource({
409
+ source_key: row.source_key,
410
+ source_type: row.source_type,
411
+ collection_slug: row.collection_slug,
412
+ content_id: row.content_id,
413
+ source_variant: row.source_variant,
414
+ locale: row.locale,
415
+ translation_group: row.translation_group,
416
+ content_slug: row.content_slug,
417
+ content_title: row.content_title,
418
+ content_status: row.content_status,
419
+ content_scheduled_at: row.content_scheduled_at,
420
+ content_deleted_at: row.content_deleted_at,
421
+ revision_id: row.revision_id,
422
+ current_generation: row.current_generation,
423
+ schema_version: row.schema_version,
424
+ indexed_at: row.indexed_at,
425
+ created_at: row.source_created_at,
426
+ updated_at: row.source_updated_at,
427
+ }),
428
+ occurrence: rowToOccurrence({
429
+ id: row.occurrence_id,
430
+ source_key: row.source_key,
431
+ generation: row.generation,
432
+ field_slug: row.field_slug,
433
+ field_path: row.field_path,
434
+ occurrence_index: row.occurrence_index,
435
+ reference_type: row.reference_type,
436
+ media_id: row.media_id,
437
+ provider: row.provider,
438
+ provider_asset_id: row.provider_asset_id,
439
+ media_kind: row.media_kind,
440
+ mime_type: row.mime_type,
441
+ created_at: row.occurrence_created_at,
442
+ }),
443
+ };
444
+ }
@@ -143,7 +143,13 @@ export class MediaRepository {
143
143
  */
144
144
  async confirmUpload(
145
145
  id: string,
146
- metadata?: { width?: number; height?: number; size?: number },
146
+ metadata?: {
147
+ width?: number;
148
+ height?: number;
149
+ size?: number;
150
+ blurhash?: string;
151
+ dominantColor?: string;
152
+ },
147
153
  ): Promise<MediaItem | null> {
148
154
  const existing = await this.findById(id);
149
155
  if (!existing) {
@@ -156,6 +162,8 @@ export class MediaRepository {
156
162
  if (metadata?.width !== undefined) updates.width = metadata.width;
157
163
  if (metadata?.height !== undefined) updates.height = metadata.height;
158
164
  if (metadata?.size !== undefined) updates.size = metadata.size;
165
+ if (metadata?.blurhash !== undefined) updates.blurhash = metadata.blurhash;
166
+ if (metadata?.dominantColor !== undefined) updates.dominant_color = metadata.dominantColor;
159
167
 
160
168
  await this.db.updateTable("media").set(updates).where("id", "=", id).execute();
161
169