emdash 0.29.0 → 0.31.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 (876) hide show
  1. package/dist/{adapters-DPAkL6QF.d.mts → adapters-C0ONzkUP.d.mts} +10 -1
  2. package/dist/adapters-C0ONzkUP.d.mts.map +1 -0
  3. package/dist/{allowed-origins-FXB-FplB.mjs → allowed-origins-CCEi9bPI.mjs} +2 -2
  4. package/dist/{allowed-origins-FXB-FplB.mjs.map → allowed-origins-CCEi9bPI.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.d.mts.map +1 -1
  7. package/dist/api/route-utils.mjs +19 -19
  8. package/dist/api/schemas/index.d.mts +2 -2
  9. package/dist/api/schemas/index.mjs +5 -4
  10. package/dist/{api-DVbHNApo.mjs → api-EibxGP-V.mjs} +260 -160
  11. package/dist/api-EibxGP-V.mjs.map +1 -0
  12. package/dist/api-tokens-Cvmixds7.mjs +3 -0
  13. package/dist/{api-tokens-UYBkl17d.mjs → api-tokens-uPt8UDpx.mjs} +2 -2
  14. package/dist/{api-tokens-UYBkl17d.mjs.map → api-tokens-uPt8UDpx.mjs.map} +1 -1
  15. package/dist/{apply-D23iUV4q.mjs → apply-fGHCAwWN.mjs} +42 -44
  16. package/dist/apply-fGHCAwWN.mjs.map +1 -0
  17. package/dist/astro/index.d.mts +12 -13
  18. package/dist/astro/index.d.mts.map +1 -1
  19. package/dist/astro/index.mjs +90 -11
  20. package/dist/astro/index.mjs.map +1 -1
  21. package/dist/astro/middleware/auth.d.mts +10 -11
  22. package/dist/astro/middleware/auth.d.mts.map +1 -1
  23. package/dist/astro/middleware/auth.mjs +56 -107
  24. package/dist/astro/middleware/auth.mjs.map +1 -1
  25. package/dist/astro/middleware/redirect.mjs +7 -7
  26. package/dist/astro/middleware/request-context.mjs +4 -4
  27. package/dist/astro/middleware/request-context.mjs.map +1 -1
  28. package/dist/astro/middleware/setup.mjs +1 -1
  29. package/dist/astro/middleware.d.mts +47 -3
  30. package/dist/astro/middleware.d.mts.map +1 -1
  31. package/dist/astro/middleware.mjs +410 -123
  32. package/dist/astro/middleware.mjs.map +1 -1
  33. package/dist/astro/routes/PluginRegistry.d.mts +5 -1
  34. package/dist/astro/routes/PluginRegistry.d.mts.map +1 -1
  35. package/dist/astro/routes/PluginRegistry.mjs +2 -1
  36. package/dist/astro/routes/PluginRegistry.mjs.map +1 -1
  37. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +9 -8
  38. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
  39. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +9 -8
  40. package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
  41. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -5
  42. package/dist/astro/routes/api/admin/api-tokens/index.mjs +7 -7
  43. package/dist/astro/routes/api/admin/api-tokens/index.mjs.map +1 -1
  44. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -5
  45. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +10 -9
  46. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
  47. package/dist/astro/routes/api/admin/byline-fields/index.mjs +10 -9
  48. package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
  49. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +10 -9
  50. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
  51. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +16 -15
  52. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
  53. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +17 -22
  54. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
  55. package/dist/astro/routes/api/admin/bylines/index.d.mts.map +1 -1
  56. package/dist/astro/routes/api/admin/bylines/index.mjs +20 -18
  57. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  58. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +13 -12
  59. package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
  60. package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
  61. package/dist/astro/routes/api/admin/comments/bulk.mjs +11 -10
  62. package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
  63. package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
  64. package/dist/astro/routes/api/admin/comments/index.mjs +11 -10
  65. package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
  66. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
  67. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
  68. package/dist/astro/routes/api/admin/media-usage/repair.mjs +12 -10
  69. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
  70. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
  71. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
  72. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +41 -40
  73. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  74. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +41 -40
  75. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  76. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +46 -40
  77. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  78. package/dist/astro/routes/api/admin/plugins/_id_/mcp.d.mts +8 -0
  79. package/dist/astro/routes/api/admin/plugins/_id_/mcp.d.mts.map +1 -0
  80. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +39 -0
  81. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs.map +1 -0
  82. package/dist/astro/routes/api/admin/plugins/_id_/settings.d.mts +9 -0
  83. package/dist/astro/routes/api/admin/plugins/_id_/settings.d.mts.map +1 -0
  84. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +78 -0
  85. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -0
  86. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +40 -39
  87. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  88. package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts.map +1 -1
  89. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +43 -40
  90. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  91. package/dist/astro/routes/api/admin/plugins/index.mjs +46 -40
  92. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  93. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -4
  94. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +40 -39
  95. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  96. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts.map +1 -1
  97. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +46 -41
  98. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  99. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +40 -39
  100. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  101. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +40 -39
  102. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  103. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -1
  104. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +44 -41
  105. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  106. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +40 -39
  107. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  108. package/dist/astro/routes/api/admin/plugins/registry/install.d.mts.map +1 -1
  109. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +45 -42
  110. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  111. package/dist/astro/routes/api/admin/plugins/updates.mjs +41 -40
  112. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  113. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +40 -39
  114. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  115. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +4 -4
  116. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +40 -39
  117. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  118. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +4 -4
  119. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +4 -4
  120. package/dist/astro/routes/api/admin/users/_id_/index.mjs +9 -8
  121. package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
  122. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +5 -5
  123. package/dist/astro/routes/api/admin/users/index.mjs +9 -8
  124. package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
  125. package/dist/astro/routes/api/auth/dev-bypass.mjs +7 -7
  126. package/dist/astro/routes/api/auth/invite/accept.mjs +4 -4
  127. package/dist/astro/routes/api/auth/invite/complete.mjs +13 -12
  128. package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
  129. package/dist/astro/routes/api/auth/invite/index.mjs +10 -9
  130. package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
  131. package/dist/astro/routes/api/auth/invite/register-options.mjs +12 -11
  132. package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
  133. package/dist/astro/routes/api/auth/logout.mjs +4 -4
  134. package/dist/astro/routes/api/auth/magic-link/send.mjs +12 -11
  135. package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
  136. package/dist/astro/routes/api/auth/magic-link/verify.mjs +5 -5
  137. package/dist/astro/routes/api/auth/me.mjs +9 -8
  138. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  139. package/dist/astro/routes/api/auth/mode.d.mts.map +1 -1
  140. package/dist/astro/routes/api/auth/mode.mjs +6 -3
  141. package/dist/astro/routes/api/auth/mode.mjs.map +1 -1
  142. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +15 -5
  143. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs.map +1 -1
  144. package/dist/astro/routes/api/auth/oauth/_provider_.d.mts.map +1 -1
  145. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +11 -5
  146. package/dist/astro/routes/api/auth/oauth/_provider_.mjs.map +1 -1
  147. package/dist/astro/routes/api/auth/passkey/_id_.mjs +8 -7
  148. package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
  149. package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
  150. package/dist/astro/routes/api/auth/passkey/options.mjs +13 -12
  151. package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
  152. package/dist/astro/routes/api/auth/passkey/register/options.mjs +11 -10
  153. package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
  154. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +12 -11
  155. package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
  156. package/dist/astro/routes/api/auth/passkey/verify.mjs +12 -11
  157. package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
  158. package/dist/astro/routes/api/auth/signup/complete.mjs +13 -12
  159. package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
  160. package/dist/astro/routes/api/auth/signup/request.mjs +12 -11
  161. package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
  162. package/dist/astro/routes/api/auth/signup/verify.mjs +4 -4
  163. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +14 -13
  164. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
  165. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +13 -12
  166. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs.map +1 -1
  167. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +5 -5
  168. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs.map +1 -1
  169. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +5 -4
  170. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  171. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +6 -5
  172. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs.map +1 -1
  173. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +5 -5
  174. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs.map +1 -1
  175. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +14 -13
  176. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
  177. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +10 -8
  178. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  179. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.d.mts.map +1 -1
  180. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +20 -19
  181. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  182. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +18 -17
  183. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  184. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +5 -4
  185. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs.map +1 -1
  186. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +5 -5
  187. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs.map +1 -1
  188. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +10 -8
  189. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  190. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts.map +1 -1
  191. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +19 -18
  192. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  193. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +5 -5
  194. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs.map +1 -1
  195. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +5 -4
  196. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  197. package/dist/astro/routes/api/content/_collection_/_id_.d.mts.map +1 -1
  198. package/dist/astro/routes/api/content/_collection_/_id_.mjs +11 -9
  199. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  200. package/dist/astro/routes/api/content/_collection_/authors.mjs +5 -5
  201. package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -1
  202. package/dist/astro/routes/api/content/_collection_/index.d.mts.map +1 -1
  203. package/dist/astro/routes/api/content/_collection_/index.mjs +12 -10
  204. package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
  205. package/dist/astro/routes/api/content/_collection_/trash.mjs +10 -9
  206. package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
  207. package/dist/astro/routes/api/dashboard.mjs +8 -8
  208. package/dist/astro/routes/api/dev/emails.mjs +4 -4
  209. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  210. package/dist/astro/routes/api/import/probe.mjs +17 -16
  211. package/dist/astro/routes/api/import/probe.mjs.map +1 -1
  212. package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
  213. package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -12
  214. package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
  215. package/dist/astro/routes/api/import/wordpress/execute.mjs +13 -13
  216. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -11
  217. package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
  218. package/dist/astro/routes/api/import/wordpress/media.mjs +11 -10
  219. package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
  220. package/dist/astro/routes/api/import/wordpress/prepare.mjs +11 -10
  221. package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
  222. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -12
  223. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts.map +1 -1
  224. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +11 -10
  225. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  226. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  227. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +17 -16
  228. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
  229. package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
  230. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -12
  231. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts.map +1 -1
  232. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +24 -23
  233. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  234. package/dist/astro/routes/api/manifest.mjs +7 -8
  235. package/dist/astro/routes/api/manifest.mjs.map +1 -1
  236. package/dist/astro/routes/api/mcp.d.mts.map +1 -1
  237. package/dist/astro/routes/api/mcp.mjs +149 -45
  238. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  239. package/dist/astro/routes/api/media/_id_/confirm.mjs +9 -8
  240. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  241. package/dist/astro/routes/api/media/_id_/usage.d.mts +8 -0
  242. package/dist/astro/routes/api/media/_id_/usage.d.mts.map +1 -0
  243. package/dist/astro/routes/api/media/_id_/usage.mjs +37 -0
  244. package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -0
  245. package/dist/astro/routes/api/media/_id_.d.mts.map +1 -1
  246. package/dist/astro/routes/api/media/_id_.mjs +26 -10
  247. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  248. package/dist/astro/routes/api/media/file/_...key_.mjs +4 -3
  249. package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
  250. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
  251. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
  252. package/dist/astro/routes/api/media/providers/index.mjs +4 -4
  253. package/dist/astro/routes/api/media/upload-url.mjs +10 -9
  254. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  255. package/dist/astro/routes/api/media.d.mts.map +1 -1
  256. package/dist/astro/routes/api/media.mjs +31 -11
  257. package/dist/astro/routes/api/media.mjs.map +1 -1
  258. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +10 -9
  259. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
  260. package/dist/astro/routes/api/menus/_name_/items.mjs +10 -9
  261. package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
  262. package/dist/astro/routes/api/menus/_name_/reorder.mjs +10 -9
  263. package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
  264. package/dist/astro/routes/api/menus/_name_/translations.mjs +10 -9
  265. package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
  266. package/dist/astro/routes/api/menus/_name_.mjs +10 -9
  267. package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
  268. package/dist/astro/routes/api/menus/index.mjs +10 -9
  269. package/dist/astro/routes/api/menus/index.mjs.map +1 -1
  270. package/dist/astro/routes/api/oauth/authorize.d.mts.map +1 -1
  271. package/dist/astro/routes/api/oauth/authorize.mjs +29 -16
  272. package/dist/astro/routes/api/oauth/authorize.mjs.map +1 -1
  273. package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -7
  274. package/dist/astro/routes/api/oauth/device/code.mjs +10 -10
  275. package/dist/astro/routes/api/oauth/device/token.mjs +9 -9
  276. package/dist/astro/routes/api/oauth/register.mjs +4 -4
  277. package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -7
  278. package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -7
  279. package/dist/astro/routes/api/oauth/token.mjs +7 -7
  280. package/dist/astro/routes/api/openapi.json.mjs +52 -24
  281. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  282. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts.map +1 -1
  283. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +20 -32
  284. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs.map +1 -1
  285. package/dist/astro/routes/api/redirects/404s/index.mjs +12 -11
  286. package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
  287. package/dist/astro/routes/api/redirects/404s/summary.mjs +12 -11
  288. package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
  289. package/dist/astro/routes/api/redirects/_id_.mjs +13 -12
  290. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  291. package/dist/astro/routes/api/redirects/index.mjs +13 -12
  292. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  293. package/dist/astro/routes/api/relations/_id_/index.mjs +16 -15
  294. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  295. package/dist/astro/routes/api/relations/_id_/translations.mjs +11 -11
  296. package/dist/astro/routes/api/relations/index.mjs +16 -15
  297. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  298. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
  299. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +5 -4
  300. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs.map +1 -1
  301. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +40 -39
  302. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  303. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +40 -39
  304. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  305. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +40 -39
  306. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  307. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +40 -39
  308. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  309. package/dist/astro/routes/api/schema/collections/index.mjs +40 -39
  310. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  311. package/dist/astro/routes/api/schema/index.mjs +9 -9
  312. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +40 -39
  313. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  314. package/dist/astro/routes/api/schema/orphans/index.mjs +40 -39
  315. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  316. package/dist/astro/routes/api/search/enable.mjs +13 -12
  317. package/dist/astro/routes/api/search/enable.mjs.map +1 -1
  318. package/dist/astro/routes/api/search/index.mjs +12 -11
  319. package/dist/astro/routes/api/search/index.mjs.map +1 -1
  320. package/dist/astro/routes/api/search/rebuild.mjs +13 -12
  321. package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
  322. package/dist/astro/routes/api/search/stats.mjs +8 -8
  323. package/dist/astro/routes/api/search/suggest.mjs +12 -11
  324. package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
  325. package/dist/astro/routes/api/sections/_slug_.mjs +12 -11
  326. package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
  327. package/dist/astro/routes/api/sections/index.mjs +12 -11
  328. package/dist/astro/routes/api/sections/index.mjs.map +1 -1
  329. package/dist/astro/routes/api/settings/backups/archives/_name_.d.mts +9 -0
  330. package/dist/astro/routes/api/settings/backups/archives/_name_.d.mts.map +1 -0
  331. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +48 -0
  332. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs.map +1 -0
  333. package/dist/astro/routes/api/settings/backups/archives/index.d.mts +8 -0
  334. package/dist/astro/routes/api/settings/backups/archives/index.d.mts.map +1 -0
  335. package/dist/astro/routes/api/settings/backups/archives/index.mjs +23 -0
  336. package/dist/astro/routes/api/settings/backups/archives/index.mjs.map +1 -0
  337. package/dist/astro/routes/api/settings/backups/export.d.mts +8 -0
  338. package/dist/astro/routes/api/settings/backups/export.d.mts.map +1 -0
  339. package/dist/astro/routes/api/settings/backups/export.mjs +35 -0
  340. package/dist/astro/routes/api/settings/backups/export.mjs.map +1 -0
  341. package/dist/astro/routes/api/settings/backups/index.d.mts +9 -0
  342. package/dist/astro/routes/api/settings/backups/index.d.mts.map +1 -0
  343. package/dist/astro/routes/api/settings/backups/index.mjs +52 -0
  344. package/dist/astro/routes/api/settings/backups/index.mjs.map +1 -0
  345. package/dist/astro/routes/api/settings/email.mjs +5 -5
  346. package/dist/astro/routes/api/settings.mjs +14 -13
  347. package/dist/astro/routes/api/settings.mjs.map +1 -1
  348. package/dist/astro/routes/api/setup/admin-verify.mjs +19 -15
  349. package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
  350. package/dist/astro/routes/api/setup/admin.mjs +18 -14
  351. package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
  352. package/dist/astro/routes/api/setup/dev-bypass.mjs +26 -26
  353. package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
  354. package/dist/astro/routes/api/setup/index.mjs +28 -27
  355. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  356. package/dist/astro/routes/api/setup/status.mjs +5 -5
  357. package/dist/astro/routes/api/snapshot.mjs +8 -215
  358. package/dist/astro/routes/api/snapshot.mjs.map +1 -1
  359. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +15 -14
  360. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  361. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +15 -14
  362. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  363. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +15 -14
  364. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  365. package/dist/astro/routes/api/taxonomies/index.mjs +15 -14
  366. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  367. package/dist/astro/routes/api/themes/preview.mjs +6 -6
  368. package/dist/astro/routes/api/typegen.mjs +6 -6
  369. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  370. package/dist/astro/routes/api/well-known/oauth-authorization-server.d.mts.map +1 -1
  371. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +4 -3
  372. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs.map +1 -1
  373. package/dist/astro/routes/api/well-known/oauth-protected-resource.d.mts.map +1 -1
  374. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +4 -3
  375. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs.map +1 -1
  376. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +9 -8
  377. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  378. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +12 -11
  379. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  380. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +12 -11
  381. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  382. package/dist/astro/routes/api/widget-areas/_name_.mjs +7 -7
  383. package/dist/astro/routes/api/widget-areas/index.mjs +12 -11
  384. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  385. package/dist/astro/routes/api/widget-components.mjs +4 -4
  386. package/dist/astro/routes/robots.txt.mjs +7 -7
  387. package/dist/astro/routes/sitemap-_collection_.xml.mjs +11 -11
  388. package/dist/astro/routes/sitemap.xml.mjs +8 -8
  389. package/dist/astro/types.d.mts +38 -17
  390. package/dist/astro/types.d.mts.map +1 -1
  391. package/dist/auth/providers/github.d.mts +1 -1
  392. package/dist/auth/providers/google.d.mts +1 -1
  393. package/dist/{authorize-BmCwQCib.mjs → authorize-6WPZa1xZ.mjs} +7 -4
  394. package/dist/authorize-6WPZa1xZ.mjs.map +1 -0
  395. package/dist/backup-Qr9PaGUo.mjs +241 -0
  396. package/dist/backup-Qr9PaGUo.mjs.map +1 -0
  397. package/dist/{base64-C74Msx8h.mjs → base64-B-PsqheR.mjs} +8 -4
  398. package/dist/base64-B-PsqheR.mjs.map +1 -0
  399. package/dist/{byline-BWb5H-65.mjs → byline-BTOxUlFF.mjs} +6 -6
  400. package/dist/{byline-BWb5H-65.mjs.map → byline-BTOxUlFF.mjs.map} +1 -1
  401. package/dist/{byline-fields-DO6bAYK8.mjs → byline-fields-CdU_LTF1.mjs} +1 -1
  402. package/dist/{byline-fields-DO6bAYK8.mjs.map → byline-fields-CdU_LTF1.mjs.map} +1 -1
  403. package/dist/{bylines-CHojDMpj.mjs → bylines-FyZu6TbJ.mjs} +7 -7
  404. package/dist/{bylines-CHojDMpj.mjs.map → bylines-FyZu6TbJ.mjs.map} +1 -1
  405. package/dist/{bylines-BGuKFaBy.mjs → bylines-oVN0iPwX.mjs} +12 -8
  406. package/dist/bylines-oVN0iPwX.mjs.map +1 -0
  407. package/dist/{cache-BeR7RfN8.mjs → cache-DDwy3JDW.mjs} +2 -2
  408. package/dist/{cache-BeR7RfN8.mjs.map → cache-DDwy3JDW.mjs.map} +1 -1
  409. package/dist/{challenge-store-B426wQ6O.mjs → challenge-store-BFzgFRog.mjs} +1 -1
  410. package/dist/{challenge-store-B426wQ6O.mjs.map → challenge-store-BFzgFRog.mjs.map} +1 -1
  411. package/dist/{chunks-BU5_nrAC.mjs → chunks-Bq-5DPrD.mjs} +2 -2
  412. package/dist/{chunks-BU5_nrAC.mjs.map → chunks-Bq-5DPrD.mjs.map} +1 -1
  413. package/dist/cli/index.mjs +139 -32
  414. package/dist/cli/index.mjs.map +1 -1
  415. package/dist/client/cf-access.d.mts +1 -1
  416. package/dist/client/index.d.mts +54 -3
  417. package/dist/client/index.d.mts.map +1 -1
  418. package/dist/client/index.mjs +16 -4
  419. package/dist/client/index.mjs.map +1 -1
  420. package/dist/{comment-SWh31kS0.mjs → comment-B9cUud8H.mjs} +2 -2
  421. package/dist/{comment-SWh31kS0.mjs.map → comment-B9cUud8H.mjs.map} +1 -1
  422. package/dist/{comment-reaction-DgcpziXI.mjs → comment-reaction-VUXO981F.mjs} +2 -2
  423. package/dist/{comment-reaction-DgcpziXI.mjs.map → comment-reaction-VUXO981F.mjs.map} +1 -1
  424. package/dist/{comments-kZYGppMS.mjs → comments-PXj1E9Si.mjs} +3 -3
  425. package/dist/{comments-kZYGppMS.mjs.map → comments-PXj1E9Si.mjs.map} +1 -1
  426. package/dist/{components-CliOkZAs.mjs → components-DZkL4Fx0.mjs} +1 -1
  427. package/dist/{components-CliOkZAs.mjs.map → components-DZkL4Fx0.mjs.map} +1 -1
  428. package/dist/{config-CVssduLe.mjs → config-B6b_BobR.mjs} +6 -2
  429. package/dist/config-B6b_BobR.mjs.map +1 -0
  430. package/dist/{content-BO61Sx4j.mjs → content-BC43pICM.mjs} +53 -19
  431. package/dist/content-BC43pICM.mjs.map +1 -0
  432. package/dist/{content-refresh-BkkApzeS.mjs → content-refresh-CPMSauD3.mjs} +96 -4
  433. package/dist/content-refresh-CPMSauD3.mjs.map +1 -0
  434. package/dist/{context-DtSFYFuS.mjs → context-DOurRIrb.mjs} +97 -74
  435. package/dist/context-DOurRIrb.mjs.map +1 -0
  436. package/dist/{cron-YFJ7uNVA.mjs → cron-BlKIMD_e.mjs} +1 -1
  437. package/dist/{cron-YFJ7uNVA.mjs.map → cron-BlKIMD_e.mjs.map} +1 -1
  438. package/dist/{dashboard-xo_gPs_D.mjs → dashboard-CBm4zhc3.mjs} +4 -4
  439. package/dist/{dashboard-xo_gPs_D.mjs.map → dashboard-CBm4zhc3.mjs.map} +1 -1
  440. package/dist/database/pg-migration-lock.d.mts.map +1 -1
  441. package/dist/database/pg-migration-lock.mjs +1 -4
  442. package/dist/database/pg-migration-lock.mjs.map +1 -1
  443. package/dist/db/index.d.mts +3 -3
  444. package/dist/db/index.mjs +2 -2
  445. package/dist/db/index.mjs.map +1 -1
  446. package/dist/db/libsql.d.mts +1 -1
  447. package/dist/db/postgres.d.mts +1 -1
  448. package/dist/db/sqlite.d.mts +1 -1
  449. package/dist/{db-errors-BKuXh_fi.mjs → db-errors-CcWLaRiR.mjs} +4 -2
  450. package/dist/db-errors-CcWLaRiR.mjs.map +1 -0
  451. package/dist/{default-DY5ovGUG.mjs → default-C4v9KP56.mjs} +1 -1
  452. package/dist/{default-DY5ovGUG.mjs.map → default-C4v9KP56.mjs.map} +1 -1
  453. package/dist/{device-flow-CPXQ_QfW.mjs → device-flow-BeA6lUS1.mjs} +5 -6
  454. package/dist/device-flow-BeA6lUS1.mjs.map +1 -0
  455. package/dist/{dialect-helpers-Cue7QQOR.mjs → dialect-helpers-BtOwo2us.mjs} +60 -14
  456. package/dist/dialect-helpers-BtOwo2us.mjs.map +1 -0
  457. package/dist/{email-console-CjkQc7oY.mjs → email-console-C-9Ng8DM.mjs} +1 -1
  458. package/dist/{email-console-CjkQc7oY.mjs.map → email-console-C-9Ng8DM.mjs.map} +1 -1
  459. package/dist/error-DJ98TRbQ.mjs +90 -0
  460. package/dist/error-DJ98TRbQ.mjs.map +1 -0
  461. package/dist/{error-CfJMLSkd.mjs → errors-DtEXIQQV.mjs} +15 -84
  462. package/dist/errors-DtEXIQQV.mjs.map +1 -0
  463. package/dist/{escape-r8_GbEU2.mjs → escape-CFedIB1C.mjs} +1 -1
  464. package/dist/{escape-r8_GbEU2.mjs.map → escape-CFedIB1C.mjs.map} +1 -1
  465. package/dist/{field-defs-cache-BVrNCgSj.mjs → field-defs-cache-KfNJ_qLQ.mjs} +2 -2
  466. package/dist/{field-defs-cache-BVrNCgSj.mjs.map → field-defs-cache-KfNJ_qLQ.mjs.map} +1 -1
  467. package/dist/{fts-manager-BD9s77qh.mjs → fts-manager-hyCU9OOp.mjs} +3 -3
  468. package/dist/{fts-manager-BD9s77qh.mjs.map → fts-manager-hyCU9OOp.mjs.map} +1 -1
  469. package/dist/{hreflang-CgIxhH4k.d.mts → hreflang-CIxH_LKz.d.mts} +2 -2
  470. package/dist/{hreflang-CgIxhH4k.d.mts.map → hreflang-CIxH_LKz.d.mts.map} +1 -1
  471. package/dist/{hreflang-pAWUiPd_.mjs → hreflang-CbYeCzl9.mjs} +7 -7
  472. package/dist/{hreflang-pAWUiPd_.mjs.map → hreflang-CbYeCzl9.mjs.map} +1 -1
  473. package/dist/{import-2jHtLzsO.mjs → import-oDYCgNYQ.mjs} +4 -4
  474. package/dist/{import-2jHtLzsO.mjs.map → import-oDYCgNYQ.mjs.map} +1 -1
  475. package/dist/{index-DxjWLKGT.d.mts → index-BEui-cRd.d.mts} +3 -3
  476. package/dist/{index-DxjWLKGT.d.mts.map → index-BEui-cRd.d.mts.map} +1 -1
  477. package/dist/{index-BXOCyLj0.d.mts → index-BcEH_FfS.d.mts} +812 -16
  478. package/dist/index-BcEH_FfS.d.mts.map +1 -0
  479. package/dist/index.d.mts +18 -19
  480. package/dist/index.mjs +61 -60
  481. package/dist/{load-DgoWMdRX.mjs → load-Dg5gtWma.mjs} +2 -2
  482. package/dist/{load-DgoWMdRX.mjs.map → load-Dg5gtWma.mjs.map} +1 -1
  483. package/dist/{loader-Rb8QlGZp.mjs → loader-D4rigvjR.mjs} +170 -6
  484. package/dist/loader-D4rigvjR.mjs.map +1 -0
  485. package/dist/{manifest-schema-DeGNma2O.mjs → manifest-schema-bCq54i7F.mjs} +16 -2
  486. package/dist/manifest-schema-bCq54i7F.mjs.map +1 -0
  487. package/dist/media/image-endpoint.d.mts +30 -2
  488. package/dist/media/image-endpoint.d.mts.map +1 -1
  489. package/dist/media/image-endpoint.mjs +29 -3
  490. package/dist/media/image-endpoint.mjs.map +1 -1
  491. package/dist/media/index.d.mts +1 -1
  492. package/dist/media/local-runtime.d.mts +12 -13
  493. package/dist/media/local-runtime.d.mts.map +1 -1
  494. package/dist/media/local-runtime.mjs +6 -6
  495. package/dist/media-BJ6SRY3g.mjs +247 -0
  496. package/dist/media-BJ6SRY3g.mjs.map +1 -0
  497. package/dist/{media-allowlist-Dc05vKtt.mjs → media-allowlist-jUppPWFy.mjs} +1 -1
  498. package/dist/{media-allowlist-Dc05vKtt.mjs.map → media-allowlist-jUppPWFy.mjs.map} +1 -1
  499. package/dist/{media-D2ZFowpn.mjs → media-its1WLYv.mjs} +2 -2
  500. package/dist/{media-D2ZFowpn.mjs.map → media-its1WLYv.mjs.map} +1 -1
  501. package/dist/media-upload-DNu3QySc.mjs +145 -0
  502. package/dist/media-upload-DNu3QySc.mjs.map +1 -0
  503. package/dist/{media-url-DgIEYZr4.mjs → media-url-BCm5vBn6.mjs} +1 -1
  504. package/dist/{media-url-DgIEYZr4.mjs.map → media-url-BCm5vBn6.mjs.map} +1 -1
  505. package/dist/{media-usage-B__v9NMm.mjs → media-usage-DPQ3K4xB.mjs} +138 -5
  506. package/dist/{media-usage-B__v9NMm.mjs.map → media-usage-DPQ3K4xB.mjs.map} +1 -1
  507. package/dist/{menus-BpfevlJD.mjs → menus-BMGzWyWH.mjs} +15 -8
  508. package/dist/menus-BMGzWyWH.mjs.map +1 -0
  509. package/dist/{menus-BtvH7Z7t.mjs → menus-CPf_SW7u.mjs} +234 -13
  510. package/dist/menus-CPf_SW7u.mjs.map +1 -0
  511. package/dist/{mode-BVnhGZ2o.mjs → mode-fiXRMfeA.mjs} +1 -1
  512. package/dist/{mode-BVnhGZ2o.mjs.map → mode-fiXRMfeA.mjs.map} +1 -1
  513. package/dist/{oauth-authorization-BWqWTHBK.mjs → oauth-authorization-oqfbBuMH.mjs} +5 -6
  514. package/dist/oauth-authorization-oqfbBuMH.mjs.map +1 -0
  515. package/dist/{oauth-clients-43_XADoa.mjs → oauth-clients-BlrUHAsf.mjs} +1 -1
  516. package/dist/{oauth-clients-43_XADoa.mjs.map → oauth-clients-BlrUHAsf.mjs.map} +1 -1
  517. package/dist/{oauth-state-store-BRSUEkmx.mjs → oauth-state-store-DnvjRMy6.mjs} +2 -1
  518. package/dist/{oauth-state-store-BRSUEkmx.mjs.map → oauth-state-store-DnvjRMy6.mjs.map} +1 -1
  519. package/dist/{oauth-user-lookup-Ctgemb0k.mjs → oauth-user-lookup-oUllxvAv.mjs} +1 -1
  520. package/dist/{oauth-user-lookup-Ctgemb0k.mjs.map → oauth-user-lookup-oUllxvAv.mjs.map} +1 -1
  521. package/dist/object-cache/memory.d.mts +1 -1
  522. package/dist/{options-COXGTtqd.d.mts → options-BqMkWfYa.d.mts} +6 -6
  523. package/dist/{options-COXGTtqd.d.mts.map → options-BqMkWfYa.d.mts.map} +1 -1
  524. package/dist/page/index.d.mts +2 -2
  525. package/dist/page/index.d.mts.map +1 -1
  526. package/dist/page/index.mjs.map +1 -1
  527. package/dist/{parse-GLksnn-B.mjs → parse-53zyxlpv.mjs} +4 -12
  528. package/dist/{parse-GLksnn-B.mjs.map → parse-53zyxlpv.mjs.map} +1 -1
  529. package/dist/{passkey-config-CjA1UbDj.mjs → passkey-config-C-SKpc0-.mjs} +1 -1
  530. package/dist/{passkey-config-CjA1UbDj.mjs.map → passkey-config-C-SKpc0-.mjs.map} +1 -1
  531. package/dist/{placeholder-DR-eeZr-.d.mts → placeholder-Dop47JMM.d.mts} +1 -1
  532. package/dist/{placeholder-DR-eeZr-.d.mts.map → placeholder-Dop47JMM.d.mts.map} +1 -1
  533. package/dist/plugin-types.d.mts +21 -2
  534. package/dist/plugin-types.d.mts.map +1 -1
  535. package/dist/plugin-utils.d.mts +18 -17
  536. package/dist/plugin-utils.d.mts.map +1 -1
  537. package/dist/plugin-utils.mjs +8 -6
  538. package/dist/plugin-utils.mjs.map +1 -1
  539. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -11
  540. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  541. package/dist/plugins/adapt-sandbox-entry.mjs +15 -3
  542. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  543. package/dist/{portable-text-BAkgEKcv.mjs → portable-text-sDj92Kxv.mjs} +1 -1
  544. package/dist/{portable-text-BAkgEKcv.mjs.map → portable-text-sDj92Kxv.mjs.map} +1 -1
  545. package/dist/{preview-Rloa09Wp.mjs → preview-D4Jnbfx7.mjs} +2 -2
  546. package/dist/{preview-Rloa09Wp.mjs.map → preview-D4Jnbfx7.mjs.map} +1 -1
  547. package/dist/{public-url-C0cEvrRu.mjs → public-url-DSGTnJFw.mjs} +5 -3
  548. package/dist/public-url-DSGTnJFw.mjs.map +1 -0
  549. package/dist/{query-ZzvXyxWq.mjs → query-BbJQ-dGg.mjs} +15 -15
  550. package/dist/{query-ZzvXyxWq.mjs.map → query-BbJQ-dGg.mjs.map} +1 -1
  551. package/dist/{query-BxT95opY.mjs → query-DdFGHzQF.mjs} +3 -3
  552. package/dist/{query-BxT95opY.mjs.map → query-DdFGHzQF.mjs.map} +1 -1
  553. package/dist/{rate-limit-Cg4Rvc_-.mjs → rate-limit-D3IEiUi2.mjs} +2 -2
  554. package/dist/{rate-limit-Cg4Rvc_-.mjs.map → rate-limit-D3IEiUi2.mjs.map} +1 -1
  555. package/dist/{redirect-Ch3E98qZ.mjs → redirect-BoUac4NT.mjs} +10 -4
  556. package/dist/redirect-BoUac4NT.mjs.map +1 -0
  557. package/dist/{redirect-Ce_6Yb6z.mjs → redirect-Botom7X6.mjs} +1 -1
  558. package/dist/{redirect-Ce_6Yb6z.mjs.map → redirect-Botom7X6.mjs.map} +1 -1
  559. package/dist/{redirects-CXmVGRNZ.mjs → redirects-ChbpqKiS.mjs} +4 -4
  560. package/dist/{redirects-CXmVGRNZ.mjs.map → redirects-ChbpqKiS.mjs.map} +1 -1
  561. package/dist/{redirects-CeD7K7EX.mjs → redirects-D457qwt0.mjs} +10 -181
  562. package/dist/redirects-D457qwt0.mjs.map +1 -0
  563. package/dist/{registry-CyIXPi4E.mjs → registry-y21kjhfG.mjs} +97 -8
  564. package/dist/registry-y21kjhfG.mjs.map +1 -0
  565. package/dist/{relation-Ba68Tm4Z.mjs → relation-Du0r04E0.mjs} +3 -3
  566. package/dist/{relation-Ba68Tm4Z.mjs.map → relation-Du0r04E0.mjs.map} +1 -1
  567. package/dist/{relations-CbBpoOys.d.mts → relations-BKybBoOX.d.mts} +315 -9
  568. package/dist/relations-BKybBoOX.d.mts.map +1 -0
  569. package/dist/{relations-BmBmX4Eb.mjs → relations-CZMpBqEd.mjs} +3 -3
  570. package/dist/{relations-BmBmX4Eb.mjs.map → relations-CZMpBqEd.mjs.map} +1 -1
  571. package/dist/{relations-C8hVyEy0.mjs → relations-DtX1UMBH.mjs} +13 -7
  572. package/dist/relations-DtX1UMBH.mjs.map +1 -0
  573. package/dist/{request-meta-DCwktC5P.mjs → request-meta-DzXYYI-n.mjs} +2 -2
  574. package/dist/{request-meta-DCwktC5P.mjs.map → request-meta-DzXYYI-n.mjs.map} +1 -1
  575. package/dist/{resolve-Cd9dzclN.mjs → resolve-DFr4kq72.mjs} +2 -2
  576. package/dist/{resolve-Cd9dzclN.mjs.map → resolve-DFr4kq72.mjs.map} +1 -1
  577. package/dist/{runner-jt65bF7F.mjs → runner-BdeLEgad.mjs} +334 -200
  578. package/dist/runner-BdeLEgad.mjs.map +1 -0
  579. package/dist/{runner-CiNdIk3y.d.mts → runner-Dd8m8k9F.d.mts} +2 -2
  580. package/dist/{runner-CiNdIk3y.d.mts.map → runner-Dd8m8k9F.d.mts.map} +1 -1
  581. package/dist/runtime.d.mts +11 -12
  582. package/dist/runtime.d.mts.map +1 -1
  583. package/dist/runtime.mjs +4 -4
  584. package/dist/{schema-DwjufZ1S.mjs → schema-BH8P8vaC.mjs} +3 -3
  585. package/dist/{schema-DwjufZ1S.mjs.map → schema-BH8P8vaC.mjs.map} +1 -1
  586. package/dist/scopes-DTIkIaGA.mjs +27 -0
  587. package/dist/scopes-DTIkIaGA.mjs.map +1 -0
  588. package/dist/{search-Cl875CVE.mjs → search-D-Yhpmvs.mjs} +8 -7
  589. package/dist/search-D-Yhpmvs.mjs.map +1 -0
  590. package/dist/{secrets-CO_bEmII.mjs → secrets-CQ-69kF9.mjs} +25 -19
  591. package/dist/secrets-CQ-69kF9.mjs.map +1 -0
  592. package/dist/{sections-DTKzgoiS.mjs → sections-CzVBAKjw.mjs} +3 -3
  593. package/dist/{sections-DTKzgoiS.mjs.map → sections-CzVBAKjw.mjs.map} +1 -1
  594. package/dist/seed/index.d.mts +2 -2
  595. package/dist/seed/index.mjs +20 -20
  596. package/dist/seo/index.d.mts +14 -3
  597. package/dist/seo/index.d.mts.map +1 -1
  598. package/dist/seo/index.mjs +17 -17
  599. package/dist/seo/index.mjs.map +1 -1
  600. package/dist/{seo-BjrbXAd_.mjs → seo-C3GDfT0V.mjs} +1 -1
  601. package/dist/{seo-BjrbXAd_.mjs.map → seo-C3GDfT0V.mjs.map} +1 -1
  602. package/dist/{seo-BLm6SXzS.mjs → seo-kJzVAmmu.mjs} +2 -2
  603. package/dist/{seo-BLm6SXzS.mjs.map → seo-kJzVAmmu.mjs.map} +1 -1
  604. package/dist/{service-D6zDmKXy.mjs → service-CQnc276a.mjs} +3 -3
  605. package/dist/{service-D6zDmKXy.mjs.map → service-CQnc276a.mjs.map} +1 -1
  606. package/dist/{session-user-DoSs6YM8.mjs → session-user-DbHqKDKe.mjs} +1 -1
  607. package/dist/{session-user-DoSs6YM8.mjs.map → session-user-DbHqKDKe.mjs.map} +1 -1
  608. package/dist/{settings-BuQYvLfy.mjs → settings-C2PaBS49.mjs} +4 -4
  609. package/dist/{settings-BuQYvLfy.mjs.map → settings-C2PaBS49.mjs.map} +1 -1
  610. package/dist/{settings-CmS3TwXf.mjs → settings-Dtc0DU31.mjs} +3 -3
  611. package/dist/{settings-CmS3TwXf.mjs.map → settings-Dtc0DU31.mjs.map} +1 -1
  612. package/dist/{setup-complete-BPNgLHlU.mjs → setup-complete-CdIlqloh.mjs} +1 -1
  613. package/dist/{setup-complete-BPNgLHlU.mjs.map → setup-complete-CdIlqloh.mjs.map} +1 -1
  614. package/dist/{setup-nonce-DEPZX96T.mjs → setup-nonce-DqiIPr-J.mjs} +1 -1
  615. package/dist/{setup-nonce-DEPZX96T.mjs.map → setup-nonce-DqiIPr-J.mjs.map} +1 -1
  616. package/dist/{single-flight-cache-DEtW-OEG.mjs → single-flight-cache-C2exrGAi.mjs} +1 -1
  617. package/dist/{single-flight-cache-DEtW-OEG.mjs.map → single-flight-cache-C2exrGAi.mjs.map} +1 -1
  618. package/dist/{site-url-CcLy5D5s.mjs → site-url-NGJT6NTU.mjs} +1 -1
  619. package/dist/{site-url-CcLy5D5s.mjs.map → site-url-NGJT6NTU.mjs.map} +1 -1
  620. package/dist/snapshot-BdpUJKD-.mjs +216 -0
  621. package/dist/snapshot-BdpUJKD-.mjs.map +1 -0
  622. package/dist/{ssrf-TrKYJAy0.mjs → ssrf-CviKqWmq.mjs} +1 -1
  623. package/dist/{ssrf-TrKYJAy0.mjs.map → ssrf-CviKqWmq.mjs.map} +1 -1
  624. package/dist/state-xxv6ZTMv.mjs +128 -0
  625. package/dist/state-xxv6ZTMv.mjs.map +1 -0
  626. package/dist/{status-D3Gy1Zc6.mjs → status-COfycGIU.mjs} +1 -1
  627. package/dist/{status-D3Gy1Zc6.mjs.map → status-COfycGIU.mjs.map} +1 -1
  628. package/dist/storage/local.d.mts +1 -1
  629. package/dist/storage/local.mjs +1 -1
  630. package/dist/storage/s3.d.mts +1 -1
  631. package/dist/storage/s3.mjs +1 -1
  632. package/dist/{taxonomies-pUkuD6nj.mjs → taxonomies-HI9fnTdp.mjs} +8 -8
  633. package/dist/{taxonomies-pUkuD6nj.mjs.map → taxonomies-HI9fnTdp.mjs.map} +1 -1
  634. package/dist/{taxonomies-ClS4RKaZ.mjs → taxonomies-rMeBzOjX.mjs} +26 -17
  635. package/dist/taxonomies-rMeBzOjX.mjs.map +1 -0
  636. package/dist/{taxonomy-BkxbUMSw.mjs → taxonomy-0mCI25HN.mjs} +51 -13
  637. package/dist/taxonomy-0mCI25HN.mjs.map +1 -0
  638. package/dist/{tokens-BV6fgl5A.mjs → tokens-DVltpO2D.mjs} +2 -2
  639. package/dist/{tokens-BV6fgl5A.mjs.map → tokens-DVltpO2D.mjs.map} +1 -1
  640. package/dist/{transport-DTFhhPOg.mjs → transport-CKrlqSpD.mjs} +1 -1
  641. package/dist/{transport-DTFhhPOg.mjs.map → transport-CKrlqSpD.mjs.map} +1 -1
  642. package/dist/{transport-DmlmzD2U.d.mts → transport-Vd7VdqHv.d.mts} +1 -1
  643. package/dist/{transport-DmlmzD2U.d.mts.map → transport-Vd7VdqHv.d.mts.map} +1 -1
  644. package/dist/{trusted-proxy-CSgZVESw.mjs → trusted-proxy-CwjQj0YG.mjs} +1 -1
  645. package/dist/{trusted-proxy-CSgZVESw.mjs.map → trusted-proxy-CwjQj0YG.mjs.map} +1 -1
  646. package/dist/{types-B6-N9cBB.d.mts → types-B4vm81VH.d.mts} +1 -1
  647. package/dist/{types-B6-N9cBB.d.mts.map → types-B4vm81VH.d.mts.map} +1 -1
  648. package/dist/{types-Cm4Cz-8M.d.mts → types-B97jg3aW.d.mts} +6 -1
  649. package/dist/{types-Cm4Cz-8M.d.mts.map → types-B97jg3aW.d.mts.map} +1 -1
  650. package/dist/{types-BNZsPL9z.mjs → types-BD40g7zz.mjs} +1 -1
  651. package/dist/{types-BNZsPL9z.mjs.map → types-BD40g7zz.mjs.map} +1 -1
  652. package/dist/{types-QnlQIyPE.d.mts → types-BNfPHfay.d.mts} +1 -1
  653. package/dist/{types-QnlQIyPE.d.mts.map → types-BNfPHfay.d.mts.map} +1 -1
  654. package/dist/{types-DLIrNtv5.d.mts → types-BfYUPJdP.d.mts} +2 -2
  655. package/dist/{types-DLIrNtv5.d.mts.map → types-BfYUPJdP.d.mts.map} +1 -1
  656. package/dist/{types-CtDVagM4.d.mts → types-BvB7gDOD.d.mts} +38 -4
  657. package/dist/types-BvB7gDOD.d.mts.map +1 -0
  658. package/dist/{types-B_pmP7Yq.d.mts → types-C7U0HlEr.d.mts} +1 -1
  659. package/dist/{types-B_pmP7Yq.d.mts.map → types-C7U0HlEr.d.mts.map} +1 -1
  660. package/dist/{types-Ch8pFMEm.mjs → types-CrRUFdej.mjs} +3 -3
  661. package/dist/{types-Ch8pFMEm.mjs.map → types-CrRUFdej.mjs.map} +1 -1
  662. package/dist/{types-momGysZk.d.mts → types-DPKhI5ib.d.mts} +9 -1
  663. package/dist/types-DPKhI5ib.d.mts.map +1 -0
  664. package/dist/{types-BCVuUPrL.d.mts → types-Kmaxdarz.d.mts} +1 -1
  665. package/dist/{types-BCVuUPrL.d.mts.map → types-Kmaxdarz.d.mts.map} +1 -1
  666. package/dist/{types-qljd1hsL.d.mts → types-iVeZ5g6N.d.mts} +1 -1
  667. package/dist/{types-qljd1hsL.d.mts.map → types-iVeZ5g6N.d.mts.map} +1 -1
  668. package/dist/{user-DTxX_Fha.mjs → user-aTXqmXBz.mjs} +3 -3
  669. package/dist/{user-DTxX_Fha.mjs.map → user-aTXqmXBz.mjs.map} +1 -1
  670. package/dist/{utils-Cz8PVrl4.mjs → utils-BbUgmeZG.mjs} +1 -1
  671. package/dist/{utils-Cz8PVrl4.mjs.map → utils-BbUgmeZG.mjs.map} +1 -1
  672. package/dist/{validate-C3CpWlNt.mjs → validate-CDT1jl6_.mjs} +11 -6
  673. package/dist/validate-CDT1jl6_.mjs.map +1 -0
  674. package/dist/{validate-9jLuE301.d.mts → validate-DULfgpJP.d.mts} +5 -5
  675. package/dist/{validate-9jLuE301.d.mts.map → validate-DULfgpJP.d.mts.map} +1 -1
  676. package/dist/{validation-DM2K_NJC.mjs → validation-DlD0E35W.mjs} +10 -10
  677. package/dist/{validation-DM2K_NJC.mjs.map → validation-DlD0E35W.mjs.map} +1 -1
  678. package/dist/version-Ci6bw6YK.mjs +7 -0
  679. package/dist/{version-BlIJvNAZ.mjs.map → version-Ci6bw6YK.mjs.map} +1 -1
  680. package/dist/{widgets-B_X6_UXJ.mjs → widgets-5fipNkMT.mjs} +3 -3
  681. package/dist/{widgets-B_X6_UXJ.mjs.map → widgets-5fipNkMT.mjs.map} +1 -1
  682. package/dist/{wxr-taxonomies-tADW4KTa.d.mts → wxr-taxonomies-B4tMuN5T.d.mts} +1 -1
  683. package/dist/{wxr-taxonomies-tADW4KTa.d.mts.map → wxr-taxonomies-B4tMuN5T.d.mts.map} +1 -1
  684. package/dist/{wxr-taxonomies-oNJB2S81.mjs → wxr-taxonomies-BmUnBNgz.mjs} +4 -4
  685. package/dist/{wxr-taxonomies-oNJB2S81.mjs.map → wxr-taxonomies-BmUnBNgz.mjs.map} +1 -1
  686. package/dist/{zod-generator-CrfV79De.mjs → zod-generator-CwkHWz4f.mjs} +2 -2
  687. package/dist/{zod-generator-CrfV79De.mjs.map → zod-generator-CwkHWz4f.mjs.map} +1 -1
  688. package/package.json +8 -8
  689. package/src/api/authorize.ts +10 -1
  690. package/src/api/error.ts +5 -5
  691. package/src/api/errors.ts +14 -0
  692. package/src/api/handlers/backup.ts +312 -0
  693. package/src/api/handlers/bylines.ts +5 -4
  694. package/src/api/handlers/content.ts +61 -9
  695. package/src/api/handlers/device-flow.ts +2 -3
  696. package/src/api/handlers/index.ts +16 -0
  697. package/src/api/handlers/marketplace.ts +38 -0
  698. package/src/api/handlers/media-upload.ts +210 -0
  699. package/src/api/handlers/media-usage.ts +211 -2
  700. package/src/api/handlers/menus.ts +11 -6
  701. package/src/api/handlers/oauth-authorization.ts +2 -6
  702. package/src/api/handlers/plugin-settings.ts +218 -0
  703. package/src/api/handlers/plugins.ts +41 -0
  704. package/src/api/handlers/registry.ts +39 -0
  705. package/src/api/handlers/relations.ts +7 -2
  706. package/src/api/handlers/snapshot.ts +21 -3
  707. package/src/api/handlers/taxonomies.ts +22 -14
  708. package/src/api/openapi/document.ts +48 -6
  709. package/src/api/parse.ts +1 -15
  710. package/src/api/public-url.ts +4 -2
  711. package/src/api/schemas/bylines.ts +4 -4
  712. package/src/api/schemas/common.ts +13 -8
  713. package/src/api/schemas/media-usage.ts +72 -0
  714. package/src/api/schemas/media.ts +32 -1
  715. package/src/api/schemas/menus.ts +2 -1
  716. package/src/api/schemas/relations.ts +3 -1
  717. package/src/api/schemas/taxonomies.ts +4 -2
  718. package/src/astro/integration/index.ts +38 -0
  719. package/src/astro/integration/routes.ts +35 -0
  720. package/src/astro/integration/runtime.ts +6 -0
  721. package/src/astro/integration/virtual-modules.ts +41 -7
  722. package/src/astro/integration/vite-config.ts +17 -1
  723. package/src/astro/middleware/auth.ts +70 -78
  724. package/src/astro/middleware/csp.ts +5 -2
  725. package/src/astro/middleware.ts +86 -31
  726. package/src/astro/public-plugin-api-routes.ts +2 -1
  727. package/src/astro/routes/PluginRegistry.tsx +5 -2
  728. package/src/astro/routes/admin.astro +56 -16
  729. package/src/astro/routes/api/admin/api-tokens/index.ts +2 -2
  730. package/src/astro/routes/api/admin/bylines/[id]/translations.ts +2 -5
  731. package/src/astro/routes/api/admin/bylines/index.ts +5 -8
  732. package/src/astro/routes/api/admin/plugins/[id]/index.ts +6 -0
  733. package/src/astro/routes/api/admin/plugins/[id]/mcp.ts +42 -0
  734. package/src/astro/routes/api/admin/plugins/[id]/settings.ts +80 -0
  735. package/src/astro/routes/api/admin/plugins/[id]/update.ts +2 -0
  736. package/src/astro/routes/api/admin/plugins/index.ts +9 -0
  737. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +2 -0
  738. package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +2 -0
  739. package/src/astro/routes/api/admin/plugins/registry/install.ts +2 -0
  740. package/src/astro/routes/api/admin/plugins/updates.ts +2 -4
  741. package/src/astro/routes/api/auth/mode.ts +6 -14
  742. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +21 -4
  743. package/src/astro/routes/api/auth/oauth/[provider].ts +24 -6
  744. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +3 -4
  745. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +2 -3
  746. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +2 -3
  747. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +3 -4
  748. package/src/astro/routes/api/content/[collection]/[id]/references/[relation]/children.ts +4 -6
  749. package/src/astro/routes/api/content/[collection]/[id]/references/[relation]/parents.ts +2 -3
  750. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -4
  751. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +4 -6
  752. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +3 -4
  753. package/src/astro/routes/api/content/[collection]/[id].ts +3 -4
  754. package/src/astro/routes/api/content/[collection]/authors.ts +2 -3
  755. package/src/astro/routes/api/content/[collection]/index.ts +6 -8
  756. package/src/astro/routes/api/content/[collection]/trash.ts +2 -3
  757. package/src/astro/routes/api/manifest.ts +2 -9
  758. package/src/astro/routes/api/mcp.ts +2 -1
  759. package/src/astro/routes/api/media/[id]/usage.ts +30 -0
  760. package/src/astro/routes/api/media/[id].ts +17 -6
  761. package/src/astro/routes/api/media/file/[...key].ts +8 -0
  762. package/src/astro/routes/api/media.ts +21 -2
  763. package/src/astro/routes/api/oauth/authorize.ts +43 -15
  764. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +17 -3
  765. package/src/astro/routes/api/settings/backups/archives/[name].ts +78 -0
  766. package/src/astro/routes/api/settings/backups/archives/index.ts +33 -0
  767. package/src/astro/routes/api/settings/backups/export.ts +43 -0
  768. package/src/astro/routes/api/settings/backups/index.ts +76 -0
  769. package/src/astro/routes/api/setup/admin-verify.ts +8 -4
  770. package/src/astro/routes/api/setup/admin.ts +11 -4
  771. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +4 -1
  772. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +5 -1
  773. package/src/astro/types.ts +48 -2
  774. package/src/auth/api-tokens.ts +1 -0
  775. package/src/auth/oauth-state-store.ts +3 -0
  776. package/src/auth/providers/github-admin.tsx +5 -6
  777. package/src/auth/providers/google-admin.tsx +5 -6
  778. package/src/auth/scopes.ts +3 -9
  779. package/src/cli/client-factory.ts +0 -1
  780. package/src/cli/commands/bundle-utils.ts +50 -1
  781. package/src/cli/commands/bundle.ts +9 -2
  782. package/src/cli/commands/media.ts +127 -0
  783. package/src/client/index.ts +84 -2
  784. package/src/client/transport.ts +1 -1
  785. package/src/components/EmDashHead.astro +4 -8
  786. package/src/components/Gallery.astro +141 -26
  787. package/src/components/InlinePortableTextEditor.tsx +56 -35
  788. package/src/components/marks/Link.astro +5 -1
  789. package/src/config/secrets.ts +25 -19
  790. package/src/content/converters/gallery.ts +61 -0
  791. package/src/content/converters/portable-text-to-prosemirror.ts +19 -0
  792. package/src/content/converters/prosemirror-to-portable-text.ts +18 -0
  793. package/src/content/converters/types.ts +36 -0
  794. package/src/database/dialect-helpers.ts +80 -7
  795. package/src/database/migrations/051_content_taxonomies_denorm.ts +120 -0
  796. package/src/database/migrations/052_media_usage_read_index.ts +26 -0
  797. package/src/database/migrations/053_plugin_mcp_tools.ts +21 -0
  798. package/src/database/migrations/runner.ts +6 -0
  799. package/src/database/pg-migration-lock.ts +1 -4
  800. package/src/database/repositories/audit.ts +2 -1
  801. package/src/database/repositories/content.ts +98 -16
  802. package/src/database/repositories/media-usage.ts +250 -1
  803. package/src/database/repositories/plugin-storage.ts +29 -32
  804. package/src/database/repositories/redirect.ts +14 -2
  805. package/src/database/repositories/taxonomy.ts +57 -8
  806. package/src/database/types.ts +23 -0
  807. package/src/db/adapters.ts +9 -0
  808. package/src/emdash-runtime.ts +365 -83
  809. package/src/i18n/config.ts +8 -0
  810. package/src/i18n/repair-locale-casing.ts +51 -0
  811. package/src/loader.ts +292 -1
  812. package/src/mcp/server.ts +222 -5
  813. package/src/media/image-endpoint.ts +38 -2
  814. package/src/page/metadata.ts +27 -0
  815. package/src/plugin-types.ts +19 -0
  816. package/src/plugin-utils.ts +8 -6
  817. package/src/plugins/adapt-sandbox-entry.ts +30 -1
  818. package/src/plugins/context.ts +7 -0
  819. package/src/plugins/define-plugin.ts +16 -0
  820. package/src/plugins/manifest-schema.ts +28 -1
  821. package/src/plugins/routes.ts +27 -1
  822. package/src/plugins/sandbox/runner-options.ts +16 -0
  823. package/src/plugins/sandbox/types.ts +6 -1
  824. package/src/plugins/state.ts +17 -1
  825. package/src/plugins/storage-query.ts +67 -33
  826. package/src/plugins/types.ts +41 -0
  827. package/src/schema/registry.ts +141 -5
  828. package/src/search/query.ts +3 -2
  829. package/src/seed/apply.ts +37 -36
  830. package/src/seed/validate.ts +15 -4
  831. package/src/seo/index.ts +19 -4
  832. package/src/storage/types.ts +6 -0
  833. package/src/utils/base64.ts +7 -2
  834. package/src/utils/db-errors.ts +3 -1
  835. package/src/virtual-modules.d.ts +13 -0
  836. package/src/visual-editing/toolbar.ts +1 -1
  837. package/dist/adapters-DPAkL6QF.d.mts.map +0 -1
  838. package/dist/api-DVbHNApo.mjs.map +0 -1
  839. package/dist/api-tokens-Dk2GrJMY.mjs +0 -3
  840. package/dist/apply-D23iUV4q.mjs.map +0 -1
  841. package/dist/authorize-BmCwQCib.mjs.map +0 -1
  842. package/dist/base64-C74Msx8h.mjs.map +0 -1
  843. package/dist/bylines-BGuKFaBy.mjs.map +0 -1
  844. package/dist/config-CVssduLe.mjs.map +0 -1
  845. package/dist/content-BO61Sx4j.mjs.map +0 -1
  846. package/dist/content-refresh-BkkApzeS.mjs.map +0 -1
  847. package/dist/context-DtSFYFuS.mjs.map +0 -1
  848. package/dist/db-errors-BKuXh_fi.mjs.map +0 -1
  849. package/dist/device-flow-CPXQ_QfW.mjs.map +0 -1
  850. package/dist/dialect-helpers-Cue7QQOR.mjs.map +0 -1
  851. package/dist/error-CfJMLSkd.mjs.map +0 -1
  852. package/dist/index-BXOCyLj0.d.mts.map +0 -1
  853. package/dist/loader-Rb8QlGZp.mjs.map +0 -1
  854. package/dist/manifest-schema-DeGNma2O.mjs.map +0 -1
  855. package/dist/menus-BpfevlJD.mjs.map +0 -1
  856. package/dist/menus-BtvH7Z7t.mjs.map +0 -1
  857. package/dist/oauth-authorization-BWqWTHBK.mjs.map +0 -1
  858. package/dist/public-url-C0cEvrRu.mjs.map +0 -1
  859. package/dist/redirect-Ch3E98qZ.mjs.map +0 -1
  860. package/dist/redirects-CeD7K7EX.mjs.map +0 -1
  861. package/dist/registry-CyIXPi4E.mjs.map +0 -1
  862. package/dist/relations-C8hVyEy0.mjs.map +0 -1
  863. package/dist/relations-CbBpoOys.d.mts.map +0 -1
  864. package/dist/runner-jt65bF7F.mjs.map +0 -1
  865. package/dist/scheduled-publish-DlJHGcuL.d.mts +0 -11
  866. package/dist/scheduled-publish-DlJHGcuL.d.mts.map +0 -1
  867. package/dist/search-Cl875CVE.mjs.map +0 -1
  868. package/dist/secrets-CO_bEmII.mjs.map +0 -1
  869. package/dist/taxonomies-ClS4RKaZ.mjs.map +0 -1
  870. package/dist/taxonomy-BkxbUMSw.mjs.map +0 -1
  871. package/dist/types-CtDVagM4.d.mts.map +0 -1
  872. package/dist/types-momGysZk.d.mts.map +0 -1
  873. package/dist/validate-C3CpWlNt.mjs.map +0 -1
  874. package/dist/version-BlIJvNAZ.mjs +0 -7
  875. /package/dist/{ssrf-B5DOv5E5.mjs → ssrf-BB__xfNM.mjs} +0 -0
  876. /package/dist/{types-DeCZBZxn.mjs → types-CK0LxPUQ.mjs} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"file":"content-refresh-BkkApzeS.mjs","names":["isRecord","isRecord","readString"],"sources":["../src/database/repositories/media-usage.ts","../src/media/usage/content-fields.ts","../src/media/usage/extractor.ts","../src/media/usage/source-key.ts","../src/media/usage/content-snapshots.ts","../src/media/usage/content-refresh.ts"],"sourcesContent":["import {\n\tsql,\n\ttype ExpressionBuilder,\n\ttype Insertable,\n\ttype Kysely,\n\ttype Selectable,\n\ttype Transaction,\n\ttype Updateable,\n} from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport type { MediaUsageContentSourceVariant } from \"../../media/usage/source-key.js\";\nimport type { MediaKind, MediaUsageReferenceType } from \"../../media/usage/types.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../../utils/chunks.js\";\nimport { withTransaction } from \"../transaction.js\";\nimport type {\n\tDatabase,\n\tMediaUsageIndexStatusTable,\n\tMediaUsageSourceTable,\n\tMediaUsageTable,\n} from \"../types.js\";\nimport { validateIdentifier } from \"../validate.js\";\nimport { decodeCursor, encodeCursor, type FindManyResult } from \"./types.js\";\n\ntype DatabaseExecutor = Kysely<Database> | Transaction<Database>;\ntype MediaUsageSourceNullableStringColumn =\n\t| \"source_fingerprint\"\n\t| \"source_updated_at\"\n\t| \"revision_id\"\n\t| \"updated_at\"\n\t| \"last_attempted_at\"\n\t| \"last_error_code\";\n\nconst OCCURRENCE_BIND_COLUMNS = 13;\nconst OCCURRENCE_INSERT_BATCH_SIZE = Math.max(\n\t1,\n\tMath.floor(SQL_BATCH_SIZE / OCCURRENCE_BIND_COLUMNS),\n);\n\nexport interface MediaUsageSourceInput {\n\tsourceKey: string;\n\tsourceType: string;\n\tcollectionSlug?: string | null;\n\tcontentId?: string | null;\n\tsourceVariant: MediaUsageContentSourceVariant;\n\tlocale?: string | null;\n\ttranslationGroup?: string | null;\n\tcontentSlug?: string | null;\n\tcontentTitle?: string | null;\n\tcontentStatus?: string | null;\n\tcontentScheduledAt?: string | null;\n\tcontentDeletedAt?: string | null;\n\trevisionId?: string | null;\n\tschemaVersion?: number;\n\tsourceUpdatedAt?: string | null;\n\tsourceVersion?: number | null;\n\tsourceFingerprint?: string | null;\n\tsourceCompleteness?: MediaUsageSourceCompleteness;\n\tlastAttemptedAt?: string | null;\n\tlastErrorCode?: string | null;\n}\n\nexport interface MediaUsageOccurrenceInput {\n\tfieldSlug: string;\n\tfieldPath: string;\n\toccurrenceIndex?: number;\n\treferenceType: MediaUsageReferenceType;\n\tmediaId: string | null;\n\tprovider: string;\n\tproviderAssetId: string;\n\tmediaKind?: MediaKind | null;\n\tmimeType?: string | null;\n}\n\nexport interface MediaUsageSource {\n\tsourceKey: string;\n\tsourceType: string;\n\tcollectionSlug: string | null;\n\tcontentId: string | null;\n\tsourceVariant: string;\n\tlocale: string | null;\n\ttranslationGroup: string | null;\n\tcontentSlug: string | null;\n\tcontentTitle: string | null;\n\tcontentStatus: string | null;\n\tcontentScheduledAt: string | null;\n\tcontentDeletedAt: string | null;\n\trevisionId: string | null;\n\tcurrentGeneration: string;\n\tschemaVersion: number;\n\tsourceUpdatedAt: string | null;\n\tsourceVersion: number | null;\n\tsourceFingerprint: string | null;\n\tsourceCompleteness: string;\n\tlastAttemptedAt: string | null;\n\tlastErrorCode: string | null;\n\tindexedAt: string;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface MediaUsageGuardedReplaceResult {\n\treplaced: boolean;\n\t/** Populated only when a guarded replacement did not win the current source row. */\n\tsource: MediaUsageSource | null;\n}\n\nexport interface MediaUsageGuardedDeleteResult {\n\tdeleted: boolean;\n\tsource: MediaUsageSource | null;\n}\n\nexport interface MediaUsageGuardedAbsentDeleteResult extends MediaUsageGuardedDeleteResult {\n\tcontentPresent: boolean;\n}\n\nexport interface MediaUsageGuardedAttemptResult {\n\tattempted: boolean;\n\t/** Populated only when a guarded attempted mark did not win the current source row. */\n\tsource: MediaUsageSource | null;\n}\n\nexport interface MediaUsageIndexStatusRepairInput extends MediaUsageIndexStatusIdentity {\n\trunToken: string;\n\tschemaVersion?: number;\n\tstartedAt: string;\n\tupdatedAt?: string;\n}\n\nexport interface MediaUsageIndexStatusFinalizeInput extends MediaUsageIndexStatusIdentity {\n\trunToken: string;\n\tstatus: Exclude<MediaUsageIndexStatusValue, \"never\" | \"running\" | \"stale\">;\n\tschemaVersion?: number;\n\tcompletedAt: string;\n\tindexedSourceCount?: number;\n\tfailedSourceCount?: number;\n\tlastErrorCode?: string | null;\n\tupdatedAt?: string;\n}\n\nexport interface MediaUsageGuardedIndexStatusResult {\n\tfinalized: boolean;\n\tstatus: MediaUsageIndexStatus | null;\n}\n\nexport type MediaUsageSourceCompleteness =\n\t| \"unknown\"\n\t| \"complete\"\n\t| \"partial\"\n\t| \"failed\"\n\t| \"unsupported\";\n\nexport type MediaUsageIndexStatusValue =\n\t| \"never\"\n\t| \"running\"\n\t| \"complete\"\n\t| \"partial\"\n\t| \"failed\"\n\t| \"stale\";\n\nexport interface MediaUsageIndexStatusIdentity {\n\tadapterId: string;\n\tscopeType: string;\n\tscopeKey: string;\n}\n\nexport interface MediaUsageIndexStatusInput extends MediaUsageIndexStatusIdentity {\n\tstatus: MediaUsageIndexStatusValue;\n\tschemaVersion?: number;\n\tstartedAt?: string | null;\n\tcompletedAt?: string | null;\n\tcursor?: string | null;\n\tindexedSourceCount?: number;\n\tfailedSourceCount?: number;\n\tlastErrorCode?: string | null;\n\tupdatedAt?: string;\n}\n\nexport interface MediaUsageIndexStatus extends MediaUsageIndexStatusIdentity {\n\tstatus: string;\n\tschemaVersion: number;\n\tstartedAt: string | null;\n\tcompletedAt: string | null;\n\tcursor: string | null;\n\tindexedSourceCount: number;\n\tfailedSourceCount: number;\n\tlastErrorCode: string | null;\n\tupdatedAt: string;\n}\n\nexport interface FindMediaUsageOptions {\n\tlimit?: number;\n\tcursor?: string;\n}\n\ninterface MediaUsageSourceRow {\n\tsource_key: string;\n\tsource_type: string;\n\tcollection_slug: string | null;\n\tcontent_id: string | null;\n\tsource_variant: string;\n\tlocale: string | null;\n\ttranslation_group: string | null;\n\tcontent_slug: string | null;\n\tcontent_title: string | null;\n\tcontent_status: string | null;\n\tcontent_scheduled_at: string | null;\n\tcontent_deleted_at: string | null;\n\trevision_id: string | null;\n\tcurrent_generation: string;\n\tschema_version: number;\n\tsource_updated_at: string | null;\n\tsource_version: number | null;\n\tsource_fingerprint: string | null;\n\tsource_completeness: string;\n\tlast_attempted_at: string | null;\n\tlast_error_code: string | null;\n\tindexed_at: string;\n\tcreated_at: string;\n\tupdated_at: string;\n}\n\nexport interface MediaUsageOccurrence {\n\tid: string;\n\tsourceKey: string;\n\tgeneration: string;\n\tfieldSlug: string;\n\tfieldPath: string;\n\toccurrenceIndex: number;\n\treferenceType: string;\n\tmediaId: string | null;\n\tprovider: string;\n\tproviderAssetId: string;\n\tmediaKind: string | null;\n\tmimeType: string | null;\n\tcreatedAt: string;\n}\n\nexport interface MediaUsageRecord {\n\tsource: MediaUsageSource;\n\toccurrence: MediaUsageOccurrence;\n}\n\ninterface JoinedUsageRow {\n\tsource_key: string;\n\tsource_type: string;\n\tcollection_slug: string | null;\n\tcontent_id: string | null;\n\tsource_variant: string;\n\tlocale: string | null;\n\ttranslation_group: string | null;\n\tcontent_slug: string | null;\n\tcontent_title: string | null;\n\tcontent_status: string | null;\n\tcontent_scheduled_at: string | null;\n\tcontent_deleted_at: string | null;\n\trevision_id: string | null;\n\tcurrent_generation: string;\n\tschema_version: number;\n\tsource_updated_at: string | null;\n\tsource_version: number | null;\n\tsource_fingerprint: string | null;\n\tsource_completeness: string;\n\tlast_attempted_at: string | null;\n\tlast_error_code: string | null;\n\tindexed_at: string;\n\tsource_created_at: string;\n\tsource_row_updated_at: string;\n\toccurrence_id: string;\n\tgeneration: string;\n\tfield_slug: string;\n\tfield_path: string;\n\toccurrence_index: number;\n\treference_type: string;\n\tmedia_id: string | null;\n\tprovider: string;\n\tprovider_asset_id: string;\n\tmedia_kind: string | null;\n\tmime_type: string | null;\n\toccurrence_created_at: string;\n}\n\n/** Persistence-only repository for the internal media usage projection tables. */\nexport class MediaUsageRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\tasync replaceSource(\n\t\tsource: MediaUsageSourceInput,\n\t\toccurrences: readonly MediaUsageOccurrenceInput[],\n\t): Promise<MediaUsageSource> {\n\t\tconst generation = ulid();\n\t\tconst now = new Date().toISOString();\n\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tawait this.insertOccurrences(trx, source.sourceKey, generation, occurrences, now);\n\t\t\tawait this.upsertSource(trx, source, generation, now);\n\t\t});\n\n\t\tconst replaced = await this.findSource(source.sourceKey);\n\t\tif (!replaced) {\n\t\t\tthrow new Error(`Media usage source ${source.sourceKey} was not persisted`);\n\t\t}\n\t\treturn replaced;\n\t}\n\tasync replaceSourceIfCurrent(\n\t\tsource: MediaUsageSourceInput,\n\t\toccurrences: readonly MediaUsageOccurrenceInput[],\n\t\texpectedCurrentGeneration: string | null,\n\t): Promise<MediaUsageGuardedReplaceResult> {\n\t\tconst generation = ulid();\n\t\tconst now = new Date().toISOString();\n\t\tconst row = this.buildSourceRow(source, generation, now);\n\t\tlet replaced = false;\n\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tawait this.insertOccurrences(trx, source.sourceKey, generation, occurrences, now);\n\t\t\tif (expectedCurrentGeneration === null) {\n\t\t\t\treplaced = await this.insertSourceIfAbsent(trx, row);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treplaced = await this.updateSourceIfGeneration(trx, row, expectedCurrentGeneration);\n\t\t});\n\n\t\treturn {\n\t\t\treplaced,\n\t\t\tsource: replaced ? null : await this.findSource(source.sourceKey),\n\t\t};\n\t}\n\n\tasync findSource(sourceKey: string): Promise<MediaUsageSource | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage_sources\")\n\t\t\t.selectAll()\n\t\t\t.where(\"source_key\", \"=\", sourceKey)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? rowToSource(row) : null;\n\t}\n\n\tasync findSources(sourceKeys: readonly string[]): Promise<Map<string, MediaUsageSource>> {\n\t\tconst uniqueSourceKeys = [...new Set(sourceKeys)];\n\t\tconst sources = new Map<string, MediaUsageSource>();\n\t\tif (uniqueSourceKeys.length === 0) return sources;\n\n\t\tfor (const sourceKeyBatch of chunks(uniqueSourceKeys, SQL_BATCH_SIZE)) {\n\t\t\tconst rows = await this.db\n\t\t\t\t.selectFrom(\"_emdash_media_usage_sources\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"source_key\", \"in\", sourceKeyBatch)\n\t\t\t\t.execute();\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst source = rowToSource(row);\n\t\t\t\tsources.set(source.sourceKey, source);\n\t\t\t}\n\t\t}\n\n\t\treturn sources;\n\t}\n\n\tasync replaceSourceIfMatching(\n\t\tsource: MediaUsageSourceInput,\n\t\toccurrences: readonly MediaUsageOccurrenceInput[],\n\t\texpectedSource: MediaUsageSource | null,\n\t): Promise<MediaUsageGuardedReplaceResult> {\n\t\tconst generation = ulid();\n\t\tconst now = new Date().toISOString();\n\t\tconst row = this.buildSourceRow(source, generation, now);\n\t\tlet replaced = false;\n\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tawait this.insertOccurrences(trx, source.sourceKey, generation, occurrences, now);\n\t\t\tif (expectedSource === null) {\n\t\t\t\treplaced = await this.insertSourceIfAbsent(trx, row);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treplaced = await this.updateSourceIfMatching(trx, row, expectedSource);\n\t\t});\n\n\t\treturn {\n\t\t\treplaced,\n\t\t\tsource: replaced ? null : await this.findSource(source.sourceKey),\n\t\t};\n\t}\n\n\tasync markSourceAttempted(source: MediaUsageSourceInput): Promise<MediaUsageSource> {\n\t\tconst now = new Date().toISOString();\n\t\tconst row = this.buildAttemptedSourceRow(source, now);\n\t\tconst updates = this.attemptedSourceUpdateSet(source, row);\n\n\t\tawait this.db\n\t\t\t.insertInto(\"_emdash_media_usage_sources\")\n\t\t\t.values(row)\n\t\t\t.onConflict((oc) => oc.column(\"source_key\").doUpdateSet(updates))\n\t\t\t.execute();\n\n\t\tconst attempted = await this.findSource(source.sourceKey);\n\t\tif (!attempted) {\n\t\t\tthrow new Error(`Media usage source ${source.sourceKey} was not persisted`);\n\t\t}\n\t\treturn attempted;\n\t}\n\n\tasync markSourceAttemptedIfMatching(\n\t\tsource: MediaUsageSourceInput,\n\t\texpectedSource: MediaUsageSource | null,\n\t): Promise<MediaUsageGuardedAttemptResult> {\n\t\tconst now = new Date().toISOString();\n\t\tconst row = this.buildAttemptedSourceRow(source, now);\n\t\tlet attempted = false;\n\n\t\tif (expectedSource === null) {\n\t\t\tattempted = await this.insertSourceIfAbsent(this.db, row);\n\t\t} else {\n\t\t\tattempted = await this.updateAttemptedSourceIfMatching(this.db, source, row, expectedSource);\n\t\t}\n\n\t\treturn {\n\t\t\tattempted,\n\t\t\tsource: attempted ? null : await this.findSource(source.sourceKey),\n\t\t};\n\t}\n\n\tasync findCurrentUsageByMediaId(mediaId: string): Promise<MediaUsageRecord[]> {\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage_sources as s\")\n\t\t\t.innerJoin(\"_emdash_media_usage as u\", (join) =>\n\t\t\t\tjoin\n\t\t\t\t\t.onRef(\"u.source_key\", \"=\", \"s.source_key\")\n\t\t\t\t\t.onRef(\"u.generation\", \"=\", \"s.current_generation\"),\n\t\t\t)\n\t\t\t.select(currentUsageSelect)\n\t\t\t.where(\"u.media_id\", \"=\", mediaId)\n\t\t\t.orderBy(\"s.source_key\", \"asc\")\n\t\t\t.orderBy(\"u.field_path\", \"asc\")\n\t\t\t.orderBy(\"u.occurrence_index\", \"asc\")\n\t\t\t.execute();\n\n\t\treturn rows.map(rowToUsageRecord);\n\t}\n\n\tasync findCurrentUsageByProviderAsset(\n\t\tprovider: string,\n\t\tproviderAssetId: string,\n\t): Promise<MediaUsageRecord[]> {\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage_sources as s\")\n\t\t\t.innerJoin(\"_emdash_media_usage as u\", (join) =>\n\t\t\t\tjoin\n\t\t\t\t\t.onRef(\"u.source_key\", \"=\", \"s.source_key\")\n\t\t\t\t\t.onRef(\"u.generation\", \"=\", \"s.current_generation\"),\n\t\t\t)\n\t\t\t.select(currentUsageSelect)\n\t\t\t.where(\"u.provider\", \"=\", provider)\n\t\t\t.where(\"u.provider_asset_id\", \"=\", providerAssetId)\n\t\t\t.orderBy(\"s.source_key\", \"asc\")\n\t\t\t.orderBy(\"u.field_path\", \"asc\")\n\t\t\t.orderBy(\"u.occurrence_index\", \"asc\")\n\t\t\t.execute();\n\n\t\treturn rows.map(rowToUsageRecord);\n\t}\n\n\tasync findCurrentUsagePageByMediaId(\n\t\tmediaId: string,\n\t\toptions: FindMediaUsageOptions = {},\n\t): Promise<FindManyResult<MediaUsageRecord>> {\n\t\treturn this.findCurrentUsagePage((query) => query.where(\"u.media_id\", \"=\", mediaId), options);\n\t}\n\n\tasync findCurrentUsagePageByProviderAsset(\n\t\tprovider: string,\n\t\tproviderAssetId: string,\n\t\toptions: FindMediaUsageOptions = {},\n\t): Promise<FindManyResult<MediaUsageRecord>> {\n\t\treturn this.findCurrentUsagePage(\n\t\t\t(query) =>\n\t\t\t\tquery.where(\"u.provider\", \"=\", provider).where(\"u.provider_asset_id\", \"=\", providerAssetId),\n\t\t\toptions,\n\t\t);\n\t}\n\n\tasync deleteSource(sourceKey: string): Promise<number> {\n\t\treturn this.deleteSources([sourceKey]);\n\t}\n\n\tasync deleteSourceIfCurrent(\n\t\tsourceKey: string,\n\t\texpectedCurrentGeneration: string,\n\t): Promise<MediaUsageGuardedDeleteResult> {\n\t\tlet deleted = false;\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tconst result = await trx\n\t\t\t\t.deleteFrom(\"_emdash_media_usage_sources\")\n\t\t\t\t.where(\"source_key\", \"=\", sourceKey)\n\t\t\t\t.where(\"current_generation\", \"=\", expectedCurrentGeneration)\n\t\t\t\t.executeTakeFirst();\n\t\t\tdeleted = Number(result.numDeletedRows ?? 0) > 0;\n\t\t\tif (!deleted) return;\n\t\t\tawait this.deleteSourceGenerationOccurrences(trx, sourceKey, expectedCurrentGeneration);\n\t\t});\n\n\t\treturn {\n\t\t\tdeleted,\n\t\t\tsource: await this.findSource(sourceKey),\n\t\t};\n\t}\n\n\tasync deleteSourceIfMatching(\n\t\tsourceKey: string,\n\t\texpectedSource: MediaUsageSource,\n\t): Promise<MediaUsageGuardedDeleteResult> {\n\t\tlet deleted = false;\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tconst result = await trx\n\t\t\t\t.deleteFrom(\"_emdash_media_usage_sources\")\n\t\t\t\t.where(\"source_key\", \"=\", sourceKey)\n\t\t\t\t.where(this.sourceMatchExpression(expectedSource))\n\t\t\t\t.executeTakeFirst();\n\t\t\tdeleted = Number(result.numDeletedRows ?? 0) > 0;\n\t\t\tif (!deleted) return;\n\t\t\tawait this.deleteSourceGenerationOccurrences(\n\t\t\t\ttrx,\n\t\t\t\tsourceKey,\n\t\t\t\texpectedSource.currentGeneration,\n\t\t\t);\n\t\t});\n\n\t\treturn {\n\t\t\tdeleted,\n\t\t\tsource: await this.findSource(sourceKey),\n\t\t};\n\t}\n\n\tasync deleteSourceIfMatchingContentAbsent(\n\t\tsourceKey: string,\n\t\texpectedSource: MediaUsageSource,\n\t\tcollectionSlug: string,\n\t\tcontentId: string,\n\t): Promise<MediaUsageGuardedAbsentDeleteResult> {\n\t\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\t\tconst tableName = `ec_${collectionSlug}`;\n\t\tlet deleted = false;\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tconst result = await trx\n\t\t\t\t.deleteFrom(\"_emdash_media_usage_sources\")\n\t\t\t\t.where(\"source_key\", \"=\", sourceKey)\n\t\t\t\t.where(this.sourceMatchExpression(expectedSource))\n\t\t\t\t.where(\n\t\t\t\t\tsql<boolean>`NOT EXISTS (SELECT 1 FROM ${sql.ref(tableName)} WHERE id = ${contentId})`,\n\t\t\t\t)\n\t\t\t\t.executeTakeFirst();\n\t\t\tdeleted = Number(result.numDeletedRows ?? 0) > 0;\n\t\t\tif (!deleted) return;\n\t\t\tawait this.deleteSourceGenerationOccurrences(\n\t\t\t\ttrx,\n\t\t\t\tsourceKey,\n\t\t\t\texpectedSource.currentGeneration,\n\t\t\t);\n\t\t});\n\t\tconst contentPresent = deleted ? false : await this.contentRowExists(tableName, contentId);\n\n\t\treturn {\n\t\t\tdeleted,\n\t\t\tcontentPresent,\n\t\t\tsource: deleted || contentPresent ? null : await this.findSource(sourceKey),\n\t\t};\n\t}\n\n\tasync deleteSources(sourceKeys: readonly string[]): Promise<number> {\n\t\treturn this.deleteSourceKeys(sourceKeys);\n\t}\n\n\tasync deleteContentSources(collectionSlug: string, contentId: string): Promise<number> {\n\t\tconst sourceRows = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage_sources\")\n\t\t\t.select(\"source_key\")\n\t\t\t.where(\"source_type\", \"=\", \"content\")\n\t\t\t.where(\"collection_slug\", \"=\", collectionSlug)\n\t\t\t.where(\"content_id\", \"=\", contentId)\n\t\t\t.execute();\n\t\tconst sourceKeys = sourceRows.map((row) => row.source_key);\n\t\treturn this.deleteSourceKeys(sourceKeys);\n\t}\n\n\tasync deleteCollectionSources(collectionSlug: string): Promise<number> {\n\t\tlet deleted = 0;\n\t\twhile (true) {\n\t\t\tconst sourceRows = await this.db\n\t\t\t\t.selectFrom(\"_emdash_media_usage_sources\")\n\t\t\t\t.select(\"source_key\")\n\t\t\t\t.where(\"source_type\", \"=\", \"content\")\n\t\t\t\t.where(\"collection_slug\", \"=\", collectionSlug)\n\t\t\t\t.orderBy(\"source_key\", \"asc\")\n\t\t\t\t.limit(SQL_BATCH_SIZE)\n\t\t\t\t.execute();\n\t\t\tif (sourceRows.length === 0) break;\n\n\t\t\tdeleted += await this.deleteSourceKeys(sourceRows.map((row) => row.source_key));\n\t\t}\n\t\treturn deleted;\n\t}\n\n\tasync findCollectionContentSources(collectionSlug: string): Promise<MediaUsageSource[]> {\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage_sources\")\n\t\t\t.selectAll()\n\t\t\t.where(\"source_type\", \"=\", \"content\")\n\t\t\t.where(\"collection_slug\", \"=\", collectionSlug)\n\t\t\t.orderBy(\"source_key\", \"asc\")\n\t\t\t.execute();\n\t\treturn rows.map((row) => rowToSource(row));\n\t}\n\n\tasync deleteOrphanOccurrencesOlderThan(cutoff: string, limit: number): Promise<number> {\n\t\tconst batchLimit = Math.floor(limit);\n\t\tif (batchLimit <= 0) return 0;\n\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage as u\")\n\t\t\t.leftJoin(\"_emdash_media_usage_sources as s\", (join) =>\n\t\t\t\tjoin.onRef(\"s.source_key\", \"=\", \"u.source_key\"),\n\t\t\t)\n\t\t\t.select(\"u.id\")\n\t\t\t.where(\"s.source_key\", \"is\", null)\n\t\t\t.where(\"u.created_at\", \"<\", cutoff)\n\t\t\t.orderBy(\"u.created_at\", \"asc\")\n\t\t\t.orderBy(\"u.id\", \"asc\")\n\t\t\t.limit(batchLimit)\n\t\t\t.execute();\n\n\t\tlet deleted = 0;\n\t\tfor (const idBatch of chunks(\n\t\t\trows.map((row) => row.id),\n\t\t\tSQL_BATCH_SIZE,\n\t\t)) {\n\t\t\tconst result = await this.db\n\t\t\t\t.deleteFrom(\"_emdash_media_usage\")\n\t\t\t\t.where(\"id\", \"in\", idBatch)\n\t\t\t\t.where(\"created_at\", \"<\", cutoff)\n\t\t\t\t.where(\n\t\t\t\t\tsql<boolean>`NOT EXISTS (SELECT 1 FROM _emdash_media_usage_sources s WHERE s.source_key = _emdash_media_usage.source_key)`,\n\t\t\t\t)\n\t\t\t\t.executeTakeFirst();\n\t\t\tdeleted += Number(result.numDeletedRows ?? 0);\n\t\t}\n\t\treturn deleted;\n\t}\n\n\tasync deleteStaleGenerationsOlderThan(cutoff: string, limit: number): Promise<number> {\n\t\tconst batchLimit = Math.floor(limit);\n\t\tif (batchLimit <= 0) return 0;\n\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage as u\")\n\t\t\t.innerJoin(\"_emdash_media_usage_sources as s\", (join) =>\n\t\t\t\tjoin.onRef(\"s.source_key\", \"=\", \"u.source_key\"),\n\t\t\t)\n\t\t\t.select(\"u.id\")\n\t\t\t.where(\"u.created_at\", \"<\", cutoff)\n\t\t\t.whereRef(\"u.generation\", \"!=\", \"s.current_generation\")\n\t\t\t.whereRef(\"u.created_at\", \"<\", \"s.indexed_at\")\n\t\t\t.orderBy(\"u.created_at\", \"asc\")\n\t\t\t.orderBy(\"u.id\", \"asc\")\n\t\t\t.limit(batchLimit)\n\t\t\t.execute();\n\n\t\tconst ids = rows.map((row) => row.id);\n\t\tif (ids.length === 0) return 0;\n\n\t\tlet deleted = 0;\n\t\tfor (const idBatch of chunks(ids, SQL_BATCH_SIZE)) {\n\t\t\tconst result = await this.db\n\t\t\t\t.deleteFrom(\"_emdash_media_usage\")\n\t\t\t\t.where(\"id\", \"in\", idBatch)\n\t\t\t\t.where(\"created_at\", \"<\", cutoff)\n\t\t\t\t.where((eb) =>\n\t\t\t\t\teb.exists(\n\t\t\t\t\t\teb\n\t\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_sources as s\")\n\t\t\t\t\t\t\t.select(\"s.source_key\")\n\t\t\t\t\t\t\t.whereRef(\"s.source_key\", \"=\", \"_emdash_media_usage.source_key\")\n\t\t\t\t\t\t\t.whereRef(\"s.current_generation\", \"!=\", \"_emdash_media_usage.generation\")\n\t\t\t\t\t\t\t.whereRef(\"_emdash_media_usage.created_at\", \"<\", \"s.indexed_at\"),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\t.executeTakeFirst();\n\t\t\tdeleted += Number(result.numDeletedRows ?? 0);\n\t\t}\n\t\treturn deleted;\n\t}\n\n\tasync deleteAbandonedGenerationsOlderThan(cutoff: string, limit: number): Promise<number> {\n\t\tconst batchLimit = Math.floor(limit);\n\t\tif (batchLimit <= 0) return 0;\n\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage as u\")\n\t\t\t.innerJoin(\"_emdash_media_usage_sources as s\", (join) =>\n\t\t\t\tjoin.onRef(\"s.source_key\", \"=\", \"u.source_key\"),\n\t\t\t)\n\t\t\t.select(\"u.id\")\n\t\t\t.where(\"u.created_at\", \"<\", cutoff)\n\t\t\t.whereRef(\"u.generation\", \"!=\", \"s.current_generation\")\n\t\t\t.whereRef(\"u.created_at\", \">=\", \"s.indexed_at\")\n\t\t\t.orderBy(\"u.created_at\", \"asc\")\n\t\t\t.orderBy(\"u.id\", \"asc\")\n\t\t\t.limit(batchLimit)\n\t\t\t.execute();\n\n\t\tlet deleted = 0;\n\t\tfor (const idBatch of chunks(\n\t\t\trows.map((row) => row.id),\n\t\t\tSQL_BATCH_SIZE,\n\t\t)) {\n\t\t\tconst result = await this.db\n\t\t\t\t.deleteFrom(\"_emdash_media_usage\")\n\t\t\t\t.where(\"id\", \"in\", idBatch)\n\t\t\t\t.where(\"created_at\", \"<\", cutoff)\n\t\t\t\t.where((eb) =>\n\t\t\t\t\teb.exists(\n\t\t\t\t\t\teb\n\t\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_sources as s\")\n\t\t\t\t\t\t\t.select(\"s.source_key\")\n\t\t\t\t\t\t\t.whereRef(\"s.source_key\", \"=\", \"_emdash_media_usage.source_key\")\n\t\t\t\t\t\t\t.whereRef(\"s.current_generation\", \"!=\", \"_emdash_media_usage.generation\")\n\t\t\t\t\t\t\t.whereRef(\"_emdash_media_usage.created_at\", \">=\", \"s.indexed_at\"),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\t.executeTakeFirst();\n\t\t\tdeleted += Number(result.numDeletedRows ?? 0);\n\t\t}\n\t\treturn deleted;\n\t}\n\n\tasync upsertIndexStatus(input: MediaUsageIndexStatusInput): Promise<MediaUsageIndexStatus> {\n\t\tconst now = input.updatedAt ?? new Date().toISOString();\n\t\tconst row = {\n\t\t\tadapter_id: input.adapterId,\n\t\t\tscope_type: input.scopeType,\n\t\t\tscope_key: input.scopeKey,\n\t\t\tstatus: input.status,\n\t\t\tschema_version: input.schemaVersion ?? 1,\n\t\t\tstarted_at: input.startedAt ?? null,\n\t\t\tcompleted_at: input.completedAt ?? null,\n\t\t\tcursor: input.cursor ?? null,\n\t\t\tindexed_source_count: input.indexedSourceCount ?? 0,\n\t\t\tfailed_source_count: input.failedSourceCount ?? 0,\n\t\t\tlast_error_code: input.lastErrorCode ?? null,\n\t\t\tupdated_at: now,\n\t\t};\n\n\t\tawait this.db\n\t\t\t.insertInto(\"_emdash_media_usage_index_status\")\n\t\t\t.values(row)\n\t\t\t.onConflict((oc) =>\n\t\t\t\toc.columns([\"adapter_id\", \"scope_type\", \"scope_key\"]).doUpdateSet({\n\t\t\t\t\tstatus: row.status,\n\t\t\t\t\tschema_version: row.schema_version,\n\t\t\t\t\tstarted_at: row.started_at,\n\t\t\t\t\tcompleted_at: row.completed_at,\n\t\t\t\t\tcursor: row.cursor,\n\t\t\t\t\tindexed_source_count: row.indexed_source_count,\n\t\t\t\t\tfailed_source_count: row.failed_source_count,\n\t\t\t\t\tlast_error_code: row.last_error_code,\n\t\t\t\t\tupdated_at: row.updated_at,\n\t\t\t\t}),\n\t\t\t)\n\t\t\t.execute();\n\n\t\tconst status = await this.findIndexStatus(input);\n\t\tif (!status) {\n\t\t\tthrow new Error(\n\t\t\t\t`Media usage index status ${input.adapterId}:${input.scopeType}:${input.scopeKey} was not persisted`,\n\t\t\t);\n\t\t}\n\t\treturn status;\n\t}\n\n\tasync beginIndexStatusRepair(\n\t\tinput: MediaUsageIndexStatusRepairInput,\n\t): Promise<MediaUsageIndexStatus> {\n\t\treturn this.upsertIndexStatus({\n\t\t\tadapterId: input.adapterId,\n\t\t\tscopeType: input.scopeType,\n\t\t\tscopeKey: input.scopeKey,\n\t\t\tstatus: \"running\",\n\t\t\tschemaVersion: input.schemaVersion,\n\t\t\tstartedAt: input.startedAt,\n\t\t\tcompletedAt: null,\n\t\t\tcursor: input.runToken,\n\t\t\tindexedSourceCount: 0,\n\t\t\tfailedSourceCount: 0,\n\t\t\tlastErrorCode: null,\n\t\t\tupdatedAt: input.updatedAt,\n\t\t});\n\t}\n\n\tasync finalizeIndexStatusRepairIfRunning(\n\t\tinput: MediaUsageIndexStatusFinalizeInput,\n\t): Promise<MediaUsageGuardedIndexStatusResult> {\n\t\tconst updates: Updateable<MediaUsageIndexStatusTable> = {\n\t\t\tstatus: input.status,\n\t\t\tcompleted_at: input.completedAt,\n\t\t\tcursor: null,\n\t\t\tindexed_source_count: input.indexedSourceCount ?? 0,\n\t\t\tfailed_source_count: input.failedSourceCount ?? 0,\n\t\t\tlast_error_code: input.lastErrorCode ?? null,\n\t\t\tupdated_at: input.updatedAt ?? new Date().toISOString(),\n\t\t};\n\t\tif (input.schemaVersion !== undefined) updates.schema_version = input.schemaVersion;\n\n\t\tconst result = await this.db\n\t\t\t.updateTable(\"_emdash_media_usage_index_status\")\n\t\t\t.set(updates)\n\t\t\t.where(\"adapter_id\", \"=\", input.adapterId)\n\t\t\t.where(\"scope_type\", \"=\", input.scopeType)\n\t\t\t.where(\"scope_key\", \"=\", input.scopeKey)\n\t\t\t.where(\"status\", \"=\", \"running\")\n\t\t\t.where(\"cursor\", \"=\", input.runToken)\n\t\t\t.executeTakeFirst();\n\t\tconst finalized = Number(result.numUpdatedRows ?? 0) > 0;\n\n\t\treturn {\n\t\t\tfinalized,\n\t\t\tstatus: await this.findIndexStatus(input),\n\t\t};\n\t}\n\n\tasync findIndexStatus(\n\t\tidentity: MediaUsageIndexStatusIdentity,\n\t): Promise<MediaUsageIndexStatus | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage_index_status\")\n\t\t\t.selectAll()\n\t\t\t.where(\"adapter_id\", \"=\", identity.adapterId)\n\t\t\t.where(\"scope_type\", \"=\", identity.scopeType)\n\t\t\t.where(\"scope_key\", \"=\", identity.scopeKey)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? rowToIndexStatus(row) : null;\n\t}\n\n\tasync deleteIndexStatus(identity: MediaUsageIndexStatusIdentity): Promise<number> {\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_emdash_media_usage_index_status\")\n\t\t\t.where(\"adapter_id\", \"=\", identity.adapterId)\n\t\t\t.where(\"scope_type\", \"=\", identity.scopeType)\n\t\t\t.where(\"scope_key\", \"=\", identity.scopeKey)\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numDeletedRows ?? 0);\n\t}\n\n\tprivate async findCurrentUsagePage(\n\t\tapplyFilter: (\n\t\t\tquery: ReturnType<MediaUsageRepository[\"currentUsageBaseQuery\"]>,\n\t\t) => ReturnType<MediaUsageRepository[\"currentUsageBaseQuery\"]>,\n\t\toptions: FindMediaUsageOptions,\n\t): Promise<FindManyResult<MediaUsageRecord>> {\n\t\tconst limit = Math.min(Math.max(1, options.limit ?? 50), 100);\n\t\tlet query = applyFilter(this.currentUsageBaseQuery())\n\t\t\t.orderBy(\"u.id\", \"asc\")\n\t\t\t.limit(limit + 1);\n\n\t\tif (options.cursor) {\n\t\t\tconst { id } = decodeCursor(options.cursor);\n\t\t\tquery = query.where(\"u.id\", \">\", id);\n\t\t}\n\n\t\tconst rows = await query.execute();\n\t\tconst items = rows.slice(0, limit).map(rowToUsageRecord);\n\t\tconst result: FindManyResult<MediaUsageRecord> = { items };\n\n\t\tif (rows.length > limit && items.length > 0) {\n\t\t\tconst last = items.at(-1)!;\n\t\t\tresult.nextCursor = encodeCursor(last.occurrence.id, last.occurrence.id);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tprivate currentUsageBaseQuery() {\n\t\treturn this.db\n\t\t\t.selectFrom(\"_emdash_media_usage_sources as s\")\n\t\t\t.innerJoin(\"_emdash_media_usage as u\", (join) =>\n\t\t\t\tjoin\n\t\t\t\t\t.onRef(\"u.source_key\", \"=\", \"s.source_key\")\n\t\t\t\t\t.onRef(\"u.generation\", \"=\", \"s.current_generation\"),\n\t\t\t)\n\t\t\t.select(currentUsageSelect);\n\t}\n\n\tprivate async deleteSourceKeys(sourceKeys: readonly string[]): Promise<number> {\n\t\tconst uniqueSourceKeys = [...new Set(sourceKeys)];\n\t\tif (uniqueSourceKeys.length === 0) return 0;\n\n\t\treturn withTransaction(this.db, async (trx) => {\n\t\t\tlet deleted = 0;\n\t\t\tfor (const sourceKeyBatch of chunks(uniqueSourceKeys, SQL_BATCH_SIZE)) {\n\t\t\t\tconst result = await trx\n\t\t\t\t\t.deleteFrom(\"_emdash_media_usage_sources\")\n\t\t\t\t\t.where(\"source_key\", \"in\", sourceKeyBatch)\n\t\t\t\t\t.executeTakeFirst();\n\t\t\t\tdeleted += Number(result.numDeletedRows ?? 0);\n\n\t\t\t\tawait trx\n\t\t\t\t\t.deleteFrom(\"_emdash_media_usage\")\n\t\t\t\t\t.where(\"source_key\", \"in\", sourceKeyBatch)\n\t\t\t\t\t.execute();\n\t\t\t}\n\t\t\treturn deleted;\n\t\t});\n\t}\n\n\tprivate async deleteSourceGenerationOccurrences(\n\t\tdb: DatabaseExecutor,\n\t\tsourceKey: string,\n\t\tgeneration: string,\n\t): Promise<void> {\n\t\t// Guarded source deletes remove only the generation that won the source CAS;\n\t\t// unmatched generations become invisible orphans reclaimed by age-gated cleanup.\n\t\tawait db\n\t\t\t.deleteFrom(\"_emdash_media_usage\")\n\t\t\t.where(\"source_key\", \"=\", sourceKey)\n\t\t\t.where(\"generation\", \"=\", generation)\n\t\t\t.execute();\n\t}\n\n\tprivate async insertOccurrences(\n\t\tdb: DatabaseExecutor,\n\t\tsourceKey: string,\n\t\tgeneration: string,\n\t\toccurrences: readonly MediaUsageOccurrenceInput[],\n\t\tnow: string,\n\t): Promise<void> {\n\t\tif (occurrences.length === 0) return;\n\n\t\tconst rows = occurrences.map((occurrence) => ({\n\t\t\tid: ulid(),\n\t\t\tsource_key: sourceKey,\n\t\t\tgeneration,\n\t\t\tfield_slug: occurrence.fieldSlug,\n\t\t\tfield_path: occurrence.fieldPath,\n\t\t\toccurrence_index: occurrence.occurrenceIndex ?? 0,\n\t\t\treference_type: occurrence.referenceType,\n\t\t\tmedia_id: occurrence.mediaId,\n\t\t\tprovider: occurrence.provider,\n\t\t\tprovider_asset_id: occurrence.providerAssetId,\n\t\t\tmedia_kind: occurrence.mediaKind ?? null,\n\t\t\tmime_type: occurrence.mimeType ?? null,\n\t\t\tcreated_at: now,\n\t\t}));\n\n\t\tfor (const rowBatch of chunks(rows, OCCURRENCE_INSERT_BATCH_SIZE)) {\n\t\t\tawait db.insertInto(\"_emdash_media_usage\").values(rowBatch).execute();\n\t\t}\n\t}\n\n\tprivate async upsertSource(\n\t\tdb: DatabaseExecutor,\n\t\tsource: MediaUsageSourceInput,\n\t\tgeneration: string,\n\t\tnow: string,\n\t): Promise<void> {\n\t\tconst row = this.buildSourceRow(source, generation, now);\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_media_usage_sources\")\n\t\t\t.values(row)\n\t\t\t.onConflict((oc) => oc.column(\"source_key\").doUpdateSet(this.sourceUpdateSet(row)))\n\t\t\t.execute();\n\t}\n\n\tprivate async insertSourceIfAbsent(\n\t\tdb: DatabaseExecutor,\n\t\trow: Insertable<MediaUsageSourceTable>,\n\t): Promise<boolean> {\n\t\tconst result = await db\n\t\t\t.insertInto(\"_emdash_media_usage_sources\")\n\t\t\t.values(row)\n\t\t\t.onConflict((oc) => oc.column(\"source_key\").doNothing())\n\t\t\t.executeTakeFirst();\n\t\treturn (result.numInsertedOrUpdatedRows ?? 0n) > 0n;\n\t}\n\n\tprivate async updateSourceIfGeneration(\n\t\tdb: DatabaseExecutor,\n\t\trow: ReturnType<MediaUsageRepository[\"buildSourceRow\"]>,\n\t\texpectedCurrentGeneration: string,\n\t): Promise<boolean> {\n\t\tconst result = await db\n\t\t\t.updateTable(\"_emdash_media_usage_sources\")\n\t\t\t.set(this.sourceUpdateSet(row))\n\t\t\t.where(\"source_key\", \"=\", row.source_key)\n\t\t\t.where(\"current_generation\", \"=\", expectedCurrentGeneration)\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) > 0;\n\t}\n\n\tprivate async updateSourceIfMatching(\n\t\tdb: DatabaseExecutor,\n\t\trow: ReturnType<MediaUsageRepository[\"buildSourceRow\"]>,\n\t\texpectedSource: MediaUsageSource,\n\t): Promise<boolean> {\n\t\tconst result = await db\n\t\t\t.updateTable(\"_emdash_media_usage_sources\")\n\t\t\t.set(this.sourceUpdateSet(row))\n\t\t\t.where(\"source_key\", \"=\", row.source_key)\n\t\t\t.where(this.sourceMatchExpression(expectedSource))\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) > 0;\n\t}\n\n\tprivate async updateAttemptedSourceIfMatching(\n\t\tdb: DatabaseExecutor,\n\t\tsource: MediaUsageSourceInput,\n\t\trow: ReturnType<MediaUsageRepository[\"buildAttemptedSourceRow\"]>,\n\t\texpectedSource: MediaUsageSource,\n\t): Promise<boolean> {\n\t\tconst result = await db\n\t\t\t.updateTable(\"_emdash_media_usage_sources\")\n\t\t\t.set(this.attemptedSourceUpdateSet(source, row))\n\t\t\t.where(\"source_key\", \"=\", row.source_key)\n\t\t\t.where(this.sourceMatchExpression(expectedSource))\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) > 0;\n\t}\n\n\tprivate sourceMatchExpression(expectedSource: MediaUsageSource) {\n\t\treturn (eb: ExpressionBuilder<Database, \"_emdash_media_usage_sources\">) =>\n\t\t\teb.and([\n\t\t\t\teb(\"current_generation\", \"=\", expectedSource.currentGeneration),\n\t\t\t\teb(\"source_completeness\", \"=\", expectedSource.sourceCompleteness),\n\t\t\t\tthis.nullableStringExpression(eb, \"updated_at\", expectedSource.updatedAt),\n\t\t\t\tthis.nullableStringExpression(eb, \"source_fingerprint\", expectedSource.sourceFingerprint),\n\t\t\t\tthis.nullableStringExpression(eb, \"source_updated_at\", expectedSource.sourceUpdatedAt),\n\t\t\t\tthis.nullableNumberExpression(eb, \"source_version\", expectedSource.sourceVersion),\n\t\t\t\tthis.nullableStringExpression(eb, \"revision_id\", expectedSource.revisionId),\n\t\t\t\tthis.nullableStringExpression(eb, \"last_attempted_at\", expectedSource.lastAttemptedAt),\n\t\t\t\tthis.nullableStringExpression(eb, \"last_error_code\", expectedSource.lastErrorCode),\n\t\t\t]);\n\t}\n\n\tprivate nullableStringExpression(\n\t\teb: ExpressionBuilder<Database, \"_emdash_media_usage_sources\">,\n\t\tcolumn: MediaUsageSourceNullableStringColumn,\n\t\tvalue: string | null,\n\t) {\n\t\treturn value === null ? eb(column, \"is\", null) : eb(column, \"=\", value);\n\t}\n\n\tprivate nullableNumberExpression(\n\t\teb: ExpressionBuilder<Database, \"_emdash_media_usage_sources\">,\n\t\tcolumn: \"source_version\",\n\t\tvalue: number | null,\n\t) {\n\t\treturn value === null ? eb(column, \"is\", null) : eb(column, \"=\", value);\n\t}\n\n\tprivate async contentRowExists(tableName: string, contentId: string): Promise<boolean> {\n\t\tconst result = await sql<{ id: string }>`\n\t\t\tSELECT id\n\t\t\tFROM ${sql.ref(tableName)}\n\t\t\tWHERE id = ${contentId}\n\t\t\tLIMIT 1\n\t\t`.execute(this.db);\n\t\treturn result.rows.length > 0;\n\t}\n\n\tprivate buildSourceRow(source: MediaUsageSourceInput, generation: string, now: string) {\n\t\treturn {\n\t\t\tsource_key: source.sourceKey,\n\t\t\tsource_type: source.sourceType,\n\t\t\tcollection_slug: source.collectionSlug ?? null,\n\t\t\tcontent_id: source.contentId ?? null,\n\t\t\tsource_variant: source.sourceVariant,\n\t\t\tlocale: source.locale ?? null,\n\t\t\ttranslation_group: source.translationGroup ?? null,\n\t\t\tcontent_slug: source.contentSlug ?? null,\n\t\t\tcontent_title: source.contentTitle ?? null,\n\t\t\tcontent_status: source.contentStatus ?? null,\n\t\t\tcontent_scheduled_at: source.contentScheduledAt ?? null,\n\t\t\tcontent_deleted_at: source.contentDeletedAt ?? null,\n\t\t\trevision_id: source.revisionId ?? null,\n\t\t\tcurrent_generation: generation,\n\t\t\tschema_version: source.schemaVersion ?? 1,\n\t\t\tsource_updated_at: source.sourceUpdatedAt ?? null,\n\t\t\tsource_version: source.sourceVersion ?? null,\n\t\t\tsource_fingerprint: source.sourceFingerprint ?? null,\n\t\t\t// Complete means this source was fully refreshed for the extractor's current\n\t\t\t// schema/version coverage, not that every possible reference shape is known.\n\t\t\tsource_completeness: source.sourceCompleteness ?? \"complete\",\n\t\t\tlast_attempted_at: source.lastAttemptedAt ?? now,\n\t\t\tlast_error_code: null,\n\t\t\tindexed_at: now,\n\t\t\tupdated_at: now,\n\t\t};\n\t}\n\n\tprivate buildAttemptedSourceRow(source: MediaUsageSourceInput, now: string) {\n\t\treturn {\n\t\t\tsource_key: source.sourceKey,\n\t\t\tsource_type: source.sourceType,\n\t\t\tcollection_slug: source.collectionSlug ?? null,\n\t\t\tcontent_id: source.contentId ?? null,\n\t\t\tsource_variant: source.sourceVariant,\n\t\t\tlocale: source.locale ?? null,\n\t\t\ttranslation_group: source.translationGroup ?? null,\n\t\t\tcontent_slug: source.contentSlug ?? null,\n\t\t\tcontent_title: source.contentTitle ?? null,\n\t\t\tcontent_status: source.contentStatus ?? null,\n\t\t\tcontent_scheduled_at: source.contentScheduledAt ?? null,\n\t\t\tcontent_deleted_at: source.contentDeletedAt ?? null,\n\t\t\trevision_id: source.revisionId ?? null,\n\t\t\tcurrent_generation: ulid(),\n\t\t\tschema_version: source.schemaVersion ?? 1,\n\t\t\tsource_updated_at: source.sourceUpdatedAt ?? null,\n\t\t\tsource_version: source.sourceVersion ?? null,\n\t\t\tsource_fingerprint: source.sourceFingerprint ?? null,\n\t\t\tsource_completeness:\n\t\t\t\tsource.sourceCompleteness ?? (source.lastErrorCode ? \"failed\" : \"unknown\"),\n\t\t\tlast_attempted_at: source.lastAttemptedAt ?? now,\n\t\t\tlast_error_code: source.lastErrorCode ?? null,\n\t\t\tindexed_at: now,\n\t\t\tupdated_at: now,\n\t\t};\n\t}\n\n\tprivate attemptedSourceUpdateSet(\n\t\tsource: MediaUsageSourceInput,\n\t\trow: ReturnType<MediaUsageRepository[\"buildAttemptedSourceRow\"]>,\n\t): Updateable<MediaUsageSourceTable> {\n\t\tconst updates: Updateable<MediaUsageSourceTable> = {\n\t\t\tsource_type: row.source_type,\n\t\t\tsource_variant: row.source_variant,\n\t\t\tsource_completeness: row.source_completeness,\n\t\t\tlast_attempted_at: row.last_attempted_at,\n\t\t\tlast_error_code: row.last_error_code,\n\t\t\tupdated_at: row.updated_at,\n\t\t};\n\n\t\tif (source.collectionSlug !== undefined) updates.collection_slug = row.collection_slug;\n\t\tif (source.contentId !== undefined) updates.content_id = row.content_id;\n\t\tif (source.locale !== undefined) updates.locale = row.locale;\n\t\tif (source.translationGroup !== undefined) updates.translation_group = row.translation_group;\n\t\tif (source.contentSlug !== undefined) updates.content_slug = row.content_slug;\n\t\tif (source.contentTitle !== undefined) updates.content_title = row.content_title;\n\t\tif (source.contentStatus !== undefined) updates.content_status = row.content_status;\n\t\tif (source.contentScheduledAt !== undefined) {\n\t\t\tupdates.content_scheduled_at = row.content_scheduled_at;\n\t\t}\n\t\tif (source.contentDeletedAt !== undefined) updates.content_deleted_at = row.content_deleted_at;\n\t\tif (source.revisionId !== undefined) updates.revision_id = row.revision_id;\n\t\tif (source.schemaVersion !== undefined) updates.schema_version = row.schema_version;\n\t\tif (source.sourceUpdatedAt !== undefined) updates.source_updated_at = row.source_updated_at;\n\t\tif (source.sourceVersion !== undefined) updates.source_version = row.source_version;\n\t\tif (source.sourceFingerprint !== undefined) {\n\t\t\tupdates.source_fingerprint = row.source_fingerprint;\n\t\t}\n\n\t\treturn updates;\n\t}\n\n\tprivate sourceUpdateSet(\n\t\trow: ReturnType<MediaUsageRepository[\"buildSourceRow\"]>,\n\t): Updateable<MediaUsageSourceTable> {\n\t\treturn {\n\t\t\tsource_type: row.source_type,\n\t\t\tcollection_slug: row.collection_slug,\n\t\t\tcontent_id: row.content_id,\n\t\t\tsource_variant: row.source_variant,\n\t\t\tlocale: row.locale,\n\t\t\ttranslation_group: row.translation_group,\n\t\t\tcontent_slug: row.content_slug,\n\t\t\tcontent_title: row.content_title,\n\t\t\tcontent_status: row.content_status,\n\t\t\tcontent_scheduled_at: row.content_scheduled_at,\n\t\t\tcontent_deleted_at: row.content_deleted_at,\n\t\t\trevision_id: row.revision_id,\n\t\t\tcurrent_generation: row.current_generation,\n\t\t\tschema_version: row.schema_version,\n\t\t\tsource_updated_at: row.source_updated_at,\n\t\t\tsource_version: row.source_version,\n\t\t\tsource_fingerprint: row.source_fingerprint,\n\t\t\tsource_completeness: row.source_completeness,\n\t\t\tlast_attempted_at: row.last_attempted_at,\n\t\t\tlast_error_code: row.last_error_code,\n\t\t\tindexed_at: row.indexed_at,\n\t\t\tupdated_at: row.updated_at,\n\t\t};\n\t}\n}\n\nconst currentUsageSelect = [\n\t\"s.source_key as source_key\",\n\t\"s.source_type as source_type\",\n\t\"s.collection_slug as collection_slug\",\n\t\"s.content_id as content_id\",\n\t\"s.source_variant as source_variant\",\n\t\"s.locale as locale\",\n\t\"s.translation_group as translation_group\",\n\t\"s.content_slug as content_slug\",\n\t\"s.content_title as content_title\",\n\t\"s.content_status as content_status\",\n\t\"s.content_scheduled_at as content_scheduled_at\",\n\t\"s.content_deleted_at as content_deleted_at\",\n\t\"s.revision_id as revision_id\",\n\t\"s.current_generation as current_generation\",\n\t\"s.schema_version as schema_version\",\n\t\"s.source_updated_at as source_updated_at\",\n\t\"s.source_version as source_version\",\n\t\"s.source_fingerprint as source_fingerprint\",\n\t\"s.source_completeness as source_completeness\",\n\t\"s.last_attempted_at as last_attempted_at\",\n\t\"s.last_error_code as last_error_code\",\n\t\"s.indexed_at as indexed_at\",\n\t\"s.created_at as source_created_at\",\n\t\"s.updated_at as source_row_updated_at\",\n\t\"u.id as occurrence_id\",\n\t\"u.generation as generation\",\n\t\"u.field_slug as field_slug\",\n\t\"u.field_path as field_path\",\n\t\"u.occurrence_index as occurrence_index\",\n\t\"u.reference_type as reference_type\",\n\t\"u.media_id as media_id\",\n\t\"u.provider as provider\",\n\t\"u.provider_asset_id as provider_asset_id\",\n\t\"u.media_kind as media_kind\",\n\t\"u.mime_type as mime_type\",\n\t\"u.created_at as occurrence_created_at\",\n] as const;\n\nfunction rowToSource(row: MediaUsageSourceRow): MediaUsageSource {\n\treturn {\n\t\tsourceKey: row.source_key,\n\t\tsourceType: row.source_type,\n\t\tcollectionSlug: row.collection_slug,\n\t\tcontentId: row.content_id,\n\t\tsourceVariant: row.source_variant,\n\t\tlocale: row.locale,\n\t\ttranslationGroup: row.translation_group,\n\t\tcontentSlug: row.content_slug,\n\t\tcontentTitle: row.content_title,\n\t\tcontentStatus: row.content_status,\n\t\tcontentScheduledAt: row.content_scheduled_at,\n\t\tcontentDeletedAt: row.content_deleted_at,\n\t\trevisionId: row.revision_id,\n\t\tcurrentGeneration: row.current_generation,\n\t\tschemaVersion: Number(row.schema_version),\n\t\tsourceUpdatedAt: row.source_updated_at,\n\t\tsourceVersion: row.source_version === null ? null : Number(row.source_version),\n\t\tsourceFingerprint: row.source_fingerprint,\n\t\tsourceCompleteness: row.source_completeness,\n\t\tlastAttemptedAt: row.last_attempted_at,\n\t\tlastErrorCode: row.last_error_code,\n\t\tindexedAt: row.indexed_at,\n\t\tcreatedAt: row.created_at,\n\t\tupdatedAt: row.updated_at,\n\t};\n}\n\nfunction rowToOccurrence(row: Selectable<MediaUsageTable>): MediaUsageOccurrence {\n\treturn {\n\t\tid: row.id,\n\t\tsourceKey: row.source_key,\n\t\tgeneration: row.generation,\n\t\tfieldSlug: row.field_slug,\n\t\tfieldPath: row.field_path,\n\t\toccurrenceIndex: Number(row.occurrence_index),\n\t\treferenceType: row.reference_type,\n\t\tmediaId: row.media_id,\n\t\tprovider: row.provider,\n\t\tproviderAssetId: row.provider_asset_id,\n\t\tmediaKind: row.media_kind,\n\t\tmimeType: row.mime_type,\n\t\tcreatedAt: row.created_at,\n\t};\n}\n\nfunction rowToUsageRecord(row: JoinedUsageRow): MediaUsageRecord {\n\treturn {\n\t\tsource: rowToSource({\n\t\t\tsource_key: row.source_key,\n\t\t\tsource_type: row.source_type,\n\t\t\tcollection_slug: row.collection_slug,\n\t\t\tcontent_id: row.content_id,\n\t\t\tsource_variant: row.source_variant,\n\t\t\tlocale: row.locale,\n\t\t\ttranslation_group: row.translation_group,\n\t\t\tcontent_slug: row.content_slug,\n\t\t\tcontent_title: row.content_title,\n\t\t\tcontent_status: row.content_status,\n\t\t\tcontent_scheduled_at: row.content_scheduled_at,\n\t\t\tcontent_deleted_at: row.content_deleted_at,\n\t\t\trevision_id: row.revision_id,\n\t\t\tcurrent_generation: row.current_generation,\n\t\t\tschema_version: row.schema_version,\n\t\t\tsource_updated_at: row.source_updated_at,\n\t\t\tsource_version: row.source_version,\n\t\t\tsource_fingerprint: row.source_fingerprint,\n\t\t\tsource_completeness: row.source_completeness,\n\t\t\tlast_attempted_at: row.last_attempted_at,\n\t\t\tlast_error_code: row.last_error_code,\n\t\t\tindexed_at: row.indexed_at,\n\t\t\tcreated_at: row.source_created_at,\n\t\t\tupdated_at: row.source_row_updated_at,\n\t\t}),\n\t\toccurrence: rowToOccurrence({\n\t\t\tid: row.occurrence_id,\n\t\t\tsource_key: row.source_key,\n\t\t\tgeneration: row.generation,\n\t\t\tfield_slug: row.field_slug,\n\t\t\tfield_path: row.field_path,\n\t\t\toccurrence_index: row.occurrence_index,\n\t\t\treference_type: row.reference_type,\n\t\t\tmedia_id: row.media_id,\n\t\t\tprovider: row.provider,\n\t\t\tprovider_asset_id: row.provider_asset_id,\n\t\t\tmedia_kind: row.media_kind,\n\t\t\tmime_type: row.mime_type,\n\t\t\tcreated_at: row.occurrence_created_at,\n\t\t}),\n\t};\n}\n\nfunction rowToIndexStatus(row: Selectable<MediaUsageIndexStatusTable>): MediaUsageIndexStatus {\n\treturn {\n\t\tadapterId: row.adapter_id,\n\t\tscopeType: row.scope_type,\n\t\tscopeKey: row.scope_key,\n\t\tstatus: row.status,\n\t\tschemaVersion: Number(row.schema_version),\n\t\tstartedAt: row.started_at,\n\t\tcompletedAt: row.completed_at,\n\t\tcursor: row.cursor,\n\t\tindexedSourceCount: Number(row.indexed_source_count),\n\t\tfailedSourceCount: Number(row.failed_source_count),\n\t\tlastErrorCode: row.last_error_code,\n\t\tupdatedAt: row.updated_at,\n\t};\n}\n","import type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { validateIdentifier } from \"../../database/validate.js\";\nimport type { MediaUsageExtractionField, MediaUsageExtractionSubField } from \"./types.js\";\n\nexport type ContentMediaUsageField = MediaUsageExtractionField;\n\nexport interface ContentMediaUsageFieldDiscovery {\n\textractionFields: ContentMediaUsageField[];\n\tdisplayFieldSlugs: string[];\n}\n\nexport class MediaUsageFieldDiscoveryError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic code: \"INVALID_REPEATER_VALIDATION\",\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MediaUsageFieldDiscoveryError\";\n\t}\n}\n\ninterface FieldDiscoveryRow {\n\tslug: string;\n\ttype: string;\n\tvalidation: string | null;\n}\n\nconst DISPLAY_FIELD_SLUGS = [\"title\", \"name\"] as const;\nconst SUPPORTED_TOP_LEVEL_TYPES = [\"file\", \"image\", \"portableText\"] as const;\n\ntype SupportedTopLevelType = (typeof SUPPORTED_TOP_LEVEL_TYPES)[number];\n\nexport async function loadContentMediaUsageFields(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n): Promise<ContentMediaUsageFieldDiscovery> {\n\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\n\tconst rows = await db\n\t\t.selectFrom(\"_emdash_fields\")\n\t\t.innerJoin(\"_emdash_collections\", \"_emdash_collections.id\", \"_emdash_fields.collection_id\")\n\t\t.select([\"_emdash_fields.slug\", \"_emdash_fields.type\", \"_emdash_fields.validation\"])\n\t\t.where(\"_emdash_collections.slug\", \"=\", collectionSlug)\n\t\t.execute();\n\n\tconst extractionFields: ContentMediaUsageField[] = [];\n\tconst rowBySlug = new Map<string, FieldDiscoveryRow>();\n\n\tfor (const row of rows) {\n\t\trowBySlug.set(row.slug, row);\n\t\tif (isSupportedTopLevelType(row.type)) {\n\t\t\tvalidateIdentifier(row.slug, \"media usage field slug\");\n\t\t\textractionFields.push({ slug: row.slug, type: row.type });\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (row.type === \"repeater\") {\n\t\t\tvalidateIdentifier(row.slug, \"media usage field slug\");\n\t\t\tconst subFields = normalizeRepeaterImageSubFields(row.validation);\n\t\t\tif (subFields.length > 0) {\n\t\t\t\textractionFields.push({\n\t\t\t\t\tslug: row.slug,\n\t\t\t\t\ttype: \"repeater\",\n\t\t\t\t\tvalidation: { subFields },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\textractionFields.sort((a, b) => a.slug.localeCompare(b.slug));\n\n\treturn {\n\t\textractionFields,\n\t\tdisplayFieldSlugs: DISPLAY_FIELD_SLUGS.filter((slug) => {\n\t\t\tif (!rowBySlug.has(slug)) return false;\n\t\t\tvalidateIdentifier(slug, \"media usage display field slug\");\n\t\t\treturn true;\n\t\t}),\n\t};\n}\n\nfunction normalizeRepeaterImageSubFields(\n\trawValidation: string | null,\n): MediaUsageExtractionSubField[] {\n\tconst validation = parseValidation(rawValidation);\n\tif (!isRecord(validation) || !Array.isArray(validation.subFields)) return [];\n\n\tconst subFields: MediaUsageExtractionSubField[] = [];\n\tfor (const subField of validation.subFields) {\n\t\tif (!isRecord(subField) || subField.type !== \"image\") continue;\n\t\tif (typeof subField.slug !== \"string\") continue;\n\t\tvalidateIdentifier(subField.slug, \"media usage repeater sub-field slug\");\n\t\tsubFields.push({ slug: subField.slug, type: \"image\" });\n\t}\n\n\treturn subFields.toSorted((a, b) => a.slug.localeCompare(b.slug));\n}\n\nfunction parseValidation(rawValidation: string | null): unknown {\n\tif (!rawValidation) return null;\n\ttry {\n\t\treturn JSON.parse(rawValidation);\n\t} catch {\n\t\tthrow new MediaUsageFieldDiscoveryError(\n\t\t\t\"Repeater field validation must be valid JSON before media usage can be discovered\",\n\t\t\t\"INVALID_REPEATER_VALIDATION\",\n\t\t);\n\t}\n}\n\nfunction isSupportedTopLevelType(value: string): value is SupportedTopLevelType {\n\treturn (SUPPORTED_TOP_LEVEL_TYPES as readonly string[]).includes(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import { normalizeMime } from \"../mime.js\";\nimport { INTERNAL_MEDIA_PREFIX } from \"../normalize.js\";\nimport type {\n\tExtractedMediaUsageOccurrence,\n\tExtractMediaUsageOccurrencesInput,\n\tMediaKind,\n\tMediaUsageExtractionSubField,\n\tMediaUsageReferenceType,\n} from \"./types.js\";\n\nconst URL_LIKE_RE = /^(?:[a-z][a-z0-9+.-]*:|\\/\\/)/i;\n\ninterface MediaRef {\n\tmediaId: string | null;\n\tprovider: string;\n\tproviderAssetId: string;\n\tmediaKind: MediaKind | null;\n\tmimeType: string | null;\n}\n\ninterface AddOccurrenceInput {\n\tfieldSlug: string;\n\tfieldPath: string;\n\treferenceType: MediaUsageReferenceType;\n\tvalue: unknown;\n\tfallbackKind: MediaKind | null;\n}\n\nexport function extractMediaUsageOccurrences({\n\tfields,\n\tdata,\n}: ExtractMediaUsageOccurrencesInput): ExtractedMediaUsageOccurrence[] {\n\tconst occurrences: ExtractedMediaUsageOccurrence[] = [];\n\tconst seen = new Set<string>();\n\n\tfor (const field of fields) {\n\t\tconst value = data[field.slug];\n\n\t\tif (field.type === \"image\") {\n\t\t\taddOccurrence(occurrences, seen, {\n\t\t\t\tfieldSlug: field.slug,\n\t\t\t\tfieldPath: field.slug,\n\t\t\t\treferenceType: \"image_field\",\n\t\t\t\tvalue,\n\t\t\t\tfallbackKind: \"image\",\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (field.type === \"file\") {\n\t\t\taddOccurrence(occurrences, seen, {\n\t\t\t\tfieldSlug: field.slug,\n\t\t\t\tfieldPath: field.slug,\n\t\t\t\treferenceType: \"file_field\",\n\t\t\t\tvalue,\n\t\t\t\tfallbackKind: null,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (field.type === \"repeater\") {\n\t\t\textractRepeaterOccurrences(occurrences, seen, field.slug, value, field.validation?.subFields);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (field.type === \"portableText\") {\n\t\t\textractPortableTextOccurrences(occurrences, seen, field.slug, value);\n\t\t}\n\t}\n\n\treturn occurrences;\n}\n\nfunction extractRepeaterOccurrences(\n\toccurrences: ExtractedMediaUsageOccurrence[],\n\tseen: Set<string>,\n\tfieldSlug: string,\n\tvalue: unknown,\n\tsubFields: readonly MediaUsageExtractionSubField[] | undefined,\n): void {\n\tif (!Array.isArray(value) || !Array.isArray(subFields)) return;\n\n\tfor (const [itemIndex, item] of value.entries()) {\n\t\tif (!isRecord(item)) continue;\n\n\t\tfor (const subField of subFields) {\n\t\t\tif (subField.type !== \"image\") continue;\n\n\t\t\taddOccurrence(occurrences, seen, {\n\t\t\t\tfieldSlug,\n\t\t\t\tfieldPath: `${fieldSlug}[${itemIndex}].${subField.slug}`,\n\t\t\t\treferenceType: \"image_field\",\n\t\t\t\tvalue: item[subField.slug],\n\t\t\t\tfallbackKind: \"image\",\n\t\t\t});\n\t\t}\n\t}\n}\n\nfunction extractPortableTextOccurrences(\n\toccurrences: ExtractedMediaUsageOccurrence[],\n\tseen: Set<string>,\n\tfieldSlug: string,\n\tvalue: unknown,\n): void {\n\tif (!Array.isArray(value)) return;\n\n\tfor (const [blockIndex, block] of value.entries()) {\n\t\tif (!isRecord(block) || block._type !== \"image\" || !isRecord(block.asset)) continue;\n\n\t\tconst provider = normalizeProvider(block.asset.provider);\n\t\tconst ref = readPortableTextAssetRef(block.asset, provider);\n\t\tif (!ref) continue;\n\n\t\taddRefOccurrence(occurrences, seen, {\n\t\t\tfieldSlug,\n\t\t\tfieldPath: `${fieldSlug}[${blockIndex}].asset.${ref.key}`,\n\t\t\treferenceType: \"portable_text_image\",\n\t\t\tref: buildMediaRef({\n\t\t\t\tid: ref.id,\n\t\t\t\tprovider,\n\t\t\t\tmimeType: normalizeMimeValue(block.asset.mimeType),\n\t\t\t\tfallbackKind: \"image\",\n\t\t\t}),\n\t\t});\n\t}\n}\n\nfunction addOccurrence(\n\toccurrences: ExtractedMediaUsageOccurrence[],\n\tseen: Set<string>,\n\tinput: AddOccurrenceInput,\n): void {\n\tconst ref = readMediaRef(input.value, input.fallbackKind);\n\tif (!ref) return;\n\n\taddRefOccurrence(occurrences, seen, {\n\t\tfieldSlug: input.fieldSlug,\n\t\tfieldPath: input.fieldPath,\n\t\treferenceType: input.referenceType,\n\t\tref,\n\t});\n}\n\nfunction addRefOccurrence(\n\toccurrences: ExtractedMediaUsageOccurrence[],\n\tseen: Set<string>,\n\tinput: {\n\t\tfieldSlug: string;\n\t\tfieldPath: string;\n\t\treferenceType: MediaUsageReferenceType;\n\t\tref: MediaRef | null;\n\t},\n): void {\n\tif (!input.ref) return;\n\n\tconst occurrence: ExtractedMediaUsageOccurrence = {\n\t\tfieldSlug: input.fieldSlug,\n\t\tfieldPath: input.fieldPath,\n\t\toccurrenceIndex: 0,\n\t\treferenceType: input.referenceType,\n\t\tmediaId: input.ref.mediaId,\n\t\tprovider: input.ref.provider,\n\t\tproviderAssetId: input.ref.providerAssetId,\n\t\tmediaKind: input.ref.mediaKind,\n\t\tmimeType: input.ref.mimeType,\n\t};\n\n\tconst key = [\n\t\toccurrence.fieldSlug,\n\t\toccurrence.fieldPath,\n\t\toccurrence.occurrenceIndex,\n\t\toccurrence.referenceType,\n\t\toccurrence.provider,\n\t\toccurrence.providerAssetId,\n\t\toccurrence.mediaId ?? \"\",\n\t].join(\"\\0\");\n\n\tif (seen.has(key)) return;\n\tseen.add(key);\n\toccurrences.push(occurrence);\n}\n\nfunction readMediaRef(value: unknown, fallbackKind: MediaKind | null): MediaRef | null {\n\tif (typeof value === \"string\") {\n\t\tconst id = normalizeLocalMediaId(value);\n\t\treturn id ? buildMediaRef({ id, provider: \"local\", mimeType: null, fallbackKind }) : null;\n\t}\n\n\tif (!isRecord(value)) return null;\n\n\tconst provider = normalizeProvider(value.provider);\n\tconst id = provider === \"local\" ? normalizeLocalMediaId(value.id) : normalizeStableId(value.id);\n\tif (!id) return null;\n\n\treturn buildMediaRef({\n\t\tid,\n\t\tprovider,\n\t\tmimeType: normalizeMimeValue(value.mimeType),\n\t\tfallbackKind,\n\t});\n}\n\nfunction buildMediaRef(input: {\n\tid: string;\n\tprovider: string;\n\tmimeType: string | null;\n\tfallbackKind: MediaKind | null;\n}): MediaRef | null {\n\tconst provider = normalizeProvider(input.provider);\n\tif (provider === \"external\") return null;\n\n\treturn {\n\t\tmediaId: provider === \"local\" ? input.id : null,\n\t\tprovider,\n\t\tproviderAssetId: input.id,\n\t\tmediaKind: mediaKindFromMime(input.mimeType) ?? input.fallbackKind,\n\t\tmimeType: input.mimeType,\n\t};\n}\n\nfunction readPortableTextAssetRef(\n\tasset: Record<string, unknown>,\n\tprovider: string,\n): { key: \"_ref\" | \"id\"; id: string } | null {\n\tconst normalizeId = provider === \"local\" ? normalizeLocalMediaId : normalizeStableId;\n\tconst ref = normalizeId(asset._ref);\n\tif (ref) return { key: \"_ref\", id: ref };\n\n\tconst id = normalizeId(asset.id);\n\tif (id) return { key: \"id\", id };\n\n\treturn null;\n}\n\nfunction normalizeProvider(value: unknown): string {\n\tconst provider = readString(value)?.trim();\n\treturn provider || \"local\";\n}\n\nfunction normalizeLocalMediaId(value: unknown): string | null {\n\tconst id = normalizeStableId(value);\n\tif (!id) return null;\n\treturn id.includes(\"/\") ? null : id;\n}\n\nfunction normalizeStableId(value: unknown): string | null {\n\tif (typeof value !== \"string\") return null;\n\tconst trimmed = value.trim();\n\tif (!trimmed) return null;\n\tif (URL_LIKE_RE.test(trimmed)) return null;\n\tif (trimmed.startsWith(INTERNAL_MEDIA_PREFIX)) return null;\n\treturn trimmed;\n}\n\nfunction normalizeMimeValue(value: unknown): string | null {\n\tif (typeof value !== \"string\") return null;\n\tconst normalized = normalizeMime(value);\n\treturn normalized.includes(\"/\") ? normalized : null;\n}\n\nfunction mediaKindFromMime(mimeType: string | null): MediaKind | null {\n\tif (!mimeType) return null;\n\tif (mimeType.startsWith(\"image/\")) return \"image\";\n\tif (mimeType.startsWith(\"video/\")) return \"video\";\n\tif (mimeType.startsWith(\"audio/\")) return \"audio\";\n\tif (mimeType.startsWith(\"font/\") || mimeType.startsWith(\"application/font-\")) return \"font\";\n\tif (mimeType.startsWith(\"text/\")) return \"text\";\n\tif (isDocumentMime(mimeType)) return \"document\";\n\tif (isArchiveMime(mimeType)) return \"archive\";\n\treturn \"other\";\n}\n\nfunction isDocumentMime(mimeType: string): boolean {\n\treturn (\n\t\tmimeType === \"application/pdf\" ||\n\t\tmimeType === \"application/msword\" ||\n\t\tmimeType === \"application/rtf\" ||\n\t\tmimeType === \"application/vnd.ms-excel\" ||\n\t\tmimeType === \"application/vnd.ms-powerpoint\" ||\n\t\tmimeType.startsWith(\"application/vnd.openxmlformats-officedocument.\")\n\t);\n}\n\nfunction isArchiveMime(mimeType: string): boolean {\n\treturn (\n\t\tmimeType === \"application/zip\" ||\n\t\tmimeType === \"application/gzip\" ||\n\t\tmimeType === \"application/x-tar\" ||\n\t\tmimeType === \"application/x-7z-compressed\" ||\n\t\tmimeType === \"application/x-rar-compressed\" ||\n\t\tmimeType === \"application/vnd.rar\"\n\t);\n}\n\nfunction readString(value: unknown): string | null {\n\treturn typeof value === \"string\" ? value : null;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","export const MEDIA_USAGE_CONTENT_SOURCE_VARIANTS = [\"columns\", \"draft_overlay\"] as const;\n\nexport type MediaUsageContentSourceVariant = (typeof MEDIA_USAGE_CONTENT_SOURCE_VARIANTS)[number];\n\nexport interface ContentMediaUsageSourceKeyInput {\n\tcollectionSlug: string;\n\tcontentId: string;\n\tsourceVariant: MediaUsageContentSourceVariant;\n}\n\nexport function isMediaUsageContentSourceVariant(\n\tvalue: unknown,\n): value is MediaUsageContentSourceVariant {\n\treturn (\n\t\ttypeof value === \"string\" &&\n\t\t(MEDIA_USAGE_CONTENT_SOURCE_VARIANTS as readonly string[]).includes(value)\n\t);\n}\n\nexport function buildContentMediaUsageSourceKey(input: ContentMediaUsageSourceKeyInput): string {\n\treturn `content:${input.collectionSlug}:${input.contentId}:${input.sourceVariant}`;\n}\n","import { sql, type Kysely } from \"kysely\";\n\nimport type {\n\tMediaUsageOccurrenceInput,\n\tMediaUsageSourceInput,\n} from \"../../database/repositories/media-usage.js\";\nimport type { Database } from \"../../database/types.js\";\nimport { validateIdentifier } from \"../../database/validate.js\";\nimport { hashString } from \"../../utils/hash.js\";\nimport {\n\tloadContentMediaUsageFields,\n\ttype ContentMediaUsageField,\n\ttype ContentMediaUsageFieldDiscovery,\n} from \"./content-fields.js\";\nimport { extractMediaUsageOccurrences } from \"./extractor.js\";\nimport {\n\tbuildContentMediaUsageSourceKey,\n\ttype MediaUsageContentSourceVariant,\n} from \"./source-key.js\";\n\nexport const CONTENT_SOURCE_SCHEMA_VERSION = 1;\n\nconst CONTENT_SYSTEM_COLUMNS = [\n\t\"id\",\n\t\"slug\",\n\t\"status\",\n\t\"created_at\",\n\t\"updated_at\",\n\t\"published_at\",\n\t\"scheduled_at\",\n\t\"deleted_at\",\n\t\"version\",\n\t\"live_revision_id\",\n\t\"draft_revision_id\",\n\t\"locale\",\n\t\"translation_group\",\n] as const;\n\nexport type LoadContentMediaUsageSnapshotsResult =\n\t| { success: true; snapshots: ContentMediaUsageSnapshot[] }\n\t| {\n\t\t\tsuccess: false;\n\t\t\terror:\n\t\t\t\t| \"CONTENT_NOT_FOUND\"\n\t\t\t\t| \"DRAFT_REVISION_NOT_FOUND\"\n\t\t\t\t| \"DRAFT_REVISION_MISMATCH\"\n\t\t\t\t| \"DRAFT_REVISION_INVALID\";\n\t\t\tsource?: MediaUsageSourceInput;\n\t\t\tsnapshots?: ContentMediaUsageSnapshot[];\n\t };\n\nexport interface ContentMediaUsageSnapshot {\n\tsource: MediaUsageSourceInput;\n\toccurrences: MediaUsageOccurrenceInput[];\n\tfields: readonly ContentMediaUsageField[];\n}\n\nexport async function loadContentMediaUsageSnapshots(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tcontentId: string,\n\tfieldDiscovery?: ContentMediaUsageFieldDiscovery,\n): Promise<LoadContentMediaUsageSnapshotsResult> {\n\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\tconst discovery = fieldDiscovery ?? (await loadContentMediaUsageFields(db, collectionSlug));\n\tconst row = await loadContentRow(db, collectionSlug, contentId, [\n\t\t...discovery.extractionFields.map((field) => field.slug),\n\t\t...discovery.displayFieldSlugs,\n\t]);\n\n\tif (!row) return { success: false, error: \"CONTENT_NOT_FOUND\" };\n\n\tconst columnsData = projectData(\n\t\trow,\n\t\tdiscovery.extractionFields.map((field) => field.slug),\n\t);\n\tconst displayData = projectRawData(row, discovery.displayFieldSlugs);\n\tconst occurrences = extractMediaUsageOccurrences({\n\t\tfields: discovery.extractionFields,\n\t\tdata: columnsData,\n\t});\n\tconst columnsRevisionId = readNullableString(row.live_revision_id);\n\tconst columnsFingerprint = await buildSourceFingerprint({\n\t\tcollectionSlug,\n\t\tsourceVariant: \"columns\",\n\t\trevisionId: columnsRevisionId,\n\t\tfields: discovery.extractionFields,\n\t\tdata: columnsData,\n\t});\n\tconst snapshots: ContentMediaUsageSnapshot[] = [\n\t\t{\n\t\t\tsource: buildContentSource({\n\t\t\t\tcollectionSlug,\n\t\t\t\trow,\n\t\t\t\tdisplayData,\n\t\t\t\tsourceVariant: \"columns\",\n\t\t\t\trevisionId: columnsRevisionId,\n\t\t\t\tsourceFingerprint: columnsFingerprint,\n\t\t\t}),\n\t\t\toccurrences,\n\t\t\tfields: discovery.extractionFields,\n\t\t},\n\t];\n\n\tconst draftRevisionId = readNullableString(row.draft_revision_id);\n\tif (draftRevisionId) {\n\t\tconst attemptedDraftSource = buildContentSource({\n\t\t\tcollectionSlug,\n\t\t\trow,\n\t\t\tdisplayData,\n\t\t\tsourceVariant: \"draft_overlay\",\n\t\t\trevisionId: draftRevisionId,\n\t\t});\n\t\tconst revisionResult = await loadRevisionRow(db, draftRevisionId);\n\t\tif (!revisionResult) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: \"DRAFT_REVISION_NOT_FOUND\",\n\t\t\t\tsource: attemptedDraftSource,\n\t\t\t\tsnapshots,\n\t\t\t};\n\t\t}\n\t\tif (!revisionResult.success) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: \"DRAFT_REVISION_INVALID\",\n\t\t\t\tsource: attemptedDraftSource,\n\t\t\t\tsnapshots,\n\t\t\t};\n\t\t}\n\t\tconst revision = revisionResult.revision;\n\t\tif (revision.collection !== collectionSlug || revision.entryId !== row.id) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: \"DRAFT_REVISION_MISMATCH\",\n\t\t\t\tsource: attemptedDraftSource,\n\t\t\t\tsnapshots,\n\t\t\t};\n\t\t}\n\n\t\tconst revisionData = stripRevisionMetadata(revision.data);\n\t\tconst draftOverlayData = { ...columnsData, ...revisionData };\n\t\tconst draftDisplayData = {\n\t\t\t...displayData,\n\t\t\t...projectPresentData(revisionData, discovery.displayFieldSlugs),\n\t\t};\n\t\tconst draftContentSlug =\n\t\t\treadNullableString(revision.data._slug) ?? readNullableString(row.slug);\n\t\tconst draftFingerprint = await buildSourceFingerprint({\n\t\t\tcollectionSlug,\n\t\t\tsourceVariant: \"draft_overlay\",\n\t\t\trevisionId: draftRevisionId,\n\t\t\tfields: discovery.extractionFields,\n\t\t\tdata: draftOverlayData,\n\t\t});\n\t\tsnapshots.push({\n\t\t\tsource: buildContentSource({\n\t\t\t\tcollectionSlug,\n\t\t\t\trow,\n\t\t\t\tdisplayData: draftDisplayData,\n\t\t\t\tsourceVariant: \"draft_overlay\",\n\t\t\t\trevisionId: draftRevisionId,\n\t\t\t\tcontentSlug: draftContentSlug,\n\t\t\t\tsourceFingerprint: draftFingerprint,\n\t\t\t}),\n\t\t\toccurrences: extractMediaUsageOccurrences({\n\t\t\t\tfields: discovery.extractionFields,\n\t\t\t\tdata: draftOverlayData,\n\t\t\t}),\n\t\t\tfields: discovery.extractionFields,\n\t\t});\n\t}\n\n\treturn {\n\t\tsuccess: true,\n\t\tsnapshots,\n\t};\n}\n\ninterface RevisionSnapshotRow {\n\tid: string;\n\tcollection: string;\n\tentryId: string;\n\tdata: Record<string, unknown>;\n}\n\nasync function loadContentRow(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tcontentId: string,\n\tfieldSlugs: readonly string[],\n): Promise<Record<string, unknown> | null> {\n\tconst tableName = getContentTableName(collectionSlug);\n\tconst columns = uniqueColumns([...CONTENT_SYSTEM_COLUMNS, ...fieldSlugs]);\n\tconst columnRefs = columns.map((column) => sql.ref(column));\n\tconst result = await sql<Record<string, unknown>>`\n\t\tSELECT ${sql.join(columnRefs, sql`, `)}\n\t\tFROM ${sql.ref(tableName)}\n\t\tWHERE id = ${contentId}\n\t\tLIMIT 1\n\t`.execute(db);\n\n\treturn result.rows[0] ?? null;\n}\n\nasync function loadRevisionRow(\n\tdb: Kysely<Database>,\n\trevisionId: string,\n): Promise<{ success: true; revision: RevisionSnapshotRow } | { success: false } | null> {\n\tconst row = await db\n\t\t.selectFrom(\"revisions\")\n\t\t.select([\"id\", \"collection\", \"entry_id\", \"data\"])\n\t\t.where(\"id\", \"=\", revisionId)\n\t\t.executeTakeFirst();\n\tif (!row) return null;\n\tconst data = parseRevisionData(row.data);\n\tif (!data) return { success: false };\n\treturn {\n\t\tsuccess: true,\n\t\trevision: {\n\t\t\tid: row.id,\n\t\t\tcollection: row.collection,\n\t\t\tentryId: row.entry_id,\n\t\t\tdata,\n\t\t},\n\t};\n}\n\nfunction buildContentSource(input: {\n\tcollectionSlug: string;\n\trow: Record<string, unknown>;\n\tdisplayData: Record<string, unknown>;\n\tsourceVariant: MediaUsageContentSourceVariant;\n\trevisionId: string | null;\n\tcontentSlug?: string | null;\n\tsourceFingerprint?: string | null;\n}): MediaUsageSourceInput {\n\tconst { collectionSlug, row, displayData, sourceVariant, revisionId } = input;\n\tconst contentId = readString(row.id) ?? \"\";\n\tconst contentSlug = input.contentSlug ?? readNullableString(row.slug);\n\tconst source: MediaUsageSourceInput = {\n\t\tsourceKey: buildContentMediaUsageSourceKey({\n\t\t\tcollectionSlug,\n\t\t\tcontentId,\n\t\t\tsourceVariant,\n\t\t}),\n\t\tsourceType: \"content\",\n\t\tcollectionSlug,\n\t\tcontentId,\n\t\tsourceVariant,\n\t\tlocale: readNullableString(row.locale),\n\t\ttranslationGroup: readNullableString(row.translation_group),\n\t\tcontentSlug,\n\t\tcontentTitle: deriveContentTitle(displayData, contentSlug, contentId),\n\t\tcontentStatus: readNullableString(row.status),\n\t\tcontentScheduledAt: readNullableString(row.scheduled_at),\n\t\tcontentDeletedAt: readNullableString(row.deleted_at),\n\t\trevisionId,\n\t\tschemaVersion: CONTENT_SOURCE_SCHEMA_VERSION,\n\t\tsourceUpdatedAt: readNullableString(row.updated_at),\n\t\tsourceVersion: readNumber(row.version),\n\t};\n\tif (input.sourceFingerprint !== undefined) source.sourceFingerprint = input.sourceFingerprint;\n\treturn source;\n}\n\nasync function buildSourceFingerprint(input: {\n\tcollectionSlug: string;\n\tsourceVariant: MediaUsageContentSourceVariant;\n\trevisionId: string | null;\n\tfields: readonly ContentMediaUsageField[];\n\tdata: Record<string, unknown>;\n}): Promise<string> {\n\treturn hashString(\n\t\tcanonicalJson({\n\t\t\tschemaVersion: CONTENT_SOURCE_SCHEMA_VERSION,\n\t\t\tcollectionSlug: input.collectionSlug,\n\t\t\tsourceVariant: input.sourceVariant,\n\t\t\tfields: normalizeFingerprintFields(input.fields),\n\t\t\tvalues: projectFingerprintData(input.data, input.fields),\n\t\t\trevisionId: input.sourceVariant === \"draft_overlay\" ? input.revisionId : null,\n\t\t}),\n\t);\n}\n\nfunction normalizeFingerprintFields(\n\tfields: readonly ContentMediaUsageField[],\n): Record<string, unknown>[] {\n\treturn fields\n\t\t.map((field) => {\n\t\t\tif (field.type !== \"repeater\") return { slug: field.slug, type: field.type };\n\t\t\treturn {\n\t\t\t\tslug: field.slug,\n\t\t\t\ttype: field.type,\n\t\t\t\tsubFields: (field.validation?.subFields ?? [])\n\t\t\t\t\t.map((subField) => ({ slug: subField.slug, type: subField.type }))\n\t\t\t\t\t.toSorted((a, b) => a.slug.localeCompare(b.slug)),\n\t\t\t};\n\t\t})\n\t\t.toSorted((a, b) => String(a.slug).localeCompare(String(b.slug)));\n}\n\nfunction projectFingerprintData(\n\tdata: Record<string, unknown>,\n\tfields: readonly ContentMediaUsageField[],\n): Record<string, unknown> {\n\tconst projected: Record<string, unknown> = {};\n\tfor (const field of fields) {\n\t\tprojected[field.slug] = Object.hasOwn(data, field.slug) ? data[field.slug] : null;\n\t}\n\treturn projected;\n}\n\nfunction canonicalJson(value: unknown): string {\n\treturn JSON.stringify(canonicalize(value));\n}\n\nfunction canonicalize(value: unknown): unknown {\n\tif (value === undefined) return null;\n\tif (typeof value === \"bigint\") return value.toString();\n\tif (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n\tif (Array.isArray(value)) return value.map((item) => canonicalize(item));\n\tif (!isRecord(value)) return value;\n\n\tconst canonical: Record<string, unknown> = {};\n\tfor (const key of Object.keys(value).toSorted()) {\n\t\tcanonical[key] = canonicalize(value[key]);\n\t}\n\treturn canonical;\n}\n\nfunction projectData(\n\trow: Record<string, unknown>,\n\tfieldSlugs: readonly string[],\n): Record<string, unknown> {\n\tconst data: Record<string, unknown> = {};\n\tfor (const fieldSlug of fieldSlugs) {\n\t\tdata[fieldSlug] = deserializeValue(row[fieldSlug] ?? null);\n\t}\n\treturn data;\n}\n\nfunction projectRawData(\n\trow: Record<string, unknown>,\n\tfieldSlugs: readonly string[],\n): Record<string, unknown> {\n\tconst data: Record<string, unknown> = {};\n\tfor (const fieldSlug of fieldSlugs) {\n\t\tdata[fieldSlug] = row[fieldSlug] ?? null;\n\t}\n\treturn data;\n}\n\nfunction projectPresentData(\n\trow: Record<string, unknown>,\n\tfieldSlugs: readonly string[],\n): Record<string, unknown> {\n\tconst data: Record<string, unknown> = {};\n\tfor (const fieldSlug of fieldSlugs) {\n\t\tif (Object.hasOwn(row, fieldSlug)) data[fieldSlug] = row[fieldSlug];\n\t}\n\treturn data;\n}\n\nfunction uniqueColumns(columns: readonly string[]): string[] {\n\tconst unique = [...new Set(columns)];\n\tfor (const column of unique) validateIdentifier(column, \"content media usage column\");\n\treturn unique;\n}\n\nfunction getContentTableName(collectionSlug: string): string {\n\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\treturn `ec_${collectionSlug}`;\n}\n\nfunction deserializeValue(value: unknown): unknown {\n\tif (typeof value === \"string\" && (value.startsWith(\"{\") || value.startsWith(\"[\"))) {\n\t\ttry {\n\t\t\treturn JSON.parse(value);\n\t\t} catch {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn value;\n}\n\nfunction parseRevisionData(value: unknown): Record<string, unknown> | null {\n\tif (typeof value === \"string\") {\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(value);\n\t\t\treturn isRecord(parsed) ? parsed : null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\treturn isRecord(value) ? value : null;\n}\n\nfunction stripRevisionMetadata(data: Record<string, unknown>): Record<string, unknown> {\n\tconst stripped: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tif (!key.startsWith(\"_\")) stripped[key] = value;\n\t}\n\treturn stripped;\n}\n\nfunction deriveContentTitle(\n\tdisplayData: Record<string, unknown>,\n\tcontentSlug: string | null,\n\tcontentId: string,\n): string | null {\n\tfor (const fieldSlug of [\"title\", \"name\"] as const) {\n\t\tconst value = displayData[fieldSlug];\n\t\tif (typeof value === \"string\" && value.trim()) return value;\n\t}\n\treturn contentSlug ?? contentId;\n}\n\nfunction readString(value: unknown): string | null {\n\treturn typeof value === \"string\" ? value : null;\n}\n\nfunction readNullableString(value: unknown): string | null {\n\treturn value === null || value === undefined ? null : readString(value);\n}\n\nfunction readNumber(value: unknown): number | null {\n\tif (typeof value === \"number\" && Number.isFinite(value)) return value;\n\tif (typeof value === \"bigint\") return Number(value);\n\tif (typeof value === \"string\" && value) {\n\t\tconst parsed = Number(value);\n\t\treturn Number.isFinite(parsed) ? parsed : null;\n\t}\n\treturn null;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import { sql, type Kysely } from \"kysely\";\n\nimport { MediaUsageRepository } from \"../../database/repositories/media-usage.js\";\nimport type { Database } from \"../../database/types.js\";\nimport { validateIdentifier } from \"../../database/validate.js\";\nimport { isI18nEnabled } from \"../../i18n/config.js\";\nimport { loadContentMediaUsageFields } from \"./content-fields.js\";\nimport {\n\tCONTENT_SOURCE_SCHEMA_VERSION,\n\tloadContentMediaUsageSnapshots,\n} from \"./content-snapshots.js\";\nimport {\n\tbuildContentMediaUsageSourceKey,\n\tMEDIA_USAGE_CONTENT_SOURCE_VARIANTS,\n} from \"./source-key.js\";\n\nexport const CONTENT_MEDIA_USAGE_ADAPTER_ID = \"content-media\";\nexport const CONTENT_MEDIA_USAGE_COLLECTION_SCOPE = \"collection\";\n\nconst CONTENT_USAGE_LOCKS_KEY = Symbol.for(\"emdash.mediaUsage.contentLocks\");\nconst CONTENT_USAGE_COLLECTION_LOCKS_KEY = Symbol.for(\"emdash.mediaUsage.collectionLocks\");\nconst CONTENT_USAGE_REFRESH_MAX_ATTEMPTS = 2;\n\n// These maps only de-dupe usage work inside the current isolate/process. Cross-worker\n// correctness comes from expected-generation guards on repository writes.\n\nexport type ContentMediaUsageRefreshErrorCode =\n\t| \"CONTENT_NOT_FOUND\"\n\t| \"DRAFT_REVISION_NOT_FOUND\"\n\t| \"DRAFT_REVISION_MISMATCH\"\n\t| \"DRAFT_REVISION_INVALID\"\n\t| \"CONTENT_USAGE_REFRESH_ERROR\"\n\t| \"CONTENT_USAGE_DELETE_ERROR\"\n\t| \"CONTENT_USAGE_GENERATION_CONFLICT\"\n\t| \"CONTENT_USAGE_STALE\";\n\nexport interface ContentMediaUsageRefreshResult {\n\tsuccess: boolean;\n\trefreshedSourceCount: number;\n\tdeletedSourceCount: number;\n\tfailedSourceCount: number;\n\terrorCode?: ContentMediaUsageRefreshErrorCode;\n}\n\nconst ZERO_RESULT: ContentMediaUsageRefreshResult = {\n\tsuccess: true,\n\trefreshedSourceCount: 0,\n\tdeletedSourceCount: 0,\n\tfailedSourceCount: 0,\n};\n\nexport async function refreshContentMediaUsage(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tcontentId: string,\n): Promise<ContentMediaUsageRefreshResult> {\n\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\treturn withContentUsageCollectionLock(collectionSlug, () =>\n\t\twithContentUsageLock(collectionSlug, contentId, () =>\n\t\t\trefreshContentMediaUsageUnlocked(db, collectionSlug, contentId),\n\t\t),\n\t);\n}\n\nasync function refreshContentMediaUsageUnlocked(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tcontentId: string,\n): Promise<ContentMediaUsageRefreshResult> {\n\ttry {\n\t\tlet conflictResult: ContentMediaUsageRefreshResult | null = null;\n\t\tfor (let attempt = 0; attempt < CONTENT_USAGE_REFRESH_MAX_ATTEMPTS; attempt++) {\n\t\t\tconst result = await refreshContentMediaUsageAttempt(db, collectionSlug, contentId);\n\t\t\tif (result.errorCode !== \"CONTENT_USAGE_GENERATION_CONFLICT\") return result;\n\t\t\tconflictResult = result;\n\t\t}\n\n\t\treturn markGenerationConflict(db, collectionSlug, {\n\t\t\trefreshedSourceCount: conflictResult?.refreshedSourceCount ?? 0,\n\t\t\tdeletedSourceCount: conflictResult?.deletedSourceCount ?? 0,\n\t\t});\n\t} catch (error) {\n\t\tconsole.error(`[media-usage] Failed to refresh ${collectionSlug}/${contentId}:`, error);\n\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\tdb,\n\t\t\tcollectionSlug,\n\t\t\t\"CONTENT_USAGE_REFRESH_ERROR\",\n\t\t);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\trefreshedSourceCount: 0,\n\t\t\tdeletedSourceCount: 0,\n\t\t\tfailedSourceCount: 0,\n\t\t\terrorCode: \"CONTENT_USAGE_REFRESH_ERROR\",\n\t\t};\n\t}\n}\n\nasync function refreshContentMediaUsageAttempt(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tcontentId: string,\n): Promise<ContentMediaUsageRefreshResult> {\n\tconst repo = new MediaUsageRepository(db);\n\tconst observedGenerations = await loadObservedContentSourceGenerations(\n\t\tdb,\n\t\tcollectionSlug,\n\t\tcontentId,\n\t);\n\tconst snapshotsResult = await loadContentMediaUsageSnapshots(db, collectionSlug, contentId);\n\tif (!snapshotsResult.success) {\n\t\treturn markSnapshotFailure(db, collectionSlug, snapshotsResult);\n\t}\n\n\tif (!(await contentCollectionExists(db, collectionSlug))) {\n\t\tconst deletedSourceCount = await repo.deleteContentSources(collectionSlug, contentId);\n\t\treturn { ...ZERO_RESULT, deletedSourceCount };\n\t}\n\n\tlet refreshedSourceCount = 0;\n\tfor (const snapshot of snapshotsResult.snapshots) {\n\t\tconst result = await repo.replaceSourceIfCurrent(\n\t\t\tsnapshot.source,\n\t\t\tsnapshot.occurrences,\n\t\t\tobservedGenerations.get(snapshot.source.sourceKey) ?? null,\n\t\t);\n\t\tif (!result.replaced) {\n\t\t\treturn generationConflictResult({\n\t\t\t\trefreshedSourceCount,\n\t\t\t\tdeletedSourceCount: 0,\n\t\t\t});\n\t\t}\n\t\trefreshedSourceCount++;\n\t}\n\tif (!(await contentCollectionExists(db, collectionSlug))) {\n\t\tconst deletedSourceCount = await repo.deleteContentSources(collectionSlug, contentId);\n\t\treturn { ...ZERO_RESULT, deletedSourceCount };\n\t}\n\n\tconst expectedSourceKeys = new Set(\n\t\tsnapshotsResult.snapshots.map((snapshot) => snapshot.source.sourceKey),\n\t);\n\tconst absentSourceKeys = MEDIA_USAGE_CONTENT_SOURCE_VARIANTS.map((sourceVariant) =>\n\t\tbuildContentMediaUsageSourceKey({ collectionSlug, contentId, sourceVariant }),\n\t).filter((sourceKey) => !expectedSourceKeys.has(sourceKey));\n\tlet deletedSourceCount = 0;\n\tfor (const sourceKey of absentSourceKeys) {\n\t\tconst expectedGeneration = observedGenerations.get(sourceKey) ?? null;\n\t\tif (expectedGeneration === null) continue;\n\n\t\tconst result = await repo.deleteSourceIfCurrent(sourceKey, expectedGeneration);\n\t\tif (result.deleted) {\n\t\t\tdeletedSourceCount++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (result.source) {\n\t\t\treturn generationConflictResult({\n\t\t\t\trefreshedSourceCount,\n\t\t\t\tdeletedSourceCount,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn {\n\t\tsuccess: true,\n\t\trefreshedSourceCount,\n\t\tdeletedSourceCount,\n\t\tfailedSourceCount: 0,\n\t};\n}\n\nasync function loadObservedContentSourceGenerations(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tcontentId: string,\n): Promise<Map<string, string | null>> {\n\tconst generations = new Map<string, string | null>();\n\tconst sourceKeys = MEDIA_USAGE_CONTENT_SOURCE_VARIANTS.map((sourceVariant) =>\n\t\tbuildContentMediaUsageSourceKey({\n\t\t\tcollectionSlug,\n\t\t\tcontentId,\n\t\t\tsourceVariant,\n\t\t}),\n\t);\n\tfor (const sourceKey of sourceKeys) {\n\t\tgenerations.set(sourceKey, null);\n\t}\n\n\tconst rows = await db\n\t\t.selectFrom(\"_emdash_media_usage_sources\")\n\t\t.select([\"source_key\", \"current_generation\"])\n\t\t.where(\"source_key\", \"in\", sourceKeys)\n\t\t.execute();\n\tfor (const row of rows) {\n\t\tgenerations.set(row.source_key, row.current_generation);\n\t}\n\n\treturn generations;\n}\n\nasync function markGenerationConflict(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tcounts: Pick<ContentMediaUsageRefreshResult, \"refreshedSourceCount\" | \"deletedSourceCount\">,\n): Promise<ContentMediaUsageRefreshResult> {\n\tawait markContentMediaUsageCollectionStaleSafely(\n\t\tdb,\n\t\tcollectionSlug,\n\t\t\"CONTENT_USAGE_GENERATION_CONFLICT\",\n\t);\n\treturn {\n\t\tsuccess: false,\n\t\trefreshedSourceCount: counts.refreshedSourceCount,\n\t\tdeletedSourceCount: counts.deletedSourceCount,\n\t\tfailedSourceCount: 0,\n\t\terrorCode: \"CONTENT_USAGE_GENERATION_CONFLICT\",\n\t};\n}\n\nfunction generationConflictResult(\n\tcounts: Pick<ContentMediaUsageRefreshResult, \"refreshedSourceCount\" | \"deletedSourceCount\">,\n): ContentMediaUsageRefreshResult {\n\treturn {\n\t\tsuccess: false,\n\t\trefreshedSourceCount: counts.refreshedSourceCount,\n\t\tdeletedSourceCount: counts.deletedSourceCount,\n\t\tfailedSourceCount: 0,\n\t\terrorCode: \"CONTENT_USAGE_GENERATION_CONFLICT\",\n\t};\n}\n\nasync function contentCollectionExists(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n): Promise<boolean> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_collections\")\n\t\t.select(\"id\")\n\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t.executeTakeFirst();\n\treturn row !== undefined;\n}\n\nexport async function deleteContentMediaUsage(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tcontentId: string,\n): Promise<ContentMediaUsageRefreshResult> {\n\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\treturn withContentUsageCollectionLock(collectionSlug, () =>\n\t\twithContentUsageLock(collectionSlug, contentId, () =>\n\t\t\tdeleteContentMediaUsageUnlocked(db, collectionSlug, contentId),\n\t\t),\n\t);\n}\n\nasync function deleteContentMediaUsageUnlocked(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tcontentId: string,\n): Promise<ContentMediaUsageRefreshResult> {\n\ttry {\n\t\tconst deletedSourceCount = await new MediaUsageRepository(db).deleteContentSources(\n\t\t\tcollectionSlug,\n\t\t\tcontentId,\n\t\t);\n\t\treturn { ...ZERO_RESULT, deletedSourceCount };\n\t} catch (error) {\n\t\tconsole.error(\n\t\t\t`[media-usage] Failed to delete usage for ${collectionSlug}/${contentId}:`,\n\t\t\terror,\n\t\t);\n\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\tdb,\n\t\t\tcollectionSlug,\n\t\t\t\"CONTENT_USAGE_DELETE_ERROR\",\n\t\t);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\trefreshedSourceCount: 0,\n\t\t\tdeletedSourceCount: 0,\n\t\t\tfailedSourceCount: 0,\n\t\t\terrorCode: \"CONTENT_USAGE_DELETE_ERROR\",\n\t\t};\n\t}\n}\n\nexport async function deleteContentMediaUsageCollection(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n): Promise<ContentMediaUsageRefreshResult> {\n\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\treturn withContentUsageCollectionLock(collectionSlug, () =>\n\t\tdeleteContentMediaUsageCollectionUnlocked(db, collectionSlug),\n\t);\n}\n\nasync function deleteContentMediaUsageCollectionUnlocked(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n): Promise<ContentMediaUsageRefreshResult> {\n\ttry {\n\t\tconst repo = new MediaUsageRepository(db);\n\t\tconst deletedSourceCount = await repo.deleteCollectionSources(collectionSlug);\n\t\tawait repo.deleteIndexStatus({\n\t\t\tadapterId: CONTENT_MEDIA_USAGE_ADAPTER_ID,\n\t\t\tscopeType: CONTENT_MEDIA_USAGE_COLLECTION_SCOPE,\n\t\t\tscopeKey: collectionSlug,\n\t\t});\n\t\treturn { ...ZERO_RESULT, deletedSourceCount };\n\t} catch (error) {\n\t\tconsole.error(`[media-usage] Failed to delete usage for collection ${collectionSlug}:`, error);\n\t\ttry {\n\t\t\tawait new MediaUsageRepository(db).deleteIndexStatus({\n\t\t\t\tadapterId: CONTENT_MEDIA_USAGE_ADAPTER_ID,\n\t\t\t\tscopeType: CONTENT_MEDIA_USAGE_COLLECTION_SCOPE,\n\t\t\t\tscopeKey: collectionSlug,\n\t\t\t});\n\t\t} catch (statusError) {\n\t\t\tconsole.error(\n\t\t\t\t`[media-usage] Failed to clear usage status for deleted collection ${collectionSlug}:`,\n\t\t\t\tstatusError,\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\trefreshedSourceCount: 0,\n\t\t\tdeletedSourceCount: 0,\n\t\t\tfailedSourceCount: 0,\n\t\t\terrorCode: \"CONTENT_USAGE_DELETE_ERROR\",\n\t\t};\n\t}\n}\n\nexport async function refreshContentMediaUsageAfterWrite(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tcontentId: string,\n): Promise<void> {\n\tconst result = await refreshContentMediaUsage(db, collectionSlug, contentId);\n\tif (!result.success) {\n\t\tconsole.error(\n\t\t\t`[media-usage] Usage refresh for ${collectionSlug}/${contentId} finished with ${result.errorCode}`,\n\t\t);\n\t}\n}\n\nexport async function markContentMediaUsageCollectionStale(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tlastErrorCode: string,\n): Promise<void> {\n\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\tconst repo = new MediaUsageRepository(db);\n\tconst identity = {\n\t\tadapterId: CONTENT_MEDIA_USAGE_ADAPTER_ID,\n\t\tscopeType: CONTENT_MEDIA_USAGE_COLLECTION_SCOPE,\n\t\tscopeKey: collectionSlug,\n\t};\n\tconst existing = await repo.findIndexStatus(identity);\n\tawait repo.upsertIndexStatus({\n\t\t...identity,\n\t\tstatus: \"stale\",\n\t\tschemaVersion: existing?.schemaVersion ?? CONTENT_SOURCE_SCHEMA_VERSION,\n\t\tstartedAt: existing?.startedAt ?? null,\n\t\tcompletedAt: existing?.completedAt ?? null,\n\t\tcursor: existing?.cursor ?? null,\n\t\tindexedSourceCount: existing?.indexedSourceCount ?? 0,\n\t\tfailedSourceCount: existing?.failedSourceCount ?? 0,\n\t\tlastErrorCode,\n\t});\n}\n\nexport async function findNonTranslatableSiblingContentIds(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tupdatedContentId: string,\n\ttranslationGroup: string | null | undefined,\n\tupdatedData: Record<string, unknown> | undefined,\n): Promise<string[]> {\n\tif (!isI18nEnabled() || !updatedData || !translationGroup) return [];\n\n\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\tconst collection = await db\n\t\t.selectFrom(\"_emdash_collections\")\n\t\t.select(\"id\")\n\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t.executeTakeFirst();\n\tif (!collection) return [];\n\n\tconst fields = await db\n\t\t.selectFrom(\"_emdash_fields\")\n\t\t.select(\"slug\")\n\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t.where(\"translatable\", \"=\", 0)\n\t\t.execute();\n\n\tconst touchedNonTranslatableSlugs = fields\n\t\t.filter((field) => field.slug in updatedData)\n\t\t.map((field) => field.slug);\n\tif (touchedNonTranslatableSlugs.length === 0) return [];\n\n\tconst usageFields = await loadContentMediaUsageFields(db, collectionSlug);\n\tconst usageRelevantSlugs = new Set([\n\t\t...usageFields.extractionFields.map((field) => field.slug),\n\t\t...usageFields.displayFieldSlugs,\n\t]);\n\tif (!touchedNonTranslatableSlugs.some((slug) => usageRelevantSlugs.has(slug))) return [];\n\n\tconst tableName = `ec_${collectionSlug}`;\n\tconst rows = await sql<{ id: string }>`\n\t\tSELECT id\n\t\tFROM ${sql.ref(tableName)}\n\t\tWHERE translation_group = ${translationGroup}\n\t\tAND id != ${updatedContentId}\n\t\tORDER BY id ASC\n\t`.execute(db);\n\n\treturn rows.rows.map((row) => row.id);\n}\n\nasync function markSnapshotFailure(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tresult: Exclude<Awaited<ReturnType<typeof loadContentMediaUsageSnapshots>>, { success: true }>,\n): Promise<ContentMediaUsageRefreshResult> {\n\tconst repo = new MediaUsageRepository(db);\n\tif (result.source) {\n\t\tawait repo.markSourceAttempted({\n\t\t\t...result.source,\n\t\t\tsourceCompleteness: \"failed\",\n\t\t\tlastErrorCode: result.error,\n\t\t});\n\t}\n\tawait markContentMediaUsageCollectionStale(db, collectionSlug, result.error);\n\treturn {\n\t\tsuccess: false,\n\t\trefreshedSourceCount: 0,\n\t\tdeletedSourceCount: 0,\n\t\tfailedSourceCount: result.source ? 1 : 0,\n\t\terrorCode: result.error,\n\t};\n}\n\nexport async function markContentMediaUsageCollectionStaleSafely(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tlastErrorCode: ContentMediaUsageRefreshErrorCode,\n): Promise<boolean> {\n\ttry {\n\t\tawait markContentMediaUsageCollectionStale(db, collectionSlug, lastErrorCode);\n\t\treturn true;\n\t} catch (error) {\n\t\tconsole.error(`[media-usage] Failed to mark ${collectionSlug} stale:`, error);\n\t\treturn false;\n\t}\n}\n\nasync function withContentUsageLock<T>(\n\tcollectionSlug: string,\n\tcontentId: string,\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst locks = getContentUsageLocks();\n\tconst lockKey = `${collectionSlug}\\0${contentId}`;\n\tconst previous = locks.get(lockKey) ?? Promise.resolve();\n\tlet releaseCurrent!: () => void;\n\tconst current = new Promise<void>((resolve) => {\n\t\treleaseCurrent = resolve;\n\t});\n\tconst next = previous.catch(() => {}).then(() => current);\n\tlocks.set(lockKey, next);\n\n\ttry {\n\t\tawait previous.catch(() => {});\n\t\treturn await fn();\n\t} finally {\n\t\treleaseCurrent();\n\t\tif (locks.get(lockKey) === next) locks.delete(lockKey);\n\t}\n}\n\nexport async function withContentUsageCollectionLock<T>(\n\tcollectionSlug: string,\n\tfn: () => Promise<T>,\n): Promise<T> {\n\t// Coarse by design: row refreshes and collection source deletes must not interleave.\n\tconst locks = getContentUsageCollectionLocks();\n\tconst previous = locks.get(collectionSlug) ?? Promise.resolve();\n\tlet releaseCurrent!: () => void;\n\tconst current = new Promise<void>((resolve) => {\n\t\treleaseCurrent = resolve;\n\t});\n\tconst next = previous.catch(() => {}).then(() => current);\n\tlocks.set(collectionSlug, next);\n\n\ttry {\n\t\tawait previous.catch(() => {});\n\t\treturn await fn();\n\t} finally {\n\t\treleaseCurrent();\n\t\tif (locks.get(collectionSlug) === next) locks.delete(collectionSlug);\n\t}\n}\n\nfunction getContentUsageLocks(): Map<string, Promise<void>> {\n\tconst global = globalThis as typeof globalThis & Record<symbol, unknown>;\n\tconst existing = global[CONTENT_USAGE_LOCKS_KEY];\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis symbol slot stores only this map\n\tif (existing instanceof Map) return existing as Map<string, Promise<void>>;\n\tconst locks = new Map<string, Promise<void>>();\n\tglobal[CONTENT_USAGE_LOCKS_KEY] = locks;\n\treturn locks;\n}\n\nfunction getContentUsageCollectionLocks(): Map<string, Promise<void>> {\n\tconst global = globalThis as typeof globalThis & Record<symbol, unknown>;\n\tconst existing = global[CONTENT_USAGE_COLLECTION_LOCKS_KEY];\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis symbol slot stores only this map\n\tif (existing instanceof Map) return existing as Map<string, Promise<void>>;\n\tconst locks = new Map<string, Promise<void>>();\n\tglobal[CONTENT_USAGE_COLLECTION_LOCKS_KEY] = locks;\n\treturn locks;\n}\n"],"mappings":";;;;;;;;;;;;AAkCA,MAAM,+BAA+B,KAAK,IACzC,GACA,KAAK,MAAM,iBAHoB,GAGqB,CACpD;;AAsPD,IAAa,uBAAb,MAAkC;CACjC,YAAY,AAAQ,IAAsB;EAAtB;;CAEpB,MAAM,cACL,QACA,aAC4B;EAC5B,MAAM,aAAa,MAAM;EACzB,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;AAEpC,QAAM,gBAAgB,KAAK,IAAI,OAAO,QAAQ;AAC7C,SAAM,KAAK,kBAAkB,KAAK,OAAO,WAAW,YAAY,aAAa,IAAI;AACjF,SAAM,KAAK,aAAa,KAAK,QAAQ,YAAY,IAAI;IACpD;EAEF,MAAM,WAAW,MAAM,KAAK,WAAW,OAAO,UAAU;AACxD,MAAI,CAAC,SACJ,OAAM,IAAI,MAAM,sBAAsB,OAAO,UAAU,oBAAoB;AAE5E,SAAO;;CAER,MAAM,uBACL,QACA,aACA,2BAC0C;EAC1C,MAAM,aAAa,MAAM;EACzB,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;EACpC,MAAM,MAAM,KAAK,eAAe,QAAQ,YAAY,IAAI;EACxD,IAAI,WAAW;AAEf,QAAM,gBAAgB,KAAK,IAAI,OAAO,QAAQ;AAC7C,SAAM,KAAK,kBAAkB,KAAK,OAAO,WAAW,YAAY,aAAa,IAAI;AACjF,OAAI,8BAA8B,MAAM;AACvC,eAAW,MAAM,KAAK,qBAAqB,KAAK,IAAI;AACpD;;AAED,cAAW,MAAM,KAAK,yBAAyB,KAAK,KAAK,0BAA0B;IAClF;AAEF,SAAO;GACN;GACA,QAAQ,WAAW,OAAO,MAAM,KAAK,WAAW,OAAO,UAAU;GACjE;;CAGF,MAAM,WAAW,WAAqD;EACrE,MAAM,MAAM,MAAM,KAAK,GACrB,WAAW,8BAA8B,CACzC,WAAW,CACX,MAAM,cAAc,KAAK,UAAU,CACnC,kBAAkB;AAEpB,SAAO,MAAM,YAAY,IAAI,GAAG;;CAGjC,MAAM,YAAY,YAAuE;EACxF,MAAM,mBAAmB,CAAC,GAAG,IAAI,IAAI,WAAW,CAAC;EACjD,MAAM,0BAAU,IAAI,KAA+B;AACnD,MAAI,iBAAiB,WAAW,EAAG,QAAO;AAE1C,OAAK,MAAM,kBAAkB,OAAO,kBAAkB,eAAe,EAAE;GACtE,MAAM,OAAO,MAAM,KAAK,GACtB,WAAW,8BAA8B,CACzC,WAAW,CACX,MAAM,cAAc,MAAM,eAAe,CACzC,SAAS;AACX,QAAK,MAAM,OAAO,MAAM;IACvB,MAAM,SAAS,YAAY,IAAI;AAC/B,YAAQ,IAAI,OAAO,WAAW,OAAO;;;AAIvC,SAAO;;CAGR,MAAM,wBACL,QACA,aACA,gBAC0C;EAC1C,MAAM,aAAa,MAAM;EACzB,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;EACpC,MAAM,MAAM,KAAK,eAAe,QAAQ,YAAY,IAAI;EACxD,IAAI,WAAW;AAEf,QAAM,gBAAgB,KAAK,IAAI,OAAO,QAAQ;AAC7C,SAAM,KAAK,kBAAkB,KAAK,OAAO,WAAW,YAAY,aAAa,IAAI;AACjF,OAAI,mBAAmB,MAAM;AAC5B,eAAW,MAAM,KAAK,qBAAqB,KAAK,IAAI;AACpD;;AAED,cAAW,MAAM,KAAK,uBAAuB,KAAK,KAAK,eAAe;IACrE;AAEF,SAAO;GACN;GACA,QAAQ,WAAW,OAAO,MAAM,KAAK,WAAW,OAAO,UAAU;GACjE;;CAGF,MAAM,oBAAoB,QAA0D;EACnF,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;EACpC,MAAM,MAAM,KAAK,wBAAwB,QAAQ,IAAI;EACrD,MAAM,UAAU,KAAK,yBAAyB,QAAQ,IAAI;AAE1D,QAAM,KAAK,GACT,WAAW,8BAA8B,CACzC,OAAO,IAAI,CACX,YAAY,OAAO,GAAG,OAAO,aAAa,CAAC,YAAY,QAAQ,CAAC,CAChE,SAAS;EAEX,MAAM,YAAY,MAAM,KAAK,WAAW,OAAO,UAAU;AACzD,MAAI,CAAC,UACJ,OAAM,IAAI,MAAM,sBAAsB,OAAO,UAAU,oBAAoB;AAE5E,SAAO;;CAGR,MAAM,8BACL,QACA,gBAC0C;EAC1C,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;EACpC,MAAM,MAAM,KAAK,wBAAwB,QAAQ,IAAI;EACrD,IAAI,YAAY;AAEhB,MAAI,mBAAmB,KACtB,aAAY,MAAM,KAAK,qBAAqB,KAAK,IAAI,IAAI;MAEzD,aAAY,MAAM,KAAK,gCAAgC,KAAK,IAAI,QAAQ,KAAK,eAAe;AAG7F,SAAO;GACN;GACA,QAAQ,YAAY,OAAO,MAAM,KAAK,WAAW,OAAO,UAAU;GAClE;;CAGF,MAAM,0BAA0B,SAA8C;AAe7E,UAda,MAAM,KAAK,GACtB,WAAW,mCAAmC,CAC9C,UAAU,6BAA6B,SACvC,KACE,MAAM,gBAAgB,KAAK,eAAe,CAC1C,MAAM,gBAAgB,KAAK,uBAAuB,CACpD,CACA,OAAO,mBAAmB,CAC1B,MAAM,cAAc,KAAK,QAAQ,CACjC,QAAQ,gBAAgB,MAAM,CAC9B,QAAQ,gBAAgB,MAAM,CAC9B,QAAQ,sBAAsB,MAAM,CACpC,SAAS,EAEC,IAAI,iBAAiB;;CAGlC,MAAM,gCACL,UACA,iBAC8B;AAgB9B,UAfa,MAAM,KAAK,GACtB,WAAW,mCAAmC,CAC9C,UAAU,6BAA6B,SACvC,KACE,MAAM,gBAAgB,KAAK,eAAe,CAC1C,MAAM,gBAAgB,KAAK,uBAAuB,CACpD,CACA,OAAO,mBAAmB,CAC1B,MAAM,cAAc,KAAK,SAAS,CAClC,MAAM,uBAAuB,KAAK,gBAAgB,CAClD,QAAQ,gBAAgB,MAAM,CAC9B,QAAQ,gBAAgB,MAAM,CAC9B,QAAQ,sBAAsB,MAAM,CACpC,SAAS,EAEC,IAAI,iBAAiB;;CAGlC,MAAM,8BACL,SACA,UAAiC,EAAE,EACS;AAC5C,SAAO,KAAK,sBAAsB,UAAU,MAAM,MAAM,cAAc,KAAK,QAAQ,EAAE,QAAQ;;CAG9F,MAAM,oCACL,UACA,iBACA,UAAiC,EAAE,EACS;AAC5C,SAAO,KAAK,sBACV,UACA,MAAM,MAAM,cAAc,KAAK,SAAS,CAAC,MAAM,uBAAuB,KAAK,gBAAgB,EAC5F,QACA;;CAGF,MAAM,aAAa,WAAoC;AACtD,SAAO,KAAK,cAAc,CAAC,UAAU,CAAC;;CAGvC,MAAM,sBACL,WACA,2BACyC;EACzC,IAAI,UAAU;AACd,QAAM,gBAAgB,KAAK,IAAI,OAAO,QAAQ;GAC7C,MAAM,SAAS,MAAM,IACnB,WAAW,8BAA8B,CACzC,MAAM,cAAc,KAAK,UAAU,CACnC,MAAM,sBAAsB,KAAK,0BAA0B,CAC3D,kBAAkB;AACpB,aAAU,OAAO,OAAO,kBAAkB,EAAE,GAAG;AAC/C,OAAI,CAAC,QAAS;AACd,SAAM,KAAK,kCAAkC,KAAK,WAAW,0BAA0B;IACtF;AAEF,SAAO;GACN;GACA,QAAQ,MAAM,KAAK,WAAW,UAAU;GACxC;;CAGF,MAAM,uBACL,WACA,gBACyC;EACzC,IAAI,UAAU;AACd,QAAM,gBAAgB,KAAK,IAAI,OAAO,QAAQ;GAC7C,MAAM,SAAS,MAAM,IACnB,WAAW,8BAA8B,CACzC,MAAM,cAAc,KAAK,UAAU,CACnC,MAAM,KAAK,sBAAsB,eAAe,CAAC,CACjD,kBAAkB;AACpB,aAAU,OAAO,OAAO,kBAAkB,EAAE,GAAG;AAC/C,OAAI,CAAC,QAAS;AACd,SAAM,KAAK,kCACV,KACA,WACA,eAAe,kBACf;IACA;AAEF,SAAO;GACN;GACA,QAAQ,MAAM,KAAK,WAAW,UAAU;GACxC;;CAGF,MAAM,oCACL,WACA,gBACA,gBACA,WAC+C;AAC/C,qBAAmB,gBAAgB,kBAAkB;EACrD,MAAM,YAAY,MAAM;EACxB,IAAI,UAAU;AACd,QAAM,gBAAgB,KAAK,IAAI,OAAO,QAAQ;GAC7C,MAAM,SAAS,MAAM,IACnB,WAAW,8BAA8B,CACzC,MAAM,cAAc,KAAK,UAAU,CACnC,MAAM,KAAK,sBAAsB,eAAe,CAAC,CACjD,MACA,GAAY,6BAA6B,IAAI,IAAI,UAAU,CAAC,cAAc,UAAU,GACpF,CACA,kBAAkB;AACpB,aAAU,OAAO,OAAO,kBAAkB,EAAE,GAAG;AAC/C,OAAI,CAAC,QAAS;AACd,SAAM,KAAK,kCACV,KACA,WACA,eAAe,kBACf;IACA;EACF,MAAM,iBAAiB,UAAU,QAAQ,MAAM,KAAK,iBAAiB,WAAW,UAAU;AAE1F,SAAO;GACN;GACA;GACA,QAAQ,WAAW,iBAAiB,OAAO,MAAM,KAAK,WAAW,UAAU;GAC3E;;CAGF,MAAM,cAAc,YAAgD;AACnE,SAAO,KAAK,iBAAiB,WAAW;;CAGzC,MAAM,qBAAqB,gBAAwB,WAAoC;EAQtF,MAAM,cAPa,MAAM,KAAK,GAC5B,WAAW,8BAA8B,CACzC,OAAO,aAAa,CACpB,MAAM,eAAe,KAAK,UAAU,CACpC,MAAM,mBAAmB,KAAK,eAAe,CAC7C,MAAM,cAAc,KAAK,UAAU,CACnC,SAAS,EACmB,KAAK,QAAQ,IAAI,WAAW;AAC1D,SAAO,KAAK,iBAAiB,WAAW;;CAGzC,MAAM,wBAAwB,gBAAyC;EACtE,IAAI,UAAU;AACd,SAAO,MAAM;GACZ,MAAM,aAAa,MAAM,KAAK,GAC5B,WAAW,8BAA8B,CACzC,OAAO,aAAa,CACpB,MAAM,eAAe,KAAK,UAAU,CACpC,MAAM,mBAAmB,KAAK,eAAe,CAC7C,QAAQ,cAAc,MAAM,CAC5B,MAAM,eAAe,CACrB,SAAS;AACX,OAAI,WAAW,WAAW,EAAG;AAE7B,cAAW,MAAM,KAAK,iBAAiB,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC;;AAEhF,SAAO;;CAGR,MAAM,6BAA6B,gBAAqD;AAQvF,UAPa,MAAM,KAAK,GACtB,WAAW,8BAA8B,CACzC,WAAW,CACX,MAAM,eAAe,KAAK,UAAU,CACpC,MAAM,mBAAmB,KAAK,eAAe,CAC7C,QAAQ,cAAc,MAAM,CAC5B,SAAS,EACC,KAAK,QAAQ,YAAY,IAAI,CAAC;;CAG3C,MAAM,iCAAiC,QAAgB,OAAgC;EACtF,MAAM,aAAa,KAAK,MAAM,MAAM;AACpC,MAAI,cAAc,EAAG,QAAO;EAE5B,MAAM,OAAO,MAAM,KAAK,GACtB,WAAW,2BAA2B,CACtC,SAAS,qCAAqC,SAC9C,KAAK,MAAM,gBAAgB,KAAK,eAAe,CAC/C,CACA,OAAO,OAAO,CACd,MAAM,gBAAgB,MAAM,KAAK,CACjC,MAAM,gBAAgB,KAAK,OAAO,CAClC,QAAQ,gBAAgB,MAAM,CAC9B,QAAQ,QAAQ,MAAM,CACtB,MAAM,WAAW,CACjB,SAAS;EAEX,IAAI,UAAU;AACd,OAAK,MAAM,WAAW,OACrB,KAAK,KAAK,QAAQ,IAAI,GAAG,EACzB,eACA,EAAE;GACF,MAAM,SAAS,MAAM,KAAK,GACxB,WAAW,sBAAsB,CACjC,MAAM,MAAM,MAAM,QAAQ,CAC1B,MAAM,cAAc,KAAK,OAAO,CAChC,MACA,GAAY,+GACZ,CACA,kBAAkB;AACpB,cAAW,OAAO,OAAO,kBAAkB,EAAE;;AAE9C,SAAO;;CAGR,MAAM,gCAAgC,QAAgB,OAAgC;EACrF,MAAM,aAAa,KAAK,MAAM,MAAM;AACpC,MAAI,cAAc,EAAG,QAAO;EAgB5B,MAAM,OAdO,MAAM,KAAK,GACtB,WAAW,2BAA2B,CACtC,UAAU,qCAAqC,SAC/C,KAAK,MAAM,gBAAgB,KAAK,eAAe,CAC/C,CACA,OAAO,OAAO,CACd,MAAM,gBAAgB,KAAK,OAAO,CAClC,SAAS,gBAAgB,MAAM,uBAAuB,CACtD,SAAS,gBAAgB,KAAK,eAAe,CAC7C,QAAQ,gBAAgB,MAAM,CAC9B,QAAQ,QAAQ,MAAM,CACtB,MAAM,WAAW,CACjB,SAAS,EAEM,KAAK,QAAQ,IAAI,GAAG;AACrC,MAAI,IAAI,WAAW,EAAG,QAAO;EAE7B,IAAI,UAAU;AACd,OAAK,MAAM,WAAW,OAAO,KAAK,eAAe,EAAE;GAClD,MAAM,SAAS,MAAM,KAAK,GACxB,WAAW,sBAAsB,CACjC,MAAM,MAAM,MAAM,QAAQ,CAC1B,MAAM,cAAc,KAAK,OAAO,CAChC,OAAO,OACP,GAAG,OACF,GACE,WAAW,mCAAmC,CAC9C,OAAO,eAAe,CACtB,SAAS,gBAAgB,KAAK,iCAAiC,CAC/D,SAAS,wBAAwB,MAAM,iCAAiC,CACxE,SAAS,kCAAkC,KAAK,eAAe,CACjE,CACD,CACA,kBAAkB;AACpB,cAAW,OAAO,OAAO,kBAAkB,EAAE;;AAE9C,SAAO;;CAGR,MAAM,oCAAoC,QAAgB,OAAgC;EACzF,MAAM,aAAa,KAAK,MAAM,MAAM;AACpC,MAAI,cAAc,EAAG,QAAO;EAE5B,MAAM,OAAO,MAAM,KAAK,GACtB,WAAW,2BAA2B,CACtC,UAAU,qCAAqC,SAC/C,KAAK,MAAM,gBAAgB,KAAK,eAAe,CAC/C,CACA,OAAO,OAAO,CACd,MAAM,gBAAgB,KAAK,OAAO,CAClC,SAAS,gBAAgB,MAAM,uBAAuB,CACtD,SAAS,gBAAgB,MAAM,eAAe,CAC9C,QAAQ,gBAAgB,MAAM,CAC9B,QAAQ,QAAQ,MAAM,CACtB,MAAM,WAAW,CACjB,SAAS;EAEX,IAAI,UAAU;AACd,OAAK,MAAM,WAAW,OACrB,KAAK,KAAK,QAAQ,IAAI,GAAG,EACzB,eACA,EAAE;GACF,MAAM,SAAS,MAAM,KAAK,GACxB,WAAW,sBAAsB,CACjC,MAAM,MAAM,MAAM,QAAQ,CAC1B,MAAM,cAAc,KAAK,OAAO,CAChC,OAAO,OACP,GAAG,OACF,GACE,WAAW,mCAAmC,CAC9C,OAAO,eAAe,CACtB,SAAS,gBAAgB,KAAK,iCAAiC,CAC/D,SAAS,wBAAwB,MAAM,iCAAiC,CACxE,SAAS,kCAAkC,MAAM,eAAe,CAClE,CACD,CACA,kBAAkB;AACpB,cAAW,OAAO,OAAO,kBAAkB,EAAE;;AAE9C,SAAO;;CAGR,MAAM,kBAAkB,OAAmE;EAC1F,MAAM,MAAM,MAAM,8BAAa,IAAI,MAAM,EAAC,aAAa;EACvD,MAAM,MAAM;GACX,YAAY,MAAM;GAClB,YAAY,MAAM;GAClB,WAAW,MAAM;GACjB,QAAQ,MAAM;GACd,gBAAgB,MAAM,iBAAiB;GACvC,YAAY,MAAM,aAAa;GAC/B,cAAc,MAAM,eAAe;GACnC,QAAQ,MAAM,UAAU;GACxB,sBAAsB,MAAM,sBAAsB;GAClD,qBAAqB,MAAM,qBAAqB;GAChD,iBAAiB,MAAM,iBAAiB;GACxC,YAAY;GACZ;AAED,QAAM,KAAK,GACT,WAAW,mCAAmC,CAC9C,OAAO,IAAI,CACX,YAAY,OACZ,GAAG,QAAQ;GAAC;GAAc;GAAc;GAAY,CAAC,CAAC,YAAY;GACjE,QAAQ,IAAI;GACZ,gBAAgB,IAAI;GACpB,YAAY,IAAI;GAChB,cAAc,IAAI;GAClB,QAAQ,IAAI;GACZ,sBAAsB,IAAI;GAC1B,qBAAqB,IAAI;GACzB,iBAAiB,IAAI;GACrB,YAAY,IAAI;GAChB,CAAC,CACF,CACA,SAAS;EAEX,MAAM,SAAS,MAAM,KAAK,gBAAgB,MAAM;AAChD,MAAI,CAAC,OACJ,OAAM,IAAI,MACT,4BAA4B,MAAM,UAAU,GAAG,MAAM,UAAU,GAAG,MAAM,SAAS,oBACjF;AAEF,SAAO;;CAGR,MAAM,uBACL,OACiC;AACjC,SAAO,KAAK,kBAAkB;GAC7B,WAAW,MAAM;GACjB,WAAW,MAAM;GACjB,UAAU,MAAM;GAChB,QAAQ;GACR,eAAe,MAAM;GACrB,WAAW,MAAM;GACjB,aAAa;GACb,QAAQ,MAAM;GACd,oBAAoB;GACpB,mBAAmB;GACnB,eAAe;GACf,WAAW,MAAM;GACjB,CAAC;;CAGH,MAAM,mCACL,OAC8C;EAC9C,MAAM,UAAkD;GACvD,QAAQ,MAAM;GACd,cAAc,MAAM;GACpB,QAAQ;GACR,sBAAsB,MAAM,sBAAsB;GAClD,qBAAqB,MAAM,qBAAqB;GAChD,iBAAiB,MAAM,iBAAiB;GACxC,YAAY,MAAM,8BAAa,IAAI,MAAM,EAAC,aAAa;GACvD;AACD,MAAI,MAAM,kBAAkB,OAAW,SAAQ,iBAAiB,MAAM;EAEtE,MAAM,SAAS,MAAM,KAAK,GACxB,YAAY,mCAAmC,CAC/C,IAAI,QAAQ,CACZ,MAAM,cAAc,KAAK,MAAM,UAAU,CACzC,MAAM,cAAc,KAAK,MAAM,UAAU,CACzC,MAAM,aAAa,KAAK,MAAM,SAAS,CACvC,MAAM,UAAU,KAAK,UAAU,CAC/B,MAAM,UAAU,KAAK,MAAM,SAAS,CACpC,kBAAkB;AAGpB,SAAO;GACN,WAHiB,OAAO,OAAO,kBAAkB,EAAE,GAAG;GAItD,QAAQ,MAAM,KAAK,gBAAgB,MAAM;GACzC;;CAGF,MAAM,gBACL,UACwC;EACxC,MAAM,MAAM,MAAM,KAAK,GACrB,WAAW,mCAAmC,CAC9C,WAAW,CACX,MAAM,cAAc,KAAK,SAAS,UAAU,CAC5C,MAAM,cAAc,KAAK,SAAS,UAAU,CAC5C,MAAM,aAAa,KAAK,SAAS,SAAS,CAC1C,kBAAkB;AAEpB,SAAO,MAAM,iBAAiB,IAAI,GAAG;;CAGtC,MAAM,kBAAkB,UAA0D;EACjF,MAAM,SAAS,MAAM,KAAK,GACxB,WAAW,mCAAmC,CAC9C,MAAM,cAAc,KAAK,SAAS,UAAU,CAC5C,MAAM,cAAc,KAAK,SAAS,UAAU,CAC5C,MAAM,aAAa,KAAK,SAAS,SAAS,CAC1C,kBAAkB;AACpB,SAAO,OAAO,OAAO,kBAAkB,EAAE;;CAG1C,MAAc,qBACb,aAGA,SAC4C;EAC5C,MAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,GAAG,QAAQ,SAAS,GAAG,EAAE,IAAI;EAC7D,IAAI,QAAQ,YAAY,KAAK,uBAAuB,CAAC,CACnD,QAAQ,QAAQ,MAAM,CACtB,MAAM,QAAQ,EAAE;AAElB,MAAI,QAAQ,QAAQ;GACnB,MAAM,EAAE,OAAO,aAAa,QAAQ,OAAO;AAC3C,WAAQ,MAAM,MAAM,QAAQ,KAAK,GAAG;;EAGrC,MAAM,OAAO,MAAM,MAAM,SAAS;EAClC,MAAM,QAAQ,KAAK,MAAM,GAAG,MAAM,CAAC,IAAI,iBAAiB;EACxD,MAAM,SAA2C,EAAE,OAAO;AAE1D,MAAI,KAAK,SAAS,SAAS,MAAM,SAAS,GAAG;GAC5C,MAAM,OAAO,MAAM,GAAG,GAAG;AACzB,UAAO,aAAa,aAAa,KAAK,WAAW,IAAI,KAAK,WAAW,GAAG;;AAGzE,SAAO;;CAGR,AAAQ,wBAAwB;AAC/B,SAAO,KAAK,GACV,WAAW,mCAAmC,CAC9C,UAAU,6BAA6B,SACvC,KACE,MAAM,gBAAgB,KAAK,eAAe,CAC1C,MAAM,gBAAgB,KAAK,uBAAuB,CACpD,CACA,OAAO,mBAAmB;;CAG7B,MAAc,iBAAiB,YAAgD;EAC9E,MAAM,mBAAmB,CAAC,GAAG,IAAI,IAAI,WAAW,CAAC;AACjD,MAAI,iBAAiB,WAAW,EAAG,QAAO;AAE1C,SAAO,gBAAgB,KAAK,IAAI,OAAO,QAAQ;GAC9C,IAAI,UAAU;AACd,QAAK,MAAM,kBAAkB,OAAO,kBAAkB,eAAe,EAAE;IACtE,MAAM,SAAS,MAAM,IACnB,WAAW,8BAA8B,CACzC,MAAM,cAAc,MAAM,eAAe,CACzC,kBAAkB;AACpB,eAAW,OAAO,OAAO,kBAAkB,EAAE;AAE7C,UAAM,IACJ,WAAW,sBAAsB,CACjC,MAAM,cAAc,MAAM,eAAe,CACzC,SAAS;;AAEZ,UAAO;IACN;;CAGH,MAAc,kCACb,IACA,WACA,YACgB;AAGhB,QAAM,GACJ,WAAW,sBAAsB,CACjC,MAAM,cAAc,KAAK,UAAU,CACnC,MAAM,cAAc,KAAK,WAAW,CACpC,SAAS;;CAGZ,MAAc,kBACb,IACA,WACA,YACA,aACA,KACgB;AAChB,MAAI,YAAY,WAAW,EAAG;EAE9B,MAAM,OAAO,YAAY,KAAK,gBAAgB;GAC7C,IAAI,MAAM;GACV,YAAY;GACZ;GACA,YAAY,WAAW;GACvB,YAAY,WAAW;GACvB,kBAAkB,WAAW,mBAAmB;GAChD,gBAAgB,WAAW;GAC3B,UAAU,WAAW;GACrB,UAAU,WAAW;GACrB,mBAAmB,WAAW;GAC9B,YAAY,WAAW,aAAa;GACpC,WAAW,WAAW,YAAY;GAClC,YAAY;GACZ,EAAE;AAEH,OAAK,MAAM,YAAY,OAAO,MAAM,6BAA6B,CAChE,OAAM,GAAG,WAAW,sBAAsB,CAAC,OAAO,SAAS,CAAC,SAAS;;CAIvE,MAAc,aACb,IACA,QACA,YACA,KACgB;EAChB,MAAM,MAAM,KAAK,eAAe,QAAQ,YAAY,IAAI;AAExD,QAAM,GACJ,WAAW,8BAA8B,CACzC,OAAO,IAAI,CACX,YAAY,OAAO,GAAG,OAAO,aAAa,CAAC,YAAY,KAAK,gBAAgB,IAAI,CAAC,CAAC,CAClF,SAAS;;CAGZ,MAAc,qBACb,IACA,KACmB;AAMnB,WALe,MAAM,GACnB,WAAW,8BAA8B,CACzC,OAAO,IAAI,CACX,YAAY,OAAO,GAAG,OAAO,aAAa,CAAC,WAAW,CAAC,CACvD,kBAAkB,EACL,4BAA4B,MAAM;;CAGlD,MAAc,yBACb,IACA,KACA,2BACmB;EACnB,MAAM,SAAS,MAAM,GACnB,YAAY,8BAA8B,CAC1C,IAAI,KAAK,gBAAgB,IAAI,CAAC,CAC9B,MAAM,cAAc,KAAK,IAAI,WAAW,CACxC,MAAM,sBAAsB,KAAK,0BAA0B,CAC3D,kBAAkB;AACpB,SAAO,OAAO,OAAO,kBAAkB,EAAE,GAAG;;CAG7C,MAAc,uBACb,IACA,KACA,gBACmB;EACnB,MAAM,SAAS,MAAM,GACnB,YAAY,8BAA8B,CAC1C,IAAI,KAAK,gBAAgB,IAAI,CAAC,CAC9B,MAAM,cAAc,KAAK,IAAI,WAAW,CACxC,MAAM,KAAK,sBAAsB,eAAe,CAAC,CACjD,kBAAkB;AACpB,SAAO,OAAO,OAAO,kBAAkB,EAAE,GAAG;;CAG7C,MAAc,gCACb,IACA,QACA,KACA,gBACmB;EACnB,MAAM,SAAS,MAAM,GACnB,YAAY,8BAA8B,CAC1C,IAAI,KAAK,yBAAyB,QAAQ,IAAI,CAAC,CAC/C,MAAM,cAAc,KAAK,IAAI,WAAW,CACxC,MAAM,KAAK,sBAAsB,eAAe,CAAC,CACjD,kBAAkB;AACpB,SAAO,OAAO,OAAO,kBAAkB,EAAE,GAAG;;CAG7C,AAAQ,sBAAsB,gBAAkC;AAC/D,UAAQ,OACP,GAAG,IAAI;GACN,GAAG,sBAAsB,KAAK,eAAe,kBAAkB;GAC/D,GAAG,uBAAuB,KAAK,eAAe,mBAAmB;GACjE,KAAK,yBAAyB,IAAI,cAAc,eAAe,UAAU;GACzE,KAAK,yBAAyB,IAAI,sBAAsB,eAAe,kBAAkB;GACzF,KAAK,yBAAyB,IAAI,qBAAqB,eAAe,gBAAgB;GACtF,KAAK,yBAAyB,IAAI,kBAAkB,eAAe,cAAc;GACjF,KAAK,yBAAyB,IAAI,eAAe,eAAe,WAAW;GAC3E,KAAK,yBAAyB,IAAI,qBAAqB,eAAe,gBAAgB;GACtF,KAAK,yBAAyB,IAAI,mBAAmB,eAAe,cAAc;GAClF,CAAC;;CAGJ,AAAQ,yBACP,IACA,QACA,OACC;AACD,SAAO,UAAU,OAAO,GAAG,QAAQ,MAAM,KAAK,GAAG,GAAG,QAAQ,KAAK,MAAM;;CAGxE,AAAQ,yBACP,IACA,QACA,OACC;AACD,SAAO,UAAU,OAAO,GAAG,QAAQ,MAAM,KAAK,GAAG,GAAG,QAAQ,KAAK,MAAM;;CAGxE,MAAc,iBAAiB,WAAmB,WAAqC;AAOtF,UANe,MAAM,GAAmB;;UAEhC,IAAI,IAAI,UAAU,CAAC;gBACb,UAAU;;IAEtB,QAAQ,KAAK,GAAG,EACJ,KAAK,SAAS;;CAG7B,AAAQ,eAAe,QAA+B,YAAoB,KAAa;AACtF,SAAO;GACN,YAAY,OAAO;GACnB,aAAa,OAAO;GACpB,iBAAiB,OAAO,kBAAkB;GAC1C,YAAY,OAAO,aAAa;GAChC,gBAAgB,OAAO;GACvB,QAAQ,OAAO,UAAU;GACzB,mBAAmB,OAAO,oBAAoB;GAC9C,cAAc,OAAO,eAAe;GACpC,eAAe,OAAO,gBAAgB;GACtC,gBAAgB,OAAO,iBAAiB;GACxC,sBAAsB,OAAO,sBAAsB;GACnD,oBAAoB,OAAO,oBAAoB;GAC/C,aAAa,OAAO,cAAc;GAClC,oBAAoB;GACpB,gBAAgB,OAAO,iBAAiB;GACxC,mBAAmB,OAAO,mBAAmB;GAC7C,gBAAgB,OAAO,iBAAiB;GACxC,oBAAoB,OAAO,qBAAqB;GAGhD,qBAAqB,OAAO,sBAAsB;GAClD,mBAAmB,OAAO,mBAAmB;GAC7C,iBAAiB;GACjB,YAAY;GACZ,YAAY;GACZ;;CAGF,AAAQ,wBAAwB,QAA+B,KAAa;AAC3E,SAAO;GACN,YAAY,OAAO;GACnB,aAAa,OAAO;GACpB,iBAAiB,OAAO,kBAAkB;GAC1C,YAAY,OAAO,aAAa;GAChC,gBAAgB,OAAO;GACvB,QAAQ,OAAO,UAAU;GACzB,mBAAmB,OAAO,oBAAoB;GAC9C,cAAc,OAAO,eAAe;GACpC,eAAe,OAAO,gBAAgB;GACtC,gBAAgB,OAAO,iBAAiB;GACxC,sBAAsB,OAAO,sBAAsB;GACnD,oBAAoB,OAAO,oBAAoB;GAC/C,aAAa,OAAO,cAAc;GAClC,oBAAoB,MAAM;GAC1B,gBAAgB,OAAO,iBAAiB;GACxC,mBAAmB,OAAO,mBAAmB;GAC7C,gBAAgB,OAAO,iBAAiB;GACxC,oBAAoB,OAAO,qBAAqB;GAChD,qBACC,OAAO,uBAAuB,OAAO,gBAAgB,WAAW;GACjE,mBAAmB,OAAO,mBAAmB;GAC7C,iBAAiB,OAAO,iBAAiB;GACzC,YAAY;GACZ,YAAY;GACZ;;CAGF,AAAQ,yBACP,QACA,KACoC;EACpC,MAAM,UAA6C;GAClD,aAAa,IAAI;GACjB,gBAAgB,IAAI;GACpB,qBAAqB,IAAI;GACzB,mBAAmB,IAAI;GACvB,iBAAiB,IAAI;GACrB,YAAY,IAAI;GAChB;AAED,MAAI,OAAO,mBAAmB,OAAW,SAAQ,kBAAkB,IAAI;AACvE,MAAI,OAAO,cAAc,OAAW,SAAQ,aAAa,IAAI;AAC7D,MAAI,OAAO,WAAW,OAAW,SAAQ,SAAS,IAAI;AACtD,MAAI,OAAO,qBAAqB,OAAW,SAAQ,oBAAoB,IAAI;AAC3E,MAAI,OAAO,gBAAgB,OAAW,SAAQ,eAAe,IAAI;AACjE,MAAI,OAAO,iBAAiB,OAAW,SAAQ,gBAAgB,IAAI;AACnE,MAAI,OAAO,kBAAkB,OAAW,SAAQ,iBAAiB,IAAI;AACrE,MAAI,OAAO,uBAAuB,OACjC,SAAQ,uBAAuB,IAAI;AAEpC,MAAI,OAAO,qBAAqB,OAAW,SAAQ,qBAAqB,IAAI;AAC5E,MAAI,OAAO,eAAe,OAAW,SAAQ,cAAc,IAAI;AAC/D,MAAI,OAAO,kBAAkB,OAAW,SAAQ,iBAAiB,IAAI;AACrE,MAAI,OAAO,oBAAoB,OAAW,SAAQ,oBAAoB,IAAI;AAC1E,MAAI,OAAO,kBAAkB,OAAW,SAAQ,iBAAiB,IAAI;AACrE,MAAI,OAAO,sBAAsB,OAChC,SAAQ,qBAAqB,IAAI;AAGlC,SAAO;;CAGR,AAAQ,gBACP,KACoC;AACpC,SAAO;GACN,aAAa,IAAI;GACjB,iBAAiB,IAAI;GACrB,YAAY,IAAI;GAChB,gBAAgB,IAAI;GACpB,QAAQ,IAAI;GACZ,mBAAmB,IAAI;GACvB,cAAc,IAAI;GAClB,eAAe,IAAI;GACnB,gBAAgB,IAAI;GACpB,sBAAsB,IAAI;GAC1B,oBAAoB,IAAI;GACxB,aAAa,IAAI;GACjB,oBAAoB,IAAI;GACxB,gBAAgB,IAAI;GACpB,mBAAmB,IAAI;GACvB,gBAAgB,IAAI;GACpB,oBAAoB,IAAI;GACxB,qBAAqB,IAAI;GACzB,mBAAmB,IAAI;GACvB,iBAAiB,IAAI;GACrB,YAAY,IAAI;GAChB,YAAY,IAAI;GAChB;;;AAIH,MAAM,qBAAqB;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAED,SAAS,YAAY,KAA4C;AAChE,QAAO;EACN,WAAW,IAAI;EACf,YAAY,IAAI;EAChB,gBAAgB,IAAI;EACpB,WAAW,IAAI;EACf,eAAe,IAAI;EACnB,QAAQ,IAAI;EACZ,kBAAkB,IAAI;EACtB,aAAa,IAAI;EACjB,cAAc,IAAI;EAClB,eAAe,IAAI;EACnB,oBAAoB,IAAI;EACxB,kBAAkB,IAAI;EACtB,YAAY,IAAI;EAChB,mBAAmB,IAAI;EACvB,eAAe,OAAO,IAAI,eAAe;EACzC,iBAAiB,IAAI;EACrB,eAAe,IAAI,mBAAmB,OAAO,OAAO,OAAO,IAAI,eAAe;EAC9E,mBAAmB,IAAI;EACvB,oBAAoB,IAAI;EACxB,iBAAiB,IAAI;EACrB,eAAe,IAAI;EACnB,WAAW,IAAI;EACf,WAAW,IAAI;EACf,WAAW,IAAI;EACf;;AAGF,SAAS,gBAAgB,KAAwD;AAChF,QAAO;EACN,IAAI,IAAI;EACR,WAAW,IAAI;EACf,YAAY,IAAI;EAChB,WAAW,IAAI;EACf,WAAW,IAAI;EACf,iBAAiB,OAAO,IAAI,iBAAiB;EAC7C,eAAe,IAAI;EACnB,SAAS,IAAI;EACb,UAAU,IAAI;EACd,iBAAiB,IAAI;EACrB,WAAW,IAAI;EACf,UAAU,IAAI;EACd,WAAW,IAAI;EACf;;AAGF,SAAS,iBAAiB,KAAuC;AAChE,QAAO;EACN,QAAQ,YAAY;GACnB,YAAY,IAAI;GAChB,aAAa,IAAI;GACjB,iBAAiB,IAAI;GACrB,YAAY,IAAI;GAChB,gBAAgB,IAAI;GACpB,QAAQ,IAAI;GACZ,mBAAmB,IAAI;GACvB,cAAc,IAAI;GAClB,eAAe,IAAI;GACnB,gBAAgB,IAAI;GACpB,sBAAsB,IAAI;GAC1B,oBAAoB,IAAI;GACxB,aAAa,IAAI;GACjB,oBAAoB,IAAI;GACxB,gBAAgB,IAAI;GACpB,mBAAmB,IAAI;GACvB,gBAAgB,IAAI;GACpB,oBAAoB,IAAI;GACxB,qBAAqB,IAAI;GACzB,mBAAmB,IAAI;GACvB,iBAAiB,IAAI;GACrB,YAAY,IAAI;GAChB,YAAY,IAAI;GAChB,YAAY,IAAI;GAChB,CAAC;EACF,YAAY,gBAAgB;GAC3B,IAAI,IAAI;GACR,YAAY,IAAI;GAChB,YAAY,IAAI;GAChB,YAAY,IAAI;GAChB,YAAY,IAAI;GAChB,kBAAkB,IAAI;GACtB,gBAAgB,IAAI;GACpB,UAAU,IAAI;GACd,UAAU,IAAI;GACd,mBAAmB,IAAI;GACvB,YAAY,IAAI;GAChB,WAAW,IAAI;GACf,YAAY,IAAI;GAChB,CAAC;EACF;;AAGF,SAAS,iBAAiB,KAAoE;AAC7F,QAAO;EACN,WAAW,IAAI;EACf,WAAW,IAAI;EACf,UAAU,IAAI;EACd,QAAQ,IAAI;EACZ,eAAe,OAAO,IAAI,eAAe;EACzC,WAAW,IAAI;EACf,aAAa,IAAI;EACjB,QAAQ,IAAI;EACZ,oBAAoB,OAAO,IAAI,qBAAqB;EACpD,mBAAmB,OAAO,IAAI,oBAAoB;EAClD,eAAe,IAAI;EACnB,WAAW,IAAI;EACf;;;;;AC7yCF,IAAa,gCAAb,cAAmD,MAAM;CACxD,YACC,SACA,AAAO,MACN;AACD,QAAM,QAAQ;EAFP;AAGP,OAAK,OAAO;;;AAUd,MAAM,sBAAsB,CAAC,SAAS,OAAO;AAC7C,MAAM,4BAA4B;CAAC;CAAQ;CAAS;CAAe;AAInE,eAAsB,4BACrB,IACA,gBAC2C;AAC3C,oBAAmB,gBAAgB,kBAAkB;CAErD,MAAM,OAAO,MAAM,GACjB,WAAW,iBAAiB,CAC5B,UAAU,uBAAuB,0BAA0B,+BAA+B,CAC1F,OAAO;EAAC;EAAuB;EAAuB;EAA4B,CAAC,CACnF,MAAM,4BAA4B,KAAK,eAAe,CACtD,SAAS;CAEX,MAAM,mBAA6C,EAAE;CACrD,MAAM,4BAAY,IAAI,KAAgC;AAEtD,MAAK,MAAM,OAAO,MAAM;AACvB,YAAU,IAAI,IAAI,MAAM,IAAI;AAC5B,MAAI,wBAAwB,IAAI,KAAK,EAAE;AACtC,sBAAmB,IAAI,MAAM,yBAAyB;AACtD,oBAAiB,KAAK;IAAE,MAAM,IAAI;IAAM,MAAM,IAAI;IAAM,CAAC;AACzD;;AAGD,MAAI,IAAI,SAAS,YAAY;AAC5B,sBAAmB,IAAI,MAAM,yBAAyB;GACtD,MAAM,YAAY,gCAAgC,IAAI,WAAW;AACjE,OAAI,UAAU,SAAS,EACtB,kBAAiB,KAAK;IACrB,MAAM,IAAI;IACV,MAAM;IACN,YAAY,EAAE,WAAW;IACzB,CAAC;;;AAKL,kBAAiB,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAAC;AAE7D,QAAO;EACN;EACA,mBAAmB,oBAAoB,QAAQ,SAAS;AACvD,OAAI,CAAC,UAAU,IAAI,KAAK,CAAE,QAAO;AACjC,sBAAmB,MAAM,iCAAiC;AAC1D,UAAO;IACN;EACF;;AAGF,SAAS,gCACR,eACiC;CACjC,MAAM,aAAa,gBAAgB,cAAc;AACjD,KAAI,CAACA,WAAS,WAAW,IAAI,CAAC,MAAM,QAAQ,WAAW,UAAU,CAAE,QAAO,EAAE;CAE5E,MAAM,YAA4C,EAAE;AACpD,MAAK,MAAM,YAAY,WAAW,WAAW;AAC5C,MAAI,CAACA,WAAS,SAAS,IAAI,SAAS,SAAS,QAAS;AACtD,MAAI,OAAO,SAAS,SAAS,SAAU;AACvC,qBAAmB,SAAS,MAAM,sCAAsC;AACxE,YAAU,KAAK;GAAE,MAAM,SAAS;GAAM,MAAM;GAAS,CAAC;;AAGvD,QAAO,UAAU,UAAU,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAAC;;AAGlE,SAAS,gBAAgB,eAAuC;AAC/D,KAAI,CAAC,cAAe,QAAO;AAC3B,KAAI;AACH,SAAO,KAAK,MAAM,cAAc;SACzB;AACP,QAAM,IAAI,8BACT,qFACA,8BACA;;;AAIH,SAAS,wBAAwB,OAA+C;AAC/E,QAAQ,0BAAgD,SAAS,MAAM;;AAGxE,SAASA,WAAS,OAAkD;AACnE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;;;;AC3G5E,MAAM,cAAc;AAkBpB,SAAgB,6BAA6B,EAC5C,QACA,QACsE;CACtE,MAAM,cAA+C,EAAE;CACvD,MAAM,uBAAO,IAAI,KAAa;AAE9B,MAAK,MAAM,SAAS,QAAQ;EAC3B,MAAM,QAAQ,KAAK,MAAM;AAEzB,MAAI,MAAM,SAAS,SAAS;AAC3B,iBAAc,aAAa,MAAM;IAChC,WAAW,MAAM;IACjB,WAAW,MAAM;IACjB,eAAe;IACf;IACA,cAAc;IACd,CAAC;AACF;;AAGD,MAAI,MAAM,SAAS,QAAQ;AAC1B,iBAAc,aAAa,MAAM;IAChC,WAAW,MAAM;IACjB,WAAW,MAAM;IACjB,eAAe;IACf;IACA,cAAc;IACd,CAAC;AACF;;AAGD,MAAI,MAAM,SAAS,YAAY;AAC9B,8BAA2B,aAAa,MAAM,MAAM,MAAM,OAAO,MAAM,YAAY,UAAU;AAC7F;;AAGD,MAAI,MAAM,SAAS,eAClB,gCAA+B,aAAa,MAAM,MAAM,MAAM,MAAM;;AAItE,QAAO;;AAGR,SAAS,2BACR,aACA,MACA,WACA,OACA,WACO;AACP,KAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,CAAC,MAAM,QAAQ,UAAU,CAAE;AAExD,MAAK,MAAM,CAAC,WAAW,SAAS,MAAM,SAAS,EAAE;AAChD,MAAI,CAACC,WAAS,KAAK,CAAE;AAErB,OAAK,MAAM,YAAY,WAAW;AACjC,OAAI,SAAS,SAAS,QAAS;AAE/B,iBAAc,aAAa,MAAM;IAChC;IACA,WAAW,GAAG,UAAU,GAAG,UAAU,IAAI,SAAS;IAClD,eAAe;IACf,OAAO,KAAK,SAAS;IACrB,cAAc;IACd,CAAC;;;;AAKL,SAAS,+BACR,aACA,MACA,WACA,OACO;AACP,KAAI,CAAC,MAAM,QAAQ,MAAM,CAAE;AAE3B,MAAK,MAAM,CAAC,YAAY,UAAU,MAAM,SAAS,EAAE;AAClD,MAAI,CAACA,WAAS,MAAM,IAAI,MAAM,UAAU,WAAW,CAACA,WAAS,MAAM,MAAM,CAAE;EAE3E,MAAM,WAAW,kBAAkB,MAAM,MAAM,SAAS;EACxD,MAAM,MAAM,yBAAyB,MAAM,OAAO,SAAS;AAC3D,MAAI,CAAC,IAAK;AAEV,mBAAiB,aAAa,MAAM;GACnC;GACA,WAAW,GAAG,UAAU,GAAG,WAAW,UAAU,IAAI;GACpD,eAAe;GACf,KAAK,cAAc;IAClB,IAAI,IAAI;IACR;IACA,UAAU,mBAAmB,MAAM,MAAM,SAAS;IAClD,cAAc;IACd,CAAC;GACF,CAAC;;;AAIJ,SAAS,cACR,aACA,MACA,OACO;CACP,MAAM,MAAM,aAAa,MAAM,OAAO,MAAM,aAAa;AACzD,KAAI,CAAC,IAAK;AAEV,kBAAiB,aAAa,MAAM;EACnC,WAAW,MAAM;EACjB,WAAW,MAAM;EACjB,eAAe,MAAM;EACrB;EACA,CAAC;;AAGH,SAAS,iBACR,aACA,MACA,OAMO;AACP,KAAI,CAAC,MAAM,IAAK;CAEhB,MAAM,aAA4C;EACjD,WAAW,MAAM;EACjB,WAAW,MAAM;EACjB,iBAAiB;EACjB,eAAe,MAAM;EACrB,SAAS,MAAM,IAAI;EACnB,UAAU,MAAM,IAAI;EACpB,iBAAiB,MAAM,IAAI;EAC3B,WAAW,MAAM,IAAI;EACrB,UAAU,MAAM,IAAI;EACpB;CAED,MAAM,MAAM;EACX,WAAW;EACX,WAAW;EACX,WAAW;EACX,WAAW;EACX,WAAW;EACX,WAAW;EACX,WAAW,WAAW;EACtB,CAAC,KAAK,KAAK;AAEZ,KAAI,KAAK,IAAI,IAAI,CAAE;AACnB,MAAK,IAAI,IAAI;AACb,aAAY,KAAK,WAAW;;AAG7B,SAAS,aAAa,OAAgB,cAAiD;AACtF,KAAI,OAAO,UAAU,UAAU;EAC9B,MAAM,KAAK,sBAAsB,MAAM;AACvC,SAAO,KAAK,cAAc;GAAE;GAAI,UAAU;GAAS,UAAU;GAAM;GAAc,CAAC,GAAG;;AAGtF,KAAI,CAACA,WAAS,MAAM,CAAE,QAAO;CAE7B,MAAM,WAAW,kBAAkB,MAAM,SAAS;CAClD,MAAM,KAAK,aAAa,UAAU,sBAAsB,MAAM,GAAG,GAAG,kBAAkB,MAAM,GAAG;AAC/F,KAAI,CAAC,GAAI,QAAO;AAEhB,QAAO,cAAc;EACpB;EACA;EACA,UAAU,mBAAmB,MAAM,SAAS;EAC5C;EACA,CAAC;;AAGH,SAAS,cAAc,OAKH;CACnB,MAAM,WAAW,kBAAkB,MAAM,SAAS;AAClD,KAAI,aAAa,WAAY,QAAO;AAEpC,QAAO;EACN,SAAS,aAAa,UAAU,MAAM,KAAK;EAC3C;EACA,iBAAiB,MAAM;EACvB,WAAW,kBAAkB,MAAM,SAAS,IAAI,MAAM;EACtD,UAAU,MAAM;EAChB;;AAGF,SAAS,yBACR,OACA,UAC4C;CAC5C,MAAM,cAAc,aAAa,UAAU,wBAAwB;CACnE,MAAM,MAAM,YAAY,MAAM,KAAK;AACnC,KAAI,IAAK,QAAO;EAAE,KAAK;EAAQ,IAAI;EAAK;CAExC,MAAM,KAAK,YAAY,MAAM,GAAG;AAChC,KAAI,GAAI,QAAO;EAAE,KAAK;EAAM;EAAI;AAEhC,QAAO;;AAGR,SAAS,kBAAkB,OAAwB;AAElD,QADiBC,aAAW,MAAM,EAAE,MAAM,IACvB;;AAGpB,SAAS,sBAAsB,OAA+B;CAC7D,MAAM,KAAK,kBAAkB,MAAM;AACnC,KAAI,CAAC,GAAI,QAAO;AAChB,QAAO,GAAG,SAAS,IAAI,GAAG,OAAO;;AAGlC,SAAS,kBAAkB,OAA+B;AACzD,KAAI,OAAO,UAAU,SAAU,QAAO;CACtC,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,CAAC,QAAS,QAAO;AACrB,KAAI,YAAY,KAAK,QAAQ,CAAE,QAAO;AACtC,KAAI,QAAQ,WAAW,sBAAsB,CAAE,QAAO;AACtD,QAAO;;AAGR,SAAS,mBAAmB,OAA+B;AAC1D,KAAI,OAAO,UAAU,SAAU,QAAO;CACtC,MAAM,aAAa,cAAc,MAAM;AACvC,QAAO,WAAW,SAAS,IAAI,GAAG,aAAa;;AAGhD,SAAS,kBAAkB,UAA2C;AACrE,KAAI,CAAC,SAAU,QAAO;AACtB,KAAI,SAAS,WAAW,SAAS,CAAE,QAAO;AAC1C,KAAI,SAAS,WAAW,SAAS,CAAE,QAAO;AAC1C,KAAI,SAAS,WAAW,SAAS,CAAE,QAAO;AAC1C,KAAI,SAAS,WAAW,QAAQ,IAAI,SAAS,WAAW,oBAAoB,CAAE,QAAO;AACrF,KAAI,SAAS,WAAW,QAAQ,CAAE,QAAO;AACzC,KAAI,eAAe,SAAS,CAAE,QAAO;AACrC,KAAI,cAAc,SAAS,CAAE,QAAO;AACpC,QAAO;;AAGR,SAAS,eAAe,UAA2B;AAClD,QACC,aAAa,qBACb,aAAa,wBACb,aAAa,qBACb,aAAa,8BACb,aAAa,mCACb,SAAS,WAAW,iDAAiD;;AAIvE,SAAS,cAAc,UAA2B;AACjD,QACC,aAAa,qBACb,aAAa,sBACb,aAAa,uBACb,aAAa,iCACb,aAAa,kCACb,aAAa;;AAIf,SAASA,aAAW,OAA+B;AAClD,QAAO,OAAO,UAAU,WAAW,QAAQ;;AAG5C,SAASD,WAAS,OAAkD;AACnE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;;;;AC5S5E,MAAa,sCAAsC,CAAC,WAAW,gBAAgB;AAmB/E,SAAgB,gCAAgC,OAAgD;AAC/F,QAAO,WAAW,MAAM,eAAe,GAAG,MAAM,UAAU,GAAG,MAAM;;;;;ACApE,MAAa,gCAAgC;AAE7C,MAAM,yBAAyB;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAqBD,eAAsB,+BACrB,IACA,gBACA,WACA,gBACgD;AAChD,oBAAmB,gBAAgB,kBAAkB;CACrD,MAAM,YAAY,kBAAmB,MAAM,4BAA4B,IAAI,eAAe;CAC1F,MAAM,MAAM,MAAM,eAAe,IAAI,gBAAgB,WAAW,CAC/D,GAAG,UAAU,iBAAiB,KAAK,UAAU,MAAM,KAAK,EACxD,GAAG,UAAU,kBACb,CAAC;AAEF,KAAI,CAAC,IAAK,QAAO;EAAE,SAAS;EAAO,OAAO;EAAqB;CAE/D,MAAM,cAAc,YACnB,KACA,UAAU,iBAAiB,KAAK,UAAU,MAAM,KAAK,CACrD;CACD,MAAM,cAAc,eAAe,KAAK,UAAU,kBAAkB;CACpE,MAAM,cAAc,6BAA6B;EAChD,QAAQ,UAAU;EAClB,MAAM;EACN,CAAC;CACF,MAAM,oBAAoB,mBAAmB,IAAI,iBAAiB;CAQlE,MAAM,YAAyC,CAC9C;EACC,QAAQ,mBAAmB;GAC1B;GACA;GACA;GACA,eAAe;GACf,YAAY;GACZ,mBAfwB,MAAM,uBAAuB;IACvD;IACA,eAAe;IACf,YAAY;IACZ,QAAQ,UAAU;IAClB,MAAM;IACN,CAAC;GAUC,CAAC;EACF;EACA,QAAQ,UAAU;EAClB,CACD;CAED,MAAM,kBAAkB,mBAAmB,IAAI,kBAAkB;AACjE,KAAI,iBAAiB;EACpB,MAAM,uBAAuB,mBAAmB;GAC/C;GACA;GACA;GACA,eAAe;GACf,YAAY;GACZ,CAAC;EACF,MAAM,iBAAiB,MAAM,gBAAgB,IAAI,gBAAgB;AACjE,MAAI,CAAC,eACJ,QAAO;GACN,SAAS;GACT,OAAO;GACP,QAAQ;GACR;GACA;AAEF,MAAI,CAAC,eAAe,QACnB,QAAO;GACN,SAAS;GACT,OAAO;GACP,QAAQ;GACR;GACA;EAEF,MAAM,WAAW,eAAe;AAChC,MAAI,SAAS,eAAe,kBAAkB,SAAS,YAAY,IAAI,GACtE,QAAO;GACN,SAAS;GACT,OAAO;GACP,QAAQ;GACR;GACA;EAGF,MAAM,eAAe,sBAAsB,SAAS,KAAK;EACzD,MAAM,mBAAmB;GAAE,GAAG;GAAa,GAAG;GAAc;EAC5D,MAAM,mBAAmB;GACxB,GAAG;GACH,GAAG,mBAAmB,cAAc,UAAU,kBAAkB;GAChE;EACD,MAAM,mBACL,mBAAmB,SAAS,KAAK,MAAM,IAAI,mBAAmB,IAAI,KAAK;EACxE,MAAM,mBAAmB,MAAM,uBAAuB;GACrD;GACA,eAAe;GACf,YAAY;GACZ,QAAQ,UAAU;GAClB,MAAM;GACN,CAAC;AACF,YAAU,KAAK;GACd,QAAQ,mBAAmB;IAC1B;IACA;IACA,aAAa;IACb,eAAe;IACf,YAAY;IACZ,aAAa;IACb,mBAAmB;IACnB,CAAC;GACF,aAAa,6BAA6B;IACzC,QAAQ,UAAU;IAClB,MAAM;IACN,CAAC;GACF,QAAQ,UAAU;GAClB,CAAC;;AAGH,QAAO;EACN,SAAS;EACT;EACA;;AAUF,eAAe,eACd,IACA,gBACA,WACA,YAC0C;CAC1C,MAAM,YAAY,oBAAoB,eAAe;CAErD,MAAM,aADU,cAAc,CAAC,GAAG,wBAAwB,GAAG,WAAW,CAAC,CAC9C,KAAK,WAAW,IAAI,IAAI,OAAO,CAAC;AAQ3D,SAPe,MAAM,GAA4B;WACvC,IAAI,KAAK,YAAY,GAAG,KAAK,CAAC;SAChC,IAAI,IAAI,UAAU,CAAC;eACb,UAAU;;GAEtB,QAAQ,GAAG,EAEC,KAAK,MAAM;;AAG1B,eAAe,gBACd,IACA,YACwF;CACxF,MAAM,MAAM,MAAM,GAChB,WAAW,YAAY,CACvB,OAAO;EAAC;EAAM;EAAc;EAAY;EAAO,CAAC,CAChD,MAAM,MAAM,KAAK,WAAW,CAC5B,kBAAkB;AACpB,KAAI,CAAC,IAAK,QAAO;CACjB,MAAM,OAAO,kBAAkB,IAAI,KAAK;AACxC,KAAI,CAAC,KAAM,QAAO,EAAE,SAAS,OAAO;AACpC,QAAO;EACN,SAAS;EACT,UAAU;GACT,IAAI,IAAI;GACR,YAAY,IAAI;GAChB,SAAS,IAAI;GACb;GACA;EACD;;AAGF,SAAS,mBAAmB,OAQF;CACzB,MAAM,EAAE,gBAAgB,KAAK,aAAa,eAAe,eAAe;CACxE,MAAM,YAAY,WAAW,IAAI,GAAG,IAAI;CACxC,MAAM,cAAc,MAAM,eAAe,mBAAmB,IAAI,KAAK;CACrE,MAAM,SAAgC;EACrC,WAAW,gCAAgC;GAC1C;GACA;GACA;GACA,CAAC;EACF,YAAY;EACZ;EACA;EACA;EACA,QAAQ,mBAAmB,IAAI,OAAO;EACtC,kBAAkB,mBAAmB,IAAI,kBAAkB;EAC3D;EACA,cAAc,mBAAmB,aAAa,aAAa,UAAU;EACrE,eAAe,mBAAmB,IAAI,OAAO;EAC7C,oBAAoB,mBAAmB,IAAI,aAAa;EACxD,kBAAkB,mBAAmB,IAAI,WAAW;EACpD;EACA,eAAe;EACf,iBAAiB,mBAAmB,IAAI,WAAW;EACnD,eAAe,WAAW,IAAI,QAAQ;EACtC;AACD,KAAI,MAAM,sBAAsB,OAAW,QAAO,oBAAoB,MAAM;AAC5E,QAAO;;AAGR,eAAe,uBAAuB,OAMlB;AACnB,QAAO,WACN,cAAc;EACb,eAAe;EACf,gBAAgB,MAAM;EACtB,eAAe,MAAM;EACrB,QAAQ,2BAA2B,MAAM,OAAO;EAChD,QAAQ,uBAAuB,MAAM,MAAM,MAAM,OAAO;EACxD,YAAY,MAAM,kBAAkB,kBAAkB,MAAM,aAAa;EACzE,CAAC,CACF;;AAGF,SAAS,2BACR,QAC4B;AAC5B,QAAO,OACL,KAAK,UAAU;AACf,MAAI,MAAM,SAAS,WAAY,QAAO;GAAE,MAAM,MAAM;GAAM,MAAM,MAAM;GAAM;AAC5E,SAAO;GACN,MAAM,MAAM;GACZ,MAAM,MAAM;GACZ,YAAY,MAAM,YAAY,aAAa,EAAE,EAC3C,KAAK,cAAc;IAAE,MAAM,SAAS;IAAM,MAAM,SAAS;IAAM,EAAE,CACjE,UAAU,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAAC;GAClD;GACA,CACD,UAAU,GAAG,MAAM,OAAO,EAAE,KAAK,CAAC,cAAc,OAAO,EAAE,KAAK,CAAC,CAAC;;AAGnE,SAAS,uBACR,MACA,QAC0B;CAC1B,MAAM,YAAqC,EAAE;AAC7C,MAAK,MAAM,SAAS,OACnB,WAAU,MAAM,QAAQ,OAAO,OAAO,MAAM,MAAM,KAAK,GAAG,KAAK,MAAM,QAAQ;AAE9E,QAAO;;AAGR,SAAS,cAAc,OAAwB;AAC9C,QAAO,KAAK,UAAU,aAAa,MAAM,CAAC;;AAG3C,SAAS,aAAa,OAAyB;AAC9C,KAAI,UAAU,OAAW,QAAO;AAChC,KAAI,OAAO,UAAU,SAAU,QAAO,MAAM,UAAU;AACtD,KAAI,OAAO,UAAU,SAAU,QAAO,OAAO,SAAS,MAAM,GAAG,QAAQ;AACvE,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,MAAM,KAAK,SAAS,aAAa,KAAK,CAAC;AACxE,KAAI,CAAC,SAAS,MAAM,CAAE,QAAO;CAE7B,MAAM,YAAqC,EAAE;AAC7C,MAAK,MAAM,OAAO,OAAO,KAAK,MAAM,CAAC,UAAU,CAC9C,WAAU,OAAO,aAAa,MAAM,KAAK;AAE1C,QAAO;;AAGR,SAAS,YACR,KACA,YAC0B;CAC1B,MAAM,OAAgC,EAAE;AACxC,MAAK,MAAM,aAAa,WACvB,MAAK,aAAa,iBAAiB,IAAI,cAAc,KAAK;AAE3D,QAAO;;AAGR,SAAS,eACR,KACA,YAC0B;CAC1B,MAAM,OAAgC,EAAE;AACxC,MAAK,MAAM,aAAa,WACvB,MAAK,aAAa,IAAI,cAAc;AAErC,QAAO;;AAGR,SAAS,mBACR,KACA,YAC0B;CAC1B,MAAM,OAAgC,EAAE;AACxC,MAAK,MAAM,aAAa,WACvB,KAAI,OAAO,OAAO,KAAK,UAAU,CAAE,MAAK,aAAa,IAAI;AAE1D,QAAO;;AAGR,SAAS,cAAc,SAAsC;CAC5D,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC;AACpC,MAAK,MAAM,UAAU,OAAQ,oBAAmB,QAAQ,6BAA6B;AACrF,QAAO;;AAGR,SAAS,oBAAoB,gBAAgC;AAC5D,oBAAmB,gBAAgB,kBAAkB;AACrD,QAAO,MAAM;;AAGd,SAAS,iBAAiB,OAAyB;AAClD,KAAI,OAAO,UAAU,aAAa,MAAM,WAAW,IAAI,IAAI,MAAM,WAAW,IAAI,EAC/E,KAAI;AACH,SAAO,KAAK,MAAM,MAAM;SACjB;AACP,SAAO;;AAGT,QAAO;;AAGR,SAAS,kBAAkB,OAAgD;AAC1E,KAAI,OAAO,UAAU,SACpB,KAAI;EACH,MAAM,SAAkB,KAAK,MAAM,MAAM;AACzC,SAAO,SAAS,OAAO,GAAG,SAAS;SAC5B;AACP,SAAO;;AAGT,QAAO,SAAS,MAAM,GAAG,QAAQ;;AAGlC,SAAS,sBAAsB,MAAwD;CACtF,MAAM,WAAoC,EAAE;AAC5C,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,CAC9C,KAAI,CAAC,IAAI,WAAW,IAAI,CAAE,UAAS,OAAO;AAE3C,QAAO;;AAGR,SAAS,mBACR,aACA,aACA,WACgB;AAChB,MAAK,MAAM,aAAa,CAAC,SAAS,OAAO,EAAW;EACnD,MAAM,QAAQ,YAAY;AAC1B,MAAI,OAAO,UAAU,YAAY,MAAM,MAAM,CAAE,QAAO;;AAEvD,QAAO,eAAe;;AAGvB,SAAS,WAAW,OAA+B;AAClD,QAAO,OAAO,UAAU,WAAW,QAAQ;;AAG5C,SAAS,mBAAmB,OAA+B;AAC1D,QAAO,UAAU,QAAQ,UAAU,SAAY,OAAO,WAAW,MAAM;;AAGxE,SAAS,WAAW,OAA+B;AAClD,KAAI,OAAO,UAAU,YAAY,OAAO,SAAS,MAAM,CAAE,QAAO;AAChE,KAAI,OAAO,UAAU,SAAU,QAAO,OAAO,MAAM;AACnD,KAAI,OAAO,UAAU,YAAY,OAAO;EACvC,MAAM,SAAS,OAAO,MAAM;AAC5B,SAAO,OAAO,SAAS,OAAO,GAAG,SAAS;;AAE3C,QAAO;;AAGR,SAAS,SAAS,OAAkD;AACnE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;;;;ACra5E,MAAa,iCAAiC;AAC9C,MAAa,uCAAuC;AAEpD,MAAM,0BAA0B,OAAO,IAAI,iCAAiC;AAC5E,MAAM,qCAAqC,OAAO,IAAI,oCAAoC;AAC1F,MAAM,qCAAqC;AAuB3C,MAAM,cAA8C;CACnD,SAAS;CACT,sBAAsB;CACtB,oBAAoB;CACpB,mBAAmB;CACnB;AAED,eAAsB,yBACrB,IACA,gBACA,WAC0C;AAC1C,oBAAmB,gBAAgB,kBAAkB;AACrD,QAAO,+BAA+B,sBACrC,qBAAqB,gBAAgB,iBACpC,iCAAiC,IAAI,gBAAgB,UAAU,CAC/D,CACD;;AAGF,eAAe,iCACd,IACA,gBACA,WAC0C;AAC1C,KAAI;EACH,IAAI,iBAAwD;AAC5D,OAAK,IAAI,UAAU,GAAG,UAAU,oCAAoC,WAAW;GAC9E,MAAM,SAAS,MAAM,gCAAgC,IAAI,gBAAgB,UAAU;AACnF,OAAI,OAAO,cAAc,oCAAqC,QAAO;AACrE,oBAAiB;;AAGlB,SAAO,uBAAuB,IAAI,gBAAgB;GACjD,sBAAsB,gBAAgB,wBAAwB;GAC9D,oBAAoB,gBAAgB,sBAAsB;GAC1D,CAAC;UACM,OAAO;AACf,UAAQ,MAAM,mCAAmC,eAAe,GAAG,UAAU,IAAI,MAAM;AACvF,QAAM,2CACL,IACA,gBACA,8BACA;AACD,SAAO;GACN,SAAS;GACT,sBAAsB;GACtB,oBAAoB;GACpB,mBAAmB;GACnB,WAAW;GACX;;;AAIH,eAAe,gCACd,IACA,gBACA,WAC0C;CAC1C,MAAM,OAAO,IAAI,qBAAqB,GAAG;CACzC,MAAM,sBAAsB,MAAM,qCACjC,IACA,gBACA,UACA;CACD,MAAM,kBAAkB,MAAM,+BAA+B,IAAI,gBAAgB,UAAU;AAC3F,KAAI,CAAC,gBAAgB,QACpB,QAAO,oBAAoB,IAAI,gBAAgB,gBAAgB;AAGhE,KAAI,CAAE,MAAM,wBAAwB,IAAI,eAAe,EAAG;EACzD,MAAM,qBAAqB,MAAM,KAAK,qBAAqB,gBAAgB,UAAU;AACrF,SAAO;GAAE,GAAG;GAAa;GAAoB;;CAG9C,IAAI,uBAAuB;AAC3B,MAAK,MAAM,YAAY,gBAAgB,WAAW;AAMjD,MAAI,EALW,MAAM,KAAK,uBACzB,SAAS,QACT,SAAS,aACT,oBAAoB,IAAI,SAAS,OAAO,UAAU,IAAI,KACtD,EACW,SACX,QAAO,yBAAyB;GAC/B;GACA,oBAAoB;GACpB,CAAC;AAEH;;AAED,KAAI,CAAE,MAAM,wBAAwB,IAAI,eAAe,EAAG;EACzD,MAAM,qBAAqB,MAAM,KAAK,qBAAqB,gBAAgB,UAAU;AACrF,SAAO;GAAE,GAAG;GAAa;GAAoB;;CAG9C,MAAM,qBAAqB,IAAI,IAC9B,gBAAgB,UAAU,KAAK,aAAa,SAAS,OAAO,UAAU,CACtE;CACD,MAAM,mBAAmB,oCAAoC,KAAK,kBACjE,gCAAgC;EAAE;EAAgB;EAAW;EAAe,CAAC,CAC7E,CAAC,QAAQ,cAAc,CAAC,mBAAmB,IAAI,UAAU,CAAC;CAC3D,IAAI,qBAAqB;AACzB,MAAK,MAAM,aAAa,kBAAkB;EACzC,MAAM,qBAAqB,oBAAoB,IAAI,UAAU,IAAI;AACjE,MAAI,uBAAuB,KAAM;EAEjC,MAAM,SAAS,MAAM,KAAK,sBAAsB,WAAW,mBAAmB;AAC9E,MAAI,OAAO,SAAS;AACnB;AACA;;AAED,MAAI,OAAO,OACV,QAAO,yBAAyB;GAC/B;GACA;GACA,CAAC;;AAIJ,QAAO;EACN,SAAS;EACT;EACA;EACA,mBAAmB;EACnB;;AAGF,eAAe,qCACd,IACA,gBACA,WACsC;CACtC,MAAM,8BAAc,IAAI,KAA4B;CACpD,MAAM,aAAa,oCAAoC,KAAK,kBAC3D,gCAAgC;EAC/B;EACA;EACA;EACA,CAAC,CACF;AACD,MAAK,MAAM,aAAa,WACvB,aAAY,IAAI,WAAW,KAAK;CAGjC,MAAM,OAAO,MAAM,GACjB,WAAW,8BAA8B,CACzC,OAAO,CAAC,cAAc,qBAAqB,CAAC,CAC5C,MAAM,cAAc,MAAM,WAAW,CACrC,SAAS;AACX,MAAK,MAAM,OAAO,KACjB,aAAY,IAAI,IAAI,YAAY,IAAI,mBAAmB;AAGxD,QAAO;;AAGR,eAAe,uBACd,IACA,gBACA,QAC0C;AAC1C,OAAM,2CACL,IACA,gBACA,oCACA;AACD,QAAO;EACN,SAAS;EACT,sBAAsB,OAAO;EAC7B,oBAAoB,OAAO;EAC3B,mBAAmB;EACnB,WAAW;EACX;;AAGF,SAAS,yBACR,QACiC;AACjC,QAAO;EACN,SAAS;EACT,sBAAsB,OAAO;EAC7B,oBAAoB,OAAO;EAC3B,mBAAmB;EACnB,WAAW;EACX;;AAGF,eAAe,wBACd,IACA,gBACmB;AAMnB,QALY,MAAM,GAChB,WAAW,sBAAsB,CACjC,OAAO,KAAK,CACZ,MAAM,QAAQ,KAAK,eAAe,CAClC,kBAAkB,KACL;;AAGhB,eAAsB,wBACrB,IACA,gBACA,WAC0C;AAC1C,oBAAmB,gBAAgB,kBAAkB;AACrD,QAAO,+BAA+B,sBACrC,qBAAqB,gBAAgB,iBACpC,gCAAgC,IAAI,gBAAgB,UAAU,CAC9D,CACD;;AAGF,eAAe,gCACd,IACA,gBACA,WAC0C;AAC1C,KAAI;EACH,MAAM,qBAAqB,MAAM,IAAI,qBAAqB,GAAG,CAAC,qBAC7D,gBACA,UACA;AACD,SAAO;GAAE,GAAG;GAAa;GAAoB;UACrC,OAAO;AACf,UAAQ,MACP,4CAA4C,eAAe,GAAG,UAAU,IACxE,MACA;AACD,QAAM,2CACL,IACA,gBACA,6BACA;AACD,SAAO;GACN,SAAS;GACT,sBAAsB;GACtB,oBAAoB;GACpB,mBAAmB;GACnB,WAAW;GACX;;;AAIH,eAAsB,kCACrB,IACA,gBAC0C;AAC1C,oBAAmB,gBAAgB,kBAAkB;AACrD,QAAO,+BAA+B,sBACrC,0CAA0C,IAAI,eAAe,CAC7D;;AAGF,eAAe,0CACd,IACA,gBAC0C;AAC1C,KAAI;EACH,MAAM,OAAO,IAAI,qBAAqB,GAAG;EACzC,MAAM,qBAAqB,MAAM,KAAK,wBAAwB,eAAe;AAC7E,QAAM,KAAK,kBAAkB;GAC5B,WAAW;GACX,WAAW;GACX,UAAU;GACV,CAAC;AACF,SAAO;GAAE,GAAG;GAAa;GAAoB;UACrC,OAAO;AACf,UAAQ,MAAM,uDAAuD,eAAe,IAAI,MAAM;AAC9F,MAAI;AACH,SAAM,IAAI,qBAAqB,GAAG,CAAC,kBAAkB;IACpD,WAAW;IACX,WAAW;IACX,UAAU;IACV,CAAC;WACM,aAAa;AACrB,WAAQ,MACP,qEAAqE,eAAe,IACpF,YACA;;AAEF,SAAO;GACN,SAAS;GACT,sBAAsB;GACtB,oBAAoB;GACpB,mBAAmB;GACnB,WAAW;GACX;;;AAIH,eAAsB,mCACrB,IACA,gBACA,WACgB;CAChB,MAAM,SAAS,MAAM,yBAAyB,IAAI,gBAAgB,UAAU;AAC5E,KAAI,CAAC,OAAO,QACX,SAAQ,MACP,mCAAmC,eAAe,GAAG,UAAU,iBAAiB,OAAO,YACvF;;AAIH,eAAsB,qCACrB,IACA,gBACA,eACgB;AAChB,oBAAmB,gBAAgB,kBAAkB;CACrD,MAAM,OAAO,IAAI,qBAAqB,GAAG;CACzC,MAAM,WAAW;EAChB,WAAW;EACX,WAAW;EACX,UAAU;EACV;CACD,MAAM,WAAW,MAAM,KAAK,gBAAgB,SAAS;AACrD,OAAM,KAAK,kBAAkB;EAC5B,GAAG;EACH,QAAQ;EACR,eAAe,UAAU,iBAAiB;EAC1C,WAAW,UAAU,aAAa;EAClC,aAAa,UAAU,eAAe;EACtC,QAAQ,UAAU,UAAU;EAC5B,oBAAoB,UAAU,sBAAsB;EACpD,mBAAmB,UAAU,qBAAqB;EAClD;EACA,CAAC;;AAGH,eAAsB,qCACrB,IACA,gBACA,kBACA,kBACA,aACoB;AACpB,KAAI,CAAC,eAAe,IAAI,CAAC,eAAe,CAAC,iBAAkB,QAAO,EAAE;AAEpE,oBAAmB,gBAAgB,kBAAkB;CACrD,MAAM,aAAa,MAAM,GACvB,WAAW,sBAAsB,CACjC,OAAO,KAAK,CACZ,MAAM,QAAQ,KAAK,eAAe,CAClC,kBAAkB;AACpB,KAAI,CAAC,WAAY,QAAO,EAAE;CAS1B,MAAM,+BAPS,MAAM,GACnB,WAAW,iBAAiB,CAC5B,OAAO,OAAO,CACd,MAAM,iBAAiB,KAAK,WAAW,GAAG,CAC1C,MAAM,gBAAgB,KAAK,EAAE,CAC7B,SAAS,EAGT,QAAQ,UAAU,MAAM,QAAQ,YAAY,CAC5C,KAAK,UAAU,MAAM,KAAK;AAC5B,KAAI,4BAA4B,WAAW,EAAG,QAAO,EAAE;CAEvD,MAAM,cAAc,MAAM,4BAA4B,IAAI,eAAe;CACzE,MAAM,qBAAqB,IAAI,IAAI,CAClC,GAAG,YAAY,iBAAiB,KAAK,UAAU,MAAM,KAAK,EAC1D,GAAG,YAAY,kBACf,CAAC;AACF,KAAI,CAAC,4BAA4B,MAAM,SAAS,mBAAmB,IAAI,KAAK,CAAC,CAAE,QAAO,EAAE;CAExF,MAAM,YAAY,MAAM;AASxB,SARa,MAAM,GAAmB;;SAE9B,IAAI,IAAI,UAAU,CAAC;8BACE,iBAAiB;cACjC,iBAAiB;;GAE5B,QAAQ,GAAG,EAED,KAAK,KAAK,QAAQ,IAAI,GAAG;;AAGtC,eAAe,oBACd,IACA,gBACA,QAC0C;CAC1C,MAAM,OAAO,IAAI,qBAAqB,GAAG;AACzC,KAAI,OAAO,OACV,OAAM,KAAK,oBAAoB;EAC9B,GAAG,OAAO;EACV,oBAAoB;EACpB,eAAe,OAAO;EACtB,CAAC;AAEH,OAAM,qCAAqC,IAAI,gBAAgB,OAAO,MAAM;AAC5E,QAAO;EACN,SAAS;EACT,sBAAsB;EACtB,oBAAoB;EACpB,mBAAmB,OAAO,SAAS,IAAI;EACvC,WAAW,OAAO;EAClB;;AAGF,eAAsB,2CACrB,IACA,gBACA,eACmB;AACnB,KAAI;AACH,QAAM,qCAAqC,IAAI,gBAAgB,cAAc;AAC7E,SAAO;UACC,OAAO;AACf,UAAQ,MAAM,gCAAgC,eAAe,UAAU,MAAM;AAC7E,SAAO;;;AAIT,eAAe,qBACd,gBACA,WACA,IACa;CACb,MAAM,QAAQ,sBAAsB;CACpC,MAAM,UAAU,GAAG,eAAe,IAAI;CACtC,MAAM,WAAW,MAAM,IAAI,QAAQ,IAAI,QAAQ,SAAS;CACxD,IAAI;CACJ,MAAM,UAAU,IAAI,SAAe,YAAY;AAC9C,mBAAiB;GAChB;CACF,MAAM,OAAO,SAAS,YAAY,GAAG,CAAC,WAAW,QAAQ;AACzD,OAAM,IAAI,SAAS,KAAK;AAExB,KAAI;AACH,QAAM,SAAS,YAAY,GAAG;AAC9B,SAAO,MAAM,IAAI;WACR;AACT,kBAAgB;AAChB,MAAI,MAAM,IAAI,QAAQ,KAAK,KAAM,OAAM,OAAO,QAAQ;;;AAIxD,eAAsB,+BACrB,gBACA,IACa;CAEb,MAAM,QAAQ,gCAAgC;CAC9C,MAAM,WAAW,MAAM,IAAI,eAAe,IAAI,QAAQ,SAAS;CAC/D,IAAI;CACJ,MAAM,UAAU,IAAI,SAAe,YAAY;AAC9C,mBAAiB;GAChB;CACF,MAAM,OAAO,SAAS,YAAY,GAAG,CAAC,WAAW,QAAQ;AACzD,OAAM,IAAI,gBAAgB,KAAK;AAE/B,KAAI;AACH,QAAM,SAAS,YAAY,GAAG;AAC9B,SAAO,MAAM,IAAI;WACR;AACT,kBAAgB;AAChB,MAAI,MAAM,IAAI,eAAe,KAAK,KAAM,OAAM,OAAO,eAAe;;;AAItE,SAAS,uBAAmD;CAC3D,MAAM,SAAS;CACf,MAAM,WAAW,OAAO;AAExB,KAAI,oBAAoB,IAAK,QAAO;CACpC,MAAM,wBAAQ,IAAI,KAA4B;AAC9C,QAAO,2BAA2B;AAClC,QAAO;;AAGR,SAAS,iCAA6D;CACrE,MAAM,SAAS;CACf,MAAM,WAAW,OAAO;AAExB,KAAI,oBAAoB,IAAK,QAAO;CACpC,MAAM,wBAAQ,IAAI,KAA4B;AAC9C,QAAO,sCAAsC;AAC7C,QAAO"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"context-DtSFYFuS.mjs","names":[],"sources":["../src/plugins/storage-query.ts","../src/database/repositories/plugin-storage.ts","../src/plugins/context.ts"],"sourcesContent":["/**\n * Plugin Storage Query Validation and Building\n *\n * Validates that queries only use indexed fields and builds SQL WHERE clauses.\n *\n * @see PLUGIN-SYSTEM.md § Plugin Storage > Query Validation\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { jsonExtractExpr } from \"../database/dialect-helpers.js\";\nimport { validateJsonFieldName } from \"../database/validate.js\";\nimport type { WhereClause, WhereValue, RangeFilter, InFilter, StartsWithFilter } from \"./types.js\";\n\n/**\n * Error thrown when querying non-indexed fields\n */\nexport class StorageQueryError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic field?: string,\n\t\tpublic suggestion?: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"StorageQueryError\";\n\t}\n}\n\n/**\n * Check if a value is a range filter\n */\nexport function isRangeFilter(value: WhereValue): value is RangeFilter {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn \"gt\" in value || \"gte\" in value || \"lt\" in value || \"lte\" in value;\n}\n\n/**\n * Check if a value is an IN filter\n */\nexport function isInFilter(value: WhereValue): value is InFilter {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn \"in\" in value && Array.isArray(value.in);\n}\n\n/**\n * Check if a value is a startsWith filter\n */\nexport function isStartsWithFilter(value: WhereValue): value is StartsWithFilter {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn \"startsWith\" in value && typeof value.startsWith === \"string\";\n}\n\n/**\n * Get the set of indexed fields from index declarations\n */\nexport function getIndexedFields(indexes: Array<string | string[]>): Set<string> {\n\tconst fields = new Set<string>();\n\tfor (const index of indexes) {\n\t\tif (Array.isArray(index)) {\n\t\t\tfor (const field of index) {\n\t\t\t\tfields.add(field);\n\t\t\t}\n\t\t} else {\n\t\t\tfields.add(index);\n\t\t}\n\t}\n\treturn fields;\n}\n\n/**\n * Validate that all fields in a where clause are indexed\n */\nexport function validateWhereClause(\n\twhere: WhereClause,\n\tindexedFields: Set<string>,\n\tpluginId: string,\n\tcollection: string,\n): void {\n\tfor (const field of Object.keys(where)) {\n\t\tif (!indexedFields.has(field)) {\n\t\t\tthrow new StorageQueryError(\n\t\t\t\t`Cannot query on non-indexed field '${field}'.`,\n\t\t\t\tfield,\n\t\t\t\t`Add '${field}' to storage.${collection}.indexes in plugin '${pluginId}' to enable this query.`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * Validate orderBy fields are indexed\n */\nexport function validateOrderByClause(\n\torderBy: Record<string, \"asc\" | \"desc\">,\n\tindexedFields: Set<string>,\n\tpluginId: string,\n\tcollection: string,\n): void {\n\tfor (const field of Object.keys(orderBy)) {\n\t\tif (!indexedFields.has(field)) {\n\t\t\tthrow new StorageQueryError(\n\t\t\t\t`Cannot order by non-indexed field '${field}'.`,\n\t\t\t\tfield,\n\t\t\t\t`Add '${field}' to storage.${collection}.indexes in plugin '${pluginId}' to enable ordering by this field.`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * SQL expression for extracting JSON field.\n *\n * Validates the field name before interpolation to prevent SQL injection\n * via crafted JSON path expressions.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function jsonExtract(db: Kysely<any>, field: string): string {\n\tvalidateJsonFieldName(field, \"query field name\");\n\treturn jsonExtractExpr(db, \"data\", field);\n}\n\n/**\n * Build a WHERE clause condition for a single field\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function buildCondition(\n\tdb: Kysely<any>,\n\tfield: string,\n\tvalue: WhereValue,\n): { sql: string; params: unknown[] } {\n\tconst extract = jsonExtract(db, field);\n\n\tif (value === null) {\n\t\treturn { sql: `${extract} IS NULL`, params: [] };\n\t}\n\n\tif (typeof value === \"string\" || typeof value === \"number\") {\n\t\treturn { sql: `${extract} = ?`, params: [value] };\n\t}\n\n\tif (typeof value === \"boolean\") {\n\t\t// JSON booleans are stored as true/false strings\n\t\treturn { sql: `${extract} = ?`, params: [value] };\n\t}\n\n\tif (isInFilter(value)) {\n\t\tconst placeholders = value.in.map(() => \"?\").join(\", \");\n\t\treturn {\n\t\t\tsql: `${extract} IN (${placeholders})`,\n\t\t\tparams: value.in,\n\t\t};\n\t}\n\n\tif (isStartsWithFilter(value)) {\n\t\treturn {\n\t\t\tsql: `${extract} LIKE ?`,\n\t\t\tparams: [`${value.startsWith}%`],\n\t\t};\n\t}\n\n\tif (isRangeFilter(value)) {\n\t\tconst conditions: string[] = [];\n\t\tconst params: unknown[] = [];\n\n\t\tif (value.gt !== undefined) {\n\t\t\tconditions.push(`${extract} > ?`);\n\t\t\tparams.push(value.gt);\n\t\t}\n\t\tif (value.gte !== undefined) {\n\t\t\tconditions.push(`${extract} >= ?`);\n\t\t\tparams.push(value.gte);\n\t\t}\n\t\tif (value.lt !== undefined) {\n\t\t\tconditions.push(`${extract} < ?`);\n\t\t\tparams.push(value.lt);\n\t\t}\n\t\tif (value.lte !== undefined) {\n\t\t\tconditions.push(`${extract} <= ?`);\n\t\t\tparams.push(value.lte);\n\t\t}\n\n\t\treturn {\n\t\t\tsql: conditions.join(\" AND \"),\n\t\t\tparams,\n\t\t};\n\t}\n\n\tthrow new StorageQueryError(`Unknown filter type for field '${field}'`);\n}\n\n/**\n * Build a complete WHERE clause from a WhereClause object\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function buildWhereClause(\n\tdb: Kysely<any>,\n\twhere: WhereClause,\n): {\n\tsql: string;\n\tparams: unknown[];\n} {\n\tconst conditions: string[] = [];\n\tconst params: unknown[] = [];\n\n\tfor (const [field, value] of Object.entries(where)) {\n\t\tconst condition = buildCondition(db, field, value);\n\t\tconditions.push(condition.sql);\n\t\tparams.push(...condition.params);\n\t}\n\n\tif (conditions.length === 0) {\n\t\treturn { sql: \"\", params: [] };\n\t}\n\n\treturn {\n\t\tsql: conditions.join(\" AND \"),\n\t\tparams,\n\t};\n}\n\n/**\n * Build ORDER BY clause\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function buildOrderByClause(\n\tdb: Kysely<any>,\n\torderBy: Record<string, \"asc\" | \"desc\">,\n): string {\n\tconst clauses: string[] = [];\n\n\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\tclauses.push(`${jsonExtract(db, field)} ${direction.toUpperCase()}`);\n\t}\n\n\tif (clauses.length === 0) {\n\t\treturn \"\";\n\t}\n\n\treturn `ORDER BY ${clauses.join(\", \")}`;\n}\n","/**\n * Plugin Storage Repository\n *\n * Provides a document store API for plugin data storage.\n * Uses a single _plugin_storage table with JSON documents and expression indexes.\n *\n * @see PLUGIN-SYSTEM.md § Plugin Storage > Full API Reference\n */\n\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport {\n\tbuildWhereClause,\n\tvalidateWhereClause,\n\tvalidateOrderByClause,\n\tgetIndexedFields,\n\tjsonExtract,\n} from \"../../plugins/storage-query.js\";\nimport type {\n\tStorageCollection,\n\tQueryOptions,\n\tPaginatedResult,\n\tWhereClause,\n} from \"../../plugins/types.js\";\nimport { withTransaction } from \"../transaction.js\";\nimport type { Database } from \"../types.js\";\nimport { encodeCursor, decodeCursor } from \"./types.js\";\n\n/**\n * Plugin Storage Repository\n *\n * Implements the StorageCollection interface for a specific plugin and collection.\n */\nexport class PluginStorageRepository<T = unknown> implements StorageCollection<T> {\n\tprivate indexedFields: Set<string>;\n\n\tconstructor(\n\t\tprivate db: Kysely<Database>,\n\t\tprivate pluginId: string,\n\t\tprivate collection: string,\n\t\tindexes: Array<string | string[]>,\n\t) {\n\t\tthis.indexedFields = getIndexedFields(indexes);\n\t}\n\n\t/**\n\t * Get a document by ID\n\t */\n\tasync get(id: string): Promise<T | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select(\"data\")\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) return null;\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\treturn JSON.parse(row.data) as T;\n\t}\n\n\t/**\n\t * Store a document\n\t */\n\tasync put(id: string, data: T): Promise<void> {\n\t\tconst now = new Date().toISOString();\n\t\tconst jsonData = JSON.stringify(data);\n\n\t\tawait this.db\n\t\t\t.insertInto(\"_plugin_storage\")\n\t\t\t.values({\n\t\t\t\tplugin_id: this.pluginId,\n\t\t\t\tcollection: this.collection,\n\t\t\t\tid,\n\t\t\t\tdata: jsonData,\n\t\t\t\tcreated_at: now,\n\t\t\t\tupdated_at: now,\n\t\t\t})\n\t\t\t.onConflict((oc) =>\n\t\t\t\toc.columns([\"plugin_id\", \"collection\", \"id\"]).doUpdateSet({\n\t\t\t\t\tdata: jsonData,\n\t\t\t\t\tupdated_at: now,\n\t\t\t\t}),\n\t\t\t)\n\t\t\t.execute();\n\t}\n\n\t/**\n\t * Delete a document\n\t */\n\tasync delete(id: string): Promise<boolean> {\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_plugin_storage\")\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn (result.numDeletedRows ?? 0) > 0;\n\t}\n\n\t/**\n\t * Check if a document exists\n\t */\n\tasync exists(id: string): Promise<boolean> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn !!row;\n\t}\n\n\t/**\n\t * Get multiple documents by ID\n\t */\n\tasync getMany(ids: string[]): Promise<Map<string, T>> {\n\t\tif (ids.length === 0) return new Map();\n\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select([\"id\", \"data\"])\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"in\", ids)\n\t\t\t.execute();\n\n\t\tconst result = new Map<string, T>();\n\t\tfor (const row of rows) {\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\t\tresult.set(row.id, JSON.parse(row.data) as T);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Store multiple documents\n\t */\n\tasync putMany(items: Array<{ id: string; data: T }>): Promise<void> {\n\t\tif (items.length === 0) return;\n\n\t\tconst now = new Date().toISOString();\n\n\t\t// SQLite doesn't support batch upserts well, so we do them one at a time\n\t\t// In a transaction for atomicity\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tfor (const item of items) {\n\t\t\t\tconst jsonData = JSON.stringify(item.data);\n\t\t\t\tawait trx\n\t\t\t\t\t.insertInto(\"_plugin_storage\")\n\t\t\t\t\t.values({\n\t\t\t\t\t\tplugin_id: this.pluginId,\n\t\t\t\t\t\tcollection: this.collection,\n\t\t\t\t\t\tid: item.id,\n\t\t\t\t\t\tdata: jsonData,\n\t\t\t\t\t\tcreated_at: now,\n\t\t\t\t\t\tupdated_at: now,\n\t\t\t\t\t})\n\t\t\t\t\t.onConflict((oc) =>\n\t\t\t\t\t\toc.columns([\"plugin_id\", \"collection\", \"id\"]).doUpdateSet({\n\t\t\t\t\t\t\tdata: jsonData,\n\t\t\t\t\t\t\tupdated_at: now,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t\t.execute();\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Delete multiple documents\n\t */\n\tasync deleteMany(ids: string[]): Promise<number> {\n\t\tif (ids.length === 0) return 0;\n\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_plugin_storage\")\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"in\", ids)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result.numDeletedRows ?? 0);\n\t}\n\n\t/**\n\t * Query documents with filters\n\t */\n\tasync query(options: QueryOptions = {}): Promise<PaginatedResult<{ id: string; data: T }>> {\n\t\tconst { where = {}, orderBy = {}, cursor } = options;\n\t\tconst limit = Math.min(options.limit ?? 50, 100);\n\n\t\t// Validate that all queried fields are indexed\n\t\tvalidateWhereClause(where, this.indexedFields, this.pluginId, this.collection);\n\t\tif (Object.keys(orderBy).length > 0) {\n\t\t\tvalidateOrderByClause(orderBy, this.indexedFields, this.pluginId, this.collection);\n\t\t}\n\n\t\t// Build base query\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select([\"id\", \"data\", \"created_at\"])\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection);\n\n\t\t// Add JSON extraction WHERE conditions\n\t\tconst whereResult = buildWhereClause(this.db, where);\n\t\tif (whereResult.sql) {\n\t\t\t// Use sql template to add the raw WHERE conditions with params\n\t\t\tconst whereSqlParts: ReturnType<typeof sql>[] = [];\n\t\t\tlet paramIndex = 0;\n\t\t\tconst sqlParts = whereResult.sql.split(\"?\");\n\t\t\tfor (let i = 0; i < sqlParts.length; i++) {\n\t\t\t\tif (i > 0) {\n\t\t\t\t\twhereSqlParts.push(sql`${whereResult.params[paramIndex++]}`);\n\t\t\t\t}\n\t\t\t\tif (sqlParts[i]) {\n\t\t\t\t\twhereSqlParts.push(sql.raw(sqlParts[i]));\n\t\t\t\t}\n\t\t\t}\n\t\t\tquery = query.where(({ eb }) => eb(sql.join(whereSqlParts, sql.raw(\"\")), \"=\", sql.raw(\"1\")));\n\t\t}\n\n\t\t// Handle cursor-based pagination — throws on invalid cursor.\n\t\tif (cursor) {\n\t\t\tconst decoded = decodeCursor(cursor);\n\t\t\tquery = query.where(({ eb }) =>\n\t\t\t\teb(sql`(created_at, id)`, \">\", sql`(${decoded.orderValue}, ${decoded.id})`),\n\t\t\t);\n\t\t}\n\n\t\t// Build ORDER BY using sql template\n\t\tif (Object.keys(orderBy).length > 0) {\n\t\t\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\t\t\tconst extract = jsonExtract(this.db, field);\n\t\t\t\tconst orderExpr =\n\t\t\t\t\tdirection === \"desc\" ? sql`${sql.raw(extract)} desc` : sql`${sql.raw(extract)} asc`;\n\t\t\t\tquery = query.orderBy(orderExpr);\n\t\t\t}\n\t\t} else {\n\t\t\t// Default ordering for consistent pagination\n\t\t\tquery = query.orderBy(\"created_at\", \"asc\").orderBy(\"id\", \"asc\");\n\t\t}\n\n\t\t// Apply limit (fetch one extra to detect if there's more)\n\t\tquery = query.limit(limit + 1);\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) => ({\n\t\t\tid: row.id,\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\t\tdata: JSON.parse(row.data) as T,\n\t\t}));\n\n\t\t// Generate cursor for next page if there are more results\n\t\tlet nextCursor: string | undefined;\n\t\tif (hasMore) {\n\t\t\tconst lastItem = rows[limit - 1];\n\t\t\tif (lastItem) {\n\t\t\t\tnextCursor = encodeCursor(lastItem.created_at, lastItem.id);\n\t\t\t}\n\t\t}\n\n\t\treturn { items, cursor: nextCursor, hasMore };\n\t}\n\n\t/**\n\t * Count documents matching a filter\n\t */\n\tasync count(where?: WhereClause): Promise<number> {\n\t\tif (where && Object.keys(where).length > 0) {\n\t\t\tvalidateWhereClause(where, this.indexedFields, this.pluginId, this.collection);\n\t\t}\n\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select(sql<number>`COUNT(*)`.as(\"count\"))\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection);\n\n\t\t// Add JSON extraction WHERE conditions\n\t\tif (where && Object.keys(where).length > 0) {\n\t\t\tconst whereResult = buildWhereClause(this.db, where);\n\t\t\tif (whereResult.sql) {\n\t\t\t\t// Use sql template to add the raw WHERE conditions with params\n\t\t\t\tconst whereSqlParts: ReturnType<typeof sql>[] = [];\n\t\t\t\tlet paramIndex = 0;\n\t\t\t\tconst sqlParts = whereResult.sql.split(\"?\");\n\t\t\t\tfor (let i = 0; i < sqlParts.length; i++) {\n\t\t\t\t\tif (i > 0) {\n\t\t\t\t\t\twhereSqlParts.push(sql`${whereResult.params[paramIndex++]}`);\n\t\t\t\t\t}\n\t\t\t\t\tif (sqlParts[i]) {\n\t\t\t\t\t\twhereSqlParts.push(sql.raw(sqlParts[i]));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tquery = query.where(({ eb }) =>\n\t\t\t\t\teb(sql.join(whereSqlParts, sql.raw(\"\")), \"=\", sql.raw(\"1\")),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst result = await query.executeTakeFirst();\n\t\treturn result?.count ?? 0;\n\t}\n}\n\n/**\n * Create a scoped storage accessor for a plugin\n */\nexport function createPluginStorageAccessor(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n\tstorageConfig: Record<\n\t\tstring,\n\t\t{ indexes: Array<string | string[]>; uniqueIndexes?: Array<string | string[]> }\n\t>,\n): Record<string, StorageCollection> {\n\tconst accessor: Record<string, StorageCollection> = {};\n\n\tfor (const [collectionName, config] of Object.entries(storageConfig)) {\n\t\tconst allIndexes = [...config.indexes, ...(config.uniqueIndexes ?? [])];\n\t\taccessor[collectionName] = new PluginStorageRepository(\n\t\t\tdb,\n\t\t\tpluginId,\n\t\t\tcollectionName,\n\t\t\tallIndexes,\n\t\t);\n\t}\n\n\treturn accessor;\n}\n\n/**\n * Delete all storage data for a plugin\n */\nexport async function deleteAllPluginStorage(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n): Promise<number> {\n\tconst result = await db\n\t\t.deleteFrom(\"_plugin_storage\")\n\t\t.where(\"plugin_id\", \"=\", pluginId)\n\t\t.executeTakeFirst();\n\n\treturn Number(result.numDeletedRows ?? 0);\n}\n\n/**\n * Delete all storage data for a plugin collection\n */\nexport async function deletePluginCollection(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n\tcollection: string,\n): Promise<number> {\n\tconst result = await db\n\t\t.deleteFrom(\"_plugin_storage\")\n\t\t.where(\"plugin_id\", \"=\", pluginId)\n\t\t.where(\"collection\", \"=\", collection)\n\t\t.executeTakeFirst();\n\n\treturn Number(result.numDeletedRows ?? 0);\n}\n","/**\n * Plugin Context v2\n *\n * Creates the unified context object provided to plugins in all hooks and routes.\n *\n */\n\nimport type { Kysely } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { ContentRepository } from \"../database/repositories/content.js\";\nimport { MediaRepository } from \"../database/repositories/media.js\";\nimport { OptionsRepository } from \"../database/repositories/options.js\";\nimport { PluginStorageRepository } from \"../database/repositories/plugin-storage.js\";\nimport { SeoRepository } from \"../database/repositories/seo.js\";\nimport { TaxonomyRepository, type Taxonomy } from \"../database/repositories/taxonomy.js\";\nimport { UserRepository } from \"../database/repositories/user.js\";\nimport { withTransaction } from \"../database/transaction.js\";\nimport type { Database } from \"../database/types.js\";\nimport {\n\tresolveAndValidateExternalUrl,\n\tSsrfError,\n\tstripCredentialHeaders,\n} from \"../import/ssrf.js\";\nimport { enrichImageMetadata } from \"../media/enrich.js\";\nimport { markContentMediaUsageCollectionStaleSafely } from \"../media/usage/content-refresh.js\";\nimport { invalidateSiteSettingsCache } from \"../settings/index.js\";\nimport type { Storage } from \"../storage/types.js\";\nimport { CronAccessImpl } from \"./cron.js\";\nimport type { EmailPipeline } from \"./email.js\";\nimport type {\n\tResolvedPlugin,\n\tPluginContext,\n\tPluginStorageConfig,\n\tStorageCollection,\n\tKVAccess,\n\tCronAccess,\n\tEmailAccess,\n\tContentAccess,\n\tContentAccessWithWrite,\n\tMediaAccess,\n\tMediaAccessWithWrite,\n\tHttpAccess,\n\tLogAccess,\n\tSiteInfo,\n\tUserAccess,\n\tUserInfo,\n\tContentItem,\n\tContentItemSeoInput,\n\tContentWriteInput,\n\tMediaItem,\n\tPaginatedResult,\n\tQueryOptions,\n\tContentListOptions,\n\tMediaListOptions,\n\tTaxonomyAccess,\n\tTaxonomyDefInfo,\n\tTaxonomyTermInfo,\n\tTaxonomyReadOptions,\n} from \"./types.js\";\n\n// =============================================================================\n// KV Access\n// =============================================================================\n\n/**\n * Create KV accessor for a plugin\n * All keys are automatically prefixed with the plugin ID\n */\nexport function createKVAccess(optionsRepo: OptionsRepository, pluginId: string): KVAccess {\n\tconst prefix = `plugin:${pluginId}:`;\n\n\treturn {\n\t\tasync get<T>(key: string): Promise<T | null> {\n\t\t\treturn optionsRepo.get<T>(`${prefix}${key}`);\n\t\t},\n\n\t\tasync set(key: string, value: unknown): Promise<void> {\n\t\t\tawait optionsRepo.set(`${prefix}${key}`, value);\n\t\t},\n\n\t\tasync delete(key: string): Promise<boolean> {\n\t\t\treturn optionsRepo.delete(`${prefix}${key}`);\n\t\t},\n\n\t\tasync list(keyPrefix?: string): Promise<Array<{ key: string; value: unknown }>> {\n\t\t\tconst fullPrefix = `${prefix}${keyPrefix ?? \"\"}`;\n\t\t\tconst entriesMap = await optionsRepo.getByPrefix(fullPrefix);\n\t\t\tconst result: Array<{ key: string; value: unknown }> = [];\n\t\t\tfor (const [fullKey, value] of entriesMap) {\n\t\t\t\tresult.push({\n\t\t\t\t\tkey: fullKey.slice(prefix.length),\n\t\t\t\t\tvalue,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn result;\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Storage Access\n// =============================================================================\n\n/**\n * Create storage collection accessor for a plugin\n * Wraps PluginStorageRepository with the v2 interface (no async iterators)\n */\nfunction createStorageCollection<T>(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n\tcollectionName: string,\n\tindexes: Array<string | string[]>,\n): StorageCollection<T> {\n\tconst repo = new PluginStorageRepository<T>(db, pluginId, collectionName, indexes);\n\n\treturn {\n\t\tget: (id) => repo.get(id),\n\t\tput: (id, data) => repo.put(id, data),\n\t\tdelete: (id) => repo.delete(id),\n\t\texists: (id) => repo.exists(id),\n\t\tgetMany: (ids) => repo.getMany(ids),\n\t\tputMany: (items) => repo.putMany(items),\n\t\tdeleteMany: (ids) => repo.deleteMany(ids),\n\t\tcount: (where) => repo.count(where),\n\n\t\t// Query returns PaginatedResult instead of the old format\n\t\tasync query(options?: QueryOptions): Promise<PaginatedResult<{ id: string; data: T }>> {\n\t\t\tconst result = await repo.query({\n\t\t\t\twhere: options?.where,\n\t\t\t\torderBy: options?.orderBy,\n\t\t\t\tlimit: options?.limit,\n\t\t\t\tcursor: options?.cursor,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\titems: result.items,\n\t\t\t\tcursor: result.cursor,\n\t\t\t\thasMore: result.hasMore,\n\t\t\t};\n\t\t},\n\t};\n}\n\n/**\n * Create storage accessor with all declared collections\n */\nexport function createStorageAccess<T extends PluginStorageConfig>(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n\tstorageConfig: T,\n): Record<string, StorageCollection> {\n\tconst storage: Record<string, StorageCollection> = {};\n\n\tfor (const [collectionName, config] of Object.entries(storageConfig)) {\n\t\tconst allIndexes = [...config.indexes, ...(config.uniqueIndexes ?? [])];\n\t\tstorage[collectionName] = createStorageCollection(db, pluginId, collectionName, allIndexes);\n\t}\n\n\treturn storage;\n}\n\n// =============================================================================\n// Content Access\n// =============================================================================\n\n/**\n * Extract `seo` from a plugin-supplied content write input and return both\n * parts. Mutates nothing — returns a new field map without the `seo` key.\n */\nfunction splitSeoFromInput(input: ContentWriteInput): {\n\tfields: Record<string, unknown>;\n\tseo: ContentItemSeoInput | undefined;\n} {\n\tconst { seo, ...fields } = input;\n\t// Reject non-object seo values rather than silently dropping them.\n\tif (seo !== undefined && (seo === null || typeof seo !== \"object\" || Array.isArray(seo))) {\n\t\tthrow new Error(\"content.seo must be an object\");\n\t}\n\treturn { fields, seo };\n}\n\n/**\n * Reject writing SEO to a collection that does not have it enabled.\n * Matches the REST API behavior (VALIDATION_ERROR).\n */\nasync function assertSeoEnabled(\n\tseoRepo: SeoRepository,\n\tcollection: string,\n\tseo: ContentItemSeoInput | undefined,\n): Promise<boolean> {\n\tconst hasSeo = await seoRepo.isEnabled(collection);\n\tif (seo !== undefined && !hasSeo) {\n\t\tthrow new Error(\n\t\t\t`Collection \"${collection}\" does not have SEO enabled. ` +\n\t\t\t\t`Remove the seo field or enable SEO on this collection.`,\n\t\t);\n\t}\n\treturn hasSeo;\n}\n\n/**\n * Parse the `collections` JSON column into a string array (`[]` on anything\n * else). Mirrors the guards in the Cloudflare/workerd bridges so an\n * in-process plugin degrades on malformed data instead of crashing.\n */\nfunction parseCollectionsColumn(value: string | null): string[] {\n\tif (!value) return [];\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(value);\n\t\treturn Array.isArray(parsed)\n\t\t\t? parsed.filter((item): item is string => typeof item === \"string\")\n\t\t\t: [];\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/** Map a repository `Taxonomy` row to the plugin-facing term shape. */\nfunction taxonomyToTermInfo(term: Taxonomy): TaxonomyTermInfo {\n\treturn {\n\t\tid: term.id,\n\t\ttaxonomy: term.name,\n\t\tslug: term.slug,\n\t\tlabel: term.label,\n\t\tparentId: term.parentId,\n\t\tdata: term.data,\n\t\tlocale: term.locale,\n\t\ttranslationGroup: term.translationGroup,\n\t};\n}\n\n/**\n * Create read-only content access\n */\nexport function createContentAccess(db: Kysely<Database>): ContentAccess {\n\tconst contentRepo = new ContentRepository(db);\n\tconst seoRepo = new SeoRepository(db);\n\n\treturn {\n\t\tasync get(collection: string, id: string): Promise<ContentItem | null> {\n\t\t\tconst item = await contentRepo.findById(collection, id);\n\t\t\tif (!item) return null;\n\n\t\t\tconst result: ContentItem = {\n\t\t\t\tid: item.id,\n\t\t\t\ttype: item.type,\n\t\t\t\tslug: item.slug,\n\t\t\t\tstatus: item.status,\n\t\t\t\tdata: item.data,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\tlocale: item.locale,\n\t\t\t\tpublishedAt: item.publishedAt,\n\t\t\t};\n\n\t\t\tif (await seoRepo.isEnabled(collection)) {\n\t\t\t\tresult.seo = await seoRepo.get(collection, item.id);\n\t\t\t}\n\n\t\t\treturn result;\n\t\t},\n\n\t\tasync list(\n\t\t\tcollection: string,\n\t\t\toptions?: ContentListOptions,\n\t\t): Promise<PaginatedResult<ContentItem>> {\n\t\t\t// Convert orderBy format if provided\n\t\t\tlet orderBy: { field: string; direction: \"asc\" | \"desc\" } | undefined;\n\t\t\tif (options?.orderBy) {\n\t\t\t\tconst entries = Object.entries(options.orderBy);\n\t\t\t\tconst first = entries[0];\n\t\t\t\tif (first) {\n\t\t\t\t\torderBy = { field: first[0], direction: first[1] };\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst result = await contentRepo.findMany(collection, {\n\t\t\t\tlimit: options?.limit ?? 50,\n\t\t\t\tcursor: options?.cursor,\n\t\t\t\torderBy,\n\t\t\t\twhere: options?.where,\n\t\t\t});\n\n\t\t\tconst items: ContentItem[] = result.items.map((item) => ({\n\t\t\t\tid: item.id,\n\t\t\t\ttype: item.type,\n\t\t\t\tslug: item.slug,\n\t\t\t\tstatus: item.status,\n\t\t\t\tdata: item.data,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\tlocale: item.locale,\n\t\t\t\tpublishedAt: item.publishedAt,\n\t\t\t}));\n\n\t\t\tif (items.length > 0 && (await seoRepo.isEnabled(collection))) {\n\t\t\t\tconst seoMap = await seoRepo.getMany(\n\t\t\t\t\tcollection,\n\t\t\t\t\titems.map((i) => i.id),\n\t\t\t\t);\n\t\t\t\tfor (const item of items) {\n\t\t\t\t\tconst seo = seoMap.get(item.id);\n\t\t\t\t\tif (seo) item.seo = seo;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\titems,\n\t\t\t\tcursor: result.nextCursor,\n\t\t\t\thasMore: !!result.nextCursor,\n\t\t\t};\n\t\t},\n\t};\n}\n\n/**\n * Create read-only taxonomy access (gated on `taxonomies:read`).\n */\nexport function createTaxonomyAccess(db: Kysely<Database>): TaxonomyAccess {\n\tconst taxonomyRepo = new TaxonomyRepository(db);\n\n\treturn {\n\t\tasync getAll(options?: TaxonomyReadOptions): Promise<TaxonomyDefInfo[]> {\n\t\t\tlet query = db.selectFrom(\"_emdash_taxonomy_defs\").selectAll();\n\t\t\tif (options?.locale !== undefined) query = query.where(\"locale\", \"=\", options.locale);\n\t\t\tconst rows = await query.orderBy(\"name\", \"asc\").execute();\n\t\t\treturn rows.map((row) => ({\n\t\t\t\tname: row.name,\n\t\t\t\tlabel: row.label,\n\t\t\t\tlabelSingular: row.label_singular,\n\t\t\t\thierarchical: row.hierarchical === 1,\n\t\t\t\tcollections: parseCollectionsColumn(row.collections),\n\t\t\t\tlocale: row.locale,\n\t\t\t}));\n\t\t},\n\n\t\tasync getTerms(taxonomy: string, options?: TaxonomyReadOptions): Promise<TaxonomyTermInfo[]> {\n\t\t\tconst terms = await taxonomyRepo.findByName(taxonomy, { locale: options?.locale });\n\t\t\treturn terms.map(taxonomyToTermInfo);\n\t\t},\n\n\t\tasync getEntryTerms(\n\t\t\tcollection: string,\n\t\t\tentryId: string,\n\t\t\toptions?: TaxonomyReadOptions & { taxonomy?: string },\n\t\t): Promise<TaxonomyTermInfo[]> {\n\t\t\tconst terms = await taxonomyRepo.getTermsForEntry(\n\t\t\t\tcollection,\n\t\t\t\tentryId,\n\t\t\t\toptions?.taxonomy,\n\t\t\t\toptions?.locale,\n\t\t\t);\n\t\t\treturn terms.map(taxonomyToTermInfo);\n\t\t},\n\t};\n}\n\n/**\n * Create full content access with write operations.\n *\n * `create` and `update` accept a reserved `seo` key in their `data`\n * argument. When present, it is routed to the core SEO panel\n * (`_emdash_seo`) via `SeoRepository.upsert`, in the same transaction as\n * the content write. The returned `ContentItem.seo` reflects the resulting\n * SEO state for SEO-enabled collections.\n */\nexport function createContentAccessWithWrite(db: Kysely<Database>): ContentAccessWithWrite {\n\tconst readAccess = createContentAccess(db);\n\n\treturn {\n\t\t...readAccess,\n\n\t\tasync create(collection: string, data: ContentWriteInput): Promise<ContentItem> {\n\t\t\tconst { fields, seo } = splitSeoFromInput(data);\n\t\t\tlet contentMutated = false;\n\n\t\t\ttry {\n\t\t\t\tconst created = await withTransaction(db, async (trx) => {\n\t\t\t\t\tconst trxContentRepo = new ContentRepository(trx);\n\t\t\t\t\tconst trxSeoRepo = new SeoRepository(trx);\n\n\t\t\t\t\tconst hasSeo = await assertSeoEnabled(trxSeoRepo, collection, seo);\n\n\t\t\t\t\tconst item = await trxContentRepo.create({\n\t\t\t\t\t\ttype: collection,\n\t\t\t\t\t\tdata: fields,\n\t\t\t\t\t});\n\t\t\t\t\tcontentMutated = true;\n\n\t\t\t\t\tconst result: ContentItem = {\n\t\t\t\t\t\tid: item.id,\n\t\t\t\t\t\ttype: item.type,\n\t\t\t\t\t\tslug: item.slug,\n\t\t\t\t\t\tstatus: item.status,\n\t\t\t\t\t\tdata: item.data,\n\t\t\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\t\t\tlocale: item.locale,\n\t\t\t\t\t\tpublishedAt: item.publishedAt,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (hasSeo) {\n\t\t\t\t\t\tresult.seo =\n\t\t\t\t\t\t\tseo !== undefined\n\t\t\t\t\t\t\t\t? await trxSeoRepo.upsert(collection, item.id, seo)\n\t\t\t\t\t\t\t\t: await trxSeoRepo.get(collection, item.id);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn result;\n\t\t\t\t});\n\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(db, collection, \"CONTENT_USAGE_STALE\");\n\t\t\t\treturn created;\n\t\t\t} catch (error) {\n\t\t\t\tif (contentMutated) {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(db, collection, \"CONTENT_USAGE_STALE\");\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\n\t\tasync update(collection: string, id: string, data: ContentWriteInput): Promise<ContentItem> {\n\t\t\tconst { fields, seo } = splitSeoFromInput(data);\n\t\t\tconst hasFieldUpdates = Object.keys(fields).length > 0;\n\t\t\tlet contentMutated = false;\n\n\t\t\ttry {\n\t\t\t\tconst updated = await withTransaction(db, async (trx) => {\n\t\t\t\t\tconst trxContentRepo = new ContentRepository(trx);\n\t\t\t\t\tconst trxSeoRepo = new SeoRepository(trx);\n\n\t\t\t\t\tconst hasSeo = await assertSeoEnabled(trxSeoRepo, collection, seo);\n\n\t\t\t\t\t// Pass the `data` payload to ContentRepository.update only when\n\t\t\t\t\t// there are field updates — passing an empty object would still\n\t\t\t\t\t// bump updated_at/version, but we want a seo-only call to touch\n\t\t\t\t\t// only the SEO table. ContentRepository.update handles the no-op\n\t\t\t\t\t// path by returning the current row.\n\t\t\t\t\tconst item = hasFieldUpdates\n\t\t\t\t\t\t? await trxContentRepo.update(collection, id, { data: fields })\n\t\t\t\t\t\t: await (async () => {\n\t\t\t\t\t\t\t\tconst existing = await trxContentRepo.findById(collection, id);\n\t\t\t\t\t\t\t\tif (!existing) throw new Error(\"Content not found\");\n\t\t\t\t\t\t\t\treturn existing;\n\t\t\t\t\t\t\t})();\n\t\t\t\t\tif (hasFieldUpdates) contentMutated = true;\n\n\t\t\t\t\tconst result: ContentItem = {\n\t\t\t\t\t\tid: item.id,\n\t\t\t\t\t\ttype: item.type,\n\t\t\t\t\t\tslug: item.slug,\n\t\t\t\t\t\tstatus: item.status,\n\t\t\t\t\t\tdata: item.data,\n\t\t\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\t\t\tlocale: item.locale,\n\t\t\t\t\t\tpublishedAt: item.publishedAt,\n\t\t\t\t\t};\n\n\t\t\t\t\tif (hasSeo) {\n\t\t\t\t\t\tresult.seo =\n\t\t\t\t\t\t\tseo !== undefined\n\t\t\t\t\t\t\t\t? await trxSeoRepo.upsert(collection, item.id, seo)\n\t\t\t\t\t\t\t\t: await trxSeoRepo.get(collection, item.id);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn result;\n\t\t\t\t});\n\t\t\t\tif (hasFieldUpdates) {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(db, collection, \"CONTENT_USAGE_STALE\");\n\t\t\t\t}\n\t\t\t\treturn updated;\n\t\t\t} catch (error) {\n\t\t\t\tif (contentMutated) {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(db, collection, \"CONTENT_USAGE_STALE\");\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\n\t\tasync delete(collection: string, id: string): Promise<boolean> {\n\t\t\tconst contentRepo = new ContentRepository(db);\n\t\t\tconst deleted = await contentRepo.delete(collection, id);\n\t\t\tif (deleted) {\n\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(db, collection, \"CONTENT_USAGE_STALE\");\n\t\t\t}\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Media Access\n// =============================================================================\n\n/**\n * Create read-only media access\n */\nexport function createMediaAccess(db: Kysely<Database>): MediaAccess {\n\tconst mediaRepo = new MediaRepository(db);\n\n\treturn {\n\t\tasync get(id: string): Promise<MediaItem | null> {\n\t\t\tconst item = await mediaRepo.findById(id);\n\t\t\tif (!item) return null;\n\n\t\t\treturn {\n\t\t\t\tid: item.id,\n\t\t\t\tfilename: item.filename,\n\t\t\t\tmimeType: item.mimeType,\n\t\t\t\tsize: item.size,\n\t\t\t\t// Construct URL from storage key (or use a sensible default path)\n\t\t\t\turl: `/media/${item.id}/${item.filename}`,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t};\n\t\t},\n\n\t\tasync list(options?: MediaListOptions): Promise<PaginatedResult<MediaItem>> {\n\t\t\tconst result = await mediaRepo.findMany({\n\t\t\t\tlimit: options?.limit ?? 50,\n\t\t\t\tcursor: options?.cursor,\n\t\t\t\tmimeType: options?.mimeType,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\titems: result.items.map((item) => ({\n\t\t\t\t\tid: item.id,\n\t\t\t\t\tfilename: item.filename,\n\t\t\t\t\tmimeType: item.mimeType,\n\t\t\t\t\tsize: item.size,\n\t\t\t\t\turl: `/media/${item.id}/${item.filename}`,\n\t\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\t})),\n\t\t\t\tcursor: result.nextCursor,\n\t\t\t\thasMore: !!result.nextCursor,\n\t\t\t};\n\t\t},\n\t};\n}\n\n/**\n * Create full media access with write operations.\n *\n * `getUploadUrlFn` is optional: when omitted, `getUploadUrl()` is derived from\n * `storage` (create a pending record + a signed PUT URL), mirroring the REST\n * `/_emdash/api/media/upload-url` endpoint. `upload()` only needs `storage`.\n * If storage is not provided, both throw at call time.\n */\nexport function createMediaAccessWithWrite(\n\tdb: Kysely<Database>,\n\tgetUploadUrlFn:\n\t\t| ((filename: string, contentType: string) => Promise<{ uploadUrl: string; mediaId: string }>)\n\t\t| undefined,\n\tstorage?: Storage,\n): MediaAccessWithWrite {\n\tconst mediaRepo = new MediaRepository(db);\n\tconst readAccess = createMediaAccess(db);\n\n\tconst getUploadUrl =\n\t\tgetUploadUrlFn ??\n\t\t(async (filename: string, contentType: string) => {\n\t\t\tif (!storage) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Media getUploadUrl() requires a storage backend. Configure storage in PluginContextFactoryOptions.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst basename = filename.split(\"/\").pop() ?? filename;\n\t\t\tconst dotIdx = basename.lastIndexOf(\".\");\n\t\t\tconst ext = dotIdx > 0 ? basename.slice(dotIdx).toLowerCase() : \"\";\n\t\t\tconst storageKey = `${ulid()}${ext}`;\n\n\t\t\tconst media = await mediaRepo.createPending({\n\t\t\t\tfilename: basename,\n\t\t\t\tmimeType: contentType,\n\t\t\t\tstorageKey,\n\t\t\t});\n\n\t\t\tconst signed = await storage.getSignedUploadUrl({\n\t\t\t\tkey: storageKey,\n\t\t\t\tcontentType,\n\t\t\t\texpiresIn: 3600,\n\t\t\t});\n\n\t\t\treturn { uploadUrl: signed.url, mediaId: media.id };\n\t\t});\n\n\treturn {\n\t\t...readAccess,\n\n\t\tgetUploadUrl,\n\n\t\tasync upload(\n\t\t\tfilename: string,\n\t\t\tcontentType: string,\n\t\t\tbytes: ArrayBuffer,\n\t\t): Promise<{ mediaId: string; storageKey: string; url: string }> {\n\t\t\tif (!storage) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Media upload() requires a storage backend. Configure storage in PluginContextFactoryOptions.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Generate a storage key with a unique prefix\n\t\t\tconst keyPrefix = ulid();\n\t\t\t// Extract extension from basename (ignore path separators)\n\t\t\tconst basename = filename.split(\"/\").pop() ?? filename;\n\t\t\tconst dotIdx = basename.lastIndexOf(\".\");\n\t\t\tconst ext = dotIdx > 0 ? basename.slice(dotIdx).toLowerCase() : \"\";\n\t\t\tconst storageKey = `${keyPrefix}${ext}`;\n\n\t\t\t// Upload to storage first\n\t\t\tawait storage.upload({\n\t\t\t\tkey: storageKey,\n\t\t\t\tbody: new Uint8Array(bytes),\n\t\t\t\tcontentType,\n\t\t\t});\n\n\t\t\t// Derive dimensions + LQIP placeholders (no-op for non-images).\n\t\t\tconst enriched = await enrichImageMetadata(new Uint8Array(bytes), contentType);\n\n\t\t\t// Create DB record — clean up storage on failure\n\t\t\tlet media;\n\t\t\ttry {\n\t\t\t\tmedia = await mediaRepo.create({\n\t\t\t\t\tfilename: basename,\n\t\t\t\t\tmimeType: contentType,\n\t\t\t\t\tsize: bytes.byteLength,\n\t\t\t\t\tstorageKey,\n\t\t\t\t\tstatus: \"ready\",\n\t\t\t\t\twidth: enriched.width,\n\t\t\t\t\theight: enriched.height,\n\t\t\t\t\tblurhash: enriched.blurhash,\n\t\t\t\t\tdominantColor: enriched.dominantColor,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\ttry {\n\t\t\t\t\tawait storage.delete(storageKey);\n\t\t\t\t} catch {\n\t\t\t\t\t// Best-effort cleanup\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tmediaId: media.id,\n\t\t\t\tstorageKey,\n\t\t\t\turl: `/_emdash/api/media/file/${storageKey}`,\n\t\t\t};\n\t\t},\n\n\t\tasync delete(id: string): Promise<boolean> {\n\t\t\tconst deleted = await mediaRepo.delete(id);\n\t\t\t// Plugins can delete media that's referenced by site settings\n\t\t\t// (`logo`, `favicon`, `seo.defaultOgImage`); the worker-scoped\n\t\t\t// resolved-URL cache must be dropped or it will keep serving\n\t\t\t// 404s. Matches the invalidation in\n\t\t\t// `EmDashRuntime.handleMediaDelete`.\n\t\t\tif (deleted) {\n\t\t\t\tinvalidateSiteSettingsCache();\n\t\t\t}\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n\n// =============================================================================\n// HTTP Access\n// =============================================================================\n\n/** Maximum number of redirects to follow in plugin HTTP access */\nconst MAX_PLUGIN_REDIRECTS = 5;\n\n/**\n * Check if a hostname matches any pattern in the allowed list.\n * Patterns: \"*\" matches all, \"*.example.com\" matches subdomains AND bare \"example.com\",\n * \"api.example.com\" matches exactly.\n */\nfunction isHostAllowed(host: string, allowedHosts: string[]): boolean {\n\treturn allowedHosts.some((pattern) => {\n\t\tif (pattern === \"*\") return true;\n\t\tif (pattern.startsWith(\"*.\")) {\n\t\t\tconst suffix = pattern.slice(1); // \".example.com\"\n\t\t\t// Match subdomains (foo.example.com) and bare domain (example.com)\n\t\t\treturn host.endsWith(suffix) || host === pattern.slice(2);\n\t\t}\n\t\treturn host === pattern;\n\t});\n}\n\n/**\n * Create HTTP access with host validation.\n *\n * Uses redirect: \"manual\" to re-validate each redirect target against\n * the allowedHosts list, preventing redirects to unauthorized hosts.\n */\nexport function createHttpAccess(pluginId: string, allowedHosts: string[]): HttpAccess {\n\treturn {\n\t\tasync fetch(url: string, init?: RequestInit): Promise<Response> {\n\t\t\t// Deny by default — plugins must declare allowed hosts\n\t\t\tif (allowedHosts.length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Plugin \"${pluginId}\" has no allowed hosts configured. ` +\n\t\t\t\t\t\t`Add hosts to the plugin's allowedHosts array to enable HTTP requests.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlet currentUrl = url;\n\t\t\tlet currentInit = init;\n\n\t\t\tfor (let i = 0; i <= MAX_PLUGIN_REDIRECTS; i++) {\n\t\t\t\tconst hostname = new URL(currentUrl).hostname;\n\t\t\t\tif (!isHostAllowed(hostname, allowedHosts)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Plugin \"${pluginId}\" is not allowed to fetch from host \"${hostname}\". ` +\n\t\t\t\t\t\t\t`Allowed hosts: ${allowedHosts.join(\", \")}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst response = await globalThis.fetch(currentUrl, {\n\t\t\t\t\t...currentInit,\n\t\t\t\t\tredirect: \"manual\",\n\t\t\t\t});\n\n\t\t\t\t// Not a redirect -- return directly\n\t\t\t\tif (response.status < 300 || response.status >= 400) {\n\t\t\t\t\treturn response;\n\t\t\t\t}\n\n\t\t\t\t// Extract redirect target\n\t\t\t\tconst location = response.headers.get(\"Location\");\n\t\t\t\tif (!location) {\n\t\t\t\t\treturn response;\n\t\t\t\t}\n\n\t\t\t\t// Resolve relative redirects; strip credentials on cross-origin hops\n\t\t\t\tconst previousOrigin = new URL(currentUrl).origin;\n\t\t\t\tcurrentUrl = new URL(location, currentUrl).href;\n\t\t\t\tconst nextOrigin = new URL(currentUrl).origin;\n\n\t\t\t\tif (previousOrigin !== nextOrigin && currentInit) {\n\t\t\t\t\tcurrentInit = stripCredentialHeaders(currentInit);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthrow new Error(`Plugin \"${pluginId}\": too many redirects (max ${MAX_PLUGIN_REDIRECTS})`);\n\t\t},\n\t};\n}\n\n/**\n * Create unrestricted HTTP access (for plugins with network:fetch:any capability).\n * No host validation, but applies SSRF protection on redirect targets to\n * prevent plugins from being tricked into reaching internal services.\n */\nexport function createUnrestrictedHttpAccess(pluginId: string): HttpAccess {\n\treturn {\n\t\tasync fetch(url: string, init?: RequestInit): Promise<Response> {\n\t\t\tlet currentUrl = url;\n\t\t\tlet currentInit = init;\n\n\t\t\tfor (let i = 0; i <= MAX_PLUGIN_REDIRECTS; i++) {\n\t\t\t\t// Validate each URL against SSRF rules (private IPs, metadata\n\t\t\t\t// endpoints, wildcard DNS, resolved-IP private ranges).\n\t\t\t\ttry {\n\t\t\t\t\tawait resolveAndValidateExternalUrl(currentUrl);\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconst msg = e instanceof SsrfError ? e.message : \"SSRF validation failed\";\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Plugin \"${pluginId}\": blocked fetch to \"${new URL(currentUrl).hostname}\": ${msg}`,\n\t\t\t\t\t\t{ cause: e },\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst response = await globalThis.fetch(currentUrl, {\n\t\t\t\t\t...currentInit,\n\t\t\t\t\tredirect: \"manual\",\n\t\t\t\t});\n\n\t\t\t\t// Not a redirect -- return directly\n\t\t\t\tif (response.status < 300 || response.status >= 400) {\n\t\t\t\t\treturn response;\n\t\t\t\t}\n\n\t\t\t\t// Extract redirect target\n\t\t\t\tconst location = response.headers.get(\"Location\");\n\t\t\t\tif (!location) {\n\t\t\t\t\treturn response;\n\t\t\t\t}\n\n\t\t\t\t// Resolve relative redirects; strip credentials on cross-origin hops\n\t\t\t\tconst previousOrigin = new URL(currentUrl).origin;\n\t\t\t\tcurrentUrl = new URL(location, currentUrl).href;\n\t\t\t\tconst nextOrigin = new URL(currentUrl).origin;\n\n\t\t\t\tif (previousOrigin !== nextOrigin && currentInit) {\n\t\t\t\t\tcurrentInit = stripCredentialHeaders(currentInit);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthrow new Error(`Plugin \"${pluginId}\": too many redirects (max ${MAX_PLUGIN_REDIRECTS})`);\n\t\t},\n\t};\n}\n\n/**\n * Create blocked HTTP access (for plugins without network:request capability)\n */\nexport function createBlockedHttpAccess(pluginId: string): HttpAccess {\n\treturn {\n\t\tasync fetch(): Promise<never> {\n\t\t\tthrow new Error(\n\t\t\t\t`Plugin \"${pluginId}\" does not have the \"network:request\" capability. ` +\n\t\t\t\t\t`Add \"network:request\" to the plugin's capabilities to enable HTTP requests.`,\n\t\t\t);\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Log Access\n// =============================================================================\n\n/**\n * Create logger for a plugin\n */\nexport function createLogAccess(pluginId: string): LogAccess {\n\tconst prefix = `[plugin:${pluginId}]`;\n\n\treturn {\n\t\tdebug(message: string, data?: unknown): void {\n\t\t\tif (data !== undefined) {\n\t\t\t\tconsole.debug(prefix, message, data);\n\t\t\t} else {\n\t\t\t\tconsole.debug(prefix, message);\n\t\t\t}\n\t\t},\n\n\t\tinfo(message: string, data?: unknown): void {\n\t\t\tif (data !== undefined) {\n\t\t\t\tconsole.info(prefix, message, data);\n\t\t\t} else {\n\t\t\t\tconsole.info(prefix, message);\n\t\t\t}\n\t\t},\n\n\t\twarn(message: string, data?: unknown): void {\n\t\t\tif (data !== undefined) {\n\t\t\t\tconsole.warn(prefix, message, data);\n\t\t\t} else {\n\t\t\t\tconsole.warn(prefix, message);\n\t\t\t}\n\t\t},\n\n\t\terror(message: string, data?: unknown): void {\n\t\t\tif (data !== undefined) {\n\t\t\t\tconsole.error(prefix, message, data);\n\t\t\t} else {\n\t\t\t\tconsole.error(prefix, message);\n\t\t\t}\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Site Info\n// =============================================================================\n\nconst TRAILING_SLASH_RE = /\\/$/;\n\n/**\n * Options for creating site info\n */\nexport interface SiteInfoOptions {\n\t/** Site name from options table */\n\tsiteName?: string;\n\t/** Site URL from options table or Astro config */\n\tsiteUrl?: string;\n\t/** Site locale from options table */\n\tlocale?: string;\n}\n\n/**\n * Create site info from config and settings.\n *\n * Resolution order for URL:\n * 1. options table (emdash:site_url)\n * 2. Astro `site` config\n * 3. fallback to empty string\n */\nexport function createSiteInfo(options: SiteInfoOptions): SiteInfo {\n\treturn {\n\t\tname: options.siteName ?? \"\",\n\t\turl: (options.siteUrl ?? \"\").replace(TRAILING_SLASH_RE, \"\"), // strip trailing slash\n\t\tlocale: options.locale ?? \"en\",\n\t};\n}\n\n/**\n * Create a URL helper that generates absolute URLs from relative paths.\n * Validates that path starts with \"/\" and rejects protocol-relative paths (\"//\").\n */\nexport function createUrlHelper(siteUrl: string): (path: string) => string {\n\tconst base = siteUrl.replace(TRAILING_SLASH_RE, \"\"); // strip trailing slash\n\n\treturn (path: string): string => {\n\t\tif (!path.startsWith(\"/\")) {\n\t\t\tthrow new Error(`URL path must start with \"/\", got: \"${path}\"`);\n\t\t}\n\t\tif (path.startsWith(\"//\")) {\n\t\t\tthrow new Error(`URL path must not be protocol-relative, got: \"${path}\"`);\n\t\t}\n\t\treturn `${base}${path}`;\n\t};\n}\n\n// =============================================================================\n// User Access\n// =============================================================================\n\n/**\n * Convert a UserRepository user to the plugin-facing UserInfo shape.\n * Strips sensitive fields (avatarUrl, emailVerified, data).\n */\nfunction toUserInfo(user: {\n\tid: string;\n\temail: string;\n\tname: string | null;\n\trole: number;\n\tcreatedAt: string;\n}): UserInfo {\n\treturn {\n\t\tid: user.id,\n\t\temail: user.email,\n\t\tname: user.name,\n\t\trole: user.role,\n\t\tcreatedAt: user.createdAt,\n\t};\n}\n\n/**\n * Create read-only user access for plugins.\n * Excludes sensitive fields (password hashes, sessions, passkeys, avatar URL, data).\n */\nexport function createUserAccess(db: Kysely<Database>): UserAccess {\n\tconst userRepo = new UserRepository(db);\n\n\treturn {\n\t\tasync get(id: string): Promise<UserInfo | null> {\n\t\t\tconst user = await userRepo.findById(id);\n\t\t\tif (!user) return null;\n\t\t\treturn toUserInfo(user);\n\t\t},\n\n\t\tasync getByEmail(email: string): Promise<UserInfo | null> {\n\t\t\tconst user = await userRepo.findByEmail(email);\n\t\t\tif (!user) return null;\n\t\t\treturn toUserInfo(user);\n\t\t},\n\n\t\tasync list(opts?: {\n\t\t\trole?: number;\n\t\t\tlimit?: number;\n\t\t\tcursor?: string;\n\t\t}): Promise<{ items: UserInfo[]; nextCursor?: string }> {\n\t\t\tconst result = await userRepo.findMany({\n\t\t\t\trole: opts?.role as 10 | 20 | 30 | 40 | 50 | undefined,\n\t\t\t\tcursor: opts?.cursor,\n\t\t\t\tlimit: opts?.limit,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\titems: result.items.map(toUserInfo),\n\t\t\t\tnextCursor: result.nextCursor,\n\t\t\t};\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Plugin Context Factory\n// =============================================================================\n\nexport interface PluginContextFactoryOptions {\n\tdb: Kysely<Database>;\n\t/**\n\t * Resolver for the database connection, preferred over `db` when present.\n\t * Called per `createContext()` so connection-backed adapters (e.g. Postgres\n\t * over Hyperdrive) get the current request/event-scoped connection from ALS\n\t * rather than a snapshot of the per-isolate singleton — reusing the\n\t * singleton's socket from a later event trips workerd's cross-request I/O\n\t * guard. When omitted, `db` is used directly (correct for stateless\n\t * adapters like D1 and Node SQLite). `db` remains required as the fallback.\n\t */\n\tgetDb?: () => Kysely<Database>;\n\t/**\n\t * Storage backend for direct media uploads.\n\t * If not provided, upload() will throw.\n\t */\n\tstorage?: Storage;\n\t/**\n\t * Explicit provider for `ctx.media.getUploadUrl()`. Optional: when omitted\n\t * but `storage` is configured, the factory derives a working `getUploadUrl()`\n\t * (and `upload()`) from storage. Only when neither `getUploadUrl` nor\n\t * `storage` is present do media write operations become unavailable.\n\t */\n\tgetUploadUrl?: (\n\t\tfilename: string,\n\t\tcontentType: string,\n\t) => Promise<{ uploadUrl: string; mediaId: string }>;\n\t/**\n\t * Site information for ctx.site and ctx.url().\n\t * If not provided, site info will have empty defaults.\n\t */\n\tsiteInfo?: SiteInfoOptions;\n\t/**\n\t * Callback to notify the cron scheduler that the next due time may have changed.\n\t * If not provided, ctx.cron will not be available.\n\t */\n\tcronReschedule?: () => void;\n\t/**\n\t * Email pipeline instance for ctx.email.\n\t * If not provided (or no provider configured), ctx.email will be undefined.\n\t */\n\temailPipeline?: EmailPipeline;\n\t/**\n\t * Pre-resolved list of trusted proxy header names (from the runtime\n\t * `EmDashConfig.trustedProxyHeaders` or the env var). Plugin route\n\t * handlers pass this to `extractRequestMeta` so plugins see the same\n\t * client IP the core auth path does.\n\t */\n\ttrustedProxyHeaders?: string[];\n}\n\n/**\n * Factory for creating plugin contexts\n */\nexport class PluginContextFactory {\n\tprivate resolveDb: () => Kysely<Database>;\n\tprivate storage?: Storage;\n\tprivate getUploadUrl?: (\n\t\tfilename: string,\n\t\tcontentType: string,\n\t) => Promise<{ uploadUrl: string; mediaId: string }>;\n\tprivate site: SiteInfo;\n\tprivate urlHelper: (path: string) => string;\n\tprivate cronReschedule?: () => void;\n\tprivate emailPipeline?: EmailPipeline;\n\t/**\n\t * Plugin IDs already warned about a missing media-write backend, so the\n\t * warning fires once per factory instead of on every hook/route context\n\t * creation (which would spam logs for hook-participating plugins).\n\t */\n\tprivate warnedMissingMediaBackend = new Set<string>();\n\n\tconstructor(options: PluginContextFactoryOptions) {\n\t\tconst fixedDb = options.db;\n\t\tthis.resolveDb = options.getDb ?? (() => fixedDb);\n\t\tthis.storage = options.storage;\n\t\tthis.getUploadUrl = options.getUploadUrl;\n\t\tthis.site = createSiteInfo(options.siteInfo ?? {});\n\t\tthis.urlHelper = createUrlHelper(this.site.url);\n\t\tthis.cronReschedule = options.cronReschedule;\n\t\tthis.emailPipeline = options.emailPipeline;\n\t}\n\n\t/**\n\t * Create the unified plugin context\n\t */\n\tcreateContext(plugin: ResolvedPlugin): PluginContext {\n\t\tconst capabilities = new Set(plugin.capabilities);\n\n\t\t// Resolve the connection once per context. For stateless adapters this\n\t\t// is the singleton; for connection-backed adapters it's the current\n\t\t// request/event-scoped connection from ALS. All repos below are built\n\t\t// from this local `db` so a hook never queries a stale singleton socket.\n\t\tconst db = this.resolveDb();\n\t\tconst optionsRepo = new OptionsRepository(db);\n\n\t\t// Always available\n\t\tconst kv = createKVAccess(optionsRepo, plugin.id);\n\t\tconst log = createLogAccess(plugin.id);\n\t\tconst storage = createStorageAccess(db, plugin.id, plugin.storage);\n\n\t\t// Capability-gated: content\n\t\t// Note: capabilities reach this point already normalized to the\n\t\t// canonical names by definePlugin / adaptSandboxEntry. Deprecated\n\t\t// names (\"read:content\", \"write:content\") never appear here.\n\t\tlet content: ContentAccess | ContentAccessWithWrite | undefined;\n\t\tif (capabilities.has(\"content:write\")) {\n\t\t\tcontent = createContentAccessWithWrite(db);\n\t\t} else if (capabilities.has(\"content:read\")) {\n\t\t\tcontent = createContentAccess(db);\n\t\t}\n\n\t\t// Capability-gated: taxonomies (read-only)\n\t\tlet taxonomies: TaxonomyAccess | undefined;\n\t\tif (capabilities.has(\"taxonomies:read\")) {\n\t\t\ttaxonomies = createTaxonomyAccess(db);\n\t\t}\n\n\t\t// Capability-gated: media\n\t\t// `upload()` only needs `storage`; `getUploadUrl()` is derived from\n\t\t// storage when no explicit provider is wired. Granting write access on\n\t\t// either avoids silently degrading media:write to read-only — the bug\n\t\t// where the runtime threads `storage` but not `getUploadUrl`.\n\t\tlet media: MediaAccess | MediaAccessWithWrite | undefined;\n\t\tif (capabilities.has(\"media:write\")) {\n\t\t\tif (this.getUploadUrl || this.storage) {\n\t\t\t\tmedia = createMediaAccessWithWrite(db, this.getUploadUrl, this.storage);\n\t\t\t} else {\n\t\t\t\tif (!this.warnedMissingMediaBackend.has(plugin.id)) {\n\t\t\t\t\tthis.warnedMissingMediaBackend.add(plugin.id);\n\t\t\t\t\tlog.warn(\n\t\t\t\t\t\t\"declares the media:write capability but no storage backend is configured; upload() is unavailable.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (capabilities.has(\"media:read\")) {\n\t\t\t\t\tmedia = createMediaAccess(db);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (capabilities.has(\"media:read\")) {\n\t\t\tmedia = createMediaAccess(db);\n\t\t}\n\n\t\t// Capability-gated: http\n\t\tlet http: HttpAccess | undefined;\n\t\tif (capabilities.has(\"network:request:unrestricted\")) {\n\t\t\thttp = createUnrestrictedHttpAccess(plugin.id);\n\t\t} else if (capabilities.has(\"network:request\")) {\n\t\t\thttp = createHttpAccess(plugin.id, plugin.allowedHosts);\n\t\t}\n\n\t\t// Capability-gated: users\n\t\tlet users: UserAccess | undefined;\n\t\tif (capabilities.has(\"users:read\")) {\n\t\t\tusers = createUserAccess(db);\n\t\t}\n\n\t\t// Cron access — always available (scoped to plugin), but only if\n\t\t// the runtime provided a reschedule callback (i.e. cron is wired up).\n\t\tlet cron: CronAccess | undefined;\n\t\tif (this.cronReschedule) {\n\t\t\tcron = new CronAccessImpl(db, plugin.id, this.cronReschedule);\n\t\t}\n\n\t\t// Email access — requires email:send capability AND a configured provider\n\t\tlet email: EmailAccess | undefined;\n\t\tif (capabilities.has(\"email:send\") && this.emailPipeline?.isAvailable()) {\n\t\t\tconst pipeline = this.emailPipeline;\n\t\t\tconst pluginId = plugin.id;\n\t\t\temail = {\n\t\t\t\tsend: (message) => pipeline.send(message, pluginId),\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tplugin: {\n\t\t\t\tid: plugin.id,\n\t\t\t\tversion: plugin.version,\n\t\t\t},\n\t\t\tstorage,\n\t\t\tkv,\n\t\t\tcontent,\n\t\t\ttaxonomies,\n\t\t\tmedia,\n\t\t\thttp,\n\t\t\tlog,\n\t\t\tsite: this.site,\n\t\t\turl: this.urlHelper,\n\t\t\tusers,\n\t\t\tcron,\n\t\t\temail,\n\t\t};\n\t}\n}\n\n/**\n * Create a plugin context for a resolved plugin\n */\nexport function createPluginContext(\n\toptions: PluginContextFactoryOptions,\n\tplugin: ResolvedPlugin,\n): PluginContext {\n\tconst factory = new PluginContextFactory(options);\n\treturn factory.createContext(plugin);\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"db-errors-BKuXh_fi.mjs","names":[],"sources":["../src/utils/db-errors.ts"],"sourcesContent":["/**\n * Shared detection helpers for database-layer error messages.\n *\n * Different SQL dialects phrase \"table or relation does not exist\" differently:\n *\n * - SQLite / D1: \"no such table: foo\"\n * - PostgreSQL: 'relation \"foo\" does not exist'\n * 'table \"foo\" does not exist'\n * - MySQL (future): \"Table 'db.foo' doesn't exist\"\n *\n * Runtime code paths that short-circuit on missing tables (pre-migration\n * probes, optional feature tables, etc.) should use these helpers rather\n * than hand-rolling string matches per call-site.\n */\n\n/**\n * Extract a lowercase error message from any unknown value, safely.\n */\nfunction messageOf(error: unknown): string {\n\tif (error instanceof Error) return error.message.toLowerCase();\n\tif (typeof error === \"string\") return error.toLowerCase();\n\treturn \"\";\n}\n\n/**\n * Returns true when `error` is a \"column does not exist\" error.\n * Used to handle where filters that reference non-existent field names\n * gracefully (return empty results) instead of propagating a SQL error.\n */\nexport function isMissingColumnError(error: unknown): boolean {\n\tconst message = messageOf(error);\n\tif (!message) return false;\n\n\t// SQLite / D1: \"no such column: foo\"\n\tif (message.includes(\"no such column\")) return true;\n\n\t// PostgreSQL SQLSTATE 42703: 'column \"foo\" does not exist'\n\t// Exclude \"relation\" to avoid false positives on table names containing \"column\"\n\tif (\n\t\tmessage.includes(\"does not exist\") &&\n\t\tmessage.includes(\"column\") &&\n\t\t!message.includes(\"relation\")\n\t)\n\t\treturn true;\n\n\treturn false;\n}\n\n/**\n * Returns true when `error` is a \"table does not exist\" error across the\n * dialects EmDash supports (D1/SQLite and PostgreSQL). Used by runtime\n * probes to treat pre-migration databases as empty without logging a scary\n * warning, while still propagating unrelated errors (permissions, connection\n * loss, syntax issues) to callers.\n */\nexport function isMissingTableError(error: unknown): boolean {\n\tconst message = messageOf(error);\n\tif (!message) return false;\n\n\t// SQLite / D1\n\tif (message.includes(\"no such table\")) return true;\n\n\t// PostgreSQL (and some MySQL variants): \"relation ... does not exist\" /\n\t// \"table ... does not exist\" / \"doesn't exist\".\n\tif (message.includes(\"does not exist\") || message.includes(\"doesn't exist\")) {\n\t\treturn message.includes(\"relation\") || message.includes(\"table\");\n\t}\n\n\treturn false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAS,UAAU,OAAwB;AAC1C,KAAI,iBAAiB,MAAO,QAAO,MAAM,QAAQ,aAAa;AAC9D,KAAI,OAAO,UAAU,SAAU,QAAO,MAAM,aAAa;AACzD,QAAO;;;;;;;AAQR,SAAgB,qBAAqB,OAAyB;CAC7D,MAAM,UAAU,UAAU,MAAM;AAChC,KAAI,CAAC,QAAS,QAAO;AAGrB,KAAI,QAAQ,SAAS,iBAAiB,CAAE,QAAO;AAI/C,KACC,QAAQ,SAAS,iBAAiB,IAClC,QAAQ,SAAS,SAAS,IAC1B,CAAC,QAAQ,SAAS,WAAW,CAE7B,QAAO;AAER,QAAO;;;;;;;;;AAUR,SAAgB,oBAAoB,OAAyB;CAC5D,MAAM,UAAU,UAAU,MAAM;AAChC,KAAI,CAAC,QAAS,QAAO;AAGrB,KAAI,QAAQ,SAAS,gBAAgB,CAAE,QAAO;AAI9C,KAAI,QAAQ,SAAS,iBAAiB,IAAI,QAAQ,SAAS,gBAAgB,CAC1E,QAAO,QAAQ,SAAS,WAAW,IAAI,QAAQ,SAAS,QAAQ;AAGjE,QAAO"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"device-flow-CPXQ_QfW.mjs","names":[],"sources":["../src/api/handlers/device-flow.ts"],"sourcesContent":["/**\n * OAuth Device Flow handlers (RFC 8628).\n *\n * EmDash acts as an OAuth 2.0 authorization server. The CLI requests\n * a device code, displays a URL + user code, and polls for a token.\n * The user opens a browser, logs in, enters the code, and the CLI gets\n * an access + refresh token pair.\n *\n * Uses arctic for code generation and @emdash-cms/auth for token utilities.\n */\n\nimport { clampScopes } from \"@emdash-cms/auth\";\nimport type { RoleLevel } from \"@emdash-cms/auth\";\nimport { generateCodeVerifier } from \"arctic\";\nimport type { Kysely } from \"kysely\";\n\nimport {\n\tgeneratePrefixedToken,\n\thashApiToken,\n\tTOKEN_PREFIXES,\n\tVALID_SCOPES,\n} from \"../../auth/api-tokens.js\";\nimport { withTransaction } from \"../../database/transaction.js\";\nimport type { Database } from \"../../database/types.js\";\nimport type { ApiResult } from \"../types.js\";\nimport { lookupOAuthClient } from \"./oauth-clients.js\";\nimport { lookupUserRoleAndStatus } from \"./oauth-user-lookup.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Device codes expire after 15 minutes */\nconst DEVICE_CODE_TTL_SECONDS = 15 * 60;\n\n/** Default polling interval in seconds */\nconst DEFAULT_INTERVAL = 5;\n\n/** RFC 8628 §3.5: interval increase on slow_down */\nconst SLOW_DOWN_INCREMENT = 5;\n\n/** Maximum slow_down interval cap (seconds) */\nconst MAX_SLOW_DOWN_INTERVAL = 60;\n\n/** Access token TTL: 1 hour */\nconst ACCESS_TOKEN_TTL_SECONDS = 60 * 60;\n\n/** Refresh token TTL: 90 days */\nconst REFRESH_TOKEN_TTL_SECONDS = 90 * 24 * 60 * 60;\n\n/** Default scopes for CLI login */\nconst DEFAULT_SCOPES = [\n\t\"content:read\",\n\t\"content:write\",\n\t\"media:read\",\n\t\"media:write\",\n\t\"schema:read\",\n] as const;\n\n/** Pattern to normalize user codes (strip hyphens) */\nconst HYPHEN_PATTERN = /-/g;\n\n/** Characters for user codes (uppercase, no ambiguous chars like 0/O, 1/I) */\nconst USER_CODE_CHARS = \"ABCDEFGHJKLMNPQRSTUVWXYZ23456789\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DeviceCodeResponse {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\texpires_in: number;\n\tinterval: number;\n}\n\nexport interface TokenResponse {\n\taccess_token: string;\n\trefresh_token: string;\n\ttoken_type: \"Bearer\";\n\texpires_in: number;\n\tscope: string;\n}\n\n// RFC 8628 error codes\nexport type DeviceFlowError =\n\t| \"authorization_pending\"\n\t| \"slow_down\"\n\t| \"expired_token\"\n\t| \"access_denied\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Generate a short human-readable user code (XXXX-XXXX) */\nfunction generateUserCode(): string {\n\tconst bytes = new Uint8Array(8);\n\tcrypto.getRandomValues(bytes);\n\tconst chars = Array.from(bytes, (b) => USER_CODE_CHARS[b % USER_CODE_CHARS.length]).join(\"\");\n\treturn `${chars.slice(0, 4)}-${chars.slice(4, 8)}`;\n}\n\n/** Get an ISO datetime string offset from now */\nfunction expiresAt(seconds: number): string {\n\treturn new Date(Date.now() + seconds * 1000).toISOString();\n}\n\n/** Validate and normalize scopes. Returns validated scope list. */\nfunction normalizeScopes(requested?: string[]): string[] {\n\tif (!requested || requested.length === 0) {\n\t\treturn [...DEFAULT_SCOPES];\n\t}\n\tconst validSet = new Set<string>(VALID_SCOPES);\n\treturn requested.filter((s) => validSet.has(s));\n}\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * POST /oauth/device/code\n *\n * Issue a device code + user code. The CLI displays the user code\n * and tells the user to open the verification URI.\n */\nexport async function handleDeviceCodeRequest(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tclient_id?: string;\n\t\tscope?: string;\n\t},\n\tverificationUri: string,\n): Promise<ApiResult<DeviceCodeResponse>> {\n\ttry {\n\t\t// Note: client_id is accepted but not validated against _emdash_oauth_clients\n\t\t// because the CLI uses a well-known built-in client ID (\"emdash-cli\") that\n\t\t// isn't stored in the DB. Full client_id validation + scope clamping for the\n\t\t// device flow is tracked as a follow-up.\n\n\t\t// Parse and validate scopes\n\t\tconst requestedScopes = input.scope ? input.scope.split(\" \").filter(Boolean) : [];\n\t\tconst scopes = normalizeScopes(requestedScopes);\n\n\t\tif (scopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_SCOPE\", message: \"No valid scopes requested\" },\n\t\t\t};\n\t\t}\n\n\t\tconst deviceCode = generateCodeVerifier();\n\t\tconst userCode = generateUserCode();\n\t\tconst expires = expiresAt(DEVICE_CODE_TTL_SECONDS);\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_device_codes\")\n\t\t\t.values({\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t\tuser_code: userCode,\n\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\tstatus: \"pending\",\n\t\t\t\texpires_at: expires,\n\t\t\t\tinterval: DEFAULT_INTERVAL,\n\t\t\t})\n\t\t\t.execute();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tdevice_code: deviceCode,\n\t\t\t\tuser_code: userCode,\n\t\t\t\tverification_uri: verificationUri,\n\t\t\t\texpires_in: DEVICE_CODE_TTL_SECONDS,\n\t\t\t\tinterval: DEFAULT_INTERVAL,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"DEVICE_CODE_ERROR\",\n\t\t\t\tmessage: \"Failed to create device code\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/device/token\n *\n * CLI polls this endpoint with the device_code. Returns:\n * - 200 with tokens if authorized\n * - 400 with error \"authorization_pending\" while waiting\n * - 400 with error \"slow_down\" if polling too fast\n * - 400 with error \"expired_token\" if the code expired\n * - 400 with error \"access_denied\" if the user denied\n */\nexport async function handleDeviceTokenExchange(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tdevice_code: string;\n\t\tgrant_type: string;\n\t},\n): Promise<\n\tApiResult<TokenResponse> & { deviceFlowError?: DeviceFlowError; deviceFlowInterval?: number }\n> {\n\ttry {\n\t\t// Validate grant_type\n\t\tif (input.grant_type !== \"urn:ietf:params:oauth:grant-type:device_code\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"UNSUPPORTED_GRANT_TYPE\", message: \"Invalid grant_type\" },\n\t\t\t};\n\t\t}\n\n\t\t// Look up the device code\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_device_codes\")\n\t\t\t.selectAll()\n\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid device code\" },\n\t\t\t};\n\t\t}\n\n\t\tconst now = new Date();\n\n\t\t// Check expiry\n\t\tif (new Date(row.expires_at) < now) {\n\t\t\t// Clean up expired code\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tdeviceFlowError: \"expired_token\",\n\t\t\t\terror: { code: \"expired_token\", message: \"The device code has expired\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check status\n\t\tif (row.status === \"denied\") {\n\t\t\t// Clean up denied code\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tdeviceFlowError: \"access_denied\",\n\t\t\t\terror: { code: \"access_denied\", message: \"The user denied the request\" },\n\t\t\t};\n\t\t}\n\n\t\tif (row.status === \"pending\") {\n\t\t\t// RFC 8628 §3.5: slow_down enforcement during polling phase.\n\t\t\t// Only applies while waiting for authorization — once authorized,\n\t\t\t// the final exchange proceeds without throttling.\n\t\t\tif (row.last_polled_at) {\n\t\t\t\tconst lastPolled = new Date(row.last_polled_at);\n\t\t\t\tconst elapsedSeconds = (now.getTime() - lastPolled.getTime()) / 1000;\n\n\t\t\t\tif (elapsedSeconds < row.interval) {\n\t\t\t\t\t// Too fast — increase interval by 5s per RFC 8628 §3.5, capped at 60s\n\t\t\t\t\tconst newInterval = Math.min(row.interval + SLOW_DOWN_INCREMENT, MAX_SLOW_DOWN_INTERVAL);\n\t\t\t\t\tawait db\n\t\t\t\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t\t\t\t.set({\n\t\t\t\t\t\t\tinterval: newInterval,\n\t\t\t\t\t\t\tlast_polled_at: now.toISOString(),\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t\t\t.execute();\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\tdeviceFlowError: \"slow_down\",\n\t\t\t\t\t\tdeviceFlowInterval: newInterval,\n\t\t\t\t\t\terror: { code: \"slow_down\", message: \"Polling too fast\" },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update last_polled_at for future slow_down checks\n\t\t\tawait db\n\t\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t\t.set({ last_polled_at: now.toISOString() })\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tdeviceFlowError: \"authorization_pending\",\n\t\t\t\terror: { code: \"authorization_pending\", message: \"Authorization pending\" },\n\t\t\t};\n\t\t}\n\n\t\tif (row.status !== \"authorized\" || !row.user_id) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid device code state\" },\n\t\t\t};\n\t\t}\n\n\t\t// Generate tokens before consuming the device code so that if\n\t\t// generation fails, the code is still available for retry.\n\t\tconst accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);\n\t\tconst accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);\n\t\tconst refreshToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_REFRESH);\n\t\tconst refreshExpires = expiresAt(REFRESH_TOKEN_TTL_SECONDS);\n\n\t\t// Atomically consume the device code and create tokens in a single\n\t\t// transaction. DELETE...RETURNING prevents TOCTOU: two concurrent\n\t\t// requests race on the DELETE, only one gets a row back. Wrapping\n\t\t// in a transaction ensures the code isn't consumed if token storage fails.\n\t\tconst result = await withTransaction(db, async (trx) => {\n\t\t\tconst consumed = await trx\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", input.device_code)\n\t\t\t\t.where(\"status\", \"=\", \"authorized\")\n\t\t\t\t.returningAll()\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tif (!consumed) return null;\n\n\t\t\tif (!consumed.user_id) return null;\n\n\t\t\tconst scopes = JSON.parse(consumed.scopes) as string[];\n\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: accessToken.hash,\n\t\t\t\t\ttoken_type: \"access\",\n\t\t\t\t\tuser_id: consumed.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"cli\",\n\t\t\t\t\texpires_at: accessExpires,\n\t\t\t\t\trefresh_token_hash: refreshToken.hash,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: refreshToken.hash,\n\t\t\t\t\ttoken_type: \"refresh\",\n\t\t\t\t\tuser_id: consumed.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"cli\",\n\t\t\t\t\texpires_at: refreshExpires,\n\t\t\t\t\trefresh_token_hash: null,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\treturn { scopes };\n\t\t});\n\n\t\tif (!result) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Device code already consumed\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\taccess_token: accessToken.raw,\n\t\t\t\trefresh_token: refreshToken.raw,\n\t\t\t\ttoken_type: \"Bearer\",\n\t\t\t\texpires_in: ACCESS_TOKEN_TTL_SECONDS,\n\t\t\t\tscope: result.scopes.join(\" \"),\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_EXCHANGE_ERROR\",\n\t\t\t\tmessage: \"Failed to exchange device code\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/device/authorize\n *\n * The user submits the user_code after logging in via the browser.\n * This authorizes the device code, allowing the CLI to exchange it for tokens.\n *\n * Scopes are clamped to the user's role at this point. The stored scopes\n * are replaced with the intersection of requested scopes and the scopes\n * the user's role permits. This prevents scope escalation.\n */\nexport async function handleDeviceAuthorize(\n\tdb: Kysely<Database>,\n\tuserId: string,\n\tuserRole: RoleLevel,\n\tinput: {\n\t\tuser_code: string;\n\t\taction?: \"approve\" | \"deny\";\n\t},\n): Promise<ApiResult<{ authorized: boolean }>> {\n\ttry {\n\t\t// Normalize user code (strip hyphens, uppercase)\n\t\tconst normalizedCode = input.user_code.replace(HYPHEN_PATTERN, \"\").toUpperCase();\n\n\t\t// Look up the device code by user_code\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_device_codes\")\n\t\t\t.selectAll()\n\t\t\t.where(\"status\", \"=\", \"pending\")\n\t\t\t.execute();\n\n\t\t// Find the matching code (strip hyphens for comparison)\n\t\tconst match = row.find(\n\t\t\t(r) => r.user_code.replace(HYPHEN_PATTERN, \"\").toUpperCase() === normalizedCode,\n\t\t);\n\n\t\tif (!match) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_CODE\", message: \"Invalid or expired code\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check expiry\n\t\tif (new Date(match.expires_at) < new Date()) {\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_device_codes\")\n\t\t\t\t.where(\"device_code\", \"=\", match.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"EXPIRED_CODE\", message: \"This code has expired\" },\n\t\t\t};\n\t\t}\n\n\t\tconst action = input.action ?? \"approve\";\n\n\t\tif (action === \"deny\") {\n\t\t\tawait db\n\t\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t\t.set({ status: \"denied\" })\n\t\t\t\t.where(\"device_code\", \"=\", match.device_code)\n\t\t\t\t.execute();\n\n\t\t\treturn { success: true, data: { authorized: false } };\n\t\t}\n\n\t\t// Clamp requested scopes to those the user's role permits.\n\t\t// effective_scopes = requested_scopes ∩ scopesForRole(user.role)\n\t\tconst requestedScopes = JSON.parse(match.scopes) as string[];\n\t\tconst effectiveScopes = clampScopes(requestedScopes, userRole);\n\n\t\tif (effectiveScopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INSUFFICIENT_ROLE\",\n\t\t\t\t\tmessage: \"Your role does not permit any of the requested scopes\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Approve: set user_id, status, and clamped scopes\n\t\tawait db\n\t\t\t.updateTable(\"_emdash_device_codes\")\n\t\t\t.set({\n\t\t\t\tstatus: \"authorized\",\n\t\t\t\tuser_id: userId,\n\t\t\t\tscopes: JSON.stringify(effectiveScopes),\n\t\t\t})\n\t\t\t.where(\"device_code\", \"=\", match.device_code)\n\t\t\t.execute();\n\n\t\treturn { success: true, data: { authorized: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"AUTHORIZE_ERROR\",\n\t\t\t\tmessage: \"Failed to authorize device\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/token/refresh\n *\n * Exchange a refresh token for a new access token.\n * The refresh token itself is not rotated (per spec: optional rotation).\n */\nexport async function handleTokenRefresh(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\trefresh_token: string;\n\t\tgrant_type: string;\n\t},\n): Promise<ApiResult<TokenResponse>> {\n\ttry {\n\t\tif (input.grant_type !== \"refresh_token\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"UNSUPPORTED_GRANT_TYPE\", message: \"Invalid grant_type\" },\n\t\t\t};\n\t\t}\n\n\t\tif (!input.refresh_token.startsWith(TOKEN_PREFIXES.OAUTH_REFRESH)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid refresh token format\" },\n\t\t\t};\n\t\t}\n\n\t\tconst refreshHash = hashApiToken(input.refresh_token);\n\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_oauth_tokens\")\n\t\t\t.selectAll()\n\t\t\t.where(\"token_hash\", \"=\", refreshHash)\n\t\t\t.where(\"token_type\", \"=\", \"refresh\")\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Invalid refresh token\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check expiry\n\t\tif (new Date(row.expires_at) < new Date()) {\n\t\t\t// Clean up expired refresh token and its access tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", refreshHash).execute();\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_oauth_tokens\")\n\t\t\t\t.where(\"refresh_token_hash\", \"=\", refreshHash)\n\t\t\t\t.execute();\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"Refresh token expired\" },\n\t\t\t};\n\t\t}\n\n\t\t// SEC-42: Revalidate user role before issuing new access token.\n\t\t// SEC-43: Reject refresh if user is disabled or deleted.\n\t\tconst userInfo = await lookupUserRoleAndStatus(db, row.user_id);\n\t\tif (!userInfo) {\n\t\t\t// User no longer exists — revoke all their tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"user_id\", \"=\", row.user_id).execute();\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"User not found\" },\n\t\t\t};\n\t\t}\n\n\t\tif (userInfo.disabled) {\n\t\t\t// User is disabled — revoke all their tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"user_id\", \"=\", row.user_id).execute();\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_GRANT\", message: \"User account is disabled\" },\n\t\t\t};\n\t\t}\n\n\t\t// Revalidate stored scopes against the user's current role.\n\t\t// A demoted user's refresh token may carry stale elevated scopes.\n\t\tconst storedScopes = JSON.parse(row.scopes) as string[];\n\t\tlet scopes = clampScopes(storedScopes, userInfo.role);\n\n\t\t// SEC-41: Intersect with the client's registered scopes (if any).\n\t\t// Same check as the approval path — a client registered with limited\n\t\t// scopes should never receive elevated scopes on refresh, even if the\n\t\t// user's role would allow them.\n\t\tif (row.client_id) {\n\t\t\tconst client = await lookupOAuthClient(db, row.client_id);\n\t\t\tif (client?.scopes?.length) {\n\t\t\t\tscopes = scopes.filter((s: string) => client.scopes!.includes(s));\n\t\t\t}\n\t\t}\n\n\t\tif (scopes.length === 0) {\n\t\t\t// User's role no longer supports any of the token's scopes — revoke\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", refreshHash).execute();\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"_emdash_oauth_tokens\")\n\t\t\t\t.where(\"refresh_token_hash\", \"=\", refreshHash)\n\t\t\t\t.execute();\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INVALID_GRANT\",\n\t\t\t\t\tmessage: \"User role no longer supports any of the token's scopes\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Delete old access tokens for this refresh token\n\t\tawait db\n\t\t\t.deleteFrom(\"_emdash_oauth_tokens\")\n\t\t\t.where(\"refresh_token_hash\", \"=\", refreshHash)\n\t\t\t.where(\"token_type\", \"=\", \"access\")\n\t\t\t.execute();\n\n\t\t// Generate new access token\n\t\tconst accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);\n\t\tconst accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t.values({\n\t\t\t\ttoken_hash: accessToken.hash,\n\t\t\t\ttoken_type: \"access\",\n\t\t\t\tuser_id: row.user_id,\n\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\tclient_type: row.client_type,\n\t\t\t\texpires_at: accessExpires,\n\t\t\t\trefresh_token_hash: refreshHash,\n\t\t\t})\n\t\t\t.execute();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\taccess_token: accessToken.raw,\n\t\t\t\trefresh_token: input.refresh_token, // Return same refresh token\n\t\t\t\ttoken_type: \"Bearer\",\n\t\t\t\texpires_in: ACCESS_TOKEN_TTL_SECONDS,\n\t\t\t\tscope: scopes.join(\" \"),\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_REFRESH_ERROR\",\n\t\t\t\tmessage: \"Failed to refresh token\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * POST /oauth/token/revoke\n *\n * Revoke an access or refresh token. If a refresh token is revoked,\n * also revoke all associated access tokens.\n *\n * Per RFC 7009, this endpoint always returns 200 (even for invalid tokens).\n */\nexport async function handleTokenRevoke(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\ttoken: string;\n\t},\n): Promise<ApiResult<{ revoked: boolean }>> {\n\ttry {\n\t\tconst hash = hashApiToken(input.token);\n\n\t\t// Look up the token\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_oauth_tokens\")\n\t\t\t.select([\"token_hash\", \"token_type\", \"refresh_token_hash\"])\n\t\t\t.where(\"token_hash\", \"=\", hash)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\t// Per RFC 7009: always 200, even for invalid tokens\n\t\t\treturn { success: true, data: { revoked: true } };\n\t\t}\n\n\t\tif (row.token_type === \"refresh\") {\n\t\t\t// Revoke refresh token and all its access tokens\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"refresh_token_hash\", \"=\", hash).execute();\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", hash).execute();\n\t\t} else {\n\t\t\t// Revoke just the access token\n\t\t\tawait db.deleteFrom(\"_emdash_oauth_tokens\").where(\"token_hash\", \"=\", hash).execute();\n\t\t}\n\n\t\treturn { success: true, data: { revoked: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_REVOKE_ERROR\",\n\t\t\t\tmessage: \"Failed to revoke token\",\n\t\t\t},\n\t\t};\n\t}\n}\n"],"mappings":"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"}