emdash 0.18.0 → 0.20.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 (528) hide show
  1. package/dist/{adapters-C5AWLJSD.d.mts → adapters-BzIHV3sw.d.mts} +1 -1
  2. package/dist/{adapters-C5AWLJSD.d.mts.map → adapters-BzIHV3sw.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-CyYLEJkp.mjs → allowed-origins-B1u7Qnvg.mjs} +2 -2
  4. package/dist/{allowed-origins-CyYLEJkp.mjs.map → allowed-origins-B1u7Qnvg.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +15 -15
  7. package/dist/api/schemas/index.d.mts +2 -2
  8. package/dist/api/schemas/index.mjs +3 -3
  9. package/dist/{api-Cs7DAACP.mjs → api-DStv36ik.mjs} +123 -20
  10. package/dist/api-DStv36ik.mjs.map +1 -0
  11. package/dist/{api-tokens-VrXNiNvV.mjs → api-tokens-DPfhPu5V.mjs} +2 -2
  12. package/dist/{api-tokens-VrXNiNvV.mjs.map → api-tokens-DPfhPu5V.mjs.map} +1 -1
  13. package/dist/{apply-BWMV4Zmw.mjs → apply-Dr7snAMT.mjs} +23 -23
  14. package/dist/apply-Dr7snAMT.mjs.map +1 -0
  15. package/dist/astro/index.d.mts +10 -10
  16. package/dist/astro/index.d.mts.map +1 -1
  17. package/dist/astro/index.mjs +115 -25
  18. package/dist/astro/index.mjs.map +1 -1
  19. package/dist/astro/middleware/auth.d.mts +9 -9
  20. package/dist/astro/middleware/auth.mjs +6 -6
  21. package/dist/astro/middleware/redirect.mjs +4 -4
  22. package/dist/astro/middleware/request-context.mjs +2 -2
  23. package/dist/astro/middleware/setup.mjs +1 -1
  24. package/dist/astro/middleware.d.mts +26 -4
  25. package/dist/astro/middleware.d.mts.map +1 -1
  26. package/dist/astro/middleware.mjs +242 -259
  27. package/dist/astro/middleware.mjs.map +1 -1
  28. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
  29. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
  30. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
  31. package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
  32. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -5
  33. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
  34. package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
  35. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
  36. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -12
  37. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +12 -12
  38. package/dist/astro/routes/api/admin/bylines/index.mjs +12 -12
  39. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +11 -11
  40. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  41. package/dist/astro/routes/api/admin/comments/bulk.mjs +8 -8
  42. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  43. package/dist/astro/routes/api/admin/comments/index.mjs +8 -8
  44. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
  45. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
  46. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
  47. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
  48. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +34 -34
  49. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +34 -34
  50. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +33 -33
  51. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +33 -33
  52. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +33 -33
  53. package/dist/astro/routes/api/admin/plugins/index.mjs +33 -33
  54. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  55. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +33 -33
  56. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +33 -33
  57. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +33 -33
  58. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +33 -33
  59. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +34 -34
  60. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +33 -33
  61. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +34 -34
  62. package/dist/astro/routes/api/admin/plugins/updates.mjs +33 -33
  63. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +33 -33
  64. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  65. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +33 -33
  66. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
  67. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  68. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  69. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
  70. package/dist/astro/routes/api/admin/users/index.mjs +5 -5
  71. package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -5
  72. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  73. package/dist/astro/routes/api/auth/invite/complete.mjs +10 -10
  74. package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
  75. package/dist/astro/routes/api/auth/invite/register-options.mjs +9 -9
  76. package/dist/astro/routes/api/auth/logout.mjs +3 -3
  77. package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
  78. package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
  79. package/dist/astro/routes/api/auth/me.mjs +6 -6
  80. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  81. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  82. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  83. package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
  84. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  85. package/dist/astro/routes/api/auth/passkey/options.mjs +10 -10
  86. package/dist/astro/routes/api/auth/passkey/register/options.mjs +9 -9
  87. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +10 -10
  88. package/dist/astro/routes/api/auth/passkey/verify.mjs +10 -10
  89. package/dist/astro/routes/api/auth/signup/complete.mjs +10 -10
  90. package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
  91. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  92. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -11
  93. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  94. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +6 -5
  95. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  96. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  97. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  98. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +8 -8
  99. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +9 -8
  100. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  101. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  102. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  103. package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts.map +1 -1
  104. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +12 -10
  105. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  106. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +11 -11
  107. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  108. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +6 -5
  109. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  110. package/dist/astro/routes/api/content/_collection_/_id_.mjs +9 -8
  111. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  112. package/dist/astro/routes/api/content/_collection_/authors.d.mts +8 -0
  113. package/dist/astro/routes/api/content/_collection_/authors.d.mts.map +1 -0
  114. package/dist/astro/routes/api/content/_collection_/authors.mjs +19 -0
  115. package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -0
  116. package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
  117. package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
  118. package/dist/astro/routes/api/dashboard.mjs +7 -7
  119. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  120. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  121. package/dist/astro/routes/api/import/probe.mjs +6 -6
  122. package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
  123. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  124. package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
  125. package/dist/astro/routes/api/import/wordpress/media.mjs +6 -6
  126. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  127. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
  128. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  129. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +6 -6
  130. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  131. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +9 -9
  132. package/dist/astro/routes/api/manifest.mjs +4 -4
  133. package/dist/astro/routes/api/mcp.mjs +29 -29
  134. package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
  135. package/dist/astro/routes/api/media/_id_.mjs +6 -6
  136. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  137. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  138. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  139. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  140. package/dist/astro/routes/api/media/upload-url.mjs +7 -7
  141. package/dist/astro/routes/api/media.mjs +8 -8
  142. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +7 -7
  143. package/dist/astro/routes/api/menus/_name_/items.mjs +7 -7
  144. package/dist/astro/routes/api/menus/_name_/reorder.mjs +7 -7
  145. package/dist/astro/routes/api/menus/_name_/translations.mjs +7 -7
  146. package/dist/astro/routes/api/menus/_name_.mjs +7 -7
  147. package/dist/astro/routes/api/menus/index.mjs +7 -7
  148. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  149. package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
  150. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  151. package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
  152. package/dist/astro/routes/api/oauth/register.mjs +3 -3
  153. package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
  154. package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
  155. package/dist/astro/routes/api/oauth/token.mjs +6 -6
  156. package/dist/astro/routes/api/openapi.json.mjs +17 -3
  157. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  158. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  159. package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
  160. package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
  161. package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
  162. package/dist/astro/routes/api/redirects/index.mjs +10 -10
  163. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  164. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  165. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +33 -33
  166. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +33 -33
  167. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +33 -33
  168. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +33 -33
  169. package/dist/astro/routes/api/schema/collections/index.mjs +33 -33
  170. package/dist/astro/routes/api/schema/index.mjs +9 -14
  171. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  172. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +33 -33
  173. package/dist/astro/routes/api/schema/orphans/index.mjs +33 -33
  174. package/dist/astro/routes/api/search/enable.mjs +9 -9
  175. package/dist/astro/routes/api/search/index.mjs +8 -8
  176. package/dist/astro/routes/api/search/rebuild.mjs +9 -9
  177. package/dist/astro/routes/api/search/stats.mjs +6 -6
  178. package/dist/astro/routes/api/search/suggest.mjs +8 -8
  179. package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
  180. package/dist/astro/routes/api/sections/index.mjs +8 -8
  181. package/dist/astro/routes/api/settings/email.mjs +5 -5
  182. package/dist/astro/routes/api/settings.mjs +12 -12
  183. package/dist/astro/routes/api/setup/admin-verify.mjs +11 -11
  184. package/dist/astro/routes/api/setup/admin.mjs +10 -10
  185. package/dist/astro/routes/api/setup/dev-bypass.mjs +23 -23
  186. package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
  187. package/dist/astro/routes/api/setup/index.mjs +23 -23
  188. package/dist/astro/routes/api/setup/status.mjs +4 -4
  189. package/dist/astro/routes/api/snapshot.mjs +6 -6
  190. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -11
  191. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -11
  192. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -11
  193. package/dist/astro/routes/api/taxonomies/index.mjs +11 -11
  194. package/dist/astro/routes/api/themes/preview.mjs +6 -6
  195. package/dist/astro/routes/api/typegen.mjs +5 -5
  196. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  197. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  198. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  199. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
  200. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -8
  201. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  202. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -8
  203. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  204. package/dist/astro/routes/api/widget-areas/_name_.mjs +6 -5
  205. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  206. package/dist/astro/routes/api/widget-areas/index.mjs +9 -8
  207. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  208. package/dist/astro/routes/api/widget-components.mjs +3 -3
  209. package/dist/astro/routes/robots.txt.mjs +7 -7
  210. package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
  211. package/dist/astro/routes/sitemap-_collection_.xml.mjs +16 -9
  212. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  213. package/dist/astro/routes/sitemap.xml.mjs +8 -8
  214. package/dist/astro/types.d.mts +19 -12
  215. package/dist/astro/types.d.mts.map +1 -1
  216. package/dist/auth/providers/github.d.mts +1 -1
  217. package/dist/auth/providers/google.d.mts +1 -1
  218. package/dist/{authorize-CotM4Yiu.mjs → authorize-DsMSVSaY.mjs} +2 -2
  219. package/dist/{authorize-CotM4Yiu.mjs.map → authorize-DsMSVSaY.mjs.map} +1 -1
  220. package/dist/{byline-CWQ9aSoz.mjs → byline-DUx48sJp.mjs} +6 -6
  221. package/dist/{byline-CWQ9aSoz.mjs.map → byline-DUx48sJp.mjs.map} +1 -1
  222. package/dist/{byline-fields-DC3Wkk-U.mjs → byline-fields--WxSNS79.mjs} +2 -2
  223. package/dist/{byline-fields-DC3Wkk-U.mjs.map → byline-fields--WxSNS79.mjs.map} +1 -1
  224. package/dist/{byline-fields-Dr-xcb6S.mjs → byline-fields-8TMtkBnH.mjs} +3 -3
  225. package/dist/{byline-fields-Dr-xcb6S.mjs.map → byline-fields-8TMtkBnH.mjs.map} +1 -1
  226. package/dist/{byline-fields-BNy7Ng1U.d.mts → byline-fields-DbibsvTl.d.mts} +30 -2
  227. package/dist/byline-fields-DbibsvTl.d.mts.map +1 -0
  228. package/dist/{byline-registry-CxK5g559.mjs → byline-registry-CWP7I71B.mjs} +3 -3
  229. package/dist/{byline-registry-CxK5g559.mjs.map → byline-registry-CWP7I71B.mjs.map} +1 -1
  230. package/dist/{bylines-LJMgENMI.mjs → bylines-BdxWCnPL.mjs} +3 -3
  231. package/dist/{bylines-LJMgENMI.mjs.map → bylines-BdxWCnPL.mjs.map} +1 -1
  232. package/dist/{bylines-BJSva1Un.mjs → bylines-s8c2DXbH.mjs} +50 -6
  233. package/dist/{bylines-BJSva1Un.mjs.map → bylines-s8c2DXbH.mjs.map} +1 -1
  234. package/dist/{cache-lZL7SgVb.mjs → cache-B_HzASVT.mjs} +3 -3
  235. package/dist/{cache-lZL7SgVb.mjs.map → cache-B_HzASVT.mjs.map} +1 -1
  236. package/dist/{challenge-store-DGwuCc4R.mjs → challenge-store-DXX3rfdI.mjs} +1 -1
  237. package/dist/{challenge-store-DGwuCc4R.mjs.map → challenge-store-DXX3rfdI.mjs.map} +1 -1
  238. package/dist/{chunks-BU-vP9Dh.mjs → chunks-BerYVuve.mjs} +2 -2
  239. package/dist/{chunks-BU-vP9Dh.mjs.map → chunks-BerYVuve.mjs.map} +1 -1
  240. package/dist/cli/index.mjs +46 -32
  241. package/dist/cli/index.mjs.map +1 -1
  242. package/dist/client/cf-access.d.mts +1 -1
  243. package/dist/client/index.d.mts +1 -1
  244. package/dist/client/index.mjs +1 -1
  245. package/dist/{comment-C4jVbCM8.mjs → comment-sqQxNpN3.mjs} +2 -2
  246. package/dist/{comment-C4jVbCM8.mjs.map → comment-sqQxNpN3.mjs.map} +1 -1
  247. package/dist/{comments-BTAbC0Ek.mjs → comments-Vkivawyl.mjs} +3 -3
  248. package/dist/{comments-BTAbC0Ek.mjs.map → comments-Vkivawyl.mjs.map} +1 -1
  249. package/dist/{components-CTfpu3PZ.mjs → components-CK0cuUoH.mjs} +1 -1
  250. package/dist/{components-CTfpu3PZ.mjs.map → components-CK0cuUoH.mjs.map} +1 -1
  251. package/dist/{content-CyqOmOzm.mjs → content-BIlVx-RX.mjs} +132 -43
  252. package/dist/content-BIlVx-RX.mjs.map +1 -0
  253. package/dist/{context-DZ7bEh5-.mjs → context-Y7BRkWes.mjs} +10 -10
  254. package/dist/{context-DZ7bEh5-.mjs.map → context-Y7BRkWes.mjs.map} +1 -1
  255. package/dist/{cron-DZovZUnC.mjs → cron-BJ2ClIlj.mjs} +4 -3
  256. package/dist/cron-BJ2ClIlj.mjs.map +1 -0
  257. package/dist/{dashboard-B5WQpNTP.mjs → dashboard-2JgAMWxK.mjs} +4 -4
  258. package/dist/{dashboard-B5WQpNTP.mjs.map → dashboard-2JgAMWxK.mjs.map} +1 -1
  259. package/dist/database/instrumentation.d.mts +10 -1
  260. package/dist/database/instrumentation.d.mts.map +1 -1
  261. package/dist/database/instrumentation.mjs +13 -1
  262. package/dist/database/instrumentation.mjs.map +1 -1
  263. package/dist/db/index.d.mts +3 -3
  264. package/dist/db/index.mjs +1 -1
  265. package/dist/db/libsql.d.mts +1 -1
  266. package/dist/db/postgres.d.mts +1 -1
  267. package/dist/db/sqlite.d.mts +1 -1
  268. package/dist/{default-xLFNSsZ9.mjs → default-IlBaTFxM.mjs} +1 -1
  269. package/dist/{default-xLFNSsZ9.mjs.map → default-IlBaTFxM.mjs.map} +1 -1
  270. package/dist/{device-flow-ptLrVINd.mjs → device-flow-R23SIbQ2.mjs} +5 -5
  271. package/dist/{device-flow-ptLrVINd.mjs.map → device-flow-R23SIbQ2.mjs.map} +1 -1
  272. package/dist/{error-DJOsMVSt.mjs → error-RwM4dD35.mjs} +2 -2
  273. package/dist/{error-DJOsMVSt.mjs.map → error-RwM4dD35.mjs.map} +1 -1
  274. package/dist/{escape-bIyGoW5W.mjs → escape-Ds07EEyu.mjs} +1 -1
  275. package/dist/{escape-bIyGoW5W.mjs.map → escape-Ds07EEyu.mjs.map} +1 -1
  276. package/dist/{fts-manager-DR1ERA0c.mjs → fts-manager-1RgHmopc.mjs} +2 -2
  277. package/dist/{fts-manager-DR1ERA0c.mjs.map → fts-manager-1RgHmopc.mjs.map} +1 -1
  278. package/dist/{index-CjKdMZ3U.d.mts → index-B1keaX5Y.d.mts} +237 -24
  279. package/dist/index-B1keaX5Y.d.mts.map +1 -0
  280. package/dist/{index-D60_SzHG.d.mts → index-DR56od45.d.mts} +3 -3
  281. package/dist/{index-D60_SzHG.d.mts.map → index-DR56od45.d.mts.map} +1 -1
  282. package/dist/index.d.mts +17 -17
  283. package/dist/index.mjs +46 -46
  284. package/dist/{load-6ZrRhepW.mjs → load-BBetCvLC.mjs} +2 -2
  285. package/dist/{load-6ZrRhepW.mjs.map → load-BBetCvLC.mjs.map} +1 -1
  286. package/dist/{loader-Dyx8dhFV.mjs → loader-ZN1ll-d-.mjs} +36 -37
  287. package/dist/loader-ZN1ll-d-.mjs.map +1 -0
  288. package/dist/{manifest-schema-Cj-YrzrF.mjs → manifest-schema-BtwbL_vj.mjs} +55 -2
  289. package/dist/manifest-schema-BtwbL_vj.mjs.map +1 -0
  290. package/dist/media/index.d.mts +1 -1
  291. package/dist/media/index.mjs +1 -1
  292. package/dist/media/local-runtime.d.mts +11 -11
  293. package/dist/media/local-runtime.mjs +6 -6
  294. package/dist/{media-C-oovGCG.mjs → media-JOf3pNkw.mjs} +2 -2
  295. package/dist/{media-C-oovGCG.mjs.map → media-JOf3pNkw.mjs.map} +1 -1
  296. package/dist/{media-allowlist-CMcoYIjQ.mjs → media-allowlist-Dknq-OFY.mjs} +1 -1
  297. package/dist/{media-allowlist-CMcoYIjQ.mjs.map → media-allowlist-Dknq-OFY.mjs.map} +1 -1
  298. package/dist/media-url-VClf8glU.mjs +26 -0
  299. package/dist/media-url-VClf8glU.mjs.map +1 -0
  300. package/dist/{menus-DugoYwTX.mjs → menus-DX4_E01q.mjs} +3 -3
  301. package/dist/{menus-DugoYwTX.mjs.map → menus-DX4_E01q.mjs.map} +1 -1
  302. package/dist/{menus-BKkxXCmd.mjs → menus-DrQLusqj.mjs} +87 -37
  303. package/dist/menus-DrQLusqj.mjs.map +1 -0
  304. package/dist/{mode-BjlXswIw.mjs → mode-CO2vQHfq.mjs} +1 -1
  305. package/dist/{mode-BjlXswIw.mjs.map → mode-CO2vQHfq.mjs.map} +1 -1
  306. package/dist/{normalize-DVV8nbrL.mjs → normalize-CK5o04zr.mjs} +2 -2
  307. package/dist/{normalize-DVV8nbrL.mjs.map → normalize-CK5o04zr.mjs.map} +1 -1
  308. package/dist/{oauth-authorization-DvBAL75d.mjs → oauth-authorization-Bw4NdF_S.mjs} +5 -5
  309. package/dist/{oauth-authorization-DvBAL75d.mjs.map → oauth-authorization-Bw4NdF_S.mjs.map} +1 -1
  310. package/dist/{oauth-clients-8mPDStMv.mjs → oauth-clients-BGGFp57s.mjs} +1 -1
  311. package/dist/{oauth-clients-8mPDStMv.mjs.map → oauth-clients-BGGFp57s.mjs.map} +1 -1
  312. package/dist/{oauth-state-store-BJ7YtrfD.mjs → oauth-state-store-97x0xtN2.mjs} +1 -1
  313. package/dist/{oauth-state-store-BJ7YtrfD.mjs.map → oauth-state-store-97x0xtN2.mjs.map} +1 -1
  314. package/dist/{oauth-user-lookup-BdDSDvjF.mjs → oauth-user-lookup-B_vnZHKO.mjs} +1 -1
  315. package/dist/{oauth-user-lookup-BdDSDvjF.mjs.map → oauth-user-lookup-B_vnZHKO.mjs.map} +1 -1
  316. package/dist/{options-BL4X94qY.mjs → options-BPCVnesz.mjs} +1 -1
  317. package/dist/{options-BL4X94qY.mjs.map → options-BPCVnesz.mjs.map} +1 -1
  318. package/dist/{options-tb7DJROi.d.mts → options-DyYIYpPd.d.mts} +3 -3
  319. package/dist/{options-tb7DJROi.d.mts.map → options-DyYIYpPd.d.mts.map} +1 -1
  320. package/dist/page/index.d.mts +2 -2
  321. package/dist/{parse-BBkFmLVr.mjs → parse-CrGndy1A.mjs} +2 -2
  322. package/dist/{parse-BBkFmLVr.mjs.map → parse-CrGndy1A.mjs.map} +1 -1
  323. package/dist/{passkey-config-BDVM86Tj.mjs → passkey-config-C3QgnQnU.mjs} +1 -1
  324. package/dist/{passkey-config-BDVM86Tj.mjs.map → passkey-config-C3QgnQnU.mjs.map} +1 -1
  325. package/dist/{patterns-CqG5Ya3i.mjs → patterns-p-RBdTbM.mjs} +1 -1
  326. package/dist/{patterns-CqG5Ya3i.mjs.map → patterns-p-RBdTbM.mjs.map} +1 -1
  327. package/dist/{placeholder-B9lUUEmj.d.mts → placeholder-CVBv5z8k.d.mts} +1 -1
  328. package/dist/{placeholder-B9lUUEmj.d.mts.map → placeholder-CVBv5z8k.d.mts.map} +1 -1
  329. package/dist/plugin-types.d.mts +1 -1
  330. package/dist/plugin-utils.d.mts +9 -9
  331. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  332. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  333. package/dist/{public-url-egRHCy1m.mjs → public-url-BFVC2OTJ.mjs} +1 -1
  334. package/dist/{public-url-egRHCy1m.mjs.map → public-url-BFVC2OTJ.mjs.map} +1 -1
  335. package/dist/{query-Ctlq1aOk.mjs → query-CbUcI4Xk.mjs} +33 -17
  336. package/dist/query-CbUcI4Xk.mjs.map +1 -0
  337. package/dist/{rate-limit-CH6W6ikK.mjs → rate-limit-C7hjdkS5.mjs} +2 -2
  338. package/dist/{rate-limit-CH6W6ikK.mjs.map → rate-limit-C7hjdkS5.mjs.map} +1 -1
  339. package/dist/{redirect-Cw3JTlmj.mjs → redirect-B_q19j4v.mjs} +1 -1
  340. package/dist/{redirect-Cw3JTlmj.mjs.map → redirect-B_q19j4v.mjs.map} +1 -1
  341. package/dist/{redirect-C6tJA7tk.mjs → redirect-CRWIt8Zj.mjs} +3 -3
  342. package/dist/{redirect-C6tJA7tk.mjs.map → redirect-CRWIt8Zj.mjs.map} +1 -1
  343. package/dist/{redirects-C0L9JUk4.mjs → redirects-CCbCqCCd.mjs} +28 -4
  344. package/dist/redirects-CCbCqCCd.mjs.map +1 -0
  345. package/dist/{redirects-CacE9eQa.mjs → redirects-DxVoR7PI.mjs} +5 -5
  346. package/dist/{redirects-CacE9eQa.mjs.map → redirects-DxVoR7PI.mjs.map} +1 -1
  347. package/dist/{registry-CIDxZbhh.mjs → registry-brYh-rAT.mjs} +6 -6
  348. package/dist/{registry-CIDxZbhh.mjs.map → registry-brYh-rAT.mjs.map} +1 -1
  349. package/dist/{request-cache-BYMs-BGX.mjs → request-cache-D32LpnmI.mjs} +1 -1
  350. package/dist/{request-cache-BYMs-BGX.mjs.map → request-cache-D32LpnmI.mjs.map} +1 -1
  351. package/dist/request-context.d.mts +7 -0
  352. package/dist/request-context.d.mts.map +1 -1
  353. package/dist/request-context.mjs +2 -1
  354. package/dist/request-context.mjs.map +1 -1
  355. package/dist/{runner-pt6Wl-l-.mjs → runner--4wMWwKM.mjs} +217 -166
  356. package/dist/runner--4wMWwKM.mjs.map +1 -0
  357. package/dist/{runner-DM1yR5qd.d.mts → runner-DTdhuI9i.d.mts} +2 -2
  358. package/dist/{runner-DM1yR5qd.d.mts.map → runner-DTdhuI9i.d.mts.map} +1 -1
  359. package/dist/runtime.d.mts +10 -10
  360. package/dist/runtime.mjs +2 -2
  361. package/dist/{schema-B4tk0HAG.mjs → schema-C1E70ug_.mjs} +5 -5
  362. package/dist/{schema-B4tk0HAG.mjs.map → schema-C1E70ug_.mjs.map} +1 -1
  363. package/dist/{search-f-fNfwab.mjs → search-B3SGZw91.mjs} +4 -4
  364. package/dist/{search-f-fNfwab.mjs.map → search-B3SGZw91.mjs.map} +1 -1
  365. package/dist/{secrets-YYbTgB1w.mjs → secrets-ChPTmy9x.mjs} +2 -2
  366. package/dist/{secrets-YYbTgB1w.mjs.map → secrets-ChPTmy9x.mjs.map} +1 -1
  367. package/dist/{sections-biElLfT9.mjs → sections-D_lVzwRZ.mjs} +3 -3
  368. package/dist/{sections-biElLfT9.mjs.map → sections-D_lVzwRZ.mjs.map} +1 -1
  369. package/dist/seed/index.d.mts +2 -2
  370. package/dist/seed/index.mjs +17 -17
  371. package/dist/seo/index.d.mts +1 -1
  372. package/dist/seo/index.d.mts.map +1 -1
  373. package/dist/seo/index.mjs +3 -12
  374. package/dist/seo/index.mjs.map +1 -1
  375. package/dist/{seo-BR39kvTF.mjs → seo-B5e6y9Wk.mjs} +2 -2
  376. package/dist/{seo-BR39kvTF.mjs.map → seo-B5e6y9Wk.mjs.map} +1 -1
  377. package/dist/{seo-DfjLvu8i.mjs → seo-D_LPkOtu.mjs} +4 -3
  378. package/dist/seo-D_LPkOtu.mjs.map +1 -0
  379. package/dist/{service-BhR2acnc.mjs → service-ChDcsTBs.mjs} +3 -3
  380. package/dist/{service-BhR2acnc.mjs.map → service-ChDcsTBs.mjs.map} +1 -1
  381. package/dist/{settings-D_NJvjgN.mjs → settings-Cv47v9u8.mjs} +3 -3
  382. package/dist/{settings-D_NJvjgN.mjs.map → settings-Cv47v9u8.mjs.map} +1 -1
  383. package/dist/settings-DfxiWY_s.mjs +411 -0
  384. package/dist/settings-DfxiWY_s.mjs.map +1 -0
  385. package/dist/{setup-complete-VoEZfasi.mjs → setup-complete-yvPE4OsP.mjs} +2 -2
  386. package/dist/{setup-complete-VoEZfasi.mjs.map → setup-complete-yvPE4OsP.mjs.map} +1 -1
  387. package/dist/{setup-nonce-Bm0uKqmf.mjs → setup-nonce-C9aFzb94.mjs} +1 -1
  388. package/dist/{setup-nonce-Bm0uKqmf.mjs.map → setup-nonce-C9aFzb94.mjs.map} +1 -1
  389. package/dist/{site-url-Cm8-sJy7.mjs → site-url-CnHlmAs9.mjs} +2 -2
  390. package/dist/{site-url-Cm8-sJy7.mjs.map → site-url-CnHlmAs9.mjs.map} +1 -1
  391. package/dist/storage/local.d.mts +1 -1
  392. package/dist/storage/s3.d.mts +1 -1
  393. package/dist/{taxonomies-Mhn9rjTQ.mjs → taxonomies-BILwiyGk.mjs} +4 -4
  394. package/dist/{taxonomies-Mhn9rjTQ.mjs.map → taxonomies-BILwiyGk.mjs.map} +1 -1
  395. package/dist/{taxonomies-Crtzy4MT.mjs → taxonomies-BdAmbOwx.mjs} +50 -12
  396. package/dist/taxonomies-BdAmbOwx.mjs.map +1 -0
  397. package/dist/{taxonomy-DTZrIQpi.mjs → taxonomy-CdllE4oq.mjs} +3 -3
  398. package/dist/{taxonomy-DTZrIQpi.mjs.map → taxonomy-CdllE4oq.mjs.map} +1 -1
  399. package/dist/{transaction-NQj4VJ7Z.mjs → transaction-x2tJQ-A1.mjs} +1 -1
  400. package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-x2tJQ-A1.mjs.map} +1 -1
  401. package/dist/{transport-OnMNbsIA.d.mts → transport-B7PPP2CC.d.mts} +1 -1
  402. package/dist/{transport-OnMNbsIA.d.mts.map → transport-B7PPP2CC.d.mts.map} +1 -1
  403. package/dist/{transport--Ck3RBin.mjs → transport-CmpLD7W3.mjs} +1 -1
  404. package/dist/{transport--Ck3RBin.mjs.map → transport-CmpLD7W3.mjs.map} +1 -1
  405. package/dist/{types-DWnN7weG.d.mts → types-BFgrqwSk.d.mts} +1 -1
  406. package/dist/{types-DWnN7weG.d.mts.map → types-BFgrqwSk.d.mts.map} +1 -1
  407. package/dist/{types-Qa7-HJJC.d.mts → types-BH8-30hc.d.mts} +1 -1
  408. package/dist/{types-Qa7-HJJC.d.mts.map → types-BH8-30hc.d.mts.map} +1 -1
  409. package/dist/{types-DawhLFwy.d.mts → types-BPzXTV9x.d.mts} +26 -1
  410. package/dist/{types-DawhLFwy.d.mts.map → types-BPzXTV9x.d.mts.map} +1 -1
  411. package/dist/{types-DbCWhHet.d.mts → types-BUUVn1zr.d.mts} +2 -2
  412. package/dist/types-BUUVn1zr.d.mts.map +1 -0
  413. package/dist/{types-K3MDsxpy.mjs → types-BXSUSAjt.mjs} +16 -3
  414. package/dist/{types-K3MDsxpy.mjs.map → types-BXSUSAjt.mjs.map} +1 -1
  415. package/dist/{types-DMwSpvcw.d.mts → types-CPAPl93j.d.mts} +9 -3
  416. package/dist/{types-DMwSpvcw.d.mts.map → types-CPAPl93j.d.mts.map} +1 -1
  417. package/dist/types-CZI4E3qG.mjs +3 -0
  418. package/dist/{types-kwqCOUxj.d.mts → types-D4kUqbHh.d.mts} +1 -1
  419. package/dist/{types-kwqCOUxj.d.mts.map → types-D4kUqbHh.d.mts.map} +1 -1
  420. package/dist/{types-i8_uzhMD.d.mts → types-DTniiNto.d.mts} +19 -4
  421. package/dist/types-DTniiNto.d.mts.map +1 -0
  422. package/dist/{types-D8bhH891.mjs → types-DZk_y-MU.mjs} +1 -1
  423. package/dist/types-DZk_y-MU.mjs.map +1 -0
  424. package/dist/{types-DX6v9KzJ.d.mts → types-S15DXXNi.d.mts} +1 -1
  425. package/dist/{types-DX6v9KzJ.d.mts.map → types-S15DXXNi.d.mts.map} +1 -1
  426. package/dist/{user-DzEUl5zA.mjs → user-C0um7wrg.mjs} +18 -2
  427. package/dist/user-C0um7wrg.mjs.map +1 -0
  428. package/dist/{validate-JCXcsqiY.mjs → validate-Bz4vqcX1.mjs} +6 -3
  429. package/dist/validate-Bz4vqcX1.mjs.map +1 -0
  430. package/dist/{validate-Dy6nkNls.d.mts → validate-CNwkPWzz.d.mts} +13 -5
  431. package/dist/validate-CNwkPWzz.d.mts.map +1 -0
  432. package/dist/{validation-Bq-VyKJg.mjs → validation-DgGTJm3u.mjs} +5 -5
  433. package/dist/{validation-Bq-VyKJg.mjs.map → validation-DgGTJm3u.mjs.map} +1 -1
  434. package/dist/version-D-5txk2m.mjs +7 -0
  435. package/dist/{version-CnS-Cr8A.mjs.map → version-D-5txk2m.mjs.map} +1 -1
  436. package/dist/{widgets-Bap1eS1X.mjs → widgets-DZfmAbE4.mjs} +47 -44
  437. package/dist/widgets-DZfmAbE4.mjs.map +1 -0
  438. package/dist/{zod-generator-BSDpkqSH.mjs → zod-generator-Djo_VHCt.mjs} +2 -2
  439. package/dist/{zod-generator-BSDpkqSH.mjs.map → zod-generator-Djo_VHCt.mjs.map} +1 -1
  440. package/package.json +10 -10
  441. package/src/api/handlers/content.ts +107 -8
  442. package/src/api/handlers/index.ts +2 -0
  443. package/src/api/handlers/marketplace.ts +2 -5
  444. package/src/api/handlers/registry.ts +70 -0
  445. package/src/api/handlers/seo.ts +9 -1
  446. package/src/api/openapi/document.ts +25 -0
  447. package/src/api/schemas/content.ts +33 -0
  448. package/src/api/schemas/schema.ts +13 -1
  449. package/src/astro/integration/index.ts +98 -0
  450. package/src/astro/integration/routes.ts +6 -0
  451. package/src/astro/integration/virtual-modules.ts +39 -0
  452. package/src/astro/integration/vite-config.ts +12 -0
  453. package/src/astro/middleware.ts +48 -6
  454. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
  455. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +4 -2
  456. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +8 -4
  457. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +4 -2
  458. package/src/astro/routes/api/content/[collection]/[id].ts +4 -2
  459. package/src/astro/routes/api/content/[collection]/authors.ts +34 -0
  460. package/src/astro/routes/api/schema/index.ts +7 -15
  461. package/src/astro/routes/sitemap-[collection].xml.ts +13 -2
  462. package/src/astro/types.ts +8 -1
  463. package/src/bylines/index.ts +57 -0
  464. package/src/cli/commands/bundle-utils.ts +2 -0
  465. package/src/cli/commands/export-seed.ts +28 -12
  466. package/src/cli/commands/secrets.ts +2 -2
  467. package/src/components/EmDashImage.astro +22 -4
  468. package/src/components/Image.astro +20 -3
  469. package/src/database/instrumentation.ts +13 -0
  470. package/src/database/migrations/043_content_references.ts +121 -0
  471. package/src/database/migrations/runner.ts +2 -0
  472. package/src/database/repositories/content.ts +225 -67
  473. package/src/database/repositories/index.ts +7 -0
  474. package/src/database/repositories/relation.ts +467 -0
  475. package/src/database/repositories/types.ts +31 -0
  476. package/src/database/repositories/user.ts +18 -0
  477. package/src/database/types.ts +34 -0
  478. package/src/emdash-runtime.ts +172 -67
  479. package/src/index.ts +8 -1
  480. package/src/loader.ts +81 -39
  481. package/src/media/responsive.ts +125 -0
  482. package/src/plugins/cron.ts +3 -2
  483. package/src/plugins/index.ts +5 -0
  484. package/src/plugins/manifest-schema.ts +75 -0
  485. package/src/plugins/marketplace.ts +2 -5
  486. package/src/plugins/scheduler/node.ts +9 -2
  487. package/src/plugins/types.ts +12 -0
  488. package/src/query.ts +45 -7
  489. package/src/request-context.ts +8 -0
  490. package/src/scheduled-publish.ts +153 -0
  491. package/src/schema/types.ts +11 -1
  492. package/src/seed/apply.ts +16 -6
  493. package/src/seed/types.ts +9 -0
  494. package/src/seed/validate.ts +15 -0
  495. package/src/seo/index.ts +2 -28
  496. package/src/seo/media-url.ts +32 -0
  497. package/src/settings/index.ts +32 -40
  498. package/src/taxonomies/index.ts +79 -12
  499. package/src/utils/isolate-cache.ts +189 -0
  500. package/src/virtual-modules.d.ts +11 -0
  501. package/src/widgets/index.ts +57 -54
  502. package/dist/api-Cs7DAACP.mjs.map +0 -1
  503. package/dist/apply-BWMV4Zmw.mjs.map +0 -1
  504. package/dist/byline-fields-BNy7Ng1U.d.mts.map +0 -1
  505. package/dist/content-CyqOmOzm.mjs.map +0 -1
  506. package/dist/cron-DZovZUnC.mjs.map +0 -1
  507. package/dist/index-CjKdMZ3U.d.mts.map +0 -1
  508. package/dist/loader-Dyx8dhFV.mjs.map +0 -1
  509. package/dist/manifest-schema-Cj-YrzrF.mjs.map +0 -1
  510. package/dist/menus-BKkxXCmd.mjs.map +0 -1
  511. package/dist/query-Ctlq1aOk.mjs.map +0 -1
  512. package/dist/redirects-C0L9JUk4.mjs.map +0 -1
  513. package/dist/runner-pt6Wl-l-.mjs.map +0 -1
  514. package/dist/seo-DfjLvu8i.mjs.map +0 -1
  515. package/dist/settings-b5zW1R1T.mjs +0 -235
  516. package/dist/settings-b5zW1R1T.mjs.map +0 -1
  517. package/dist/taxonomies-Crtzy4MT.mjs.map +0 -1
  518. package/dist/types-Cj2S6FuC.mjs +0 -3
  519. package/dist/types-D8bhH891.mjs.map +0 -1
  520. package/dist/types-DbCWhHet.d.mts.map +0 -1
  521. package/dist/types-i8_uzhMD.d.mts.map +0 -1
  522. package/dist/user-DzEUl5zA.mjs.map +0 -1
  523. package/dist/validate-Dy6nkNls.d.mts.map +0 -1
  524. package/dist/validate-JCXcsqiY.mjs.map +0 -1
  525. package/dist/version-CnS-Cr8A.mjs +0 -7
  526. package/dist/widgets-Bap1eS1X.mjs.map +0 -1
  527. package/src/plugins/scheduler/piggyback.ts +0 -71
  528. /package/dist/{api-tokens-B6VgoE6M.mjs → api-tokens-Oq39ba-Z.mjs} +0 -0
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { capabilitiesToDeclaredAccess, declaredAccessToCapabilities } from "@emdash-cms/plugin-types";
2
3
 
3
4
  //#region src/plugins/manifest-schema.ts
4
5
  /**
@@ -202,13 +203,48 @@ const pluginAdminConfigSchema = z.object({
202
203
  })).optional()
203
204
  });
204
205
  /**
206
+ * An operation's constraint object. Open vocabulary: keys the runtime
207
+ * recognises are enforced, others are advisory. The bundler emits `{}` for a
208
+ * granted operation; presence (not value) signals the grant.
209
+ */
210
+ const accessConstraints = z.record(z.string(), z.unknown());
211
+ /**
212
+ * Structured trust contract embedded in the bundle manifest. Mirrors
213
+ * `DeclaredAccess` in `@emdash-cms/plugin-types`. Categories are host
214
+ * subsystems; operations are modes of participation.
215
+ */
216
+ const declaredAccessSchema = z.object({
217
+ content: z.object({
218
+ read: accessConstraints.optional(),
219
+ write: accessConstraints.optional()
220
+ }).optional(),
221
+ media: z.object({
222
+ read: accessConstraints.optional(),
223
+ write: accessConstraints.optional()
224
+ }).optional(),
225
+ network: z.object({ request: z.object({ allowedHosts: z.array(z.string()).min(1).optional() }).optional() }).optional(),
226
+ email: z.object({
227
+ send: accessConstraints.optional(),
228
+ events: accessConstraints.optional(),
229
+ transport: accessConstraints.optional()
230
+ }).optional(),
231
+ page: z.object({ fragments: accessConstraints.optional() }).optional(),
232
+ users: z.object({ read: accessConstraints.optional() }).optional()
233
+ });
234
+ /**
205
235
  * Zod schema matching the PluginManifest interface from types.ts.
206
236
  *
207
237
  * Every JSON.parse of a manifest.json should validate through this.
238
+ *
239
+ * `declaredAccess` is the trust contract; `capabilities`/`allowedHosts` are the
240
+ * runtime's enforcement currency. Apply `reconcileManifestAccess` after parsing
241
+ * to make them consistent (declaredAccess authoritative when present). Kept a
242
+ * plain object (no `.transform`) because callers `.pick()`/`.extend()` it.
208
243
  */
209
244
  const pluginManifestSchema = z.object({
210
245
  id: z.string().min(1),
211
246
  version: z.string().min(1),
247
+ declaredAccess: declaredAccessSchema.optional(),
212
248
  capabilities: z.array(z.enum(PLUGIN_CAPABILITIES)),
213
249
  allowedHosts: z.array(z.string()),
214
250
  storage: z.record(z.string(), storageCollectionSchema),
@@ -217,6 +253,23 @@ const pluginManifestSchema = z.object({
217
253
  admin: pluginAdminConfigSchema
218
254
  });
219
255
  /**
256
+ * Reconcile a parsed manifest's trust contract with its enforcement currency.
257
+ * `declaredAccess` is authoritative: when present, `capabilities`/`allowedHosts`
258
+ * are re-derived from it so what the runtime enforces always matches what was
259
+ * recorded and consented to. A pre-migration bundle without `declaredAccess`
260
+ * has it derived from the legacy capability list instead. The result always
261
+ * carries both, mutually consistent. Apply this at every bundle-parse site.
262
+ */
263
+ function reconcileManifestAccess(manifest) {
264
+ return manifest.declaredAccess ? {
265
+ ...manifest,
266
+ ...declaredAccessToCapabilities(manifest.declaredAccess)
267
+ } : {
268
+ ...manifest,
269
+ declaredAccess: capabilitiesToDeclaredAccess(manifest.capabilities, manifest.allowedHosts)
270
+ };
271
+ }
272
+ /**
220
273
  * Normalize a manifest route entry — plain strings become `{ name }` objects.
221
274
  */
222
275
  function normalizeManifestRoute(entry) {
@@ -225,5 +278,5 @@ function normalizeManifestRoute(entry) {
225
278
  }
226
279
 
227
280
  //#endregion
228
- export { pluginManifestSchema as i, PLUGIN_CAPABILITIES as n, normalizeManifestRoute as r, HOOK_NAMES as t };
229
- //# sourceMappingURL=manifest-schema-Cj-YrzrF.mjs.map
281
+ export { reconcileManifestAccess as a, pluginManifestSchema as i, PLUGIN_CAPABILITIES as n, normalizeManifestRoute as r, HOOK_NAMES as t };
282
+ //# sourceMappingURL=manifest-schema-BtwbL_vj.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-schema-BtwbL_vj.mjs","names":[],"sources":["../src/plugins/manifest-schema.ts"],"sourcesContent":["/**\n * Zod schema for PluginManifest validation\n *\n * Used to validate manifest.json from plugin bundles at every parse site:\n * - Client-side download (marketplace.ts extractBundle)\n * - R2 load (api/handlers/marketplace.ts loadBundleFromR2)\n * - CLI publish preview (cli/commands/publish.ts readManifestFromTarball)\n * - Marketplace ingest extends this with publishing-specific fields\n */\n\nimport {\n\tcapabilitiesToDeclaredAccess,\n\tdeclaredAccessToCapabilities,\n} from \"@emdash-cms/plugin-types\";\nimport { z } from \"zod\";\n\nimport type { PluginManifest } from \"./types.js\";\n\n// ── Enum values (must stay in sync with types.ts) ───────────────\n\n/**\n * Current capability names — the ones authors should use going forward.\n * See `PluginCapability` in `types.ts` for documentation of each.\n */\nexport const CURRENT_PLUGIN_CAPABILITIES = [\n\t\"network:request\",\n\t\"network:request:unrestricted\",\n\t\"content:read\",\n\t\"content:write\",\n\t\"media:read\",\n\t\"media:write\",\n\t\"users:read\",\n\t\"email:send\",\n\t\"hooks.email-transport:register\",\n\t\"hooks.email-events:register\",\n\t\"hooks.page-fragments:register\",\n] as const;\n\n/**\n * Legacy capability names accepted during the deprecation window.\n * Normalized to current names via `normalizeCapability()` in types.ts\n * before reaching the runtime. Plugin authors are warned at bundle/validate\n * and hard-failed at publish.\n */\nexport const DEPRECATED_PLUGIN_CAPABILITIES = [\n\t\"network:fetch\",\n\t\"network:fetch:any\",\n\t\"read:content\",\n\t\"write:content\",\n\t\"read:media\",\n\t\"write:media\",\n\t\"read:users\",\n\t\"email:provide\",\n\t\"email:intercept\",\n\t\"page:inject\",\n] as const;\n\n/**\n * Full set of accepted capability strings — current + deprecated.\n *\n * The manifest schema accepts both during the transition. The runtime only\n * ever sees current names because `normalizeCapability()` rewrites legacy\n * names at every external boundary (definePlugin, adaptSandboxEntry).\n */\nexport const PLUGIN_CAPABILITIES = [\n\t...CURRENT_PLUGIN_CAPABILITIES,\n\t...DEPRECATED_PLUGIN_CAPABILITIES,\n] as const;\n\n/** Must stay in sync with FieldType in schema/types.ts */\nconst FIELD_TYPES = [\n\t\"string\",\n\t\"text\",\n\t\"number\",\n\t\"integer\",\n\t\"boolean\",\n\t\"datetime\",\n\t\"select\",\n\t\"multiSelect\",\n\t\"portableText\",\n\t\"image\",\n\t\"file\",\n\t\"reference\",\n\t\"json\",\n\t\"slug\",\n\t\"repeater\",\n] as const;\n\nexport const HOOK_NAMES = [\n\t\"plugin:install\",\n\t\"plugin:activate\",\n\t\"plugin:deactivate\",\n\t\"plugin:uninstall\",\n\t\"content:beforeSave\",\n\t\"content:afterSave\",\n\t\"content:beforeDelete\",\n\t\"content:afterDelete\",\n\t\"content:afterPublish\",\n\t\"content:afterUnpublish\",\n\t\"media:beforeUpload\",\n\t\"media:afterUpload\",\n\t\"cron\",\n\t\"email:beforeSend\",\n\t\"email:deliver\",\n\t\"email:afterSend\",\n\t\"comment:beforeCreate\",\n\t\"comment:moderate\",\n\t\"comment:afterCreate\",\n\t\"comment:afterModerate\",\n\t\"page:metadata\",\n\t\"page:fragments\",\n] as const;\n\n/**\n * Structured hook entry for manifest — name plus optional metadata.\n * During a transition period, both plain strings and objects are accepted.\n */\nconst manifestHookEntrySchema = z.object({\n\tname: z.enum(HOOK_NAMES),\n\texclusive: z.boolean().optional(),\n\tpriority: z.number().int().optional(),\n\ttimeout: z.number().int().positive().optional(),\n});\n\n/**\n * Structured route entry for manifest — name plus optional metadata.\n * Both plain strings and objects are accepted; strings are normalized\n * to `{ name }` objects via `normalizeManifestRoute()`.\n */\n/** Route names must be safe path segments — alphanumeric, hyphens, underscores, forward slashes */\nconst routeNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9_\\-/]*$/;\n\nconst manifestRouteEntrySchema = z.object({\n\tname: z.string().min(1).regex(routeNamePattern, \"Route name must be a safe path segment\"),\n\tpublic: z.boolean().optional(),\n});\n\n// ── Sub-schemas ─────────────────────────────────────────────────\n\n/** Index field names must be valid identifiers to prevent SQL injection via JSON path expressions */\nconst indexFieldName = z.string().regex(/^[a-zA-Z][a-zA-Z0-9_]*$/);\n\nconst storageCollectionSchema = z.object({\n\tindexes: z.array(z.union([indexFieldName, z.array(indexFieldName)])),\n\tuniqueIndexes: z.array(z.union([indexFieldName, z.array(indexFieldName)])).optional(),\n});\n\nconst baseSettingFields = {\n\tlabel: z.string(),\n\tdescription: z.string().optional(),\n};\n\nconst settingFieldSchema = z.discriminatedUnion(\"type\", [\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"string\"),\n\t\tdefault: z.string().optional(),\n\t\tmultiline: z.boolean().optional(),\n\t}),\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"number\"),\n\t\tdefault: z.number().optional(),\n\t\tmin: z.number().optional(),\n\t\tmax: z.number().optional(),\n\t}),\n\tz.object({ ...baseSettingFields, type: z.literal(\"boolean\"), default: z.boolean().optional() }),\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"select\"),\n\t\toptions: z.array(z.object({ value: z.string(), label: z.string() })),\n\t\tdefault: z.string().optional(),\n\t}),\n\tz.object({ ...baseSettingFields, type: z.literal(\"secret\") }),\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"url\"),\n\t\tdefault: z.string().optional(),\n\t\tplaceholder: z.string().optional(),\n\t}),\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"email\"),\n\t\tdefault: z.string().optional(),\n\t\tplaceholder: z.string().optional(),\n\t}),\n]);\n\nconst adminPageSchema = z.object({\n\tpath: z.string(),\n\tlabel: z.string(),\n\ticon: z.string().optional(),\n});\n\nconst dashboardWidgetSchema = z.object({\n\tid: z.string(),\n\tsize: z.enum([\"full\", \"half\", \"third\"]).optional(),\n\ttitle: z.string().optional(),\n});\n\nconst pluginAdminConfigSchema = z.object({\n\tentry: z.string().optional(),\n\tsettingsSchema: z.record(z.string(), settingFieldSchema).optional(),\n\tpages: z.array(adminPageSchema).optional(),\n\twidgets: z.array(dashboardWidgetSchema).optional(),\n\tfieldWidgets: z\n\t\t.array(\n\t\t\tz.object({\n\t\t\t\tname: z.string().min(1),\n\t\t\t\tlabel: z.string().min(1),\n\t\t\t\tfieldTypes: z.array(z.enum(FIELD_TYPES)),\n\t\t\t\telements: z\n\t\t\t\t\t.array(\n\t\t\t\t\t\tz\n\t\t\t\t\t\t\t.object({\n\t\t\t\t\t\t\t\ttype: z.string(),\n\t\t\t\t\t\t\t\taction_id: z.string(),\n\t\t\t\t\t\t\t\tlabel: z.string().optional(),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.passthrough(),\n\t\t\t\t\t)\n\t\t\t\t\t.optional(),\n\t\t\t}),\n\t\t)\n\t\t.optional(),\n});\n\n// ── declaredAccess ──────────────────────────────────────────────\n\n/**\n * An operation's constraint object. Open vocabulary: keys the runtime\n * recognises are enforced, others are advisory. The bundler emits `{}` for a\n * granted operation; presence (not value) signals the grant.\n */\nconst accessConstraints = z.record(z.string(), z.unknown());\n\n/**\n * Structured trust contract embedded in the bundle manifest. Mirrors\n * `DeclaredAccess` in `@emdash-cms/plugin-types`. Categories are host\n * subsystems; operations are modes of participation.\n */\nconst declaredAccessSchema = z.object({\n\tcontent: z\n\t\t.object({ read: accessConstraints.optional(), write: accessConstraints.optional() })\n\t\t.optional(),\n\tmedia: z\n\t\t.object({ read: accessConstraints.optional(), write: accessConstraints.optional() })\n\t\t.optional(),\n\tnetwork: z\n\t\t.object({\n\t\t\t// allowedHosts: absent = unrestricted; present = host-restricted. Reject\n\t\t\t// an empty array (which the decoder would otherwise have to treat as\n\t\t\t// deny-all) to match the record lexicon's `minLength: 1` and keep the\n\t\t\t// \"absent vs empty\" distinction from ever reaching enforcement ambiguous.\n\t\t\trequest: z.object({ allowedHosts: z.array(z.string()).min(1).optional() }).optional(),\n\t\t})\n\t\t.optional(),\n\temail: z\n\t\t.object({\n\t\t\tsend: accessConstraints.optional(),\n\t\t\tevents: accessConstraints.optional(),\n\t\t\ttransport: accessConstraints.optional(),\n\t\t})\n\t\t.optional(),\n\tpage: z.object({ fragments: accessConstraints.optional() }).optional(),\n\tusers: z.object({ read: accessConstraints.optional() }).optional(),\n});\n\n// ── Main schema ─────────────────────────────────────────────────\n\n/**\n * Zod schema matching the PluginManifest interface from types.ts.\n *\n * Every JSON.parse of a manifest.json should validate through this.\n *\n * `declaredAccess` is the trust contract; `capabilities`/`allowedHosts` are the\n * runtime's enforcement currency. Apply `reconcileManifestAccess` after parsing\n * to make them consistent (declaredAccess authoritative when present). Kept a\n * plain object (no `.transform`) because callers `.pick()`/`.extend()` it.\n */\nexport const pluginManifestSchema = z.object({\n\tid: z.string().min(1),\n\tversion: z.string().min(1),\n\tdeclaredAccess: declaredAccessSchema.optional(),\n\tcapabilities: z.array(z.enum(PLUGIN_CAPABILITIES)),\n\tallowedHosts: z.array(z.string()),\n\tstorage: z.record(z.string(), storageCollectionSchema),\n\t/**\n\t * Hook declarations — accepts both plain name strings (legacy) and\n\t * structured objects with exclusive/priority/timeout metadata.\n\t * Plain strings are normalized to `{ name }` objects after parsing.\n\t */\n\thooks: z.array(z.union([z.enum(HOOK_NAMES), manifestHookEntrySchema])),\n\t/**\n\t * Route declarations — accepts both plain name strings and\n\t * structured objects with public metadata.\n\t * Plain strings are normalized to `{ name }` objects after parsing.\n\t */\n\troutes: z.array(\n\t\tz.union([\n\t\t\tz.string().min(1).regex(routeNamePattern, \"Route name must be a safe path segment\"),\n\t\t\tmanifestRouteEntrySchema,\n\t\t]),\n\t),\n\tadmin: pluginAdminConfigSchema,\n});\n\nexport type ValidatedPluginManifest = z.infer<typeof pluginManifestSchema>;\n\n/**\n * Reconcile a parsed manifest's trust contract with its enforcement currency.\n * `declaredAccess` is authoritative: when present, `capabilities`/`allowedHosts`\n * are re-derived from it so what the runtime enforces always matches what was\n * recorded and consented to. A pre-migration bundle without `declaredAccess`\n * has it derived from the legacy capability list instead. The result always\n * carries both, mutually consistent. Apply this at every bundle-parse site.\n */\nexport function reconcileManifestAccess(manifest: ValidatedPluginManifest): PluginManifest {\n\tconst reconciled: ValidatedPluginManifest = manifest.declaredAccess\n\t\t? { ...manifest, ...declaredAccessToCapabilities(manifest.declaredAccess) }\n\t\t: {\n\t\t\t\t...manifest,\n\t\t\t\tdeclaredAccess: capabilitiesToDeclaredAccess(manifest.capabilities, manifest.allowedHosts),\n\t\t\t};\n\t// Block Kit admin elements are typed as `unknown` by the Zod schema (their\n\t// Element shape is validated at render time), so the validated manifest\n\t// needs a structural cast up to the runtime PluginManifest.\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- admin elements are unknown[] in Zod; Element type checked at render time\n\treturn reconciled as unknown as PluginManifest;\n}\n\n/**\n * Normalize a manifest hook entry — plain strings become `{ name }` objects.\n */\nexport function normalizeManifestHook(\n\tentry: string | { name: string; exclusive?: boolean; priority?: number; timeout?: number },\n): { name: string; exclusive?: boolean; priority?: number; timeout?: number } {\n\tif (typeof entry === \"string\") {\n\t\treturn { name: entry };\n\t}\n\treturn entry;\n}\n\n/**\n * Normalize a manifest route entry — plain strings become `{ name }` objects.\n */\nexport function normalizeManifestRoute(entry: string | { name: string; public?: boolean }): {\n\tname: string;\n\tpublic?: boolean;\n} {\n\tif (typeof entry === \"string\") {\n\t\treturn { name: entry };\n\t}\n\treturn entry;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { _ as MediaValue, a as ComponentEmbed, b as mediaItemToValue, c as EmbedResult, d as MediaListResult, f as MediaProvider, g as MediaUploadInput, h as MediaProviderItem, i as AudioEmbed, l as ImageEmbed, m as MediaProviderDescriptor, n as generatePlaceholder, o as CreateMediaProviderFn, p as MediaProviderCapabilities, r as normalizeMediaValue, s as EmbedOptions, t as PlaceholderData, u as MediaListOptions, v as ThumbnailOptions, y as VideoEmbed } from "../placeholder-B9lUUEmj.mjs";
1
+ import { _ as MediaValue, a as ComponentEmbed, b as mediaItemToValue, c as EmbedResult, d as MediaListResult, f as MediaProvider, g as MediaUploadInput, h as MediaProviderItem, i as AudioEmbed, l as ImageEmbed, m as MediaProviderDescriptor, n as generatePlaceholder, o as CreateMediaProviderFn, p as MediaProviderCapabilities, r as normalizeMediaValue, s as EmbedOptions, t as PlaceholderData, u as MediaListOptions, v as ThumbnailOptions, y as VideoEmbed } from "../placeholder-CVBv5z8k.mjs";
2
2
 
3
3
  //#region src/media/local.d.ts
4
4
  interface LocalMediaConfig {
@@ -1,4 +1,4 @@
1
- import { t as normalizeMediaValue } from "../normalize-DVV8nbrL.mjs";
1
+ import { n as normalizeMediaValue } from "../normalize-CK5o04zr.mjs";
2
2
  import { t as generatePlaceholder } from "../placeholder-BZxr8W1j.mjs";
3
3
 
4
4
  //#region src/media/types.ts
@@ -1,14 +1,14 @@
1
- import "../options-tb7DJROi.mjs";
2
- import { t as Database } from "../types-DawhLFwy.mjs";
3
- import "../types-DMwSpvcw.mjs";
4
- import "../byline-fields-BNy7Ng1U.mjs";
5
- import { h as MediaProviderItem, o as CreateMediaProviderFn } from "../placeholder-B9lUUEmj.mjs";
6
- import "../index-CjKdMZ3U.mjs";
7
- import "../runner-DM1yR5qd.mjs";
8
- import "../index-D60_SzHG.mjs";
9
- import { d as Storage } from "../types-kwqCOUxj.mjs";
10
- import "../types-DWnN7weG.mjs";
11
- import "../validate-Dy6nkNls.mjs";
1
+ import "../options-DyYIYpPd.mjs";
2
+ import { t as Database } from "../types-BPzXTV9x.mjs";
3
+ import "../types-CPAPl93j.mjs";
4
+ import "../byline-fields-DbibsvTl.mjs";
5
+ import { h as MediaProviderItem, o as CreateMediaProviderFn } from "../placeholder-CVBv5z8k.mjs";
6
+ import "../index-B1keaX5Y.mjs";
7
+ import "../runner-DTdhuI9i.mjs";
8
+ import "../index-DR56od45.mjs";
9
+ import { d as Storage } from "../types-D4kUqbHh.mjs";
10
+ import "../types-BFgrqwSk.mjs";
11
+ import "../validate-CNwkPWzz.mjs";
12
12
  import "../index.mjs";
13
13
  import { Kysely } from "kysely";
14
14
 
@@ -1,11 +1,11 @@
1
1
  import "../dialect-helpers-DRI5pyY3.mjs";
2
2
  import "../base64-CqR-7kqF.mjs";
3
- import "../types-K3MDsxpy.mjs";
4
- import { t as MediaRepository } from "../media-C-oovGCG.mjs";
5
- import "../options-BL4X94qY.mjs";
6
- import "../request-cache-BYMs-BGX.mjs";
7
- import "../loader-Dyx8dhFV.mjs";
8
- import { o as invalidateSiteSettingsCache } from "../settings-b5zW1R1T.mjs";
3
+ import "../types-BXSUSAjt.mjs";
4
+ import { t as MediaRepository } from "../media-JOf3pNkw.mjs";
5
+ import "../options-BPCVnesz.mjs";
6
+ import "../request-cache-D32LpnmI.mjs";
7
+ import "../loader-ZN1ll-d-.mjs";
8
+ import { o as invalidateSiteSettingsCache } from "../settings-DfxiWY_s.mjs";
9
9
 
10
10
  //#region src/media/local-runtime.ts
11
11
  /**
@@ -1,4 +1,4 @@
1
- import { i as encodeCursor, r as decodeCursor } from "./types-K3MDsxpy.mjs";
1
+ import { a as encodeCursor, i as decodeCursor } from "./types-BXSUSAjt.mjs";
2
2
  import { sql } from "kysely";
3
3
  import { ulid } from "ulidx";
4
4
 
@@ -211,4 +211,4 @@ var MediaRepository = class {
211
211
 
212
212
  //#endregion
213
213
  export { MediaRepository as t };
214
- //# sourceMappingURL=media-C-oovGCG.mjs.map
214
+ //# sourceMappingURL=media-JOf3pNkw.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"media-C-oovGCG.mjs","names":[],"sources":["../src/database/repositories/media.ts"],"sourcesContent":["import { sql, type ExpressionBuilder, type Kysely, type SqlBool } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport type { Database, MediaRow } from \"../types.js\";\nimport type { FindManyResult } from \"./types.js\";\nimport { encodeCursor, decodeCursor } from \"./types.js\";\n\n/** Escape LIKE wildcard characters and the escape char itself in user-supplied values */\nfunction escapeLike(value: string): string {\n\treturn value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"%\", \"\\\\%\").replaceAll(\"_\", \"\\\\_\");\n}\n\n/**\n * Normalize a mimeType filter (string or array) into a clean string[].\n * Entries that are empty strings are dropped.\n */\nfunction normalizeMimeFilter(input?: string | readonly string[]): string[] {\n\tif (!input) return [];\n\tconst arr = Array.isArray(input) ? input : [input];\n\treturn arr\n\t\t.filter((entry): entry is string => typeof entry === \"string\" && entry.length > 0)\n\t\t.map((entry) =>\n\t\t\tentry.endsWith(\"/\") ? entry.toLowerCase() : entry.split(\";\")[0].trim().toLowerCase(),\n\t\t);\n}\n\n/**\n * Build a WHERE clause that matches `mime_type` against any of the given\n * filter entries — exact equality for full MIMEs, LIKE prefix for entries\n * ending in \"/\".\n */\nfunction mimeMatchExpr(eb: ExpressionBuilder<Database, \"media\">, filters: string[]) {\n\treturn eb.or(\n\t\tfilters.map((entry) =>\n\t\t\tentry.endsWith(\"/\")\n\t\t\t\t? sql<SqlBool>`mime_type LIKE ${`${escapeLike(entry)}%`} ESCAPE '\\\\'`\n\t\t\t\t: eb(\"mime_type\", \"=\", entry),\n\t\t),\n\t);\n}\n\nexport type MediaStatus = \"pending\" | \"ready\" | \"failed\";\n\nexport interface MediaItem {\n\tid: string;\n\tfilename: string;\n\tmimeType: string;\n\tsize: number | null;\n\twidth: number | null;\n\theight: number | null;\n\talt: string | null;\n\tcaption: string | null;\n\tstorageKey: string;\n\tstatus: MediaStatus;\n\tcontentHash: string | null;\n\tblurhash: string | null;\n\tdominantColor: string | null;\n\tcreatedAt: string;\n\tauthorId: string | null;\n}\n\nexport interface CreateMediaInput {\n\tfilename: string;\n\tmimeType: string;\n\tsize?: number;\n\twidth?: number;\n\theight?: number;\n\talt?: string;\n\tcaption?: string;\n\tstorageKey: string;\n\tcontentHash?: string;\n\tblurhash?: string;\n\tdominantColor?: string;\n\tstatus?: MediaStatus;\n\tauthorId?: string;\n}\n\nexport interface FindManyMediaOptions {\n\tlimit?: number;\n\tcursor?: string;\n\t/** Filter by MIME type. Pass a string for a single prefix/exact, or an array to match any. Strings ending with \"/\" are treated as LIKE prefix matches; others are exact equality. */\n\tmimeType?: string | readonly string[];\n\tstatus?: MediaStatus | \"all\"; // Filter by status, defaults to \"ready\"\n\t/** Case-insensitive substring matched against the filename (covers filename and extension). */\n\tq?: string;\n}\n\n/**\n * Media repository for database operations\n */\nexport class MediaRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Create a new media item\n\t */\n\tasync create(input: CreateMediaInput): Promise<MediaItem> {\n\t\tconst id = ulid();\n\t\tconst now = new Date().toISOString();\n\n\t\tconst row: Omit<MediaRow, \"rowid\"> = {\n\t\t\tid,\n\t\t\tfilename: input.filename,\n\t\t\tmime_type: input.mimeType,\n\t\t\tsize: input.size ?? null,\n\t\t\twidth: input.width ?? null,\n\t\t\theight: input.height ?? null,\n\t\t\talt: input.alt ?? null,\n\t\t\tcaption: input.caption ?? null,\n\t\t\tstorage_key: input.storageKey,\n\t\t\tcontent_hash: input.contentHash ?? null,\n\t\t\tblurhash: input.blurhash ?? null,\n\t\t\tdominant_color: input.dominantColor ?? null,\n\t\t\tstatus: input.status ?? \"ready\",\n\t\t\tcreated_at: now,\n\t\t\tauthor_id: input.authorId ?? null,\n\t\t};\n\n\t\tawait this.db.insertInto(\"media\").values(row).execute();\n\n\t\treturn this.rowToItem(row as MediaRow);\n\t}\n\n\t/**\n\t * Create a pending media item (for signed URL upload flow)\n\t */\n\tasync createPending(input: {\n\t\tfilename: string;\n\t\tmimeType: string;\n\t\tsize?: number;\n\t\tstorageKey: string;\n\t\tcontentHash?: string;\n\t\tauthorId?: string;\n\t}): Promise<MediaItem> {\n\t\treturn this.create({\n\t\t\t...input,\n\t\t\tstatus: \"pending\",\n\t\t});\n\t}\n\n\t/**\n\t * Confirm upload (mark as ready)\n\t */\n\tasync confirmUpload(\n\t\tid: string,\n\t\tmetadata?: { width?: number; height?: number; size?: number },\n\t): Promise<MediaItem | null> {\n\t\tconst existing = await this.findById(id);\n\t\tif (!existing) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst updates: Partial<MediaRow> = {\n\t\t\tstatus: \"ready\",\n\t\t};\n\t\tif (metadata?.width !== undefined) updates.width = metadata.width;\n\t\tif (metadata?.height !== undefined) updates.height = metadata.height;\n\t\tif (metadata?.size !== undefined) updates.size = metadata.size;\n\n\t\tawait this.db.updateTable(\"media\").set(updates).where(\"id\", \"=\", id).execute();\n\n\t\treturn this.findById(id);\n\t}\n\n\t/**\n\t * Mark upload as failed\n\t */\n\tasync markFailed(id: string): Promise<MediaItem | null> {\n\t\tconst existing = await this.findById(id);\n\t\tif (!existing) {\n\t\t\treturn null;\n\t\t}\n\n\t\tawait this.db.updateTable(\"media\").set({ status: \"failed\" }).where(\"id\", \"=\", id).execute();\n\n\t\treturn this.findById(id);\n\t}\n\n\t/**\n\t * Find media by ID\n\t */\n\tasync findById(id: string): Promise<MediaItem | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"media\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToItem(row) : null;\n\t}\n\n\t/**\n\t * Find media by filename\n\t * Useful for idempotent imports\n\t */\n\tasync findByFilename(filename: string): Promise<MediaItem | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"media\")\n\t\t\t.selectAll()\n\t\t\t.where(\"filename\", \"=\", filename)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToItem(row) : null;\n\t}\n\n\t/**\n\t * Find media by content hash\n\t * Used for deduplication - same content = same hash\n\t */\n\tasync findByContentHash(contentHash: string): Promise<MediaItem | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"media\")\n\t\t\t.selectAll()\n\t\t\t.where(\"content_hash\", \"=\", contentHash)\n\t\t\t.where(\"status\", \"=\", \"ready\")\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToItem(row) : null;\n\t}\n\n\t/**\n\t * Find many media items with cursor pagination\n\t *\n\t * Uses keyset pagination (cursor-based) for consistent results.\n\t * The cursor encodes the created_at and id of the last item.\n\t */\n\tasync findMany(options: FindManyMediaOptions = {}): Promise<FindManyResult<MediaItem>> {\n\t\tconst limit = Math.min(options.limit || 50, 100);\n\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"media\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(\"created_at\", \"desc\")\n\t\t\t.orderBy(\"id\", \"desc\")\n\t\t\t.limit(limit + 1);\n\n\t\t// Handle cursor-based pagination — throws on invalid cursor.\n\t\tif (options.cursor) {\n\t\t\tconst { orderValue: createdAt, id: cursorId } = decodeCursor(options.cursor);\n\n\t\t\t// Keyset pagination: get items where (created_at, id) < cursor\n\t\t\tquery = query.where((eb) =>\n\t\t\t\teb.or([\n\t\t\t\t\teb(\"created_at\", \"<\", createdAt),\n\t\t\t\t\teb.and([eb(\"created_at\", \"=\", createdAt), eb(\"id\", \"<\", cursorId)]),\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\n\t\tconst mimeFilters = normalizeMimeFilter(options.mimeType);\n\t\tif (mimeFilters.length > 0) {\n\t\t\tquery = query.where((eb) => mimeMatchExpr(eb, mimeFilters));\n\t\t}\n\n\t\t// Case-insensitive filename substring search (also matches extensions).\n\t\t// LIKE wildcards in the term are escaped so they're treated literally.\n\t\tconst term = options.q?.trim();\n\t\tif (term) {\n\t\t\tconst pattern = `%${escapeLike(term)}%`;\n\t\t\tquery = query.where(\n\t\t\t\tsql<string>`lower(filename)`,\n\t\t\t\t\"like\",\n\t\t\t\tsql<string>`lower(${pattern}) escape '\\\\'`,\n\t\t\t);\n\t\t}\n\n\t\t// Default to only showing ready items\n\t\tif (options.status !== \"all\") {\n\t\t\tquery = query.where(\"status\", \"=\", options.status ?? \"ready\");\n\t\t}\n\n\t\tconst rows = await query.execute();\n\n\t\tconst hasMore = rows.length > limit;\n\t\tconst items = rows.slice(0, limit).map((row) => this.rowToItem(row));\n\n\t\tlet nextCursor: string | undefined;\n\t\tif (hasMore && items.length > 0) {\n\t\t\tconst lastItem = items.at(-1)!;\n\t\t\tnextCursor = encodeCursor(lastItem.createdAt, lastItem.id);\n\t\t}\n\n\t\treturn { items, nextCursor };\n\t}\n\n\t/**\n\t * Update media metadata\n\t */\n\tasync update(\n\t\tid: string,\n\t\tinput: Partial<Pick<CreateMediaInput, \"alt\" | \"caption\" | \"width\" | \"height\">>,\n\t): Promise<MediaItem | null> {\n\t\tconst existing = await this.findById(id);\n\t\tif (!existing) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst updates: Partial<MediaRow> = {};\n\t\tif (input.alt !== undefined) updates.alt = input.alt;\n\t\tif (input.caption !== undefined) updates.caption = input.caption;\n\t\tif (input.width !== undefined) updates.width = input.width;\n\t\tif (input.height !== undefined) updates.height = input.height;\n\n\t\tif (Object.keys(updates).length > 0) {\n\t\t\tawait this.db.updateTable(\"media\").set(updates).where(\"id\", \"=\", id).execute();\n\t\t}\n\n\t\treturn this.findById(id);\n\t}\n\n\t/**\n\t * Delete media item\n\t */\n\tasync delete(id: string): Promise<boolean> {\n\t\tconst result = await this.db.deleteFrom(\"media\").where(\"id\", \"=\", id).executeTakeFirst();\n\n\t\treturn (result.numDeletedRows ?? 0) > 0;\n\t}\n\n\t/**\n\t * Count media items\n\t */\n\tasync count(mimeType?: string | readonly string[]): Promise<number> {\n\t\tconst filters = normalizeMimeFilter(mimeType);\n\t\tlet query = this.db.selectFrom(\"media\").select((eb) => eb.fn.count<number>(\"id\").as(\"count\"));\n\n\t\tif (filters.length > 0) {\n\t\t\tquery = query.where((eb) => mimeMatchExpr(eb, filters));\n\t\t}\n\n\t\tconst result = await query.executeTakeFirst();\n\t\treturn Number(result?.count || 0);\n\t}\n\n\t/**\n\t * Delete pending uploads older than the given age.\n\t * Pending uploads that were never confirmed indicate abandoned upload flows.\n\t *\n\t * Returns the storage keys of deleted rows so callers can remove the\n\t * corresponding files from object storage.\n\t */\n\tasync cleanupPendingUploads(maxAgeMs: number = 60 * 60 * 1000): Promise<string[]> {\n\t\tconst cutoff = new Date(Date.now() - maxAgeMs).toISOString();\n\n\t\t// Select the storage keys first -- SQLite doesn't support RETURNING\n\t\t// on DELETE in all drivers, and Kysely's RETURNING isn't universal.\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"media\")\n\t\t\t.select(\"storage_key\")\n\t\t\t.where(\"status\", \"=\", \"pending\")\n\t\t\t.where(\"created_at\", \"<\", cutoff)\n\t\t\t.execute();\n\n\t\tif (rows.length === 0) return [];\n\n\t\tawait this.db\n\t\t\t.deleteFrom(\"media\")\n\t\t\t.where(\"status\", \"=\", \"pending\")\n\t\t\t.where(\"created_at\", \"<\", cutoff)\n\t\t\t.execute();\n\n\t\treturn rows.map((r) => r.storage_key);\n\t}\n\n\t/**\n\t * Convert database row to MediaItem\n\t */\n\tprivate rowToItem(row: MediaRow): MediaItem {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tfilename: row.filename,\n\t\t\tmimeType: row.mime_type,\n\t\t\tsize: row.size,\n\t\t\twidth: row.width,\n\t\t\theight: row.height,\n\t\t\talt: row.alt,\n\t\t\tcaption: row.caption,\n\t\t\tstorageKey: row.storage_key,\n\t\t\tcontentHash: row.content_hash,\n\t\t\tblurhash: row.blurhash,\n\t\t\tdominantColor: row.dominant_color,\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- DB stores string; validated at insert but linter can't follow\n\t\t\tstatus: row.status as MediaStatus,\n\t\t\tcreatedAt: row.created_at,\n\t\t\tauthorId: row.author_id,\n\t\t};\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"media-JOf3pNkw.mjs","names":[],"sources":["../src/database/repositories/media.ts"],"sourcesContent":["import { sql, type ExpressionBuilder, type Kysely, type SqlBool } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport type { Database, MediaRow } from \"../types.js\";\nimport type { FindManyResult } from \"./types.js\";\nimport { encodeCursor, decodeCursor } from \"./types.js\";\n\n/** Escape LIKE wildcard characters and the escape char itself in user-supplied values */\nfunction escapeLike(value: string): string {\n\treturn value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"%\", \"\\\\%\").replaceAll(\"_\", \"\\\\_\");\n}\n\n/**\n * Normalize a mimeType filter (string or array) into a clean string[].\n * Entries that are empty strings are dropped.\n */\nfunction normalizeMimeFilter(input?: string | readonly string[]): string[] {\n\tif (!input) return [];\n\tconst arr = Array.isArray(input) ? input : [input];\n\treturn arr\n\t\t.filter((entry): entry is string => typeof entry === \"string\" && entry.length > 0)\n\t\t.map((entry) =>\n\t\t\tentry.endsWith(\"/\") ? entry.toLowerCase() : entry.split(\";\")[0].trim().toLowerCase(),\n\t\t);\n}\n\n/**\n * Build a WHERE clause that matches `mime_type` against any of the given\n * filter entries — exact equality for full MIMEs, LIKE prefix for entries\n * ending in \"/\".\n */\nfunction mimeMatchExpr(eb: ExpressionBuilder<Database, \"media\">, filters: string[]) {\n\treturn eb.or(\n\t\tfilters.map((entry) =>\n\t\t\tentry.endsWith(\"/\")\n\t\t\t\t? sql<SqlBool>`mime_type LIKE ${`${escapeLike(entry)}%`} ESCAPE '\\\\'`\n\t\t\t\t: eb(\"mime_type\", \"=\", entry),\n\t\t),\n\t);\n}\n\nexport type MediaStatus = \"pending\" | \"ready\" | \"failed\";\n\nexport interface MediaItem {\n\tid: string;\n\tfilename: string;\n\tmimeType: string;\n\tsize: number | null;\n\twidth: number | null;\n\theight: number | null;\n\talt: string | null;\n\tcaption: string | null;\n\tstorageKey: string;\n\tstatus: MediaStatus;\n\tcontentHash: string | null;\n\tblurhash: string | null;\n\tdominantColor: string | null;\n\tcreatedAt: string;\n\tauthorId: string | null;\n}\n\nexport interface CreateMediaInput {\n\tfilename: string;\n\tmimeType: string;\n\tsize?: number;\n\twidth?: number;\n\theight?: number;\n\talt?: string;\n\tcaption?: string;\n\tstorageKey: string;\n\tcontentHash?: string;\n\tblurhash?: string;\n\tdominantColor?: string;\n\tstatus?: MediaStatus;\n\tauthorId?: string;\n}\n\nexport interface FindManyMediaOptions {\n\tlimit?: number;\n\tcursor?: string;\n\t/** Filter by MIME type. Pass a string for a single prefix/exact, or an array to match any. Strings ending with \"/\" are treated as LIKE prefix matches; others are exact equality. */\n\tmimeType?: string | readonly string[];\n\tstatus?: MediaStatus | \"all\"; // Filter by status, defaults to \"ready\"\n\t/** Case-insensitive substring matched against the filename (covers filename and extension). */\n\tq?: string;\n}\n\n/**\n * Media repository for database operations\n */\nexport class MediaRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Create a new media item\n\t */\n\tasync create(input: CreateMediaInput): Promise<MediaItem> {\n\t\tconst id = ulid();\n\t\tconst now = new Date().toISOString();\n\n\t\tconst row: Omit<MediaRow, \"rowid\"> = {\n\t\t\tid,\n\t\t\tfilename: input.filename,\n\t\t\tmime_type: input.mimeType,\n\t\t\tsize: input.size ?? null,\n\t\t\twidth: input.width ?? null,\n\t\t\theight: input.height ?? null,\n\t\t\talt: input.alt ?? null,\n\t\t\tcaption: input.caption ?? null,\n\t\t\tstorage_key: input.storageKey,\n\t\t\tcontent_hash: input.contentHash ?? null,\n\t\t\tblurhash: input.blurhash ?? null,\n\t\t\tdominant_color: input.dominantColor ?? null,\n\t\t\tstatus: input.status ?? \"ready\",\n\t\t\tcreated_at: now,\n\t\t\tauthor_id: input.authorId ?? null,\n\t\t};\n\n\t\tawait this.db.insertInto(\"media\").values(row).execute();\n\n\t\treturn this.rowToItem(row as MediaRow);\n\t}\n\n\t/**\n\t * Create a pending media item (for signed URL upload flow)\n\t */\n\tasync createPending(input: {\n\t\tfilename: string;\n\t\tmimeType: string;\n\t\tsize?: number;\n\t\tstorageKey: string;\n\t\tcontentHash?: string;\n\t\tauthorId?: string;\n\t}): Promise<MediaItem> {\n\t\treturn this.create({\n\t\t\t...input,\n\t\t\tstatus: \"pending\",\n\t\t});\n\t}\n\n\t/**\n\t * Confirm upload (mark as ready)\n\t */\n\tasync confirmUpload(\n\t\tid: string,\n\t\tmetadata?: { width?: number; height?: number; size?: number },\n\t): Promise<MediaItem | null> {\n\t\tconst existing = await this.findById(id);\n\t\tif (!existing) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst updates: Partial<MediaRow> = {\n\t\t\tstatus: \"ready\",\n\t\t};\n\t\tif (metadata?.width !== undefined) updates.width = metadata.width;\n\t\tif (metadata?.height !== undefined) updates.height = metadata.height;\n\t\tif (metadata?.size !== undefined) updates.size = metadata.size;\n\n\t\tawait this.db.updateTable(\"media\").set(updates).where(\"id\", \"=\", id).execute();\n\n\t\treturn this.findById(id);\n\t}\n\n\t/**\n\t * Mark upload as failed\n\t */\n\tasync markFailed(id: string): Promise<MediaItem | null> {\n\t\tconst existing = await this.findById(id);\n\t\tif (!existing) {\n\t\t\treturn null;\n\t\t}\n\n\t\tawait this.db.updateTable(\"media\").set({ status: \"failed\" }).where(\"id\", \"=\", id).execute();\n\n\t\treturn this.findById(id);\n\t}\n\n\t/**\n\t * Find media by ID\n\t */\n\tasync findById(id: string): Promise<MediaItem | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"media\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToItem(row) : null;\n\t}\n\n\t/**\n\t * Find media by filename\n\t * Useful for idempotent imports\n\t */\n\tasync findByFilename(filename: string): Promise<MediaItem | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"media\")\n\t\t\t.selectAll()\n\t\t\t.where(\"filename\", \"=\", filename)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToItem(row) : null;\n\t}\n\n\t/**\n\t * Find media by content hash\n\t * Used for deduplication - same content = same hash\n\t */\n\tasync findByContentHash(contentHash: string): Promise<MediaItem | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"media\")\n\t\t\t.selectAll()\n\t\t\t.where(\"content_hash\", \"=\", contentHash)\n\t\t\t.where(\"status\", \"=\", \"ready\")\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToItem(row) : null;\n\t}\n\n\t/**\n\t * Find many media items with cursor pagination\n\t *\n\t * Uses keyset pagination (cursor-based) for consistent results.\n\t * The cursor encodes the created_at and id of the last item.\n\t */\n\tasync findMany(options: FindManyMediaOptions = {}): Promise<FindManyResult<MediaItem>> {\n\t\tconst limit = Math.min(options.limit || 50, 100);\n\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"media\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(\"created_at\", \"desc\")\n\t\t\t.orderBy(\"id\", \"desc\")\n\t\t\t.limit(limit + 1);\n\n\t\t// Handle cursor-based pagination — throws on invalid cursor.\n\t\tif (options.cursor) {\n\t\t\tconst { orderValue: createdAt, id: cursorId } = decodeCursor(options.cursor);\n\n\t\t\t// Keyset pagination: get items where (created_at, id) < cursor\n\t\t\tquery = query.where((eb) =>\n\t\t\t\teb.or([\n\t\t\t\t\teb(\"created_at\", \"<\", createdAt),\n\t\t\t\t\teb.and([eb(\"created_at\", \"=\", createdAt), eb(\"id\", \"<\", cursorId)]),\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\n\t\tconst mimeFilters = normalizeMimeFilter(options.mimeType);\n\t\tif (mimeFilters.length > 0) {\n\t\t\tquery = query.where((eb) => mimeMatchExpr(eb, mimeFilters));\n\t\t}\n\n\t\t// Case-insensitive filename substring search (also matches extensions).\n\t\t// LIKE wildcards in the term are escaped so they're treated literally.\n\t\tconst term = options.q?.trim();\n\t\tif (term) {\n\t\t\tconst pattern = `%${escapeLike(term)}%`;\n\t\t\tquery = query.where(\n\t\t\t\tsql<string>`lower(filename)`,\n\t\t\t\t\"like\",\n\t\t\t\tsql<string>`lower(${pattern}) escape '\\\\'`,\n\t\t\t);\n\t\t}\n\n\t\t// Default to only showing ready items\n\t\tif (options.status !== \"all\") {\n\t\t\tquery = query.where(\"status\", \"=\", options.status ?? \"ready\");\n\t\t}\n\n\t\tconst rows = await query.execute();\n\n\t\tconst hasMore = rows.length > limit;\n\t\tconst items = rows.slice(0, limit).map((row) => this.rowToItem(row));\n\n\t\tlet nextCursor: string | undefined;\n\t\tif (hasMore && items.length > 0) {\n\t\t\tconst lastItem = items.at(-1)!;\n\t\t\tnextCursor = encodeCursor(lastItem.createdAt, lastItem.id);\n\t\t}\n\n\t\treturn { items, nextCursor };\n\t}\n\n\t/**\n\t * Update media metadata\n\t */\n\tasync update(\n\t\tid: string,\n\t\tinput: Partial<Pick<CreateMediaInput, \"alt\" | \"caption\" | \"width\" | \"height\">>,\n\t): Promise<MediaItem | null> {\n\t\tconst existing = await this.findById(id);\n\t\tif (!existing) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst updates: Partial<MediaRow> = {};\n\t\tif (input.alt !== undefined) updates.alt = input.alt;\n\t\tif (input.caption !== undefined) updates.caption = input.caption;\n\t\tif (input.width !== undefined) updates.width = input.width;\n\t\tif (input.height !== undefined) updates.height = input.height;\n\n\t\tif (Object.keys(updates).length > 0) {\n\t\t\tawait this.db.updateTable(\"media\").set(updates).where(\"id\", \"=\", id).execute();\n\t\t}\n\n\t\treturn this.findById(id);\n\t}\n\n\t/**\n\t * Delete media item\n\t */\n\tasync delete(id: string): Promise<boolean> {\n\t\tconst result = await this.db.deleteFrom(\"media\").where(\"id\", \"=\", id).executeTakeFirst();\n\n\t\treturn (result.numDeletedRows ?? 0) > 0;\n\t}\n\n\t/**\n\t * Count media items\n\t */\n\tasync count(mimeType?: string | readonly string[]): Promise<number> {\n\t\tconst filters = normalizeMimeFilter(mimeType);\n\t\tlet query = this.db.selectFrom(\"media\").select((eb) => eb.fn.count<number>(\"id\").as(\"count\"));\n\n\t\tif (filters.length > 0) {\n\t\t\tquery = query.where((eb) => mimeMatchExpr(eb, filters));\n\t\t}\n\n\t\tconst result = await query.executeTakeFirst();\n\t\treturn Number(result?.count || 0);\n\t}\n\n\t/**\n\t * Delete pending uploads older than the given age.\n\t * Pending uploads that were never confirmed indicate abandoned upload flows.\n\t *\n\t * Returns the storage keys of deleted rows so callers can remove the\n\t * corresponding files from object storage.\n\t */\n\tasync cleanupPendingUploads(maxAgeMs: number = 60 * 60 * 1000): Promise<string[]> {\n\t\tconst cutoff = new Date(Date.now() - maxAgeMs).toISOString();\n\n\t\t// Select the storage keys first -- SQLite doesn't support RETURNING\n\t\t// on DELETE in all drivers, and Kysely's RETURNING isn't universal.\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"media\")\n\t\t\t.select(\"storage_key\")\n\t\t\t.where(\"status\", \"=\", \"pending\")\n\t\t\t.where(\"created_at\", \"<\", cutoff)\n\t\t\t.execute();\n\n\t\tif (rows.length === 0) return [];\n\n\t\tawait this.db\n\t\t\t.deleteFrom(\"media\")\n\t\t\t.where(\"status\", \"=\", \"pending\")\n\t\t\t.where(\"created_at\", \"<\", cutoff)\n\t\t\t.execute();\n\n\t\treturn rows.map((r) => r.storage_key);\n\t}\n\n\t/**\n\t * Convert database row to MediaItem\n\t */\n\tprivate rowToItem(row: MediaRow): MediaItem {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tfilename: row.filename,\n\t\t\tmimeType: row.mime_type,\n\t\t\tsize: row.size,\n\t\t\twidth: row.width,\n\t\t\theight: row.height,\n\t\t\talt: row.alt,\n\t\t\tcaption: row.caption,\n\t\t\tstorageKey: row.storage_key,\n\t\t\tcontentHash: row.content_hash,\n\t\t\tblurhash: row.blurhash,\n\t\t\tdominantColor: row.dominant_color,\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- DB stores string; validated at insert but linter can't follow\n\t\t\tstatus: row.status as MediaStatus,\n\t\t\tcreatedAt: row.created_at,\n\t\t\tauthorId: row.author_id,\n\t\t};\n\t}\n}\n"],"mappings":"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"}
@@ -29,4 +29,4 @@ async function resolveFieldAllowlist(db, fieldId) {
29
29
 
30
30
  //#endregion
31
31
  export { resolveFieldAllowlist as n, GLOBAL_UPLOAD_ALLOWLIST as t };
32
- //# sourceMappingURL=media-allowlist-CMcoYIjQ.mjs.map
32
+ //# sourceMappingURL=media-allowlist-Dknq-OFY.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"media-allowlist-CMcoYIjQ.mjs","names":[],"sources":["../src/api/handlers/media-allowlist.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { parseAllowedMimeTypes } from \"../../media/mime.js\";\n\n/**\n * MIME types allowed for upload by default (when no field-specific list\n * overrides this). Entries ending with \"/\" are prefix-matched (e.g.\n * \"image/\" matches \"image/jpeg\", \"image/png\", etc.).\n */\nexport const GLOBAL_UPLOAD_ALLOWLIST: readonly string[] = [\n\t\"image/\",\n\t\"video/\",\n\t\"audio/\",\n\t\"application/pdf\",\n];\n\n/**\n * Resolve the MIME allowlist for a specific field.\n *\n * Returns the field's `allowedMimeTypes` list when the field exists, is of\n * type \"file\" or \"image\", and has a non-empty list configured. Returns null\n * in all other cases — callers should fall back to GLOBAL_UPLOAD_ALLOWLIST.\n *\n * Authentication is the caller's responsibility (the upload routes already\n * gate on `media:upload`).\n */\nexport async function resolveFieldAllowlist(\n\tdb: Kysely<Database>,\n\tfieldId: string,\n): Promise<string[] | null> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_fields\")\n\t\t.select([\"type\", \"validation\"])\n\t\t.where(\"id\", \"=\", fieldId)\n\t\t.where(\"type\", \"in\", [\"file\", \"image\"])\n\t\t.executeTakeFirst();\n\n\treturn row ? parseAllowedMimeTypes(row.validation) : null;\n}\n"],"mappings":";;;;;;;;AAUA,MAAa,0BAA6C;CACzD;CACA;CACA;CACA;CACA;;;;;;;;;;;AAYD,eAAsB,sBACrB,IACA,SAC2B;CAC3B,MAAM,MAAM,MAAM,GAChB,WAAW,iBAAiB,CAC5B,OAAO,CAAC,QAAQ,aAAa,CAAC,CAC9B,MAAM,MAAM,KAAK,QAAQ,CACzB,MAAM,QAAQ,MAAM,CAAC,QAAQ,QAAQ,CAAC,CACtC,kBAAkB;AAEpB,QAAO,MAAM,sBAAsB,IAAI,WAAW,GAAG"}
1
+ {"version":3,"file":"media-allowlist-Dknq-OFY.mjs","names":[],"sources":["../src/api/handlers/media-allowlist.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { parseAllowedMimeTypes } from \"../../media/mime.js\";\n\n/**\n * MIME types allowed for upload by default (when no field-specific list\n * overrides this). Entries ending with \"/\" are prefix-matched (e.g.\n * \"image/\" matches \"image/jpeg\", \"image/png\", etc.).\n */\nexport const GLOBAL_UPLOAD_ALLOWLIST: readonly string[] = [\n\t\"image/\",\n\t\"video/\",\n\t\"audio/\",\n\t\"application/pdf\",\n];\n\n/**\n * Resolve the MIME allowlist for a specific field.\n *\n * Returns the field's `allowedMimeTypes` list when the field exists, is of\n * type \"file\" or \"image\", and has a non-empty list configured. Returns null\n * in all other cases — callers should fall back to GLOBAL_UPLOAD_ALLOWLIST.\n *\n * Authentication is the caller's responsibility (the upload routes already\n * gate on `media:upload`).\n */\nexport async function resolveFieldAllowlist(\n\tdb: Kysely<Database>,\n\tfieldId: string,\n): Promise<string[] | null> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_fields\")\n\t\t.select([\"type\", \"validation\"])\n\t\t.where(\"id\", \"=\", fieldId)\n\t\t.where(\"type\", \"in\", [\"file\", \"image\"])\n\t\t.executeTakeFirst();\n\n\treturn row ? parseAllowedMimeTypes(row.validation) : null;\n}\n"],"mappings":";;;;;;;;AAUA,MAAa,0BAA6C;CACzD;CACA;CACA;CACA;CACA;;;;;;;;;;;AAYD,eAAsB,sBACrB,IACA,SAC2B;CAC3B,MAAM,MAAM,MAAM,GAChB,WAAW,iBAAiB,CAC5B,OAAO,CAAC,QAAQ,aAAa,CAAC,CAC9B,MAAM,MAAM,KAAK,QAAQ,CACzB,MAAM,QAAQ,MAAM,CAAC,QAAQ,QAAQ,CAAC,CACtC,kBAAkB;AAEpB,QAAO,MAAM,sBAAsB,IAAI,WAAW,GAAG"}
@@ -0,0 +1,26 @@
1
+ //#region src/seo/media-url.ts
2
+ /**
3
+ * Resolve a stored SEO image reference to a URL.
4
+ *
5
+ * The CMS SEO panel stores `seo_image` in one of these shapes:
6
+ * - an absolute URL (`https://...`) — returned as-is;
7
+ * - a root-relative path that already includes the media API prefix
8
+ * (`/_emdash/api/media/file/01KS....webp`) — prefixed with `siteUrl`;
9
+ * - a bare media id (`01KS...`) — expanded to the media API path, then
10
+ * prefixed with `siteUrl`.
11
+ *
12
+ * Shared by the SEO meta builder (`og:image`) and the sitemap route
13
+ * (`<image:image>`) so both resolve image references identically.
14
+ */
15
+ const TRAILING_SLASH_RE = /\/$/;
16
+ const ABSOLUTE_URL_RE = /^https?:\/\//i;
17
+ function buildSeoImageUrl(imageRef, siteUrl) {
18
+ if (ABSOLUTE_URL_RE.test(imageRef)) return imageRef;
19
+ if (imageRef.startsWith("/")) return siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, "")}${imageRef}` : imageRef;
20
+ const mediaPath = `/_emdash/api/media/file/${imageRef}`;
21
+ return siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, "")}${mediaPath}` : mediaPath;
22
+ }
23
+
24
+ //#endregion
25
+ export { buildSeoImageUrl as t };
26
+ //# sourceMappingURL=media-url-VClf8glU.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-url-VClf8glU.mjs","names":[],"sources":["../src/seo/media-url.ts"],"sourcesContent":["/**\n * Resolve a stored SEO image reference to a URL.\n *\n * The CMS SEO panel stores `seo_image` in one of these shapes:\n * - an absolute URL (`https://...`) — returned as-is;\n * - a root-relative path that already includes the media API prefix\n * (`/_emdash/api/media/file/01KS....webp`) — prefixed with `siteUrl`;\n * - a bare media id (`01KS...`) — expanded to the media API path, then\n * prefixed with `siteUrl`.\n *\n * Shared by the SEO meta builder (`og:image`) and the sitemap route\n * (`<image:image>`) so both resolve image references identically.\n */\nconst TRAILING_SLASH_RE = /\\/$/;\nconst ABSOLUTE_URL_RE = /^https?:\\/\\//i;\n\nexport function buildSeoImageUrl(imageRef: string, siteUrl?: string): string {\n\t// Already absolute — use as-is.\n\tif (ABSOLUTE_URL_RE.test(imageRef)) {\n\t\treturn imageRef;\n\t}\n\n\t// Root-relative path (already includes the media API prefix). Without\n\t// this branch we'd re-prefix and produce a doubled path that 404s.\n\tif (imageRef.startsWith(\"/\")) {\n\t\treturn siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, \"\")}${imageRef}` : imageRef;\n\t}\n\n\t// Bare media id — build the full media API path.\n\tconst mediaPath = `/_emdash/api/media/file/${imageRef}`;\n\treturn siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, \"\")}${mediaPath}` : mediaPath;\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;AAExB,SAAgB,iBAAiB,UAAkB,SAA0B;AAE5E,KAAI,gBAAgB,KAAK,SAAS,CACjC,QAAO;AAKR,KAAI,SAAS,WAAW,IAAI,CAC3B,QAAO,UAAU,GAAG,QAAQ,QAAQ,mBAAmB,GAAG,GAAG,aAAa;CAI3E,MAAM,YAAY,2BAA2B;AAC7C,QAAO,UAAU,GAAG,QAAQ,QAAQ,mBAAmB,GAAG,GAAG,cAAc"}
@@ -1,6 +1,6 @@
1
- import { a as __exportAll } from "./runner-pt6Wl-l-.mjs";
1
+ import { a as __exportAll } from "./runner--4wMWwKM.mjs";
2
2
  import { n as getI18nConfig } from "./config-CVssduLe.mjs";
3
- import { t as withTransaction } from "./transaction-NQj4VJ7Z.mjs";
3
+ import { t as withTransaction } from "./transaction-x2tJQ-A1.mjs";
4
4
  import { ulid } from "ulidx";
5
5
 
6
6
  //#region src/database/repositories/menu.ts
@@ -720,4 +720,4 @@ async function handleMenuItemReorder(db, menuName, items, options = {}) {
720
720
 
721
721
  //#endregion
722
722
  export { handleMenuItemDelete as a, handleMenuList as c, menus_exports as d, handleMenuItemCreate as i, handleMenuTranslations as l, handleMenuDelete as n, handleMenuItemReorder as o, handleMenuGet as r, handleMenuItemUpdate as s, handleMenuCreate as t, handleMenuUpdate as u };
723
- //# sourceMappingURL=menus-DugoYwTX.mjs.map
723
+ //# sourceMappingURL=menus-DX4_E01q.mjs.map