emdash 0.24.0 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. package/dist/api/route-utils.d.mts +2 -2
  2. package/dist/api/route-utils.mjs +9 -8
  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-D2_sbavn.mjs} +16 -12
  7. package/dist/api-D2_sbavn.mjs.map +1 -0
  8. package/dist/{apply-BE9UaWX0.mjs → apply-Bk2mAuLn.mjs} +11 -10
  9. package/dist/apply-Bk2mAuLn.mjs.map +1 -0
  10. package/dist/astro/index.d.mts +6 -6
  11. package/dist/astro/index.mjs +2 -2
  12. package/dist/astro/middleware/auth.d.mts +5 -5
  13. package/dist/astro/middleware/auth.mjs +1 -1
  14. package/dist/astro/middleware/redirect.mjs +2 -2
  15. package/dist/astro/middleware.d.mts.map +1 -1
  16. package/dist/astro/middleware.mjs +66 -37
  17. package/dist/astro/middleware.mjs.map +1 -1
  18. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +4 -4
  19. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +4 -4
  20. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +1 -1
  21. package/dist/astro/routes/api/admin/api-tokens/index.mjs +2 -2
  22. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +2 -2
  23. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +5 -5
  24. package/dist/astro/routes/api/admin/byline-fields/index.mjs +5 -5
  25. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +5 -5
  26. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +7 -7
  27. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +7 -7
  28. package/dist/astro/routes/api/admin/bylines/index.mjs +7 -7
  29. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +5 -5
  30. package/dist/astro/routes/api/admin/comments/_id_.mjs +2 -2
  31. package/dist/astro/routes/api/admin/comments/bulk.mjs +5 -5
  32. package/dist/astro/routes/api/admin/comments/counts.mjs +2 -2
  33. package/dist/astro/routes/api/admin/comments/index.mjs +5 -5
  34. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +3 -3
  35. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +2 -2
  36. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +2 -2
  37. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +2 -2
  38. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +20 -20
  39. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +20 -20
  40. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +20 -20
  41. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +20 -20
  42. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +20 -20
  43. package/dist/astro/routes/api/admin/plugins/index.mjs +20 -20
  44. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
  45. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +20 -20
  46. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +20 -20
  47. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +20 -20
  48. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +20 -20
  49. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +21 -21
  50. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +20 -20
  51. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +21 -21
  52. package/dist/astro/routes/api/admin/plugins/updates.mjs +20 -20
  53. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +20 -20
  54. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
  55. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +20 -20
  56. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +1 -1
  57. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
  58. package/dist/astro/routes/api/admin/users/_id_/index.mjs +4 -4
  59. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +1 -1
  60. package/dist/astro/routes/api/admin/users/index.mjs +4 -4
  61. package/dist/astro/routes/api/auth/dev-bypass.mjs +1 -1
  62. package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
  63. package/dist/astro/routes/api/auth/invite/complete.mjs +4 -4
  64. package/dist/astro/routes/api/auth/invite/index.mjs +4 -4
  65. package/dist/astro/routes/api/auth/invite/register-options.mjs +4 -4
  66. package/dist/astro/routes/api/auth/logout.mjs +1 -1
  67. package/dist/astro/routes/api/auth/magic-link/send.mjs +5 -5
  68. package/dist/astro/routes/api/auth/magic-link/verify.mjs +1 -1
  69. package/dist/astro/routes/api/auth/me.mjs +4 -4
  70. package/dist/astro/routes/api/auth/passkey/_id_.mjs +4 -4
  71. package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
  72. package/dist/astro/routes/api/auth/passkey/options.mjs +5 -5
  73. package/dist/astro/routes/api/auth/passkey/register/options.mjs +4 -4
  74. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +4 -4
  75. package/dist/astro/routes/api/auth/passkey/verify.mjs +4 -4
  76. package/dist/astro/routes/api/auth/signup/complete.mjs +4 -4
  77. package/dist/astro/routes/api/auth/signup/request.mjs +5 -5
  78. package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
  79. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +4 -4
  80. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +4 -4
  81. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
  82. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +2 -2
  83. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +2 -2
  84. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
  85. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +5 -5
  86. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +5 -5
  87. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +2 -2
  88. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
  89. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +5 -5
  90. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +8 -8
  91. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
  92. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +2 -2
  93. package/dist/astro/routes/api/content/_collection_/_id_.mjs +5 -5
  94. package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
  95. package/dist/astro/routes/api/content/_collection_/index.mjs +5 -5
  96. package/dist/astro/routes/api/content/_collection_/trash.mjs +5 -5
  97. package/dist/astro/routes/api/dashboard.mjs +5 -5
  98. package/dist/astro/routes/api/dev/emails.mjs +1 -1
  99. package/dist/astro/routes/api/import/probe.mjs +5 -5
  100. package/dist/astro/routes/api/import/wordpress/analyze.mjs +2 -2
  101. package/dist/astro/routes/api/import/wordpress/execute.d.mts +5 -5
  102. package/dist/astro/routes/api/import/wordpress/execute.mjs +4 -4
  103. package/dist/astro/routes/api/import/wordpress/media.d.mts +13 -1
  104. package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
  105. package/dist/astro/routes/api/import/wordpress/media.mjs +12 -8
  106. package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
  107. package/dist/astro/routes/api/import/wordpress/prepare.mjs +6 -6
  108. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +7 -7
  109. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +5 -5
  110. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +5 -5
  111. package/dist/astro/routes/api/manifest.mjs +2 -2
  112. package/dist/astro/routes/api/mcp.mjs +20 -20
  113. package/dist/astro/routes/api/media/_id_/confirm.d.mts.map +1 -1
  114. package/dist/astro/routes/api/media/_id_/confirm.mjs +43 -7
  115. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  116. package/dist/astro/routes/api/media/_id_.mjs +5 -5
  117. package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
  118. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
  119. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
  120. package/dist/astro/routes/api/media/providers/index.mjs +2 -2
  121. package/dist/astro/routes/api/media/upload-url.mjs +7 -7
  122. package/dist/astro/routes/api/media.mjs +21 -20
  123. package/dist/astro/routes/api/media.mjs.map +1 -1
  124. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +5 -5
  125. package/dist/astro/routes/api/menus/_name_/items.mjs +5 -5
  126. package/dist/astro/routes/api/menus/_name_/reorder.mjs +5 -5
  127. package/dist/astro/routes/api/menus/_name_/translations.mjs +5 -5
  128. package/dist/astro/routes/api/menus/_name_.mjs +5 -5
  129. package/dist/astro/routes/api/menus/index.mjs +5 -5
  130. package/dist/astro/routes/api/oauth/device/authorize.mjs +2 -2
  131. package/dist/astro/routes/api/oauth/device/code.mjs +3 -3
  132. package/dist/astro/routes/api/oauth/device/token.mjs +3 -3
  133. package/dist/astro/routes/api/oauth/register.mjs +1 -1
  134. package/dist/astro/routes/api/oauth/token/refresh.mjs +2 -2
  135. package/dist/astro/routes/api/oauth/token/revoke.mjs +2 -2
  136. package/dist/astro/routes/api/oauth/token.mjs +1 -1
  137. package/dist/astro/routes/api/openapi.json.mjs +2 -2
  138. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +2 -2
  139. package/dist/astro/routes/api/redirects/404s/index.mjs +5 -5
  140. package/dist/astro/routes/api/redirects/404s/summary.mjs +5 -5
  141. package/dist/astro/routes/api/redirects/_id_.mjs +6 -6
  142. package/dist/astro/routes/api/redirects/index.mjs +6 -6
  143. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
  144. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +2 -2
  145. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +20 -20
  146. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +20 -20
  147. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +20 -20
  148. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +20 -20
  149. package/dist/astro/routes/api/schema/collections/index.mjs +20 -20
  150. package/dist/astro/routes/api/schema/index.mjs +3 -3
  151. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +20 -20
  152. package/dist/astro/routes/api/schema/orphans/index.mjs +20 -20
  153. package/dist/astro/routes/api/search/enable.mjs +7 -7
  154. package/dist/astro/routes/api/search/index.mjs +6 -6
  155. package/dist/astro/routes/api/search/rebuild.mjs +7 -7
  156. package/dist/astro/routes/api/search/stats.mjs +4 -4
  157. package/dist/astro/routes/api/search/suggest.mjs +6 -6
  158. package/dist/astro/routes/api/sections/_slug_.mjs +7 -7
  159. package/dist/astro/routes/api/sections/index.mjs +7 -7
  160. package/dist/astro/routes/api/settings/email.mjs +3 -3
  161. package/dist/astro/routes/api/settings.mjs +9 -9
  162. package/dist/astro/routes/api/setup/admin-verify.mjs +4 -4
  163. package/dist/astro/routes/api/setup/admin.mjs +4 -4
  164. package/dist/astro/routes/api/setup/dev-bypass.mjs +7 -7
  165. package/dist/astro/routes/api/setup/dev-reset.mjs +1 -1
  166. package/dist/astro/routes/api/setup/index.mjs +10 -10
  167. package/dist/astro/routes/api/setup/status.mjs +1 -1
  168. package/dist/astro/routes/api/snapshot.mjs +2 -2
  169. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +8 -8
  170. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +8 -8
  171. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +8 -8
  172. package/dist/astro/routes/api/taxonomies/index.mjs +8 -8
  173. package/dist/astro/routes/api/themes/preview.mjs +2 -2
  174. package/dist/astro/routes/api/typegen.mjs +3 -3
  175. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +5 -5
  176. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +7 -7
  177. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +7 -7
  178. package/dist/astro/routes/api/widget-areas/_name_.mjs +4 -4
  179. package/dist/astro/routes/api/widget-areas/index.mjs +7 -7
  180. package/dist/astro/routes/api/widget-components.mjs +1 -1
  181. package/dist/astro/routes/robots.txt.mjs +3 -3
  182. package/dist/astro/routes/sitemap-_collection_.xml.mjs +4 -4
  183. package/dist/astro/routes/sitemap.xml.mjs +4 -4
  184. package/dist/astro/types.d.mts +7 -7
  185. package/dist/{authorize-B__u-iZJ.mjs → authorize-CuxZ0i2t.mjs} +2 -2
  186. package/dist/{authorize-B__u-iZJ.mjs.map → authorize-CuxZ0i2t.mjs.map} +1 -1
  187. package/dist/{byline-fields-RFPkDLRw.d.mts → byline-fields-KD3JFRuH.d.mts} +19 -1
  188. package/dist/byline-fields-KD3JFRuH.d.mts.map +1 -0
  189. package/dist/{byline-fields-B-KXkg5R.mjs → byline-fields-eyJMblSr.mjs} +2 -2
  190. package/dist/{byline-fields-B-KXkg5R.mjs.map → byline-fields-eyJMblSr.mjs.map} +1 -1
  191. package/dist/{bylines-BMtJnPnG.mjs → bylines-CPc_q2Vd.mjs} +4 -4
  192. package/dist/{bylines-BMtJnPnG.mjs.map → bylines-CPc_q2Vd.mjs.map} +1 -1
  193. package/dist/{cache-BUJe2w6Z.mjs → cache-C35VWD5D.mjs} +1 -1
  194. package/dist/{cache-BUJe2w6Z.mjs.map → cache-C35VWD5D.mjs.map} +1 -1
  195. package/dist/cli/index.mjs +8 -8
  196. package/dist/{content-PoVGKjp0.mjs → content-B43-YrIq.mjs} +7 -5
  197. package/dist/content-B43-YrIq.mjs.map +1 -0
  198. package/dist/{context-Dvws41_A.mjs → context-BW3N7wyt.mjs} +12 -6
  199. package/dist/context-BW3N7wyt.mjs.map +1 -0
  200. package/dist/{dashboard-DsYU8JKS.mjs → dashboard-FIe0S512.mjs} +3 -3
  201. package/dist/{dashboard-DsYU8JKS.mjs.map → dashboard-FIe0S512.mjs.map} +1 -1
  202. package/dist/{db-errors-UXB1CK42.mjs → db-errors-CK46D-ly.mjs} +1 -1
  203. package/dist/{db-errors-UXB1CK42.mjs.map → db-errors-CK46D-ly.mjs.map} +1 -1
  204. package/dist/{placeholder-B0s2mWu5.mjs → enrich-BQ0mxJRs.mjs} +61 -10
  205. package/dist/enrich-BQ0mxJRs.mjs.map +1 -0
  206. package/dist/{error-lq_fL-RS.mjs → error-DXnK2wF4.mjs} +2 -1
  207. package/dist/error-DXnK2wF4.mjs.map +1 -0
  208. package/dist/{index-BroXT-vD.d.mts → index-Cz6_JBL3.d.mts} +21 -8
  209. package/dist/index-Cz6_JBL3.d.mts.map +1 -0
  210. package/dist/index.d.mts +8 -8
  211. package/dist/index.mjs +28 -28
  212. package/dist/{loader-BR2SgfEc.mjs → loader-DrpofRon.mjs} +2 -2
  213. package/dist/{loader-BR2SgfEc.mjs.map → loader-DrpofRon.mjs.map} +1 -1
  214. package/dist/{manifest-schema-BWKnQYQF.mjs → manifest-schema-BOfPXse0.mjs} +2 -1
  215. package/dist/manifest-schema-BOfPXse0.mjs.map +1 -0
  216. package/dist/media/image-endpoint.mjs +1 -1
  217. package/dist/media/index.d.mts +45 -2
  218. package/dist/media/index.d.mts.map +1 -1
  219. package/dist/media/index.mjs +5 -3
  220. package/dist/media/index.mjs.map +1 -1
  221. package/dist/media/local-runtime.d.mts +6 -6
  222. package/dist/media/local-runtime.d.mts.map +1 -1
  223. package/dist/media/local-runtime.mjs +15 -3
  224. package/dist/media/local-runtime.mjs.map +1 -1
  225. package/dist/{media-CTjJoJTh.mjs → media-BjKxjzk8.mjs} +3 -1
  226. package/dist/media-BjKxjzk8.mjs.map +1 -0
  227. package/dist/{media-allowlist-B77I5_bw.mjs → media-allowlist-Du8t1u6a.mjs} +2 -2
  228. package/dist/{media-allowlist-B77I5_bw.mjs.map → media-allowlist-Du8t1u6a.mjs.map} +1 -1
  229. package/dist/{menus-GvRSpgIA.mjs → menus-CxqJ-z-N.mjs} +46 -4
  230. package/dist/menus-CxqJ-z-N.mjs.map +1 -0
  231. package/dist/{mime-BTzvzJ8M.mjs → mime-CCEzze7W.mjs} +1 -1
  232. package/dist/{mime-BTzvzJ8M.mjs.map → mime-CCEzze7W.mjs.map} +1 -1
  233. package/dist/{normalize-ClbNIenm.mjs → normalize-CeglrBT9.mjs} +11 -2
  234. package/dist/normalize-CeglrBT9.mjs.map +1 -0
  235. package/dist/{options-CqECG0gM.d.mts → options-BvClpP5Q.d.mts} +2 -2
  236. package/dist/{options-CqECG0gM.d.mts.map → options-BvClpP5Q.d.mts.map} +1 -1
  237. package/dist/page/index.d.mts +1 -1
  238. package/dist/{parse-DHI873qU.mjs → parse-DqMWHQ8x.mjs} +2 -2
  239. package/dist/{parse-DHI873qU.mjs.map → parse-DqMWHQ8x.mjs.map} +1 -1
  240. package/dist/{placeholder-QoI4gEzP.d.mts → placeholder-Cuce9U-m.d.mts} +20 -5
  241. package/dist/placeholder-Cuce9U-m.d.mts.map +1 -0
  242. package/dist/plugin-types.d.mts +2 -1
  243. package/dist/plugin-types.d.mts.map +1 -1
  244. package/dist/plugin-utils.d.mts +5 -5
  245. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  246. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  247. package/dist/{query-DZdlS6Cu.mjs → query-34pfm02Q.mjs} +11 -11
  248. package/dist/{query-DZdlS6Cu.mjs.map → query-34pfm02Q.mjs.map} +1 -1
  249. package/dist/{rate-limit-D1Y6VFVP.mjs → rate-limit-CBQK4ygJ.mjs} +2 -2
  250. package/dist/{rate-limit-D1Y6VFVP.mjs.map → rate-limit-CBQK4ygJ.mjs.map} +1 -1
  251. package/dist/{redirects-x9eFPeyi.mjs → redirects-Dcbuisoj.mjs} +2 -1
  252. package/dist/redirects-Dcbuisoj.mjs.map +1 -0
  253. package/dist/{registry-CJkCubOJ.mjs → registry-3aOAS4My.mjs} +11 -1
  254. package/dist/registry-3aOAS4My.mjs.map +1 -0
  255. package/dist/runtime.d.mts +6 -6
  256. package/dist/runtime.mjs +1 -1
  257. package/dist/{schema-DMjXuvNU.mjs → schema-BGwx8AQY.mjs} +3 -3
  258. package/dist/{schema-DMjXuvNU.mjs.map → schema-BGwx8AQY.mjs.map} +1 -1
  259. package/dist/{search-D1wKk3lc.mjs → search-Gl747zMP.mjs} +2 -2
  260. package/dist/{search-D1wKk3lc.mjs.map → search-Gl747zMP.mjs.map} +1 -1
  261. package/dist/{sections-CCMic8dB.mjs → sections-C-umuuLj.mjs} +2 -2
  262. package/dist/{sections-CCMic8dB.mjs.map → sections-C-umuuLj.mjs.map} +1 -1
  263. package/dist/seed/index.d.mts +1 -1
  264. package/dist/seed/index.mjs +6 -6
  265. package/dist/seo/index.d.mts +1 -1
  266. package/dist/{seo-3QrZ1EPT.mjs → seo-BNzkiK_W.mjs} +1 -1
  267. package/dist/{seo-3QrZ1EPT.mjs.map → seo-BNzkiK_W.mjs.map} +1 -1
  268. package/dist/{seo-DCEF-AVr.mjs → seo-BkhuuaaE.mjs} +25 -3
  269. package/dist/seo-BkhuuaaE.mjs.map +1 -0
  270. package/dist/{settings-D9rvaLuY.mjs → settings-C4tXRYiG.mjs} +3 -3
  271. package/dist/{settings-D9rvaLuY.mjs.map → settings-C4tXRYiG.mjs.map} +1 -1
  272. package/dist/{settings-C3Yi5Co3.mjs → settings-fPSR6dJa.mjs} +2 -2
  273. package/dist/{settings-C3Yi5Co3.mjs.map → settings-fPSR6dJa.mjs.map} +1 -1
  274. package/dist/{taxonomies-DhLaNJji.mjs → taxonomies-DbAtL6m4.mjs} +2 -2
  275. package/dist/{taxonomies-DhLaNJji.mjs.map → taxonomies-DbAtL6m4.mjs.map} +1 -1
  276. package/dist/{taxonomies-CurpLtNC.mjs → taxonomies-n8w72lLQ.mjs} +4 -4
  277. package/dist/{taxonomies-CurpLtNC.mjs.map → taxonomies-n8w72lLQ.mjs.map} +1 -1
  278. package/dist/{types-CTLJYMhN.d.mts → types-CNlaBFzx.d.mts} +11 -1
  279. package/dist/{types-CTLJYMhN.d.mts.map → types-CNlaBFzx.d.mts.map} +1 -1
  280. package/dist/{types-BWh2MjmA.d.mts → types-ETmO_jQr.d.mts} +2 -2
  281. package/dist/{types-BWh2MjmA.d.mts.map → types-ETmO_jQr.d.mts.map} +1 -1
  282. package/dist/{types-TTx8uP7V.d.mts → types-h2Cwbzi2.d.mts} +6 -3
  283. package/dist/{types-TTx8uP7V.d.mts.map → types-h2Cwbzi2.d.mts.map} +1 -1
  284. package/dist/types-tM44hEcf.mjs.map +1 -1
  285. package/dist/{validate-DOFrIb-c.d.mts → validate-Va_An3qb.d.mts} +2 -2
  286. package/dist/{validate-DOFrIb-c.d.mts.map → validate-Va_An3qb.d.mts.map} +1 -1
  287. package/dist/{validation-CY1gk4Ar.mjs → validation-B6DKPTtU.mjs} +3 -3
  288. package/dist/{validation-CY1gk4Ar.mjs.map → validation-B6DKPTtU.mjs.map} +1 -1
  289. package/dist/version-DHdCA24u.mjs +7 -0
  290. package/dist/{version-B5nHxI5Y.mjs.map → version-DHdCA24u.mjs.map} +1 -1
  291. package/dist/{widgets-Dri0spI4.mjs → widgets-C2RbFN0G.mjs} +2 -2
  292. package/dist/{widgets-Dri0spI4.mjs.map → widgets-C2RbFN0G.mjs.map} +1 -1
  293. package/package.json +5 -5
  294. package/src/api/errors.ts +1 -0
  295. package/src/api/handlers/content.ts +4 -4
  296. package/src/api/handlers/seo.ts +30 -2
  297. package/src/api/schemas/schema.ts +1 -0
  298. package/src/astro/middleware.ts +14 -1
  299. package/src/astro/routes/api/import/wordpress/media.ts +9 -3
  300. package/src/astro/routes/api/media/[id]/confirm.ts +62 -2
  301. package/src/astro/routes/api/media.ts +17 -19
  302. package/src/components/EmDashImage.astro +4 -7
  303. package/src/components/Image.astro +14 -4
  304. package/src/components/InlinePortableTextEditor.tsx +48 -1
  305. package/src/database/repositories/content.ts +8 -5
  306. package/src/database/repositories/media.ts +9 -1
  307. package/src/emdash-runtime.ts +39 -1
  308. package/src/media/enrich.ts +78 -0
  309. package/src/media/index.ts +1 -0
  310. package/src/media/local-runtime.ts +19 -0
  311. package/src/media/normalize.ts +20 -1
  312. package/src/media/placeholder.ts +29 -9
  313. package/src/media/types.ts +14 -0
  314. package/src/plugin-types.ts +2 -0
  315. package/src/plugins/context.ts +8 -0
  316. package/src/plugins/hooks.ts +45 -0
  317. package/src/plugins/index.ts +1 -0
  318. package/src/plugins/manager.ts +11 -0
  319. package/src/plugins/manifest-schema.ts +1 -0
  320. package/src/plugins/types.ts +7 -0
  321. package/src/schema/registry.ts +25 -0
  322. package/src/schema/types.ts +10 -0
  323. package/src/seed/apply.ts +1 -0
  324. package/dist/api-d_0wi9fW.mjs.map +0 -1
  325. package/dist/apply-BE9UaWX0.mjs.map +0 -1
  326. package/dist/byline-fields-RFPkDLRw.d.mts.map +0 -1
  327. package/dist/content-PoVGKjp0.mjs.map +0 -1
  328. package/dist/context-Dvws41_A.mjs.map +0 -1
  329. package/dist/error-lq_fL-RS.mjs.map +0 -1
  330. package/dist/index-BroXT-vD.d.mts.map +0 -1
  331. package/dist/manifest-schema-BWKnQYQF.mjs.map +0 -1
  332. package/dist/media-CTjJoJTh.mjs.map +0 -1
  333. package/dist/menus-GvRSpgIA.mjs.map +0 -1
  334. package/dist/normalize-ClbNIenm.mjs.map +0 -1
  335. package/dist/placeholder-B0s2mWu5.mjs.map +0 -1
  336. package/dist/placeholder-QoI4gEzP.d.mts.map +0 -1
  337. package/dist/redirects-x9eFPeyi.mjs.map +0 -1
  338. package/dist/registry-CJkCubOJ.mjs.map +0 -1
  339. package/dist/seo-DCEF-AVr.mjs.map +0 -1
  340. package/dist/version-B5nHxI5Y.mjs +0 -7
@@ -1,13 +1,13 @@
1
1
  import "../../../../../base64-CmWvODNW.mjs";
2
2
  import "../../../../../types-CmI1OyQg.mjs";
3
3
  import { n as FIELD_TYPES } from "../../../../../types-tM44hEcf.mjs";
4
- import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-lq_fL-RS.mjs";
5
- import { n as parseBody, t as isParseError } from "../../../../../parse-DHI873qU.mjs";
6
- import "../../../../../redirects-x9eFPeyi.mjs";
7
- import { g as wpPrepareBody } from "../../../../../byline-fields-B-KXkg5R.mjs";
4
+ import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-DXnK2wF4.mjs";
5
+ import { n as parseBody, t as isParseError } from "../../../../../parse-DqMWHQ8x.mjs";
6
+ import "../../../../../redirects-Dcbuisoj.mjs";
7
+ import { g as wpPrepareBody } from "../../../../../byline-fields-eyJMblSr.mjs";
8
8
  import "../../../../../status-vUK0SA17.mjs";
9
9
  import "../../../../../api/schemas/index.mjs";
10
- import { n as requirePerm } from "../../../../../authorize-B__u-iZJ.mjs";
10
+ import { n as requirePerm } from "../../../../../authorize-CuxZ0i2t.mjs";
11
11
  import { capitalize, sanitizeSlug, singularize } from "./analyze.mjs";
12
12
 
13
13
  //#region src/astro/routes/api/import/wordpress/prepare.ts
@@ -32,7 +32,7 @@ const POST = async ({ request, locals }) => {
32
32
  }
33
33
  };
34
34
  async function prepareImport(db, request) {
35
- const { SchemaRegistry } = await import("../../../../../registry-CJkCubOJ.mjs").then((n) => n.r);
35
+ const { SchemaRegistry } = await import("../../../../../registry-3aOAS4My.mjs").then((n) => n.r);
36
36
  const registry = new SchemaRegistry(db);
37
37
  const result = {
38
38
  success: true,
@@ -1,14 +1,14 @@
1
1
  import { t as validateIdentifier } from "../../../../../validate-VPnKoIzW.mjs";
2
2
  import "../../../../../base64-CmWvODNW.mjs";
3
3
  import "../../../../../types-CmI1OyQg.mjs";
4
- import { n as normalizeMediaValue } from "../../../../../normalize-ClbNIenm.mjs";
5
- import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-lq_fL-RS.mjs";
6
- import { n as parseBody, t as isParseError } from "../../../../../parse-DHI873qU.mjs";
7
- import "../../../../../redirects-x9eFPeyi.mjs";
8
- import { _ as wpRewriteUrlsBody } from "../../../../../byline-fields-B-KXkg5R.mjs";
4
+ import { n as normalizeMediaValue } from "../../../../../normalize-CeglrBT9.mjs";
5
+ import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-DXnK2wF4.mjs";
6
+ import { n as parseBody, t as isParseError } from "../../../../../parse-DqMWHQ8x.mjs";
7
+ import "../../../../../redirects-Dcbuisoj.mjs";
8
+ import { _ as wpRewriteUrlsBody } from "../../../../../byline-fields-eyJMblSr.mjs";
9
9
  import "../../../../../status-vUK0SA17.mjs";
10
10
  import "../../../../../api/schemas/index.mjs";
11
- import { n as requirePerm } from "../../../../../authorize-B__u-iZJ.mjs";
11
+ import { n as requirePerm } from "../../../../../authorize-CuxZ0i2t.mjs";
12
12
  import { buildBaseUrlMap, extractMediaUrl, findMatchingUrl, rewritePortableTextUrls, rewriteStringUrls } from "./rewrite-url-helpers.mjs";
13
13
  import { sql } from "kysely";
14
14
 
@@ -35,7 +35,7 @@ const POST = async ({ request, locals }) => {
35
35
  }
36
36
  };
37
37
  async function rewriteUrls(db, urlMap, getProvider, collections) {
38
- const { SchemaRegistry } = await import("../../../../../registry-CJkCubOJ.mjs").then((n) => n.r);
38
+ const { SchemaRegistry } = await import("../../../../../registry-3aOAS4My.mjs").then((n) => n.r);
39
39
  const registry = new SchemaRegistry(db);
40
40
  const result = {
41
41
  updated: 0,
@@ -1,16 +1,16 @@
1
1
  import "../../../../../base64-CmWvODNW.mjs";
2
2
  import "../../../../../types-CmI1OyQg.mjs";
3
3
  import { n as resolveAndValidateExternalUrl, t as SsrfError } from "../../../../../ssrf-BRKb343l.mjs";
4
- import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-lq_fL-RS.mjs";
5
- import { n as parseBody, t as isParseError } from "../../../../../parse-DHI873qU.mjs";
6
- import "../../../../../redirects-x9eFPeyi.mjs";
7
- import { m as wpPluginAnalyzeBody } from "../../../../../byline-fields-B-KXkg5R.mjs";
4
+ import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-DXnK2wF4.mjs";
5
+ import { n as parseBody, t as isParseError } from "../../../../../parse-DqMWHQ8x.mjs";
6
+ import "../../../../../redirects-Dcbuisoj.mjs";
7
+ import { m as wpPluginAnalyzeBody } from "../../../../../byline-fields-eyJMblSr.mjs";
8
8
  import "../../../../../status-vUK0SA17.mjs";
9
9
  import "../../../../../api/schemas/index.mjs";
10
10
  import { s as getSource } from "../../../../../import-Bb1T9WJS.mjs";
11
11
  import "../../../../../ssrf-CcX9zvMK.mjs";
12
12
  import "../../../../../utils-B7A57fm9.mjs";
13
- import { n as requirePerm } from "../../../../../authorize-B__u-iZJ.mjs";
13
+ import { n as requirePerm } from "../../../../../authorize-CuxZ0i2t.mjs";
14
14
  import { SchemaRegistry } from "emdash";
15
15
 
16
16
  //#region src/astro/routes/api/import/wordpress-plugin/analyze.ts
@@ -9,16 +9,16 @@ import "../../../../../byline-registry-BOjqDOim.mjs";
9
9
  import "../../../../../field-defs-cache-CYYx8xlG.mjs";
10
10
  import { t as BylineRepository } from "../../../../../byline-C-YajfV7.mjs";
11
11
  import { n as resolveAndValidateExternalUrl, t as SsrfError } from "../../../../../ssrf-BRKb343l.mjs";
12
- import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-lq_fL-RS.mjs";
13
- import { n as parseBody, t as isParseError } from "../../../../../parse-DHI873qU.mjs";
14
- import "../../../../../redirects-x9eFPeyi.mjs";
15
- import { h as wpPluginExecuteBody } from "../../../../../byline-fields-B-KXkg5R.mjs";
12
+ import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-DXnK2wF4.mjs";
13
+ import { n as parseBody, t as isParseError } from "../../../../../parse-DqMWHQ8x.mjs";
14
+ import "../../../../../redirects-Dcbuisoj.mjs";
15
+ import { h as wpPluginExecuteBody } from "../../../../../byline-fields-eyJMblSr.mjs";
16
16
  import "../../../../../status-vUK0SA17.mjs";
17
17
  import "../../../../../api/schemas/index.mjs";
18
18
  import { s as getSource } from "../../../../../import-Bb1T9WJS.mjs";
19
19
  import "../../../../../ssrf-CcX9zvMK.mjs";
20
20
  import { m as resolveImportByline } from "../../../../../utils-B7A57fm9.mjs";
21
- import { n as requirePerm } from "../../../../../authorize-B__u-iZJ.mjs";
21
+ import { n as requirePerm } from "../../../../../authorize-CuxZ0i2t.mjs";
22
22
  import { ContentRepository, SchemaRegistry } from "emdash";
23
23
 
24
24
  //#region src/astro/routes/api/import/wordpress-plugin/execute.ts
@@ -1,7 +1,7 @@
1
1
  import "../../../base64-CmWvODNW.mjs";
2
2
  import "../../../types-CmI1OyQg.mjs";
3
- import { r as handleError } from "../../../error-lq_fL-RS.mjs";
4
- import { n as VERSION, t as COMMIT } from "../../../version-B5nHxI5Y.mjs";
3
+ import { r as handleError } from "../../../error-DXnK2wF4.mjs";
4
+ import { n as VERSION, t as COMMIT } from "../../../version-DHdCA24u.mjs";
5
5
  import { t as getAuthMode } from "../../../mode-BB0F8xTC.mjs";
6
6
 
7
7
  //#region src/astro/routes/api/manifest.ts
@@ -1,8 +1,8 @@
1
1
  import "../../../base64-CmWvODNW.mjs";
2
2
  import "../../../types-CmI1OyQg.mjs";
3
- import { t as apiError } from "../../../error-lq_fL-RS.mjs";
4
- import { Dn as contentBylineInputSchema, Tn as bylineUpdateBody, un as contentSeoInput, yn as bylineCreateBody } from "../../../redirects-x9eFPeyi.mjs";
5
- import "../../../byline-fields-B-KXkg5R.mjs";
3
+ import { t as apiError } from "../../../error-DXnK2wF4.mjs";
4
+ import { Dn as contentBylineInputSchema, Tn as bylineUpdateBody, un as contentSeoInput, yn as bylineCreateBody } from "../../../redirects-Dcbuisoj.mjs";
5
+ import "../../../byline-fields-eyJMblSr.mjs";
6
6
  import "../../../status-vUK0SA17.mjs";
7
7
  import "../../../api/schemas/index.mjs";
8
8
  import { i as hasScope } from "../../../api-tokens-B4BQybOp.mjs";
@@ -157,7 +157,7 @@ function getEmDash(extra) {
157
157
  }
158
158
  async function getCollectionFields(ec, collection) {
159
159
  try {
160
- const { SchemaRegistry } = await import("../../../schema-DMjXuvNU.mjs").then((n) => n.t);
160
+ const { SchemaRegistry } = await import("../../../schema-BGwx8AQY.mjs").then((n) => n.t);
161
161
  const col = await new SchemaRegistry(ec.db).getCollectionWithFields(collection);
162
162
  return col ? col.fields : null;
163
163
  } catch {
@@ -186,7 +186,7 @@ async function applyReadMarkdown(ec, collection, items) {
186
186
  for (const item of items) if (item.data && typeof item.data === "object") item.data = convertDataForRead(item.data, fields, false);
187
187
  }
188
188
  async function invalidateBylines() {
189
- const { invalidateBylineCache } = await import("../../../bylines-BMtJnPnG.mjs").then((n) => n.t);
189
+ const { invalidateBylineCache } = await import("../../../bylines-CPc_q2Vd.mjs").then((n) => n.t);
190
190
  invalidateBylineCache();
191
191
  }
192
192
  /**
@@ -813,7 +813,7 @@ function createMcpServer() {
813
813
  requireRole(extra, Role.EDITOR);
814
814
  const ec = getEmDash(extra);
815
815
  try {
816
- const { SchemaRegistry } = await import("../../../schema-DMjXuvNU.mjs").then((n) => n.t);
816
+ const { SchemaRegistry } = await import("../../../schema-BGwx8AQY.mjs").then((n) => n.t);
817
817
  return jsonResult({ items: await new SchemaRegistry(ec.db).listCollections() });
818
818
  } catch (error) {
819
819
  return respondHandlerError(error, "SCHEMA_LIST_ERROR");
@@ -829,7 +829,7 @@ function createMcpServer() {
829
829
  requireRole(extra, Role.EDITOR);
830
830
  const ec = getEmDash(extra);
831
831
  try {
832
- const { SchemaRegistry } = await import("../../../schema-DMjXuvNU.mjs").then((n) => n.t);
832
+ const { SchemaRegistry } = await import("../../../schema-BGwx8AQY.mjs").then((n) => n.t);
833
833
  const collection = await new SchemaRegistry(ec.db).getCollectionWithFields(args.slug);
834
834
  if (!collection) return respondError("NOT_FOUND", `Collection '${args.slug}' not found`);
835
835
  return jsonResult(collection);
@@ -859,7 +859,7 @@ function createMcpServer() {
859
859
  requireRole(extra, Role.ADMIN);
860
860
  const ec = getEmDash(extra);
861
861
  try {
862
- const { SchemaRegistry } = await import("../../../schema-DMjXuvNU.mjs").then((n) => n.t);
862
+ const { SchemaRegistry } = await import("../../../schema-BGwx8AQY.mjs").then((n) => n.t);
863
863
  const collection = await new SchemaRegistry(ec.db).createCollection({
864
864
  slug: args.slug,
865
865
  label: args.label,
@@ -887,7 +887,7 @@ function createMcpServer() {
887
887
  requireRole(extra, Role.ADMIN);
888
888
  const ec = getEmDash(extra);
889
889
  try {
890
- const { SchemaRegistry } = await import("../../../schema-DMjXuvNU.mjs").then((n) => n.t);
890
+ const { SchemaRegistry } = await import("../../../schema-BGwx8AQY.mjs").then((n) => n.t);
891
891
  await new SchemaRegistry(ec.db).deleteCollection(args.slug, { force: args.force });
892
892
  ec.invalidateUrlPatternCache();
893
893
  return jsonResult({ deleted: args.slug });
@@ -941,7 +941,7 @@ function createMcpServer() {
941
941
  requireRole(extra, Role.ADMIN);
942
942
  const ec = getEmDash(extra);
943
943
  try {
944
- const { SchemaRegistry } = await import("../../../schema-DMjXuvNU.mjs").then((n) => n.t);
944
+ const { SchemaRegistry } = await import("../../../schema-BGwx8AQY.mjs").then((n) => n.t);
945
945
  return jsonResult(await new SchemaRegistry(ec.db).createField(args.collection, {
946
946
  slug: args.slug,
947
947
  label: args.label,
@@ -971,7 +971,7 @@ function createMcpServer() {
971
971
  requireRole(extra, Role.ADMIN);
972
972
  const ec = getEmDash(extra);
973
973
  try {
974
- const { SchemaRegistry } = await import("../../../schema-DMjXuvNU.mjs").then((n) => n.t);
974
+ const { SchemaRegistry } = await import("../../../schema-BGwx8AQY.mjs").then((n) => n.t);
975
975
  await new SchemaRegistry(ec.db).deleteField(args.collection, args.fieldSlug);
976
976
  return jsonResult({
977
977
  deleted: args.fieldSlug,
@@ -1092,7 +1092,7 @@ function createMcpServer() {
1092
1092
  requireScope(extra, "content:read");
1093
1093
  const ec = getEmDash(extra);
1094
1094
  try {
1095
- const { searchWithDb } = await import("../../../search-D1wKk3lc.mjs").then((n) => n.t);
1095
+ const { searchWithDb } = await import("../../../search-Gl747zMP.mjs").then((n) => n.t);
1096
1096
  return jsonResult(await searchWithDb(ec.db, args.query, {
1097
1097
  collections: args.collections,
1098
1098
  locale: args.locale,
@@ -1111,7 +1111,7 @@ function createMcpServer() {
1111
1111
  requireScope(extra, "content:read");
1112
1112
  const ec = getEmDash(extra);
1113
1113
  try {
1114
- const { handleTaxonomyList } = await import("../../../taxonomies-DhLaNJji.mjs").then((n) => n.l);
1114
+ const { handleTaxonomyList } = await import("../../../taxonomies-DbAtL6m4.mjs").then((n) => n.l);
1115
1115
  return unwrap(await handleTaxonomyList(ec.db, { locale: args.locale }));
1116
1116
  } catch (error) {
1117
1117
  return respondHandlerError(error, "TAXONOMY_LIST_ERROR");
@@ -1131,7 +1131,7 @@ function createMcpServer() {
1131
1131
  requireScope(extra, "content:read");
1132
1132
  const ec = getEmDash(extra);
1133
1133
  try {
1134
- const { handleTaxonomyList } = await import("../../../taxonomies-DhLaNJji.mjs").then((n) => n.l);
1134
+ const { handleTaxonomyList } = await import("../../../taxonomies-DbAtL6m4.mjs").then((n) => n.l);
1135
1135
  const listResult = await handleTaxonomyList(ec.db, { locale: args.locale });
1136
1136
  if (!listResult.success) return unwrap(listResult);
1137
1137
  if (!listResult.data.taxonomies.find((t) => t.name === args.taxonomy)) return respondError("NOT_FOUND", `Taxonomy '${args.taxonomy}' not found`);
@@ -1190,7 +1190,7 @@ function createMcpServer() {
1190
1190
  requireRole(extra, Role.EDITOR);
1191
1191
  const ec = getEmDash(extra);
1192
1192
  try {
1193
- const { handleTermCreate } = await import("../../../taxonomies-DhLaNJji.mjs").then((n) => n.l);
1193
+ const { handleTermCreate } = await import("../../../taxonomies-DbAtL6m4.mjs").then((n) => n.l);
1194
1194
  return unwrap(await handleTermCreate(ec.db, args.taxonomy, {
1195
1195
  slug: args.slug,
1196
1196
  label: args.label,
@@ -1219,7 +1219,7 @@ function createMcpServer() {
1219
1219
  requireRole(extra, Role.EDITOR);
1220
1220
  const ec = getEmDash(extra);
1221
1221
  try {
1222
- const { handleTermUpdate } = await import("../../../taxonomies-DhLaNJji.mjs").then((n) => n.l);
1222
+ const { handleTermUpdate } = await import("../../../taxonomies-DbAtL6m4.mjs").then((n) => n.l);
1223
1223
  return unwrap(await handleTermUpdate(ec.db, args.taxonomy, args.termSlug, {
1224
1224
  slug: args.slug,
1225
1225
  label: args.label,
@@ -1243,7 +1243,7 @@ function createMcpServer() {
1243
1243
  requireRole(extra, Role.EDITOR);
1244
1244
  const ec = getEmDash(extra);
1245
1245
  try {
1246
- const { handleTermDelete } = await import("../../../taxonomies-DhLaNJji.mjs").then((n) => n.l);
1246
+ const { handleTermDelete } = await import("../../../taxonomies-DbAtL6m4.mjs").then((n) => n.l);
1247
1247
  return unwrap(await handleTermDelete(ec.db, args.taxonomy, args.termSlug));
1248
1248
  } catch (error) {
1249
1249
  return respondHandlerError(error, "TAXONOMY_TERM_DELETE_ERROR");
@@ -1258,7 +1258,7 @@ function createMcpServer() {
1258
1258
  requireScope(extra, "content:read");
1259
1259
  const ec = getEmDash(extra);
1260
1260
  try {
1261
- const { handleTermTranslations } = await import("../../../taxonomies-DhLaNJji.mjs").then((n) => n.l);
1261
+ const { handleTermTranslations } = await import("../../../taxonomies-DbAtL6m4.mjs").then((n) => n.l);
1262
1262
  return unwrap(await handleTermTranslations(ec.db, args.id));
1263
1263
  } catch (error) {
1264
1264
  return respondHandlerError(error, "TERM_TRANSLATIONS_ERROR");
@@ -1454,7 +1454,7 @@ function createMcpServer() {
1454
1454
  requireRole(extra, Role.EDITOR);
1455
1455
  const ec = getEmDash(extra);
1456
1456
  try {
1457
- const { handleSettingsGet } = await import("../../../settings-C3Yi5Co3.mjs").then((n) => n.r);
1457
+ const { handleSettingsGet } = await import("../../../settings-fPSR6dJa.mjs").then((n) => n.r);
1458
1458
  return unwrap(await handleSettingsGet(ec.db, ec.storage));
1459
1459
  } catch (error) {
1460
1460
  return respondHandlerError(error, "SETTINGS_READ_ERROR");
@@ -1480,7 +1480,7 @@ function createMcpServer() {
1480
1480
  requireRole(extra, Role.ADMIN);
1481
1481
  const ec = getEmDash(extra);
1482
1482
  try {
1483
- const { handleSettingsUpdate } = await import("../../../settings-C3Yi5Co3.mjs").then((n) => n.r);
1483
+ const { handleSettingsUpdate } = await import("../../../settings-fPSR6dJa.mjs").then((n) => n.r);
1484
1484
  return unwrap(await handleSettingsUpdate(ec.db, ec.storage, args));
1485
1485
  } catch (error) {
1486
1486
  return respondHandlerError(error, "SETTINGS_UPDATE_ERROR");
@@ -1 +1 @@
1
- {"version":3,"file":"confirm.d.mts","names":[],"sources":["../../../../../../src/astro/routes/api/media/[id]/confirm.ts"],"mappings":";;;cAkBa,SAAA;;;;cAeA,IAAA,EAAM,QAAA"}
1
+ {"version":3,"file":"confirm.d.mts","names":[],"sources":["../../../../../../src/astro/routes/api/media/[id]/confirm.ts"],"mappings":";;;cAmBa,SAAA;;;;cAwBA,IAAA,EAAM,QAAA"}
@@ -1,17 +1,26 @@
1
1
  import "../../../../../base64-CmWvODNW.mjs";
2
2
  import "../../../../../types-CmI1OyQg.mjs";
3
- import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-lq_fL-RS.mjs";
4
- import { r as parseOptionalBody, t as isParseError } from "../../../../../parse-DHI873qU.mjs";
5
- import { Ht as mediaConfirmBody } from "../../../../../redirects-x9eFPeyi.mjs";
6
- import "../../../../../byline-fields-B-KXkg5R.mjs";
3
+ import { t as enrichImageMetadata } from "../../../../../enrich-BQ0mxJRs.mjs";
4
+ import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-DXnK2wF4.mjs";
5
+ import { r as parseOptionalBody, t as isParseError } from "../../../../../parse-DqMWHQ8x.mjs";
6
+ import { Ht as mediaConfirmBody } from "../../../../../redirects-Dcbuisoj.mjs";
7
+ import "../../../../../byline-fields-eyJMblSr.mjs";
7
8
  import "../../../../../status-vUK0SA17.mjs";
8
9
  import "../../../../../api/schemas/index.mjs";
9
- import { n as requirePerm, t as requireOwnerPerm } from "../../../../../authorize-B__u-iZJ.mjs";
10
+ import { n as requirePerm, t as requireOwnerPerm } from "../../../../../authorize-CuxZ0i2t.mjs";
10
11
  import { MediaRepository } from "emdash";
11
12
 
12
13
  //#region src/astro/routes/api/media/[id]/confirm.ts
13
14
  const prerender = false;
14
15
  /**
16
+ * Max raw bytes to buffer for server-side LQIP generation at confirm time. The
17
+ * signed-URL upload flow exists so large files bypass server buffering — re-reading
18
+ * the whole object into a Worker's 128 MB heap to compute a blurhash would OOM
19
+ * on the very uploads that flow was designed for. LQIP is progressive
20
+ * enhancement: large images simply ship without a server-generated placeholder.
21
+ */
22
+ const MAX_PLACEHOLDER_DOWNLOAD_BYTES = 8 * 1024 * 1024;
23
+ /**
15
24
  * Add URL to media item (relative URL for portability)
16
25
  */
17
26
  function addUrlToMedia(item) {
@@ -45,10 +54,37 @@ const POST = async ({ params, request, locals }) => {
45
54
  return apiError("FILE_NOT_FOUND", "File was not uploaded to storage", 400);
46
55
  }
47
56
  }
57
+ let blurhash;
58
+ let dominantColor;
59
+ let width = body.width;
60
+ let height = body.height;
61
+ if (emdash.storage && existing.mimeType.startsWith("image/")) {
62
+ const knownSize = body.size ?? existing.size ?? void 0;
63
+ if (!(knownSize != null && knownSize > MAX_PLACEHOLDER_DOWNLOAD_BYTES)) try {
64
+ const { body: stream } = await emdash.storage.download(existing.storageKey);
65
+ const bytes = new Uint8Array(await new Response(stream).arrayBuffer());
66
+ if (bytes.byteLength > MAX_PLACEHOLDER_DOWNLOAD_BYTES) console.warn(`[media] confirm skipping placeholder: object ${existing.storageKey} is ${bytes.byteLength} bytes (> ${MAX_PLACEHOLDER_DOWNLOAD_BYTES})`);
67
+ else {
68
+ const enriched = await enrichImageMetadata(bytes, existing.mimeType, { knownDimensions: body.width != null && body.height != null ? {
69
+ width: body.width,
70
+ height: body.height
71
+ } : void 0 });
72
+ blurhash = enriched.blurhash;
73
+ dominantColor = enriched.dominantColor;
74
+ width = width ?? enriched.width;
75
+ height = height ?? enriched.height;
76
+ }
77
+ } catch (error) {
78
+ console.error("[media] confirm placeholder generation failed:", error);
79
+ }
80
+ else console.warn(`[media] confirm skipping placeholder: object ${existing.storageKey} reported size ${knownSize} bytes (> ${MAX_PLACEHOLDER_DOWNLOAD_BYTES})`);
81
+ }
48
82
  const item = await repo.confirmUpload(id, {
49
83
  size: body.size,
50
- width: body.width,
51
- height: body.height
84
+ width,
85
+ height,
86
+ blurhash,
87
+ dominantColor
52
88
  });
53
89
  if (!item) return apiError("CONFIRM_FAILED", "Failed to confirm upload", 500);
54
90
  return apiSuccess({ item: addUrlToMedia(item) });
@@ -1 +1 @@
1
- {"version":3,"file":"confirm.mjs","names":[],"sources":["../../../../../../src/astro/routes/api/media/[id]/confirm.ts"],"sourcesContent":["/**\n * Confirm media upload endpoint\n *\n * POST /_emdash/api/media/{id}/confirm\n *\n * Confirms that the client has successfully uploaded the file to storage.\n * Marks the media record as ready and optionally updates metadata.\n */\n\nimport type { APIRoute } from \"astro\";\nimport { MediaRepository } from \"emdash\";\n\nimport { requireOwnerPerm, requirePerm } from \"#api/authorize.js\";\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseOptionalBody } from \"#api/parse.js\";\nimport { mediaConfirmBody } from \"#api/schemas.js\";\nimport type { MediaItem } from \"#types\";\n\nexport const prerender = false;\n\n/**\n * Add URL to media item (relative URL for portability)\n */\nfunction addUrlToMedia(item: MediaItem): MediaItem & { url: string } {\n\treturn {\n\t\t...item,\n\t\turl: `/_emdash/api/media/file/${item.storageKey}`,\n\t};\n}\n\n/**\n * Confirm upload completion\n */\nexport const POST: APIRoute = async ({ params, request, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst { id } = params;\n\n\tconst denied = requirePerm(user, \"media:upload\");\n\tif (denied) return denied;\n\n\tif (!id) {\n\t\treturn apiError(\"INVALID_REQUEST\", \"Media ID is required\", 400);\n\t}\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\tconst body = await parseOptionalBody(request, mediaConfirmBody, {});\n\t\tif (isParseError(body)) return body;\n\n\t\tconst repo = new MediaRepository(emdash.db);\n\n\t\t// Get the media item first to check status\n\t\tconst existing = await repo.findById(id);\n\t\tif (!existing) {\n\t\t\treturn apiError(\"NOT_FOUND\", `Media item not found: ${id}`, 404);\n\t\t}\n\n\t\tif (existing.status !== \"pending\") {\n\t\t\treturn apiError(\"INVALID_STATE\", `Media item is not pending: ${existing.status}`, 400);\n\t\t}\n\n\t\t// Only the uploader or a user with media:edit_any can confirm/fail a pending upload\n\t\tconst ownerDenied = requireOwnerPerm(\n\t\t\tuser,\n\t\t\texisting.authorId ?? \"\",\n\t\t\t\"media:edit_own\",\n\t\t\t\"media:edit_any\",\n\t\t);\n\t\tif (ownerDenied) return ownerDenied;\n\n\t\t// Optionally verify the file exists in storage\n\t\tif (emdash.storage) {\n\t\t\tconst exists = await emdash.storage.exists(existing.storageKey);\n\t\t\tif (!exists) {\n\t\t\t\t// Mark as failed\n\t\t\t\tawait repo.markFailed(id);\n\t\t\t\treturn apiError(\"FILE_NOT_FOUND\", \"File was not uploaded to storage\", 400);\n\t\t\t}\n\t\t}\n\n\t\t// Confirm the upload\n\t\tconst item = await repo.confirmUpload(id, {\n\t\t\tsize: body.size,\n\t\t\twidth: body.width,\n\t\t\theight: body.height,\n\t\t});\n\n\t\tif (!item) {\n\t\t\treturn apiError(\"CONFIRM_FAILED\", \"Failed to confirm upload\", 500);\n\t\t}\n\n\t\t// Add URL to the response (relative URL for portability)\n\t\tconst itemWithUrl = addUrlToMedia(item);\n\n\t\treturn apiSuccess({ item: itemWithUrl });\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to confirm upload\", \"CONFIRM_ERROR\");\n\t}\n};\n"],"mappings":";;;;;;;;;;;;AAkBA,MAAa,YAAY;;;;AAKzB,SAAS,cAAc,MAA8C;AACpE,QAAO;EACN,GAAG;EACH,KAAK,2BAA2B,KAAK;EACrC;;;;;AAMF,MAAa,OAAiB,OAAO,EAAE,QAAQ,SAAS,aAAa;CACpE,MAAM,EAAE,QAAQ,SAAS;CACzB,MAAM,EAAE,OAAO;CAEf,MAAM,SAAS,YAAY,MAAM,eAAe;AAChD,KAAI,OAAQ,QAAO;AAEnB,KAAI,CAAC,GACJ,QAAO,SAAS,mBAAmB,wBAAwB,IAAI;AAGhE,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAGpE,KAAI;EACH,MAAM,OAAO,MAAM,kBAAkB,SAAS,kBAAkB,EAAE,CAAC;AACnE,MAAI,aAAa,KAAK,CAAE,QAAO;EAE/B,MAAM,OAAO,IAAI,gBAAgB,OAAO,GAAG;EAG3C,MAAM,WAAW,MAAM,KAAK,SAAS,GAAG;AACxC,MAAI,CAAC,SACJ,QAAO,SAAS,aAAa,yBAAyB,MAAM,IAAI;AAGjE,MAAI,SAAS,WAAW,UACvB,QAAO,SAAS,iBAAiB,8BAA8B,SAAS,UAAU,IAAI;EAIvF,MAAM,cAAc,iBACnB,MACA,SAAS,YAAY,IACrB,kBACA,iBACA;AACD,MAAI,YAAa,QAAO;AAGxB,MAAI,OAAO,SAEV;OAAI,CADW,MAAM,OAAO,QAAQ,OAAO,SAAS,WAAW,EAClD;AAEZ,UAAM,KAAK,WAAW,GAAG;AACzB,WAAO,SAAS,kBAAkB,oCAAoC,IAAI;;;EAK5E,MAAM,OAAO,MAAM,KAAK,cAAc,IAAI;GACzC,MAAM,KAAK;GACX,OAAO,KAAK;GACZ,QAAQ,KAAK;GACb,CAAC;AAEF,MAAI,CAAC,KACJ,QAAO,SAAS,kBAAkB,4BAA4B,IAAI;AAMnE,SAAO,WAAW,EAAE,MAFA,cAAc,KAAK,EAEA,CAAC;UAChC,OAAO;AACf,SAAO,YAAY,OAAO,4BAA4B,gBAAgB"}
1
+ {"version":3,"file":"confirm.mjs","names":[],"sources":["../../../../../../src/astro/routes/api/media/[id]/confirm.ts"],"sourcesContent":["/**\n * Confirm media upload endpoint\n *\n * POST /_emdash/api/media/{id}/confirm\n *\n * Confirms that the client has successfully uploaded the file to storage.\n * Marks the media record as ready and optionally updates metadata.\n */\n\nimport type { APIRoute } from \"astro\";\nimport { MediaRepository } from \"emdash\";\n\nimport { requireOwnerPerm, requirePerm } from \"#api/authorize.js\";\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseOptionalBody } from \"#api/parse.js\";\nimport { mediaConfirmBody } from \"#api/schemas.js\";\nimport { enrichImageMetadata } from \"#media/enrich.js\";\nimport type { MediaItem } from \"#types\";\n\nexport const prerender = false;\n\n/**\n * Max raw bytes to buffer for server-side LQIP generation at confirm time. The\n * signed-URL upload flow exists so large files bypass server buffering — re-reading\n * the whole object into a Worker's 128 MB heap to compute a blurhash would OOM\n * on the very uploads that flow was designed for. LQIP is progressive\n * enhancement: large images simply ship without a server-generated placeholder.\n */\nconst MAX_PLACEHOLDER_DOWNLOAD_BYTES = 8 * 1024 * 1024;\n\n/**\n * Add URL to media item (relative URL for portability)\n */\nfunction addUrlToMedia(item: MediaItem): MediaItem & { url: string } {\n\treturn {\n\t\t...item,\n\t\turl: `/_emdash/api/media/file/${item.storageKey}`,\n\t};\n}\n\n/**\n * Confirm upload completion\n */\nexport const POST: APIRoute = async ({ params, request, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst { id } = params;\n\n\tconst denied = requirePerm(user, \"media:upload\");\n\tif (denied) return denied;\n\n\tif (!id) {\n\t\treturn apiError(\"INVALID_REQUEST\", \"Media ID is required\", 400);\n\t}\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\tconst body = await parseOptionalBody(request, mediaConfirmBody, {});\n\t\tif (isParseError(body)) return body;\n\n\t\tconst repo = new MediaRepository(emdash.db);\n\n\t\t// Get the media item first to check status\n\t\tconst existing = await repo.findById(id);\n\t\tif (!existing) {\n\t\t\treturn apiError(\"NOT_FOUND\", `Media item not found: ${id}`, 404);\n\t\t}\n\n\t\tif (existing.status !== \"pending\") {\n\t\t\treturn apiError(\"INVALID_STATE\", `Media item is not pending: ${existing.status}`, 400);\n\t\t}\n\n\t\t// Only the uploader or a user with media:edit_any can confirm/fail a pending upload\n\t\tconst ownerDenied = requireOwnerPerm(\n\t\t\tuser,\n\t\t\texisting.authorId ?? \"\",\n\t\t\t\"media:edit_own\",\n\t\t\t\"media:edit_any\",\n\t\t);\n\t\tif (ownerDenied) return ownerDenied;\n\n\t\t// Optionally verify the file exists in storage\n\t\tif (emdash.storage) {\n\t\t\tconst exists = await emdash.storage.exists(existing.storageKey);\n\t\t\tif (!exists) {\n\t\t\t\t// Mark as failed\n\t\t\t\tawait repo.markFailed(id);\n\t\t\t\treturn apiError(\"FILE_NOT_FOUND\", \"File was not uploaded to storage\", 400);\n\t\t\t}\n\t\t}\n\n\t\t// For images, read the just-uploaded bytes back from storage once to\n\t\t// generate LQIP placeholders (and server-side dimensions as a fallback).\n\t\t// The signed-URL flow uploads directly to storage, so this confirm is the\n\t\t// only point at which the server sees the bytes. Best-effort: a decode\n\t\t// failure must not block the upload from being marked ready. We also cap\n\t\t// the download size — buffering a large original into a Worker heap to\n\t\t// compute a 32px blurhash would OOM on the uploads the signed-URL path\n\t\t// exists to support, so oversized files skip the server-side placeholder.\n\t\tlet blurhash: string | undefined;\n\t\tlet dominantColor: string | undefined;\n\t\tlet width = body.width;\n\t\tlet height = body.height;\n\t\tif (emdash.storage && existing.mimeType.startsWith(\"image/\")) {\n\t\t\tconst knownSize = body.size ?? existing.size ?? undefined;\n\t\t\tconst tooLarge = knownSize != null && knownSize > MAX_PLACEHOLDER_DOWNLOAD_BYTES;\n\t\t\tif (!tooLarge) {\n\t\t\t\ttry {\n\t\t\t\t\tconst { body: stream } = await emdash.storage.download(existing.storageKey);\n\t\t\t\t\tconst bytes = new Uint8Array(await new Response(stream).arrayBuffer());\n\t\t\t\t\t// Defense-in-depth for the unknown-size case: even though we\n\t\t\t\t\t// already buffered it, refuse the decode so we don't also pay\n\t\t\t\t\t// the (larger) RGBA allocation.\n\t\t\t\t\tif (bytes.byteLength > MAX_PLACEHOLDER_DOWNLOAD_BYTES) {\n\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t`[media] confirm skipping placeholder: object ${existing.storageKey} is ${bytes.byteLength} bytes (> ${MAX_PLACEHOLDER_DOWNLOAD_BYTES})`,\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst enriched = await enrichImageMetadata(bytes, existing.mimeType, {\n\t\t\t\t\t\t\tknownDimensions:\n\t\t\t\t\t\t\t\tbody.width != null && body.height != null\n\t\t\t\t\t\t\t\t\t? { width: body.width, height: body.height }\n\t\t\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tblurhash = enriched.blurhash;\n\t\t\t\t\t\tdominantColor = enriched.dominantColor;\n\t\t\t\t\t\twidth = width ?? enriched.width;\n\t\t\t\t\t\theight = height ?? enriched.height;\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\"[media] confirm placeholder generation failed:\", error);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[media] confirm skipping placeholder: object ${existing.storageKey} reported size ${knownSize} bytes (> ${MAX_PLACEHOLDER_DOWNLOAD_BYTES})`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Confirm the upload\n\t\tconst item = await repo.confirmUpload(id, {\n\t\t\tsize: body.size,\n\t\t\twidth,\n\t\t\theight,\n\t\t\tblurhash,\n\t\t\tdominantColor,\n\t\t});\n\n\t\tif (!item) {\n\t\t\treturn apiError(\"CONFIRM_FAILED\", \"Failed to confirm upload\", 500);\n\t\t}\n\n\t\t// Add URL to the response (relative URL for portability)\n\t\tconst itemWithUrl = addUrlToMedia(item);\n\n\t\treturn apiSuccess({ item: itemWithUrl });\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to confirm upload\", \"CONFIRM_ERROR\");\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;AAmBA,MAAa,YAAY;;;;;;;;AASzB,MAAM,iCAAiC,IAAI,OAAO;;;;AAKlD,SAAS,cAAc,MAA8C;AACpE,QAAO;EACN,GAAG;EACH,KAAK,2BAA2B,KAAK;EACrC;;;;;AAMF,MAAa,OAAiB,OAAO,EAAE,QAAQ,SAAS,aAAa;CACpE,MAAM,EAAE,QAAQ,SAAS;CACzB,MAAM,EAAE,OAAO;CAEf,MAAM,SAAS,YAAY,MAAM,eAAe;AAChD,KAAI,OAAQ,QAAO;AAEnB,KAAI,CAAC,GACJ,QAAO,SAAS,mBAAmB,wBAAwB,IAAI;AAGhE,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAGpE,KAAI;EACH,MAAM,OAAO,MAAM,kBAAkB,SAAS,kBAAkB,EAAE,CAAC;AACnE,MAAI,aAAa,KAAK,CAAE,QAAO;EAE/B,MAAM,OAAO,IAAI,gBAAgB,OAAO,GAAG;EAG3C,MAAM,WAAW,MAAM,KAAK,SAAS,GAAG;AACxC,MAAI,CAAC,SACJ,QAAO,SAAS,aAAa,yBAAyB,MAAM,IAAI;AAGjE,MAAI,SAAS,WAAW,UACvB,QAAO,SAAS,iBAAiB,8BAA8B,SAAS,UAAU,IAAI;EAIvF,MAAM,cAAc,iBACnB,MACA,SAAS,YAAY,IACrB,kBACA,iBACA;AACD,MAAI,YAAa,QAAO;AAGxB,MAAI,OAAO,SAEV;OAAI,CADW,MAAM,OAAO,QAAQ,OAAO,SAAS,WAAW,EAClD;AAEZ,UAAM,KAAK,WAAW,GAAG;AACzB,WAAO,SAAS,kBAAkB,oCAAoC,IAAI;;;EAY5E,IAAI;EACJ,IAAI;EACJ,IAAI,QAAQ,KAAK;EACjB,IAAI,SAAS,KAAK;AAClB,MAAI,OAAO,WAAW,SAAS,SAAS,WAAW,SAAS,EAAE;GAC7D,MAAM,YAAY,KAAK,QAAQ,SAAS,QAAQ;AAEhD,OAAI,EADa,aAAa,QAAQ,YAAY,gCAEjD,KAAI;IACH,MAAM,EAAE,MAAM,WAAW,MAAM,OAAO,QAAQ,SAAS,SAAS,WAAW;IAC3E,MAAM,QAAQ,IAAI,WAAW,MAAM,IAAI,SAAS,OAAO,CAAC,aAAa,CAAC;AAItE,QAAI,MAAM,aAAa,+BACtB,SAAQ,KACP,gDAAgD,SAAS,WAAW,MAAM,MAAM,WAAW,YAAY,+BAA+B,GACtI;SACK;KACN,MAAM,WAAW,MAAM,oBAAoB,OAAO,SAAS,UAAU,EACpE,iBACC,KAAK,SAAS,QAAQ,KAAK,UAAU,OAClC;MAAE,OAAO,KAAK;MAAO,QAAQ,KAAK;MAAQ,GAC1C,QACJ,CAAC;AACF,gBAAW,SAAS;AACpB,qBAAgB,SAAS;AACzB,aAAQ,SAAS,SAAS;AAC1B,cAAS,UAAU,SAAS;;YAErB,OAAO;AACf,YAAQ,MAAM,kDAAkD,MAAM;;OAGvE,SAAQ,KACP,gDAAgD,SAAS,WAAW,iBAAiB,UAAU,YAAY,+BAA+B,GAC1I;;EAKH,MAAM,OAAO,MAAM,KAAK,cAAc,IAAI;GACzC,MAAM,KAAK;GACX;GACA;GACA;GACA;GACA,CAAC;AAEF,MAAI,CAAC,KACJ,QAAO,SAAS,kBAAkB,4BAA4B,IAAI;AAMnE,SAAO,WAAW,EAAE,MAFA,cAAc,KAAK,EAEA,CAAC;UAChC,OAAO;AACf,SAAO,YAAY,OAAO,4BAA4B,gBAAgB"}
@@ -1,12 +1,12 @@
1
1
  import "../../../../base64-CmWvODNW.mjs";
2
2
  import "../../../../types-CmI1OyQg.mjs";
3
- import { a as unwrapResult, r as handleError, t as apiError } from "../../../../error-lq_fL-RS.mjs";
4
- import { n as parseBody, t as isParseError } from "../../../../parse-DHI873qU.mjs";
5
- import { Xt as mediaUpdateBody } from "../../../../redirects-x9eFPeyi.mjs";
6
- import "../../../../byline-fields-B-KXkg5R.mjs";
3
+ import { a as unwrapResult, r as handleError, t as apiError } from "../../../../error-DXnK2wF4.mjs";
4
+ import { n as parseBody, t as isParseError } from "../../../../parse-DqMWHQ8x.mjs";
5
+ import { Xt as mediaUpdateBody } from "../../../../redirects-Dcbuisoj.mjs";
6
+ import "../../../../byline-fields-eyJMblSr.mjs";
7
7
  import "../../../../status-vUK0SA17.mjs";
8
8
  import "../../../../api/schemas/index.mjs";
9
- import { n as requirePerm, t as requireOwnerPerm } from "../../../../authorize-B__u-iZJ.mjs";
9
+ import { n as requirePerm, t as requireOwnerPerm } from "../../../../authorize-CuxZ0i2t.mjs";
10
10
 
11
11
  //#region src/astro/routes/api/media/[id].ts
12
12
  const prerender = false;
@@ -1,6 +1,6 @@
1
1
  import "../../../../../base64-CmWvODNW.mjs";
2
2
  import "../../../../../types-CmI1OyQg.mjs";
3
- import { r as handleError, t as apiError } from "../../../../../error-lq_fL-RS.mjs";
3
+ import { r as handleError, t as apiError } from "../../../../../error-DXnK2wF4.mjs";
4
4
 
5
5
  //#region src/astro/routes/api/media/file/[...key].ts
6
6
  const prerender = false;
@@ -1,7 +1,7 @@
1
1
  import "../../../../../../base64-CmWvODNW.mjs";
2
2
  import "../../../../../../types-CmI1OyQg.mjs";
3
- import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../../error-lq_fL-RS.mjs";
4
- import { n as requirePerm } from "../../../../../../authorize-B__u-iZJ.mjs";
3
+ import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../../error-DXnK2wF4.mjs";
4
+ import { n as requirePerm } from "../../../../../../authorize-CuxZ0i2t.mjs";
5
5
 
6
6
  //#region src/astro/routes/api/media/providers/[providerId]/[itemId].ts
7
7
  const prerender = false;
@@ -1,7 +1,7 @@
1
1
  import "../../../../../../base64-CmWvODNW.mjs";
2
2
  import "../../../../../../types-CmI1OyQg.mjs";
3
- import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../../error-lq_fL-RS.mjs";
4
- import { n as requirePerm } from "../../../../../../authorize-B__u-iZJ.mjs";
3
+ import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../../error-DXnK2wF4.mjs";
4
+ import { n as requirePerm } from "../../../../../../authorize-CuxZ0i2t.mjs";
5
5
 
6
6
  //#region src/astro/routes/api/media/providers/[providerId]/index.ts
7
7
  const prerender = false;
@@ -1,7 +1,7 @@
1
1
  import "../../../../../base64-CmWvODNW.mjs";
2
2
  import "../../../../../types-CmI1OyQg.mjs";
3
- import { n as apiSuccess, t as apiError } from "../../../../../error-lq_fL-RS.mjs";
4
- import { n as requirePerm } from "../../../../../authorize-B__u-iZJ.mjs";
3
+ import { n as apiSuccess, t as apiError } from "../../../../../error-DXnK2wF4.mjs";
4
+ import { n as requirePerm } from "../../../../../authorize-CuxZ0i2t.mjs";
5
5
 
6
6
  //#region src/astro/routes/api/media/providers/index.ts
7
7
  const prerender = false;
@@ -1,14 +1,14 @@
1
1
  import "../../../../base64-CmWvODNW.mjs";
2
2
  import "../../../../types-CmI1OyQg.mjs";
3
- import { n as normalizeMime, t as matchesMimeAllowlist } from "../../../../mime-BTzvzJ8M.mjs";
4
- import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-lq_fL-RS.mjs";
5
- import { n as parseBody, t as isParseError } from "../../../../parse-DHI873qU.mjs";
6
- import { Bt as DEFAULT_MAX_UPLOAD_SIZE, Zt as mediaUploadUrlBody } from "../../../../redirects-x9eFPeyi.mjs";
7
- import "../../../../byline-fields-B-KXkg5R.mjs";
3
+ import { n as normalizeMime, t as matchesMimeAllowlist } from "../../../../mime-CCEzze7W.mjs";
4
+ import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-DXnK2wF4.mjs";
5
+ import { n as parseBody, t as isParseError } from "../../../../parse-DqMWHQ8x.mjs";
6
+ import { Bt as DEFAULT_MAX_UPLOAD_SIZE, Zt as mediaUploadUrlBody } from "../../../../redirects-Dcbuisoj.mjs";
7
+ import "../../../../byline-fields-eyJMblSr.mjs";
8
8
  import "../../../../status-vUK0SA17.mjs";
9
9
  import "../../../../api/schemas/index.mjs";
10
- import { n as requirePerm } from "../../../../authorize-B__u-iZJ.mjs";
11
- import { n as resolveFieldAllowlist, t as GLOBAL_UPLOAD_ALLOWLIST } from "../../../../media-allowlist-B77I5_bw.mjs";
10
+ import { n as requirePerm } from "../../../../authorize-CuxZ0i2t.mjs";
11
+ import { n as resolveFieldAllowlist, t as GLOBAL_UPLOAD_ALLOWLIST } from "../../../../media-allowlist-Du8t1u6a.mjs";
12
12
  import { ulid } from "ulidx";
13
13
  import * as path from "node:path";
14
14
  import { MediaRepository } from "emdash";
@@ -1,17 +1,17 @@
1
1
  import "../../../base64-CmWvODNW.mjs";
2
2
  import "../../../types-CmI1OyQg.mjs";
3
- import { t as MediaRepository } from "../../../media-CTjJoJTh.mjs";
4
- import { t as generatePlaceholder } from "../../../placeholder-B0s2mWu5.mjs";
5
- import { n as normalizeMime, t as matchesMimeAllowlist } from "../../../mime-BTzvzJ8M.mjs";
3
+ import { t as MediaRepository } from "../../../media-BjKxjzk8.mjs";
4
+ import { n as normalizeMime, t as matchesMimeAllowlist } from "../../../mime-CCEzze7W.mjs";
5
+ import { t as enrichImageMetadata } from "../../../enrich-BQ0mxJRs.mjs";
6
6
  import { t as computeContentHash } from "../../../hash-DrvzIXcz.mjs";
7
- import { a as unwrapResult, n as apiSuccess, r as handleError, t as apiError } from "../../../error-lq_fL-RS.mjs";
8
- import { i as parseQuery, t as isParseError } from "../../../parse-DHI873qU.mjs";
9
- import { Bt as DEFAULT_MAX_UPLOAD_SIZE, Kt as mediaListQuery, Vt as formatFileSize } from "../../../redirects-x9eFPeyi.mjs";
10
- import "../../../byline-fields-B-KXkg5R.mjs";
7
+ import { a as unwrapResult, n as apiSuccess, r as handleError, t as apiError } from "../../../error-DXnK2wF4.mjs";
8
+ import { i as parseQuery, t as isParseError } from "../../../parse-DqMWHQ8x.mjs";
9
+ import { Bt as DEFAULT_MAX_UPLOAD_SIZE, Kt as mediaListQuery, Vt as formatFileSize } from "../../../redirects-Dcbuisoj.mjs";
10
+ import "../../../byline-fields-eyJMblSr.mjs";
11
11
  import "../../../status-vUK0SA17.mjs";
12
12
  import "../../../api/schemas/index.mjs";
13
- import { n as requirePerm } from "../../../authorize-B__u-iZJ.mjs";
14
- import { n as resolveFieldAllowlist, t as GLOBAL_UPLOAD_ALLOWLIST } from "../../../media-allowlist-B77I5_bw.mjs";
13
+ import { n as requirePerm } from "../../../authorize-CuxZ0i2t.mjs";
14
+ import { n as resolveFieldAllowlist, t as GLOBAL_UPLOAD_ALLOWLIST } from "../../../media-allowlist-Du8t1u6a.mjs";
15
15
  import { ulid } from "ulidx";
16
16
  import * as path from "node:path";
17
17
 
@@ -102,25 +102,26 @@ const POST = async ({ request, locals }) => {
102
102
  const height = heightStr ? parseInt(heightStr, 10) : void 0;
103
103
  const thumbnailEntry = formData.get("thumbnail");
104
104
  const thumbnail = thumbnailEntry instanceof File ? thumbnailEntry : null;
105
- let placeholder = null;
106
- if (file.type.startsWith("image/")) if (thumbnail) placeholder = await generatePlaceholder(new Uint8Array(await thumbnail.arrayBuffer()), thumbnail.type);
107
- else {
108
- const clientDims = width && height ? {
105
+ const enriched = await enrichImageMetadata(buffer, file.type, {
106
+ knownDimensions: width != null && height != null ? {
109
107
  width,
110
108
  height
111
- } : void 0;
112
- placeholder = await generatePlaceholder(buffer, file.type, clientDims);
113
- }
109
+ } : void 0,
110
+ placeholder: thumbnail ? {
111
+ bytes: new Uint8Array(await thumbnail.arrayBuffer()),
112
+ contentType: thumbnail.type
113
+ } : void 0
114
+ });
114
115
  const result = await emdash.handleMediaCreate({
115
116
  filename: file.name,
116
117
  mimeType: normalizeMime(file.type),
117
118
  size: file.size,
118
- width,
119
- height,
119
+ width: width ?? enriched.width,
120
+ height: height ?? enriched.height,
120
121
  storageKey,
121
122
  contentHash,
122
- blurhash: placeholder?.blurhash,
123
- dominantColor: placeholder?.dominantColor,
123
+ blurhash: enriched.blurhash,
124
+ dominantColor: enriched.dominantColor,
124
125
  authorId: user?.id
125
126
  });
126
127
  if (!result.success) {
@@ -1 +1 @@
1
- {"version":3,"file":"media.mjs","names":[],"sources":["../../../../src/astro/routes/api/media.ts"],"sourcesContent":["/**\n * Media list and upload endpoint\n *\n * GET /_emdash/api/media - List all media\n * POST /_emdash/api/media - Upload new media (via configured storage adapter)\n */\n\nimport * as path from \"node:path\";\n\nimport type { APIRoute } from \"astro\";\nimport { ulid } from \"ulidx\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, apiSuccess, handleError, unwrapResult } from \"#api/error.js\";\nimport { GLOBAL_UPLOAD_ALLOWLIST, resolveFieldAllowlist } from \"#api/handlers/media-allowlist.js\";\nimport { isParseError, parseQuery } from \"#api/parse.js\";\nimport { DEFAULT_MAX_UPLOAD_SIZE, formatFileSize, mediaListQuery } from \"#api/schemas.js\";\nimport { MediaRepository } from \"#db/repositories/media.js\";\nimport { matchesMimeAllowlist, normalizeMime } from \"#media/mime.js\";\nimport { generatePlaceholder } from \"#media/placeholder.js\";\nimport { computeContentHash } from \"#utils/hash.js\";\n\nimport type { MediaItem } from \"../../types.js\";\n\nexport const prerender = false;\n\n/**\n * Add URL to media items\n * Uses relative URLs to ensure portability across deployments\n */\nfunction addUrlToMedia(item: MediaItem): MediaItem & { url: string } {\n\treturn {\n\t\t...item,\n\t\turl: `/_emdash/api/media/file/${item.storageKey}`,\n\t};\n}\n\n/**\n * List media items\n */\nexport const GET: APIRoute = async ({ request, locals }) => {\n\tconst { emdash, user } = locals;\n\n\tconst denied = requirePerm(user, \"media:read\");\n\tif (denied) return denied;\n\n\tif (!emdash?.handleMediaList) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\tconst url = new URL(request.url);\n\tconst query = parseQuery(url, mediaListQuery);\n\tif (isParseError(query)) return query;\n\n\tconst result = await emdash.handleMediaList({\n\t\tcursor: query.cursor,\n\t\tlimit: query.limit,\n\t\tmimeType: query.mimeType,\n\t\tq: query.q,\n\t});\n\n\tif (!result.success) {\n\t\treturn unwrapResult(result);\n\t}\n\n\t// Add URL to each media item (relative URLs for portability)\n\tconst itemsWithUrl = result.data.items.map((item) => addUrlToMedia(item));\n\n\treturn apiSuccess({ items: itemsWithUrl, nextCursor: result.data.nextCursor });\n};\n\n/**\n * Upload media file\n *\n * Uses the configured storage adapter to store the file.\n */\nexport const POST: APIRoute = async ({ request, locals }) => {\n\tconst { emdash, user } = locals;\n\n\tconst denied = requirePerm(user, \"media:upload\");\n\tif (denied) return denied;\n\n\tif (!emdash?.handleMediaCreate) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\tif (!emdash?.storage) {\n\t\treturn apiError(\"NO_STORAGE\", \"Storage not configured\", 500);\n\t}\n\n\ttry {\n\t\tconst rawMax = emdash.config.maxUploadSize ?? DEFAULT_MAX_UPLOAD_SIZE;\n\t\tif (!Number.isFinite(rawMax) || rawMax <= 0) {\n\t\t\treturn apiError(\"CONFIGURATION_ERROR\", \"Invalid maxUploadSize configuration\", 500);\n\t\t}\n\t\tconst maxUploadSize = rawMax;\n\n\t\t// Best-effort size check before buffering the full multipart body\n\t\tconst contentLength = request.headers.get(\"Content-Length\");\n\t\tif (contentLength && parseInt(contentLength, 10) > maxUploadSize) {\n\t\t\treturn apiError(\"PAYLOAD_TOO_LARGE\", \"Upload too large\", 413);\n\t\t}\n\n\t\tconst formData = await request.formData();\n\t\tconst fileEntry = formData.get(\"file\");\n\t\tconst file = fileEntry instanceof File ? fileEntry : null;\n\n\t\tif (!file) {\n\t\t\treturn apiError(\"NO_FILE\", \"No file provided\", 400);\n\t\t}\n\n\t\t// Validate file type — widen the allowlist when a field-specific list is configured\n\t\tconst fieldIdEntry = formData.get(\"fieldId\");\n\t\tconst fieldId =\n\t\t\ttypeof fieldIdEntry === \"string\" && fieldIdEntry.length > 0 ? fieldIdEntry : null;\n\n\t\tconst fieldAllowlist = fieldId ? await resolveFieldAllowlist(emdash.db, fieldId) : null;\n\t\tconst allowlist = fieldAllowlist ?? [...GLOBAL_UPLOAD_ALLOWLIST];\n\n\t\tif (!matchesMimeAllowlist(file.type, allowlist)) {\n\t\t\treturn apiError(\"INVALID_TYPE\", \"File type not allowed\", 400);\n\t\t}\n\n\t\t// Check file size before buffering\n\t\tif (file.size > maxUploadSize) {\n\t\t\treturn apiError(\n\t\t\t\t\"PAYLOAD_TOO_LARGE\",\n\t\t\t\t`File exceeds maximum size of ${formatFileSize(maxUploadSize)}`,\n\t\t\t\t413,\n\t\t\t);\n\t\t}\n\n\t\t// Get file content and compute hash\n\t\tconst buffer = new Uint8Array(await file.arrayBuffer());\n\t\tconst contentHash = await computeContentHash(buffer);\n\n\t\t// Check for existing media with same content hash (deduplication)\n\t\tconst repo = new MediaRepository(emdash.db);\n\t\tconst existing = await repo.findByContentHash(contentHash);\n\t\tif (existing) {\n\t\t\t// Same content already exists - return existing item\n\t\t\tconst itemWithUrl = addUrlToMedia(existing);\n\t\t\treturn apiSuccess({ item: itemWithUrl, deduplicated: true });\n\t\t}\n\n\t\t// Generate unique storage key\n\t\tconst id = ulid();\n\t\tconst ext = path.extname(file.name) || \"\";\n\t\tconst storageKey = `${id}${ext}`;\n\n\t\t// Upload to storage using the configured adapter\n\t\tawait emdash.storage.upload({\n\t\t\tkey: storageKey,\n\t\t\tbody: buffer,\n\t\t\tcontentType: file.type,\n\t\t});\n\n\t\t// Get image dimensions from form data (sent by client)\n\t\tconst widthEntry = formData.get(\"width\");\n\t\tconst widthStr = typeof widthEntry === \"string\" ? widthEntry : null;\n\t\tconst heightEntry = formData.get(\"height\");\n\t\tconst heightStr = typeof heightEntry === \"string\" ? heightEntry : null;\n\t\tconst width = widthStr ? parseInt(widthStr, 10) : undefined;\n\t\tconst height = heightStr ? parseInt(heightStr, 10) : undefined;\n\n\t\t// Generate placeholder data for images.\n\t\t// If the client sent a thumbnail (small pre-resized image), use that\n\t\t// instead of the full buffer to avoid OOM on memory-constrained runtimes.\n\t\tconst thumbnailEntry = formData.get(\"thumbnail\");\n\t\tconst thumbnail = thumbnailEntry instanceof File ? thumbnailEntry : null;\n\n\t\tlet placeholder: Awaited<ReturnType<typeof generatePlaceholder>> = null;\n\t\tif (file.type.startsWith(\"image/\")) {\n\t\t\tif (thumbnail) {\n\t\t\t\tconst thumbBuffer = new Uint8Array(await thumbnail.arrayBuffer());\n\t\t\t\tplaceholder = await generatePlaceholder(thumbBuffer, thumbnail.type);\n\t\t\t} else {\n\t\t\t\tconst clientDims = width && height ? { width, height } : undefined;\n\t\t\t\tplaceholder = await generatePlaceholder(buffer, file.type, clientDims);\n\t\t\t}\n\t\t}\n\n\t\t// Create media record\n\t\tconst result = await emdash.handleMediaCreate({\n\t\t\tfilename: file.name,\n\t\t\tmimeType: normalizeMime(file.type),\n\t\t\tsize: file.size,\n\t\t\twidth,\n\t\t\theight,\n\t\t\tstorageKey,\n\t\t\tcontentHash,\n\t\t\tblurhash: placeholder?.blurhash,\n\t\t\tdominantColor: placeholder?.dominantColor,\n\t\t\tauthorId: user?.id,\n\t\t});\n\n\t\tif (!result.success) {\n\t\t\t// Clean up the uploaded file on failure\n\t\t\ttry {\n\t\t\t\tawait emdash.storage.delete(storageKey);\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t\treturn unwrapResult(result);\n\t\t}\n\n\t\t// Add URL to the response (relative URL for portability)\n\t\tconst itemWithUrl = addUrlToMedia(result.data.item);\n\n\t\treturn apiSuccess({ item: itemWithUrl }, 201);\n\t} catch (error) {\n\t\treturn handleError(error, \"Upload failed\", \"UPLOAD_ERROR\");\n\t}\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"media.mjs","names":[],"sources":["../../../../src/astro/routes/api/media.ts"],"sourcesContent":["/**\n * Media list and upload endpoint\n *\n * GET /_emdash/api/media - List all media\n * POST /_emdash/api/media - Upload new media (via configured storage adapter)\n */\n\nimport * as path from \"node:path\";\n\nimport type { APIRoute } from \"astro\";\nimport { ulid } from \"ulidx\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, apiSuccess, handleError, unwrapResult } from \"#api/error.js\";\nimport { GLOBAL_UPLOAD_ALLOWLIST, resolveFieldAllowlist } from \"#api/handlers/media-allowlist.js\";\nimport { isParseError, parseQuery } from \"#api/parse.js\";\nimport { DEFAULT_MAX_UPLOAD_SIZE, formatFileSize, mediaListQuery } from \"#api/schemas.js\";\nimport { MediaRepository } from \"#db/repositories/media.js\";\nimport { enrichImageMetadata } from \"#media/enrich.js\";\nimport { matchesMimeAllowlist, normalizeMime } from \"#media/mime.js\";\nimport { computeContentHash } from \"#utils/hash.js\";\n\nimport type { MediaItem } from \"../../types.js\";\n\nexport const prerender = false;\n\n/**\n * Add URL to media items\n * Uses relative URLs to ensure portability across deployments\n */\nfunction addUrlToMedia(item: MediaItem): MediaItem & { url: string } {\n\treturn {\n\t\t...item,\n\t\turl: `/_emdash/api/media/file/${item.storageKey}`,\n\t};\n}\n\n/**\n * List media items\n */\nexport const GET: APIRoute = async ({ request, locals }) => {\n\tconst { emdash, user } = locals;\n\n\tconst denied = requirePerm(user, \"media:read\");\n\tif (denied) return denied;\n\n\tif (!emdash?.handleMediaList) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\tconst url = new URL(request.url);\n\tconst query = parseQuery(url, mediaListQuery);\n\tif (isParseError(query)) return query;\n\n\tconst result = await emdash.handleMediaList({\n\t\tcursor: query.cursor,\n\t\tlimit: query.limit,\n\t\tmimeType: query.mimeType,\n\t\tq: query.q,\n\t});\n\n\tif (!result.success) {\n\t\treturn unwrapResult(result);\n\t}\n\n\t// Add URL to each media item (relative URLs for portability)\n\tconst itemsWithUrl = result.data.items.map((item) => addUrlToMedia(item));\n\n\treturn apiSuccess({ items: itemsWithUrl, nextCursor: result.data.nextCursor });\n};\n\n/**\n * Upload media file\n *\n * Uses the configured storage adapter to store the file.\n */\nexport const POST: APIRoute = async ({ request, locals }) => {\n\tconst { emdash, user } = locals;\n\n\tconst denied = requirePerm(user, \"media:upload\");\n\tif (denied) return denied;\n\n\tif (!emdash?.handleMediaCreate) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\tif (!emdash?.storage) {\n\t\treturn apiError(\"NO_STORAGE\", \"Storage not configured\", 500);\n\t}\n\n\ttry {\n\t\tconst rawMax = emdash.config.maxUploadSize ?? DEFAULT_MAX_UPLOAD_SIZE;\n\t\tif (!Number.isFinite(rawMax) || rawMax <= 0) {\n\t\t\treturn apiError(\"CONFIGURATION_ERROR\", \"Invalid maxUploadSize configuration\", 500);\n\t\t}\n\t\tconst maxUploadSize = rawMax;\n\n\t\t// Best-effort size check before buffering the full multipart body\n\t\tconst contentLength = request.headers.get(\"Content-Length\");\n\t\tif (contentLength && parseInt(contentLength, 10) > maxUploadSize) {\n\t\t\treturn apiError(\"PAYLOAD_TOO_LARGE\", \"Upload too large\", 413);\n\t\t}\n\n\t\tconst formData = await request.formData();\n\t\tconst fileEntry = formData.get(\"file\");\n\t\tconst file = fileEntry instanceof File ? fileEntry : null;\n\n\t\tif (!file) {\n\t\t\treturn apiError(\"NO_FILE\", \"No file provided\", 400);\n\t\t}\n\n\t\t// Validate file type — widen the allowlist when a field-specific list is configured\n\t\tconst fieldIdEntry = formData.get(\"fieldId\");\n\t\tconst fieldId =\n\t\t\ttypeof fieldIdEntry === \"string\" && fieldIdEntry.length > 0 ? fieldIdEntry : null;\n\n\t\tconst fieldAllowlist = fieldId ? await resolveFieldAllowlist(emdash.db, fieldId) : null;\n\t\tconst allowlist = fieldAllowlist ?? [...GLOBAL_UPLOAD_ALLOWLIST];\n\n\t\tif (!matchesMimeAllowlist(file.type, allowlist)) {\n\t\t\treturn apiError(\"INVALID_TYPE\", \"File type not allowed\", 400);\n\t\t}\n\n\t\t// Check file size before buffering\n\t\tif (file.size > maxUploadSize) {\n\t\t\treturn apiError(\n\t\t\t\t\"PAYLOAD_TOO_LARGE\",\n\t\t\t\t`File exceeds maximum size of ${formatFileSize(maxUploadSize)}`,\n\t\t\t\t413,\n\t\t\t);\n\t\t}\n\n\t\t// Get file content and compute hash\n\t\tconst buffer = new Uint8Array(await file.arrayBuffer());\n\t\tconst contentHash = await computeContentHash(buffer);\n\n\t\t// Check for existing media with same content hash (deduplication)\n\t\tconst repo = new MediaRepository(emdash.db);\n\t\tconst existing = await repo.findByContentHash(contentHash);\n\t\tif (existing) {\n\t\t\t// Same content already exists - return existing item\n\t\t\tconst itemWithUrl = addUrlToMedia(existing);\n\t\t\treturn apiSuccess({ item: itemWithUrl, deduplicated: true });\n\t\t}\n\n\t\t// Generate unique storage key\n\t\tconst id = ulid();\n\t\tconst ext = path.extname(file.name) || \"\";\n\t\tconst storageKey = `${id}${ext}`;\n\n\t\t// Upload to storage using the configured adapter\n\t\tawait emdash.storage.upload({\n\t\t\tkey: storageKey,\n\t\t\tbody: buffer,\n\t\t\tcontentType: file.type,\n\t\t});\n\n\t\t// Get image dimensions from form data (sent by client)\n\t\tconst widthEntry = formData.get(\"width\");\n\t\tconst widthStr = typeof widthEntry === \"string\" ? widthEntry : null;\n\t\tconst heightEntry = formData.get(\"height\");\n\t\tconst heightStr = typeof heightEntry === \"string\" ? heightEntry : null;\n\t\tconst width = widthStr ? parseInt(widthStr, 10) : undefined;\n\t\tconst height = heightStr ? parseInt(heightStr, 10) : undefined;\n\n\t\t// Derive dimensions + LQIP placeholders via the shared helper.\n\t\t// If the client sent a downscaled thumbnail, decode that for the blurhash\n\t\t// (avoids OOM on large originals on memory-constrained runtimes).\n\t\tconst thumbnailEntry = formData.get(\"thumbnail\");\n\t\tconst thumbnail = thumbnailEntry instanceof File ? thumbnailEntry : null;\n\t\tconst enriched = await enrichImageMetadata(buffer, file.type, {\n\t\t\tknownDimensions: width != null && height != null ? { width, height } : undefined,\n\t\t\tplaceholder: thumbnail\n\t\t\t\t? { bytes: new Uint8Array(await thumbnail.arrayBuffer()), contentType: thumbnail.type }\n\t\t\t\t: undefined,\n\t\t});\n\n\t\t// Create media record\n\t\tconst result = await emdash.handleMediaCreate({\n\t\t\tfilename: file.name,\n\t\t\tmimeType: normalizeMime(file.type),\n\t\t\tsize: file.size,\n\t\t\t// Client dimensions win over server header dimensions: the browser's\n\t\t\t// naturalWidth/Height apply EXIF orientation, while image-size reports\n\t\t\t// raw (pre-orientation) header dims — swapped for 90°/270° JPEGs.\n\t\t\twidth: width ?? enriched.width,\n\t\t\theight: height ?? enriched.height,\n\t\t\tstorageKey,\n\t\t\tcontentHash,\n\t\t\tblurhash: enriched.blurhash,\n\t\t\tdominantColor: enriched.dominantColor,\n\t\t\tauthorId: user?.id,\n\t\t});\n\n\t\tif (!result.success) {\n\t\t\t// Clean up the uploaded file on failure\n\t\t\ttry {\n\t\t\t\tawait emdash.storage.delete(storageKey);\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t\treturn unwrapResult(result);\n\t\t}\n\n\t\t// Add URL to the response (relative URL for portability)\n\t\tconst itemWithUrl = addUrlToMedia(result.data.item);\n\n\t\treturn apiSuccess({ item: itemWithUrl }, 201);\n\t} catch (error) {\n\t\treturn handleError(error, \"Upload failed\", \"UPLOAD_ERROR\");\n\t}\n};\n"],"mappings":"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"}